Amounts are integer strings in the smallest unit
Everyamount you send to the API is an integer number of the asset’s smallest unit (stroops
for XLM), written as a string: "100000000" is 10 XLM. Convert from what the user types with
raw = human × 10^decimals, using integer or decimal arithmetic, never floats.
Every classic Stellar asset has 7 decimals. Tokens issued directly on Soroban choose their own:
the Soroswap token list has some with 18. Read
decimals from
GET /asset-list?name=SOROSWAP or from the token contract’s decimals() instead of assuming 7.
"0"and negative amounts return 400amount must be a positive integer number of stroops.- An amount with a decimal point (
"10.5") currently returns 500, not 400. Never send one. - A JSON number is accepted, but a string is the only form that survives values above 2^53 in JavaScript.
Assets are contract addresses
The API identifies every asset, classic or Soroban, by its contract address (C...). The
CODE:ISSUER form and the issuer’s G... address are not accepted:
"assetIn": "USDC:GA5Z..."returns 400Invalid Stellar address."assetIn": "GA5Z..."(the issuer) returns 503Routing unavailable, which reads like an outage but is a wrong input. Check your addresses before you retry it.
CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC there. Never reuse mainnet addresses
with ?network=testnet.
Accounts are a separate question. from and to are the wallets that sign and receive: a
classic account (G...) or a contract account such as a smart wallet (C...). See
Smart wallets below.
A missing trustline
A classic account cannot receive a classic asset (anything but XLM) until it has a trustline for it. Tokens issued on Soroban need no trustline. When the receiving account lacks one:POST /quote/buildreturns 428 withaction: "CREATE_TRUSTLINE".actionData.xdris achangeTrusttransaction foractionData.signer, withassetCodeandassetIssuer. Sign it, submit it, then call/quote/buildagain with the same quote (quote again if it is no longer fresh).POST /swapreturns 200 with the sameactioninstead of an error, so one call carries the prerequisite. WithinlinePrerequisites: trueit also returns the swap XDR, with the trustline transaction in aprerequisitesarray to submit first.- On a mixed-venue route the missing trustline can be for an intermediate asset, not the output
one. Read
assetCodefrom the response instead of assuming it isassetOut. - A user with no XLM to pay for the trustline can still receive the asset in one swap through Gasless Trustline, on SDEX routes, with a sponsor account you control.
The XLM reserve
Each account keeps a minimum XLM balance it cannot spend: 0.5 XLM per base reserve, two for the account itself plus one per trustline, offer or other subentry. Opening a trustline locks another 0.5 XLM. A user who swaps “all” their XLM fails: size XLM swaps from what is spendable.GET /balances/{address} returns it as available / availableRaw, already net of the
reserve.
A built transaction expires
The XDR from/quote/build and /swap has a time bound. Past it, the network rejects the
transaction with tx_too_late.
The quote inside it ages faster than that: prices move and the minimum output was set at quote
time. If the user has not signed within the window, quote and build again. Never re-sign or
resubmit an expired XDR, and never edit its time bounds.
Simulation: the API has done it
A Soroban transaction must be simulated before it is signed, to get its footprint, resource fee and authorization entries./quote/build already did that and sized the result for the route.
Sign it and send it as is. Do not run prepareTransaction or another simulation on it:
simulating again drops the margins the API adds for Aquarius and SushiSwap routes, and the swap
can fail on chain.
The one exception is a swap XDR that comes back with simulated: false from /swap with
inlinePrerequisites: true. It could not be simulated because its prerequisites did not exist
yet: submit them first, then simulate and assemble it yourself.
A simulation that fails while building returns 400 Simulation Failed with a contract
errorCode, for example 10 (TokenError.InsufficientBalance): the account cannot cover the
amount. Fix the account state and build again.
Signing: envelope, not auth entries
The XDR the API builds uses thefrom account as transaction source, and its Soroban
authorization is source-account credentials. A classic wallet (Freighter, Stellar Wallets Kit, a
keypair on your server) signs the transaction envelope once and that covers everything. You do
not sign authorization entries separately. The one exception to a single signer is
Gasless Trustline, where the sponsor signs too.
Smart wallets and passkeys
A contract account (C...), such as a passkey smart wallet, cannot be the source of a
transaction, so it cannot use /quote/build: a C... in from returns 400
invalid version byte. expected 48, got 16. Smart wallets take the
contract path:
- Quote with
/quote, leavingsdexout ofprotocols(the classic DEX cannot pay a contract;/swapdrops it for you and returns 400 if it was the only venue). - Build the aggregator
swap_exact_in/swap_exact_outinvocation yourself fromrawTrade.distribution. - Simulate it with a fee-paying
G...account as source, have the smart wallet sign the authorization entry for its own address, then assemble and submit.
/quote/build, to must equal from or
be left out.
Testnet is a different world
?network=testnet uses the same base URL and key. Testnet quotes come only from Soroswap pools,
carry no fee and use testnet contract addresses. Fund an account with Friendbot and get test
tokens at faucet.soroswap.finance. Leaving out ?network
quotes mainnet, so always send it.
Responses you might misread
- A successful
POSTreturns 201, not 200. Treat any 2xx as success. - A missing or invalid key returns 403
Forbidden resource, never 401. See Errors.