For Frontends
Use the Liquid Lane API to redeem supported RWAs for USDC in your app. Connect the user's wallet, request approval when needed, and sign an EIP-712 order.
| Network | Base URL |
|---|---|
| Mainnet | https://swap.symbiotic.fi/api/v1 |
| Hoodi | https://swap.hoodi.symbiotic.fi/api/v1 |
| Sepolia | https://swap.sepolia.symbiotic.fi/api/v1 |
Use the matching chain and Reactor address. See the API reference for full schemas.
Integration Steps
Request a quote
Call POST /quote with the tokens, amount, wallet (swapper), and output recipients. Use type: "EXACT_INPUT", set both chain IDs to the deployment chain (1 for mainnet), and set each output's token to tokenOut.
The response contains quotes, sorted best-first. Each entry includes solver, quote, and signatureData. Choose quotes[0] for the best quote. Display the user's amount from the selected quote.orderInfo.outputs, formatted with the token's decimals. Refresh quotes while the user reviews them.
404means no quote is available. Let the user change the amount or try again.
Approve the Reactor
Call POST /check_approval with { walletAddress, chainId, token, amount }. The backend returns unsigned { to, data, value } payloads for the wallet to sign and submit:
- Sufficient allowance:
cancelandapprovalare bothnull; continue to signing. - Zero allowance: send only
approval, which grants the Reactor an unlimited ERC-20 allowance. - Nonzero but insufficient allowance: send
cancelfirst to reset the Reactor's allowance to zero, then sendapproval.
Wait for each transaction to succeed before sending the next one or signing the order. Wallets with atomic batching support can execute [cancel, approval] together in that order. cancel resets the token allowance; it does not cancel an order.
Sign
Sign signatureData as EIP-712 typed data with primaryType: "Request", passing value as the message.
Submit the order
Call POST /order with { quote, signature }, keeping the quote unchanged. Persist the returned orderId before polling. Retrying the same quote and signature returns the existing order.
404means the quote is unknown.409means it expired or no solver can honor it. Request a new quote and signature.
Track settlement
Poll GET /orders?orderId=<orderId> every few seconds.
orderStatus | Action |
|---|---|
open | Keep polling. |
filled | Show txHash and settledAmounts. |
expired, error, cancelled, insufficient-funds | Offer a new quote. |
Bound each request and the polling window. A timeout does not cancel an order: retain its ID and resume tracking after reload.
Examples
async function (: string, : object) {
const = "https://swap.symbiotic.fi/api/v1";
const = await (`${}${}`, {
: "POST",
: { "Content-Type": "application/json" },
: .(),
: .(15_000),
});
if (!.) throw new (`${}: HTTP ${.}`);
return .();
}
import type { } from "viem";
const [] = await .();
// Quote
const { } = await ("/quote", {
: 1,
: 1,
,
,
,
: "EXACT_INPUT",
,
: [{ : , : }],
});
// Choose the best quote (highest output)
const { , } = [0];
// Approve the Reactor if needed
const { , } = await ("/check_approval", {
: ,
: 1,
: ,
,
});
for (const of [, ]) {
if (!) continue;
const = await .({
: ,
...,
: (.),
});
const = await .({ });
if (. !== "success") throw new ("Approval reverted.");
}
// Sign
if (.. <= .() / 1000)
throw new ("Quote expired. Request a new quote.");
const { , , , } = ;
const = await .({
: ,
,
: as unknown as ,
,
: ,
});
// Submit; save orderId to track settlement
const { } = await ("/order", { , });async function (: string, : object) {
const = "https://swap.symbiotic.fi/api/v1";
const = await (`${}${}`, {
: "POST",
: { "Content-Type": "application/json" },
: .(),
: .(15_000),
});
if (!.) throw new (`${}: HTTP ${.}`);
return .();
}
import { , } from "@fireblocks/ts-sdk";
import type { , } from "viem";
const = { : ., : "0" };
const = ..! as ; // Address of vault 0.
async function (: string) {
for (let = 0; < 40; ++) {
const { } = await ..({ });
if (. === "COMPLETED") return ;
if (["FAILED", "BLOCKED", "CANCELLED", "REJECTED", "TIMEOUT"].(. ?? ""))
throw new (`Fireblocks: ${.}`);
await new (() => (, 3_000));
}
throw new (`Check Fireblocks transaction ${} before retrying.`);
}
// Quote
const { } = await ("/quote", {
: 1,
: 1,
,
,
,
: "EXACT_INPUT",
,
: [{ : , : }],
});
// Choose the best quote (highest output)
const { , } = [0];
// Approve the Reactor if needed
const { , } = await ("/check_approval", {
: ,
: 1,
: ,
,
});
for (const of [, ]) {
if (!) continue;
const { } = await ..({
: {
: .,
: "ETH",
,
: {
: .,
: { : . },
},
: "0",
: { : . },
},
});
await (.!);
}
// Sign
if (.. <= .() / 1000)
throw new ("Quote expired. Request a new quote.");
const { , , , } = ;
const = [
{ : "name", : "string" },
{ : "version", : "string" },
{ : "chainId", : "uint256" },
{ : "verifyingContract", : "address" },
];
const { } = await ..({
: {
: .,
: "ETH",
,
: {
: {
: [
{
: "EIP712",
: {
: { , ... },
,
,
: ,
},
},
],
},
},
},
});
const = await (.!);
const { , , } = .![0]!.!;
const : = `0x${}${}${(! + 27).(16)}`;
// Submit; save orderId to track settlement
if (.. <= .() / 1000)
throw new ("Quote expired. Request a new quote.");
const { } = await ("/order", { , });Fees
Add an output with your fee recipient and portionBps (25 = 0.25%). Exactly one output must omit portionBps to receive the remainder.
{
"outputs": [
{ "token": "0xUsdc" },
{ "token": "0xUsdc", "recipient": "0xYourFeeWallet", "portionBps": 25 }
]
}Solvers
Each quotes[] entry includes solver metadata: use solver.name for the display name and solver.metadata.logoUrl for the logo when present.
| Solver ID | Name | Network |
|---|---|---|
symbiotic_first | Symbiotic | Mainnet |
symbiotic_second | Keyrock | Mainnet |
symbiotic_third | KPK | Mainnet |
symbiotic_fourth | Cassa | Mainnet |
symbiotic_fifth | Clearstar | Mainnet |
symbiotic_sixth | InfiniFi | Mainnet |
Liquidity
Call POST /liquidity with { tokenIn } and optional solverId or solverIds. The response includes tokenIn, tokenOut, their metadata (tokenInInfo, tokenOutInfo), and these liquidity fields:
{
"totalLiquidity": "3000000000",
"solvers": [
{ "solverId": "symbiotic_first", "liquidity": "2000000000" },
{ "solverId": "symbiotic_second", "liquidity": "1000000000" }
]
}Each solver's liquidity is the sum of the effective adapters[].maxAssets available to it during quote construction, after authorization and live-discount validation. totalLiquidity sums all returned solver values. Shared adapter inventory counts for each eligible solver, so the total is not a pool of independent funds. The solvers array is sorted by liquidity from highest to lowest.
Pass solverId: "symbiotic_second" to restrict both the list and totalLiquidity to that solver. In the example above, the total becomes "1000000000" and the list contains only symbiotic_second. If that solver has no eligible liquidity, the total is "0" and the list is empty. The existing solverIds array filters both the list and the total when solverId is omitted; if both are supplied, solverId takes precedence.
Amounts are integer strings in tokenOut base units. Format them with tokenOutInfo.decimals: the example total is 3,000 tokens when decimals is 6. Use BigInt for sums and comparisons.
Use totalLiquidity to sort assets, loading it in the background. For Capacity: <capacity>, match the selected quote's solver.id to solvers[].solverId and display that solver's liquidity. Solvers without eligible inventory are omitted. Empty inventory returns HTTP 200 with totalLiquidity: "0" and solvers: [].
This response replaces the sampled levels and price-impact fields. It reads inventory without requesting solver quotes and does not reserve funds. Request a fresh /quote for the user's chosen amount before signing.
API Errors
Error responses contain { error: { code, message, status, timestamp, details? } }. Validation errors include details.issues[].
For support, record the HTTP status and X-Request-Id response header on both successful and failed requests. The backend exposes this header through CORS. Treat the ID as an opaque string.
After collecting wallet signatures for a discount, submit the signed terms to POST /discount and wait for HTTP 200 before marking it published. Publication and cancellation return { discountId }. Read request correlation from the header, which identifies the HTTP attempt and is not an idempotency key.