Issuing intents
LI.FI intent is an entirely componentized and permissionless system with no inherent trust elements. It is up to integrators to mix and match components as needed.
Catalyst offers two ways to swap:
- Off-chain relay, via the order server (recommended)
- On-chain deposits.
Catalyst recommends that you use off-chain relay for a cheaper and more seamless experience. Though, for a fully decentralised system the on-chain deposit may provide advantages for you.
Getting a quote
Section titled “Getting a quote”You can implement your own quoting logic but the order server allows you to query Catalyst solvers’ inventory. This provides you with a better execution guarantee.
const getQuote = async () => { const response = await fetch("order-server-uri/quotes/request", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ // Chain identifiers as chain IDs fromChain: "11155111", // Sepolia testnet toChain: "84532", // Base Sepolia testnet
// Token addresses on respective chains fromAsset: "0xf08A50178dfcDe18524640EA6618a1f965821715", // Token on Sepolia toAsset: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // Token on Base Sepolia
// Amount in the smallest unit of the token amount: "3000000", // Amount with appropriate decimals. For USDC it is 6. }), });
/** * { "fromChainId": 11155111, "toChainId": 84532, "fromAsset": "0xf08a50178dfcde18524640ea6618a1f965821715", "toAsset": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238", "inputAmount": 3000000, "outputAmount": 2996000, "quote": 0.9986666666666667 } */ return await response.json();};
Order structure
Section titled “Order structure”A Catalyst order generally follows the structure of the CatalystCompactOrder
:
struct CatalystCompactOrder { /** @dev Pays for the inputs of the order. If the order fails, will be recipient of the refund. */ address user; /** @dev Allocator nonce for the order. Should be unique otherwise it won't be signed by the allocator. */ uint256 nonce; /** @dev The chainId (canonical) of the input chain. */ uint256 originChainId; /** @dev The expiry of the lock. Enough time for the fill and validation needs to be provided. */ uint32 fillDeadline; /** @dev Address of the validation layer on the origin chain. */ address inputOracle; uint256[2][] inputs; OutputDescription[] outputs;}
The CatalystCompactOrder
needs to be appropriately signed. For TheCompact
settlement interface, use the following transformation:
struct BatchCompact { address arbiter; // Associated settlement contract address sponsor; // CatalystCompactOrder.user uint256 nonce; // CatalystCompactOrder.nonce uint256 expires; // CatalystCompactOrder.fillDeadline uint256[2][] idsAndAmounts; // CatalystCompactOrder.inputs CatalystWitness witness;}
struct CatalystWitness { uint32 fillDeadline; // CatalystCompactOrder.fillDeadline address inputOracle; // CatalystCompactOrder.inputOracle OutputDescription[] outputs; // CatalystCompactOrder.outputs}
The sponsor (user) shall then sign it as an EIP-712 signed structure.
Inputs
Section titled “Inputs”For TheCompact
, the inputs need to be provided as an array of [uint256 tokenId, uint256 amount]
. The tokenId
refers to the TheCompact
encoded tokenId, the first 12 bytes is a lock tag and the last 20 bytes is the token address.
Output
Section titled “Output”struct OutputDescription { bytes32 oracle; bytes32 settler; uint256 chainId; bytes32 token; uint256 amount; bytes32 recipient; bytes call; bytes context;}
Note that OutputDescription.oracle
and CatalystCompactOrder.inputOracle
need to match. Together, they define the validation layer used. Each order should only use one validation layer, which can be an aggregation of AMBs.
settler
specifies the output type. For limit orders, use the CoinFiller
and set context
as empty (0x
). For Dutch auctions, use the CoinFiller
and set context
appropriately.
Specify the token as the bytes32
identifier. For EVM, the address is left-padded, e.g., 0x000...00abcdef
.
You can schedule additional calls to happen after token delivery. Note that if you have configured multiple outputs, the order of execution is not guaranteed (it may happen over multiple blocks). If call
is provided, the recipient
is called using the Catalyst interfaces. For arbitrary calls, the Catalyst Multicaller can be used.
Off-chain relay via resource locks
Section titled “Off-chain relay via resource locks”Off-chain relaying of swaps assumes that you have an existing resource lock somewhere. This guide we assumes you use TheCompact. When using pure resource lock flow, ensure funds are already deposited into the lock. For alternative integrations, refer to the on-chain flow.
// Submit an order to the order serverconst submitOrder = async () => { const response = await fetch("order-server-uri/orders/submit", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ // Type of order to submit orderType: "CatalystCompactOrder",
// Quote information for the swap quotes: [ { fromAsset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", toAsset: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", toPrice: "0.99", fromPrice: "1.01", intermediary: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", discount: "0.005", }, ],
// The CatalystCompactOrder structure order: { user: "0x9773DAcbc46CAFb4e055060565e319922B48607D", nonce: 1005, originChainId: 84532, fillDeadline: 1744884642, inputOracle: "0xada1de62bE4F386346453A5b6F005BCdBE4515A1", inputs: [ [ "36286452483532273188258183071097127586156282419649613466036116694645176389502", 1000000, ], ], outputs: [ { oracle: "0x0000000000000000000000007bc921c858c5390d9fd74c337dd009ec9a1b6b8f", settler: "0x0000000000000000000000005d14806127d7caafcb8028c7736ae6b8aec583d9", chainId: 11155111, token: "0x0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238", amount: 1000000, recipient: "0x0000000000000000000000009773dacbc46cafb4e055060565e319922b48607d", call: "0x", context: "0x", }, ], },
// EIP-712 signature from the user/sponsor sponsorSignature: "...",
allocatorSignature: "...", }), });
/** * { "order": { // Order details (similar to the request) "user": "0x9773DAcbc46CAFb4e055060565e319922B48607D", "nonce": 1005, // Other order fields... }, "quotes": [ // Quote information ], "sponsorSignature": "0x9de6ca6df89a582d8c228b25fc84c947b52aac232e6e1d48b8a1f32c0610166226f773a501eda4489cc5d91c25c2b472e505bc3d9862690c18a6c38e8da27f371b", "allocatorSignature": "0x", "meta": { "submitTime": 1744909799926, "orderStatus": "Signed", "destinationAddress": "0x9773dacbc46cafb4e055060565e319922b48607d", "orderIdentifier": "co_6LFaJDL9CuW_y8nfNDocAdF0BizEQn", "signedAt": "2025-04-17T17:09:59.925Z", "expiredAt": null } } */ return await response.json();};
On-chain relay
Section titled “On-chain relay”For non-wallet integrators, on-chain deposits is the easiest integration. It uses more gas but abstracts the resource lock complexity away: There are 2 ways to do on-chain relaying of swaps:
- Using the
CompactSettlerWithDeposit
. This provides you with an easy and quick integration at a slight increased gas overhead.function depositFor(CatalystCompactOrder calldata order, ResetPeriod resetPeriod) external; - Using a custom
depositAndRegisterFor
implementation similar to the LI.FI facet. This requires a slightly more complicated