Quoting Swaps
These endpoints let you build a complete swap form with dynamic token pickers, "Send Max" button, amount limit validation and address parsing. You usually use them before creating the swap, to constrain the tokens and amounts the user is allowed to swap. For the bare-minimum create and execute flow, see Creating & Executing a Swap.
| Endpoint | Purpose |
|---|---|
GET /getSupportedTokens | Populate the initial source/destination token pickers. |
GET /getSwapCounterTokens | After one side is picked, show only tokens that actually route with it. |
GET /getSpendableBalance | Maximum spendable balance (considering network fees) - i.e. a "Send Max" button. |
GET /getSwapLimits | Min / max bounds for the amount field. |
GET /parseAddress | Validate and classify the destination address; can also pre-pick the swap type. |
Together, these let you build a swap form that never lets the user assemble an impossible quote.
Supported tokens list, swap counter tokens and swap limits might change over time as LPs join/leave the network, refresh them as necessary (i.e. once every 5 minutes)
List supported tokens
Call GET /getSupportedTokens with side=INPUT (source picker) or side=OUTPUT (destination picker), to get the tokens available as a swap source or destination.
curl "https://mainnet.swaps-api.atomiq.exchange/getSupportedTokens?side=INPUT"
The response is an array of ApiToken objects:
{
"id": "STARKNET-STRK", // use this exact string elsewhere in the API
"chainId": "STARKNET",
"ticker": "STRK",
"name": "Starknet Token",
"decimals": 18,
"address": "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"
}
The id field of the token is its canonical identifier (in the form of <chainId>-<ticker>), which is used throughout the API.
Find compatible counter-tokens
Once the user has picked one side (say BITCOIN-BTC as input), call GET /getSwapCounterTokens to discover which tokens actually have liquidity as the other side:
curl "https://mainnet.swaps-api.atomiq.exchange/getSwapCounterTokens?token=BITCOIN-BTC&side=INPUT"
side describes how the caller's token is being used. side=INPUT means "the user is spending BTC, show me what they can receive." side=OUTPUT means "the user wants to receive X, show me what they can pay with."
The response is the same ApiToken[] shape as GET /getSupportedTokens. Using this list for the destination picker avoids showing combinations the LP network can't actually quote.
Call GET /getSupportedTokens once at startup to populate the source and destination token picker, then call GET /getSwapCounterTokens when the user selects either the source or destination token to narrow down the token picker on the other side. You don't need to re-fetch when the user changes the amount.
Estimate spendable balance
GET /getSpendableBalance returns the balance a wallet can actually spend on a swap after accounting for chain fees. Usable as an upper limit on the swap source amount or the amount you want behind a "Send Max" button.
curl "https://mainnet.swaps-api.atomiq.exchange/getSpendableBalance?wallet=0x0123…&token=STARKNET-STRK"
Lightning balances are not supported by this endpoint. For LIGHTNING-BTC, the API will throw. Lightning is an off-chain system with no public information about balances.
Query parameters (see GetSpendableBalanceInput for the authoritative schema):
| Query param | Required | Notes |
|---|---|---|
wallet | ✓ | Address to inspect. |
token | ✓ | Token identifier (e.g. BITCOIN-BTC, STARKNET-STRK). |
feeRate | optional | Manual fee-rate override. Chain-specific format defined by SpendableBalanceFeeRate — see Fee-rate format. |
feeMultiplier | optional, number | Multiplier applied to the auto-fetched fee rate. Mutually exclusive with feeRate. |
| Bitcoin-only | ||
gasDrop | optional, boolean | For BITCOIN-BTC-source swaps: include gas-drop transaction overhead in the estimate. |
targetChain | optional | Destination chain identifier (STARKNET, SOLANA, ...). Only for BITCOIN-BTC balances, changes the estimate because different targets may use different swap protocols, and have different Bitcoin network fees. |
minBitcoinFeeRate | optional, number | Floor for the fetched Bitcoin fee rate (sats/vB). Only for BITCOIN-BTC |
Response shape (GetSpendableBalanceOutput):
{
"balance": { "amount": "0.00243", "rawAmount": "243000", "decimals": 8, "symbol": "BTC", "chain": "BITCOIN" },
"feeRate": 3.5 // only when estimating BTC on Bitcoin
}
balance follows the ApiAmount shape used throughout the API.
Fee-rate format
feeRate is chain-specific. The authoritative format is the SpendableBalanceFeeRate schema, but in brief:
| Chain | Format | Example |
|---|---|---|
| Bitcoin | Positive number, sats/vB | 3.5 |
| Solana | <microLamports/CU>[;<base fee in lamports>] | "10000;500" |
| Starknet | <l1GasCost>,<l2GasCost>,<l1DataGasCost>;v3 (in Fri per gas) | "1000000000,1000000000,0;v3" |
| EVM (Botanix, Citrea, Alpen, Goat) | <baseFeePerGas>,<priorityFeePerGas> (in Wei per gas) | "30000000000,2000000000" |
Re-run this call whenever the target chain or gasDrop toggle changes — both affect the estimate, then check periodically as fee rate does change over time. You don't need to re-run it every keystroke in the amount field.
Get swap limits
Once both tokens are chosen, call GET /getSwapLimits to fetch the current min/max for the pair:
curl "https://mainnet.swaps-api.atomiq.exchange/getSwapLimits?srcToken=BITCOIN-BTC&dstToken=STARKNET-STRK"
Response (GetSwapLimitsOutput) contains two GetSwapLimitsSide entries, each carrying ApiAmount min / max values:
{
"input": { "min": { /* ApiAmount */ }, "max": { /* ApiAmount */ } },
"output": { "min": { /* ApiAmount */ }, "max": { /* ApiAmount */ } }
}
Both input and output limits are returned so you can enforce the constraint on whichever side the user is editing — relevant when amountType is EXACT_IN vs EXACT_OUT (see ExactAmountType).
max is optional and may be absent when the LP network does not enforce a hard ceiling.
Limits can change as LPs join/leave the network, re-fetch the swap limits as needed (e.g. every 5 minutes)
Parse the destination address
GET /parseAddress accepts anything the user might reasonably paste into an address field:
- Bitcoin on-chain addresses, also BIP-21 bitcoin payment URIs -
bitcoin:... - BOLT11 invoices, also lightning deeplinks -
lightning:... - LNURL-pay (LUD-6), LNURL-withdraw (LUD-3) links and lightning static internet identifiers (LUD-16)
- Smart-chain addresses (Starknet, Solana, EVM)
Returns the parsed type plus structured data (see ParseAddressOutput for the full response shape).
Be sure to URL-encode the address in the GET request parameter, as bitcoin/lightning payment URIs might contain non-URL-safe characters.
curl "https://mainnet.swaps-api.atomiq.exchange/parseAddress?address=lnbc510400n1p4qm..."
{
"address": "lnbc510400n1p4qm...",
"type": "LIGHTNING",
"amount": { "amount": "0.00051040", "rawAmount": "51040", "decimals": 8, "symbol": "BTC", "chain": "LIGHTNING" }
}
type is one of BITCOIN, LIGHTNING, LNURL, SOLANA, STARKNET, CITREA, … (or other supported chains).
For BOLT11 invoices and Bitcoin URIs with amount parameters, the response may include an ApiAmount amount field (and min / max for LNURL). Use these to pre-populate the amount field if the user pasted a request that already carries one.
Two ways to use the result:
- Validate the recipient field. Show a chip ("Bitcoin address" / "Lightning invoice" / …) once
typeresolves; surface an error if the call throws. - Auto-pick the destination token / swap type. The returned
typetells you which destination chain the user actually pasted, so you can switch the destination picker to e.g.BITCOIN-BTCwhen they paste an on-chain address, orLIGHTNING-BTCwhen they paste a BOLT11 invoice / LNURL link.
When type is LNURL, the response includes an ApiLNURL object that's either an ApiLNURLPay or ApiLNURLWithdraw variant — see Bitcoin & Lightning → LNURL for how to use the resolved payload.
Putting it together
A minimal quote form flow:
// on mount
const inputTokens = await get("/getSupportedTokens", { side: "INPUT" });
populateSourcePicker(inputTokens);
// on source-token change
const outputTokens = await get("/getSwapCounterTokens", {
token: srcToken.id,
side: "INPUT",
});
populateDestinationPicker(outputTokens);
// on destination-address paste
const parsed = await get("/parseAddress", { address: pastedRecipient });
pickDestinationTokenFromType(parsed.type);
// on "Max" click
const { balance } = await get("/getSpendableBalance", {
wallet: srcAddress,
token: srcToken.id,
targetChain: dstToken.chainId,
gasDrop: userWantsGasDrop,
});
setAmountField(balance.rawAmount);
// on both selected (or on amount focus)
const limits = await get("/getSwapLimits", {
srcToken: srcToken.id,
dstToken: dstToken.id,
});
setAmountBounds(limits.input.min.rawAmount, limits.input.max?.rawAmount);
Next Steps
Creating & Executing
Once the form passes validation, create the swap and run the polling/signing loop until it reaches a terminal state.
Bitcoin & Lightning
Handle PSBT details, Lightning invoices, LNURL flows, preimage reveal, and gas-drop behavior for Bitcoin-specific routes.