LCX Liberty. American DeFi. Your keys. Your assets. Your control.
Quotes & execution
The two shapes of the integration, crossing chains, why a quote carries a block number, and how the split is picked.
Two shapes
One call. POST /v1/swap returns the quote and the signable transaction together. Use it when the user has already decided and you want the fewest moving parts.
Two calls. POST /v1/quote to price, then POST /v1/build with the quoteId when the user confirms. Use it when a price is shown and refreshed before anyone commits. A price ticker should not be minting calldata.
Crossing chains
Add toChainId to a quote and the same endpoint prices a route that ends on a different network. Add recipient too: the output is delivered to an address on the destination chain, and the planner needs to know whose it is before it can price the delivery.
curl -s -X POST https://swap-api.lcx.com/v1/quote \
-H "Authorization: Bearer $LCX_API_KEY" \
-H "content-type: application/json" \
-d '{
"chainId": 1, "toChainId": 42161,
"tokenIn": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"tokenOut": "0x0000000000000000000000000000000000000000",
"amount": "12", "decimals": 18, "slippageBps": 50,
"recipient": "0x…"
}'The answer is shaped differently from a same-chain quote, because it describes a journey rather than a trade. shape names the journey and legs carries it in order:
| shape | What happens |
|---|---|
| DIRECT | One bridge leg. The token you send is the token that lands. |
| SWAP_BRIDGE | Swap on the source chain, then bridge the proceeds. |
| BRIDGE_SWAP | Bridge first, then swap on the destination chain. |
| SWAP_BRIDGE_SWAP | Both ends swap, with the bridge between them. |
⚠️ Read endToEndMinOut, not the last leg's minOut. It is the floor for the whole journey, the number the recipient is guaranteed on the far side, and it is the only figure that accounts for every leg's slippage at once. Beside it, endToEndOut is the expected amount and etaSec is how long the whole journey is expected to take, in seconds. A same-chain quote has none of these, because it either fills or reverts in one block.
tracking.status instead.The corridor is not always available in both directions or for every asset. Rejected options come back in rejected with a plain-language reason, and viable alternatives in alternatives. A route refused for want of a relayer reads differently from one refused for want of liquidity, and both are worth showing.
Building and tracking a cross-chain swap
POST /v1/build takes the quoteId exactly as it does for a same-chain swap, and returns one transactionRequest to sign on the SOURCE chain. Alongside it: adapter (which bridge), approval (whose target is the cross router, not the swap router), bridgeParams, refundVault, and a tracking block.
{
"shape": "SWAP_BRIDGE",
"adapter": "across",
"approval": { "target": "0x00000000…0f68", "amount": "12000000000000000000" },
"transactionRequest": { "chainId": 1, "to": "0x00000000…0f68", "value": "0", "data": "0x…", "from": "0x…" },
"transferId": "0x89ac9386…666c",
"tracking": { "status": "/v1/bridge/transfers/0x89ac9386…666c" },
"endToEndMinOut": "…", "etaSec": 14
}⚠️ Approve the approval.target from the BUILD response, not the one on the quote. They differ: the quote names the router that would spend your token for a same-chain swap, while the build names the cross router that actually pulls it. Approving the wrong one leaves the send to fail on allowance.
After the source transaction is mined, poll tracking.status, the same transferId that later appears as bridgeTransferId on the row in GET /v1/swaps, which is how a completed journey joins the wallet's history.
Why build takes a quoteId
/v1/build derives the minimum-out floor from the quote the user actually saw, not from a fresh price fetched at build time. If the market moved between the quote and the confirmation, the transaction reverts rather than silently executing at a worse price than the one on screen.
A quoteId lives for 120 seconds. The slippageBps you send to /v1/build has to match the value the quote was priced at; a mismatch returns slippage_mismatch rather than being clamped. That check exists because an earlier build path let the same quote be rebuilt at a wider tolerance, landing a floor at half the output the user was shown.
Building does not consume the quote. Every /v1/build re-prices the route from the pinned parameters, derives the floor from the output the user was shown, never from the fresh re-quote, and checks the live route against that floor before emitting calldata, answering price_moved if the market has fallen below it. A second build of the same quoteId is therefore exactly as safe as the first, and it is how a client learns swap.to for an allowance check, builds the approval, then builds the swap, all against one quote. Only time retires a quoteId: 120 seconds, then QUOTE_EXPIRED.
quoteId is the system working. If /v1/build returns QUOTE_EXPIRED, re-quote and put the new price in front of the user. Retrying the build cannot succeed.Freshness
Quotes carry blockNumber and expiresAt, so you can prove which chain state a price came from. That matters the first time a user asks why their fill differed from the screenshot they took. A quote also carries stale: true when it was computed against an index that is behind the head.
stale: true and a refused /v1/build share one per-chain freshness window, so a stale quote will not build. Treat the flag as a pre-check rather than a warning. The window differs by chain because a healthy indexing tick costs a different amount of wall clock on each; GET /v1/chains reports the current lag and the limit it is judged against, as ageSeconds and freshnessLimitSeconds.
That endpoint also separates the two ways a chain can be unhealthy, and they want different responses from you. degraded: true means the chain still quotes but builds are refused, so retry shortly. available: false means quoting is off entirely, and unavailableReason names the cause: head_stalled, ingestion_stalled, node_unreachable or not_started. Treat an unrecognized reason as a plain outage rather than mapping it to the nearest one you already handle.
For a live price display, re-quote on an interval rather than caching. Pool reserves move every block, and a five-minute-old number is not a price. It is a memory.
Slippage
slippageBps is basis points, so 50 is 0.5%. It sets minimumAmount, which is enforced on-chain. Omit it and you get 50; send more than 2,000 and the request is rejected. Beyond that default, pick your own. A stablecoin pair and a long-tail pair do not want the same number, and only you know which one your user is trading.
priceImpactPct is the percentage of input USD value lost, so a positive number means the user is losing value. It is absent when the oracle cannot price one side of the pair. That is the dangerous case rather than the benign one: a pair we cannot price is usually a pair with very little behind it. Warn on a missing priceImpactPct instead of proceeding quietly.
How the split is chosen
A quote's routes array is the actual split. Each leg carries its own amountIn, amountOut and per-hop path, and a large order routinely fans across several pools. The router elects up to eight pool-disjoint legs, and it keeps a split only when the split beats the single best path by more than a per-chain margin: 10 basis points on Ethereum, 3 on the L2s, where a leg costs cents rather than dollars. A leg that gains less than it costs to execute is not a gain.
Ranking runs in two passes, and the order is the part worth understanding. First we find the best gross output across every candidate path. Then the gas estimate is allowed to reorder only those paths that land within 1.5% of it.
The cap exists because of a measured failure rather than a theory. On UNI to LINK, 0.05 UNI filled at rate 0.44127 through one Balancer hop while 0.08 UNI filled at 0.45428 through two. The smaller trade got a 2.9% worse rate, because scoring output net of gas with no ceiling conceded about $0.005 of price to save about $0.011 of gas. Inside its own model the router was right. It was still handing back a fill nobody had agreed to, and at 15 to 30 gwei a single Ethereum hop is $3 to $8, which is the same arithmetic applied to an entire $50 trade. So gas may break ties between comparable routes. It may not sell the fill.
Rendering the split is the honest answer to “why did my price change?” The route moved because the pools did, and the response says which ones.