Wallet Swap

Convert BRLA to USDC (or USDC to BRLA) from your Hodle wallet and deliver the result to any address on Polygon or Base.

POST /api/wallet/swap

Converts BRLA to USDC, or USDC to BRLA, out of the API key user's wallet. The converted asset is delivered on-chain to address (your own wallet when omitted) on destinationNetwork. Gas is paid by Hodle.

Enabled per account. This conversion route is turned on by Hodle for your account. Without it, BRLA ↔ USDC requests keep the account's standard swap behaviour. Talk to the Hodle team to enable it.

The swap runs in two legs:

  1. Funding (synchronous). One on-chain operation out of your wallet, signed with your walletPin. It moves the amount into the conversion account and pays Hodle's fee in the same operation, so the fee is never charged without the swap being funded. The request returns once this leg is mined, usually in a few seconds.
  2. Conversion (asynchronous). Once the deposit is credited, Hodle converts it and delivers the result to address. That usually takes 20 to 60 seconds. Poll GET /api/wallet/swap/:swapId until status is COMPLETED.

The funds are taken from Polygon first, then Base: whichever network holds the whole amount in your wallet.

Self-custodial signing. As with Wallet Transfer, every swap must carry the wallet's walletPin and its protectedSymmetricKey. The server unlocks the key in memory to sign the funding leg, then discards it.

Fees

The fee charged on a swap is returned in the response's fee object and is taken from the origin asset at funding. The conversion cost (market rate and the network cost of the delivery) is deducted from the delivered amount. Part of that cost is fixed, so very small swaps (around 1 USDC or a few BRLA) lose a large share of their value.

GET /api/wallet/swap/quote

Prices a swap before you submit it: what is charged and what would be delivered right now. It reads only, needs no PIN and moves nothing. The conversion is priced again when the swap runs, so the final amount can differ slightly.

Query paramRequiredDescription
fromAssetyesBRLA or USDC.
toAssetyesUSDC or BRLA.
amountyesGross amount in units of fromAsset, at most 10000.
destinationNetworknopolygon (default) or base.
subAccountIdnoQuote for a subaccount's wallet.
curl --url "https://api.hodle.com.br/api/wallet/swap/quote?fromAsset=USDC&toAsset=BRLA&amount=100&destinationNetwork=base" \
  --header "Authorization: Bearer $API_KEY"
{
  "success": true,
  "fromAsset": "USDC",
  "toAsset": "BRLA",
  "amount": "100",
  "destinationNetwork": "base",
  "fee": { "bps": 70, "amount": "0.7", "asset": "USDC" },
  "conversion": {
    "amountIn": "99.30",
    "rate": 4.986155,
    "cost": "2.185784",
    "costAsset": "USDC"
  },
  "amountOut": "484.226000",
  "amountOutAsset": "BRLA"
}
  • fee: what is taken from amount at funding, in units of fee.asset.
  • conversion.amountIn: what is converted after the fee.
  • conversion.rate: base rate in BRLA per USDC, for both directions.
  • conversion.cost: the conversion's own costs, in costAsset, already discounted from amountOut.
  • amountOut: what address would receive, in amountOutAsset.

Errors: 400 INVALID_AMOUNT, 400 UNSUPPORTED_PAIR, 403 NOT_ENABLED (the route is not enabled for the account), 503 STABLE_ACCOUNT_UNAVAILABLE, 502 QUOTE_UNAVAILABLE.

Request

FieldTypeRequiredDescription
fromAsset"BRLA" | "USDC"yesAsset debited from your wallet.
toAsset"USDC" | "BRLA"yesAsset delivered.
amountstringyesGross amount in units of fromAsset (e.g. "100", "12.5"), at most 10000. The fee comes out of it.
walletPinstringyesWallet PIN.
protectedSymmetricKeystringyesThe wallet's protected symmetric key.
addressstringnoEVM address that receives toAsset. Defaults to your own wallet.
destinationNetwork"polygon" | "base"noNetwork where toAsset is delivered. Defaults to polygon.
subAccountIdstringnoSwap out of a subaccount's wallet instead of the main account's.
referencestringnoYour own reference, stored with the swap.
curl --request POST \
  --url https://api.hodle.com.br/api/wallet/swap \
  --header "Authorization: Bearer $API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "fromAsset": "BRLA",
    "toAsset": "USDC",
    "amount": "100",
    "walletPin": "424242",
    "protectedSymmetricKey": "AoofiKHyVRLvdrknnXzoIh1Gd1YTwLaOBn4ibm103a4d...",
    "address": "0x9b8c4d5e6f7081234567890abcdef0123456789a",
    "destinationNetwork": "polygon",
    "reference": "order-1029"
  }'
const res = await fetch('https://api.hodle.com.br/api/wallet/swap', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    fromAsset: 'BRLA',
    toAsset: 'USDC',
    amount: '100',
    walletPin: '424242',
    protectedSymmetricKey: 'AoofiKHyVRLvdrknnXzoIh1Gd1YTwLaOBn4ibm103a4d...',
    address: '0x9b8c4d5e6f7081234567890abcdef0123456789a',
    destinationNetwork: 'polygon',
  }),
})
const swap = await res.json()
import os, requests

res = requests.post(
    "https://api.hodle.com.br/api/wallet/swap",
    headers={"Authorization": f"Bearer {os.environ['HODLE_API_KEY']}"},
    json={
        "fromAsset": "BRLA",
        "toAsset": "USDC",
        "amount": "100",
        "walletPin": "424242",
        "protectedSymmetricKey": "AoofiKHyVRLvdrknnXzoIh1Gd1YTwLaOBn4ibm103a4d...",
        "address": "0x9b8c4d5e6f7081234567890abcdef0123456789a",
        "destinationNetwork": "polygon",
    },
)
swap = res.json()

Response

{
  "success": true,
  "swapId": "a75fb23f-195c-4c23-aeb6-a9543dfd64bd",
  "status": "RUNNING",
  "destinationAddress": "0x9b8C4D5E6F7081234567890abCDef0123456789a",
  "destinationNetwork": "polygon",
  "fee": { "bps": 70, "amount": "700000000000000000", "asset": "BRLA" },
  "legs": [
    {
      "index": 0,
      "fromAsset": "BRLA",
      "toAsset": "BRLA",
      "status": "FULFILLED",
      "originTxHash": "0xa2dfcb8ae6c9ad71ed8ce088f094d3ec62834ee927d7a2587de8fa0325e9c538",
      "destinationTxHash": null
    },
    {
      "index": 1,
      "fromAsset": "BRLA",
      "toAsset": "USDC",
      "status": "PENDING",
      "originTxHash": null,
      "destinationTxHash": null
    }
  ]
}

fee.amount is in the smallest unit of fee.asset (18 decimals for BRLA, 6 for USDC). legs[0].originTxHash is the funding transaction.

GET /api/wallet/swap/:swapId

curl --url https://api.hodle.com.br/api/wallet/swap/a75fb23f-195c-4c23-aeb6-a9543dfd64bd \
  --header "Authorization: Bearer $API_KEY"

When the conversion is delivered, status is COMPLETED and legs[1].destinationTxHash is the delivery transaction on destinationNetwork.

statusMeaning
RUNNINGFunded; the conversion is in progress. Keep polling.
COMPLETEDDelivered to address.
FAILEDThe funding leg did not go through. Nothing left your wallet.
STRANDEDFunded, but the conversion did not complete. The amount is held and reviewed by Hodle; contact support with the swapId.

Errors

Every refusal below happens before anything is signed: nothing leaves your wallet.

HTTPcodeWhen
400INSUFFICIENT_BALANCENeither Polygon nor Base holds the whole amount of fromAsset in your wallet.
400INVALID_ADDRESSaddress is not a valid EVM address.
400—amount is not a positive decimal, or is above 10000. The body carries only error.
400AMOUNT_TOO_SMALLNothing is left to convert once the fee is taken.
503STABLE_ACCOUNT_UNAVAILABLEThe conversion account of your account is not provisioned or not reachable right now.
503NO_DEPOSIT_WALLETThe conversion account has no deposit wallet for fromAsset on a supported network.
503HOUSE_WALLET_UNAVAILABLETemporarily unavailable. Retry later.
502FUNDING_LEG_FAILEDThe funding operation was refused on-chain (for example a wrong PIN). The swap is FAILED and nothing moved.

Sandbox keys keep the sandbox swap behaviour; this route is available in production only.