# App Fees Source: https://docs.relay.link/features/app-fees Add App Fees on any route and collect in stablecoins ## What are App Fees? App fees are additional fees set by third-party developers on top of existing fees. These fees are specified when generating a quote. To improve the gas costs of fee setting, App fees are not paid out in real-time. Rather, App Fees accrue in an offchain base *USDC,* balance and can be withdrawn as desired at your convenience. ## Setting App Fees App Fees are measured in basis points (bps) based on the input value of an order. When setting an app fee, an EVM-based claim address must be provided, which will be used for withdrawing accumulated fees. A single quote can include multiple App fees. You can accrue fees by including `AppFees` in the Quote API: ```tsx theme={null} "appFees": [ { "recipient": "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E", //EVM Claim Address "fee": "100" //App Fee (in BPS of input token) } ] ``` ### Additional Considerations * On same-chain operations where the solver holds neither the input or output currency, an extra swap is added within the transaction to swap from the input currency into a currency accepted by the solver for App Fees * For this to occur, the fee to be taken needs to be greater than \$0.025. This is to prevent the situation where the action of taking the app fee will consume more in gas than the fee itself. * App Fee Recipients *must* be an EVM compatible wallet address, even for non-EVM chains. * App fees *can* be an array, for setting multiple fees at once * App fees work the same way with deposit address flows as with regular quote requests. If you include the `appFees` parameter on an unsupported transaction type (such as sends, transfers, multi-input, or gasless execution without a prior quote), the API will not return an error — but no fees will be collected. Always verify fee collection is working using the [monitoring endpoints](#verifying-fee-collection) described below. ## Supported Transaction Types App fees are supported on the following transaction types: | Transaction Type | Supported | | ---------------------------------- | --------------------------------------------- | | Swaps (cross-chain) | ✅ All chain combinations (EVM, SVM, SUI, BTC) | | Swaps (same-chain) | ✅ See chain-specific notes below | | Bridges | ✅ | | Calls | ✅ | | Wraps / Unwraps | ✅ | | Sends / Transfers | ❌ | | Multi-input | ❌ | | Gasless swaps | ✅ Fees from original quote are respected | | Gasless execution (no prior quote) | ❌ Same-chain only — e.g. 7702 wallet upgrades | ### Chain-Specific Notes **EVM (same-chain):** Fully supported. Fees can be collected from the input currency, the output currency, or via a parallel swap to USDC. **SVM (same-chain):** Supported with input-currency collection only. Output-currency fee collection is not available — this applies to all output currencies, not just SOL. If the input currency is not a solver-held currency, the fee is collected via a secondary swap into USDC. **SUI (same-chain):** Supported with input-currency collection only. No output-currency collection or parallel swap fallback. App fees will only be collected when the input is a solver-held currency. **Cross-chain (all combinations):** Fully supported. Same-chain limitations do not apply to cross-chain swaps — fees are calculated based on the solver's deposit currency on the origin chain. ## Verifying Fee Collection You can verify that app fees are being collected correctly using the following endpoints: **Per-transaction:** Use `/requests/v2` which returns both `appFees` (the fee quoted to the user) and `paidAppFees` (the fee actually collected). If `appFees` is present but `paidAppFees` is empty or zero, the fee was not collected for that transaction. **Accumulated balance:** Use `/app-fees/{address}/balances` to check your total accumulated app fee balance available for withdrawal. ## Withdrawing App Balance App balance accrue in USDC from app fees, and can be claimed on any chain. The balance is free to claim on Base, and Relay app balance apply to claim on any other chain or in any other currency. The easiest way to claim your App Balance is to use the [Relay App](https://relay.link/claim-app-fees). The Relay App Balance page also supports withdrawing via a multisig. Here's how you can withdraw via the API: ### Get App Balance To check your accrued App Balance, use the App fee balances API. Here's an example curl command and response. The response includes the `balances.currency` field reflecting your off-chain balance for each currency accumulated. ```bash cURL theme={null} curl --location 'https://api.relay.link/app-fees/0x03508bb71268bba25ecacc8f620e01866650532c/balances' ``` ```json Response theme={null} { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "USDC", "name": "USDC", "decimals": 6, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": false, "isNative": false } }, "amount": "75021651210714015", "amountFormatted": "0.075021651210714015", "amountUsd": "116.235557", "minimumAmount": "75021651210714015" } ``` To learn more about this api, see the [Get App Fee Balance](/references/api/get-app-fee-balances) API reference. ### How to withdraw your off-chain balance? To withdraw your app fees, use the app fees claim API. If using the API, the response will guide you through signing and submitting a message to the `/execute/permit` endpoint to verify ownership of your claim address. The SDK automates this process for you. ```bash cURL theme={null} curl --request POST \ --url https://api.relay.link/app-fees/0x03508bb71268bba25ecacc8f620e01866650532c/claim \ --header 'Content-Type: application/json' \ --data '{ "chainId": 8453, "currency": "0x0000000000000000000000000000000000000000", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c" }' ``` ```typescript SDK theme={null} import { getClient } from "@relayprotocol/relay-sdk"; import { useWalletClient } from "wagmi"; const { data: wallet } = useWalletClient(); const { data } = await getClient().actions.claimAppFees({ wallet, chainId: 8453, // Base currency: "0x0000000000000000000000000000000000000000", // ETH recipient: "0x...", // Optional onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { // custom handling }, }); ``` ```json Response theme={null} { "steps": [ { "id": "authorize", "action": "Sign authorization", "description": "Authorize claiming funds", "kind": "signature", "items": [ { "status": "incomplete", "data": { "sign": { "signatureKind": "eip191", "message": "0xa344c6123e3da9fc3f9c81edeab0b2eb39f2ea80ba54a6e0ad00123dc180619c" }, "post": { "endpoint": "/execute/permits", "method": "POST", "body": { "kind": "claim", "requestId": "0xa344c6123e3da9fc3f9c81edeab0b2eb39f2ea80ba54a6e0ad00123dc180619c" } } } } ] } ] } ``` If you're using the API directly, you can then send a request to `/execute/permit` with the `requestId` and `signature` to claim your fees. An example request would be: ```bash cURL theme={null} curl -X POST \ https://api.relay.link/execute/permits?signature=${signature} \ -H 'Content-Type: application/json' \ -d '{ "kind": "request", "requestId": "0xa344c6123e3da9fc3f9c81edeab0b2eb39f2ea80ba54a6e0ad00123dc180619c" }' ``` Once done, you can check your balance again to verify the withdrawal. To learn more about this api, see the [Claim App Fee Balance](/references/api/claim-app-fees) API reference. ### Withdrawing via a smart contract In some cases you may want to accrue fees into a smart contract instead of an EOA. This can easily be done by setting the app fees recipient. The challenge is in claiming the fees to the contract. The claim API requires a signature to claim app fees but contracts cannot sign messages. To solve this, the API supports [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) which allows a smart contract to validate a signature using arbitrary logic. Once implemented our backend will check if the contract implements `isValidSignature` and use that method to verify the signature. Here is an example of how this might work in practice: * Wallet A (owner by contract owner), signs the message * Then our backend gets the posted signature and calls the contract's `isValidSignature` method to verify the signature * If the signature is valid, the funds get sent to the recipient. Let's say in this case the contract's `isValidSignature` method returns true if the signature was generated by wallet A. # Deposit Addresses Source: https://docs.relay.link/features/deposit-addresses Learn how to use Relay with a deposit address for instant bridging. Relay supports instantly and cheaply bridging from a variety of EVM and non-EVM chain to any token. We are now launching deposit addresses to allow users to bridge without connecting a wallet. Users can send funds to a designated address and Relay will instantly deposit the correct tokens on the destination chain in the user's wallet. This is a great way for users to quickly start transacting on a new chain! ## How It Works Once generated, a single deposit address can be reused so long as the origin currency, origin chain, destination currency, and destination chain all remain the same. To enable using a deposit address for your bridge, you will need to set the `useDepositAddress` body parameter to `true` in the [quote request](https://docs.relay.link/references/api/get-quote-v2). When using deposit addresses, the `user` parameter can be any valid address, including the zero address (`0x0000000000000000000000000000000000000000`). This provides flexibility in how you structure your deposit address requests. Failures via deposit addresses are extremely rare, but are still possible. If something goes wrong, we will default to manually handling refunds. If you wish to automatically receive refunds on the origin chain, specify the `refundTo` address. This will mean refunds will automatically be issued on origin in case of any filling failures. The deposit transaction requires no calldata. You just need to send any value to the address specified in the API request response data. As long as this amount is enough to cover any gas or relay fees, it will be automatically bridged based on the route requested in the initial quote. When using deposit addresses, there are more relayer gas fees associated with bridges. Typically about 33,000 extra gas is required than regular native bridges, and about 70,000 more for ERC-20 bridges. For very small bridges, we don't recommend using deposit addresses since so much gas is required. Once a deposit occurs, the quote will automatically be generated post-deposit to account for gas fluctuation and recompute all fees. This can change the amount received to be different than the quoted value but will always be at least the minimum rate. If the minimum rate cannot be achieved, the deposit amount will be refunded. All Relay EVM deposit addresses begin and end with the character `d` (not case-sensitive). This is to help us with reducing the indexing search space. ## Supported Currencies To determine which currencies support deposit addresses on a specific chain, use the [chains API](https://docs.relay.link/references/api/get-chains) endpoint. Each chain's response includes a `solverCurrencies` array that contains all currencies available for deposit address bridging on that chain. This method provides the most straightforward way to check deposit address support without requiring additional queries. ## Edge Cases We have automatic handling for common edge cases that can occur when using deposit addresses: * Depositing to the Wrong Chain -- For example, if the bridge request requires a deposit on Base but the funds are deposited on Optimism, this amount is bridged to the destination chain originally requested as long as its enough to cover the fees and gas associated with the bridge. Please note that this will mean a new quote will be generated in-flight. NOTE: This is only supported for relay supported cross-chain currencies, see limitations. * Extra Funds are Received -- If the user deposits more funds than what is quoted, the quote will be regenerated to account for this surplus amount and the bridge will still be filled. * Less Funds are Received -- If the user deposits less funds than what is quoted, a new quote will be generated to account for the lower amount, and the bridge will be filled. ## Limitations * Calldata execution on destination is not allowed. * Only exact input bridging is allowed. Specifying exact output will not work. * When non-supported currency or NFTs are sent to the deposit address, we currently do not support recovering of those assets. ## Debugging The transaction hash of the deposit transaction cannot be used to lookup the status of the bridge for deposit address flows. ### Tracking Deposit Address Transactions For deposit address flows, the most reliable way to detect and track requests is to poll the [Get Requests API](/references/api/get-requests) with the **deposit address** passed as the `user` query parameter: ```bash Request theme={null} curl -X GET "https://api.relay.link/requests/v2?user=&sortBy=updatedAt&sortDirection=desc&limit=20" ``` ```json Response expandable theme={null} { "requests": [ { "id": "0x8bc200f95ee7cb2af30085fd0a7ecfdd2f3bcfef78d5d441f5dadfdfde95ab7f", "status": "pending", "user": "0x1ba74e01d46372008260ec971f77eb6032b938a4", "recipient": "0xf0ae622e463fa757cf72243569e18be7df1996cd", "data": { "isDepositAddressRequest": true, "inTxs": [ { "hash": "0xabc123...", "chainId": 8453, "timestamp": 1769988962 } ], "outTxs": [] }, "createdAt": "2026-02-05T15:33:25.302Z", "updatedAt": "2026-02-05T15:34:10.000Z" } ] } ``` This returns requests associated with that deposit address, including: * `id` (`requestId`) * `status` * `data.isDepositAddressRequest` * Transaction metadata (`inTxs`, `outTxs`, timestamps, fees) Once you have the active `requestId`, you can optionally use [Get Status](/references/api/get-intents-status-v3) for request-level status polling: ```bash Request theme={null} curl -X GET "https://api.relay.link/intents/status/v3?requestId=" ``` ```json Response expandable theme={null} { "status": "pending", "inTxHashes": [ "0xabc123..." ], "txHashes": [], "updatedAt": 1769988962883, "originChainId": 8453, "destinationChainId": 10 } ``` If you already have the `requestId` from the quote response, you can still use it directly. For resilient tracking across deposit-address request regeneration, poll `/requests/v2` with `user=`. ## Example Request and Response You will find an example below of sending 0.01 ETH from Base to Optimism. ```bash Request theme={null} curl -X POST \ 'https://api.relay.link/quote/v2' \ -H 'Content-Type: application/json' \ -d '{ "user": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "originChainId": 8453, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationChainId": 10, "destinationCurrency": "0x0000000000000000000000000000000000000000", "tradeType": "EXACT_INPUT", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "amount": "100000000000000000", "usePermit": false, "useExternalLiquidity": false, "referrer": "relay.link/bridge", "useDepositAddress": true, "refundTo": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Deposit funds for executing the calls", "kind": "transaction", "depositAddress": "0x1ba74e01d46372008260ec971f77eb6032b938a4", "requestId": "0xa1dea21f62a85b9ede98b7a6442b7b9574bdfd71adabc03397242adf122038de", "items": [ { "status": "incomplete", "data": { "from": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "to": "0x1ba74e01d46372008260ec971f77eb6032b938a4", "data": "0x", "value": "100000000000000000", "maxFeePerGas": "4222843", "maxPriorityFeePerGas": "987146", "chainId": 8453 }, "check": { "endpoint": "/intents/status/v3?requestId=0xa1dea21f62a85b9ede98b7a6442b7b9574bdfd71adabc03397242adf122038de", "method": "GET" } } ] } ] } ``` * `useDepositAddress` must be set to `true`
* `user` can be any valid address, including the zero address
* `refundTo` can be optionally set when `useDepositAddress` is being used. This is the address that refunds will be sent to in case of an issue. If not specified then manual refund is required. ## Common Use Cases Deposit addresses are particularly useful in the following scenarios: * **CEX-to-DeFi Bridging** -- When users are already performing a withdrawal from a centralized exchange and want to bridge to a different chain without an additional transaction. * **Onboarding Flows from Fiat Rails** -- For applications that need to seamlessly move funds from fiat onramps to specific destination chains without requiring users to understand intermediate bridging steps. * **Wallet Connection Issues** -- In situations where wallet connection or transaction signing is problematic, such as when the wallet provider doesn't support the origin chain or when building headless/automated bridging systems. # Fast Fill Source: https://docs.relay.link/features/fast-fill Accelerate the destination fill of a cross-chain request Fast Fill is a feature that allows you to accelerate the destination fill of a cross-chain request. This is useful if you want to speed up the completion of a bridge or swap operation before it's landed onchain. ## Requirements Before you can start fast filling requests, you need: 1. **An API Key** — Required to authenticate your fast fill requests. [Request an api key](/references/api/api-keys#how-to-get-an-api-key). 2. **Enterprise Partnership** — Reach out to the Relay team to become an Enterprise Partner. Learn more about the [Enterprise Partner Program](/resources/enterprise). 3. **A Linked Funding Address** — A wallet address must be linked to your API key. This is the address that will fund your app balance. [Reach out](https://forms.gle/XNeELYavjwoJwPEf7) to the Relay team to link your wallet address. 4. **Sufficient App Balance** — Your app balance must have enough funds to cover fast fills. Fast fills draw from your app balance and restore it once transactions land onchain. App balance is also used for [fee sponsorship](/features/fee-sponsorship). Once you're set up, you can begin fast filling requests. *** ## How to use it? To Fast Fill a request you simply need to call the [Fast Fill API](/references/api/fast-fill) with the request ID of the request you want to fast fill after you've submitted it onchain but before it finalizes. The request will be instantly filled and the fill amount will be deducted from your app balance. Once the transaction lands onchain and Relay indexes it, the funds are restored to your app balance. ### Example This example demonstrates the full fast fill flow: getting a quote, submitting the transaction on-chain, calling the fast fill API, and monitoring the status. ```typescript SDK theme={null} import { getClient, createClient } from "@relayprotocol/relay-sdk"; import { createWalletClient, createPublicClient, http } from "viem"; import { base } from "viem/chains"; // 1. Setup Wallet - Initialize your wallet using your preferred method const account = {}; // Your wallet account (e.g., privateKeyToAccount, or injected wallet) const wallet = createWalletClient({ account, chain: base, transport: http(), }); const publicClient = createPublicClient({ chain: base, transport: http(), }); // 2. Initialize the Relay client createClient({ baseApiUrl: "https://api.relay.link", source: "YOUR_APP_NAME", }); // 3. Get a quote using the SDK const quote = await getClient().actions.getQuote({ chainId: 8453, // Base toChainId: 42161, // Arbitrum currency: "0x0000000000000000000000000000000000000000", // ETH toCurrency: "0x0000000000000000000000000000000000000000", // ETH amount: "100000000000000", // 0.0001 ETH tradeType: "EXACT_INPUT", wallet, }); const requestId = quote.steps[0].requestId; console.log(`Request ID: ${requestId}`); // 4. Execute the quote and fast fill once the transaction is submitted let txHash: string; await getClient().actions.execute({ quote, wallet, onProgress: async ({ currentStep, currentStepItem, txHashes }) => { // Once transaction is submitted, trigger fast fill immediately if (txHashes && txHashes.length > 0 && !txHash) { txHash = txHashes[0]; console.log(`Transaction submitted: ${txHash}`); // 5. Call Fast Fill immediately after submitting (before waiting for confirmation) const fastFillResult = await getClient().actions.fastFill({ requestId, }); console.log("Fast fill initiated:", fastFillResult); } // Monitor progress if (currentStep && currentStepItem) { console.log(`Step: ${currentStep.action}, Status: ${currentStepItem.status}`); } }, }); console.log("Bridge completed successfully!"); ``` ```typescript API theme={null} import { createWalletClient, createPublicClient, http, type Hex } from "viem"; import { base } from "viem/chains"; // 1. Setup Wallet - Initialize your wallet using your preferred method const account = {}; // Your wallet account (e.g., privateKeyToAccount, or injected wallet) const walletClient = createWalletClient({ account, chain: base, transport: http(), }); const publicClient = createPublicClient({ chain: base, transport: http(), }); const API_KEY = "YOUR_API_KEY"; // Required for fast fill // 2. Get a quote from the Relay API const quoteResponse = await fetch("https://api.relay.link/quote/v2", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${API_KEY}`, }, body: JSON.stringify({ user: account.address, originChainId: 8453, // Base destinationChainId: 42161, // Arbitrum originCurrency: "0x0000000000000000000000000000000000000000", // ETH destinationCurrency: "0x0000000000000000000000000000000000000000", // ETH amount: "100000000000000", // 0.0001 ETH tradeType: "EXACT_INPUT", }), }); const quote = await quoteResponse.json(); const step = quote.steps[0]; const item = step.items[0]; const requestId = step.requestId; console.log(`Request ID: ${requestId}`); // 3. Submit the transaction on-chain const txHash = await walletClient.sendTransaction({ to: item.data.to as Hex, data: item.data.data as Hex, value: BigInt(item.data.value), }); console.log(`Transaction submitted: ${txHash}`); // 4. Call the Fast Fill API immediately after submitting (before waiting for receipt) // This accelerates the fill on the destination chain const fastFillResponse = await fetch("https://api.relay.link/fast-fill", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${API_KEY}`, }, body: JSON.stringify({ requestId, }), }); const fastFillResult = await fastFillResponse.json(); console.log("Fast fill initiated:", fastFillResult); // 5. Now wait for the transaction receipt (origin chain confirmation) const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); console.log(`Origin transaction confirmed in block ${receipt.blockNumber}`); // 6. Poll the status API to confirm the fill completed const checkStatus = async (): Promise => { const statusResponse = await fetch( `https://api.relay.link/intents/status/v3?requestId=${requestId}`, { headers: { "Authorization": `Bearer ${API_KEY}` }, } ); return statusResponse.json(); }; // Poll until success let status = await checkStatus(); while (status.status !== "success" && status.status !== "failure") { console.log(`Status: ${status.status}`); await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second status = await checkStatus(); } if (status.status === "success") { console.log("Bridge completed successfully!"); console.log(`Destination tx: ${status.txHashes?.[0]}`); } else { console.error("Bridge failed:", status); } ``` You can use the **`maxFillAmountUsd`** parameter to set a maximum USD value for a fast fill request. If the request's value exceeds this limit, the fast fill will not be executed. This is useful for managing risk and controlling exposure, especially when dealing with volatile assets or large transaction volumes. *** ## Funding Your App Balance There are two ways to deposit funds to your app balance: ### Option 1: Use the Relay App UI The simplest way to deposit funds is through the [Relay App Balance UI](https://www.relay.link/app-balance). This provides a user-friendly interface for managing your balance. ### Option 2: Direct On-Chain Deposit You can programmatically deposit to your app balance by sending a transaction on Base to the Relay solver. This method involves appending specific calldata in place of the request ID to identify the deposit. #### How It Works When you transfer funds to the solver using specific calldata, Relay treats it as a deposit to your app balance. This method uses a fixed 12-character prefix and suffix (`012345abcdef`) with the middle portion specifying which address to credit: **Calldata Format:** ``` 0x[prefix][address-to-credit][suffix] ``` * **Prefix:** `012345abcdef` * **Address to Credit:** The wallet address to deposit funds for (use `0000000000000000000000000000000000000000` for `msg.sender`) * **Suffix:** `012345abcdef` #### Examples | Calldata | Behavior | | -------------------------------------------------------------------- | ---------------------------------------------------- | | `0x012345abcdef0000000000000000000000000000000000000000012345abcdef` | Credits `msg.sender` | | `0x012345abcdefd5c0d17ccb9071d27a4f7ed8255f59989b9aee0d012345abcdef` | Credits `0xd5c0d17ccb9071d27a4f7ed8255f59989b9aee0d` | The ability to specify a different address is useful for building auto-topup systems or having more granular control over which accounts receive deposits. #### Solver Address Deposits should be sent to the Relay solver on Base: | Network | Address | | ------- | -------------------------------------------- | | Base | `0xf70da97812cb96acdf810712aa562db8dfa3dbef` | #### TypeScript Example ```typescript expandable theme={null} import { erc20Abi, encodeFunctionData, createWalletClient } from 'viem' import { base } from 'viem/chains' const BASE_USDC_ADDRESS = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' as const const RELAY_BASE_SOLVER_ADDRESS = '0xf70da97812cb96acdf810712aa562db8dfa3dbef' as const const REQUEST_ID_PREFIX = '012345abcdef' const walletClient = createWalletClient({ chain: base, transport: custom(window.ethereum!), }) const [userAddress] = await walletClient.getAddresses() const amountInWei = '100000000' // 100 USDC // Encode the transfer function call const transferData = encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [RELAY_BASE_SOLVER_ADDRESS, amountInWei] }) // Add custom calldata to credit the wallet address const customCalldata = `${REQUEST_ID_PREFIX}${userAddress.slice(2)}${REQUEST_ID_PREFIX}` as `0x${string}` const hash = await walletClient.sendTransaction({ to: BASE_USDC_ADDRESS, data: `${transferData}${customCalldata}`, account: userAddress, chain: base }) ``` *** ## Checking Your App Balance Verify your available balance using the [Get App Fee Balances](/references/api/get-app-fee-balances) API: ```bash Request theme={null} curl --location 'https://api.relay.link/app-fees/{your-wallet-address}/balances' ``` ```json Response theme={null} { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "USDC", "name": "USDC", "decimals": 6 }, "amount": "75021651210714015", "amountFormatted": "0.075021651210714015", "amountUsd": "116.235557", "minimumAmount": "75021651210714015" } ``` *** ## Caveats * Slippage may lead to a surplus or shortage in your app balance. If you have a good estimate of the final amount you can use the **`solverInputCurrencyAmount`** parameter to specify the exact amount of input currency you want to use for the fill, thus minimizing slippage. * We recommend protecting your API key on the backend by not exposing it to the client. # Fee Sponsorship Source: https://docs.relay.link/features/fee-sponsorship Sponsor your users' transaction fees to improve UX and reduce friction ## What is Fee Sponsorship? Fee Sponsorship allows you to cover the destination chain fees for your users' Relay transactions. By sponsoring these fees, you can reduce costs for your users and create a more seamless experience. This is particularly valuable for onboarding new users or providing a premium experience for your application. Fee sponsorship covers all destination chain fees (including gas topup amounts), but **not** the origin chain gas fee. Users will still need to pay the gas required to submit their transaction on the origin chain. ## Requirements Before you can sponsor transactions, you need: 1. **An API Key** — Required to authenticate your sponsorship requests and associate them with your app balance. [Request an api key](/references/api/api-keys#how-to-get-an-api-key). 2. **Enterprise Partnership** — Reach out to the Relay team to become an Enterprise Partner. Learn more about the [Enterprise Partner Program](/resources/enterprise). 3. **A Linked Funding Address** — A wallet address must be linked to your API key. This is the address that will fund your app balance. [Reach out](https://forms.gle/XNeELYavjwoJwPEf7) to the relay team to link your wallet address. 4. **Sufficient App Balance** — Your app balance must have enough funds to cover the fees you wish to sponsor. Once you're set up, you can begin depositing funds and sponsoring transactions. *** ## Funding Your App Balance There are two ways to deposit funds to your app balance: ### Option 1: Use the Relay App UI The simplest way to deposit funds is through the [Relay App Balance UI](https://www.relay.link/app-balance). This provides a user-friendly interface for managing your balance. ### Option 2: Direct On-Chain Deposit You can programmatically deposit to your app balance by sending a transaction on Base to the Relay solver. This method involves appending specific calldata in place of the request ID to identify the deposit. #### How It Works When you transfer funds to the solver using specific calldata, Relay treats it as a deposit to your app balance. This method uses a fixed 12-character prefix and suffix (`012345abcdef`) with the middle portion specifying which address to credit: **Calldata Format:** ``` 0x[prefix][address-to-credit][suffix] ``` * **Prefix:** `012345abcdef` * **Address to Credit:** The wallet address to deposit funds for (use `0000000000000000000000000000000000000000` for `msg.sender`) * **Suffix:** `012345abcdef` #### Examples | Calldata | Behavior | | -------------------------------------------------------------------- | ---------------------------------------------------- | | `0x012345abcdef0000000000000000000000000000000000000000012345abcdef` | Credits `msg.sender` | | `0x012345abcdefd5c0d17ccb9071d27a4f7ed8255f59989b9aee0d012345abcdef` | Credits `0xd5c0d17ccb9071d27a4f7ed8255f59989b9aee0d` | The ability to specify a different address is useful for building auto-topup systems or having more granular control over which accounts receive deposits. #### Solver Address Deposits should be sent to the Relay solver on Base: | Network | Address | | ------- | -------------------------------------------- | | Base | `0xf70da97812cb96acdf810712aa562db8dfa3dbef` | #### TypeScript Example ```typescript expandable theme={null} import { erc20Abi, encodeFunctionData, createWalletClient } from 'viem' import { base } from 'viem/chains' const BASE_USDC_ADDRESS = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' as const const RELAY_BASE_SOLVER_ADDRESS = '0xf70da97812cb96acdf810712aa562db8dfa3dbef' as const const REQUEST_ID_PREFIX = '012345abcdef' const walletClient = createWalletClient({ chain: base, transport: custom(window.ethereum!), }) const [userAddress] = await walletClient.getAddresses() const amountInWei = '100000000' // 100 USDC // Encode the transfer function call const transferData = encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [RELAY_BASE_SOLVER_ADDRESS, amountInWei] }) // Add custom calldata to credit the wallet address const customCalldata = `${REQUEST_ID_PREFIX}${userAddress.slice(2)}${REQUEST_ID_PREFIX}` as `0x${string}` const hash = await walletClient.sendTransaction({ to: BASE_USDC_ADDRESS, data: `${transferData}${customCalldata}`, account: userAddress, chain: base }) ``` *** ## Checking Your App Balance Verify your available balance using the [Get App Fee Balances](/references/api/get-app-fee-balances) API: ```bash Request theme={null} curl --location 'https://api.relay.link/app-fees/{your-wallet-address}/balances' ``` ```json Response theme={null} { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "USDC", "name": "USDC", "decimals": 6 }, "amount": "75021651210714015", "amountFormatted": "0.075021651210714015", "amountUsd": "116.235557", "minimumAmount": "75021651210714015" } ``` *** ## Sponsoring Transactions Once your app balance is funded, you can sponsor transactions by including specific parameters in your quote requests. ### Required Header | Header | Type | Description | | ----------- | -------- | ----------------------------------------------------------------------------------------------------- | | `x-api-key` | `string` | Your API key, required to authenticate sponsorship requests and associate them with your app balance. | ### Sponsorship Parameters Add these parameters to your [Get Quote](/references/api/get-quote) API request: | Parameter | Type | Description | | ------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `subsidizeFees` | `boolean` | Set to `true` to have the sponsor pay for all fees associated with the request, including gas topup amounts. | | `maxSubsidizationAmount` | `string` | *(Optional)* The maximum amount to subsidize in USDC with 6 decimal places (e.g., `"1000000"` = \$1.00). If the total fees exceed this threshold, the entire request will **not** be subsidized. | | `subsidizeRent` | `boolean` | *(Optional)* Set to `true` to sponsor Solana ATA rent fees. Requires `subsidizeFees` to also be enabled. See [Solana Fee Sponsorship](#solana-fee-sponsorship). | | `depositFeePayer` | `string` | *(Optional)* A Solana address to pay deposit transaction fees for Solana origin transactions. See [Solana Fee Sponsorship](#solana-fee-sponsorship). | | `sponsoredFeeComponents` | `string[]` | *(Optional)* You can also choose to sponsor specific Relay fees for your users using the `sponsoredFeeComponents` parameter. This allows integrators to selectively cover different fee types (such as gas fees or relayer fees) while still collecting app fees. | ### Example Request ```bash cURL theme={null} curl -X POST \ 'https://api.relay.link/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: your-api-key' \ -d '{ "user": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "originChainId": 8453, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationChainId": 10, "destinationCurrency": "0x0000000000000000000000000000000000000000", "tradeType": "EXACT_INPUT", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "amount": "100000000000000000", "subsidizeFees": true, "maxSubsidizationAmount": "5000000" }' ``` In this example: * `x-api-key` header authenticates your sponsorship request * `subsidizeFees: true` enables fee sponsorship * `maxSubsidizationAmount: "5000000"` caps sponsorship at \$5.00 USDC ### How `maxSubsidizationAmount` Works The `maxSubsidizationAmount` parameter acts as a safety threshold: * If the total fees are **at or below** this amount, the sponsor covers the full cost * If the total fees **exceed** this amount, **no sponsorship occurs** — the user pays all fees This protects you from unexpectedly high costs during network congestion or volatile market conditions. When `maxSubsidizationAmount` is exceeded, the entire request falls back to user-paid fees. The fees are not partially subsidized. *** ## Solana Fee Sponsorship Solana transactions involve unique cost structures that differ from EVM chains. In addition to standard transaction fees, Solana requires rent payments for creating new accounts, such as Associated Token Accounts (ATAs). Relay provides specific parameters to handle these costs. ### Understanding Solana Rent When a user receives a token on Solana for the first time, an Associated Token Account must be created to hold that token. This account creation requires a rent payment (approximately 0.002 SOL). Without sponsorship, this cost falls on the user. ### Sponsoring Destination Rent with `subsidizeRent` The `subsidizeRent` parameter allows you to sponsor the rent fees for ATA creation on Solana destination transactions. | Parameter | Type | Description | | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------- | | `subsidizeRent` | `boolean` | Set to `true` to have the sponsor pay for Solana rent fees associated with ATA creation on the destination chain. | The `subsidizeRent` parameter must be used in conjunction with `subsidizeFees`. There is currently no system for separating destination gas and rent fees. If you enable `subsidizeRent` without `subsidizeFees`, the rent will not be sponsored. **Important considerations:** * When `subsidizeFees` is enabled but `subsidizeRent` is not, requests involving ATA creation will automatically fall back to user-paid fees. This is because rent sponsorship without explicit opt-in presents an exploitation vector. * The exploitation risk stems from the fact that users can close their ATA after the transaction completes and reclaim the rent. This effectively converts sponsored rent into a direct transfer of funds to the user. * Only enable `subsidizeRent` when you have a trusted relationship with your users or have implemented safeguards against abuse. #### Example Request ```bash cURL theme={null} curl -X POST \ 'https://api.relay.link/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: your-api-key' \ -d '{ "user": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "originChainId": 8453, "originCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "destinationChainId": 792703809, "destinationCurrency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "tradeType": "EXACT_INPUT", "recipient": "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u", "amount": "10000000", "subsidizeFees": true, "subsidizeRent": true, "maxSubsidizationAmount": "5000000" }' ``` In this example, a user bridges USDC from Base to Solana. Both `subsidizeFees` and `subsidizeRent` are enabled, ensuring the sponsor covers all destination fees including any ATA rent required for the recipient to receive USDC on Solana. ### Sponsoring Origin Deposits with `depositFeePayer` For transactions originating from Solana, you can designate an alternative account to pay the transaction fees and rent for the deposit transaction using `depositFeePayer`. | Parameter | Type | Description | | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `depositFeePayer` | `string` | A Solana address that will pay the transaction fees and rent for deposit transactions on Solana. This account must have sufficient SOL to cover these costs. **Must be different from the `user` address.** | The `depositFeePayer` parameter operates independently from the `subsidizeFees` and `subsidizeRent` parameters. It applies specifically to Solana origin transactions and does not draw from your app balance. **Important restrictions and security considerations for `depositFeePayer`:** * **The `depositFeePayer` and `user` addresses must be different.** You cannot use the same wallet address for both parameters. * **Signing order is critical.** Relay returns a transaction that requires the integrator's sponsoring wallet to sign **first**. The integrator must sign the transaction before passing it to the user. If the user receives an unsigned transaction, they could potentially modify it to drain the sponsoring wallet. * **Avoid same-chain swaps with untrusted users.** The `depositFeePayer` parameter is exploitable when used for same-chain swaps on Solana. Only use it for same-chain operations when the wallet is under your control. **When to use `depositFeePayer`:** * When you want to provide a gasless experience for users depositing from Solana * When your application maintains a dedicated fee-paying wallet for Solana transactions * When you want to abstract away the complexity of SOL requirements from your users #### Example Request ```bash cURL theme={null} curl -X POST \ 'https://api.relay.link/quote' \ -H 'Content-Type: application/json' \ -H 'x-api-key: your-api-key' \ -d '{ "user": "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u", "originChainId": 792703809, "originCurrency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "destinationChainId": 8453, "destinationCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "tradeType": "EXACT_INPUT", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "amount": "10000000", "depositFeePayer": "YourFeePayerSolanaAddressHere" }' ``` In this example, a user bridges USDC from Solana to Base. The `depositFeePayer` address will cover the Solana transaction fees for the deposit, allowing the user to transact without holding SOL. ### Solana Sponsorship Summary | Parameter | Direction | Funding Source | Use Case | | --------------------------------- | ----------------------- | ----------------------- | ------------------------------------------------------------------------ | | `subsidizeFees` + `subsidizeRent` | To Solana (destination) | App Balance | Sponsor destination gas and ATA rent when users receive tokens on Solana | | `depositFeePayer` | From Solana (origin) | Specified Solana wallet | Sponsor origin transaction fees when users deposit from Solana | *** ## What Fee Sponsorship Does Not Cover Fee sponsorship is designed to cover relay and execution costs, but certain fees remain outside its scope: * **Origin chain gas fees** — Users must pay the gas required to submit their transaction on the origin chain (unless using `depositFeePayer` for Solana origins). * **App fees** — Fees configured via the `appFees` parameter are not deducted from your sponsor balance. These are separate charges that accrue to the specified recipient addresses. * **First-time approval transactions when using `usePermit`** — The initial approval transaction required for permit-based flows is not covered by fee sponsorship. However, all subsequent requests after the approval will be sponsored. *** ## Fees Object When you sponsor transactions, the quote response includes a `fees` object with a `subsidized` field showing the fees covered by your sponsorship: ```json theme={null} { "fees": { "relayerService": "...", "relayerGas": "...", "relayer": "...", "app": "...", "subsidized": { "amount": "500000", "amountUsd": "0.50" } } } ``` For more details on the fee structure, see [Relay Fees](/references/api/api_core_concepts/fees). *** ## Best Practices 1. **Monitor Your Balance** — Set up alerts or automated checks to ensure your app balance doesn't run dry during high-traffic periods. 2. **Use `maxSubsidizationAmount`** — Always set a reasonable cap to protect against unexpected fee spikes. 3. **Consider Auto-Topup** — For production applications, consider implementing an auto-topup system using the direct on-chain deposit method. 4. **Track Sponsored Transactions** — Use the [Get Requests](/references/api/get-requests) API to monitor your sponsored transaction history and costs. *** ## Related Resources * [App Fees](/features/app-fees) — Learn how to collect fees from your users * [Get App Fee Balances](/references/api/get-app-fee-balances) — API reference for checking balances * [Get Quote](/references/api/get-quote) — API reference for quote requests * [Relay Fees](/references/api/api_core_concepts/fees) — Understanding Relay's fee structure * [Solana Support](/references/api/api_guides/solana) — Guide to depositing and withdrawing on Solana # Gas Top-Up Source: https://docs.relay.link/features/gas-top-up Include destination-chain native gas in bridged fills so recipients can transact immediately. Gas top-up automatically includes a small amount of the destination chain's native gas token (e.g., ETH) with the bridged tokens. This ensures recipients have gas to interact with their received tokens immediately. ## Enabling Gas Top-Up To enable gas top-up in your quote request: * Set `topupGas` to `true` * Optionally specify the gas amount using `topupGasAmount` ## Checking Gas Top-Up Support A chain supports gas top-up when **all** of the following are true: 1. **EVM chain only** - The chain's `vmType` must be `"evm"` 2. **Non-native currency** - You cannot top up gas when bridging the chain's native token (e.g., ETH on Ethereum) 3. **Bridging enabled** - Either: * `tokenSupport` is `"All"`, OR * `currency.supportsBridging` is `true` You can verify support by checking the **Gas Top Up Supported** column in [Supported Tokens & Routes](/references/api/api_resources/supported-routes) or by querying the [Chains API](https://api.relay.link/chains). # Gasless Execution Source: https://docs.relay.link/features/gasless-execution Submit arbitrary transactions for gasless execution ## How does it work? Gasless transactions on EVM are regular transactions where the transaction signer and the gas payer are two different addresses. Rather than signing with the same address and then submitting their transaction and paying gas, a user can simply sign a transaction payload, and other entities (relayers, solvers, etc.) can submit the transaction on their behalf and sponsor the gas costs. This is not natively possible, so there are custom transaction payloads to enable this: [Permit2](https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/permit2), [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009), [ERC-2771 payloads](https://eips.ethereum.org/EIPS/eip-2771), [ERC-4337 user operations](https://eips.ethereum.org/EIPS/eip-4337), etc. The core concept is that as long as the transaction execution does not rely on `tx.origin` (the transaction submitter), any user can sign permit payloads or Smart Account actions (EIP-7702, ERC-4337 user operations etc.) and a relayer can post those payloads on chain by wrapping them in a transaction. This is where the `/execute` API comes in. The API allows users to submit raw calls (the partial payload of a regular EVM transaction). It includes the `to`, `data`, `value` and an optional EIP-7702 `authorizationList`. ## How to use it? This feature is only currently available to Enterprise Partners, learn more about the [Enterprise Partner Program](/resources/enterprise) to get started. Once you're an Enterprise Partner you should have an API key and a linked funding address. You'll then need to fund your app balance, you can learn more about that [here](/features/fee-sponsorship). ### Executing a gasless quote In this example use case you want to execute a gasless Relay quote with the [execute](/references/api/execute) API. This flow assumes that the user is using a smart wallet. Use the [quote endpoint](/references/api/get-quote-v2) to get a quote for the transaction you want to execute. The `originGasOverhead` indicates how much additional gas overhead will be necessary to include the user operation on the chain as compared to a normal EOA transaction. ```bash cURL theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "YOUR_WALLET_ADDRESS", "originChainId": 8453, "destinationChainId": 42161, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000", "tradeType": "EXACT_INPUT", "originGasOverhead": "300000" //This is the overhead gas cost of the smart wallet, this will be used when simulating the transaction }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for ETH", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "to": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "data": "0x49290c1c00000000000000000000000003508bb71268bba25ecacc8f620e01866650532c70387ea00ba97925c3bf1263363b2a7bdc0401faa00f0af4140627c7f385952c", "value": "100000000000000", "chainId": 8453, "maxFeePerGas": "3501361", "maxPriorityFeePerGas": "1000007" }, "check": { "endpoint": "/intents/status/v3?requestId=0xa9ad21ed31b40317d2dad7750d642b8a72adf086f250f8a33c1636488c780878", "method": "GET" } } ], "requestId": "0xa9ad21ed31b40317d2dad7750d642b8a72adf086f250f8a33c1636488c780878", "depositAddress": "" } # ... ], "fees": { "gas": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "89121108862", "amountFormatted": "0.000000089121108862", "amountUsd": "0.000279", "minimumAmount": "89121108862" }, "relayer": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "9212128356056", "amountFormatted": "0.000009212128356056", "amountUsd": "0.028858", "minimumAmount": "9212128356056" }, "relayerGas": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "1219936568400", "amountFormatted": "0.0000012199365684", "amountUsd": "0.003822", "minimumAmount": "1219936568400" }, "relayerService": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "7992191787656", "amountFormatted": "0.000007992191787656", "amountUsd": "0.025037", "minimumAmount": "7992191787656" }, "app": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0", "minimumAmount": "0" }, "subsidized": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0", "minimumAmount": "0" } }, "details": { "operation": "swap", "sender": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "currencyIn": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100000000000000", "amountFormatted": "0.0001", "amountUsd": "0.313265", "minimumAmount": "100000000000000" }, "currencyOut": { "currency": { "chainId": 42161, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "90787871643944", "amountFormatted": "0.000090787871643944", "amountUsd": "0.284407", "minimumAmount": "86520841676679" }, "refundCurrency": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100000000000000", "amountFormatted": "0.0001", "amountUsd": "0.313265", "minimumAmount": "100000000000000" }, "totalImpact": { "usd": "-0.028858", "percent": "-9.21" }, "swapImpact": { "usd": "0.000000", "percent": "0.00" }, "expandedPriceImpact": { "swap": { "usd": "-0.000030" }, "execution": { "usd": "-0.023822" }, "relay": { "usd": "-0.005007" }, "app": { "usd": "0" } }, "rate": "0.90787871643944", "slippageTolerance": { "origin": { "usd": "0.000000", "value": "0", "percent": "0.00" }, "destination": { "usd": "0.013339", "value": "4267029967265", "percent": "4.69" } }, "timeEstimate": 1, "userBalance": "0", "isFixedRate": true, "route": { "origin": { "inputCurrency": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100000000000000", "amountFormatted": "0.0001", "amountUsd": "0.313265", "minimumAmount": "100000000000000" }, "outputCurrency": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100000000000000", "amountFormatted": "0.0001", "amountUsd": "0.313265", "minimumAmount": "100000000000000" }, "router": "relay" }, "destination": { "inputCurrency": { "currency": { "chainId": 42161, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "90787871643944", "amountFormatted": "0.000090787871643944", "amountUsd": "0.284407", "minimumAmount": "86520841676679" }, "outputCurrency": { "currency": { "chainId": 42161, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "90787871643944", "amountFormatted": "0.000090787871643944", "amountUsd": "0.284407", "minimumAmount": "86520841676679" }, "router": "relay" } } }, "protocol": { "v2": { "orderId": "0x70387ea00ba97925c3bf1263363b2a7bdc0401faa00f0af4140627c7f385952c", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0x7cae8841bbd4c7c9da1b2a8d9ebdc948d54f5cb4178aca2d289e65edc1bddf52", "inputs": [ { "payment": { "chainId": "base", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000", "weight": "1" }, "refunds": [ { "chainId": "base", "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1768418493, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, { "chainId": "arbitrum", "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1768418493, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output": { "chainId": "arbitrum", "payments": [ { "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "86520841676679", "expectedAmount": "90787871643944" } ], "calls": [], "deadline": 1768418493, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "paymentDetails": { "chainId": "base", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000" } } } } ``` With the quote in hand, you can now execute the transaction. You'll need to pass the data from previous steps to the api. ```typescript theme={null} const options = { method: 'POST', headers: {'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'}, body: JSON.stringify({ requestId: steps[0].requestId, //Quote requestId from previous step executionKind: 'rawCalls', data: { chainId: 8453, to: 'YOUR_WALLET_ADDRESS', data: steps[0].items[0].data.data, //Quote data from previous step value: steps[0].items[0].data.value, //Quote value from previous step }, executionOptions: { //Optional execution options } }) }; fetch('https://api.relay.link/execute', options) .then(res => res.json()) .then(res => console.log(res)) .catch(err => console.error(err)); ``` Now that the transaction has been submitted, you can monitor the status of the transaction using the requestId from the quote data and the [status endpoint](/references/api/get-intents-status-v3). ```typescript theme={null} const response = await fetch(`https://api.relay.link/intents/status/v3?requestId=${requestId}`); const data = await response.json(); console.log(data); ``` Refer to the [quickstart guide](/references/api/quickstart#status-lifecycle) for more information on how to monitor the status of a transaction. ### More Use Cases If the user has a Smart Account (such as an ERC-4337 Smart Account), any transaction can be made gasless for the Smart Account by signing an ERC-4337 user operation and submitting it to the /execute API. The only step is to create the user operation, get it signed by the user and then create the final call data of the handleOps call of the Entry Point before submitting to the /execute endpoint. *Note: The maxFeePerGas and maxPriorityFeePerGas The ERC-4337 user operation should be 0; it will result in a double payment to the solver (The solver already has adjusted the cost in the quote, so no need for this payment)* ```typescript example.ts theme={null} import { createPublicClient, createWalletClient, createClient, http, encodeFunctionData, keccak256, encodeAbiParameters, toHex, } from "viem"; import { base } from "viem/chains"; import { UserOperation } from "./types.ts"; import { entryPointAbi, smartAccountAbi } from "./abis.ts"; // ---- Config ---- const ENTRY_POINT = "0xEntryPointAddress" as const; const YOUR_WALLET_ADDRESS = "0xYourAddress" as const; const BENEFICIARY = "0xYourBeneficiary" as const; // ---- Clients ---- const publicClient = createPublicClient({ chain: base, transport: http(), }); const ownerAccount = ... // Replace with your owner account const walletClient = createWalletClient({ account: ownerAccount, chain: base, transport: http() }); const userOpPackHash = keccak256(packUserOp(userOp)); const encoded = encodeAbiParameters( [ { type: "bytes32" }, // userOp hash { type: "address" }, // entryPoint { type: "uint256" }, // chainId ], [userOpPackHash, ENTRY_POINT, BigInt(base.id)], ); const userOpHash = keccak256(encoded); const signature = await walletClient.signMessage({ message: { raw: userOpHash }, }); const signedUserOp: UserOperation = { ...userOp, signature }; const handleOpsData = encodeFunctionData({ abi: entryPointAbi, functionName: "handleOps", args: [[signedUserOp], BENEFICIARY], }); ``` ```typescript types.ts theme={null} export type UserOperation = { sender: `0x${string}`; nonce: bigint; initCode: `0x${string}`; callData: `0x${string}`; callGasLimit: bigint; verificationGasLimit: bigint; preVerificationGas: bigint; maxFeePerGas: bigint; maxPriorityFeePerGas: bigint; paymasterAndData: `0x${string}`; signature: `0x${string}`; }; ``` ```typescript abis.ts theme={null} export const entryPointAbi = [ { type: "function", name: "handleOps", stateMutability: "nonpayable", inputs: [ { name: "ops", type: "tuple[]", components: [ { name: "sender", type: "address" }, { name: "nonce", type: "uint256" }, { name: "initCode", type: "bytes" }, { name: "callData", type: "bytes" }, { name: "callGasLimit", type: "uint256" }, { name: "verificationGasLimit", type: "uint256" }, { name: "preVerificationGas", type: "uint256" }, { name: "maxFeePerGas", type: "uint256" }, { name: "maxPriorityFeePerGas", type: "uint256" }, { name: "paymasterAndData", type: "bytes" }, { name: "signature", type: "bytes" }, ], }, { name: "beneficiary", type: "address" }, ], outputs: [], }, ] as const; // Example smart account ABI with an `execute` function export const smartAccountAbi = [ { type: "function", name: "execute", stateMutability: "nonpayable", inputs: [ { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "data", type: "bytes" }, ], outputs: [], }, { type: "function", name: "nonce", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint256" }], }, ] as const; ``` ```typescript Request theme={null} const options = { method: 'POST', headers: {'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'}, body: JSON.stringify({ executionKind: 'rawCalls', data: { chainId: 8453, to: 'YOUR_WALLET_ADDRESS', data: handleOpsData, value: "0" } }) }; fetch('https://api.relay.link/execute', options) .then(res => res.json()) .then(res => console.log(res)) .catch(err => console.error(err)); ``` ```json Response theme={null} { "message": "Transaction submitted", "requestId": "0xabc123..." } ``` Now that the transaction has been submitted, you can monitor the status of the transaction using the requestId returned in the api response and the [status endpoint](/references/api/get-intents-status-v3). ```typescript theme={null} const response = await fetch(`https://api.relay.link/intents/status/v3?requestId=${requestId}`); const data = await response.json(); console.log(data); ``` Another example of a flow where the transaction execution does not depend on `tx.origin` is the action of upgrading a regular EOA to an EIP-7702 smart account. In this flow, any entity can execute an upgrade payload and a signed EIP-7702 `authorizationList` from the EOA. The client must pass the correct fields for the `to` (which is the EOA that is to be upgraded), data is the call data for the initialization of the EOA's EIO-7702 Smart Account and the authorizationList which contains the signed information about the Smart Account to upgrade to. ```typescript example.ts theme={null} import { createWalletClient, http, encodeFunctionData, hexToSignature, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; // This flow requires access to the EOA private key const walletClient = createWalletClient({ account: privateKeyToAccount("0xYourEOAPrivateKey"), chain: base, transport: http() }); // Build init calldata (replace ABI + args with your real upgrade initializer) const initCalldata = encodeFunctionData({ abi: [{ type: "function", name: "initialize", inputs: [ { name: "owner", type: "address" }, { name: "config", type: "uint256" } ], outputs: [] }], functionName: "initialize", args: ["0xEOA_TO_UPGRADE", 1n] }); // --- Sign EIP-7702 Authorization (viem handles everything: r, s, yParity, nonce, chainId) --- const authorization = await client.signAuthorization({ account: client.account!, contractAddress: "0xSMART_ACCOUNT_CONTRACT" //This is the contract that will have authorization }); ``` ```typescript Request theme={null} const options = { method: 'POST', headers: {'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'}, body: JSON.stringify({ executionKind: 'rawCalls', data: { chainId: 8453, to: '0xEOA_TO_UPGRADE', data: initCalldata, value: "0", authorizationList: [authorization] } }) }; fetch('https://api.relay.link/execute', options) .then(res => res.json()) .then(res => console.log(res)) .catch(err => console.error(err)); ``` ```json Response theme={null} { "message": "Transaction submitted", "requestId": "0xabc123..." } ``` Now that the transaction has been submitted, you can monitor the status of the transaction using the requestId returned in the api response and the [status endpoint](/references/api/get-intents-status-v3). ```typescript theme={null} const response = await fetch(`https://api.relay.link/intents/status/v3?requestId=${requestId}`); const data = await response.json(); console.log(data); ``` ## Caveats * Only EVM chains are supported currently. * Partial sponsorship is not yet supported. Should the fees amount to more than the app balance, the transaction will not be sponsored. * If the transaction relies on `msg.sender`, the transaction signer must be a smart wallet (EIP-7702, ERC-4337, etc). * The API won't work if the transaction relies on `tx.origin`. # Gasless Swaps Source: https://docs.relay.link/features/gasless-swaps Gas fees add layers of complexity to swapping. Holding the native token, on the right network, at the right time to pay for gas adds friction for users. A gasless swap means that the user only pays with the input token, and this covers all fees. This means someone else is executing the transaction and paying the gas. ## What solution is right for me? There are many different solutions when it comes to implementing a gasless swap experience. We've put together a decision tree below to help you arrive at the solution that best fits your application. ```mermaid placement="top-right" theme={null} flowchart TD Start([Which gasless flow should I use?]) --> WalletControl WalletControl{Does your app control
the user's wallet?} WalletControl -->|No — BYO wallet| Permit WalletControl -->|Yes — embedded or custodial| Has4337 Has4337{Are you already
using ERC-4337?} Has4337 -->|Yes| Smart4337 Has4337 -->|No| BatchExecutor Permit[/"**Permit-based Gasless**

_Limitation: not all ERC-20s support permit_"/] Smart4337[/"**ERC-4337 Gasless**"/] BatchExecutor[/"**7702 BatchExecutor**"/] click Permit "#permit-based-gasless" _blank click Smart4337 "#erc-4337-gasless" _blank click BatchExecutor "#7702-batchexecutor" _blank style Permit fill:#f9e3e3,stroke:#c44,color:#000 style Smart4337 fill:#e3e9f9,stroke:#44c,color:#000 style BatchExecutor fill:#e3f9e6,stroke:#4a4,color:#000 ``` Same-chain gasless swaps are only supported if you sponsor the fees. Same-chain swaps route directly through DEXes, due to this the solver never has an opportunity to deduct the gas fees before the user gets the destination token. ## Permit-based Gasless Use this approach when your app does **not** control the user's wallet (e.g. MetaMask, Rainbow, or any external EOA). The user signs an off-chain EIP-3009 or Permit2 message, and Relay's solver handles everything else — no approval transaction, no gas. USDC is fully gasless. Other ERC20 tokens require a one-time approval transaction. ### How it works Call `/quote/v2` with `usePermit: true` Prompt the user to sign an EIP-712 typed-data permit message (off-chain, no gas) Submit the signed permit to Relay Relay's solver withdraws tokens from the user's EOA and fulfills the destination transaction ### Fees You can choose to subsidize all destination fees by passing `subsidizeFees: true` to the quote api (this effectively subsidizes the first time approval). The user still needs to pay for the origin gas fee. For subsidizing fees you'll need to make sure you have [fee sponsorship](/features/fee-sponsorship) set up. *See the full working example: [ byo-eoa-permit](https://github.com/relayprotocol/relay-gasless-examples/tree/main/byo-eoa-permit)* *** ## ERC-4337 Gasless Use this approach if your app already uses ERC-4337 smart accounts. ERC-4337 UserOperations normally require gas fees, either paid by the sender or a paymaster. With Relay, you set all gas fee fields to 0 and submit the `handleOps` call via Relay's [/execute](/references/api/execute) endpoint instead of directly to the EntryPoint. Relay's relayer submits the transaction and covers the gas. ### `originGasOverhead` Pass **`originGasOverhead: 300000`** in the `/quote/v2` request. This tells Relay how much additional gas the UserOperation adds over a normal EOA transaction, used during simulation to accurately price the fee. ### How it works Call `/quote/v2` with `originGasOverhead: 300000` to get deposit transaction details Build a UserOperation that batches `approve` + `deposit` calls via `executeBatch` Set **`maxFeePerGas`** and **`maxPriorityFeePerGas`** to `0` Sign the UserOperation with the account owner key Encode an `EntryPoint.handleOps()` call and submit it via `POST /execute` with `subsidizeFees: true` Poll `/intents/status/v3` until the bridge completes ### Fees You can choose to subsidize origin fees: pass `subsidizeFees: true` to the execute api `executionOptions`. You can choose to subsidize destination fees: pass `subsidizeFees: true` to the quote api. You can choose to subsidize both or none of these. If not subsidized the user pays from the output token. For subsidizing fees you'll need to make sure you have [fee sponsorship](/features/fee-sponsorship) set up. *See the full working example: [ 4337-gasless](https://github.com/relayprotocol/relay-gasless-examples/tree/main/4337-gasless)* *** ## 7702 BatchExecutor Use this approach when your app controls an embedded or custodial wallet and you want gasless execution with **any** ERC-20 token — not just permit-compatible ones. This leverages EIP-7702 to delegate the user's EOA to a BatchExecutor contract ([Calibur](https://github.com/Uniswap/calibur) or similar), enabling atomic approve + deposit in a single call. ### `originGasOverhead` Pass **`originGasOverhead: 80000`** in the `/quote/v2` request. This tells Relay how much additional gas the Calibur `execute()` wrapper adds over the raw inner calls (EIP-712 signature verification, batch dispatch, nonce check). This is lower than ERC-4337's `300000` because there's no EntryPoint or UserOp validation overhead. The `80000` value is specific to [Calibur](https://github.com/Uniswap/calibur). Different BatchExecutor contracts may have different gas overheads — adjust this value based on the implementation you use. ### How it works Check if the user's EOA is already delegated to the BatchExecutor via EIP-7702 Call `/quote/v2` with `originGasOverhead: 80000` to get deposit transaction details Build a `BatchedCall` that atomically combines `approve()` and `deposit()` Sign an EIP-7702 authorization (off-chain, no gas) delegating the EOA to the BatchExecutor Submit via POST [/execute](/references/api/execute) targeting the user's EOA ### Fees You can choose to subsidize origin fees: pass `subsidizeFees: true` to the execute api `executionOptions`. You can choose to subsidize destination fees: pass `subsidizeFees: true` to the quote api. You can choose to subsidize both or none of these. If not subsidized the user pays from the output token. For subsidizing fees you'll need to make sure you have [fee sponsorship](/features/fee-sponsorship) set up. *See the full working example: [ 7702-batch-executor](https://github.com/relayprotocol/relay-gasless-examples/tree/main/7702-batch-executor)* # Advanced Source: https://docs.relay.link/references/api/advanced # Rate Limits and API Keys Source: https://docs.relay.link/references/api/api-keys Relay enforces rate limits to ensure platform reliability and fair usage across integrators. Default limits apply to all requests. Higher limits are available via API keys. ## Default Rate Limits (No API Key) The following limits apply when no API key is provided: | Endpoint | Limit | | ---------------------- | ----------------------- | | `/quote` | 50 requests per minute | | `/requests` | 200 requests per minute | | `/transactions/status` | 200 requests per minute | | Other endpoints | 200 requests per minute | ## API Key Rate Limits When using an API key, limits increase and are applied per key: | Endpoint | Limit | | ---------------------- | ----------------------- | | `/quote` | 10 requests per second | | `/requests` | 10 requests per second | | `/transactions/status` | 10 requests per second | | Other endpoints | 200 requests per minute | API keys are recommended for server-side production integrations and higher-throughput use cases. ## How to Request an API Key Requirements: 1. Must have Relay integrated in a public app. 2. Must have enough activity to hit default API rate limits. To request an API key (once requirements are met): 1. Fill out the [API key request form](https://forms.gle/uiRVYXhtH8uBqybR9). 2. The Relay team will review within 72 hours. 3. If approved, we will email your unique API key. ## How to Use an API Key ### HTTP Requests Pass the API key in the request headers: ``` x-api-key: YOUR_API_KEY ``` This header should be included on all requests where higher rate limits are required. ### SDK Usage ```jsx Global API Key theme={null} // It is recommended to only set the apiKey parameter when using the SDK server-side. import { createClient, MAINNET_RELAY_API, } from "@relayprotocol/relay-sdk"; createClient({ baseApiUrl: MAINNET_RELAY_API, apiKey: "YOUR_API_KEY", ... //other parameters }); ``` ```jsx SDK Action API Key theme={null} // In the case that you prefer not to set a global api key // you can pass the api key into the headers parameter of the getQuote function. import { getClient } from "@relayprotocol/relay-sdk"; const quoteParams = {...} const quote = await getClient()?.actions.getQuote(params, undefined, { "x-api-key": "YOUR_API_KEY" }); ``` ### Proxy API If using the sdk on the client, create a proxy api which appends the key in the headers and then pass that endpoint into the `baseApiUrl` parameter. This prevents your key from being leaked while allowing you to set up the necessary protection in your proxy api. ```jsx theme={null} import { createClient, MAINNET_RELAY_API, } from "@relayprotocol/relay-sdk"; createClient({ baseApiUrl: "https://my-proxy.relay.link", //Replace with your proxy endpoint ... //other parameters }); ``` ## Keeping Your API Key Secure Your API key is sensitive — treat it like a password. It is tied to your account, controls your rate limits, and all requests made with it are attributed to you. If your API key is leaked, unauthorized parties could consume your rate limits or make requests on your behalf. Contact us immediately if you suspect your key has been compromised and we will rotate it for you. **Best practices:** * **Keep it server-side only** — never expose it in client-side or frontend code. Use a [proxy API](#proxy-api) if calling Relay from the browser. * **Use environment variables** — store your key in environment variables, not hardcoded in source code. * **Don't commit it to version control** — add it to `.gitignore` or use a secrets manager. * **Restrict access** — only share the key with team members who need it. # Contract Compatibility Source: https://docs.relay.link/references/api/api_core_concepts/contract-compatibility How to make your contract compatible with Relay Relay allows a user to pay on Chain A for an arbitrary set of transactions to be executed on Chain B. These transactions are executed by a relayer, not the user themselves, which means you need to make special considerations to make sure your contract is compatible. ## General overview When Relayers execute transactions on behalf of users, they do so via a multicall contract (to be specific, we use Vectorized's gas-optimized [Multicaller](https://github.com/Vectorized/multicaller) contract). This protects the Relayer from malicious transactions. One exception is simple bridge transactions, where the `data` is empty. For these, the Relayer will execute them directly to avoid the gas overhead of using Multicaller. ## Sender Attribution Because transactions are executed via the Relayer / Multicaller, you can't rely on `msg.sender` for authentication. There are a couple of ways around this: #### Unauthenticated Delegation The simplest solution is if your contract allows one user to take an action on behalf of another user. This works great for actions that don't require authentication, because they are purely net beneficial to the user: * buying an NFT * depositing into a vault * placing an auction bid You just need to allow passing the address that the action is being done on behalf of. Of course, you can't do this for actions that potentially harm the user: * selling the NFT * withdrawing from the vault * canceling the bid, etc. Some contracts don't require authentication, but also don't have native delegation built into them. In such scenarios, it might be possible to use a router contract. E.g. if an NFT only supported minting to `msg.sender`, you could mint via a router contract then have it forwarded the NFT to the user. #### Authenticated Delegation For authenticated actions, you can do those by passing in proof that the user authenticated the action. Currently, you need to build custom signature authentication into your contract, however, in the near future we will be adding support for more standardized flows, such as: * Selling tokens with permit signatures * MulticallerWithSigner * ERC2771 Trusted Forwarder #### Just-In-Time Gas Alternatively, you can simply bridge a small amount of gas and have the user execute the transaction themselves. This is viable because of how fast and cheap Relay is, and is backwards compatible with any contract. We will soon add functionality to our SDK to handle this flow completely automatically, but until then, you can do it manually like this: * Estimate the cost of the transaction on destination * Get a quote to bridge enough gas (small buffer recommended to handle fluctuation) * User executes the bridge of gas money from Origin to Destination * User executes the action on Destination * (Optional) If selling an asset, User could potentially bridge the proceeds back to Origin This might seem like a lot, but especially when Origin and Destination are both L2s, it can be quite fast and cheap. Arguably, much better than the user needing to worry about having a balance on many chains. # Relay Fees Source: https://docs.relay.link/references/api/api_core_concepts/fees Learn about Relay fees In any given Relay there are four potential fees: 1. **Execution Fees** Fees to cover execution costs including network gas on the origin and/or destination chain. * Fill gas estimate (and origin gas estimate for gasless transactions) * \$0.02 flat fee 2. **Swap Fees** Fees to liquidity providers that facilitate cross-asset and cross-chain token swaps. * DEX fees * DEX swap impact * Solver cross-chain rebalancing fees 3. **Relay Fees** Fees charged for using the Relay API gateway and related services. * 25% of swap fees and app fee 4. **App Fees** Fees added on top of a Relay by you, the integrator. If you are interested in learning how app fees work, and how you can add them to your quotes please check out our [App Fees Doc](/features/app-fees).\ \ *Please note that the above fee structure applies to standard cases. In certain cases, such as route-specific campaigns or promotions, different fees may apply.* ## Fees Object The Fees object is returned from the quote API and the requests API. These fees are only the Relay related fee, and are segmented as the following fees: **relayerService**: The service fee paid to the relayer to facilitate execution. **relayerGas**: The gas fee given to the solver to pay gas fees on the destination chain. **relayer**: The sum of the relayerService and the relayerGas. **app**: Third party fees added on top of the existing fees, this fee is added by app developers and accrues offchain to minimize gas costs. **subsidized**: The Fees in the order that are subsidized by the integrator. ## Price Impact If you are interested in understanding or showing your user the fees of the order more broadly, it's best to look at the `expanded price impact` object which reports: **execution** - the price impact that results from execution fees. **swap** - the price impact that results from cross-currency and cross-chain token exchange **relay** - the price impact that results from the relay api fee (if applicable) **app** - the price impact that results from the app fees (if applicable) # Handling Quote Errors Source: https://docs.relay.link/references/api/api_core_concepts/handling-errors When calling the `/quote/v2` endpoint, various errors may be returned depending on request conditions, user input, or internal routing logic. This guide outlines the possible error codes, their meanings, and how to handle them. ## Expected Errors These are known validation and routing issues that developers should gracefully handle in their integrations: | Error Code | Description | | --------------------------------------------- | -------------------------------------------------------------------------- | | `AMOUNT_TOO_LOW` | The amount provided is below the minimum threshold required for a quote. | | `CHAIN_DISABLED` | The origin or destination chain is currently disabled or unsupported. | | `EXTRA_TXS_NOT_SUPPORTED` | Extra transactions are not supported for this trade type. | | `FORBIDDEN` | User does not have the required role or permission. | | `INSUFFICIENT_FUNDS` | The user's wallet does not have enough balance to perform the swap. | | `INSUFFICIENT_LIQUIDITY` | There is not enough liquidity available to complete the swap. | | `INVALID_ADDRESS` | The provided user address is not valid. | | `INVALID_EXTRA_TXS` | The total value of extra transactions exceeds the intended output. | | `INVALID_GAS_LIMIT_FOR_DEPOSIT_SPECIFIED_TXS` | Deposit-specified transactions are only allowed for exact output swaps. | | `INVALID_INPUT_CURRENCY` | The provided input currency address is invalid or not supported. | | `INVALID_OUTPUT_CURRENCY` | The provided output currency address is invalid or not supported. | | `INVALID_SLIPPAGE_TOLERANCE` | Slippage value is not a valid integer string representing basis points. | | `NO_INTERNAL_SWAP_ROUTES_FOUND` | No valid swap route exists internally for the selected token pair. | | `NO_QUOTES` | No available quotes for the given parameters. | | `NO_SWAP_ROUTES_FOUND` | No route was found to fulfill the quote request with the given parameters. | | `ROUTE_TEMPORARILY_RESTRICTED` | This route is temporarily restricted due to high traffic or throttling. | | `SANCTIONED_CURRENCY` | The token involved in the transaction is on a sanctions list. | | `SANCTIONED_WALLET_ADDRESS` | The sender or recipient wallet address is sanctioned or blacklisted. | | `SWAP_IMPACT_TOO_HIGH` | The swap's price impact exceeds acceptable thresholds. | | `UNAUTHORIZED` | The user is not authenticated or lacks valid authorization. | | `UNSUPPORTED_CHAIN` | The specified chain is not supported by the platform. | | `UNSUPPORTED_CURRENCY` | The specified currency is not supported for input or output. | | `UNSUPPORTED_EXECUTION_TYPE` | The execution type used is not supported for fee estimation or execution. | | `UNSUPPORTED_ROUTE` | The swap route combination is not supported. | | `USER_RECIPIENT_MISMATCH` | User and recipient addresses must match for this type of swap. | *** ## Unexpected Errors These indicate infrastructure or downstream failures and are not common: | Error Code | Description | | -------------------------------- | ------------------------------------------------------------------------- | | `DESTINATION_TX_FAILED` | The relay transaction failed on the destination chain. | | `ERC20_ROUTER_ADDRESS_NOT_FOUND` | The router contract for a token could not be located. | | `UNKNOWN_ERROR` | An unclassified or unexpected error occurred. | | `SWAP_QUOTE_FAILED` | Failed to calculate a quote, possibly due to third-party pricing failure. | | `PERMIT_FAILED` | Permit signature validation failed for token approvals. | *** ## Example Error Responses **Invalid Input Currency** ```json theme={null} { "message": "Invalid input or output currency", "errorCode": "INVALID_INPUT_CURRENCY" } ``` ### No Routes Found ```json theme={null} { "message": "No routes found", "errorCode": "NO_SWAP_ROUTES_FOUND" } ``` ### Destination Transaction Failed ```json theme={null} { "message": "Destination transaction failed", "errorCode": "DESTINATION_TX_FAILED", "errorData": "execution reverted" } ``` # Input Validation Source: https://docs.relay.link/references/api/api_core_concepts/input-validation Perform optional onchain validation of requests By default, Relay quotes are paid for with an unvalidated transfer. This keeps gas cost to an absolute minimum. However, if you want to do your own onchain validation, this is possible. We offer an Attestation API where the Solver will attest to any subset of the order parameters, which you can check onchain. For example, you could validate that: * Correct amount was sent * Request has not already been paid for * Request ID is valid * Request has not expired * Recipient is on a known whitelist ## Guide First of all, you can make a regular request via the /quote/v2 API ``` curl -X POST https://api.relay.link/quote/v2 -H 'Content-Type: application/json' -d '{ "user":"0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "originChainId":1,"destinationChainId":7777777,"originCurrency":"0x0000000000000000000000000000000000000000", "destinationCurrency":"0x0000000000000000000000000000000000000000", "recipient":"0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "tradeType":"EXACT_INPUT", "amount":"700000000000000000", "referrer":"relay.link/swap","useExternalLiquidity":false }' | jq ``` In the steps data we return you'll see the corresponding request id associated to this quote you can take the request id and pass it to the /requests/:requestId/signature API ``` curl -X GET https://api.relay.link/requests/0x910113af21b5771f35712ab2680b578e5551a2e358916d9c540aa4f613418e2a/signature ``` The signature API will get you a response as below: ``` { "requestData": { "originChainId": 1, "originUser": "0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "originCurrency": "0x0000000000000000000000000000000000000000", "originAmount": "700000000000000000", "destinationChainId": 7777777, "destinationUser": "0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e" }, "signature": "0x4c320680bbb7f4d46892cd93eddf04884d997c8a3dbd8af20ca502e6bf5323a07bb467f97e290dfd53350683ea494e4fcb02a445ac129d1741a81c12eed066a11b" } ``` You can use the data to do various validations onchain. The signature is always generated by our EVM solver wallet (`0xf70da97812cb96acdf810712aa562db8dfa3dbef`), which has the same address across all chains we support - below is the signature generation code which you can replicate in Solidity to check that indeed the signed request data is coming from our solver: ``` export const signRequestData = async (requestId: string, requestData: RequestData) => { const packedRequestData = defaultAbiCoder.encode( ["bytes32", "uint256", "bytes32", "bytes32", "uint256", "uint256", "bytes32"], [ requestId, requestData.originChainId, requestData.originUser, requestData.originCurrency, requestData.originAmount, requestData.destinationChainId, requestData.destinationUser, ] ); return getSolver().signMessage(arrayify(keccak256(packedRequestData))); }; ``` # Refunds Source: https://docs.relay.link/references/api/api_core_concepts/refunds Learn how Relay handles refunds. Relay strives for 100% successful bridges & swaps. Occasionally, due to a variety of possible circumstances, a bridge or swap might be refunded instead of completed. Learn more about Relay's refund logic and how it works. ## What is a refund? A refund is returned funds to a user due to a failed swap or bridge. Relay will refund the user's funds on the destination chain. If the destination chain is unavailable, Relay will refund the user's funds on the deposit chain. The destination chain might be unavailable for a variety of reasons like a network outage. ## What causes refunds to happen? A refund is triggered when the swap or bridge fails to complete. A failure can occur for a variety of reasons including but not exclusive to: * *Transactions Reverting:* Before giving a quote, we simulate the transaction to ensure it will succeed. Between the time the quote is given and the user accepts & deposits funds, the transaction could revert. Transactions reverting could be caused by if there is a contested mint or execution was delayed. * *User's Wallet Address is Different Than Transaction Depositing Address:* This happens when the user switches wallets right before signing the transaction. Users must execute the transaction with the wallet address that received the quote. * *User Deposits Too Little:* The user explicitly deposits a lower amount than Relay requested. * *Destination Chain is Unavailable:* Occasionally, the destination chain will have a network outage beyond our control. An example could be an RPC that is down or the chain is experiencing a large reorganization. * *User Sends Deposits With the Same Request ID in the Calldata Multiple Times* * *User Send Deposits Without a Request ID in the Calldata* * *Quote Regeneration leads to high slippage*: Quotes are regenerated upon execution. Each quote contains a `currencyOut.minimumAmount` which details the minimum a user will receive, if regeneration leads to a lower `currencyOut.minimumAmount`, the user will be refunded. ## Refund amounts and costs The refund amount may be less than what you originally deposited due to several factors: * **Gas costs are deducted:** The gas cost of processing the refund transaction is deducted from the refunded amount. * **Origin swaps:** If your transaction included an origin swap, you will only receive what the depository contract actually received after the swap, not your original deposit amount. For example, if you send us a memecoin it will be swapped to USDC as a part of the deposit transaction. In the case of a refund we will refund in USDC for the amount the deposit contract received after the swap into USDC minus the cost of gas. ## When is there no refund given? If the refund amount is not enough to cover the cost of gas, Relay will mark the bridge or swap as failed & no refund will be sent. ## What currency is used for refunds? The refund currency can vary by route. Relay will always attempt to refund the user in the original currency they sent. Usually this is one of the currencies in the `solverCurrencies` in the [chain api response](/references/api/get-chains), which are native or stable coins. ## Are refunds automatic & instant? Yes, refunds happen automatically when swaps or bridges fail to complete. If a failure occurs, Relay will refund the user's funds almost instantly. Please remember no refund is given if the refund amount cannot cover the cost of gas. ## How can I force a refund for testing purposes? You can force a refund by passing `debug-force-refund` as the referrer. ```bash cURL theme={null} curl -X POST \ 'https://api.relay.link/quote/v2' \ -H 'Content-Type: application/json' \ -d '{ "referrer": "debug-force-refund", "user":"0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "originChainId":8453, "destinationChainId": 10, "originCurrency":"0x0000000000000000000000000000000000000000", "destinationCurrency":"0x0000000000000000000000000000000000000000", "recipient":"0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "tradeType":"EXACT_INPUT", "amount":"1000000000000000000" }' ``` # Understanding Step Execution Source: https://docs.relay.link/references/api/api_core_concepts/step-execution Tip: For advanced integrations, we recommend using Relay APIs. For simpler use cases, the [Relay SDK](/references/relay-kit/sdk/installation) automates steps and provides helpful callbacks, reducing manual effort. ## Step Execution using the API When executing orders using the API directly, there are often multiple steps, like submitting a deposit transaction to the solver or signing a message. These steps differ based on the desired action and best route to execute the action. To make this simple, you can use the [quote endpoint](/references/api/get-quote-v2), which return the exact steps that you need to follow, including descriptions that you can display to users. The flow looks like this: Call the [Get Quote API](/references/api/get-quote-v2) to start your execution. Iterate through the steps and the items within a step, taking the necessary actions. Steps with no items, or an empty array of items, should be skipped. If step item data is missing then polling the api is necessary. As mentioned above each step contains an array of one or more items. These items need to be iterated and completed depending on the kind of step ([signature](#signature-step) or [transaction](#transaction-step)). After a step is signed or submitted you should check on the progress of the fill Once all the steps are complete you can consider the action complete and notify the user. ### Signature Step A message that needs to be signed. Every signature comes with sign data and post data. The first action we need to take is to sign the message, keep in mind the signatureKind and use the appropriate signing method. Refer to your crypto library of choice on how to sign (viem, web3, etc). ```javascript Signing EIP191 theme={null} // Creating an `EIP-191` signature involves signing a message. // When you receive a `signatureKind: "eip191"` from the API, you will sign the value associated with the `message` property. // In the case below, the message `Sign in to Blur...` will be signed, and later posted to the API. // The step item also contains a `post` object detailing the endpoint to post the signed message to. const message = { signatureKind: "eip191", message: "Sign in to Blur\n\nChallenge: e2483db1d9d66d2b77f47a8dfa13a15a0e7df5aee2287ade4bf313d6d4bdd0e0", }; const eip191Message = "\x19Ethereum Signed Message:\n" + message.message.length + message.message; ``` ```javascript Signing EIP712 theme={null} //Creating an EIP-712 signature involves signing a structured object composed of a domain, types, and values. //When receiving a signatureKind: "eip712" from the API, you'll need to structure the signature in eth_signTypedData_v4 format. //Below, you'll find an example that illustrates how the domain, types, and values can be structured to create the signature. const data = { signatureKind: "eip712", domain: { name: "Seaport", version: "1.5", chainId: 137, verifyingContract: "0x00000000000000adc04c56bf30ac9d3c0aaf14dc", }, types: { OrderComponents: [ { name: "offerer", type: "address", }, { name: "zone", type: "address", }, { name: "offer", type: "OfferItem[]", }, { name: "consideration", type: "ConsiderationItem[]", }, { name: "orderType", type: "uint8", }, { name: "startTime", type: "uint256", }, { name: "endTime", type: "uint256", }, { name: "zoneHash", type: "bytes32", }, { name: "salt", type: "uint256", }, { name: "conduitKey", type: "bytes32", }, { name: "counter", type: "uint256", }, ], OfferItem: [ { name: "itemType", type: "uint8", }, { name: "token", type: "address", }, { name: "identifierOrCriteria", type: "uint256", }, { name: "startAmount", type: "uint256", }, { name: "endAmount", type: "uint256", }, ], ConsiderationItem: [ { name: "itemType", type: "uint8", }, { name: "token", type: "address", }, { name: "identifierOrCriteria", type: "uint256", }, { name: "startAmount", type: "uint256", }, { name: "endAmount", type: "uint256", }, { name: "recipient", type: "address", }, ], }, value: { kind: "single-token", offerer: "0xd6044091d0b41efb3402ca05ba4068f969fdd9e4", zone: "0x0000000000000000000000000000000000000000", offer: [ { itemType: 1, token: "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270", identifierOrCriteria: "0", startAmount: "1000000000000000", endAmount: "1000000000000000", }, ], consideration: [ { itemType: 2, token: "0x362cc74e031091992cdf0e07be54804c7cce9240", identifierOrCriteria: "32", startAmount: "1", endAmount: "1", recipient: "0xd6044091d0b41efb3402ca05ba4068f969fdd9e4", }, ], orderType: 0, startTime: 1691165262, endTime: 1691424522, zoneHash: "0x0000000000000000000000000000000000000000000000000000000000000000", salt: "0x07b88f641d4da48b00000000000000001fc57a14aea256b9eb70bf364ce57c64", conduitKey: "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000", counter: "0", signature: "0x0000000000000000000000000000000000000000000000000000000000000000", }, primaryType: "OrderComponents", }; ``` **Posting the data** After the message is signed the second action is to submit the `post` body to the `endpoint` provided in the `post` data. You'll also need to provide the signature that was generated from the sign data as a query parameter. If the request is successful we can mark the step item as complete locally. The design pattern of this API is completely generic, allowing for automatic support of new types of liquidity without needing to update the app. This data can be fed directly into an Ethereum library such as viem, making it easy to sign and submit the exact data that is needed. ### Transaction Step A transaction that needs to be submitted onchain. After the transaction is submitted onchain you can poll the step items `check` endpoint. The endpoint will return a status which when successful will return 'success'. The step item can then be successfully marked as complete. *`Note that the transaction step item contains a chainId for which the transaction should be submitted on.`* *** ## Step IDs Each step has a unique `id` that identifies the type of action required. Understanding these step IDs helps you build better UX and handle each step appropriately. ### Transaction Steps | Step ID | Description | When Used | | --------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------- | | [`deposit`](#deposit) | Deposit funds to the relayer for cross-chain execution | Used for cross-chain swaps and bridges. | | [`approve`](#approve) | Approve the transfer of an ERC20 token | Required before `deposit` or `swap` steps for ERC20 tokens the solver doesn't hold. | | `approval` | Approve token for deposit | Token approval for deposit operations. | | `swap` | Execute a same-chain token swap | For same-chain swaps. May require an approval step first for EVM swaps. | | [`send`](#send) | Send funds to the recipient | When sending the same asset on the same chain to a different recipient. | ### Signature Steps | Step ID | Description | When Used | | --------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`authorize`](#authorize) | Sign authorization for specific operations | Used for: (1) Claiming [app fees](/features/app-fees) - EIP-191 signature to verify wallet ownership, (2) [Hyperliquid](/references/api/api_guides/hyperliquid-support) v2 deposits - nonce-mapping signature. | | [`authorize1`](#authorize1) | Sign to approve swap of tokens | Main flow for cross-chain permits Permit/Permit2. | | [`authorize2`](#authorize2) | Sign to approve transfer of tokens | Same-chain swap permits. | ### Common Step Flows Depending on the action and tokens involved, Relay chains steps together. Here are common flows you may encounter: | Flow | Description | | --------------------------------------------- | ----------------------------------------------- | | [`deposit`](#deposit) | Single step for cross-chain swaps/bridges | | `swap` | Single step for same-chain swaps | | [`approve`](#approve) → [`deposit`](#deposit) | Approval needed before deposit for ERC20 tokens | | [`approve`](#approve) → `swap` | Same-chain swap requiring approval | | [`authorize1`](#authorize1) | Cross-chain permit-based transfer (gasless) | *** ## Step ID Examples Below are detailed examples of each step type you may encounter when working with the Quote API. ### deposit Deposit funds to the relayer to execute the swap. Used when the Relay solver holds the destination token. ```json expandable theme={null} { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for ETH", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "to": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "data": "0x58109c", "value": "995010715204139091", "maxFeePerGas": "18044119466", "maxPriorityFeePerGas": "2060264926", "chainId": 1, "gas": 21064 }, "check": { "endpoint": "/intents/status/v3?requestId=0x341b28c6467bfbffb72ad78ec5ddf1f77b8f9c79be134223e3248a7d4fcd43b6", "method": "GET" } } ] } ``` ### approve An approval transaction to approve the transfer of an ERC20 token. Always followed by an additional step (`deposit` or `swap`). ```json expandable theme={null} { "id": "approve", "action": "Confirm transaction in your wallet", "description": "Sign an approval for USDC", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0x095ea7b3...", "value": "0", "chainId": 8453, "maxFeePerGas": "5937131", "maxPriorityFeePerGas": "1009381" }, "check": { "endpoint": "/intents/status/v3?requestId=0x...", "method": "GET" } } ] } ``` ### send Send funds to a recipient on the same chain. ```json expandable theme={null} { "id": "send", "action": "Confirm transaction in your wallet", "description": "Send funds to the recipient", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "data": "0xa9059cbb00000000000000000000000036329d1ff4b31ec85280a86e7cf58fca7c005ed000000000000000000000000000000000000000000000000000000002540be400", "value": "0", "chainId": 8453, "maxFeePerGas": "5937131", "maxPriorityFeePerGas": "1009381" }, "check": { "endpoint": "/intents/status/v3?requestId=0xec7244ebc6c9ce3a344f9eeb6c84df921b55e0c06b6cb25a49c167fbeafb7e1b", "method": "GET" } } ], "requestId": "0xec7244ebc6c9ce3a344f9eeb6c84df921b55e0c06b6cb25a49c167fbeafb7e1b" } ``` ### authorize Sign authorization for specific operations. This step is used in two scenarios: 1. **Claiming App Fees**: Signs an EIP-191 message to verify wallet ownership before claiming accrued app fees. 2. **Hyperliquid Deposits**: Signs a nonce-mapping signature for v2 Hyperliquid deposits. ```json expandable App Fee Claim Example theme={null} { "id": "authorize", "action": "Sign authorization", "description": "Authorize claiming funds", "kind": "signature", "items": [ { "status": "incomplete", "data": { "sign": { "signatureKind": "eip191", "message": "0xa344c6123e3da9fc3f9c81edeab0b2eb39f2ea80ba54a6e0ad00123dc180619c" }, "post": { "endpoint": "/execute/permits", "method": "POST", "body": { "kind": "claim", "requestId": "0xa344c6123e3da9fc3f9c81edeab0b2eb39f2ea80ba54a6e0ad00123dc180619c" } } } } ] } ``` ### authorize1 The main flow for cross-chain permits. Uses Permit2 or TransferWithAuthorization (EIP-3009). ```json expandable theme={null} { id: "authorize1", action: "Sign authorization", description: "Sign to approve swap of USDC for ETH", kind: "signature", items: [ { status: "incomplete", data: { sign: { signatureKind: "eip712", types: { TransferWithAuthorization: [ { name: "from", type: "address", }, { name: "to", type: "address", }, { name: "value", type: "uint256", }, { name: "validAfter", type: "uint256", }, { name: "validBefore", type: "uint256", }, { name: "nonce", type: "bytes32", }, ], }, domain: { name: "USD Coin", version: "2", chainId: 8453, verifyingContract: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", }, primaryType: "TransferWithAuthorization", value: { from: "0x03508bb71268bba25ecacc8f620e01866650532c", to: "0xf70da97812cb96acdf810712aa562db8dfa3dbef", value: "1000000", validAfter: 0, validBefore: 1764948419, nonce: "0xc1333d553004054a9c9a45ceedfae13040c7f064736c29b5c7ebc1c6b11a0a71", }, }, post: { endpoint: "/execute/permits", method: "POST", body: { kind: "eip3009", requestId: "0xfadc232794fbef5794f1e28b780c3d4ffd19df3f0f42f1e3b8f873abf11e79b2", api: "swap", }, }, }, check: { endpoint: "/intents/status/v3?requestId=0xfadc232794fbef5794f1e28b780c3d4ffd19df3f0f42f1e3b8f873abf11e79b2", method: "GET", }, }, ], } ``` ### authorize2 Sign authorization for same-chain swap permits using PermitBatchWitnessTransferFrom. ```json expandable theme={null} { "id": "authorize2", "action": "Sign authorization", "description": "Sign to approve transfer of USDC", "kind": "signature", "items": [ { "status": "incomplete", "data": { "sign": { "signatureKind": "eip712", "domain": { "name": "Permit2", "chainId": 8453, "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3" }, "types": { "PermitBatchWitnessTransferFrom": [ { "name": "permitted", "type": "TokenPermissions[]" }, { "name": "spender", "type": "address" }, { "name": "nonce", "type": "uint256" }, { "name": "deadline", "type": "uint256" }, { "name": "witness", "type": "RelayerWitness" } ], "TokenPermissions": [ { "name": "token", "type": "address" }, { "name": "amount", "type": "uint256" } ], "RelayerWitness": [ { "name": "relayer", "type": "address" }, { "name": "refundTo", "type": "address" }, { "name": "nftRecipient", "type": "address" }, { "name": "call3Values", "type": "Call3Value[]" } ], "Call3Value": [ { "name": "target", "type": "address" }, { "name": "allowFailure", "type": "bool" }, { "name": "value", "type": "uint256" }, { "name": "callData", "type": "bytes" } ] }, "value": { "permitted": [ { "token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "1000000" } ], "nonce": "3073764008035", "deadline": 1764948471, "spender": "0xbbbfd134e9b44bfb5123898ba36b01de7ab93d98", "witness": { "relayer": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "refundTo": "0x03508bb71268bba25ecacc8f620e01866650532c", "nftRecipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "call3Values": [ { "target": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "allowFailure": false, "value": "0", "callData": "0x095ea7b3..." } ] } }, "primaryType": "PermitBatchWitnessTransferFrom" }, "post": { "endpoint": "/execute/permits", "method": "POST", "body": { "kind": "permit2", "requestId": "0x4d1756eae7d23122047cbe2f11a749ca8275983e4d8757a8b44af9d1b829df39", "api": "user-swap" } } }, "check": { "endpoint": "/intents/status/v3?requestId=0x4d1756eae7d23122047cbe2f11a749ca8275983e4d8757a8b44af9d1b829df39", "method": "GET" } } ] } ``` *** ### Full Quote Example ```json expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Deposit funds for executing the calls", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "to": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "data": "0x58109c", "value": "995010715204139091", "maxFeePerGas": "18044119466", "maxPriorityFeePerGas": "2060264926", "chainId": 1, "gas": 21064 }, "check": { "endpoint": "/intents/status/v3?requestId=0x341b28c6467bfbffb72ad78ec5ddf1f77b8f9c79be134223e3248a7d4fcd43b6", "method": "GET" } } ] } ], "fees": { "gas": "384398515652800", "gasCurrency": "eth", "relayer": "-4989478842712964", "relayerGas": "521157287036", "relayerService": "-4990000000000000", "relayerCurrency": "eth" }, "breakdown": { "value": "1000000000000000000", "timeEstimate": 10 }, "balances": { "userBalance": "54764083517303347", "requiredToSolve": "995010521157287036" } } ``` Along with the steps you'll see that the following objects are returned: `fees`, `breakdown` and `balances`. Information about fees is detailed [here](/references/api/api_core_concepts/fees). `breakdown` pertains to time estimation for the execution, broken down by value. The `balances` object is in regards to the user and how much they require to solve for the execution. ### Checking the fill status Along with the step data there's an optional check object. You can use this object to check if the status of the transaction is complete. The object details the method and the endpoint to request. You should poll this until the endpoint returns `success`. ```json theme={null} { "check": { "endpoint": "/intents/status/v3?requestId=0x341b28c6467bfbffb72ad78ec5ddf1f77b8f9c79be134223e3248a7d4fcd43b6", "method": "GET" } } ``` The check api will always return a status, to get a full list of statuses, please refer to the [Get Status](/references/api/get-intents-status-v3) API Reference. *** ### SDK Reference To review the official way the SDK handles execution, please refer to the [executeSteps](https://github.com/relayprotocol/relay-kit/blob/main/packages/sdk/src/utils/executeSteps/index.ts) file which implements executing steps detailed above. # Surplus & Shortage Source: https://docs.relay.link/references/api/api_core_concepts/surplus-and-shortage Learn how Relay handles surpluses or shortages. Relay allows for users to instantly bridge or swap funds cross-chain while saving on gas. There are swaps or bridges where a surplus or shortage can happen. To understand how we handle either, we first need to understand how an origin operation can produce a surplus or shortage. ## Origin Operation Types When starting a bridge or swap, Relay labels them as origin operations. After submission, origin operations send their output to the solver to complete the bridge or swap. There are three possible outcomes for an origin operation: 1. **Exact**: When the origin operation matches the solver's input exactly, the destination operation proceeds as initially quoted. 2. **Surplus**: When the solver executes the origin operation input, there is positive slippage after the swap or bridge. This results in a surplus or extra funds. 3. **Shortage**: When the solver executes the origin operation input, there is negative slippage after the swap or bridge. This results in a shortage or less funds. ## How are surpluses or shortages handled? An origin operation can produce three possible outcomes. Depending on the outcome will determine how Relay handles the bridge or swap. 1. **Exact**: The expected amount is received. The destination operation proceeds as initially quoted. 2. **Surplus**: The solver receives more than expected. The quoted rate is used as a heuristic to determine whether to proceed with the operation. Specifically, if the solver can guarantee that the input amount can be swapped for at least the quoted rate of the expected amount, the operation continues. Surpluses typically result in the user receiving more than expected on the destination chain. 3. **Shortage**: The solver receives less than expected. The quoted rate is used as a heuristic to determine whether to proceed with the operation. Specifically, if the solver can guarantee that the input amount can be swapped for at least the quoted rate of the expected amount, the operation continues. Shortages typically result in the user receiving less than expected on the destination chain. ## Surplus Example Consider the following quote to a user: * **Quoted Transaction**: 100 USDC → 0.01 ETH → 0.01 ETH → 100 DAI However, during the origin operation, this happens: * **Actual Transaction**: 100 USDC → 0.011 ETH In this case, the new output amount (0.011 ETH) is passed to the destination swap. The operation continues as long as the solver can swap 0.011 ETH at a rate of at least 0.01 ETH per 100 DAI, resulting in the user receiving a little extra. ## Shortage Example Consider the following quote to a user: * **Quoted Transaction**: 100 USDC → 0.01 ETH → 0.01 ETH → 100 DAI However, during the origin operation, this happens: * **Actual Transaction**: 100 USDC → 0.009 ETH In this case, the new output amount (0.009 ETH) is passed to the destination swap. The operation continues as long as the solver can swap 0.009 ETH at a rate of at least 0.01 ETH per 100 DAI, resulting in the user receiving a little less. # Trade Types Source: https://docs.relay.link/references/api/api_core_concepts/trade-types Learn trade types Relay supports. Relay supports three trade types: `exact_input`, `expected_output` and `exact_output`. ## Exact Input The first supported type is `EXACT_INPUT`. In this order type, the user specifies how much of the input currency they want to provide for a swap or bridge. The user is told how much of the output token they will receive for a given input. This trade type fails if the input amount is too small to cover associated fees with the request. Associated fees can include but are not limited to relay fees, app fees, and gas fees. ## Expected Output The second supported type is `EXPECTED_OUTPUT`. In this order type, the user quotes via specifying the general amount of output currency they expected to receive from the swap or bridge. The user is then told how much of the origin token this will require. The resulting output amount received can be less or more than what is requested, depending on slippage. This trade type automatically accounts for any associated fees including relay fees, app fees, and gas fees. We recommend using `EXPECTED_OUTPUT` for when an exact output amount is not necessary, or if calls do not need to be executed on destination. ## Exact Output The third supported type is `EXACT_OUTPUT`. In this order type, the user specifies exactly how much of the output currency they want to receive from the swap or bridge. The user is told how much of the origin token this will require to receive exactly the output amount. If the exact requested amount out cannot be filled, the request is failed and the user is refunded on origin. This trade type automatically accounts for any associated fees including relay fees, app fees, and gas fees. We recommend using `EXACT_OUTPUT` for bridge requests that involve destination calls, like minting using an exact erc20 amount # Wallet Detection Source: https://docs.relay.link/references/api/api_core_concepts/wallet-detection How to detect wallet types and set the explicitDeposit parameter for optimal user experience # Wallet Detection When using Protocol v2 on EVM chains, you need to detect whether your user's wallet is an **EOA (Externally Owned Account)** or a **Smart Wallet** to properly set the `explicitDeposit` parameter in your quote requests. ## Understanding Wallet Types **EOAs (Externally Owned Accounts)** are regular wallets controlled by a private key, like MetaMask, Coinbase Wallet, and hardware wallets. These wallets can only execute one transaction at a time and cannot batch multiple operations together. **Smart Wallets** are contract-based wallets that can execute multiple operations in a single transaction. Examples include Gnosis Safe, Argent, and ERC-4337 compatible wallets. Smart wallets can batch approvals and deposits atomically, making multi-step flows more user-friendly. ## The Problem Protocol v2 uses the Relay Depository contracts to enable trustless cross-chain payments. For ERC20 token deposits, there are two possible transaction flows: **Explicit Deposits (2 transactions):** 1. User approves the Relay Depository contract to spend their tokens 2. User calls the `depositErc20` function to transfer tokens with an order ID **Implicit Deposits (1 transaction):** 1. User transfers tokens directly to the solver with order ID encoded in transaction calldata The challenge is that **implicit deposits only work reliably for EOAs**, while **explicit deposits work for all wallet types but create poor UX for EOAs** who must execute two separate transactions. ## The Solution By detecting wallet type, you can set the `explicitDeposit` parameter to choose the optimal flow: * **`explicitDeposit: false`** - Enables implicit deposits for EOAs (1 transaction) * **`explicitDeposit: true`** - Uses explicit approval flow for smart wallets (2 transactions, can be batched) * **Default**: `true` (safe fallback for all wallet types) ## When to Use Wallet Detection You should implement wallet detection when: * Using `protocolVersion: "v2"` or `"preferV2"` * User is on an EVM chain * User is depositing ERC20 tokens (not native ETH) * You want to optimize transaction flow for your users For native ETH deposits, wallet detection is not necessary since no approval is required. ## Detection Methods ### 1. Smart Wallet Capabilities (EIP-5792) Check if the wallet advertises smart wallet capabilities: ```typescript theme={null} const capabilities = await wallet.getCapabilities({ account: wallet.account, chainId, }); const hasSmartWalletCapabilities = Boolean( capabilities?.atomicBatch?.supported || capabilities?.paymasterService?.supported || capabilities?.auxiliaryFunds?.supported || capabilities?.sessionKeys?.supported ); ``` ### 2. Contract Code Detection Check if the wallet address has deployed contract code: ```typescript theme={null} const code = await publicClient.getCode({ address: walletAddress, }); const hasCode = Boolean(code && code !== "0x"); ``` ### 3. EIP-7702 Delegation Detection Check if the wallet uses EIP-7702 delegation (code starts with `0xef01`): ```typescript theme={null} const isEIP7702Delegated = Boolean( code && code.toLowerCase().startsWith("0xef01") ); ``` **Note**: EIP-7702 delegated accounts are technically EOAs but gain smart wallet capabilities through authorization signatures. They require `explicitDeposit: true` for reliable batching and execution. ### Final Determination ```typescript theme={null} const isSmartWallet = hasSmartWalletCapabilities || hasCode || isEIP7702Delegated; const isEOA = !isSmartWallet; // Set explicitDeposit based on wallet type const explicitDeposit = !isEOA || isEIP7702Delegated; ``` ## Safety Checks ### Zero Native Balance Check If the user has zero native currency (ETH, MATIC, etc.), force `explicitDeposit: true` to avoid failed transactions due to insufficient gas: ```typescript theme={null} if (nativeBalance === 0n) { explicitDeposit = true; } ``` ### Low Transaction Count Check If the wallet has 1 or fewer transactions on the source chain, force `explicitDeposit: true` as this may indicate a new wallet requiring explicit handling: ```typescript theme={null} if (transactionCount <= 1) { explicitDeposit = true; } ``` ## Implementation Example Here's a complete implementation for detecting wallet types: ```typescript theme={null} export const detectWalletType = async ( wallet: WalletClient, chainId: number ) => { if (!wallet.account) { return { isEOA: false, isEIP7702Delegated: false }; } try { // Check 1: Smart wallet capabilities let hasSmartWalletCapabilities = false; try { const capabilities = await wallet.getCapabilities({ account: wallet.account, chainId, }); hasSmartWalletCapabilities = Boolean( capabilities?.atomicBatch?.supported || capabilities?.paymasterService ?.supported || capabilities?.auxiliaryFunds ?.supported || capabilities?.sessionKeys?.supported ); } catch (capabilitiesError) { // Wallet doesn't support capabilities API } // Check 2 & 3: Contract code detection const publicClient = createPublicClient({ chain: getChainById(chainId), transport: http(), }); const code = await publicClient.getCode({ address: wallet.account.address, }); const hasCode = Boolean(code && code !== "0x"); const isEIP7702Delegated = Boolean( code && code.toLowerCase().startsWith("0xef01") ); const isSmartWallet = hasSmartWalletCapabilities || hasCode || isEIP7702Delegated; const isEOA = !isSmartWallet; return { isEOA, isEIP7702Delegated }; } catch (error) { // On error, assume not EOA (safer default) return { isEOA: false, isEIP7702Delegated: false }; } }; ``` ## Example Scenarios | Wallet Type | Detection Result | explicitDeposit | User Experience | | ---------------------------------- | --------------------- | --------------- | ------------------------------- | | Regular EOA (MetaMask, Coinbase) | EOA detected | `false` | 1 transaction | | Smart Wallet (Gnosis Safe, Argent) | Smart wallet detected | `true` | 2 transactions (can be batched) | | EIP-7702 Delegated EOA | Smart wallet detected | `true` | 2 transactions (can be batched) | | Detection Error | Unknown | `true` | 2 transactions (safe fallback) | ## API Usage Include the `explicitDeposit` parameter in your quote requests: ```typescript theme={null} const quoteRequest = { user: userAddress, originChainId: sourceChainId, destinationChainId: targetChainId, originCurrency: sourceCurrencyAddress, destinationCurrency: targetCurrencyAddress, recipient: recipientAddress, amount: amountInWei, tradeType: "EXACT_INPUT", protocolVersion: "preferV2", explicitDeposit: explicitDepositValue, // Based on your detection }; ``` ## Expected User Experience ### For EOA Users (`explicitDeposit: false`) ``` ✅ Step 1: Transfer USDC directly to solver with order ID encoded in calldata ✅ One wallet confirmation ✅ Faster execution and lower cost ✅ Simpler, more intuitive user experience ``` ### For Smart Wallet Users (`explicitDeposit: true`) ``` ✅ Step 1: Approve Relay Depository contract to spend USDC ✅ Step 2: Call depositErc20() to deposit USDC ✅ Steps can be batched into single confirmation (ERC-4337/EIP-5792) ✅ Reliable, predictable execution through Depository architecture ✅ Full smart wallet capabilities (fee sponsorship, session keys, etc.) ``` ## Best Practices 1. **Handle errors gracefully** - Return `{ isEOA: false, isEIP7702Delegated: false }` on errors (safer default) 2. **Add timeout** - Detection should timeout after \~1 second to avoid blocking UX 3. **Cache results** - Cache detection results per wallet/chain combination to avoid repeated calls 4. **Include safety checks** - Always check native balance and transaction count as additional safety measures 5. **Only use with Protocol v2** - The `explicitDeposit` parameter is only relevant for Protocol v2 ## Troubleshooting ### Detection Returns Incorrect Results * Ensure you're using the correct chain ID * Verify the RPC endpoint is returning accurate data * Check that wallet capabilities API is properly supported ### Quote Requests Failing * Verify `explicitDeposit` is only included when using v2 protocol * Ensure user has sufficient native currency for gas * Check that the wallet address is valid By implementing proper wallet detection, you can provide optimal user experience for both EOA and smart wallet users while ensuring transaction reliability. # Bitcoin Support Source: https://docs.relay.link/references/api/api_guides/bitcoin How to deposit and withdraw on Bitcoin from any Relay chain Relay supports deposits to and withdrawals from Bitcoin across any supported chain. This guide covers the Bitcoin-specific parameters, PSBT signing, balance calculation, and API/SDK usage required for integration. ## Required Information Bitcoin transactions differ from EVM chains in several ways. The sections below highlight the Bitcoin-specific parameters and requirements for your integration. Bitcoin addresses are case-sensitive. ### SDK Properties | Action | Parameter | Input | Description | | --------------------- | ------------ | ------------------------------------------- | -------------------------------------- | | Deposit to Bitcoin | `toChainId` | `8253038` | Chain ID assigned to Bitcoin in Relay. | | | `recipient` | User's Bitcoin Address | Valid Bitcoin address. | | | `toCurrency` | `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8` | Native BTC identifier. | | Withdraw from Bitcoin | `chainId` | `8253038` | Chain ID assigned to Bitcoin in Relay. | | | `currency` | `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8` | Native BTC identifier. | ### Bitcoin Currency | Token | Currency Address | Symbol | Decimals | | ----- | ------------------------------------------- | ------ | -------- | | BTC | `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8` | BTC | 8 | ## PSBT Signing Bitcoin transactions use **Partially Signed Bitcoin Transactions (PSBT)** for signing. When withdrawing from Bitcoin, the quote response returns a PSBT instead of standard EVM calldata. The [Bitcoin wallet adapter](/references/relay-kit/sdk/adapters#what-adapters-are-available-out-of-the-box) handles PSBT parsing, finalization, and broadcasting. Provide a `signPsbt` callback that signs the PSBT and returns the signed result in base64 format. ## UTXOs and Bitcoin Balance Unlike EVM chains that track account balances directly, Bitcoin uses a **UTXO (Unspent Transaction Output)** model. Each address balance equals the sum of unspent outputs it has received minus outputs it has spent. To calculate a Bitcoin address balance, query the [mempool.space API](https://mempool.space/docs/api/rest#get-address) and compute the difference between funded and spent transaction outputs: ```typescript theme={null} async function getBitcoinBalance(address: string) { const response = await fetch( `https://mempool.space/api/address/${address}` ); const data = await response.json(); const fundedTxo = data.chain_stats.funded_txo_sum; const spentTxo = data.chain_stats.spent_txo_sum; const pendingSpent = data.mempool_stats.spent_txo_sum; // Balance in satoshis const balance = BigInt(fundedTxo) - BigInt(spentTxo) - BigInt(pendingSpent); return { balance, // Confirmed balance in satoshis pendingBalance: BigInt(pendingSpent), // Pending outgoing }; } ``` Balance values are returned in **satoshis** (1 BTC = 100,000,000 satoshis). ## API ### Params | Action | Parameter | Input | Description | | --------------------- | --------------------- | ------------------------------------------- | -------------------------------------- | | Deposit to Bitcoin | `recipient` | User's Bitcoin Address | Valid Bitcoin address. | | | `destinationChainId` | `8253038` | Chain ID assigned to Bitcoin in Relay. | | | `destinationCurrency` | `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8` | Native BTC identifier. | | Withdraw from Bitcoin | `user` | User's Bitcoin Address | Valid Bitcoin address. | | | `originChainId` | `8253038` | Chain ID assigned to Bitcoin in Relay. | | | `originCurrency` | `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8` | Native BTC identifier. | ### Execution Bitcoin uses the standard Relay API flow. Review the [execution steps](/references/api/api_core_concepts/step-execution) documentation, then use the [Get Quote](/references/api/get-quote-v2) endpoint. ### Example: Withdraw from Bitcoin to Base ```bash Request theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa", "originChainId": 8253038, "originCurrency": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "destinationChainId": 8453, "destinationCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "tradeType": "EXACT_INPUT", "amount": "100000" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for USDC", "kind": "transaction", "requestId": "0x90399ed9112ee951b6312fae28196e02384f6fd26c5d40a3d51bf2b0fbb1ae80", "depositAddress": "", "items": [ { "status": "incomplete", "data": { "psbt": "70736274ff0100fdb4010200000007d5..." }, "check": { "endpoint": "/intents/status/v3?requestId=0x90399ed9112ee951b6312fae28196e02384f6fd26c5d40a3d51bf2b0fbb1ae80", "method": "GET" } } ] } ], "fees": { "gas": { "currency": { "chainId": 8253038, "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "symbol": "BTC", "name": "Bitcoin", "decimals": 8, "metadata": { "logoURI": "https://assets.relay.link/icons/currencies/btc.png", "verified": true } }, "amount": "3639", "amountFormatted": "0.00003639", "amountUsd": "2.654085", "minimumAmount": "3639" }, "relayer": { "currency": { "chainId": 8253038, "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "symbol": "BTC", "name": "Bitcoin", "decimals": 8, "metadata": { "logoURI": "https://assets.relay.link/icons/currencies/btc.png", "verified": true } }, "amount": "660", "amountFormatted": "0.0000066", "amountUsd": "0.481367", "minimumAmount": "660" } }, "details": { "operation": "swap", "sender": "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currencyIn": { "currency": { "chainId": 8253038, "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "symbol": "BTC", "name": "Bitcoin", "decimals": 8, "metadata": { "logoURI": "https://assets.relay.link/icons/currencies/btc.png", "verified": true } }, "amount": "100000", "amountFormatted": "0.001", "amountUsd": "72.934456", "minimumAmount": "100000" }, "currencyOut": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", "verified": true } }, "amount": "72469395", "amountFormatted": "72.469395", "amountUsd": "72.446929", "minimumAmount": "71020007" }, "timeEstimate": 4 } } ``` ### Example: Deposit to Bitcoin from Base ```bash Request theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "0x03508bb71268bba25ecacc8f620e01866650532c", "originChainId": 8453, "originCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "destinationChainId": 8253038, "destinationCurrency": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "recipient": "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa", "tradeType": "EXACT_INPUT", "amount": "10000000" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "approve", "action": "Confirm transaction in your wallet", "description": "Sign an approval for USDC", "kind": "transaction", "requestId": "0xd24628d1b4707b389e0298ecf86733c9f524599a2e28bdb1458f27f58cc4d6ca", "items": [ { "status": "incomplete", "data": { "from": "0x03508bb71268bba25ecacc8f620e01866650532c", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "chainId": 8453 } } ] }, { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for BTC", "kind": "transaction", "requestId": "0xd24628d1b4707b389e0298ecf86733c9f524599a2e28bdb1458f27f58cc4d6ca", "depositAddress": "", "items": [ { "status": "incomplete", "data": { "from": "0x03508bb71268bba25ecacc8f620e01866650532c", "to": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "chainId": 8453 }, "check": { "endpoint": "/intents/status/v3?requestId=0xd24628d1b4707b389e0298ecf86733c9f524599a2e28bdb1458f27f58cc4d6ca", "method": "GET" } } ] } ], "fees": { "gas": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "238331268682", "amountFormatted": "0.000000238331268682", "amountUsd": "0.000510", "minimumAmount": "238331268682" }, "relayer": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", "verified": true } }, "amount": "678569", "amountFormatted": "0.678569", "amountUsd": "0.678359", "minimumAmount": "678569" } }, "details": { "operation": "swap", "sender": "0x03508bb71268bba25ecacc8f620e01866650532c", "recipient": "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa", "currencyIn": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", "verified": true } }, "amount": "10000000", "amountFormatted": "10.0", "amountUsd": "9.996900", "minimumAmount": "10000000" }, "currencyOut": { "currency": { "chainId": 8253038, "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "symbol": "BTC", "name": "Bitcoin", "decimals": 8, "metadata": { "logoURI": "https://assets.relay.link/icons/currencies/btc.png", "verified": true } }, "amount": "12759", "amountFormatted": "0.00012759", "amountUsd": "9.305707", "minimumAmount": "12504" }, "timeEstimate": 158 } } ``` Amounts are specified in the smallest unit of the currency. For BTC, this is **satoshis** (100000 satoshis = 0.001 BTC). For USDC, this is 6 decimal places (10000000 = 10 USDC). ## SDK To use the SDK with Bitcoin, install and configure the [Bitcoin wallet adapter](/references/relay-kit/sdk/adapters#what-adapters-are-available-out-of-the-box). The adapter handles PSBT signing and transaction broadcasting. ```bash theme={null} npm install @relayprotocol/relay-bitcoin-wallet-adapter ``` For implementation details and code samples, see the [Adapters documentation](/references/relay-kit/sdk/adapters). ## Deposit Addresses (Optional) Relay also supports the [deposit address flow](/features/deposit-addresses), which allows bridging without wallet connection or PSBT signing. When using deposit addresses, there are additional considerations: ### Block Confirmations When using deposit addresses, Relay waits for **1 block confirmation** before processing. Bitcoin's block time averages \~10 minutes but varies significantly. blocks can arrive within minutes of each other or take 30-50 minutes. When polling for deposit address transactions, allow for at least 2-3 blocks worth of time before considering a transaction stalled. You can monitor block times at [mempool.space](https://mempool.space). ### Quote Requirements for Deposit Addresses When using `useDepositAddress: true` with Bitcoin as origin: * The `user` address must be a valid Bitcoin address * The `user` address balance must exceed the quoted amount (otherwise returns `INSUFFICIENT_FUNDS`) * For indicative quotes, use a known high-balance address ("whale address"). The actual deposit can originate from any address. ```bash Request theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa", "originChainId": 8253038, "originCurrency": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "destinationChainId": 8453, "destinationCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "tradeType": "EXACT_INPUT", "amount": "100000", "useDepositAddress": true, "refundTo": "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for USDC", "kind": "transaction", "requestId": "0x1ebf65e0a3f7976ef40e4f83f1e0b46aaa605c567117a442374777214c432961", "depositAddress": "bc1q327vyreq97q3n5ksha8ld998w02hereae20k8n", "items": [ { "status": "incomplete", "data": { "psbt": "70736274ff0100fd67010200000007d5..." }, "check": { "endpoint": "/intents/status/v3?requestId=0x1ebf65e0a3f7976ef40e4f83f1e0b46aaa605c567117a442374777214c432961", "method": "GET" } } ] } ], "fees": { "gas": { "currency": { "chainId": 8253038, "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "symbol": "BTC", "name": "Bitcoin", "decimals": 8, "metadata": { "logoURI": "https://assets.relay.link/icons/currencies/btc.png", "verified": true } }, "amount": "3532", "amountFormatted": "0.00003532", "amountUsd": "2.576453", "minimumAmount": "3532" }, "relayer": { "currency": { "chainId": 8253038, "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "symbol": "BTC", "name": "Bitcoin", "decimals": 8, "metadata": { "logoURI": "https://assets.relay.link/icons/currencies/btc.png", "verified": true } }, "amount": "1966", "amountFormatted": "0.00001966", "amountUsd": "1.434118", "minimumAmount": "1966" } }, "details": { "operation": "swap", "sender": "bc1q4vxn43l44h30nkluqfxd9eckf45vr2awz38lwa", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currencyIn": { "currency": { "chainId": 8253038, "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8", "symbol": "BTC", "name": "Bitcoin", "decimals": 8, "metadata": { "logoURI": "https://assets.relay.link/icons/currencies/btc.png", "verified": true } }, "amount": "100000", "amountFormatted": "0.001", "amountUsd": "72.946000", "minimumAmount": "100000" }, "currencyOut": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", "verified": true } }, "amount": "71567150", "amountFormatted": "71.56715", "amountUsd": "71.544964", "minimumAmount": "70135807" }, "timeEstimate": 4 } } ``` # Bridging Integration Guide Source: https://docs.relay.link/references/api/api_guides/bridging-integration-guide How to integrate the Relay API for bridging and onboarding New to Relay? Start with the [Quickstart Guide](/references/api/quickstart) and work your way through your first cross-chain transaction. ## Before you start While not mandatory for integration, doing the following will improve the UX for your users considerably: * **Verify user balance** - Ensure the user has sufficient funds for the bridge amount plus fees * **Check chain support** - Confirm both origin and destination chains are supported * **Validate quote** - Quotes are revalidated when being filled, keep your quotes as fresh as possible. * **Handle errors** - Implement proper error handling for API requests and transaction failures ## Get a Quote Every action in Relay starts with a Quote, this includes bridging. The quote provides all necessary information including fees, transaction data, and execution steps. Use the [quote endpoint](/references/api/get-quote-v2) to request a bridge quote. ```bash Request theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "0x03508bb71268bba25ecacc8f620e01866650532c", "originChainId": 1, "destinationChainId": 8453, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000", "tradeType": "EXACT_INPUT" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Deposit funds for executing the bridge", "kind": "transaction", "requestId": "0x92b99e6e1ee1deeb9531b5ad7f87091b3d71254b3176de9e8b5f6c6d0bd3a331", "items": [ { "status": "incomplete", "data": { "from": "0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3", "to": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "data": "0x00fad611", "value": "100000000000000000", "chainId": 1 }, "check": { "endpoint": "/intents/status?requestId=0x92b99e6e1ee1deeb9531b5ad7f87091b3d71254b3176de9e8b5f6c6d0bd3a331", "method": "GET" } } ] } ], "fees": { "gas": { "amount": "21000000000000000", "currency": "eth" }, "relayer": { "amount": "5000000000000000", "currency": "eth" } }, "details": { "operation": "bridge", "timeEstimate": 30, "currencyIn": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ethereum", "decimals": 18 }, "amount": "100000000000000000" }, "currencyOut": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ethereum", "decimals": 18 }, "amount": "95000000000000000" } } } ``` You can learn more about quote request parameters and response data [here](/references/api/get-quote-v2). To get a higher rate limit, you can [request an API key](/references/api/api-keys#how-to-get-an-api-key). ## Execute the Bridge After receiving a bridge quote, execute it by processing each step in the response. The execution handles both the origin chain transaction and destination chain fulfillment.\ \ Learn more about step execution using the API [here](/references/api/api_core_concepts/step-execution). ## Monitor Bridge Status You can check the status of a bridge operation at any time using the [status endpoint](/references/api/get-intents-status-v3) with the `requestId` located inside each step object in your [quote response](/references/api/quickstart#request). ```bash Request theme={null} curl "https://api.relay.link/intents/status/v3?requestId=0xed42e2e48c56b06f8f384d66d5f3e6c450fc3a2c7cba19d92a01a649a31a0e94" ``` ```json Response theme={null} { "status": "success", "inTxHashes": [ "0xae0a66324f82bc54299663972b3a48c939af777ff795b13bd72e7755a6a68bb3" ], "txHashes": [ "3exJVjCEMfqVCBN6FBCXPFp6JUmm91pBp9Mp5XWwkoBVAgcr6fM4kZyzdAhoRWtWAvVESMZPGA7G1CibTF8uxaMk" ], "updatedAt": 1769988962883, "originChainId": 8453, "destinationChainId": 792703809 } ``` Poll this endpoint once per second. For a real-time status stream you can subscribe to Relay’s [**websocket server**](/references/api/api_guides/websockets). Learn more about how to check the status of the fill [here](/references/api/api_core_concepts/step-execution#checking-the-fill-status). Learn more about the status lifecycle, see the [status lifecycle diagram](/references/api/quickstart#status-lifecycle). ## See Also * [**App Fees**](/features/app-fees): Monetize your integration by adding a fee (in bps) to every quote. * [**Deposit Addresses**](/features/deposit-addresses): Use Relay deposit addresses to unlock new chains and reduce wallet friction. * [**Fee Sponsorship**](/features/fee-sponsorship): Sponsor fees for your users to reduce friction and improve the user experience. * [**Handling Errors**](/references/api/api_core_concepts/errors): Handle quote errors when using the Relay API. * [**Relay Fees**](/references/api/api_core_concepts/fees): Understand the fees associated with using the Relay API. # Call Execution Integration Guide Source: https://docs.relay.link/references/api/api_guides/calling-integration-guide How to execute cross-chain calls using Relay New to Relay? Start with the [Quickstart Guide](/references/api/quickstart) and work your way through your first cross-chain transaction. You can use Relay cross-chain execution to perform any action (tx) on any chain. This works by specifying the transaction data you wish to execute on the destination chain as part of the initial quoting process. ## Before you start **Origin chain gas required**: The user must have a small amount of native gas token (e.g., ETH) on the origin chain to submit the deposit transaction. While Relay handles destination chain execution and deducts fees from the user's tokens, the initial on-chain deposit still requires native gas — this is an EVM-level requirement. If your users have **zero native tokens**, see [Gasless Swaps](/features/gasless-swaps) to find the right approach, or use [Smart Accounts](/references/api/api_guides/smart_accounts/smart-accounts) (EIP-7702 or ERC-4337) / [Gasless Execution](/features/gasless-execution) for a fully gasless flow. While not mandatory for integration, doing the following will improve the UX for your users considerably: * **Verify user balance** - Confirm user has sufficient funds for amount + fees, including native gas on the origin chain for the deposit transaction * **Check chain support** - Confirm both origin and destination chains are supported * **Validate quote** - Quotes are revalidated when being filled, keep your quotes as fresh as possible. * **Handle errors** - Implement proper error handling for API requests and transaction failures ## Get a Quote To execute a cross-chain transaction, you need to specify the origin chain for payment, the destination chain where the contract is deployed, and the transaction data to execute. Use the [quote endpoint](/references/api/get-quote-v2) with specific parameters for cross-chain calling. ```bash Request theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "0x03508bb71268bba25ecacc8f620e01866650532c", "originChainId": 1, "destinationChainId": 8453, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000", "tradeType": "EXACT_OUTPUT", "txs": [ { "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "value": "100000000000000000", "data": "0xd0e30db0" } ] }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for ETH", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bb71268bba25ecacc8f620e01866650532c", "to": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "data": "0x49290c1c00000000000000000000000003508bb71268bba25ecacc8f620e01866650532c0d0f5bc9e030aa965a11905d83e54a407697e6fcdba5c300875db0fbf93e186b", "value": "100012440985606146", "chainId": 1, "gas": "32713", "maxFeePerGas": "386206132", "maxPriorityFeePerGas": "246844318" }, "check": { "endpoint": "/intents/status/v3?requestId=0xa8de0141e504d2f7ed6b17cfd03455ecc2b17ab4e63120e43c38dce8015c1004", "method": "GET" } } ], "requestId": "0xa8de0141e504d2f7ed6b17cfd03455ecc2b17ab4e63120e43c38dce8015c1004", "depositAddress": "" } ], "fees": { "gas": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "8496534904000", "amountFormatted": "0.000008496534904", "amountUsd": "0.019054", "minimumAmount": "8496534904000" }, "relayer": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "12440985606146", "amountFormatted": "0.000012440985606146", "amountUsd": "0.027900", "minimumAmount": "12440985606146" }, "relayerGas": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "1293116969649", "amountFormatted": "0.000001293116969649", "amountUsd": "0.002900", "minimumAmount": "1293116969649" }, "relayerService": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "11147868636497", "amountFormatted": "0.000011147868636497", "amountUsd": "0.025000", "minimumAmount": "11147868636497" }, "app": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0", "minimumAmount": "0" }, "subsidized": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0", "minimumAmount": "0" } }, "details": { "operation": "swap", "sender": "0x03508bb71268bba25ecacc8f620e01866650532c", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currencyIn": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100012440985606146", "amountFormatted": "0.100012440985606146", "amountUsd": "224.286014", "minimumAmount": "100012440985606146" }, "currencyOut": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "224.258114", "minimumAmount": "100000000000000000" }, "refundCurrency": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100012440985606146", "amountFormatted": "0.100012440985606146", "amountUsd": "224.286014", "minimumAmount": "100012440985606146" }, "totalImpact": { "usd": "-0.027900", "percent": "-0.01" }, "swapImpact": { "usd": "0.000000", "percent": "0.00" }, "expandedPriceImpact": { "swap": { "usd": "0.000000" }, "execution": { "usd": "-0.0229" }, "relay": { "usd": "-0.005" }, "app": { "usd": "0" } }, "rate": "0.9998756056198255", "slippageTolerance": { "origin": { "usd": "0.000000", "value": "0", "percent": "0.00" }, "destination": { "usd": "0.000000", "value": "0", "percent": "0.00" } }, "timeEstimate": 4, "userBalance": "0", "isFixedRate": true, "route": { "origin": { "inputCurrency": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100012440985606146", "amountFormatted": "0.100012440985606146", "amountUsd": "224.286014", "minimumAmount": "100012440985606146" }, "outputCurrency": { "currency": { "chainId": 1, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100012440985606146", "amountFormatted": "0.100012440985606146", "amountUsd": "224.286014", "minimumAmount": "100012440985606146" }, "router": "relay" }, "destination": { "inputCurrency": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "224.258114", "minimumAmount": "100000000000000000" }, "outputCurrency": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "224.258114", "minimumAmount": "100000000000000000" }, "router": "relay" } } }, "protocol": { "v2": { "orderId": "0x0d0f5bc9e030aa965a11905d83e54a407697e6fcdba5c300875db0fbf93e186b", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0xbf53c12cc90713fc86fc9a59f4c8d1e11377ef6ae345b4de91eae3c6e99f3141", "inputs": [ { "payment": { "chainId": "ethereum", "currency": "0x0000000000000000000000000000000000000000", "amount": "100012440985606146", "weight": "1" }, "refunds": [ { "chainId": "ethereum", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1770778474, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, { "chainId": "base", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1770778474, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output": { "chainId": "base", "payments": [ { "recipient": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "100000000000000000", "expectedAmount": "100000000000000000" } ], "calls": [ "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004d0e30db000000000000000000000000000000000000000000000000000000000" ], "deadline": 1770778474, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "paymentDetails": { "chainId": "ethereum", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency": "0x0000000000000000000000000000000000000000", "amount": "100012440985606146" } } } } ``` To get a higher rate limit, you can [request an API key](/references/api/api-keys#how-to-get-an-api-key). ### Contract Compatibility Before integrating cross-chain calls, ensure your contract is compatible with Relay. Review our [Contract Compatibility](/references/api/api_core_concepts/contract-compatibility) overview to make any necessary changes to your smart contracts. ​ ### Contract Interaction with Web3 When calling smart contracts, you'll need to encode the function call data. Here's how to do it with popular libraries: **Important for ERC20 transactions**: If your contract call involves spending ERC20 tokens, you must include an approval transaction in your `txs` array before the actual contract call. See the [ERC20 examples](#erc20-contract-calls) below. ```javascript JavaScript/ethers.js expandable theme={null} import { ethers } from "ethers"; // Contract ABI for the function you want to call const contractABI = [ "function mint(address to, uint256 amount) external payable", ]; // Create interface to encode function data const iface = new ethers.Interface(contractABI); const callData = iface.encodeFunctionData("mint", [ "0x03508bb71268bba25ecacc8f620e01866650532c", // recipient 1, // amount to mint ]); // Use this callData in your quote request const quoteRequest = { user: "0x03508bb71268bba25ecacc8f620e01866650532c", originChainId: 1, destinationChainId: 8453, originCurrency: "eth", destinationCurrency: "eth", amount: "100000000000000000", tradeType: "EXACT_OUTPUT", txs: [ { to: "0xContractAddress", value: "100000000000000000", data: callData, }, ], }; ``` ```javascript JavaScript/viem expandable theme={null} import { encodeFunctionData, parseEther } from "viem"; // Encode the function call const callData = encodeFunctionData({ abi: [ { name: "mint", type: "function", inputs: [ { name: "to", type: "address" }, { name: "amount", type: "uint256" }, ], }, ], functionName: "mint", args: ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 1n], }); const quoteRequest = { user: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", originChainId: 1, destinationChainId: 8453, originCurrency: "eth", destinationCurrency: "eth", amount: parseEther("0.1").toString(), tradeType: "EXACT_OUTPUT", txs: [ { to: "0xContractAddress", value: parseEther("0.1").toString(), data: callData, }, ], }; ``` ```python Python/web3.py expandable theme={null} from web3 import Web3 # Initialize Web3 (you don't need a provider for encoding) w3 = Web3() # Contract ABI contract_abi = [ { 'name': 'mint', 'type': 'function', 'inputs': [ {'name': 'to', 'type': 'address'}, {'name': 'amount', 'type': 'uint256'} ] } ] # Create contract instance for encoding contract = w3.eth.contract(abi=contract_abi) # Encode function call call_data = contract.encodeABI('mint', [ '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 1 ]) quote_request = { 'user': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 'originChainId': 1, 'destinationChainId': 8453, 'originCurrency': 'eth', 'destinationCurrency': 'eth', 'amount': '100000000000000000', 'tradeType': 'EXACT_OUTPUT', 'txs': [{ 'to': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 'value': '100000000000000000', 'data': call_data }] } ``` ### ERC20 Contract Calls **Critical**: When your contract call involves spending ERC20 tokens, you must include an approval transaction in your `txs` array. The approval must come before the actual contract call. #### ERC20 Approval + Contract Call Pattern ```javascript JavaScript/ethers.js expandable theme={null} import { ethers } from "ethers"; // ERC20 ABI for approval const erc20ABI = [ "function approve(address spender, uint256 amount) external returns (bool)", ]; // Contract ABI for the function you want to call const contractABI = [ "function purchaseWithUSDC(address to, uint256 usdcAmount) external", ]; // Encode approval transaction const erc20Interface = new ethers.Interface(erc20ABI); const approvalData = erc20Interface.encodeFunctionData("approve", [ "0xContractAddress", // Contract that will spend tokens "1000000000", // Amount to approve (1000 USDC with 6 decimals) ]); // Encode contract call transaction const contractInterface = new ethers.Interface(contractABI); const contractCallData = contractInterface.encodeFunctionData( "purchaseWithUSDC", [ "0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3", // recipient "1000000000", // 1000 USDC ] ); const quoteRequest = { user: "0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3", originChainId: 1, destinationChainId: 8453, originCurrency: "usdc", destinationCurrency: "usdc", amount: "1000000000", // Amount required for call (1000 USDC with 6 decimals) tradeType: "EXACT_OUTPUT", txs: [ { to: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // USDC contract address value: "0", data: approvalData, }, { to: "0xContractAddress", value: "0", data: contractCallData, }, ], }; ``` ```javascript JavaScript/viem expandable theme={null} import { encodeFunctionData, parseUnits } from "viem"; const usdcAmount = parseUnits("1000", 6); // 1000 USDC // Encode approval transaction const approvalData = encodeFunctionData({ abi: [ { name: "approve", type: "function", inputs: [ { name: "spender", type: "address" }, { name: "amount", type: "uint256" }, ], }, ], functionName: "approve", args: ["0xContractAddress", usdcAmount], }); // Encode contract call const contractCallData = encodeFunctionData({ abi: [ { name: "purchaseWithUSDC", type: "function", inputs: [ { name: "to", type: "address" }, { name: "usdcAmount", type: "uint256" }, ], }, ], functionName: "purchaseWithUSDC", args: ["0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3", usdcAmount], }); const quoteRequest = { user: "0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3", originChainId: 1, destinationChainId: 8453, originCurrency: "usdc", destinationCurrency: "usdc", amount: "0", // No ETH value for ERC20-only calls tradeType: "EXACT_OUTPUT", txs: [ { to: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // USDC contract value: "0", data: approvalData, }, { to: "0xContractAddress", value: "0", data: contractCallData, }, ], }; ``` ```python Python/web3.py expandable theme={null} from web3 import Web3 w3 = Web3() # ERC20 ABI for approval erc20_abi = [ { 'name': 'approve', 'type': 'function', 'inputs': [ {'name': 'spender', 'type': 'address'}, {'name': 'amount', 'type': 'uint256'} ] } ] # Contract ABI contract_abi = [ { 'name': 'purchaseWithUSDC', 'type': 'function', 'inputs': [ {'name': 'to', 'type': 'address'}, {'name': 'usdcAmount', 'type': 'uint256'} ] } ] # Create contract instances for encoding erc20_contract = w3.eth.contract(abi=erc20_abi) target_contract = w3.eth.contract(abi=contract_abi) usdc_amount = 1000 * 10**6 # 1000 USDC with 6 decimals # Encode approval approval_data = erc20_contract.encodeABI('approve', [ '0xContractAddress', usdc_amount ]) # Encode contract call contract_call_data = target_contract.encodeABI('purchaseWithUSDC', [ '0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3', usdc_amount ]) quote_request = { 'user': '0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3', 'originChainId': 1, 'destinationChainId': 8453, 'originCurrency': 'usdc', 'destinationCurrency': 'usdc', 'amount': '1000000000', # Amount required for call (1000 USDC with 6 decimals) 'tradeType': 'EXACT_OUTPUT', 'txs': [ { 'to': '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', # USDC contract 'value': '0', 'data': approval_data }, { 'to': '0xContractAddress', 'value': '0', 'data': contract_call_data } ] } ``` #### Sweeping ERC20 Balance Relay's router contract has a useful function that you can call to transfer out full balance of an ERC20 token, even if you don't know the full balance. There are currently two methods for doing this: * [cleanupErc20s](https://github.com/relayprotocol/relay-periphery/blob/db8d066881e6d190bd33cbe6abd94e23deeb6b31/src/v2/RelayRouter.sol#L82-L113) * [cleanupNative](https://github.com/relayprotocol/relay-periphery/blob/db8d066881e6d190bd33cbe6abd94e23deeb6b31/src/v2/RelayRouter.sol#L158-L172) You can use these by passing in the txs field as follows: ```json theme={null} { "to": "0xRouterContractAddress", "data": "0xEncodedCalldata", // encoded calldata for cleanupErc20s or cleanupNative "value": 0 } ``` #### ERC20 Troubleshooting Guide **Problem**: "ERC20: transfer amount exceeds allowance" error **Solution**: Ensure you include the approval transaction before your contract call **Problem**: Transaction reverts with "ERC20: insufficient allowance" **Solution**: Check that the approval amount is sufficient for your contract call **Problem**: Approval transaction succeeds but contract call fails **Solution**: Verify the contract address in the approval matches the contract you're calling ### ERC20 Best Practices 1. **Always approve before spending**: Include approval as the first transaction 2. **Use exact amounts**: Approve the exact amount your contract will spend 3. **Check token decimals**: USDC uses 6 decimals, most others use 18 4. **Verify contract addresses**: Use the correct token contract for each chain 5. **Handle allowances**: Some tokens require setting allowance to 0 before setting a new amount ### Quote Parameters for Cross-Chain Calls | Parameter | Type | Description | | ------------- | ------ | ------------------------------- | | `amount` | string | Total value of all txs combined | | `tradeType` | string | Must be "EXACT\_OUTPUT" | | `txs` | array | Array of transaction objects | | `txs[].to` | string | Contract address to call | | `txs[].value` | string | ETH value to send with call | | `txs[].data` | string | Encoded function call data | You can learn more about quote request parameters and response data [here](/references/api/get-quote-v2). ## Execute the Call After receiving a call quote, execute it by processing each step in the response. The execution handles both the origin chain transaction and destination chain fulfillment.\ \ Learn more about step execution using the API [here](/references/api/api_core_concepts/step-execution). ## Monitor Cross-Chain Call Status Track the progress of your cross-chain call using the status endpoint: ```bash Request theme={null} curl "https://api.relay.link/intents/status/v3?requestId=0xed42e2e48c56b06f8f384d66d5f3e6c450fc3a2c7cba19d92a01a649a31a0e94" ``` ```json Response theme={null} { "status": "success", "inTxHashes": [ "0xae0a66324f82bc54299663972b3a48c939af777ff795b13bd72e7755a6a68bb3" ], "txHashes": [ "3exJVjCEMfqVCBN6FBCXPFp6JUmm91pBp9Mp5XWwkoBVAgcr6fM4kZyzdAhoRWtWAvVESMZPGA7G1CibTF8uxaMk" ], "updatedAt": 1769988962883, "originChainId": 8453, "destinationChainId": 792703809 } ``` Learn more about how to check the status of the fill [here](/references/api/api_core_concepts/step-execution#checking-the-fill-status). Learn more about the status lifecycle, see the [status lifecycle diagram](/references/api/quickstart#status-lifecycle). ## Preflight Checklist **Contract compatibility** - Before integrating cross-chain calls, ensure your contract is compatible with Relay. Review our [Contract Compatibility](/references/api/api_core_concepts/contract-compatibility) overview to make any necessary changes to your smart contracts. **ERC20 approvals** - Include approval transactions before any ERC20 spending calls **Verify transaction data** - Confirm `amount` equals the sum of all `txs[].value` fields **Check tradeType** - Must be set to `"EXACT_OUTPUT"` for cross-chain calls **Validate call data** - Ensure contract function calls are properly encoded **Test contract calls** - Verify contract functions work as expected on destination chain **Gas estimation** - Account for potential gas usage variations in contract calls ## Common Use Cases Below are some common use cases for cross-chain calls. * **NFT Minting with ETH**: Mint NFTs on L2s while paying from L1 * **NFT Minting with ERC20**: Mint NFTs using USDC * **DeFi Operations**: Execute swaps, provide liquidity, or claim rewards on other chains * **Gaming**: Execute game actions, purchase items, or claim rewards across chains ```javascript NFT Minting with ETH theme={null} // Mint NFT cross-chain with ETH const mintTx = { to: "0xNFTContract", value: "50000000000000000", // 0.05 ETH mint price data: encodeFunctionData({ abi: nftABI, functionName: "mint", args: [userAddress, tokenId], }), }; ``` ```javascript NFT Minting with ERC20 theme={null} // Mint NFT cross-chain with USDC (requires approval first) const usdcAmount = parseUnits("50", 6); // 50 USDC const txs = [ { to: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // USDC contract value: "0", data: encodeFunctionData({ abi: erc20ABI, functionName: "approve", args: ["0xNFTContract", usdcAmount], }), }, { to: "0xNFTContract", value: "0", data: encodeFunctionData({ abi: nftABI, functionName: "mintWithUSDC", args: [userAddress, tokenId, usdcAmount], }), }, ]; ``` ```javascript DeFi Operations expandable theme={null} // Provide liquidity cross-chain with ERC20 tokens const tokenAmount = parseUnits("1000", 18); // 1000 tokens const usdcAmount = parseUnits("1000", 6); // 1000 USDC const txs = [ // Approve token A { to: "0xTokenAContract", value: "0", data: encodeFunctionData({ abi: erc20ABI, functionName: "approve", args: ["0xDEXContract", tokenAmount], }), }, // Approve token B (USDC) { to: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", value: "0", data: encodeFunctionData({ abi: erc20ABI, functionName: "approve", args: ["0xDEXContract", usdcAmount], }), }, // Add liquidity { to: "0xDEXContract", value: "0", data: encodeFunctionData({ abi: dexABI, functionName: "addLiquidity", args: [ "0xTokenAContract", "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", tokenAmount, usdcAmount, (tokenAmount * 95n) / 100n, // 5% slippage (usdcAmount * 95n) / 100n, // 5% slippage Math.floor(Date.now() / 1000) + 1800, // 30 min deadline ], }), }, ]; ``` ```javascript Gaming theme={null} // Purchase game item with USDC cross-chain const itemPrice = parseUnits("10", 6); // 10 USDC const txs = [ { to: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // USDC approval value: "0", data: encodeFunctionData({ abi: erc20ABI, functionName: "approve", args: ["0xGameContract", itemPrice], }), }, { to: "0xGameContract", value: "0", data: encodeFunctionData({ abi: gameABI, functionName: "purchaseItemWithUSDC", args: [itemId, userAddress, itemPrice], }), }, ]; ``` ## See Also * [**Gasless Swaps**](/features/gasless-swaps): Find the right gasless approach for your app — permit-based, ERC-4337, or EIP-7702. * [**Smart Accounts**](/references/api/api_guides/smart_accounts/smart-accounts): Enable fully gasless transactions where users hold zero native tokens, using EIP-7702 or ERC-4337. * [**Gasless Execution**](/features/gasless-execution): Execute gasless transactions where the transaction signer and gas payer are different addresses. * [**App Fees**](/features/app-fees): Monetize your integration by adding a fee (in bps) to every quote. * [**Fee Sponsorship**](/features/fee-sponsorship): Sponsor fees for your users to reduce friction and improve the user experience. * [**Handling Errors**](/references/api/api_core_concepts/errors): Handle quote errors when using the Relay API. * [**Relay Fees**](/references/api/api_core_concepts/fees): Understand the fees associated with using the Relay API. # Deep Linking Source: https://docs.relay.link/references/api/api_guides/deep-linking How to deep link to the Relay App ## Deep Linking to the Bridge Page The following query parameters are supported: | Parameter | Description | Example | | -------------- | ------------------------------------------------------------------------------ | ------------------------------------------------ | | `toAddress` | A valid address to receive the bridge | `0x03508bB71268BBA25ECaCC8F620e01866650532c` | | `amount` | The amount to have the input initially set to | `0.1` | | `fromChainId` | The chainId of the origin chain | `8453` | | `fromCurrency` | The currency address for the from chain | `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | | `toCurrency` | The currency address for the to chain | `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | | `tradeType` | Whether to use the amount as the output or the input for the basis of the swap | `EXACT_INPUT`, `EXPECTED_OUTPUT`, `EXACT_OUTPUT` | To deep link to a specific `toChain`, you must replace the slug after `/bridge` with the desired chain's name, like so: `relay.link/bridge/{chainName}`. Spaces should be replaced with dashes. You can check out the list of supported chains [`here`](/resources/supported-chains). ### Examples #### Bridge from Zora to Base `https://relay.link/bridge/base?fromChainId=7777777` #### Bridge from Ethereum to Optimism with a preset address `https://relay.link/bridge/optimism?fromChainId=1&toAddress=0x03508bB71268BBA25ECaCC8F620e01866650532c` #### Bridge 0.01 ETH from Optimism to Zora `https://relay.link/bridge/zora?fromChainId=10&amount=0.01` #### Bridge \$100 USDC from Base to Arbitrum `https://relay.link/bridge/arbitrum?fromChainId=8453&fromCurrency=0x833589fcd6edb6e08f4c7c32d4f71b54bda02913&toCurrency=0xaf88d065e77c8cc2239327c5edb3a432268e5831&amount=100` ## Deep Linking to dedicated Chain pages Relay hosts branded interfaces for onboarding and offramping from supported chains. If you're a chain that's launching these pages will prove to be a handy way to get users onboarded onto your chain, with a best in class user experience and support for canonical bridging. Get in touch if you want to customize these pages further. The following query parameters are supported: | Parameter | Description | Example | | -------------- | ------------------------------------------------------------------------------ | ------------------------------------------------ | | `toAddress` | A valid address to receive the swap | `0x03508bB71268BBA25ECaCC8F620e01866650532c` | | `fromCurrency` | The currency address on the origin chain | `0x4200000000000000000000000000000000000006` | | `toCurrency` | The currency address on the destination chain | `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | | `amount` | The amount to have the input initially set to in the widget | `0.1` | | `tradeType` | Whether to use the amount as the output or the input for the basis of the swap | `EXACT_INPUT`, `EXPECTED_OUTPUT`, `EXACT_OUTPUT` | | `fromChainId` | The chainId of the origin chain | `8453` | ### Examples #### Bridge from Zora to Base `https://relay.link/bridge/base?fromChainId=7777777` #### Bridge from Ethereum to Optimism with a preset address `https://relay.link/bridge/optimism?fromChainId=1&toAddress=0x03508bB71268BBA25ECaCC8F620e01866650532c` #### Bridge 0.01 ETH from Optimism to Zora `https://relay.link/bridge/zora?fromChainId=10&amount=0.01` #### Bridge \$100 USDC from Base to Arbitrum `https://relay.link/bridge/arbitrum?fromChainId=8453&fromCurrency=0x833589fcd6edb6e08f4c7c32d4f71b54bda02913&toCurrency=0xaf88d065e77c8cc2239327c5edb3a432268e5831&amount=100` ## Deep Linking to the Onramp Page The following query parameters are supported: | Parameter | Description | Example | | ------------ | ------------------------------------- | -------------------------------------------- | | `toAddress` | A valid address to receive the bridge | `0x03508bB71268BBA25ECaCC8F620e01866650532c` | | `toCurrency` | The currency address for the to chain | `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | To deep link to a specific `toChain`, you must replace the slug after `/onramp` with the desired chain's name, like so: `relay.link/onramp/{chainName}`. Spaces should be replaced with dashes. ### Unsupported Chains list * Forma * Gravity * Hychain * Onchain Points * Powerloom * Sanko * Story * Kai ### Supported Currencies Our currencies are currently limited, make sure to check the [currencies api](/references/api/get-currencies-v2) with depositAddressOnly set to true. ### Examples #### Onramp onto Cyber `https://relay.link/onramp/cyber` #### Onramp onto Degen with a preset address `https://relay.link/onramp/degen?toAddress=0x03508bB71268BBA25ECaCC8F620e01866650532c` #### Onramp onto Base with Base DEGEN `https://relay.link/onramp/base?toCurrency=0x4ed4e862860bed51a9570b96d89af5e1b0efefed` # Hyperliquid Support Source: https://docs.relay.link/references/api/api_guides/hyperliquid-support How to deposit and withdraw from Hyperliquid to any Relay chain Relay supports depositing and withdrawing Hyperliquid (Hypercore) perpsUSDC from any supported chain. To try it today, use the [Relay App](https://relay.link/bridge/hyperliquid). Relay also provides complete support for HyperEVM, which can be accessed using the standard quote flow with `chainId=999`. This document details how to integrate Hyperliquid deposits and withdrawals into your application. # API Access Hyperliquid can be accessed using the standard Relay API flow. To get started, review the [**execution steps**](https://docs.relay.link/references/api/api_core_concepts/step-execution) documentation. When you're ready to execute swaps, refer to the [**Get Quote**](https://docs.relay.link/references/api/get-quote-v2) API endpoint. ## Hyperliquid-Specific API Parameters | **Action** | **Parameter** | **Input** | **Description** | | ------------------------- | --------------- | --------- | ------------------------ | | Deposit to Hyperliquid | toChainId | 1337 | Hyperliquid Chain ID | | | recipient | | Hyperliquid Address | | Withdraw from Hyperliquid | protocolVersion | v2 | Required for withdrawals | | | fromChainId | 1337 | Hyperliquid Chain ID | These parameters are specific to Hyperliquid interactions. All other standard API parameters remain required. Note that `protocolVersion: v2` is mandatory for withdrawals. ## Supported Currencies Relay supports the following Hyperliquid currencies: * **Perps USDC**: Treated with a custom address `0x00000000000000000000000000000000` * **Spot USDC**: Address `0x6d1e7cde53ba9467b783cb7c530ce054` * **Other Spot currencies**: Including USDe, USDH, and other tokens returned by the Hyperliquid RPC For a complete and up-to-date list of supported currencies, check the [Chains API](https://docs.relay.link/references/api/get-chains) response for Hyperliquid (chainId: 1337). ## Currency Addresses * Perps USDC is treated in a custom way with its address being `0x00000000000000000000000000000000` * For any other Spot currency the address is the tokenId value returned by the Hyperliquid RPC ```bash theme={null} curl -X POST https://api.hyperliquid.xyz/info -H 'Content-Type: application/json' -d '{"type": "spotMeta"}' | jq '.tokens[]' ``` * For currencies on HIP-3 DEXs (non-Spot and non-Perps), append the hex-encoded DEX name to the corresponding Spot currency address. For example, `USDC` on the `xyz` DEX is represented as `0x6d1e7cde53ba9467b783cb7c530ce05478797a`, where `0x6d1e7cde53ba9467b783cb7c530ce054` is the Spot USDC address and `78797a` is the hex representation of "xyz". ## Withdrawals from Hyperliquid Hyperliquid withdrawals use a two-step signature flow. The v2 implementation leverages the unique nonce associated with every Hyperliquid transfer to map deposits to request IDs. This requires two signatures: 1. **Authorize**: Sign a nonce-mapping message that associates a specific nonce with a request ID 2. **Deposit**: Sign and submit the Hyperliquid transfer transaction using the same nonce The deposit transaction must use the exact same nonce from the authorization signature. If the nonces don't match, Relay cannot associate the transfer with the request ID. ### Getting Hyperliquid Balances Use the [Hyperliquid info API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint) to query balances. Pass `clearinghouseState` as the `type` parameter, then read the `withdrawable` property, which returns a USD value in human-readable format. ### Example Quote Request ```bash Request theme={null} curl 'https://api.relay.link/quote/v2' \ -H 'content-type: application/json' \ -d '{ "user": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "originChainId": 1337, "destinationChainId": 10, "originCurrency": "0x00000000000000000000000000000000", "destinationCurrency": "0x0000000000000000000000000000000000000000", "recipient": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "tradeType": "EXACT_INPUT", "amount": "1000000000", "refundTo": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "protocolVersion": "v2" }' ``` ```json Response expandable theme={null} { "steps":[ { "id":"authorize", "action":"Sign nonce-mapping for the deposit", "description":"Sign the message that maps the deposit to the order", "kind":"signature", "items":[ { "status":"incomplete", "data":{ "sign":{ "signatureKind":"eip712", "domain":{ "name":"RelayNonceMapping", "version":"1", "chainId":1, "verifyingContract":"0x0000000000000000000000000000000000000000" }, "types":{ "NonceMapping":[ { "name":"chainId", "type":"string" }, { "name":"wallet", "type":"address" }, { "name":"id", "type":"bytes32" }, { "name":"nonce", "type":"uint256" } ] }, "value":{ "chainId":"hyperliquid", "wallet":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "nonce":1770329064777, "id":"0x3bfdb54176b619a1f1f59f091a0708d0216a9f74b985b34d092cdcc4f9f4f9f3" }, "primaryType":"NonceMapping" }, "post":{ "endpoint":"/authorize", "method":"POST", "body":{ "type":"nonce-mapping", "walletChainId":1337, "wallet":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "nonce":1770329064777, "id":"0x3bfdb54176b619a1f1f59f091a0708d0216a9f74b985b34d092cdcc4f9f4f9f3", "signatureChainId":1 } } } } ], "requestId":"0x02e937b4d022321b56673e12a42064dde45f25b2f7da0db5eb6b4fa5b747f64a" }, { "id":"deposit", "action":"Confirm transaction in your wallet", "description":"Depositing funds to the relayer to execute the swap for ETH", "kind":"transaction", "items":[ { "status":"incomplete", "data":{ "action":{ "type":"sendAsset", "parameters":{ "hyperliquidChain":"Mainnet", "destination":"0x865eb9baa5492cef598adf7afb1038654fcb7081", "sourceDex":"", "destinationDex":"", "token":"USDC:0x6d1e7cde53ba9467b783cb7c530ce054", "amount":"10.000000", "fromSubAccount":"", "nonce":1770329064777 } }, "nonce":1770329064777, "eip712Types":{ "HyperliquidTransaction:SendAsset":[ { "name":"hyperliquidChain", "type":"string" }, { "name":"destination", "type":"string" }, { "name":"sourceDex", "type":"string" }, { "name":"destinationDex", "type":"string" }, { "name":"token", "type":"string" }, { "name":"amount", "type":"string" }, { "name":"fromSubAccount", "type":"string" }, { "name":"nonce", "type":"uint64" } ] }, "eip712PrimaryType":"HyperliquidTransaction:SendAsset" }, "check":{ "endpoint":"/intents/status/v3?requestId=0x02e937b4d022321b56673e12a42064dde45f25b2f7da0db5eb6b4fa5b747f64a", "method":"GET" } } ], "requestId":"0x02e937b4d022321b56673e12a42064dde45f25b2f7da0db5eb6b4fa5b747f64a", "depositAddress":"" } ], "fees":{ "gas":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"0", "amountFormatted":"0.0", "amountUsd":"0", "minimumAmount":"0" }, "relayer":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"2924363", "amountFormatted":"0.02924363", "amountUsd":"0.029239", "minimumAmount":"2924363" }, "relayerGas":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"21060", "amountFormatted":"0.0002106", "amountUsd":"0.000211", "minimumAmount":"21060" }, "relayerService":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"2903303", "amountFormatted":"0.02903303", "amountUsd":"0.029028", "minimumAmount":"2903303" }, "app":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"0", "amountFormatted":"0.0", "amountUsd":"0", "minimumAmount":"0" }, "subsidized":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"0", "amountFormatted":"0.0", "amountUsd":"0", "minimumAmount":"0" } }, "details":{ "operation":"swap", "sender":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "recipient":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "currencyIn":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"1000000000", "amountFormatted":"10.0", "amountUsd":"9.998250", "minimumAmount":"1000000000" }, "currencyOut":{ "currency":{ "chainId":10, "address":"0x0000000000000000000000000000000000000000", "symbol":"ETH", "name":"Ether", "decimals":18, "metadata":{ "logoURI":"https://assets.relay.link/icons/1/light.png", "verified":true } }, "amount":"5388446518106479", "amountFormatted":"0.005388446518106479", "amountUsd":"9.962860", "minimumAmount":"5280677587744349" }, "refundCurrency":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"1000000000", "amountFormatted":"10.0", "amountUsd":"9.998250", "minimumAmount":"1000000000" }, "totalImpact":{ "usd":"-0.035390", "percent":"-0.35" }, "swapImpact":{ "usd":"-0.006151", "percent":"-0.06" }, "expandedPriceImpact":{ "swap":{ "usd":"-0.008146" }, "execution":{ "usd":"-0.020211" }, "relay":{ "usd":"-0.007033" }, "app":{ "usd":"0" } }, "rate":"0.0005388446518106479", "slippageTolerance":{ "origin":{ "usd":"0.000000", "value":"0", "percent":"0.00" }, "destination":{ "usd":"0.199257", "value":"107768930362130", "percent":"2.00" } }, "timeEstimate":2, "userBalance":"0", "isFixedRate":true, "route":{ "origin":{ "inputCurrency":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"1000000000", "amountFormatted":"10.0", "amountUsd":"9.998250", "minimumAmount":"1000000000" }, "outputCurrency":{ "currency":{ "chainId":1337, "address":"0x00000000000000000000000000000000", "symbol":"USDC", "name":"USDC (Perps)", "decimals":8, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"1000000000", "amountFormatted":"10.0", "amountUsd":"9.998250", "minimumAmount":"1000000000" }, "router":"relay" }, "destination":{ "inputCurrency":{ "currency":{ "chainId":10, "address":"0x0000000000000000000000000000000000000000", "symbol":"ETH", "name":"Ether", "decimals":18, "metadata":{ "logoURI":"https://assets.relay.link/icons/1/light.png", "verified":true } }, "amount":"5388446518106479", "amountFormatted":"0.005388446518106479", "amountUsd":"9.962860", "minimumAmount":"5280677587744349" }, "outputCurrency":{ "currency":{ "chainId":10, "address":"0x0000000000000000000000000000000000000000", "symbol":"ETH", "name":"Ether", "decimals":18, "metadata":{ "logoURI":"https://assets.relay.link/icons/1/light.png", "verified":true } }, "amount":"5388446518106479", "amountFormatted":"0.005388446518106479", "amountUsd":"9.962860", "minimumAmount":"5280677587744349" }, "router":"relay" } } }, "protocol":{ "v2":{ "orderId":"0x3bfdb54176b619a1f1f59f091a0708d0216a9f74b985b34d092cdcc4f9f4f9f3", "orderData":{ "version":"v1", "solverChainId":"base", "solver":"0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt":"0xd50b8811d6ae77508a80cf87b5045e00b62fb2507ffa7a2c2bdef8150a919a04", "inputs":[ { "payment":{ "chainId":"hyperliquid", "currency":"0x00000000000000000000000000000000", "amount":"1000000000", "weight":"1" }, "refunds":[ { "chainId":"hyperliquid", "recipient":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "currency":"0x00000000000000000000000000000000", "minimumAmount":"0", "deadline":1770933867, "extraData":"0x" }, { "chainId":"optimism", "recipient":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "currency":"0x0000000000000000000000000000000000000000", "minimumAmount":"0", "deadline":1770933867, "extraData":"0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output":{ "chainId":"optimism", "payments":[ { "recipient":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "currency":"0x0000000000000000000000000000000000000000", "minimumAmount":"5280677587744349", "expectedAmount":"5388446518106479" } ], "calls":[ ], "deadline":1770933867, "extraData":"0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees":[ ] }, "paymentDetails":{ "chainId":"hyperliquid", "depository":"0x865eb9baa5492cef598adf7afb1038654fcb7081", "currency":"0x00000000000000000000000000000000", "amount":"1000000000" } } } } ``` The response includes two steps that must be executed in order. ### Step 1: Authorize (Nonce-Mapping Signature) The first step (`id: authorize`) requires signing an EIP-712 message that maps the nonce to the request ID. This signature can be executed on any EVM chain. **Chain ID Override Required**: The API returns Ethereum mainnet (chainId: 1) by default. You must override both `item.data.sign.domain.chainId` and `item.data.post.body.signatureChainId` to match the user's active chain ID (Base, Optimism, etc.) or your preferred chain ID. The signature submitted to the deposit step must use the same modified chainId. Example authorize step response: ```json Authorize Step expandable theme={null} { "id": "authorize", "action": "Sign nonce-mapping for the deposit", "description": "Sign the message that maps the deposit to the order", "kind": "signature", "items": [ { "status": "incomplete", "data": { "sign": { "signatureKind": "eip712", "domain": { "name": "RelayNonceMapping", "version": "1", "chainId": 1, "verifyingContract": "0x0000000000000000000000000000000000000000" }, "types": { "NonceMapping": [ { "name": "chainId", "type": "string" }, { "name": "wallet", "type": "address" }, { "name": "id", "type": "bytes32" }, { "name": "nonce", "type": "uint256" } ] }, "value": { "chainId": "hyperliquid", "wallet": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "nonce": 1765463670254, "id": "0x7b3e352d6bb600f7d937250968dc9b9c7deaf34b3787fb2312fc7374603f1667" }, "primaryType": "NonceMapping" }, "post": { "endpoint": "/authorize", "method": "POST", "body": { "type": "nonce-mapping", "walletChainId": 1337, "wallet": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "nonce": 1765463670254, "id": "0x7b3e352d6bb600f7d937250968dc9b9c7deaf34b3787fb2312fc7374603f1667", "signatureChainId": 1 } } } } ], "requestId": "0x33d530353522088c2a4016a7eeb2185b8f5926ab230eff44d4495746a43a6f87" } ``` After the message is signed the second action is to submit the `post` body to the `endpoint` provided in the `post` data. You'll also need to provide the signature that was generated from the sign data as a query parameter. ### Step 2: Deposit (Hyperliquid Transaction) The second step (`id: deposit`) requires signing and submitting the actual Hyperliquid transfer. The API response includes `eip712Types` and `eip712PrimaryType` to simplify constructing the signature data. Example deposit step response: ```json Deposit Step expandable theme={null} { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for ETH", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "action": { "type": "sendAsset", "parameters": { "hyperliquidChain": "Mainnet", "destination": "0x865eb9baa5492cef598adf7afb1038654fcb7081", "sourceDex": "", "destinationDex": "", "token": "USDC:0x6d1e7cde53ba9467b783cb7c530ce054", "amount": "10.000000", "fromSubAccount": "", "nonce": 1765463670254 } }, "nonce": 1765463670254, "eip712Types": { "HyperliquidTransaction:SendAsset": [ { "name": "hyperliquidChain", "type": "string" }, { "name": "destination", "type": "string" }, { "name": "sourceDex", "type": "string" }, { "name": "destinationDex", "type": "string" }, { "name": "token", "type": "string" }, { "name": "amount", "type": "string" }, { "name": "fromSubAccount", "type": "string" }, { "name": "nonce", "type": "uint64" } ] }, "eip712PrimaryType": "HyperliquidTransaction:SendAsset" }, "check": { "endpoint": "/intents/status?requestId=0x33d530353522088c2a4016a7eeb2185b8f5926ab230eff44d4495746a43a6f87", "method": "GET" } } ], "requestId": "0x33d530353522088c2a4016a7eeb2185b8f5926ab230eff44d4495746a43a6f87" } ``` ### Constructing the Signature Data To sign the deposit step, you need to convert it into EIP-712 signature data. Extract the necessary fields from the step response and construct the signature object: ```typescript theme={null} // Extract data from the deposit step const stepItem = step.items[0]; const action = stepItem.data.action; const eip712Types = stepItem.data.eip712Types; const eip712PrimaryType = stepItem.data.eip712PrimaryType; // The chain ID of the connected wallet const chainId = 1; // Example: Ethereum mainnet // Construct the EIP-712 signature data const signatureData = { domain: { name: "HyperliquidSignTransaction", version: "1", chainId: chainId, verifyingContract: "0x0000000000000000000000000000000000000000", }, types: { ...eip712Types, EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], }, primaryType: eip712PrimaryType, value: { ...action.parameters, type: action.type, signatureChainId: `0x${chainId.toString(16)}`, }, }; ``` Key fields explained: * `domain`: Static except for `chainId`, which should match the connected wallet's active chain * `types`: Combines the API-provided `eip712Types` with the standard `EIP712Domain` * `primaryType`: Use the `eip712PrimaryType` from the API response * `message`: Spread the action parameters and add `type` and `signatureChainId` * `signatureChainId`: Hex representation of the active chain ID ### Signing the Message Use your wallet client to sign the EIP-712 typed data: ```typescript theme={null} import { useWalletClient } from "wagmi"; const { data: walletClient } = useWalletClient(); const signature = await walletClient.signTypedData({ account: walletClient.account, domain: signatureData.domain, types: signatureData.types, primaryType: signatureData.primaryType, message: signatureData.value, }); ``` ### Submitting to Hyperliquid After signing, submit the transaction to Hyperliquid's exchange API: ```typescript theme={null} import { parseSignature } from "viem"; import axios from "axios"; const { r, s, v } = parseSignature(signature); const action = signatureData.value; const nonce = stepItem.data.nonce; // Submit to Hyperliquid const response = await axios.post("https://api.hyperliquid.xyz/exchange", { signature: { r, s, v: Number(v ?? 0n), }, nonce, action, }); if (response.status !== 200 || response.data?.status !== "ok") { throw new Error("Failed to submit transaction to Hyperliquid"); } ``` Important considerations: * Parse the signature into its component parts (`r`, `s`, `v`) * Ensure `v` is a `Number`, not a `BigInt` * The action data must exactly match what was signed * A successful response returns status `200` with `data.status: "ok"` For a complete implementation example, refer to our [publicly available SDK](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/sdk/src/utils/hyperliquid.ts). # Lighter Support Source: https://docs.relay.link/references/api/api_guides/lighter-support How to deposit to Lighter from any Relay chain Relay supports depositing to Lighter from any supported chain. To try it today, use the [Relay App](https://relay.link/bridge/lighter). This document details how to integrate Lighter deposits into your application. Currently, only deposits to Lighter are supported. Withdrawals from Lighter are not yet available. # Prerequisites Before you can bridge funds to Lighter, you must first set up an account on [lighter.exchange](https://lighter.exchange/). Once your account is created, you can retrieve your account index to use as the recipient for deposits. # API Access Lighter can be accessed using the standard Relay API flow. To get started, review the [**execution steps**](https://docs.relay.link/references/api/api_core_concepts/step-execution) documentation. When you're ready to execute deposits, refer to the [**Get Quote**](https://docs.relay.link/references/api/get-quote-v2) API endpoint. ## Lighter-Specific API Parameters | **Parameter** | **Input** | **Description** | | ------------- | --------- | -------------------------------------------- | | toChainId | 3586256 | Lighter Chain ID | | recipient | | Lighter Account Index (not a wallet address) | These parameters are specific to Lighter interactions. All other standard API parameters remain required. The `recipient` parameter must be your Lighter account index, not your wallet address. See below for how to retrieve your account index. ## Getting Your Lighter Account Index To deposit to Lighter, you need to use your Lighter account index as the `recipient` parameter. You can retrieve this using the Lighter API's `accountsByL1Address` endpoint: ```bash theme={null} curl 'https://mainnet.zklighter.elliot.ai/api/v1/accountsByL1Address?l1_address=YOUR_WALLET_ADDRESS' ``` Replace `YOUR_WALLET_ADDRESS` with your Ethereum wallet address. The response will include your `accountIndex`, which you should use as the `recipient` in your quote request. For more details, see the [Lighter API documentation](https://apidocs.lighter.xyz/reference/accountsbyl1address). ## Example Quote Request ```bash theme={null} curl 'https://api.relay.link/quote/v2' \ -H 'content-type: application/json' \ -d '{ "user": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "originChainId": 8453, "destinationChainId": 3586256, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0", "recipient": "509564", "tradeType": "EXACT_INPUT", "amount": "1000000000000000000" }' ``` In this example: * `originChainId: 8453` - Depositing from Base * `destinationChainId: 3586256` - Lighter chain ID * `recipient: "509564"` - Your Lighter account index (replace with your actual account index) The response will include the execution steps required to complete the deposit. Follow the standard [step execution](https://docs.relay.link/references/api/api_core_concepts/step-execution) flow to complete the transaction. # Smart Accounts in Relay Source: https://docs.relay.link/references/api/api_guides/smart-accounts How Relay uses ERC-4337 and EIP-7702 to enable smart wallet features like gas sponsorship, batching, and cross-chain execution Smart accounts unlock new design space in **Relay’s cross-chain infrastructure**. With them, we can: * **Batch multiple actions** into one atomic call (e.g. deposit → call user wallet → mint NFT) * **Delegate tx execution to the user**, preserving `msg.sender` on the destination chain * **Sponsor gas** or use ERC-20s for fee payments * **Route logic across chains**, bridging into EOAs that execute contract logic without a separate deployment Relay supports two complementary standards for smart account execution: *** ### [ERC-4337](/features/gasless-swaps#erc-4337-gasless) Relay uses ERC-4337 to support complex smart account flows. Key features include: * Sponsored transactions using **paymasters** * Arbitrary smart account validation logic * **Bundled execution**: approve → swap → send, all in one tx This is useful when you have smart accounts like **Safe** or **Kernel**, and interact via **UserOperations**. *** ### [EIP-7702](/features/gasless-swaps#7702-batchexecutor) Relay uses EIP-7702 to enable **EOAs to become smart accounts**. We support 7702 to: * **Execute logic on destination chains where the user is `msg.sender`**\ Useful for apps where the final contract cares about the actual sender (e.g. NFT mints, purchases) * **Preserve intent and gas delegation across chains**\ Our solver can bridge ETH or tokens to the user's EOA, and the EOA can act as a smart wallet * **Avoid smart account setup** on destination: the user's EOA behaves like a contract temporarily, powered by an `authorizationList` signature *** ### Why Smart Accounts Matter for Relay Smart accounts let us overcome key limitations in a cross-chain solver system: | Problem | Solved By | | ----------------------------------------------------------- | -------------------------------------------- | | `msg.sender` is solver, but the app needs it to be the user | ✅ ERC-4337 / 7702 | | Want to batch calls after bridging ETH | ✅ ERC-4337 / 7702 | | Want to pay gas from any chain or abstract fees entirely | ✅ ERC-4337 paymasters / 7702 signature logic | | Want smart account functionality for your EOA | ✅ EIP-7702 | For example, when someone uses ETH on Chain A to buy an NFT on Chain B: * Relay bridges the ETH to their EOA * That EOA uses a signed 7702 tx to **batch logic** on Chain B: approve → mint * `msg.sender` = the user, not Relay This wasn’t previously possible because we couldn’t inject logic into EOAs **without deploying a contract**. Now we can. *** ## 🔗 Explore the Guides * [ERC-4337 Guide](/features/gasless-swaps#erc-4337-gasless) – Learn how Relay uses UserOperations and bundlers * [EIP-7702 Guide](/features/gasless-swaps#7702-batchexecutor) – See how Relay turns EOAs into smart wallets with `authorizationList` *** Relay supports both 4337 and 7702. Whether you're building gasless dApps, trustless UX, or bridging into smart behavior — smart accounts are the foundation. Let us know what you’re building. # Solana Support Source: https://docs.relay.link/references/api/api_guides/solana How to Deposit or Withdraw on Solana from an EVM Chain Relay now fully supports depositing & withdrawing to Solana from any EVM chain we support. This is a great way to get users funds on Solana to complete transactions. You can onboard users funds from Blast ETH to Solana USDC to make transactions on a game. You could also swap funds from Solana SOL to Zora ETH for users to mint. The possibilities are limitless when you use Relay embedded in your app. ## Required Information There are few things that make transacting on Solana different than another EVM chain. The provided information below should review all the exceptions where your input is Solana specific. Solana wallet addresses are case sensitive. ### SDK Properties | Action | Parameter | Input | Description | | -------------------- | ------------ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | Deposit to Solana | `toChainId` | `792703809` | Chain ID assigned to access Solana for Relay's tools. | | | `recipient` | User's Solana Address | Must be a valid Solana address. Do **not** use an Ethereum wallet address. *Case Sensitive* | | | `toCurrency` | Contract Address of Solana Token | Must be a valid Solana token address. Do **not** use an EVM address. We support all tokens tradeable on [Jupiter](https://jup.ag/). | | Withdraw from Solana | `chainId` | `792703809` | Chain ID assigned to access Solana for Relay's tools. | | | `currency` | Contract Address of Solana Token | Must be a valid Solana token address. Do **not** use an EVM address. We support all tokens tradeable on [Jupiter](https://jup.ag/). | When withdrawing from Solana using the SDK, you will not need to specify the depositing wallet address. ### Solana Tokens In the table, we have provided the most frequently used Solana tokens & their contract addresses. We do support all tradeable tokens on [Jupiter](https://jup.ag/). Solana token addresses are case sensitive. | Token | Token Address | Token Symbol | Decimals | | ----- | ---------------------------------------------- | ------------ | -------- | | SOL | `11111111111111111111111111111111` | SOL | 9 | | USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | USDC | 6 | | wSol | `So11111111111111111111111111111111111111112` | wSOL | 9 | | USDT | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` | USDT | 6 | Relay supports **any** Solana token available on Jupiter. You can visit [Jupiter](https://jup.ag/) to explore all the tokens available. ## API ### Params | Action | Parameter | Input | Description | | -------------------- | --------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | Deposit to Solana | `recipient` | User's Solana Address | Must be a valid Solana address. Do **not** use an Ethereum wallet address. *Case Sensitive* | | | `destinationChainId` | `792703809` | Chain ID assigned to access Solana for Relay's tools. | | | `destinationCurrency` | Contract Address of Solana Token | Must be a valid Solana token address. Do **not** use an EVM address. We support all tokens tradeable on [Jupiter](https://jup.ag/). | | Withdraw from Solana | `user` | User's Solana Address | Must be a valid Solana address. Do **not** use an Ethereum wallet address. *Case Sensitive* | | | `originChainId` | `792703809` | Chain ID assigned to access Solana for Relay's tools. | | | `originCurrency` | Contract Address of Solana Token | Must be a valid Solana token address. Do **not** use an EVM address. We support all tokens tradeable on [Jupiter](https://jup.ag/). | ### Execution Once you have the necessary information, you can start utilizing our API for full Solana withdrawal and deposit support. To get started, check out the [execution steps](/references/api/api_core_concepts/step-execution) of our API. Then when you're ready to swap, head over to the [Get Quote](/references/api/get-quote-v2) API endpoint. Please note that instead of the usual calldata returned with EVM transactions, there’s different calldata that needs to be handled accordingly. ### Example: Deposit to Solana from Base ```bash Request theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "0x03508bb71268bba25ecacc8f620e01866650532c", "originChainId": 8453, "originCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "destinationChainId": 792703809, "destinationCurrency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "recipient": "GTtzwxqy67xx9DVESJjx28TgXqpc8xTqtiytgNMaQBTE", "tradeType": "EXACT_INPUT", "amount": "10000000" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "approve", "action": "Confirm transaction in your wallet", "description": "Sign an approval for USDC", "kind": "transaction", "requestId": "0x44c1ba1053b2403c5e7024140b73e4a08a7c9d7d0bf985a9755d55d8a9d771f5", "items": [ { "status": "incomplete", "data": { "from": "0x03508bb71268bba25ecacc8f620e01866650532c", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "chainId": 8453 } } ] }, { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the relayer to execute the swap for USDC", "kind": "transaction", "requestId": "0x44c1ba1053b2403c5e7024140b73e4a08a7c9d7d0bf985a9755d55d8a9d771f5", "depositAddress": "", "items": [ { "status": "incomplete", "data": { "from": "0x03508bb71268bba25ecacc8f620e01866650532c", "to": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "chainId": 8453 }, "check": { "endpoint": "/intents/status/v3?requestId=0x44c1ba1053b2403c5e7024140b73e4a08a7c9d7d0bf985a9755d55d8a9d771f5", "method": "GET" } } ] } ], "fees": { "gas": { "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18, "metadata": { "logoURI": "https://assets.relay.link/icons/1/light.png", "verified": true } }, "amount": "286882212616", "amountFormatted": "0.000000286882212616", "amountUsd": "0.000614", "minimumAmount": "286882212616" }, "relayer": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", "verified": true } }, "amount": "261358", "amountFormatted": "0.261358", "amountUsd": "0.261273", "minimumAmount": "261358" } }, "details": { "operation": "swap", "sender": "0x03508bb71268bba25ecacc8f620e01866650532c", "recipient": "GTtzwxqy67xx9DVESJjx28TgXqpc8xTqtiytgNMaQBTE", "currencyIn": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", "verified": true } }, "amount": "10000000", "amountFormatted": "10.0", "amountUsd": "9.996730", "minimumAmount": "10000000" }, "currencyOut": { "currency": { "chainId": 792703809, "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", "verified": true } }, "amount": "9738642", "amountFormatted": "9.738642", "amountUsd": "9.735457", "minimumAmount": "9543870" }, "timeEstimate": 1 } } ``` ## Transaction Size Optimization Solana transactions have a hard **1232-byte** size limit. If you append custom instructions to the transaction (e.g., for tracking, fee collection, or other logic), the combined transaction can exceed this limit and fail. Pass **`maxRouteLength`** in your quote request to limit the number of hops in the Solana swap routing. Fewer hops means a smaller transaction. **Recommended values:** | `maxRouteLength` | When to use | | ---------------- | ------------------------------------------------- | | `4` | Start here. Works for most integrations. | | `3` | Use if transactions are still too large with `4`. | As a last resort, you can also pass **`"includedOriginSwapSources": ["jupiter"]`** in the quote request. Jupiter tends to return smaller instructions, but this may limit available routes. ```bash theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "0x03508bb71268bba25ecacc8f620e01866650532c", "originChainId": 8453, "originCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "destinationChainId": 792703809, "destinationCurrency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "recipient": "GTtzwxqy67xx9DVESJjx28TgXqpc8xTqtiytgNMaQBTE", "tradeType": "EXACT_INPUT", "amount": "10000000", "maxRouteLength": 4 }' ``` For the full list of quote parameters, see the [Get Quote](/references/api/get-quote-v2) API reference. ## SDK To use the SDK with Solana, install and configure the [SVM wallet adapter](/references/relay-kit/sdk/adapters#what-adapters-are-available-out-of-the-box). The adapter handles transaction signing and broadcasting. ```bash theme={null} npm install @relayprotocol/relay-svm-wallet-adapter ``` For implementation details and code samples, see the [Adapters documentation](/references/relay-kit/sdk/adapters). # Testnet Support Source: https://docs.relay.link/references/api/api_guides/testnet How to Utilize Testnets on Relay Relay supports a variety of testnet and tokens. We recommend using testnets and their tokens as a way to test bridging with no cost. Our testnet chains are full supported across all of our tools: APIs, SDK, and UI Kit. Feel free to explore the chains offered below. For the latest list, please explore our [Testnets Supported Chains](/references/api/api_resources/supported-chains#testnets). **Testnets are not recommended for testing swaps.** Testnets typically lack real DEX liquidity making testnet swaps fail. We recommend using inexpensive L2s like Base or Arbitrum to test swapping. The cost of gas will outweight the time required to debug testnet-specific issues like illiquidity. ## Testnets Supported You can see all the testnets we support in the table below. You can also query our API to get a JSON of the supported testnets: [https://api.testnets.relay.link/chains](https://api.testnets.relay.link/chains) The API response will also return chain IDs and other relevant information. ## API Support To access testnets with our API, make sure to use the correct base URL: `https://api.testnets.relay.link` By using this base URL, you'll be able to bridge, swap, and transact on testnets Relay supports. To get started, check out the [API documentation](/references/api/overview). ## SDK Support To access testnets with our SDK, make sure to use the correct base URL: `https://api.testnets.relay.link` You'll pass this base URL in the `baseApiUrl` parameter when creating the client. To get started, check out the [SDK documentation](/references/relay-kit/sdk/installation). ## UI Kit Support To access testnet with our UI kit, make sure to pass the testnet chain you want e.g. `base sepolia`, `sepolia`, etc. To get started, check out the [UI Kit documentation](/references/relay-kit/overview). # Transaction Indexing Source: https://docs.relay.link/references/api/api_guides/transaction-indexing How to integrate Relay’s transaction indexing APIs ## Overview Relay provides two key APIs for transaction indexing: * [transactions/single](/references/api/transactions-single) - For indexing same-chain transfers, wraps, and unwraps * [transactions/index](/references/api/transactions-index) - For accelerating indexing of transactions with internal deposits ## API 1: transactions/single ### When to Use Use this API for **same-chain actions** including: * Token transfers * Token wraps (e.g., ETH to WETH) * Token unwraps (e.g., WETH to ETH) Note: This is not required for same-chain swaps. ### Purpose * Ensures same-chain actions are properly indexed * Relay's indexer doesn't actively monitor same-chain actions by default * Critical for applications supporting same-chain token operations ### API Reference [Documentation](/references/api/transactions-single) [Implementation Reference](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/sdk/src/utils/transaction.ts#L122) ### Request Example ```bash theme={null} curl -X POST 'https://api.relay.link/transactions/single' \ -H 'Content-Type: application/json' \ -d '{ "tx": "{\"from\":\"0x03508bB71268BBA25ECaCC8F620e01866650532c\",\"to\":\"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359\",\"data\":\"0xa9059cbb00000000000000000000000036329d1ff4b31ec85280a86e7cf58fca7c005ed000000000000000000000000000000000000000000000000000000000000f4240\",\"value\":\"0\",\"chainId\":137,\"gas\":\"94464\",\"maxFeePerGas\":\"34643644476\",\"maxPriorityFeePerGas\":\"34180692255\",\"txHash\":\"0x935be17e13f1dc4aee15e75cc9c119f2d639e58b061a54246843f2ddf1052f7c\"}", "chainId":"137", "requestId":"0xcb6bec4106d2bc6f692831e00445f843fa39239ca61bc304486b0c96e1d531a9" }' ``` ## API 2: transactions/index ### When to Use Use this API for transactions that contain internal deposits that need to be detected through trace analysis. This is particularly important for teams using their own proxy contracts. ### Purpose * Accelerates the indexing process by triggering indexing before transaction validation completes * Ensures Relay fetches transaction traces to detect internal deposits * Recommended for custom proxy contract implementations ### API Reference [Documentation](/references/api/transactions-index) [Implementation Reference](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/sdk/src/utils/transaction.ts#L113) ### Request Example ```bash cURL theme={null} curl -X POST 'https://api.relay.link/transactions/index' \ -H 'Content-Type: application/json' \ -d '{ "txHash": "0x9f2c5e8b1d4a7f0c3e6b9d2f5a8c1e4b7d0a3f6c9e2b5d8a1f4c7e0b3d6a9f2c", "chainId": 8453 }' ``` ### Integration Call this API immediately after submitting a transaction, before waiting for confirmation. ## Decision Matrix | Transaction Type | API Endpoint | Reason | | --------------------------- | --------------------- | -------------------------------------------------- | | Cross-chain transactions | `transactions/index` | Accelerates indexing before validation | | Proxy contract transactions | `transactions/index` | Detects internal deposits via trace analysis | | Same-chain swaps | `transactions/index` | Accelerates indexing and detects internal deposits | | Same-chain token transfers | `transactions/single` | Not actively monitored by default indexer | | ETH ↔ WETH wraps/unwraps | `transactions/single` | Same-chain operations need explicit indexing | ## Key Takeaways 1. **transactions/single** is essential for same-chain operations that aren't automatically monitored 2. **transactions/index** is for accelerating indexing of transactions with internal deposits 3. Call these APIs immediately after transaction submission for optimal indexing performance 4. Choose the appropriate API based on your transaction type using the decision matrix above For detailed implementation examples, refer to the Relay SDK source code links provided above. # Status Websockets Source: https://docs.relay.link/references/api/api_guides/websockets Remove Indexing Latency from your App using Relay Websockets Our goal at Relay is to make multichain payments feel instant. Even when transactions confirm quickly onchain, API polling latency can degrade user experience. Connecting your app to Relay Websockets means your users get streamed updates of the status of their Relays for a speedier experience. ## Relay Status Websockets Relay Status Websockets specifically stream transaction statuses for both cross and same-chain status, though the stages differ slightly. | **status** | **indication** | | ---------- | ------------------------------------------------------- | | waiting | waiting for origin chain deposit | | depositing | origin deposit confirmed via /execute API, fill pending | | pending | origin chain deposit confirmed, fill pending submission | | submitted | fill submitted on destination chain | | success | fill succeeded | | failure | fill failed | | refund | refunded | ## Integrating Relay Websockets Refer to the guide below on how to integrate the websocket into your application. You may also refer to the [websocket reference](/references/websockets/intent-status) for more details. ### **Events** | **Event** | **Tags** | **Event Description** | | ------------------------ | -------- | ---------------------------------------- | | `request.status.updated` | `id` | Triggered status of the request changes. | ## **Authentication** The Websocket Service is available for anyone with strict rate limits. For customers with an API key it can be used for authentication for higher rate limits, the service is available at the following URL `wss://ws.relay.link` To connect using an API key, provide your API key as a query param in the connection url: ``` const wss = new ws(`wss://ws.relay.link?apiKey=${YOUR_API_KEY}`); ``` Authentication is generally not required in client-side applications. **The websocket server needs to restart to deploy updates** The websocket server uses a rolling restart mechanism to deploy new changes. This will disconnect you and require you to reconnect to the websocket server. There is no downtime, as you are only disconnected once the new server is up and running. Please make sure to include logic in your implementation to handle these restarts, and reconnect accordingly. ## **Interacting with the Websocket** Before sending any messages over the Websocket, wait for a ready message back from the server. The message looks like: ```json theme={null} { "type": "connection", // The type of operation "status": "ready", // The status of the operation "data": { "id": "9nqpzwmwh86" // Your socket id for debugging purposes } } ``` ### **Subscribing with Filters** Below is an example on how to subscribe to a websocket using filters. ```json Subscribe theme={null} { "type": "subscribe", "event": "request.status.updated", "filters": { "id": "0xae0827617d4ae75b406969971c2d4df9a8e8d819ff0f09581fb45ca123fcc7a0" } } ``` ```json Subscribe Response theme={null} { "type": "subscribe", "status": "success", "data": { "event": "equest.status.updated", "filters": { "id": "0xae0827617d4ae75b406969971c2d4df9a8e8d819ff0f09581fb45ca123fcc7a0" } } } ``` ### **Unsubscribing** ```json Unsubscribe theme={null} { "type": "unsubscribe", "event": "request.status.updated", "filters": { "id": "0xae0827617d4ae75b406969971c2d4df9a8e8d819ff0f09581fb45ca123fcc7a0" } } ``` ```json Unsubscribe Response theme={null} { "type": "unsubscribe", "status": "success", "data": { "event": "request.status.updated" } } ``` Unsubscribing is not required if disconnecting from the websocket as the server cleans up automatically. ## **Example** ### **Javascript** ``` npm install ws ``` ```jsx theme={null} const ws = require("ws"); const YOUR_API_KEY = ""; const wss = new ws(`wss://ws.relay.link?apiKey=${YOUR_API_KEY}`); wss.on("open", function open() { console.log("Connected to Relay"); wss.on("message", function incoming(data) { console.log("Message received: ", JSON.stringify(JSON.parse(data))); // When the connection is ready, subscribe to the top-bids event if (JSON.parse(data).status === "ready") { console.log("Subscribing"); wss.send( JSON.stringify({ type: "subscribe", event: "request.status.updated", filters: { id: "0xae0827617d4ae75b406969971c2d4df9a8e8d819ff0f09581fb45ca123fcc7a0", }, }) ); // To unsubscribe, send the following message // wss.send( // JSON.stringify({ // type: 'unsubscribe', // event: 'request.status.updated', // }), // ); } }); }); ``` # Contract Addresses Source: https://docs.relay.link/references/api/api_resources/contract-addresses Relay's solver and contract addresses can be found below along with each chain Please note that not all deployed contracts have the same address across all chains. Please check carefully to ensure you are using the correct contract address for the chain you are using. ### Solver Addresses | Chain Type | Solver Address | | ---------- | -------------------------------------------- | | Bitcoin | bc1qq2mvrp4g3ugd424dw4xv53rgsf8szkrv853jrc | | EVM | 0xf70da97812cb96acdf810712aa562db8dfa3dbef | | SVM | F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe | | Tron | TYVWGh8XkmU49Hi9PkGAZXiiJPB3J5zJZy | ### Contract Versions | Version | Summary | London | Cancun | Zero-ZkEvm | | ------------------ | --------------------------- | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | | v2 Router | | 0x113a327221d2c4660684449bfc39bc14ad1aaf38 | 0xf5042e6ffac5a625d4e7848e0b01373d8eb9e222 | 0x8fdceeda2951a9747feaf25311435448bce47b2a | | v2 ApprovalProxy | | 0xcd740b0e005cb8647f9baf4febedc8753ceef861 | 0xbbbfd134e9b44bfb5123898ba36b01de7ab93d98 | 0xaec31c3780521c34ca59dc2eb5fb9ee2e285cebe | | v2.1 Router | Revert reasons improvements | 0xb758f3bfa7b9d39ef5457d7c7ffb3702f2ad3982 | 0x3ec130b627944cad9b2750300ecb0a695da522b6 | 0xb758f3bfa7b9d39ef5457d7c7ffb3702f2ad3982 | | v2.1 ApprovalProxy | | 0x953c95146eb8ce763f35caf2f1d46ddf6a33bea2 | 0x58cc3e0aa6cd7bf795832a225179ec2d848ce3e7 | 0x953c95146eb8ce763f35caf2f1d46ddf6a33bea2 | | v3 Router | Indexing improvements | 0x9ef6d3c2f60d7b9008d74cab1fc0f899c957c819 | 0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f | 0xe16870b028704e38dbc254a84d3f72c8ba345ca9 | | v3 ApprovalProxy | | 0x8754bc615047de01228a7527b712806a71a8dc9a | 0xccc88a9d1b4ed6b0eaba998850414b24f1c315be | 0xf6e54bbf91e564fcf0df3ed9f2dd82913e9232c3 | ### Mainnet Contracts ### Testnet Contracts # Supported Chains Source: https://docs.relay.link/references/api/api_resources/supported-chains Relay is currently supported on the following chains. Please reach out to learn more about how to add Relay to your chain. ## Supported Chains ### Mainnets ### Testnets ## Adding Relay to Your Chain Relay can easily be added to any EVM-chain, such as OP stack rollups, Arbitrum Orbit chains, or more. Please feel free to [Reach Out](mailto:support@relay.link) to discuss getting your chain supported today! # Supported Tokens & Routes Source: https://docs.relay.link/references/api/api_resources/supported-routes Relay currently supports multiple cross-chain routes across 69+ blockchain networks. This guide explains how to determine if a particular route and token pair are supported by Relay ## Solver Currencies and Swap Support ## How to check if a route is supported? To determine if a route is supported (also referred to as whether a token is supported for bridging), use the [Chains API](https://api.relay.link/chains) to check for an available route between a given token pair. ```bash theme={null} curl -X GET "https://api.relay.link/chains" ``` The response will return a list of chains, each containing supported token pairs. To verify if a route is supported, follow these steps: ### Step 1: Check Token Support Level Look for the `tokenSupport` field in the response for a given chain: * If `tokenSupport` is `"All"`, then routes involving this chain as either the origin or destination are supported for tokens where Solver or DEX liquidity is available. * If `tokenSupport` is `"Limited"`, proceed to step 2. ### Step 2: Check Individual Token Support If `tokenSupport` is `"Limited"`, check the `erc20Currencies` and `currency` fields in the response. These fields contain token objects with metadata indicating whether a token supports bridging. ### Step 3: Verify Bridging Support Locate the `supportsBridging` field in the token object: * If `supportsBridging` is `true`, the token is supported for bridging. * If `supportsBridging` is `false` or if the token pair is not listed in `erc20Currencies` or `currency`, then the route is not supported. ## Examples of Supported and Limited Token Routes ### Example: All Tokens Supported ```json theme={null} { "chains": [ { "id": 10, "name": "optimism", "displayName": "Optimism", "tokenSupport": "All", "currency": { "id": "eth", "symbol": "ETH", "name": "Ether", "address": "0x0000000000000000000000000000000000000000", "decimals": 18, "supportsBridging": true }, "erc20Currencies": [ { "id": "wbtc", "symbol": "WBTC", "name": "Wrapped BTC", "address": "0x68f180fcce6836688e9084f035309e29bf0a2095", "decimals": 8, "supportsBridging": false, "withdrawalFee": 0, "depositFee": 0, "surgeEnabled": false }, { "id": "usdt", "symbol": "USDT", "name": "Tether USD", "address": "0x94b008aa00579c1307b0ef2c499ad98a8ce58e58", "decimals": 6, "supportsBridging": true, "withdrawalFee": 0, "depositFee": 0, "surgeEnabled": false }, { "id": "dai", "symbol": "DAI", "name": "Dai Stablecoin", "address": "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1", "decimals": 18, "supportsBridging": false, "withdrawalFee": 0, "depositFee": 0, "surgeEnabled": false }, { "id": "usdc", "symbol": "USDC", "name": "USD Coin", "address": "0x0b2c639c533813f4aa9d7837caf62653d097ff85", "decimals": 6, "supportsBridging": true, "supportsPermit": true, "withdrawalFee": 0, "depositFee": 0, "surgeEnabled": false } ] } ] } ``` Since the `tokenSupport` field is `"All"`, routes involving this chain are supported for tokens where Solver or DEX liquidity is available. ### Example: Limited Token Support ```json theme={null} { "chains": [ { "id": 7777777, "name": "zora", "displayName": "Zora", "tokenSupport": "Limited", "currency": { "id": "eth", "symbol": "ETH", "name": "Ether", "address": "0x0000000000000000000000000000000000000000", "decimals": 18, "supportsBridging": true }, "erc20Currencies": [ { "id": "usdc", "symbol": "USDzC", "name": "USD Coin (Bridged from Ethereum)", "address": "0xcccccccc7021b32ebb4e8c08314bd62f7c653ec4", "decimals": 6, "supportsBridging": true, "withdrawalFee": 0, "depositFee": 0, "surgeEnabled": false } ] } ] } ``` Here, `tokenSupport` is `"Limited"`, meaning only specific tokens can be bridged. In this case, `usdc` is listed under `erc20Currencies` with `supportsBridging: true`, so routes involving `usdc` as an origin or destination token on Zora are supported. ## Gas Top-Up For full documentation on enabling gas top-up and checking support requirements, see [Gas Top-Up](/features/gas-top-up). ## API Reference For the most up-to-date information on supported tokens and routes, always refer to the [Chains API](https://api.relay.link/chains) endpoint, as token support can change over time. # Claim App Fees Source: https://docs.relay.link/references/api/claim-app-fees post /app-fees/{wallet}/claim This API claims app fees for a specific wallet. [What are app fees?](/features/app-fees) # Core Source: https://docs.relay.link/references/api/core # Deposit Address Reindex Source: https://docs.relay.link/references/api/deposit-address-reindex post /transactions/deposit-address/reindex Reindex transactions for a deposit address # Execute Gasless Txs Source: https://docs.relay.link/references/api/execute post /execute This API executes gasless transactions # Submit Permit Source: https://docs.relay.link/references/api/execute-permits post /execute/permits This API is used to submit a permit from the quote API. # Fast Fill Source: https://docs.relay.link/references/api/fast-fill post /fast-fill This API accelerates the destination fill # Get App Fee Balances Source: https://docs.relay.link/references/api/get-app-fee-balances get /app-fees/{wallet}/balances This API returns app fee balances for a specific wallet. [What are app fees?](/features/app-fees) # Get Chains Source: https://docs.relay.link/references/api/get-chains get /chains This API returns all possible chains available. # Get Chains Liquidity Source: https://docs.relay.link/references/api/get-chains-liquidity get /chains/liquidity Returns solver liquidity balances per currency on a specified chain. # Get Config Source: https://docs.relay.link/references/api/get-config get /config/v2 This API returns solver capacity data & user data. # Get Currencies Source: https://docs.relay.link/references/api/get-currencies post /currencies/v1 This api returns currency metadata from a curated list # Get Currencies Source: https://docs.relay.link/references/api/get-currencies-v2 post /currencies/v2 This api returns currency metadata from a curated list # Get Execution Status Source: https://docs.relay.link/references/api/get-intents-status get /intents/status This API returns current status of intent. Please use [Get Execution Status v2](/references/api/get-intents-status-v3) as this is deprecated # Get Status Source: https://docs.relay.link/references/api/get-intents-status-v3 get /intents/status/v3 This API returns current status of intent. Relay statuses take one of the following options: | Status | Description | | ------------ | ------------------------------------------------------- | | `waiting` | Waiting for deposit confirmation | | `depositing` | Origin deposit confirmed via /execute API, pending fill | | `pending` | Deposit confirmed, pending destination chain submission | | `submitted` | Destination transaction submitted | | `success` | Successful fill on destination | | `delayed` | Destination fill delayed, still processing | | `refunded` | Successfully refunded | | `failure` | Unsuccessful fill | # Get Price Source: https://docs.relay.link/references/api/get-price post /price This API returns a lightweight quote without calldata or steps. # Get Quote Source: https://docs.relay.link/references/api/get-quote post /quote This API returns an executable quote for swapping, bridging and calling # Get Quote Source: https://docs.relay.link/references/api/get-quote-v2 post /quote/v2 This API returns an executable quote for swapping, bridging and calling # Get Requests Source: https://docs.relay.link/references/api/get-requests get /requests/v2 This API returns all the cross-chain transactions. # Get Swap Sources Source: https://docs.relay.link/references/api/get-swap-sources get /swap-sources This API returns all the available swap sources that can be either included or excluded for routing. # Get Token Price Source: https://docs.relay.link/references/api/get-token-price get /currencies/token/price This API returns the price of a token on a specific chain. # API Overview Source: https://docs.relay.link/references/api/overview We offer multiple APIs to fully harness Relay's tools. You can use our APIs for instant bridging or cross-chain execution. The full OpenAPI specification is available at [api.relay.link/documentation/json](https://api.relay.link/documentation/json). Use it to auto-generate types, clients, or import into tools like Postman. ## Core [Get Quote](/references/api/get-quote-v2): Get an executable quote for a bridge, swap or call [Execute](/references/api/execute): Execute a gasless transaction [Get Status](/references/api/get-intents-status-v3): Returns current execution status [Get Requests](/references/api/get-requests): Returns all relay transactions ## Utilities [Get Chains](/references/api/get-chains): Returns all possible chains available and their configurations [Get Chains Liquidity](/references/api/get-chains-liquidity): Returns solver liquidity balances per currency on a specified chain [Get Currencies](/references/api/get-currencies-v2): Returns all the tokens available on a specific chain [Get Token Price](/references/api/get-token-price): Returns the price of a token on a specific chain [Transactions Index](/references/api/transactions-index): Notify the backend in order to fetch the traces and detect any internal deposits [Transactions Single](/references/api/transactions-single): Notify the backend to index transfers, wraps and unwraps [Deposit Address Reindex](/references/api/deposit-address-reindex): Reindex transactions for a deposit address ## Advanced [Multi-input Quote](/references/api/swap-multi-input): Get an executable quote for swapping tokens from multiple origin chains to a single destination chain [Fast Fill](/references/api/fast-fill): Fast fills a destination fill # Quickstart Source: https://docs.relay.link/references/api/quickstart Execute your first cross-chain transaction in under 5 minutes. Relay is a multichain payments network. You define the **Intent** (what the user wants), and Relay handles the **Execution** (how to get there). Follow this 5-step flow to integrate Relay into your application: Core Loop To see how fast and simple Relay makes transacting cross-chain, let’s bridge real assets between inexpensive L2s—Base and Arbitrum. **Prerequisites:** * **Base URL:** `https://api.relay.link` * **Environment:** Node.js installed * **Wallet:** An EOA with a small amount of ETH (\~\$2.00 USD equivalent) on Base ### API Key Provisioning You can find a self-serve form to request API keys [here](/references/api/api-keys#how-to-get-an-api-key).
Having an API key will allow you to use the API at a higher rate limit.
## Chain Configuration For chain metadata we recommend querying the [chains API](/references/api/get-chains) in your application. This will return some valuable information about the chain, we've provided a sample below: ```json Response expandable highlight={3,12,14,74} theme={null} { "chains": [{ "id":10, "name":"optimism", "displayName":"Optimism", "httpRpcUrl":"https://optimism.publicnode.com", "wsRpcUrl":"wss://optimism.publicnode.com", "explorerUrl":"https://optimistic.etherscan.io", "explorerName":"Optimism Etherscan", "depositEnabled":true, "tokenSupport":"All", "disabled":false, "partialDisableLimit":0, "blockProductionLagging":false, "currency":{ "id":"eth", "symbol":"ETH", "name":"Ether", "address":"0x0000000000000000000000000000000000000000", "decimals":18, "supportsBridging":true }, "withdrawalFee":1, "depositFee":0, "surgeEnabled":false, "featuredTokens":[ { "id":"eth", "symbol":"ETH", "name":"Ether", "address":"0x0000000000000000000000000000000000000000", "decimals":18, "supportsBridging":true, "metadata":{ "logoURI":"https://assets.relay.link/icons/1/light.png" } } ], "erc20Currencies":[ { "id":"sipher", "symbol":"SIPHER", "name":"Sipher", "address":"0xb94944669f7967e16588e55ac41be0d5ef399dcd", "decimals":18, "supportsBridging":true, "withdrawalFee":1, "depositFee":0, "surgeEnabled":false } ], "solverCurrencies":[ { "id":"sipher", "symbol":"SIPHER", "name":"Sipher", "address":"0xb94944669f7967e16588e55ac41be0d5ef399dcd", "decimals":18 }, ], "iconUrl":"https://assets.relay.link/icons/10/light.png", "contracts":{ "multicall3":"0xca11bde05977b3631167028862be2a173976ca11", "multicaller":"0x0000000000002bdbf1bf3279983603ec279cc6df", "onlyOwnerMulticaller":"0xb90ed4c123843cbfd66b11411ee7694ef37e6e72", "relayReceiver":"0xa5f565650890fba1824ee0f21ebbbf660a179934", "erc20Router":"0xf5042e6ffac5a625d4e7848e0b01373d8eb9e222", "approvalProxy":"0xbbbfd134e9b44bfb5123898ba36b01de7ab93d98", "v3":{ "erc20Router":"0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "approvalProxy":"0xccc88a9d1b4ed6b0eaba998850414b24f1c315be" } }, "vmType":"evm", "baseChainId":1, "solverAddresses":[ "0xf70da97812cb96acdf810712aa562db8dfa3dbef" ], "tags":[], "protocol":{ "v2":{ "chainId":"optimism", "depository":"0x4cd00e387622c35bddb9b4c962c136462338bc31" } } }] } ``` ```json Request theme={null} curl -X GET "https://api.relay.link/chains" ``` The endpoint exposes a vast amount of chain metadata but the important things to note are: * `vmType`: This is the type of virtual machine that the chain uses (SVM, EVM, BVM, etc). * `id`: This is the chain id of the chain. Some chains like Bitcoin/Solana/Tron have a custom id that the Relay team chose upon deployment. * `disabled`: This is a boolean that indicates if the chain is disabled. When incidents arise we may disable a chain to prevent users from interacting with it. * `blockProductionLagging`: This boolean indicates if the chain is lagging in block production. You can use this to let your users know about degraded Relay performance in real time.
Every action in Relay starts with a Quote. The [quote endpoint](/references/api/get-quote-v2) handles all of your use cases, whether it’s a [**bridge**](/use-cases/bridging), [**swap**](/use-cases/cross-chain-swaps), or [**cross-chain call**](/use-cases/calling). It calculates fees, finds the best route, and generates the transaction data. ## **Request** As an example, let’s consider the scenario of bridging 0.0001 ETH from Base (Chain ID 8453) to Arbitrum One (Chain ID 42161):
*Note: the `requestId` will be unique to every request.* ```bash Request theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "YOUR_WALLET_ADDRESS", "originChainId": 8453, "destinationChainId": 42161, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000", "tradeType": "EXACT_INPUT" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Deposit funds to the relayer to execute the bridge", "kind": "transaction", "requestId": "0x8a9b3c...", "items": [ { "status": "incomplete", "data": { "from": "YOUR_WALLET_ADDRESS", "to": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "data": "0x00fad611...", "value": "100000000000000", "chainId": 8453, "gas": "150000", "maxFeePerGas": "100000000", "maxPriorityFeePerGas": "100000000" }, "check": { "endpoint": "/intents/status?requestId=0x8a9b3c...", "method": "GET" } } ], "depositAddress": "" } ], # ... "fees": { "gas": { "amount": "21000", "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18 }, "amountUsd": "0.01" }, "relayer": { "amount": "5000000000000", "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "name": "Ether", "decimals": 18 }, "amountUsd": "0.02" } }, "details": { "operation": "bridge", "sender": "YOUR_WALLET_ADDRESS", "recipient": "YOUR_WALLET_ADDRESS", "timeEstimate": 15, "currencyIn": { "amount": "100000000000000", "currency": { "chainId": 8453, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "decimals": 18 } }, "currencyOut": { "amount": "95000000000000", "currency": { "chainId": 42161, "address": "0x0000000000000000000000000000000000000000", "symbol": "ETH", "decimals": 18 } }, "totalImpact": { "usd": "-0.02", "percent": "-0.50" }, "rate": "0.95" } } ```
The [quote endpoint](/references/api/get-quote-v2) returns a `steps` array. Think of this as a recipe your application must follow. You need to iterate through these steps and prompt the user to sign or submit them. ### Deep Dive For the full logic on parsing steps, see [**Understanding Step Execution**](/references/api/api_core_concepts/step-execution). ## The Logic For a simple ETH bridge, the `steps` array contains a single transaction item. To execute it: * **Check the Step Kind:** Identify if the step is a `transaction` (submit to chain) or a `signature` (sign off-chain). * **Execute:** For transactions, submit the provided `data`, `to`, `value`, and `chainId` using the user's wallet. For signatures, follow the `signatureKind` provided in the step item to sign the `message`. ## The Script (Node.js / Viem example) Copy the script below to execute the transaction returned by the Quote in Step 2. You'll need to add the wallet connection logic using your preferred provider. ```typescript expandable theme={null} // npm install viem import { createWalletClient, http } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { base } from 'viem/chains'; // 1. Setup Wallet (Base) //Initialize your wallet using a provider or your preferred method const account = {} const client = createWalletClient({ account, chain: base, transport: http() }); // 2. The Quote from Step 2 (Paste JSON response) const quote = { /* PASTE_FULL_JSON_RESPONSE_HERE */ }; async function execute() { console.log("🚀 Starting Execution..."); // Iterate through steps (usually just 1 for ETH bridge) for (const step of quote.steps) { //Although there is an array of items, you can safely select the first item //as there is just one returned const item = step.items[0] if (step.kind === 'transaction') { console.log(`Submitting Transaction...`); // 3. Send Transaction const hash = await client.sendTransaction({ to: item.data.to, data: item.data.data, value: BigInt(item.data.value), chain: base }); //4. Optional but you may want to wait for the transaction receipt //to ensure that the origin transaction was successfully submitted. console.log(`✅ Bridge Initiated: ${hash}`); console.log(`requestId: ${step.requestId}`); // Save this for Step 4! } } } execute(); ``` Once you submit the transaction, the Relay Solver detects the deposit and fills the request on the destination chain. Use the [status endpoint](/references/api/get-intents-status-v3) with the `requestId` located inside each step object in your [quote response](/references/api/quickstart#request) to track status and confirm success. You can also use the `check.endpoint` property inside the step item object as the endpoint for checking the status of the request.
*Note: Replace with the requestId returned in your quote* ```bash Request theme={null} curl "https://api.relay.link/intents/status/v3?requestId=0x20538510fd9eab7a90c3e54418f8f477bfef24d83c11955a8ca835e6154b59d3" ``` ```json Response theme={null} { "status": "success", "inTxHashes": [ "0x9421772820b92c239162341a17994b4ad8706c974af2ebf88084a1b3b614d328" ], "txHashes": [ "0xef2a71eb2ddd952b31f7b552e133d3af048e4be30a568f2bea54a36d8f3b12cd" ], "updatedAt": 1767385462193, "originChainId": 8453, "destinationChainId": 56 } ``` Poll this endpoint once per second. For a real-time status stream you can subscribe to Relay’s [**websocket server**](/references/api/api_guides/websockets). ## Status Lifecycle The diagram below illustrates the journey of a cross-chain transaction through Relay's infrastructure and the corresponding API statuses. Below are descriptions for each status. Refer to the [Get Status](/references/api/get-intents-status-v3) endpoint for a full list of statuses and what they mean. * `waiting`: The user is submitting or has submitted the deposit transaction to the Relay Depository contract on the origin chain, but it has not yet been indexed by the system. * `depositing`: The origin chain deposit has been confirmed via the /execute API. The system is preparing to fill on the destination chain. * `pending`: The deposit was successfully indexed. The Relay Solver is now monitoring the request and preparing the fill transaction on the destination chain. * `success`: The Relay Solver successfully executed the "Fill Tx", and the funds have reached the recipient. Status Lifecycle The diagram also depicts paths where a transaction may end up in a failure or refund state if the fill cannot be completed. Learn more about how Relay helps you handle [refunds](https://docs.relay.link/references/api/api_core_concepts/refunds).
You have successfully executed your first cross-chain transaction with Relay! Check out some of the advanced features we offer to customize the experience: * [**App Fees**](/features/app-fees): Monetize your integration by adding a fee (in bps) to every quote. Revenue is collected automatically in USDC. * [**Smart Accounts**](/references/api/api_guides/smart_accounts/smart-accounts): Use [ERC-4337](/references/api/api_guides/smart_accounts/erc-4337) and [EIP-7702](/references/api/api_guides/smart_accounts/eip-7702) to enable *Gas Sponsorship* and *Atomic Batching* (e.g., Approve + Swap in one click). * [**Transaction Indexing**](/references/api/api_guides/transaction-indexing): Handle complex settlement scenarios, like tracking complex same-chain wraps or transfers.
*** ## Try it in a Sandbox Experiment with the Quickstart code in our [interactive sandbox](https://codesandbox.io/p/sandbox/github/GiselleNessi/relay-demo). You can fork or clone it to build your own project with Relay. ### See Also * [**Bridging & Onboarding**](/use-cases/bridging): Learn more about instant cross-chain deposits and withdrawals. * [**Swaps**](/use-cases/cross-chain-swaps): Combine bridging with DEX meta-aggregation for any-to-any token swaps. * [**Call Execution**](/use-cases/calling): Execute arbitrary transactions on any chain, paying with any token. # Swap Multi-Input Source: https://docs.relay.link/references/api/swap-multi-input post /execute/swap/multi-input This API returns an executable quote for swapping tokens from multiple origin chains to a single destination chain # Transactions Index Source: https://docs.relay.link/references/api/transactions-index post /transactions/index Notify the backend in order to fetch the traces and detect any internal deposits # Transactions Single Source: https://docs.relay.link/references/api/transactions-single post /transactions/single Notify the backend to index transfers, wraps and unwraps. # Troubleshooting Source: https://docs.relay.link/references/api/troubleshooting Troubleshooting tips and tricks for debugging issues with Relay. ## Failed Fill Transactions If the fill transaction fails you may get some useful data returned from the [requests api](/references/api/get-requests): * [failedTxHash](/references/api/get-requests#response-requests-items-data-failed-tx-hash) * [failedTxBlockNumber](/references/api/get-requests#response-requests-items-data-failed-tx-block-number) * [failedCalldata](/references/api/get-requests#response-requests-items-data-failed-call-data) You can use these values to simulate the transaction in [Tenderly](https://tenderly.co) or similar tools to see the exact error message and revert reason. ### Step 1: Get the failed data You can also access this data by going to the [Relay transaction page](https://www.relay.link/transaction/0x27714ec23ddd6466f383c77368692f948d7c52d320de03b371d0f310437bcb2a) and using the Ctrl + i shortcut to open up the full request data. ```bash cURL theme={null} curl -X GET "https://api.relay.link/requests/v2?id=0x27714ec23ddd6466f383c77368692f948d7c52d320de03b371d0f310437bcb2a" ``` ```json Response theme={null} { "failedTxBlockNumber": 43159746, "failedCallData": { "from": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF", "to": "0xBBbfD134E9b44BfB5123898BA36b01dE7ab93d98", "data": "0x33739082000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000180000000000000000000000000b5640ba93c22d35101eb45094afa735b0a38eeb9000000000000000000000000b5640ba93c22d35101eb45094afa735b0a38eeb90000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000520f6fae9d000000000000000000000000000000000000000000000000000000000069189f7900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002a22f9c3b484c3629090feed35f17ff8f88f76f0000000000000000000000000000000000000000000000000000000002439410e00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000027600000000000000000000000002a22f9c3b484c3629090feed35f17ff8f88f76f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000006352a56caadc4f1e25cd6c75970fa768a3304e64000000000000000000000000000000000000000000000000000000002439410e000000000000000000000000000000000000000000000000000000000000000000000000000000006352a56caadc4f1e25cd6c75970fa768a3304e64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000254490411a320000000000000000000000008d2b7e5501eb6d92f8e349f2febe785dd070be74000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000002a22f9c3b484c3629090feed35f17ff8f88f76f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d2b7e5501eb6d92f8e349f2febe785dd070be74000000000000000000000000f5042e6ffac5a625d4e7848e0b01373d8eb9e222000000000000000000000000000000000000000000000000000000002439410e000000000000000000000000000000000000000000000020dcc82702adb1d2ba00000000000000000000000000000000000000000000002102cf7c6f36006ac30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef00000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000ba00000000000000000000000000000000000000000000000000000000000000dc00000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000000000000000000000000000000000000000152000000000000000000000000000000000000000000000000000000000000019800000000000000000000000000000000000000000000000000000000000001dc00000000000000000000000000000000000000000000000000000000000001fe0000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064eb5625d90000000000000000000000002a22f9c3b484c3629090feed35f17ff8f88f76f00000000000000000000000000392a2f5ac47388945d8c84212469f545fae52b20000000000000000000000000000000000000000000000000000000022699766000000000000000000000000000000000000000000000000000000000000000000000000000000000392a2f5ac47388945d8c84212469f545fae52b200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000242e1a7d4d00000000000000000000000000000000000000000000000000000000226997660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064eb5625d9000000000000000000000000ddafbb505ad214d7b80b1f830fccc89b60fb7a830000000000000000000000007f90122bf0700f9e7e1f688fe926940e8839f3530000000000000000000000000000000000000000000000000000000021b9664a000000000000000000000000000000000000000000000000000000000000000000000000000000007f90122bf0700f9e7e1f688fe926940e8839f35300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000843df02124000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021b9664a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004e451a74316000000000000000000000000ddafbb505ad214d7b80b1f830fccc89b60fb7a8300000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064eb5625d9000000000000000000000000ddafbb505ad214d7b80b1f830fccc89b60fb7a83000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001c452bbbe2900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000008d2b7e5501eb6d92f8e349f2febe785dd070be7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d2b7e5501eb6d92f8e349f2febe785dd070be740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff2086f52651837600180de173b09470f54ef7491000000000000000000000004f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ddafbb505ad214d7b80b1f830fccc89b60fb7a83000000000000000000000000e91d153e0b41518a2ce8dd3d7944fa863463a97d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001649f865422000000000000000000000000e91d153e0b41518a2ce8dd3d7944fa863463a97d000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004000000000000000000000000e91d153e0b41518a2ce8dd3d7944fa863463a97d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104e5b07cdb0000000000000000000000005a2fb66e66b2af7f1c2f71c6c695492faab2e58700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001cfa9a80000000000000000000000008d2b7e5501eb6d92f8e349f2febe785dd070be7400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002e2a22f9c3b484c3629090feed35f17ff8f88f76f0000064af204776c7245bf4147c2612bf6e5972ee48370100000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004e451a74316000000000000000000000000af204776c7245bf4147c2612bf6e5972ee48370100000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064eb5625d9000000000000000000000000af204776c7245bf4147c2612bf6e5972ee483701000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001c452bbbe2900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000008d2b7e5501eb6d92f8e349f2febe785dd070be7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d2b7e5501eb6d92f8e349f2febe785dd070be740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffdd439304a77f54b1f7854751ac1169b279591ef70000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af204776c7245bf4147c2612bf6e5972ee483701000000000000000000000000cb444e90d8198415266c6a2724b7900fb12fc56e000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000003a451a74316000000000000000000000000cb444e90d8198415266c6a2724b7900fb12fc56e00000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064eb5625d9000000000000000000000000cb444e90d8198415266c6a2724b7900fb12fc56e000000000000000000000000056c6c5e684cec248635ed86033378cc444459b0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000056c6c5e684cec248635ed86033378cc444459b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000845b41b908000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000038451a743160000000000000000000000001337bedc9d22ecbe766df105c9623922a27963ec00000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064eb5625d90000000000000000000000001337bedc9d22ecbe766df105c9623922a27963ec0000000000000000000000007f90122bf0700f9e7e1f688fe926940e8839f3530000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000007f90122bf0700f9e7e1f688fe926940e8839f35300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000641a4d01d20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001649f865422000000000000000000000000e91d153e0b41518a2ce8dd3d7944fa863463a97d000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004000000000000000000000000e91d153e0b41518a2ce8dd3d7944fa863463a97d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000242e1a7d4d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000648a6a1e850000000000000000000000000000000000000000000000000000000000000000000000000000000000000000922164bbbd36acf9e854acbbf32facc949fcaeef00000000000000000000000000000000000000000000002102cf7c6f36006ac300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a49f865422000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d1660f990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f5042e6ffac5a625d4e7848e0b01373d8eb9e2220000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f5042e6ffac5a625d4e7848e0b01373d8eb9e22200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000445de81e3f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5640ba93c22d35101eb45094afa735b0a38eeb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412f895cd3347409bb51fb7da43f0772115863b87df05b30ab05f92c9ee44a0240716d70fa3b2946821644eff514b59ca26ef5ac259d6cded7d5add10ec5d506001c0000000000000000000000000000000000000000000000000000000000000027714ec23ddd6466f383c77368692f948d7c52d320de03b371d0f310437bcb2a", "value": "0" } } ``` ### Step 2: Simulate the transaction in Tenderly Open up your Tenderly dashboard, click simulator and then new simulation. Then paste in the required datapoints from the values above. Tenderly Simulation You should then see the simulation rendered in Tenderly with an in depth error message and where the revert happened. # Utilities Source: https://docs.relay.link/references/api/utilities # Addresses Source: https://docs.relay.link/references/protocol/addresses Contract addresses for the Relay Settlement Protocol ## Relay Chain Core settlement contracts deployed on the [Relay Chain](/references/protocol/components/relay-chain) (Chain ID `537713`). | Contract | Address | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ | | **Hub** | [`0xDDD361727C22A01EB137880678A20b0BEaE69318`](https://explorer.chain.relay.link/address/0xDDD361727C22A01EB137880678A20b0BEaE69318) | | **Oracle** | [`0xd4b9fdB83C723c096d7fBE72da252aa23f1387aa`](https://explorer.chain.relay.link/address/0xd4b9fdB83C723c096d7fBE72da252aa23f1387aa) | ## Aurora The [Allocator](/references/protocol/components/allocator) and [Security Council](/references/protocol/components/security-council) multisig are deployed on Aurora for integration with the NEAR MPC network. | Contract | Address | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | **Allocator** | [`0x7EdA04920F22ba6A2b9f2573fd9a6F6F1946Ff9f`](https://explorer.aurora.dev/address/0x7EdA04920F22ba6A2b9f2573fd9a6F6F1946Ff9f) | | **Security Council Multisig** | [`0xb538ee6515F9d16eBD0BACD0503733815c9b070c`](https://explorer.aurora.dev/address/0xb538ee6515F9d16eBD0BACD0503733815c9b070c) | ## Depository Contracts [Depository](/references/protocol/components/depository) contracts are deployed on every supported origin chain. | Chain | Address | VM Type | EVM Chain Id | | --------------- | -------------------------------------------- | ----------- | ------------ | | ethereum | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1 | | optimism | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 10 | | cronos | 0x59916da825d2d2ec1bf878d71c88826f6633ecca | ethereum-vm | 25 | | bnb | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 56 | | gnosis | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 100 | | unichain | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 130 | | polygon | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 137 | | sonic | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 146 | | manta | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 169 | | mint | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 185 | | boba | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 288 | | zksync | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 324 | | shape | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 360 | | appchain | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 466 | | worldchain | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 480 | | redstone | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 690 | | flow | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 747 | | hyperevm | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 999 | | metis | 0x59916da825d2d2ec1bf878d71c88826f6633ecca | ethereum-vm | 1088 | | polygon\_zkevm | 0x59916da825d2d2ec1bf878d71c88826f6633ecca | ethereum-vm | 1101 | | lisk | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1135 | | sei | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1329 | | perennial | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1424 | | story | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1514 | | gravity | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1625 | | soneium | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1868 | | swellchain | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1923 | | sanko | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1996 | | ronin | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 2020 | | abstract | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 2741 | | morph | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 2818 | | hychain | 0x59916da825d2d2ec1bf878d71c88826f6633ecca | ethereum-vm | 2911 | | mantle | 0x59916da825d2d2ec1bf878d71c88826f6633ecca | ethereum-vm | 5000 | | somnia | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 5031 | | superseed | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 5330 | | cyber | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 7560 | | powerloom | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 7869 | | arena\_z | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 7897 | | b3 | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 8333 | | base | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 8453 | | onchain\_points | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 17071 | | apechain | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 33139 | | funki | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 33979 | | mode | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 34443 | | arbitrum | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 42161 | | arbitrum\_nova | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 42170 | | celo | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 42220 | | hemi | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 43111 | | avalanche | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 43114 | | gunz | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 43419 | | zircuit | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 48900 | | superposition | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 55244 | | ink | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 57073 | | linea | 0x59916da825d2d2ec1bf878d71c88826f6633ecca | ethereum-vm | 59144 | | bob | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 60808 | | anime | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 69000 | | berachain | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 80094 | | blast | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 81457 | | plume | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 98866 | | taiko | 0x59916da825d2d2ec1bf878d71c88826f6633ecca | ethereum-vm | 167000 | | syndicate | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 510003 | | scroll | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 534352 | | zero | 0xa88cf7864951147a08707ed732237eaa9b1c3b9b | ethereum-vm | 543210 | | xai | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 660279 | | katana | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 747474 | | forma | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 984122 | | zora | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 7777777 | | bitcoin | bc1qdqqsq6y7csd0cr3ye45h9lv8ydh777j2wehgl6 | bitcoin-vm | | | eclipse | 99vQwtBwYtrqqD9YSXbdum3KBdxPAVxYTaQ3cfnJSrN2 | solana-vm | | | soon | 99vQwtBwYtrqqD9YSXbdum3KBdxPAVxYTaQ3cfnJSrN2 | solana-vm | | | corn | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 21000000 | | degen | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 666666666 | | solana | 99vQwtBwYtrqqD9YSXbdum3KBdxPAVxYTaQ3cfnJSrN2 | solana-vm | | | ancient8 | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 888888888 | | rari | 0x4cd00e387622c35bddb9b4c962c136462338bc31 | ethereum-vm | 1380012617 | # Allocator Source: https://docs.relay.link/references/protocol/components/allocator The component that authorizes crosschain withdrawals ## Overview The Allocator is the component responsible for authorizing withdrawals from [Depository](/references/protocol/components/depository) contracts. When a solver wants to claim funds they've earned by filling orders, the Allocator verifies their [Hub](/references/protocol/components/hub) balance and generates a cryptographic proof that the Depository will accept. The Allocator uses **MPC chain signatures** for signing, meaning no single entity holds the private keys needed to authorize withdrawals. It is governed by the [Security Council](/references/protocol/components/security-council), which can suspend approved withdrawers or replace the Allocator in an emergency. ## How It Works ```mermaid theme={null} sequenceDiagram participant Solver participant Oracle participant Hub as Hub (Relay Chain) participant Spender as AllocatorSpender (Aurora) participant Allocator as Allocator (Aurora) participant PB as Payload Builder participant Depository Solver->>Oracle: 1. Request withdrawal initiation Oracle-->>Solver: 2. Return signed Hub execution Solver->>Hub: 3. Execute transfer to withdrawal address Solver->>Oracle: 4. Request payload params Oracle-->>Solver: 5. Return signed payload params + Oracle signature Solver->>Spender: 6. Request proof (with Oracle signature) Spender->>Spender: Verify Oracle signature Spender->>Allocator: 7. Forward to Allocator Allocator->>PB: Construct chain-specific payload PB-->>Allocator: Withdrawal request (e.g. CallRequest) Allocator->>Allocator: Generate MPC signature Allocator-->>Solver: 8. Return signed withdrawal proof Solver->>Depository: 9. Submit proof to target chain Depository-->>Solver: Funds released ``` The withdrawal authorization process: While solvers are the primary users of the withdrawal flow, any address that holds a balance on the Hub can withdraw using the same mechanism. 1. **Solver requests withdrawal initiation** — The solver requests a withdrawal from the [Oracle](/references/protocol/components/oracle), including the withdrawal parameters and owner authorization. 2. **Oracle returns Hub execution** — The Oracle computes the deterministic [withdrawal address](/references/protocol/components/hub#withdrawal-addresses) for the request and returns a signed execution that transfers the corresponding Hub balance to that address. 3. **Solver executes transfer on Hub** — The solver submits that execution on the Relay Chain so the withdrawal address now holds the requested Hub balance. 4. **Solver requests payload params** — Once the transfer is reflected on the Hub, the solver calls the Oracle again to obtain the signed payload parameters for the withdrawal. 5. **Oracle returns payload params and signature** — The Oracle verifies the withdrawal address balance and returns the payload parameters along with an **EIP-712 signature** over the withdrawal request. 6. **Solver submits to AllocatorSpender** — The solver calls the `RelayAllocatorSpender` contract on Aurora, passing the withdrawal parameters and the Oracle's signature. 7. **AllocatorSpender verifies and forwards** — The `RelayAllocatorSpender` verifies that the Oracle signature is valid and from a registered Oracle (via `ORACLE_ROLE`), then forwards the request to the Allocator. Because the `RelayAllocatorSpender` holds `APPROVED_WITHDRAWER_ROLE` on the Allocator, the call is authorized. 8. **MPC signing** — The Allocator constructs the chain-specific payload via a **Payload Builder** and requests an MPC signature from the NEAR chain signatures network. 9. **Proof delivery** — The signed withdrawal proof is returned to the solver, who submits it to the Depository contract on the target chain. ## Oracle-Gated Access The Allocator uses role-based access control — only addresses holding `APPROVED_WITHDRAWER_ROLE` can trigger withdrawal proof generation. Rather than granting this role to individual solvers, the protocol uses a gateway contract called `RelayAllocatorSpender`. In the current production deployment, `RelayAllocatorSpender` is the contract used to hold `APPROVED_WITHDRAWER_ROLE` and gate withdrawal signing behind Oracle authorization. The Allocator contract itself does not require this to be the only approved withdrawer; additional withdrawers could be granted the role by governance. In the deployed flow, this means: * **No withdrawal proof can be generated without Oracle authorization** — The Oracle must sign the withdrawal parameters, adding a verification layer on top of the MPC signing * **Oracles are managed via `ORACLE_ROLE`** — The [Security Council](/references/protocol/components/security-council) controls which Oracle instances are registered on the `RelayAllocatorSpender` * **Suspension can halt withdrawals in the current deployment** — If `RelayAllocatorSpender` is the only approved withdrawer, suspending it halts withdrawal proof generation across the protocol ## Payload Builders Different chains require different transaction formats. The Allocator uses specialized Payload Builders for each chain type: | Builder | Chains | Signature Format | | --------------------------- | ---------------------------------------------------- | ------------------ | | **EVM Payload Builder** | Ethereum, Base, Arbitrum, Optimism, + all EVM chains | EIP-712 typed data | | **Solana Payload Builder** | Solana, Eclipse, Soon | Ed25519 | | **Bitcoin Payload Builder** | Bitcoin | ECDSA (secp256k1) | Each Payload Builder constructs the chain-specific request structure (e.g., `CallRequest` for EVM, `TransferRequest` for Solana) with the appropriate encoding (ABI for EVM, Borsh for Solana). ## MPC Signing The Allocator uses **Multi-Party Computation (MPC) chain signatures** for withdrawal authorization. Instead of a single private key controlling access to Depository funds, the signing key is split across multiple independent MPC nodes. A threshold of nodes must cooperate to produce a valid signature — no single entity ever holds the full key. Key properties: * **No single key holder** — The private key is split across multiple MPC nodes, and a threshold must cooperate to produce a signature * **Programmable authorization** — The Allocator enforces rules (balance checks) before requesting any signature * **Chain-agnostic** — MPC signing can produce signatures for any curve or format (ECDSA, Ed25519, etc.), enabling support for EVM, Solana, Bitcoin, and other VMs * **Auditable** — All signing requests flow through an onchain contract, creating a transparent record The protocol currently uses **NEAR MPC chain signatures** for its signing infrastructure. The Allocator contract is deployed on **Aurora** (NEAR ecosystem) for direct integration with the NEAR MPC network. The MPC signing layer is designed to be implementation-agnostic. While NEAR chain signatures are used today, the architecture allows for alternative MPC networks or signing backends without changes to the rest of the protocol. ## Security Model The Allocator is a trust-critical component — it controls access to funds in the Depository. Several safeguards protect against misuse: * **[MPC signing](#mpc-signing)** — No single entity holds the full signing key. A threshold of independent MPC nodes must cooperate to produce a valid signature. * **[Oracle-gated access](#oracle-gated-access)** — Withdrawal proofs require a valid Oracle signature, verified by the `RelayAllocatorSpender` before the Allocator processes the request. * **Balance-bounded** — The Allocator can only authorize withdrawals up to a solver's existing [Hub](/references/protocol/components/hub) balance. It cannot mint new balances or alter the ledger. * **[Security Council](/references/protocol/components/security-council)** — A multisig where any single member can suspend an approved withdrawer such as `RelayAllocatorSpender`. In the current deployment, that can halt withdrawal proof generation. A supermajority can replace the Allocator entirely. * **Replay protection** — Every withdrawal proof includes a unique nonce and expiration timestamp, preventing reuse of stale proofs. The Allocator cannot mint new balances or alter the Hub ledger. It can only authorize withdrawals up to a solver's existing Hub balance. Even a compromised Allocator cannot create funds that don't exist. ## Source Code The Allocator contract (`RelayAllocator.sol`) and the gateway contract (`RelayAllocatorSpender.sol`) are part of the [`settlement-protocol`](https://github.com/relayprotocol/settlement-protocol) repository. # Deposit Addresses Source: https://docs.relay.link/references/protocol/components/deposit-addresses Deterministic deposit addresses using counterfactual smart contracts ## Overview Deposit Addresses allow users to bridge funds by sending tokens to a pre-computed address — no wallet connection, no calldata, and no approval transactions required. Under the hood, Relay uses **counterfactual smart contracts** to generate deterministic deposit addresses for each `(orderId, depositor)` pair. Funds sent to these addresses are swept into the [Depository](/references/protocol/components/depository) and attributed to the correct order and depositor. This is powered by two contracts: a **DepositAddressFactory** (deployed once per chain) and a **DepositAddress** (a stateless implementation contract that proxies delegate to). For API integration details and usage examples, see the [Deposit Addresses feature guide](/features/deposit-addresses). ## How It Works The flow has three phases: address computation, deposit, and sweep. ```mermaid theme={null} sequenceDiagram participant API as Relay API participant F as DepositAddressFactory participant DA as Deposit Address participant D as Depository API->>F: 1. computeDepositAddress(orderId, depositor) F-->>API: 2. Return deterministic address Note over API: 3. Return address to user Note over DA: 4. User sends funds to address API->>F: 5. sweep(orderId, depositor, tokens) F->>DA: 6. Deploy EIP-1167 proxy via CREATE2 DA->>D: 7. Sweep funds to Depository ``` ### Address Computation Each deposit address is deterministically derived from the **orderId** and **depositor** using CREATE2 and [EIP-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy clones. The factory computes a salt as `keccak256(orderId, depositor)`, so the address depends on the factory address, the implementation address, and that combined salt. This means the address can be computed **before any contract is deployed** there. This is what makes deposit addresses "counterfactual" — the address is valid and can receive funds even though no contract exists at that address yet. ### Deposit The user sends ERC20 tokens or native ETH directly to the computed address. No calldata is needed — a simple transfer is sufficient. The funds sit at the address until the sweep is triggered. ### Sweep Once funds arrive, the Relay backend calls `sweep` on the DepositAddressFactory. This atomically: 1. **Deploys** an EIP-1167 minimal proxy at the pre-computed address (using CREATE2) 2. **Calls** the proxy's `sweep` function via delegatecall to the DepositAddress implementation 3. **Transfers** all funds from the deposit address into the Depository, tagged with the correct `orderId` and `depositor` If the proxy has already been deployed (e.g., from a previous deposit for the same `(orderId, depositor)` pair), the factory calls `sweep` directly on the existing proxy — no redeployment needed. ## Contracts ### DepositAddressFactory The factory is deployed once per supported chain. It has two key functions: * **`computeDepositAddress(orderId, depositor)`** — Returns the deterministic address where a user should send funds for a given order and credited depositor. This is a view function that requires no gas. * **`sweep(orderId, depositor, tokens)`** — Deploys the proxy (if needed) and sweeps all specified tokens to the Depository. The `tokens` array supports sweeping multiple token types in a single transaction. The factory holds two immutable references: * **DEPOSITORY** — The Depository contract on that chain * **IMPLEMENTATION** — The DepositAddress implementation contract ### DepositAddress The DepositAddress is a stateless implementation contract. It is never called directly — instead, EIP-1167 minimal proxies delegate to it. When called via a proxy, it: 1. Reads the token balance at the proxy address 2. For **ERC20 tokens**: approves the Depository and calls `depositErc20(depositor, token, amount, orderId)` 3. For **native ETH**: calls `depositNative(depositor, orderId)` with the ETH value The DepositAddress includes a `receive()` function to handle edge cases where ETH is sent to the proxy via `selfdestruct` or block rewards, preventing griefing attacks that could block proxy deployment. ## Multi-Token Support A single deposit address can receive multiple token types. The `sweep` function accepts an array of token addresses, sweeping each one to the Depository in a single transaction. The zero address (`0x0000000000000000000000000000000000000000`) represents native ETH. ## Security * **Deterministic and verifiable** — Anyone can independently compute a deposit address from the `orderId` and `depositor` and verify it matches what the API returned * **Atomic operations** — `sweep` is atomic: if any part fails, the entire transaction reverts and funds remain safe at the deposit address * **No approval risk** — Users send funds via simple transfers. No token approvals are granted to third parties * **Non-upgradable** — Both the factory and DepositAddress are immutable contracts * **Re-sweep safe** — If additional funds are sent to an address after the initial sweep, they can be recovered by calling `sweep` again ## Source Code The DepositAddressFactory and DepositAddress contracts are part of the [`settlement-protocol`](https://github.com/relayprotocol/settlement-protocol) repository. # Depository Source: https://docs.relay.link/references/protocol/components/depository The escrow contract deployed on every supported chain ## Overview The Depository is deployed on every chain that Relay supports — as a smart contract on EVM and Solana chains, and as an MPC-controlled address on Bitcoin. It serves as the entry point for user funds — accepting deposits and releasing withdrawals when authorized by the [Allocator](/references/protocol/components/allocator). There are currently 80+ Depository contracts deployed, one per supported chain. Each contract is **non-upgradable**, meaning its logic cannot be changed after deployment. See [Contract Addresses](/references/protocol/addresses) for the full list. ## How It Works The Depository has two core responsibilities: 1. **Accept deposits** — Users send funds to the Depository, tagged with an **orderId** that ties the deposit to a specific solver commitment 2. **Execute withdrawals** — When a solver presents a valid proof from the Allocator, the Depository releases funds The Depository does not track orders, verify fills, or manage balances. It is intentionally minimal — a secure vault that holds funds and releases them only when presented with a valid cryptographic proof. ## Deposits Deposits are designed to be as gas-efficient as possible. On EVM chains, **native-token deposits** can be close to a raw ETH transfer (\~21,000 gas). ERC20 deposits are more expensive because they also perform a token `transferFrom`. ### EVM Chains The EVM Depository supports two deposit methods: * **`depositNative(depositor, id)`** — Deposit native ETH (or the chain's native asset) * **`depositErc20(depositor, token, amount, id)`** — Deposit any ERC20 token Both methods emit events that the [Oracle](/references/protocol/components/oracle) monitors to verify deposits. ### Solana The Solana Depository is an Anchor program that supports: * **`deposit_native(amount, id)`** — Deposit native SOL * **`deposit_token(amount, id)`** — Deposit SPL tokens (including Token-2022) Funds are held in a program-derived address (PDA) vault. ### Bitcoin On Bitcoin, the Depository is an MPC-controlled address rather than a smart contract. Deposits are standard Bitcoin transactions to this address, and withdrawals are authorized by the [Allocator](/references/protocol/components/allocator) via ECDSA (secp256k1) MPC signatures. ## Withdrawals Withdrawals are triggered by solvers who have accumulated balances on the [Hub](/references/protocol/components/hub). The process: 1. Solver requests a withdrawal from the [Allocator](/references/protocol/components/allocator) 2. Allocator generates a signed **CallRequest** (EVM) or **TransferRequest** (Solana) 3. Solver submits the signed request to the Depository's `execute` function 4. Depository verifies the signature against the registered allocator address 5. Funds are transferred to the solver Each withdrawal request includes a **nonce** and **expiration** to prevent replay attacks and stale proofs. Only the registered Allocator can authorize withdrawals. The Allocator address is set at deployment and can only be changed by the contract owner (a security council multisig). ## Security The Depository is designed with minimal trust assumptions: * **Non-upgradable** — Contract logic cannot change after deployment * **Single authority** — Only the registered Allocator can authorize fund movements. The Allocator address can only be changed by the contract **owner**, which is the [Security Council](/references/protocol/components/security-council) multisig * **Audited** — Reviewed by [Spearbit](/references/protocol/security) (February 2025) and [Certora](/references/protocol/security) (June 2025). The broader Settlement Protocol, including Depository flows, was later audited by [Zellic](/references/protocol/security) in November 2025. ## Contract References For full API documentation of the Depository contracts: * [EVM Depository Reference](/references/protocol/contracts/evm-depository) * [SVM Depository Reference](/references/protocol/contracts/solana-depository) * [Contract Addresses](/references/protocol/addresses) # Hub Source: https://docs.relay.link/references/protocol/components/hub The central ledger tracking all token ownership on the Relay Chain ## Overview The Hub is a smart contract on the [Relay Chain](/references/protocol/components/relay-chain) that serves as the central ledger for the protocol. It tracks the ownership of all funds deposited into [Depository](/references/protocol/components/depository) contracts across every supported chain. Implemented as an [ERC6909](https://eips.ethereum.org/EIPS/eip-6909) multi-token contract, the Hub can represent any deposited asset (ETH, USDC, SOL, etc.) from any chain as a token balance. When the [Oracle](/references/protocol/components/oracle) attests that a deposit or fill occurred, the Hub updates balances accordingly. ## How It Works The Hub maintains a global balance sheet: * When a user **deposits** into a Depository, the Oracle attests this, and the Hub **mints** a corresponding token balance for the user * When a solver **fills** an order, the Oracle attests this, and the Hub **transfers** the balance from the user to the solver * When a withdrawal is **initiated**, an Oracle-signed execution moves the balance to a [withdrawal address](#withdrawal-addresses) on the Hub. After funds are claimed from the Depository, the Oracle attests the withdrawal and the Hub **burns** the corresponding balance This means the Hub reflects the current state of all finalized funds across all chains, updated as Oracle attestations are processed. ## Actions The Hub processes three types of actions, all triggered by the [Oracle](/references/protocol/components/oracle): | Action | Trigger | Effect | | ------------ | -------------------------- | --------------------------------------- | | **MINT** | User deposit attested | Creates token balance for the depositor | | **TRANSFER** | Solver fill attested | Moves balance from user to solver | | **BURN** | Solver withdrawal attested | Removes balance (funds already claimed) | Each action is idempotent — the same attestation cannot be processed twice, preventing double-counting. ## Real-Time Settlement A key advantage of the Hub model is **real-time, per-order settlement**. Every order settles individually as soon as the Oracle attests the fill. There is no batching window or settlement delay. This is possible because settlement happens on the Relay Chain, where gas is extremely cheap (\~\$0.005 per settlement). In contrast, protocols that settle on origin chains must batch orders to amortize the high gas costs, forcing solvers to wait hours before they can reclaim capital. **Benefit for solvers:** Immediate balance updates mean solvers can track their available capital in real-time and make withdrawal decisions based on current inventory needs, rather than waiting for batch cycles. ## Token Representation The Hub uses the ERC6909 standard (multi-token) to represent deposited assets. Each unique combination of asset and origin chain gets a token ID on the Hub. For example: * ETH deposited on Optimism → token ID `X` * ETH deposited on Base → token ID `Y` * USDC deposited on Arbitrum → token ID `Z` Token metadata (name, symbol, decimals, origin chain) is stored on-chain, and each Hub token can be viewed through an ERC20-compatible wrapper contract (`ERC20View`) for standard wallet compatibility. ## ERC6909 Interface The Hub implements the full ERC6909 interface, including: * **`balanceOf(owner, tokenId)`** — Check a user's or solver's balance for a specific token * **`transfer(receiver, tokenId, amount)`** — Transfer Hub tokens between addresses * **`approve(spender, tokenId, amount)`** — Approve another address to spend tokens * **Operator system** — Grant an address full control over all token balances * **EIP-712 permits** — Gasless approval signatures This pattern keeps the Hub simple — it's pure ERC6909 with no custom withdrawal logic. The withdrawal semantics are layered on top by convention. The Hub also uses **order addresses** — deterministic virtual addresses derived from deposit parameters, not from `orderId` alone. In the settlement SDK, the order address is derived from the deposit `chainId`, encoded `depositor`, `timestamp`, and `depositId`. When a user deposits, the Hub tracks the order through these addresses, linking deposits to solver fills using the same address-based convention as withdrawals. ## Withdrawal Addresses Withdrawal addresses are deterministic virtual addresses derived from the withdrawal parameters: * Target chain and depository * Currency and amount * Recipient address * Withdrawal nonce The withdrawal address is computed by hashing these parameters together. It doesn't correspond to a real contract — it's a routing alias on the Hub. In the current withdrawal flow, the [Oracle](/references/protocol/components/oracle) returns a signed Hub execution that transfers funds from the owner alias to this withdrawal address. Once that balance is in place, the Oracle can verify it and produce the payload parameters used to obtain a withdrawal proof from the [Allocator](/references/protocol/components/allocator). ## Source Code The Hub contract (`RelayHub.sol`) is part of the [`settlement-protocol`](https://github.com/relayprotocol/settlement-protocol) repository and deployed on the [Relay Chain](https://explorer.chain.relay.link). # Oracle Source: https://docs.relay.link/references/protocol/components/oracle The oracle service and onchain verifier that process settlement attestations ## Overview The Oracle witnesses crosschain activity and produces signed attestations for the [Hub](/references/protocol/components/hub) on the [Relay Chain](/references/protocol/components/relay-chain). It has two components: 1. **Oracle Service** (offchain) — One or more oracle operators that read chain data, witness what has already happened onchain, and return cryptographically signed EIP-712 attestations to the caller 2. **Oracle Contract** (on Relay Chain) — Accepts attestation submissions, verifies that the submitted signature is valid for an authorized oracle signer or oracle signer contract, and executes the corresponding actions on the Hub. Each attestation can only be processed once. The Oracle only acts in response to requests. Solvers and users call the Oracle when they need verification or withdrawal initiation, and the Oracle returns signed attestations or executions that the caller then submits onchain. This **pull-based** model keeps costs low and avoids the overhead of maintaining persistent message channels to every chain. ## What the Oracle Attests The Oracle supports deposits, fills, refunds, and several withdrawal-related attestations: ### Deposits When requested, the Oracle verifies that a user's deposit into the [Depository](/references/protocol/components/depository) occurred on the origin chain. The resulting attestation triggers a **MINT** action on the Hub, creating a token balance that represents the deposited funds. ### Fills When requested, the Oracle verifies that a solver's fill on the destination chain matched the user's intent (correct destination, amount, and action). The resulting attestation triggers a **TRANSFER** action on the Hub, moving the balance from the user to the solver. ### Refunds If a solver can't fill an order, they can send funds directly to the user on the origin chain. The solver then requests an attestation from the Oracle, which verifies the refund occurred and returns a signed attestation. The solver submits this to the Oracle contract to get reimbursed — the same settlement process as a fill. ### Withdrawals The Oracle plays three roles in the withdrawal flow: 1. **Initiation** — The Oracle computes the deterministic [withdrawal address](/references/protocol/components/hub#withdrawal-addresses) for the request and returns a signed Hub execution that transfers the requested balance to that address. 2. **Payload authorization** — Once the withdrawal address holds the requested balance, the Oracle returns signed payload parameters that can be used with the [Allocator](/references/protocol/components/allocator) withdrawal flow to obtain a chain-specific proof. 3. **Completion / burn** — After the solver claims funds from the Depository, the solver requests a withdrawal attestation. The Oracle verifies the target-chain withdrawal and returns a signed execution that burns the corresponding Hub balance (or refunds it back if the withdrawal expires). ## How It Works ```mermaid theme={null} sequenceDiagram participant Caller as Caller (Solver / User) participant V as Oracle Service participant Chains as Supported Chains participant OC as Oracle Contract (Relay Chain) participant Hub as Hub (Relay Chain) Caller->>V: 1. Request attestation V->>Chains: 2. Read and verify chain data Note over V: 3. Oracle signer signs EIP-712 attestation V-->>Caller: 4. Return signed attestation Caller->>OC: 5. Submit signed execution OC->>OC: 6. Verify authorized signer OC->>Hub: 7. Execute actions (MINT / TRANSFER / BURN) ``` The Oracle operates as a request-response pipeline: 1. **Request** — A solver or user calls the Oracle to request verification of a crosschain event 2. **Verify** — The oracle service reads chain data to confirm event details 3. **Sign** — An authorized oracle signer creates an EIP-712 typed data signature over the execution or payload parameters 4. **Optional peer collection** — Oracle instances can request additional peer signatures when configured, but the onchain `RelayOracle` contract itself verifies one authorized signer address per submitted execution 5. **Return** — The signed attestation or execution is returned to the caller, who submits it to the Oracle contract on the Relay Chain. The contract verifies the signer and executes the actions on the Hub ### Pull-Based Verification A key cost optimization is that the Oracle does not require message-passing infrastructure (like LayerZero or Hyperlane) between chains. Instead: * Validators read **historical chain data** on-demand when a caller requests verification * Attestations are submitted by the caller to the Relay Chain, meaning **no gas is spent on origin or destination chains** for verification * This dramatically reduces the per-order cost of verification compared to protocols that emit and relay crosschain messages ### Batch Execution The Oracle supports batch attestation via `executeMultiple()`. Multiple attestations (across different orders and action types) can be submitted in a single transaction on the Relay Chain, further amortizing gas costs. If an individual attestation in a batch has already been processed, it is skipped rather than reverting the entire batch. ## Oracle Contract The Oracle contract on the Relay Chain is a thin verification layer. It receives signed executions, verifies that the submitted signature is valid for an authorized oracle address, and calls the corresponding action on the Hub. That authorized oracle address can itself be an EOA or an EIP-1271 contract such as `RelayOracleMultisig`. The contract itself does not perform any crosschain verification — it trusts the configured oracle signer. The contract manages authorized oracle signers via role-based access control, and ensures idempotency so that the same attestation cannot be processed twice. ## Security The Oracle is a trust-critical component — it determines which deposits and fills are considered valid. Several properties limit the impact of a compromise: * **Signer-controlled** — The onchain Oracle accepts attestations only from authorized oracle signers. If a multisig signer such as `RelayOracleMultisig` is used, any thresholding happens behind that signer contract rather than inside `RelayOracle` * **Cannot steal funds** — Even a compromised Oracle can only incorrectly attribute balances on the Hub. User funds in the Depository remain protected by the [Allocator's](/references/protocol/components/allocator) independent withdrawal authorization. * **Bounded impact** — The [Security Council](/references/protocol/components/security-council) can halt all withdrawal proof generation if anomalous attestations are detected ## Source Code * **Oracle application** — [`relay-protocol-oracle`](https://github.com/relayprotocol/relay-protocol-oracle). See the [Third-Party Oracle guide](/references/protocol/guides/third-party-oracle) for deployment and operation instructions. * **Oracle contract** — Part of the [`settlement-protocol`](https://github.com/relayprotocol/settlement-protocol) repository, deployed on the [Relay Chain](https://explorer.chain.relay.link). # The Relay Chain Source: https://docs.relay.link/references/protocol/components/relay-chain A purpose-built settlement chain for crosschain intent processing ## Overview The Relay Chain is a dedicated blockchain purpose-built for settlement of crosschain intents. It serves as the home of the [Hub](/references/protocol/components/hub) contract — the central ledger that tracks all token ownership and solver balances in the protocol. By running settlement on a dedicated chain, Relay avoids competing for blockspace on congested networks and keeps settlement costs minimal, regardless of how expensive the origin or destination chains are. ## Why a Dedicated Chain Intent protocols typically settle on the origin chain, which means settlement competes with all other activity on that chain. This creates several problems: * **Cost** — Gas prices on popular chains (Ethereum, Base, Arbitrum) fluctuate with demand, making settlement costs unpredictable * **Throughput** — High-volume order flow can be bottlenecked by chain congestion * **Complexity** — Each origin chain requires its own settlement logic and proof verification The Relay Chain solves these by providing: * **Predictable, low-cost gas** — Settlement costs \~\$0.005 per order, regardless of origin/destination chain gas prices * **Dedicated throughput** — No competition with external traffic. The chain is optimized for settlement operations * **Single settlement point** — All orders across all chains settle in one place, simplifying the protocol and reducing the number of moving parts Users and app developers never interact with the Relay Chain directly. It operates entirely in the background — deposits and fills happen on the origin/destination chains, while settlement is handled automatically. ## Technical Details | Property | Value | | --------------------- | -------------------------------------------------------------- | | **Chain ID** | `537713` | | **RPC** | `https://rpc.chain.relay.link` | | **Block Explorer** | [explorer.chain.relay.link](https://explorer.chain.relay.link) | | **Native Asset** | ETH | | **Stack** | Sovereign Stack (rollup) | | **Data Availability** | Celestia | | **VM** | EVM-compatible | ## What Lives on the Relay Chain The Relay Chain hosts the core settlement contracts: * **[Hub](/references/protocol/components/hub)** — The ERC6909 multi-token ledger that tracks all balances * **[Oracle](/references/protocol/components/oracle)** — The contract that processes oracle attestations (mint, burn, transfer) * **ERC20View** — A wrapper contract that exposes Hub token balances as standard ERC20 tokens for wallet compatibility The [Allocator](/references/protocol/components/allocator) is deployed on **Aurora** (NEAR ecosystem) rather than on the Relay Chain, for direct integration with the NEAR MPC network used for withdrawal proof generation. Settlement activity on the Relay Chain includes deposit attestations, fill and refund settlement executions, balance transfers, and withdrawal-burn executions. Withdrawal proof generation itself happens on Aurora via the [Allocator](/references/protocol/components/allocator), not on the Relay Chain. ## Exploring Settlement Activity Every settlement transaction is visible on the [Relay Chain Block Explorer](https://explorer.chain.relay.link). You can trace the full lifecycle of any order by looking at the Oracle contract's attestation transactions, which record deposits, fills, and withdrawals. # Security Council Source: https://docs.relay.link/references/protocol/components/security-council The multisig governance body that protects the protocol ## Overview The Security Council is a multisig composed of multiple independent parties across different timezones. It serves as the protocol's emergency response and governance mechanism, with the authority to suspend approved withdrawers, manage roles, and replace the [Allocator](/references/protocol/components/allocator). ## Tiered Thresholds The Security Council uses tiered thresholds to balance rapid response with governance safety: | Action | Threshold | Purpose | | ------------------------------- | ----------------- | ------------------------------------------------------------------ | | **Suspend approved withdrawer** | 1-of-N | Any member can revoke `APPROVED_WITHDRAWER_ROLE` from a withdrawer | | **Restore approved withdrawer** | Governance action | Re-grant `APPROVED_WITHDRAWER_ROLE` after investigation | | **Update Allocator** | Quorum | Add payload builders to add support for new chains | | **Add/Remove Oracle instances** | Quorum | Add or remove Oracles whose attestations are taken into account | | **Replace Allocator** | Supermajority | Upgrade or fix the Allocator contract | | **Change Membership** | Supermajority | Add or remove council members | The low suspend threshold (1-of-N) ensures rapid response to emergencies, while the high thresholds for structural changes prevent unilateral action. Restoring a suspended withdrawer is done through a role grant by the Allocator owner; any stricter threshold is an operational governance choice rather than a rule enforced by the contract itself. ## Suspend Mechanism In the current production deployment, withdrawal proof generation flows through the [`RelayAllocatorSpender`](/references/protocol/components/allocator#oracle-gated-access) contract, which holds `APPROVED_WITHDRAWER_ROLE` on the Allocator. Any single Security Council member can call `suspend(address)` on the Allocator to revoke this role from a specific withdrawer, including `RelayAllocatorSpender`. If `RelayAllocatorSpender` is the only approved withdrawer, then suspending this single address is effectively a **global halt** for new withdrawal proofs. That is a deployment property, not an invariant enforced by the Allocator contract itself. ## Scope The Security Council cannot: * Modify the [Hub](/references/protocol/components/hub) ledger or create balances * Withdraw user funds from the [Depository](/references/protocol/components/depository) * Alter Oracle attestations * Access funds held in any contract # EVM Depository Reference Source: https://docs.relay.link/references/protocol/contracts/evm-depository Solidity API reference for the EVM Relay Depository contract ## Solidity API ## EVM Relay Depository Provides secure deposit functionality and execution of allocator-signed requests for EVM chains *EVM implementation using EIP-712 for structured data signing, supporting native ETH and ERC20 tokens* ### AddressCannotBeZero ```solidity theme={null} error AddressCannotBeZero() ``` Revert if the address is zero ### InvalidSignature ```solidity theme={null} error InvalidSignature() ``` Revert if the signature is invalid ### CallRequestExpired ```solidity theme={null} error CallRequestExpired() ``` Revert if the call request is expired ### CallRequestAlreadyUsed ```solidity theme={null} error CallRequestAlreadyUsed() ``` Revert if the call request has already been used ### CallFailed ```solidity theme={null} error CallFailed(bytes returnData) ``` Revert if a call fails #### Parameters | Name | Type | Description | | ---------- | ----- | -------------------------------------- | | returnData | bytes | The data returned from the failed call | ### RelayNativeDeposit ```solidity theme={null} event RelayNativeDeposit(address from, uint256 amount, bytes32 id) ``` Emit event when a native deposit is made #### Parameters | Name | Type | Description | | ------ | ------- | ------------------------------------------------- | | from | address | The address that made the deposit | | amount | uint256 | The amount of native currency deposited | | id | bytes32 | The unique identifier associated with the deposit | ### RelayErc20Deposit ```solidity theme={null} event RelayErc20Deposit(address from, address token, uint256 amount, bytes32 id) ``` Emit event when an erc20 deposit is made #### Parameters | Name | Type | Description | | ------ | ------- | ------------------------------------------------- | | from | address | The address that made the deposit | | token | address | The address of the ERC20 token | | amount | uint256 | The amount of tokens deposited | | id | bytes32 | The unique identifier associated with the deposit | ### RelayCallExecuted ```solidity theme={null} event RelayCallExecuted(bytes32 id, struct Call call) ``` Emit event when a call is executed #### Parameters | Name | Type | Description | | ---- | ----------- | ----------------------------------- | | id | bytes32 | The identifier of the call request | | call | struct Call | The call details that were executed | ### \_CALL\_TYPEHASH ```solidity theme={null} bytes32 _CALL_TYPEHASH ``` The EIP-712 typehash for the Call struct *Used in structured data hashing for signature verification* ### \_CALL\_REQUEST\_TYPEHASH ```solidity theme={null} bytes32 _CALL_REQUEST_TYPEHASH ``` The EIP-712 typehash for the CallRequest struct *Used in structured data hashing for signature verification* ### callRequests ```solidity theme={null} mapping(bytes32 => bool) callRequests ``` Set of executed call requests *Maps the hash of a call request to whether it has been executed* ### allocator ```solidity theme={null} address allocator ``` The allocator address *Must be set to a secure and trusted entity* ### constructor ```solidity theme={null} constructor(address _owner, address _allocator) public ``` Initializes the contract with an owner and allocator #### Parameters | Name | Type | Description | | ----------- | ------- | -------------------------------------------------- | | \_owner | address | The address that will own the contract | | \_allocator | address | The address authorized to sign withdrawal requests | ### setAllocator ```solidity theme={null} function setAllocator(address _allocator) external ``` Set the allocator address *Only callable by the contract owner* #### Parameters | Name | Type | Description | | ----------- | ------- | ------------------------- | | \_allocator | address | The new allocator address | ### depositNative ```solidity theme={null} function depositNative(address depositor, bytes32 id) external payable ``` Deposit native tokens and emit a `RelayNativeDeposit` event *Emits a RelayNativeDeposit event with the deposit details* #### Parameters | Name | Type | Description | | --------- | ------- | ------------------------------------------------------------------------- | | depositor | address | The address of the depositor - set to `address(0)` to credit `msg.sender` | | id | bytes32 | The identifier associated with the deposit | ### depositErc20 ```solidity theme={null} function depositErc20(address depositor, address token, uint256 amount, bytes32 id) public ``` Deposit erc20 tokens and emit an `RelayErc20Deposit` event *Transfers tokens from msg.sender to this contract and emits a RelayErc20Deposit event* #### Parameters | Name | Type | Description | | --------- | ------- | ------------------------------------------------------------------------- | | depositor | address | The address of the depositor - set to `address(0)` to credit `msg.sender` | | token | address | The erc20 token to deposit | | amount | uint256 | The amount to deposit | | id | bytes32 | The identifier associated with the deposit | ### depositErc20 ```solidity theme={null} function depositErc20(address depositor, address token, bytes32 id) external ``` Deposit erc20 tokens and emit an `RelayErc20Deposit` event *Uses the full allowance granted to this contract and calls depositErc20* #### Parameters | Name | Type | Description | | --------- | ------- | ------------------------------------------------------------------------- | | depositor | address | The address of the depositor - set to `address(0)` to credit `msg.sender` | | token | address | The erc20 token to deposit | | id | bytes32 | The identifier associated with the deposit | ### execute ```solidity theme={null} function execute(struct CallRequest request, bytes signature) external returns (struct CallResult[] results) ``` Execute a `CallRequest` signed by the allocator *Verifies the signature, expiration, and uniqueness before execution* #### Parameters | Name | Type | Description | | --------- | ------------------ | -------------------------------- | | request | struct CallRequest | The `CallRequest` to execute | | signature | bytes | The signature from the allocator | #### Return Values | Name | Type | Description | | ------- | -------------------- | ------------------------ | | results | struct CallResult\[] | The results of the calls | ### \_executeCalls ```solidity theme={null} function _executeCalls(bytes32 id, struct Call[] calls) internal returns (struct CallResult[] returnData) ``` Internal function to execute a list of calls *Handles multiple calls and properly manages failures based on allowFailure flag* #### Parameters | Name | Type | Description | | ----- | -------------- | ---------------------------------- | | id | bytes32 | The identifier of the call request | | calls | struct Call\[] | The array of calls to execute | #### Return Values | Name | Type | Description | | ---------- | -------------------- | --------------------------------- | | returnData | struct CallResult\[] | The results of each executed call | ### \_hashCallRequest ```solidity theme={null} function _hashCallRequest(struct CallRequest request) internal view returns (bytes32 structHash, bytes32 eip712Hash) ``` Helper function to hash a `CallRequest` and return the EIP-712 digest *Implements EIP-712 structured data hashing for the complex CallRequest type* #### Parameters | Name | Type | Description | | ------- | ------------------ | ------------------------- | | request | struct CallRequest | The `CallRequest` to hash | #### Return Values | Name | Type | Description | | ---------- | ------- | --------------- | | structHash | bytes32 | The struct hash | | eip712Hash | bytes32 | The EIP712 hash | ### \_domainNameAndVersion ```solidity theme={null} function _domainNameAndVersion() internal pure returns (string name, string version) ``` Returns the domain name and version of the contract to be used in the domain separator *Implements required function from EIP712 base contract* #### Return Values | Name | Type | Description | | ------- | ------ | --------------- | | name | string | The domain name | | version | string | The version | ## Call A structure representing a single call to be executed ### Parameters | Name | Type | Description | | ---- | ---- | ----------- | ```solidity theme={null} struct Call { address to; bytes data; uint256 value; bool allowFailure; } ``` ## CallRequest A request containing multiple calls to be executed after signature verification ### Parameters | Name | Type | Description | | ---- | ---- | ----------- | ```solidity theme={null} struct CallRequest { struct Call[] calls; uint256 nonce; uint256 expiration; } ``` ## CallResult The result of an executed call ### Parameters | Name | Type | Description | | ---- | ---- | ----------- | ```solidity theme={null} struct CallResult { bool success; bytes returnData; } ``` # SVM Depository Reference Source: https://docs.relay.link/references/protocol/contracts/solana-depository Anchor API reference for the Solana Relay Depository program ## API **Version:** 0.1.0 **Format Version:** 46 ## Module `relay_depository` ## Modules ## Module `program` Module representing the program. ```rust theme={null} pub mod program { /* ... */ } ``` ### Types #### Struct `RelayDepository` Type representing the program. ```rust theme={null} pub struct RelayDepository; ``` ## Module `relay_depository` ```rust theme={null} pub mod relay_depository { /* ... */ } ``` ### Functions #### Function `initialize` Initialize the relay depository program with owner and allocator Creates and initializes the relay depository account with the specified owner and allocator. ###### Parameters * `ctx` - The context containing the accounts ###### Returns * `Ok(())` on success ```rust theme={null} pub fn initialize(ctx: Context<''_, ''_, ''_, ''_, Initialize<''_>>) -> Result<()> { /* ... */ } ``` #### Function `set_allocator` Update the allocator public key Allows the owner to change the authorized allocator that can sign transfer requests. ###### Parameters * `ctx` - The context containing the accounts * `new_allocator` - The public key of the new allocator ###### Returns * `Ok(())` on success * `Err(error)` if not authorized ```rust theme={null} pub fn set_allocator(ctx: Context<''_, ''_, ''_, ''_, SetAllocator<''_>>, new_allocator: Pubkey) -> Result<()> { /* ... */ } ``` #### Function `set_owner` Update the owner public key Allows the current owner to transfer ownership to a new address. ###### Parameters * `ctx` - The context containing the accounts * `new_owner` - The public key of the new owner ###### Returns * `Ok(())` on success * `Err(error)` if not authorized ```rust theme={null} pub fn set_owner(ctx: Context<''_, ''_, ''_, ''_, SetOwner<''_>>, new_owner: Pubkey) -> Result<()> { /* ... */ } ``` #### Function `deposit_native` Deposit native SOL tokens into the vault Transfers SOL from the sender to the vault and emits a deposit event. ###### Parameters * `ctx` - The context containing the accounts * `amount` - The amount of SOL to deposit * `id` - A unique identifier for the deposit ###### Returns * `Ok(())` on success ```rust theme={null} pub fn deposit_native(ctx: Context<''_, ''_, ''_, ''_, DepositNative<''_>>, amount: u64, id: [u8; 32]) -> Result<()> { /* ... */ } ``` #### Function `deposit_token` Deposit SPL tokens into the vault Creates the vault's token account if needed, transfers tokens from the sender, and emits a deposit event. ###### Parameters * `ctx` - The context containing the accounts * `amount` - The amount of tokens to deposit * `id` - A unique identifier for the deposit ###### Returns * `Ok(())` on success ```rust theme={null} pub fn deposit_token(ctx: Context<''_, ''_, ''_, ''_, DepositToken<''_>>, amount: u64, id: [u8; 32]) -> Result<()> { /* ... */ } ``` #### Function `execute_transfer` Execute a transfer with allocator signature Verifies the allocator's signature, transfers tokens to the recipient, and marks the request as used. ###### Parameters * `ctx` - The context containing the accounts * `request` - The transfer request details and signature ###### Returns * `Ok(())` on success * `Err(error)` if signature is invalid or request can't be processed ```rust theme={null} pub fn execute_transfer(ctx: Context<''_, ''_, ''_, ''_, ExecuteTransfer<''_>>, request: TransferRequest) -> Result<()> { /* ... */ } ``` ## Module `instruction` An Anchor generated module containing the program's set of instructions, where each method handler in the `#[program]` mod is associated with a struct defining the input arguments to the method. These should be used directly, when one wants to serialize Anchor instruction data, for example, when specifying instructions on a client. ```rust theme={null} pub mod instruction { /* ... */ } ``` ### Types #### Struct `Initialize` Instruction. ```rust theme={null} pub struct Initialize; ``` #### Struct `SetAllocator` Instruction. ```rust theme={null} pub struct SetAllocator { pub new_allocator: Pubkey, } ``` ##### Fields | Name | Type | Documentation | | --------------- | -------- | ------------- | | `new_allocator` | `Pubkey` | | #### Struct `SetOwner` Instruction. ```rust theme={null} pub struct SetOwner { pub new_owner: Pubkey, } ``` ##### Fields | Name | Type | Documentation | | ----------- | -------- | ------------- | | `new_owner` | `Pubkey` | | #### Struct `DepositNative` Instruction. ```rust theme={null} pub struct DepositNative { pub amount: u64, pub id: [u8; 32], } ``` ##### Fields | Name | Type | Documentation | | -------- | ---------- | ------------- | | `amount` | `u64` | | | `id` | `[u8; 32]` | | #### Struct `DepositToken` Instruction. ```rust theme={null} pub struct DepositToken { pub amount: u64, pub id: [u8; 32], } ``` ##### Fields | Name | Type | Documentation | | -------- | ---------- | ------------- | | `amount` | `u64` | | | `id` | `[u8; 32]` | | #### Struct `ExecuteTransfer` Instruction. ```rust theme={null} pub struct ExecuteTransfer { pub request: TransferRequest, } ``` ##### Fields | Name | Type | Documentation | | --------- | ----------------- | ------------- | | `request` | `TransferRequest` | | ## Module `cpi` **Attributes:** * `#[(feature = "cpi")]` ```rust theme={null} pub mod cpi { /* ... */ } ``` ### Modules ## Module `accounts` An Anchor generated module, providing a set of structs mirroring the structs deriving `Accounts`, where each field is an `AccountInfo`. This is useful for CPI. ```rust theme={null} pub mod accounts { /* ... */ } ``` ### Re-exports #### Re-export `crate::__cpi_client_accounts_set_allocator::*` ```rust theme={null} pub use crate::__cpi_client_accounts_set_allocator::*; ``` #### Re-export `crate::__cpi_client_accounts_set_owner::*` ```rust theme={null} pub use crate::__cpi_client_accounts_set_owner::*; ``` #### Re-export `crate::__cpi_client_accounts_initialize::*` ```rust theme={null} pub use crate::__cpi_client_accounts_initialize::*; ``` #### Re-export `crate::__cpi_client_accounts_deposit_token::*` ```rust theme={null} pub use crate::__cpi_client_accounts_deposit_token::*; ``` #### Re-export `crate::__cpi_client_accounts_execute_transfer::*` ```rust theme={null} pub use crate::__cpi_client_accounts_execute_transfer::*; ``` #### Re-export `crate::__cpi_client_accounts_deposit_native::*` ```rust theme={null} pub use crate::__cpi_client_accounts_deposit_native::*; ``` ### Types #### Struct `Return` ```rust theme={null} pub struct Return { // Some fields omitted } ``` ##### Fields | Name | Type | Documentation | | ---------------- | ---- | ------------------------------- | | *private fields* | ... | *Some fields have been omitted* | ### Functions #### Function `initialize` ```rust theme={null} pub fn initialize<''a, ''b, ''c, ''info>(ctx: anchor_lang::context::CpiContext<''a, ''b, ''c, ''info, crate::cpi::accounts::Initialize<''info>>) -> anchor_lang::Result<()> { /* ... */ } ``` #### Function `set_allocator` ```rust theme={null} pub fn set_allocator<''a, ''b, ''c, ''info>(ctx: anchor_lang::context::CpiContext<''a, ''b, ''c, ''info, crate::cpi::accounts::SetAllocator<''info>>, new_allocator: Pubkey) -> anchor_lang::Result<()> { /* ... */ } ``` #### Function `set_owner` ```rust theme={null} pub fn set_owner<''a, ''b, ''c, ''info>(ctx: anchor_lang::context::CpiContext<''a, ''b, ''c, ''info, crate::cpi::accounts::SetOwner<''info>>, new_owner: Pubkey) -> anchor_lang::Result<()> { /* ... */ } ``` #### Function `deposit_native` ```rust theme={null} pub fn deposit_native<''a, ''b, ''c, ''info>(ctx: anchor_lang::context::CpiContext<''a, ''b, ''c, ''info, crate::cpi::accounts::DepositNative<''info>>, amount: u64, id: [u8; 32]) -> anchor_lang::Result<()> { /* ... */ } ``` #### Function `deposit_token` ```rust theme={null} pub fn deposit_token<''a, ''b, ''c, ''info>(ctx: anchor_lang::context::CpiContext<''a, ''b, ''c, ''info, crate::cpi::accounts::DepositToken<''info>>, amount: u64, id: [u8; 32]) -> anchor_lang::Result<()> { /* ... */ } ``` #### Function `execute_transfer` ```rust theme={null} pub fn execute_transfer<''a, ''b, ''c, ''info>(ctx: anchor_lang::context::CpiContext<''a, ''b, ''c, ''info, crate::cpi::accounts::ExecuteTransfer<''info>>, request: TransferRequest) -> anchor_lang::Result<()> { /* ... */ } ``` ## Module `accounts` An Anchor generated module, providing a set of structs mirroring the structs deriving `Accounts`, where each field is a `Pubkey`. This is useful for specifying accounts for a client. ```rust theme={null} pub mod accounts { /* ... */ } ``` ### Re-exports #### Re-export `crate::__client_accounts_execute_transfer::*` ```rust theme={null} pub use crate::__client_accounts_execute_transfer::*; ``` #### Re-export `crate::__client_accounts_deposit_token::*` ```rust theme={null} pub use crate::__client_accounts_deposit_token::*; ``` #### Re-export `crate::__client_accounts_initialize::*` ```rust theme={null} pub use crate::__client_accounts_initialize::*; ``` #### Re-export `crate::__client_accounts_set_owner::*` ```rust theme={null} pub use crate::__client_accounts_set_owner::*; ``` #### Re-export `crate::__client_accounts_set_allocator::*` ```rust theme={null} pub use crate::__client_accounts_set_allocator::*; ``` #### Re-export `crate::__client_accounts_deposit_native::*` ```rust theme={null} pub use crate::__client_accounts_deposit_native::*; ``` ## Types ### Struct `RelayDepository` Relay depository account that stores configuration and state This account is a PDA derived from the `RELAY_DEPOSITORY_SEED` and contains the ownership and allocation information. ```rust theme={null} pub struct RelayDepository { pub owner: Pubkey, pub allocator: Pubkey, pub vault_bump: u8, } ``` #### Fields | Name | Type | Documentation | | ------------ | -------- | -------------------------------------------------------------------- | | `owner` | `Pubkey` | The owner of the relay depository who can update settings | | `allocator` | `Pubkey` | The authorized allocator that can sign transfer requests | | `vault_bump` | `u8` | The bump seed for the vault PDA, used for deriving the vault address | ### Struct `UsedRequest` Account that tracks whether a transfer request has been used This account is created for each transfer request to prevent replay attacks. ```rust theme={null} pub struct UsedRequest { pub is_used: bool, } ``` #### Fields | Name | Type | Documentation | | --------- | ------ | ------------------------------------------------------ | | `is_used` | `bool` | Flag indicating whether the request has been processed | ### Struct `Initialize` Accounts required for initializing the relay depository ```rust theme={null} pub struct Initialize<''info> { pub relay_depository: Account<''info, RelayDepository>, pub vault: UncheckedAccount<''info>, pub owner: Signer<''info>, pub allocator: UncheckedAccount<''info>, pub system_program: Program<''info, System>, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `relay_depository` | `Account<''info, RelayDepository>` | The relay depository account to be initialized
This is a PDA derived from the RELAY\_DEPOSITORY\_SEED | | `vault` | `UncheckedAccount<''info>` | PDA that will hold SOL deposits
CHECK: This is a PDA derived from the VAULT\_SEED | | `owner` | `Signer<''info>` | The owner account that pays for initialization
Must match the AUTHORIZED\_PUBKEY | | `allocator` | `UncheckedAccount<''info>` | The allocator account that will be authorized to sign transfer requests
CHECK: Used as public key only | | `system_program` | `Program<''info, System>` | | ### Struct `InitializeBumps` ```rust theme={null} pub struct InitializeBumps { pub relay_depository: u8, pub vault: u8, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---- | ------------- | | `relay_depository` | `u8` | | | `vault` | `u8` | | ### Struct `SetAllocator` Accounts required for updating the allocator ```rust theme={null} pub struct SetAllocator<''info> { pub relay_depository: Account<''info, RelayDepository>, pub owner: Signer<''info>, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---------------------------------- | -------------------------------------- | | `relay_depository` | `Account<''info, RelayDepository>` | The relay depository account to update | | `owner` | `Signer<''info>` | The owner of the relay depository | ### Struct `SetAllocatorBumps` ```rust theme={null} pub struct SetAllocatorBumps { pub relay_depository: u8, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---- | ------------- | | `relay_depository` | `u8` | | ### Struct `SetOwner` Accounts required for updating the owner ```rust theme={null} pub struct SetOwner<''info> { pub relay_depository: Account<''info, RelayDepository>, pub owner: Signer<''info>, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---------------------------------- | ----------------------------------------- | | `relay_depository` | `Account<''info, RelayDepository>` | The relay depository account to update | | `owner` | `Signer<''info>` | The current owner of the relay depository | ### Struct `SetOwnerBumps` ```rust theme={null} pub struct SetOwnerBumps { pub relay_depository: u8, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---- | ------------- | | `relay_depository` | `u8` | | ### Struct `DepositNative` Accounts required for depositing native currency ```rust theme={null} pub struct DepositNative<''info> { pub relay_depository: Account<''info, RelayDepository>, pub sender: Signer<''info>, pub depositor: UncheckedAccount<''info>, pub vault: UncheckedAccount<''info>, pub system_program: Program<''info, System>, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------- | | `relay_depository` | `Account<''info, RelayDepository>` | The relay depository account | | `sender` | `Signer<''info>` | The sender of the deposit | | `depositor` | `UncheckedAccount<''info>` | The account credited for the deposit
CHECK: The account credited for the deposit | | `vault` | `UncheckedAccount<''info>` | The vault PDA that will receive the SOL
CHECK: The vault PDA that will receive the SOL | | `system_program` | `Program<''info, System>` | The system program | ### Struct `DepositNativeBumps` ```rust theme={null} pub struct DepositNativeBumps { pub relay_depository: u8, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---- | ------------- | | `relay_depository` | `u8` | | ### Struct `DepositToken` Accounts required for depositing tokens ```rust theme={null} pub struct DepositToken<''info> { pub relay_depository: Account<''info, RelayDepository>, pub sender: Signer<''info>, pub depositor: UncheckedAccount<''info>, pub vault: UncheckedAccount<''info>, pub mint: InterfaceAccount<''info, anchor_spl::token_interface::Mint>, pub sender_token_account: InterfaceAccount<''info, anchor_spl::token_interface::TokenAccount>, pub vault_token_account: UncheckedAccount<''info>, pub token_program: Interface<''info, anchor_spl::token_interface::TokenInterface>, pub associated_token_program: Program<''info, anchor_spl::associated_token::AssociatedToken>, pub system_program: Program<''info, System>, } ``` #### Fields | Name | Type | Documentation | | -------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `relay_depository` | `Account<''info, RelayDepository>` | The relay depository account | | `sender` | `Signer<''info>` | The sender of the deposit | | `depositor` | `UncheckedAccount<''info>` | The account credited for the deposit
CHECK: The account credited for the deposit | | `vault` | `UncheckedAccount<''info>` | The vault PDA that will receive the tokens
CHECK: The vault PDA that will receive the tokens | | `mint` | `InterfaceAccount<''info, anchor_spl::token_interface::Mint>` | The mint of the token being deposited | | `sender_token_account` | `InterfaceAccount<''info, anchor_spl::token_interface::TokenAccount>` | The sender's token account | | `vault_token_account` | `UncheckedAccount<''info>` | CHECK: The vault's token account | | `token_program` | `Interface<''info, anchor_spl::token_interface::TokenInterface>` | The token program | | `associated_token_program` | `Program<''info, anchor_spl::associated_token::AssociatedToken>` | The associated token program | | `system_program` | `Program<''info, System>` | The system program | ### Struct `DepositTokenBumps` ```rust theme={null} pub struct DepositTokenBumps { pub relay_depository: u8, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---- | ------------- | | `relay_depository` | `u8` | | ### Struct `ExecuteTransfer` **Attributes:** * `#[instruction(request: TransferRequest)]` Accounts required for executing a transfer ```rust theme={null} pub struct ExecuteTransfer<''info> { pub relay_depository: Account<''info, RelayDepository>, pub executor: Signer<''info>, pub recipient: UncheckedAccount<''info>, pub vault: UncheckedAccount<''info>, pub mint: Option>, pub recipient_token_account: Option>, pub vault_token_account: Option>, pub used_request: Account<''info, UsedRequest>, pub ix_sysvar: AccountInfo<''info>, pub token_program: Interface<''info, anchor_spl::token_interface::TokenInterface>, pub associated_token_program: Program<''info, anchor_spl::associated_token::AssociatedToken>, pub system_program: Program<''info, System>, } ``` #### Fields | Name | Type | Documentation | | -------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `relay_depository` | `Account<''info, RelayDepository>` | The relay depository account
CHECK: The relay depository account | | `executor` | `Signer<''info>` | The executor of the transfer
CHECK: The executor of the transfer | | `recipient` | `UncheckedAccount<''info>` | The recipient of the transfer
CHECK: The recipient of the transfer | | `vault` | `UncheckedAccount<''info>` | The vault PDA that will receive the tokens
CHECK: The vault PDA that will receive the tokens | | `mint` | `Option>` | The mint of the token being transferred | | `recipient_token_account` | `Option>` | The recipient's token account | | `vault_token_account` | `Option>` | The vault's token account | | `used_request` | `Account<''info, UsedRequest>` | The account that tracks whether a transfer request has been used

This account is created for each transfer request to prevent replay attacks. | | `ix_sysvar` | `AccountInfo<''info>` | The instruction sysvar for ed25519 verification
CHECK: The instruction sysvar for ed25519 verification | | `token_program` | `Interface<''info, anchor_spl::token_interface::TokenInterface>` | The token program | | `associated_token_program` | `Program<''info, anchor_spl::associated_token::AssociatedToken>` | The associated token program | | `system_program` | `Program<''info, System>` | The system program | ### Struct `ExecuteTransferBumps` ```rust theme={null} pub struct ExecuteTransferBumps { pub relay_depository: u8, pub used_request: u8, } ``` #### Fields | Name | Type | Documentation | | ------------------ | ---- | ------------- | | `relay_depository` | `u8` | | | `used_request` | `u8` | | ### Struct `TransferRequest` Structure representing a transfer request signed by the allocator ```rust theme={null} pub struct TransferRequest { pub recipient: Pubkey, pub token: Option, pub amount: u64, pub nonce: u64, pub expiration: i64, } ``` #### Fields | Name | Type | Documentation | | ------------ | ---------------- | --------------------------------------------------------------- | | `recipient` | `Pubkey` | The recipient of the transfer | | `token` | `Option` | The token mint (None for native SOL, Some(mint) for SPL tokens) | | `amount` | `u64` | The amount to transfer | | `nonce` | `u64` | A unique nonce | | `expiration` | `i64` | The expiration timestamp for the request | #### Implementations ##### Methods * ```rust theme={null} pub fn get_hash(self: &Self) -> Hash { /* ... */ } ``` Computes a hash of the serialized request for signature verification ### Struct `TransferExecutedEvent` Event emitted when a transfer is executed ```rust theme={null} pub struct TransferExecutedEvent { pub request: TransferRequest, pub executor: Pubkey, pub id: Pubkey, } ``` #### Fields | Name | Type | Documentation | | ---------- | ----------------- | --------------------------------------------------------- | | `request` | `TransferRequest` | The transfer request that was executed | | `executor` | `Pubkey` | The public key of the executor who processed the transfer | | `id` | `Pubkey` | The unique identifier for the used request account | ### Struct `DepositEvent` Event emitted when a deposit is made ```rust theme={null} pub struct DepositEvent { pub depositor: Pubkey, pub token: Option, pub amount: u64, pub id: [u8; 32], } ``` #### Fields | Name | Type | Documentation | | ----------- | ---------------- | --------------------------------------------------------------- | | `depositor` | `Pubkey` | The public key of the depositor | | `token` | `Option` | The token mint (None for native SOL, Some(mint) for SPL tokens) | | `amount` | `u64` | The amount deposited | | `id` | `[u8; 32]` | A unique identifier for the deposit | ### Enum `CustomError` **Attributes:** * `#[repr(u32)]` Custom error codes for the relay depository program ```rust theme={null} pub enum CustomError { TransferRequestAlreadyUsed, InvalidMint, Unauthorized, AllocatorSignerMismatch, MessageMismatch, MalformedEd25519Data, MissingSignature, SignatureExpired, InvalidRecipient, InvalidVaultTokenAccount, InsufficientVaultBalance, } ``` #### Variants ##### `TransferRequestAlreadyUsed` Thrown when trying to execute a transfer request that has already been used ##### `InvalidMint` Thrown when the provided mint does not match the expected mint ##### `Unauthorized` Thrown when an account attempts an operation it is not authorized for ##### `AllocatorSignerMismatch` Thrown when the signature's signer doesn't match the expected allocator ##### `MessageMismatch` Thrown when the signed message doesn't match the expected request ##### `MalformedEd25519Data` Thrown when the Ed25519 signature data is malformed ##### `MissingSignature` Thrown when a required signature is missing ##### `SignatureExpired` Thrown when the signature has expired ##### `InvalidRecipient` Thrown when the recipient doesn't match the expected recipient ##### `InvalidVaultTokenAccount` Thrown when the vault token account doesn't match the expected address ##### `InsufficientVaultBalance` Thrown when a transfer would leave the vault with insufficient balance for rent #### Implementations ##### Methods * ```rust theme={null} pub fn name(self: &Self) -> String { /* ... */ } ``` Gets the name of this \[#enum\_name]. ## Functions ### Function `check_id` Confirms that a given pubkey is equivalent to the program ID ```rust theme={null} pub fn check_id(id: &anchor_lang::solana_program::pubkey::Pubkey) -> bool { /* ... */ } ``` ### Function `id` Returns the program ID ```rust theme={null} pub fn id() -> anchor_lang::solana_program::pubkey::Pubkey { /* ... */ } ``` ### Function `id_const` Const version of `ID` ```rust theme={null} pub const fn id_const() -> anchor_lang::solana_program::pubkey::Pubkey { /* ... */ } ``` ### Function `entry` The Anchor codegen exposes a programming model where a user defines a set of methods inside of a `#[program]` module in a way similar to writing RPC request handlers. The macro then generates a bunch of code wrapping these user defined methods into something that can be executed on Solana. These methods fall into one category for now. Global methods - regular methods inside of the `#[program]`. Care must be taken by the codegen to prevent collisions between methods in these different namespaces. For this reason, Anchor uses a variant of sighash to perform method dispatch, rather than something like a simple enum variant discriminator. The execution flow of the generated code can be roughly outlined: * Start program via the entrypoint. * Strip method identifier off the first 8 bytes of the instruction data and invoke the identified method. The method identifier is a variant of sighash. See docs.rs for `anchor_lang` for details. * If the method identifier is an IDL identifier, execute the IDL instructions, which are a special set of hardcoded instructions baked into every Anchor program. Then exit. * Otherwise, the method identifier is for a user defined instruction, i.e., one of the methods in the user defined `#[program]` module. Perform method dispatch, i.e., execute the big match statement mapping method identifier to method handler wrapper. * Run the method handler wrapper. This wraps the code the user actually wrote, deserializing the accounts, constructing the context, invoking the user's code, and finally running the exit routine, which typically persists account changes. The `entry` function here, defines the standard entry to a Solana program, where execution begins. ```rust theme={null} pub fn entry<''info>(program_id: &Pubkey, accounts: &''info [AccountInfo<''info>], data: &[u8]) -> anchor_lang::solana_program::entrypoint::ProgramResult { /* ... */ } ``` ### Function `get_transfer_fee` Calculates the transfer fee for a token Determines the fee amount for the given mint and transfer amount, taking into account the token extension for transfer fees if present. ###### Parameters * `mint_account` - The mint account of the token * `pre_fee_amount` - The amount to transfer before fees ###### Returns * The calculated fee amount ```rust theme={null} pub fn get_transfer_fee(mint_account: &InterfaceAccount<''_, anchor_spl::token_interface::Mint>, pre_fee_amount: u64) -> Result { /* ... */ } ``` ## Constants and Statics ### Static `ID` The static program ID ```rust theme={null} pub static ID: anchor_lang::solana_program::pubkey::Pubkey = _; ``` ### Constant `ID_CONST` Const version of `ID` ```rust theme={null} pub const ID_CONST: anchor_lang::solana_program::pubkey::Pubkey = _; ``` # For Apps Source: https://docs.relay.link/references/protocol/guides/for-apps How application integrators interact with the settlement protocol ## Overview Application developers typically interact with Relay through the [Relay API](/api-reference), which abstracts the settlement protocol entirely. However, understanding the protocol flow can help when debugging transactions or building advanced integrations. This page explains how the protocol surfaces in API responses and how to trace an order through the settlement flow. ## Protocol Deposits via the API When requesting a quote using the Relay API's `/quote/v2` endpoint, the response includes a **`protocol`** field when the order will be settled through the Relay Settlement protocol: ```json theme={null} { "steps": [...], "protocol": { "orderId": "0x1234...abcd", "depositChainId": 10, "paymentDetails": { "recipient": "0xDepositoryAddress...", "amount": "1000000", "token": "0xUSDCAddress..." } } } ``` The key fields: * **`orderId`** — A unique identifier for this order, used to track it through the settlement flow * **`depositChainId`** — The chain where the user's deposit will go * **`paymentDetails`** — The Depository address, token, and amount for the deposit The `protocol` field only appears when the order uses the settlement protocol. Some routes may use alternative execution paths. ## Tracing an Order Once an order is submitted, you can trace it through the settlement flow: ### 1. Deposit Transaction The user's deposit transaction is a standard transfer to the [Depository](/references/protocol/components/depository) contract on the origin chain. You can find this on the origin chain's block explorer. ### 2. Settlement on Relay Chain After the solver fills the order, the [Oracle](/references/protocol/components/oracle) attests the deposit and fill on the [Relay Chain](/references/protocol/components/relay-chain). These attestation transactions are visible on the [Relay Chain Explorer](https://explorer.chain.relay.link). For application debugging, treat the **`orderId`** as the stable correlation key across the whole lifecycle: * Use `paymentDetails.recipient` and `depositChainId` to find the origin-chain deposit transaction * Use your destination-chain execution transaction to confirm the solver fill * Use the Relay Chain attestation submitted after the fill to confirm settlement If you need to trace the Hub's internal **order address** directly, note that it is **not** derived from `orderId` alone. The settlement SDK derives it from: * `chainId` as a string * `depositor` encoded for its VM type * `timestamp` * `depositId` The formula used in the settlement SDK is: ```ts theme={null} const hash = keccak256( encodePacked( ["string", "bytes", "uint256", "bytes32"], [chainId, encodeAddress(depositor, vmType), timestamp, depositId] ) ) const orderAddress = `0x${hash.slice(2).slice(-40)}` ``` In practice, most app integrators should only rely on this if they already have the full deposit parameters. If you only have `orderId`, use it as a correlation identifier rather than assuming it can be converted directly into the Hub order address. ### Example: deriving a Hub order address For the Relay request `0xd5695a549c7e71a3258f9c07a659231d4852513d7da6e30f0c93079cf4569753`, the origin deposit is the Arbitrum transaction `0x693af85007932519865beed409b039ea85a8fe499fa97c537f3bbb264826554c`. The deposit parameters used by the Oracle are: * `chainId = "42161"` * `depositor = "0x710227b881611b30ee794aa63a7da7086888f3c8"` * `timestamp = 1773954184` * `depositId = 0xcc7ec6bbf43d40717e5556fa50b232d866ade6c9a3c654434b1124322fbcf414` The `depositId` above comes from the trailing 32 bytes of the ERC20 transfer calldata into the Depository. For a transaction with a single standard ERC20 transfer into the Depository, this is the same value the Oracle indexes as the deposit id. Using the settlement SDK formula yields the Hub order address: ```ts theme={null} const orderAddress = "0xab73c17f93668cda196db73334e5f1f2ba08257a" ``` You can inspect that balance owner on the Relay Chain Explorer here: * [Relay Chain order address: `0xab73c17f93668cda196db73334e5f1f2ba08257a`](https://explorer.chain.relay.link/address/0xab73c17f93668cda196db73334e5f1f2ba08257a) ### 3. Fill Transaction The solver's fill transaction is on the destination chain. This is the transaction that delivers the user's requested action (transfer, swap, etc.). ## When to Use Direct Protocol Integration Most applications should use the Relay API. Consider direct protocol integration only if you need to: * Build custom deposit flows outside the standard API * Monitor settlement status at the protocol level * Implement custom order management For direct integration details, see the [Depository](/references/protocol/components/depository) component documentation and contract references: * [EVM Depository Reference](/references/protocol/contracts/evm-depository) * [SVM Depository Reference](/references/protocol/contracts/solana-depository) # For Solvers Source: https://docs.relay.link/references/protocol/guides/for-solvers How solvers interact with the settlement protocol ## Overview Solvers (also called relayers) are the liquidity providers that fill crosschain orders in Relay. They front their own capital to execute user intents on destination chains, and earn payment by claiming the user's deposited funds from the [Depository](/references/protocol/components/depository). This page explains the solver lifecycle and how each protocol component fits into the solver's workflow. ## Solver Lifecycle ### 1. Monitor Deposits Solvers watch [Depository](/references/protocol/components/depository) contracts across supported chains for new deposits. Each deposit includes: * **Origin chain and asset** — What the user deposited * **Destination chain and action** — What the user wants executed * **Amount and fees** — The deposit amount and solver compensation * **Order ID** — A unique identifier linking the deposit to the order ### 2. Fill Orders When a solver decides to fill an order, they execute the user's requested action on the destination chain using their own capital. This can be a simple transfer, a swap, or any arbitrary onchain action. Fills can be as gas-efficient as a standard transfer — no routing through protocol contracts is required on the destination. ### 3. Settlement After filling, the solver waits for the [Oracle](/references/protocol/components/oracle) to attest both the user's deposit and the solver's fill. The solver then submits the signed execution to the Oracle contract on the [Relay Chain](/references/protocol/components/relay-chain), which updates the [Hub](/references/protocol/components/hub) and credits the solver's balance. Settlement happens in real-time — there is no batching window. The solver's Hub balance updates as soon as the Oracle processes the attestation. ### 4. Withdrawal Solvers accrue balances on the Hub and can withdraw from any [Depository](/references/protocol/components/depository) on any chain at any time. The withdrawal process: 1. Ask the [Oracle](/references/protocol/components/oracle) to initiate the withdrawal and return the signed Hub execution 2. Execute that Hub transfer to the deterministic withdrawal address on the [Relay Chain](/references/protocol/components/relay-chain) 3. Request the withdrawal payload params from the Oracle 4. Submit the Oracle-signed payload params to `RelayAllocatorSpender`, which verifies the Oracle signature and forwards the request to the [Allocator](/references/protocol/components/allocator) 5. Receive the signed withdrawal proof 6. Submit the signed proof to the Depository on the target chain 7. Receive funds from the Depository ## Managing Balances ### Hub Balance The solver's Hub balance represents the total amount they are owed across all chains. This balance increases with each settled fill and decreases with each withdrawal. Solvers can check their Hub balance by reading the [Hub](/references/protocol/components/hub) contract on the Relay Chain. Each asset from each origin chain has a unique token ID on the Hub. ### Withdrawal Strategy Solvers choose their own withdrawal strategy based on capital needs: * **Frequent withdrawals** — Withdraw every few minutes for maximum capital velocity. Best for solvers with limited capital. * **Batched withdrawals** — Withdraw less frequently to reduce transaction costs. Best for solvers with deep capital pools. * **Strategic rebalancing** — Withdraw on chains where capital is running low, regardless of where the original deposits occurred. The Hub balance accrues in real-time regardless of withdrawal frequency. Solvers can optimize their withdrawal timing independently of settlement. ## Fees and Payment Solver compensation is determined during the quoting phase, before the order is executed. The solver's fee is included in the deposit amount — the user deposits slightly more than the fill amount, and the difference is the solver's payment. Settlement fees on the Relay Chain are paid by the solver but are minimal (\~\$0.005 per order). ## Contract References For contract-level integration details, see: * [EVM Depository Reference](/references/protocol/contracts/evm-depository) * [SVM Depository Reference](/references/protocol/contracts/solana-depository) * [Addresses](/references/protocol/addresses) # Operate a Third-Party Relay Oracle Source: https://docs.relay.link/references/protocol/guides/third-party-oracle How to deploy and run a Relay Oracle outside Relay's internal infrastructure ## Overview The Relay Oracle attests to the onchain facts that drive Relay Protocol settlement. This guide covers how it fits into the protocol and how to deploy and operate an external oracle outside Relay's internal infrastructure. ## What the Relay Oracle is The Relay Oracle is the service that attests to onchain facts used by the Relay Protocol settlement flow. It is: * An operationally stateless HTTP service. * A Dockerized service that exposes an HTTP API. * A signer that produces authenticated messages and execution signatures. * A verifier that reads chain state from RPC endpoints and chain-specific APIs. Its job is to inspect chain state, determine whether a specific protocol-relevant event happened, and sign the corresponding result for downstream settlement flows. ## How the oracle fits into the protocol At a high level, the Relay Protocol needs reliable confirmation that key lifecycle steps actually happened onchain before downstream settlement actions are accepted. The oracle is responsible for attesting these core actions: * Deposits into depository contracts. * Withdrawals from depository contracts. * Solver fills. * Solver refunds. * Withdrawal-address flows used during withdrawal handling. When a client asks the oracle to attest an event: 1. The oracle fetches the relevant chain configuration. 2. It reads chain state from the configured RPC endpoint or chain-specific service. 3. It builds a protocol message that describes what happened. 4. It signs the message with its configured signer. 5. For endpoints that produce execution payloads, it may also collect corroborating signatures from peer oracles. 6. The caller can then use those signatures in the wider settlement flow. The service can be run as a single instance or as multiple cooperating instances. ## What the service actually does The service exposes HTTP endpoints for: * Health checks. * Chain discovery. * Attestation requests. * Swagger-based API documentation. The main utility routes are: * `GET /lives/v1` * `GET /chains/v1` * `GET /documentation` The attestation routes are: * `POST /attestations/depository-deposits/v1` * `POST /attestations/depository-withdrawals/v1` * `POST /attestations/solver-fills/v1` * `POST /attestations/solver-refunds/v1` * `POST /attestations/withdrawal-initiation/v1` * `POST /attestations/withdrawal-initiated/v1` The built-in Swagger UI at `/documentation` is the best reference for exact request and response schemas. ## Supported chains and data sources The oracle supports multiple VM families, with chain-specific attestation logic behind a unified API. The current VM attestor implementation supports: * `ethereum-vm` * `solana-vm` * `bitcoin-vm` * `hyperliquid-vm` * `lighter-vm` * `sui-vm` * `tron-vm` Which chains are enabled is controlled by the selected environment config: * `configs/chains..json` * `configs/chains.hub..json` These config files do not hardcode every endpoint. Instead, many values are placeholders that are resolved from environment variables at runtime. That means your real deployment depends on the quality of the RPC and service endpoints you provide. ## Signing model Each running instance uses one configured signer. The service supports two signing modes: * `raw-private-key` * `aws-kms` The signer is used for: * Message signatures returned by attestation endpoints. * Execution signatures used in settlement flows. If your instance is meant to participate in active production signing, coordinate signer onboarding with Relay before expecting its signatures to be used downstream. If you are just getting started, run the instance first in shadow mode: it signs requests and proves operational readiness, but its signatures are not yet relied on for quorum. ## Peer oracles and quorum A single oracle can answer requests on its own, and the code also supports peering across multiple oracle instances. For execution-producing endpoints, an oracle can fan out the same request to configured peer oracles and aggregate their signatures when: * The incoming request sets `requestPeerSignatures=true`. * Peer URLs are configured in `PEERS`. * A peer agrees with the local oracle's assessment for that request. Important operational behavior: * Peer calls are best-effort. * If a peer times out, returns an error, or does not agree with the local result, that peer is skipped. * One bad peer does not fail the whole request path. This allows a new oracle to be introduced in shadow mode without turning peer availability into a hard dependency for each request. ## Security and trust model If you operate a third-party oracle, you are expected to treat it as production infrastructure. Minimum security expectations: * Use a dedicated signing key that is not shared with any other oracle. * Prefer a hardened key-management approach. `aws-kms` is supported; if you use raw keys, store them in a real secret manager. * Require API keys on your public endpoints. * Use HTTPS for any publicly reachable deployment. * Use dedicated peer API keys when exchanging traffic with other oracle operators. * Restrict management access to the host platform and secret storage. The oracle is stateless, but its signer is security-critical. If the signing key is compromised, the instance should be treated as compromised immediately. ## Infrastructure requirements You should run the oracle as a Dockerized service on a platform that can run a Docker container and expose an HTTP service. For most third-party operators, Railway should be treated as the preferred starting point. It is the recommended default when you want the simplest path to running a single external oracle instance. Typical Docker-hosting options: * Railway * ECS * Kubernetes * Fly.io * A VM running Docker You need: * A long-running Docker runtime. * Stable outbound access to all configured RPC and chain service endpoints. * Stable inbound access from callers and peer oracles. * Secure secret storage for signing material and API keys. Because the service is stateless, scaling horizontally is possible. In practice, the biggest operational constraints are uptime, RPC quality, and signer security. If you already operate production container infrastructure, another platform can be a valid choice. If not, default to Railway. ## Configuration model The runtime configuration is mostly environment-variable driven. For a recommended raw-key mainnet deployment, define: ```sh theme={null} ENVIRONMENT=mainnets.prod ECDSA_PRIVATE_KEY=0x... API_KEYS=your-api-key:your-name ``` `SIGNING_MODULE=raw-private-key` is optional because raw-key signing is the default code path, but setting it explicitly is clearer for operators. You can also set: ```sh theme={null} HTTP_PORT=3000 PEERS=https://peer-one.example.com|peer-api-key;https://peer-two.example.com|peer-api-key PEER_REQUEST_TIMEOUT_MS=5000 ``` Notes: * `HTTP_PORT` is optional if your platform injects `PORT`. The server resolves ports in this order: `HTTP_PORT`, then `PORT`, then `3000`. * `API_KEYS` is optional in code, but strongly recommended for any deployment that is reachable over a network. * `PEER_REQUEST_TIMEOUT_MS` defaults to `5000`. If you use AWS KMS instead of a raw key: ```sh theme={null} SIGNING_MODULE=aws-kms AWS_KMS_SIGNER_KEY_ID=... AWS_KMS_SIGNER_KEY_REGION=... ``` ## Chain RPC and additional service variables The chain config files reference environment variables dynamically. Your deployment is only as reliable as the endpoints and credentials you provide there. There are two categories to think about: * Standard RPC URLs for the chains the oracle reads. * Supplemental service variables for chains that need more than a plain RPC endpoint. ### Core RPC variables For a mainnet deployment, the most important RPC variables to populate first are: ```sh theme={null} ETHEREUM_RPC_URL= BASE_RPC_URL= ARBITRUM_RPC_URL= BNB_RPC_URL= POLYGON_RPC_URL= SOLANA_RPC_URL= BITCOIN_RPC_URL= HYPERLIQUID_RPC_URL= ``` Why these matter: * The oracle validates chain state in real time, so weak or rate-limited RPCs directly reduce reliability. * If a configured chain does not have a working RPC endpoint, any attestation that depends on that chain will fail. You may also need additional chain RPC variables beyond the list above, depending on which chains remain enabled in your selected `ENVIRONMENT`. The chain config files are the source of truth for the full required set. ### Supplemental non-RPC variables Some chains and integrations need service-specific URLs or credentials in addition to a raw RPC endpoint. Common examples are: ```sh theme={null} HYPERLIQUID_HUB_API_URL= HYPERLIQUID_HUB_API_KEY= HYPERLIQUID_PROXY_API_URL= HYPERLIQUID_PROXY_API_KEY= BITCOIN_ESPLORA_COMPATIBLE_API_URL= BITCOIN_BLOCKSTREAM_CLIENT_ID= BITCOIN_BLOCKSTREAM_CLIENT_SECRET= BITCOIN_MAESTRO_API_KEY= LIGHTER_RPC_API_KEY= ``` Why these matter: * Some ecosystems expose critical settlement-related data more reliably through companion APIs than through a single generic RPC method. * Some providers require API credentials even when the underlying access pattern still looks like a chain read. * These variables improve reliability and coverage for chain-specific reads without changing the public API of the oracle itself. You do not need to expose internal provider choices publicly. You only need to ensure the variables required by your chosen providers are present, valid, and managed like any other production secret. ### Practical guidance * Public RPC endpoints may work for lower-volume or non-critical traffic, but are typically less predictable. * Hyperliquid and Bitcoin commonly need supplemental services, not just a single RPC URL. * When adding or removing supported chains, recheck the required environment variables before deploying. ## Authentication When `API_KEYS` is set, the service requires a valid `x-api-key` header for all protected routes. These routes remain accessible without an API key: * `/documentation` * `/lives/v1` `/chains/v1` and all attestation endpoints should be treated as protected endpoints in a real deployment. `API_KEYS` uses this format: ```sh theme={null} API_KEYS=key-one:partner-a;key-two:partner-b ``` The right-hand label is stored as metadata for that key. ## Peering with other oracle operators Use `PEERS` to define other oracle instances this oracle should call when peer signatures are requested. Format: ```sh theme={null} PEERS=https://peer-a.example.com|peer-a-key;https://peer-b.example.com|peer-b-key ``` Each entry is: * A base URL. * A per-peer API key, separated by `|`. `pass-through` is also supported as a peer credential, which forwards the incoming `x-api-key` instead of a dedicated peer key. That is generally better suited to tightly controlled internal environments. For separate organizations, use dedicated peer keys. If Relay-managed oracles need to call your instance, the usual pattern is: 1. You expose a stable public HTTPS URL. 2. You create a dedicated incoming API key for peer traffic. 3. Relay adds your URL and that key to the `PEERS` list used by its oracle instances. ## Recommended deployment sequence The safest way to bring up a new third-party oracle is: 1. Create a new signer dedicated to this oracle. 2. Provision RPC and chain-service endpoints. 3. Deploy the container on Railway first unless you already have an established alternative platform. 4. Verify health and chain loading. 5. Protect the service with API keys. 6. Expose the service over HTTPS. 7. Configure peering and receive live requests in shadow mode. 8. Run long enough to observe stability, latency, and RPC usage. 9. Only then consider onboarding the signer for active participation. This order lets you validate the infrastructure and chain dependencies before your signer becomes operationally important. ## Running the service Build and run the service as a Docker container: ```sh theme={null} docker build -t relay-protocol-oracle . docker run --rm -p 3000:3000 --env-file .env relay-protocol-oracle ``` The container entrypoint also loads secrets from `/vault/secrets` when present, which is useful for secret-injection systems. If your platform builds from source automatically, the important requirement is still the same: the service should be deployed and run as the repository's Docker image, with the required environment variables injected at runtime. ## First-run verification checklist After deployment, verify: * `GET /lives/v1` returns `{"status":"ok"}`. * `GET /chains/v1` returns the chains expected for your selected `ENVIRONMENT` when called with a valid `x-api-key` if `API_KEYS` is configured. * `GET /documentation` loads the Swagger UI. * Your logs show the service started successfully. * Your logs show the signer address you expect. Then test at least one real attestation flow, using a valid `x-api-key` if `API_KEYS` is configured. ## Operational monitoring At minimum, monitor: * Container restarts. * Request latency. * Error rate by endpoint. * Peer timeout and peer mismatch warnings. * RPC provider failures and rate limits. * Memory and CPU consumption. * Signer address correctness after deploys. A healthy process can still be operationally unusable if its RPC dependencies are degraded, so RPC observability matters as much as container health. # How It Works Source: https://docs.relay.link/references/protocol/how-it-works End-to-end walkthrough of the settlement protocol flows Relay is a crosschain intents protocol. Users express what they want (e.g., bridge 1 ETH from Optimism to Base), and third-party solvers fill those orders using their own capital. The protocol then settles solvers — verifying fills and reimbursing them from the user's escrowed deposit. What makes Relay different is how settlement works. It's designed from the ground up for maximum efficiency & compatibility: * A dedicated low-cost settlement chain ([Relay Chain](/references/protocol/components/relay-chain)) * A custom [Oracle](/references/protocol/components/oracle) that can *read* data from any chain / VM * Programmable MPC signatures to *write* back to any chain / VM ## Contracts The protocol is coordinated through 3 main smart contracts: | Contract | Role | Location | | ------------------------------------------------------------ | ------------------------------------------ | --------------------------- | | [**Depository**](/references/protocol/components/depository) | Holds user deposits on each origin chain | Every supported chain (80+) | | [**Hub**](/references/protocol/components/hub) | Tracks token ownership and solver balances | Relay Chain | | [**Allocator**](/references/protocol/components/allocator) | Generates MPC withdrawal payloads | Aurora (NEAR) | ## Flows Every crosschain order in Relay passes through three sequential flows: **Execution**, **Settlement**, and **Withdrawal**. ```mermaid theme={null} sequenceDiagram participant User participant Solver participant O as Origin Chain participant D as Destination Chain participant R as Relay Chain rect rgb(240, 240, 255) Note over User,D: Execution User->>O: 1. Deposit into Depository Solver->>D: 2. Fill order end rect rgb(240, 255, 240) Note over Solver,R: Settlement Solver->>R: 3. Submit attestation to Oracle contract end rect rgb(255, 240, 240) Note over Solver,R: Withdrawal Solver->>R: 4. Move balance to withdrawal address Solver->>O: 5. Submit proof, claim funds end ``` ### 1. Execution Flow Execution is the user-facing part of the process. The user deposits funds on the origin chain, and a solver fills their order on the destination chain. ```mermaid theme={null} sequenceDiagram participant User participant Depository as Depository (Origin) participant Solver participant Dest as Destination Chain User->>+Depository: 1. Deposit funds (orderId) Note over Depository: Native deposits can be ~21,000 gas Depository-->>-User: Deposit confirmed Solver->>Depository: 2. Detect deposit Solver->>+Dest: 3. Fill order (own capital) Dest-->>-User: Action executed ``` **Step by step:** 1. **User requests a quote** — The user specifies what they want (e.g., bridge 1 ETH from Optimism to Base). The Relay API returns quotes from available solvers. 2. **User deposits into Depository** — The user sends funds to the [Depository](/references/protocol/components/depository) contract on the origin chain. The deposit is tagged with an **orderId** that links it to the solver's commitment. This costs approximately 21,000 gas — close to a simple transfer. 3. **Solver fills on destination** — The solver detects the deposit and executes the user's requested action on the destination chain using their own capital. The fill can be a simple transfer, a swap, or any arbitrary onchain action. Because deposits go to the Depository (not to the solver directly), user funds are protected. If the solver fails to fill, the user can reclaim their deposit. ### 2. Settlement Flow Settlement is the process of verifying that the solver correctly filled the order, and crediting them on the Hub. ```mermaid theme={null} sequenceDiagram participant Origin as Origin Chain participant Solver participant Oracle participant Dest as Destination Chain participant OC as Oracle Contract (Relay Chain) participant Hub as Hub (Relay Chain) Solver->>Oracle: 1. Request attestation Oracle->>Origin: 2. Read deposit data Oracle->>Dest: 3. Read fill data Note over Oracle: Verify deposit + fill match Oracle-->>Solver: 4. Return signed attestation Solver->>OC: 5. Submit attestation OC->>Hub: 6. Execute MINT + TRANSFER Note over Hub: Solver balance updated ``` **Step by step:** 1. **Solver requests attestation** — After filling an order, the solver calls the [Oracle](/references/protocol/components/oracle) to request settlement. 2. **Oracle reads origin chain** — The Oracle reads the origin chain to verify that the user's deposit occurred and matches the expected order. 3. **Oracle reads destination chain** — The Oracle reads the destination chain to verify that the solver's fill matches the user's intent (correct destination, amount, and action). 4. **Oracle returns attestation** — Oracle validators each verify the data and sign an EIP-712 attestation. Once a threshold of signatures is collected, the signed attestation is returned to the solver. 5. **Solver submits to Oracle contract** — The solver submits the signed attestation to the Oracle contract on the [Relay Chain](/references/protocol/components/relay-chain), which verifies the authorized signer and executes on the [Hub](/references/protocol/components/hub). This triggers two actions: * **MINT** — The user's deposit is represented as a token balance on the Hub * **TRANSFER** — That balance is transferred from the user to the solver Settlement happens in real-time, per-order. There is no batching window. As soon as the Oracle verifies a fill, the solver's balance is updated on the Hub. ### 3. Withdrawal Flow Withdrawal is how solvers extract funds from the Depository to replenish their capital. Solvers accumulate balances on the Hub and can withdraw from any origin chain at any time. ```mermaid theme={null} sequenceDiagram participant Solver participant Oracle participant Hub as Hub (Relay Chain) participant Spender as AllocatorSpender (Aurora) participant Allocator as Allocator (Aurora) participant Depository as Depository (Origin) Solver->>Oracle: 1. Request withdrawal initiation Oracle-->>Solver: 2. Return signed Hub execution Solver->>Hub: 3. Move balance to withdrawal address Solver->>Oracle: 4. Request payload params Oracle-->>Solver: 5. Return signed payload params + Oracle signature Solver->>Spender: 6. Request proof Spender->>Allocator: 7. Verify + forward request Allocator-->>Solver: 8. Return MPC-signed proof Solver->>+Depository: 9. Submit proof Depository->>Depository: Verify signature + nonce Depository-->>-Solver: 10. Release funds ``` **Step by step:** 1. **Solver requests withdrawal initiation** — The solver asks the [Oracle](/references/protocol/components/oracle) to initiate a withdrawal, providing the target chain, depository, currency, amount, recipient, and nonce. 2. **Oracle returns Hub execution** — The Oracle computes the deterministic **withdrawal address** for those parameters and returns a signed Hub execution that moves the requested balance to that address. 3. **Solver moves balance on Hub** — The solver submits that execution on the [Relay Chain](/references/protocol/components/relay-chain), so the withdrawal address now holds the requested Hub balance. 4. **Solver requests payload params** — The solver calls the [Oracle](/references/protocol/components/oracle) again to request the signed payload parameters for the withdrawal. 5. **Oracle returns payload params** — The Oracle verifies that the withdrawal address holds the requested balance and returns the payload parameters together with an Oracle signature for the next step. 6. **Solver submits to `RelayAllocatorSpender`** — The solver submits those payload parameters and the Oracle signature to `RelayAllocatorSpender`, which verifies the Oracle authorization and forwards the request to the [Allocator](/references/protocol/components/allocator). 7. **Allocator builds and signs** — The [Allocator](/references/protocol/components/allocator) constructs the chain-specific payload and generates an MPC-signed cryptographic proof (EIP-712 for EVM, Ed25519 for Solana). 8. **Proof is returned to solver** — The signed proof is returned to the solver. 9. **Solver submits proof** — The solver submits the signed proof to the [Depository](/references/protocol/components/depository) contract on the target chain. 10. **Depository releases funds** — The Depository verifies the signature, confirms the nonce hasn't been used, and transfers the funds to the solver. Solvers choose their own withdrawal strategy. They can withdraw frequently to maximize capital velocity, or batch withdrawals to minimize transaction costs. The Hub balance accrues in real-time regardless. ## Refunds Inevitably, some orders cannot be filled, and the user needs to be refunded. Relay is designed to make this experience as smooth as possible, with multiple supported pathways, depending on the circumstances: ### Fast Refund If a solver can't fill an order on the destination, they can instantly refund the user on the origin chain. The refund mirrors the execution flow — the solver detects the deposit and acts — but instead of filling on the destination, they return funds to the user on the origin. The refund is then settled like any other fill. ```mermaid theme={null} sequenceDiagram participant User participant Depository as Depository (Origin) participant Solver participant Dest as Destination Chain participant Oracle participant OC as Oracle Contract (Relay Chain) participant Hub as Hub (Relay Chain) User->>+Depository: 1. Deposit funds (orderId) Depository-->>-User: Deposit confirmed Solver->>Depository: 2. Detect deposit Solver->>Dest: 3. Simulate fill Note over Solver,Dest: Fill not possible Solver->>+Depository: 4. Refund user on origin Depository-->>-User: Funds returned Solver->>Oracle: 5. Request refund attestation Oracle->>Depository: 6. Read refund data Oracle-->>Solver: 7. Return signed attestation Solver->>OC: 8. Submit attestation OC->>Hub: 9. Execute settlement ``` **Step by step:** 1. **User deposits into Depository** — The user deposits funds into the [Depository](/references/protocol/components/depository) on the origin chain, tagged with an **orderId**. 2. **Solver detects deposit** — The solver detects the deposit and evaluates whether it can fill the order. 3. **Solver simulates fill** — The solver simulates the fill on the destination chain and determines it can't be completed (e.g., insufficient liquidity, contract error). 4. **Solver refunds on origin** — Instead of filling, the solver sends funds directly to the user on the origin chain, returning them immediately. 5. **Solver requests attestation** — The solver calls the [Oracle](/references/protocol/components/oracle) to request a refund attestation. 6. **Oracle reads origin chain** — The Oracle reads the origin chain to verify the refund occurred and matched the order's requirements. 7. **Oracle returns attestation** — Validators sign and return the attestation to the solver. 8. **Solver submits to Oracle contract** — The solver submits the attestation to the Oracle contract, which executes the settlement on the [Hub](/references/protocol/components/hub) — the same settlement process as a fill. Fast refunds are the quickest way to return user funds. Because the solver acts immediately on the origin chain, the user doesn't need to wait for any expiry window. Additional refund and recovery UX may exist at the product layer, but only the solver-driven refund attestation flow above is directly evidenced by the protocol code in `relay-settlement` and `relay-protocol-oracle`. # Relay Settlement Source: https://docs.relay.link/references/protocol/overview A crosschain settlement protocol optimized for speed, cost, and reliability Relay Settlement is a hyper-optimized intents protocol, designed for high-performance solvers that want to offer non-custodial crosschain relaying with the minimum possible overhead. It's optimized on 5 key dimensions: * **Speed** — Sub-second fills with optimistic execution * **Cost** — Minimal overhead on top of raw transfers * **Capital Efficiency** — Flexible batching for maximum inventory re-use * **Coverage** — Works on any chain, with minimal effort to deploy to new chains and VMs * **UX** — Simple transfer flows + fast refunds Unlike other intent protocols, Relay Settlement does not attempt to be a solver marketplace, or offer any sort of orderflow auction. It's a pure utility, designed to be used by solvers that already have their own orderflow and just need efficient settlement. ## How It Works Relay Settlement works similarly to other crosschain intent protocols: 1. User locks funds on the origin chain 2. Solver fills the intent on the destination chain 3. Solver proves fulfillment, to unlock payment The main difference is that instead of settlement happening point-to-point between the origin and destination, it all happens on the [Relay Chain](/references/protocol/components/relay-chain). Solvers cheaply settle thousands of orders in real-time, accrue a balance, and then claim in a batch with one efficient proof when they need access to funds. Settlement Flow ## Speed Relay is designed for **sub-second fill times** in production. Several design choices enable this: * **Optimistic execution** — Solvers fill orders before origin chain finality, taking on minimal confirmation risk for recent blocks * **No auction delay** — There is no orderflow auction or solver competition window. Solvers receive orders and fill immediately. * **Instant settlement** — The solver's Hub balance updates as soon as the Oracle attests the fill, giving solvers confidence to fill aggressively without waiting for batch cycles The result is a user experience where crosschain transfers feel as fast as same-chain transfers. ## Cost Crosschain intent systems can consume significant gas: passing order objects in calldata, verifying correct fulfillment, tracking intent status, emitting events. These might seem insignificant in an age of abundant blockspace, but add up in aggregate. Any gas paid is a wholesale cost that comes out before the solver's margin. Relay keeps gas consumption to an absolute minimum, as close to raw transfers as possible: * **Low-overhead deposits** — Native-token deposits can be close to a raw transfer cost (\~21,000 gas), while ERC20 deposits still avoid the heavier escrow patterns common in other protocols * **Zero-overhead fills** — Solvers can fill with a simple transfer, no contract interaction needed on destination * **\~\$0.005 settlement** — Orders settle on a dedicated low-cost chain, not the origin This enables lower costs for users, and higher margins for solvers. ## Capital Efficiency A core concern for solvers is getting paid as quickly as possible, in order to re-use capital for future orderflow. Claiming payment for every intent on the origin can be expensive, so most high-volume solvers want some form of batching. But not all batching is the same — some protocols globally batch all intents on an hourly interval, resulting in significant capital lockup. Relay Settlement allows solvers to withdraw whenever they want, choosing their optimal point on the trade-off spectrum between cost and capital efficiency. Settle thousands of orders in real-time on the Hub, and batch withdrawals on your own schedule. ## Coverage Relay can work on any chain, and deployment to new chains and VMs is designed to be as simple as possible — so it can be ready day 1. * Heavy logic is shifted to the Relay Chain, so each new VM is a small module * Pull-based verification allows oracles to watch more chains with fewer resources * No dependencies on specific EIPs, precompiles, or advanced RPC methods This has allowed Relay to expand to 80+ chains. More often than not, it's available weeks before a chain publicly launches. ## UX Too many protocols prioritize protocol simplicity over end-user simplicity. Relay's philosophy is different — if there's any opportunity to reduce user friction, it takes it, even at the expense of protocol complexity. **Deposit UX** Users don't want to make two transactions to approve and deposit. Gasless flows can be great, but shouldn't be forced on users with gas. Relay allows depositing via a simple transfer, for both native and ERC20 tokens — a UX that can compete with centralized exchanges. **Failure UX** For most intent protocols, failures are an afterthought. If something goes wrong, the user is forced to manually collect their funds out of escrow, often after a delay. Relay allows solvers to instantly refund once they determine an order can't be filled, abstracting complexity and giving users a UX that feels similar to using a single chain. # Security & Audits Source: https://docs.relay.link/references/protocol/security Trust model, security architecture, and audit history ## Trust Model Relay Settlement is designed to be **non-custodial** — no single entity can unilaterally access user funds. Each component has different trust properties: ### Depository The Depository contracts are the most trust-minimized component: * **Non-upgradable** — Contract logic is immutable after deployment * **Single authorization path** — Only the registered Allocator can authorize withdrawals * **Short custody duration** — Funds are typically held for seconds to minutes, not hours or days * **No admin withdrawal** — There is no backdoor function for the contract owner to withdraw user funds The Depository's security relies on the Allocator's signing authority. If the Allocator is compromised, it could authorize unauthorized withdrawals — but it cannot create balances that don't exist on the Hub. ### Oracle The [Oracle](/references/protocol/components/oracle) determines which deposits and fills are considered valid: * **Can attribute balances** — The Oracle controls what gets minted, transferred, or burned on the Hub * **Cannot steal Depository funds** — Even an incorrect attestation only affects Hub balances. The Allocator independently verifies Hub balances before authorizing any withdrawal. * **Authorized signer model** — Attestations must be signed by an authorized oracle signer. If a signer contract such as `RelayOracleMultisig` is used, any thresholding is enforced there rather than in the `RelayOracle` contract itself. The Oracle is the main trust-bearing component. A compromised unauthorized signer cannot submit false attestations. A compromised authorized oracle signer, or a compromised threshold behind an oracle signer contract, could incorrectly attribute balances, but the damage is bounded by the Allocator's balance checks and the [Security Council's](/references/protocol/components/security-council) ability to halt all withdrawal proof generation. ### Hub The Hub is a deterministic smart contract on the [Relay Chain](/references/protocol/components/relay-chain): * **Rule-based** — Balance changes only occur through Oracle-attested actions (MINT, TRANSFER, BURN) * **Idempotent** — The same attestation cannot be processed twice * **Transparent** — All balance changes are visible on the Relay Chain block explorer ### Allocator The [Allocator](/references/protocol/components/allocator) controls withdrawal authorization: * **[MPC signatures](/references/protocol/components/allocator#mpc-signing)** — No single entity holds the full signing key * **Balance-bounded** — Can only authorize withdrawals up to a solver's Hub balance * **[Oracle-gated](/references/protocol/components/allocator#oracle-gated-access)** — Withdrawal proofs require a valid Oracle signature, verified by the `RelayAllocatorSpender` before the Allocator processes the request * **Governed by [Security Council](/references/protocol/components/security-council)** — A multisig where any single member can suspend an approved withdrawer, and a supermajority can replace the Allocator * **Replay-protected** — Nonces and expirations prevent proof reuse ## Security Council The [Security Council](/references/protocol/components/security-council) is a multisig that governs the Allocator. Any single member can immediately suspend an approved withdrawer such as `RelayAllocatorSpender`. In the current deployment, that halts new withdrawal proof generation because withdrawals are routed through that contract. Structural changes (replacing the Allocator, changing membership) require a supermajority. See the full [Security Council](/references/protocol/components/security-council) page for details on tiered thresholds and scope. ## Audits The protocol has been audited by leading security firms: | Date | Scope | Auditor | Report | | ------------- | ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | February 2025 | Relay Depository (EVM) | [Spearbit](https://spearbit.com/) | [View Report](https://github.com/relayprotocol/relay-depository/blob/main/packages/ethereum-vm/audit/report.pdf) | | June 2025 | Relay Depository (EVM) | [Certora](https://www.certora.com/) | [View Report](/references/protocol/depository/relay-escrow-report-certora.pdf) | | November 2025 | Settlement Protocol | [Zellic](https://www.zellic.io/) | [View Report](https://github.com/relayprotocol/settlement-protocol/blob/main/smart-contracts/audit/report.pdf) | ## Bug Bounty Details of the Relay bug bounty program can be found on the [Bug Bounties](/security/bounties) page. ## Source Code All protocol contracts are open source: * [`settlement-protocol`](https://github.com/relayprotocol/settlement-protocol) — Depository, Deposit Addresses, Hub, Oracle, Allocator contracts * [`relay-protocol-oracle`](https://github.com/relayprotocol/relay-protocol-oracle) — Relay Protocol Oracle application # Architecture Source: https://docs.relay.link/references/protocol/vaults/architecture Architecture of Relay Vaults A Relay Vault is a smart contract ([RelayPool.sol](./contracts/RelayPool)) that implements the [ERC4626](https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/) standard. It is deployed on specific EVM network, for a specific currency, represented by an ERC20. It is possible to use wrapped tokens (such as WETH) to handle native currencies (we provide a [Native Gateway](./contracts/RelayPoolNativeGateway) for this purpose). Each vault has a base yield pool, in which funds are instantly deposited and withdrawn. This pool is typically a lending protocol such as Aave or Morpho, but it can be any contract that implements the ERC4626 standard. The base yield pool is used to generate yield for the vault. Each vault also has **origins**. Origins are contracts deployed on different chains. Origins can be treated as "bridging" contracts. When a user sends funds to a bridge, they can receive the same amount (minus fees) of funds from the vault on the destination chain. In order to achieve this, the RelayBridge contracts uses [Hyperlane](https://hyperlane.xyz/) to send a message to the vault. As soon as the message reaches the vault, the funds are pulled from the yield pool and sent to the recipient. At the same time, the vault initiates the transfer of funds using an underlying bridge (e.g. native bridges, ... ). > The Relay Vaults can be considered to be an "accelerator" where funds are > "dispersed" without requiring to wait for the full resolution of the bridge, > in exchange for a small fee. In order to abstract the underlying bridges, we rely on the concept of [`BridgeProxy`](./contracts/RelayBridge). A BridgeProxy contract is deployed on both the origin chain and the vault's chain and implements the specificities of the underlying bridge. Each origin has its own **fee** which represents the "opportunity cost" of the funds being bridged: shorter bridges should have lower fees than longer ones. Finally, the vault contract is designed to resist "inflation" attacks. Any yield generated (thru fees, or extra rewards) is not accrued instantly by the vault, but streamed over a specific period of time, rendering any attack where funds deposited and withdrawn in a short period of time ineffective. # Relay Vaults Backend Source: https://docs.relay.link/references/protocol/vaults/backend The Relay Vaults backend service is a GraphQL API that allows developers to query vault data. Relay Vaults Backend is a GraphQL API that allows developers to query vault data. It provides a simple and efficient way to access information about Relay Vaults, including their balances, yields, and other relevant metrics. The Backend API is accessible at [https://vaults-api.relay.link/](https://vaults-api.relay.link/). It is designed to be easy to use and provides a wide range of queries to retrieve information about the vaults. # RelayBridge Source: https://docs.relay.link/references/protocol/vaults/contracts/RelayBridge # Solidity API ## IRelayBridge Interface for the RelayBridge contract *Defines the bridge function for cross-chain asset transfers* ### bridge ```solidity theme={null} function bridge(uint256 amount, address recipient, address poolAsset, uint256 poolGas, bytes extraData) external payable returns (uint256 nonce) ``` Bridges an asset (ERC20 token or native currency) from the origin chain to a pool chain #### Parameters | Name | Type | Description | | --------- | ------- | ----------------------------------------------------------------- | | amount | uint256 | Amount of asset units to bridge (ERC20 tokens or native currency) | | recipient | address | Address on the pool chain to receive the bridged asset | | poolAsset | address | Address of the asset on the pool chain to bridge to | | poolGas | uint256 | Gas limit for the pool chain transaction | | extraData | bytes | Extra data passed to the bridge proxy | #### Return Values | Name | Type | Description | | ----- | ------- | --------------------------------------------- | | nonce | uint256 | Unique identifier for this bridge transaction | ## RelayBridge RelayBridge contract enabling cross-chain bridging via Hyperlane *Bridges assets from an origin chain to a configured pool chain* ### BridgeTransaction *Internal struct capturing parameters for a bridge transaction* ```solidity theme={null} struct BridgeTransaction { uint256 amount; address recipient; address poolAsset; uint256 poolGas; bytes extraData; uint256 nonce; bytes txParams; uint32 poolChainId; bytes32 poolId; } ``` ### BridgingFailed ```solidity theme={null} error BridgingFailed(uint256 nonce) ``` Error for failed bridging operations #### Parameters | Name | Type | Description | | ----- | ------- | ----------------------------------- | | nonce | uint256 | The nonce of the failed transaction | ### InsufficientValue ```solidity theme={null} error InsufficientValue(uint256 received, uint256 expected) ``` Error for insufficient native value sent to cover fees or asset #### Parameters | Name | Type | Description | | -------- | ------- | -------------------------------------- | | received | uint256 | The amount of native currency received | | expected | uint256 | The amount of native currency expected | ### FailedFeeRefund ```solidity theme={null} error FailedFeeRefund(uint256 value) ``` Error when refunding leftover native value fails #### Parameters | Name | Type | Description | | ----- | ------- | --------------------------------------------------- | | value | uint256 | The amount of native currency that failed to refund | ### transferNonce ```solidity theme={null} uint256 transferNonce ``` Counter for assigning unique nonces to bridge transactions *Incremented with each bridge transaction to ensure uniqueness* ### ASSET ```solidity theme={null} address ASSET ``` Asset address on the origin chain being bridged (address(0) for native currency) *Immutable to ensure the bridge always handles the same asset* ### BRIDGE\_PROXY ```solidity theme={null} contract BridgeProxy BRIDGE_PROXY ``` BridgeProxy contract handling origin-chain transfer logic *Uses delegatecall to execute custom bridge logic* ### HYPERLANE\_MAILBOX ```solidity theme={null} address HYPERLANE_MAILBOX ``` Hyperlane Mailbox contract for cross-chain messaging *Used to dispatch messages to the pool chain* ### BridgeInitiated ```solidity theme={null} event BridgeInitiated(uint256 nonce, address sender, address recipient, address ASSET, address poolAsset, uint256 amount, contract BridgeProxy BRIDGE_PROXY) ``` Emitted when a bridge transaction is initiated on the origin chain #### Parameters | Name | Type | Description | | ------------- | -------------------- | ------------------------------------------------------------------ | | nonce | uint256 | Nonce of the transaction | | sender | address | Address on the origin chain that initiated the bridge | | recipient | address | Address on the pool chain to receive assets | | ASSET | address | Asset address on the origin chain (address(0) for native currency) | | poolAsset | address | Asset address on the pool chain (address(0) for native currency) | | amount | uint256 | Amount of asset units bridged | | BRIDGE\_PROXY | contract BridgeProxy | BridgeProxy used | ### BridgeExecuted ```solidity theme={null} event BridgeExecuted(uint256 nonce) ``` Emitted after a bridge transaction is executed on the pool chain *This event would be emitted by the pool chain contract* #### Parameters | Name | Type | Description | | ----- | ------- | --------------------------------- | | nonce | uint256 | Nonce of the executed transaction | ### BridgeCancelled ```solidity theme={null} event BridgeCancelled(uint256 nonce) ``` Emitted if a bridge transaction is cancelled prior to execution *This event would be emitted if a bridge is cancelled* #### Parameters | Name | Type | Description | | ----- | ------- | ---------------------------------- | | nonce | uint256 | Nonce of the cancelled transaction | ### constructor ```solidity theme={null} constructor(address asset, contract BridgeProxy bridgeProxy, address hyperlaneMailbox) public ``` Initializes the RelayBridge with asset and infrastructure contracts *All parameters are immutable after deployment* #### Parameters | Name | Type | Description | | ---------------- | -------------------- | ------------------------------------------------------------------- | | asset | address | Asset address on the origin chain to bridge (0 for native currency) | | bridgeProxy | contract BridgeProxy | BridgeProxy contract for origin-chain transfers | | hyperlaneMailbox | address | Hyperlane Mailbox address for messaging | ### getFee ```solidity theme={null} function getFee(uint256 amount, address recipient, uint256 poolGas) external view returns (uint256 fee) ``` Calculates the fee required in native currency to dispatch a cross-chain message *Uses Hyperlane's quoteDispatch to estimate the cross-chain messaging fee* #### Parameters | Name | Type | Description | | --------- | ------- | ------------------------------------------------------------- | | amount | uint256 | Amount of asset units to bridge | | recipient | address | Address on the pool chain that will receive the bridged asset | | poolGas | uint256 | Gas limit for the pool chain transaction | #### Return Values | Name | Type | Description | | ---- | ------- | -------------------------------------------- | | fee | uint256 | Required fee in native currency for dispatch | ### bridge ```solidity theme={null} function bridge(uint256 amount, address recipient, address poolAsset, uint256 poolGas, bytes extraData) external payable returns (uint256 nonce) ``` Bridges an asset (ERC20 token or native currency) from the origin chain to a pool chain \_Executes a cross-chain bridge transaction with the following steps: 1. Validates sufficient payment for fees (and asset amount if native) 2. Transfers the asset from sender to this contract (if ERC20) 3. Executes bridge logic via delegatecall to BRIDGE\_PROXY 4. Dispatches cross-chain message via Hyperlane 5. Refunds any excess native currency to sender\_ #### Parameters | Name | Type | Description | | --------- | ------- | ----------------------------------------------------------------- | | amount | uint256 | Amount of asset units to bridge (ERC20 tokens or native currency) | | recipient | address | Address on the pool chain to receive the bridged asset | | poolAsset | address | Address of the asset on the pool chain to bridge to | | poolGas | uint256 | Gas limit for the pool chain transaction | | extraData | bytes | Extra data passed to the bridge proxy | #### Return Values | Name | Type | Description | | ----- | ------- | --------------------------------------------- | | nonce | uint256 | Unique identifier for this bridge transaction | # RelayBridgeFactory Source: https://docs.relay.link/references/protocol/vaults/contracts/RelayBridgeFactory # Solidity API ## RelayBridgeFactory Factory contract for deploying RelayBridge instances for different assets *This factory deploys new RelayBridge contracts and maintains a registry of bridges by asset address* ### HYPERLANE\_MAILBOX ```solidity theme={null} address HYPERLANE_MAILBOX ``` The Hyperlane mailbox address used for cross-chain messaging *Immutable to ensure consistency across all deployed bridges* ### bridgesByAsset ```solidity theme={null} mapping(address => address[]) bridgesByAsset ``` Mapping from asset address to array of deployed bridge addresses *Multiple bridges can exist for the same asset* ### BridgeDeployed ```solidity theme={null} event BridgeDeployed(address bridge, address asset, contract BridgeProxy proxyBridge) ``` Emitted when a new bridge is deployed #### Parameters | Name | Type | Description | | ----------- | -------------------- | ------------------------------------------------------ | | bridge | address | The address of the newly deployed RelayBridge contract | | asset | address | The address of the asset that the bridge will handle | | proxyBridge | contract BridgeProxy | The BridgeProxy contract associated with this bridge | ### constructor ```solidity theme={null} constructor(address hMailbox) public ``` Initializes the factory with the Hyperlane mailbox address *The mailbox address cannot be changed after deployment* #### Parameters | Name | Type | Description | | -------- | ------- | --------------------------------------------- | | hMailbox | address | The address of the Hyperlane mailbox contract | ### deployBridge ```solidity theme={null} function deployBridge(address asset, contract BridgeProxy proxyBridge) public returns (address) ``` Deploys a new RelayBridge for a specific asset *Creates a new RelayBridge instance and adds it to the bridgesByAsset mapping* #### Parameters | Name | Type | Description | | ----------- | -------------------- | ------------------------------------------------------------ | | asset | address | The address of the asset (token) that the bridge will handle | | proxyBridge | contract BridgeProxy | The BridgeProxy contract that will work with this bridge | #### Return Values | Name | Type | Description | | ---- | ------- | ------------------------------------------------------ | | \[0] | address | The address of the newly deployed RelayBridge contract | # RelayPool Source: https://docs.relay.link/references/protocol/vaults/contracts/RelayPool # Solidity API ## RelayPool ERC4626 vault that enables cross-chain asset bridging and yield generation *Receives bridged assets via Hyperlane, provides instant liquidity, and deposits idle funds into yield pools* ### OriginSettings Configuration for an authorized origin chain and bridge #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | ```solidity theme={null} struct OriginSettings { uint32 chainId; address bridge; address curator; uint256 maxDebt; uint256 outstandingDebt; address proxyBridge; uint32 bridgeFee; uint32 coolDown; } ``` ### OriginParam Parameters for adding a new origin #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | ```solidity theme={null} struct OriginParam { address curator; uint32 chainId; address bridge; address proxyBridge; uint256 maxDebt; uint32 bridgeFee; uint32 coolDown; } ``` ### UnauthorizedCaller ```solidity theme={null} error UnauthorizedCaller(address sender) ``` Error when caller is not authorized for the operation #### Parameters | Name | Type | Description | | ------ | ------- | ------------------------------------------------ | | sender | address | The address that attempted the unauthorized call | ### UnauthorizedSwap ```solidity theme={null} error UnauthorizedSwap(address token) ``` Error when attempting to swap the pool's underlying asset #### Parameters | Name | Type | Description | | ----- | ------- | -------------------------------------------------- | | token | address | The token address that was attempted to be swapped | ### UnauthorizedOrigin ```solidity theme={null} error UnauthorizedOrigin(uint32 chainId, address bridge) ``` Error when message is from an unauthorized origin #### Parameters | Name | Type | Description | | ------- | ------- | --------------------------------------------- | | chainId | uint32 | The chain ID of the unauthorized origin | | bridge | address | The bridge address of the unauthorized origin | ### MessageAlreadyProcessed ```solidity theme={null} error MessageAlreadyProcessed(uint32 chainId, address bridge, uint256 nonce) ``` Error when attempting to process an already processed message #### Parameters | Name | Type | Description | | ------- | ------- | ------------------------------------------ | | chainId | uint32 | The chain ID of the message origin | | bridge | address | The bridge address of the message origin | | nonce | uint256 | The nonce of the already processed message | ### TooMuchDebtFromOrigin ```solidity theme={null} error TooMuchDebtFromOrigin(uint32 chainId, address bridge, uint256 maxDebt, uint256 nonce, address recipient, uint256 amount) ``` Error when origin would exceed its maximum allowed debt #### Parameters | Name | Type | Description | | --------- | ------- | ------------------------------------------- | | chainId | uint32 | The chain ID of the origin | | bridge | address | The bridge address of the origin | | maxDebt | uint256 | The maximum allowed debt for this origin | | nonce | uint256 | The nonce of the rejected transaction | | recipient | address | The intended recipient of the funds | | amount | uint256 | The amount that would exceed the debt limit | ### FailedTransfer ```solidity theme={null} error FailedTransfer(address recipient, uint256 amount) ``` Error when native currency transfer fails #### Parameters | Name | Type | Description | | --------- | ------- | -------------------------------------- | | recipient | address | The intended recipient of the transfer | | amount | uint256 | The amount that failed to transfer | ### InsufficientFunds ```solidity theme={null} error InsufficientFunds(uint256 amount, uint256 balance) ``` Error when insufficient funds are available #### Parameters | Name | Type | Description | | ------- | ------- | -------------------- | | amount | uint256 | The amount available | | balance | uint256 | The balance required | ### NotAWethPool ```solidity theme={null} error NotAWethPool() ``` Error when native currency is sent to a non-WETH pool ### MessageTooRecent ```solidity theme={null} error MessageTooRecent(uint32 chainId, address bridge, uint256 nonce, uint256 timestamp, uint32 coolDown) ``` Error when message timestamp is too recent based on cooldown period #### Parameters | Name | Type | Description | | --------- | ------- | ---------------------------------------- | | chainId | uint32 | The chain ID of the message origin | | bridge | address | The bridge address of the message origin | | nonce | uint256 | The nonce of the message | | timestamp | uint256 | The timestamp of the message | | coolDown | uint32 | The required cooldown period | ### SharePriceTooLow ```solidity theme={null} error SharePriceTooLow(uint256 actualPrice, uint256 minPrice) ``` Error when share price is below minimum acceptable threshold #### Parameters | Name | Type | Description | | ----------- | ------- | ---------------------------------- | | actualPrice | uint256 | The actual share price | | minPrice | uint256 | The minimum acceptable share price | ### SharePriceTooHigh ```solidity theme={null} error SharePriceTooHigh(uint256 actualPrice, uint256 maxPrice) ``` Error when share price is above maximum acceptable threshold #### Parameters | Name | Type | Description | | ----------- | ------- | ---------------------------------- | | actualPrice | uint256 | The actual share price | | maxPrice | uint256 | The maximum acceptable share price | ### HYPERLANE\_MAILBOX ```solidity theme={null} address HYPERLANE_MAILBOX ``` The address of the Hyperlane mailbox *Used to receive cross-chain messages* ### WETH ```solidity theme={null} address WETH ``` The address of the WETH contract (used for native pools) *Set to WETH address for native currency pools, otherwise can be address(0)* ### FRACTIONAL\_BPS\_DENOMINATOR ```solidity theme={null} uint256 FRACTIONAL_BPS_DENOMINATOR ``` Denominator for fractional basis points calculations (1 = 0.0000001 bps) ### outstandingDebt ```solidity theme={null} uint256 outstandingDebt ``` Keeping track of the outstanding debt for ERC4626 computations *Represents funds that have been sent but not yet claimed from bridges* ### authorizedOrigins ```solidity theme={null} mapping(uint32 => mapping(address => struct RelayPool.OriginSettings)) authorizedOrigins ``` Mapping of origins to their settings *\[chainId]\[bridgeAddress] => OriginSettings* ### messages ```solidity theme={null} mapping(uint32 => mapping(address => mapping(uint256 => bytes))) messages ``` Mapping of messages by origin *\[chainId]\[bridgeAddress]\[nonce] => message data* ### yieldPool ```solidity theme={null} address yieldPool ``` The address of the yield pool where funds are deposited *Must be an ERC4626 vault for the same underlying asset* ### tokenSwapAddress ```solidity theme={null} address tokenSwapAddress ``` UniswapV3 wrapper contract for token swaps ### pendingBridgeFees ```solidity theme={null} uint256 pendingBridgeFees ``` Keeping track of the total fees collected *Fees are held in the yield pool until they finish streaming* ### totalAssetsToStream ```solidity theme={null} uint256 totalAssetsToStream ``` All incoming assets are streamed (even though they are instantly deposited in the yield pool) *Total amount of assets currently being streamed* ### lastAssetsCollectedAt ```solidity theme={null} uint256 lastAssetsCollectedAt ``` Timestamp when assets were last collected for streaming ### endOfStream ```solidity theme={null} uint256 endOfStream ``` Timestamp when current streaming period ends ### streamingPeriod ```solidity theme={null} uint256 streamingPeriod ``` Duration over which collected assets are streamed ### LoanEmitted ```solidity theme={null} event LoanEmitted(uint256 nonce, address recipient, contract ERC20 asset, uint256 amount, struct RelayPool.OriginSettings origin, uint256 fees) ``` Emitted when a loan is provided to a bridge recipient #### Parameters | Name | Type | Description | | --------- | ------------------------------- | ---------------------------------------- | | nonce | uint256 | The unique identifier of the transaction | | recipient | address | The address receiving the funds | | asset | contract ERC20 | The asset being transferred | | amount | uint256 | The total amount including fees | | origin | struct RelayPool.OriginSettings | The origin settings for this bridge | | fees | uint256 | The fee amount collected | ### BridgeCompleted ```solidity theme={null} event BridgeCompleted(uint32 chainId, address bridge, uint256 amount, uint256 fees) ``` Emitted when bridged funds are claimed and deposited #### Parameters | Name | Type | Description | | ------- | ------- | -------------------------------------- | | chainId | uint32 | The chain ID of the bridge origin | | bridge | address | The bridge address on the origin chain | | amount | uint256 | The total amount claimed | | fees | uint256 | The fee amount collected | ### OutstandingDebtChanged ```solidity theme={null} event OutstandingDebtChanged(uint256 oldDebt, uint256 newDebt, struct RelayPool.OriginSettings origin, uint256 oldOriginDebt, uint256 newOriginDebt) ``` Emitted when outstanding debt changes #### Parameters | Name | Type | Description | | ------------- | ------------------------------- | ---------------------------------------- | | oldDebt | uint256 | Previous total outstanding debt | | newDebt | uint256 | New total outstanding debt | | origin | struct RelayPool.OriginSettings | The origin settings involved | | oldOriginDebt | uint256 | Previous outstanding debt for the origin | | newOriginDebt | uint256 | New outstanding debt for the origin | ### AssetsDepositedIntoYieldPool ```solidity theme={null} event AssetsDepositedIntoYieldPool(uint256 amount, address yieldPool) ``` Emitted when assets are deposited into the yield pool #### Parameters | Name | Type | Description | | --------- | ------- | ---------------------- | | amount | uint256 | The amount deposited | | yieldPool | address | The yield pool address | ### AssetsWithdrawnFromYieldPool ```solidity theme={null} event AssetsWithdrawnFromYieldPool(uint256 amount, address yieldPool) ``` Emitted when assets are withdrawn from the yield pool #### Parameters | Name | Type | Description | | --------- | ------- | ---------------------- | | amount | uint256 | The amount withdrawn | | yieldPool | address | The yield pool address | ### TokenSwapChanged ```solidity theme={null} event TokenSwapChanged(address prevAddress, address newAddress) ``` Emitted when the token swap address is changed #### Parameters | Name | Type | Description | | ----------- | ------- | ---------------------------------- | | prevAddress | address | The previous swap contract address | | newAddress | address | The new swap contract address | ### YieldPoolChanged ```solidity theme={null} event YieldPoolChanged(address oldPool, address newPool) ``` Emitted when the yield pool is changed #### Parameters | Name | Type | Description | | ------- | ------- | ------------------------------- | | oldPool | address | The previous yield pool address | | newPool | address | The new yield pool address | ### StreamingPeriodChanged ```solidity theme={null} event StreamingPeriodChanged(uint256 oldPeriod, uint256 newPeriod) ``` Emitted when the streaming period is changed #### Parameters | Name | Type | Description | | --------- | ------- | ----------------------------- | | oldPeriod | uint256 | The previous streaming period | | newPeriod | uint256 | The new streaming period | ### OriginAdded ```solidity theme={null} event OriginAdded(struct RelayPool.OriginParam origin) ``` Emitted when a new origin is added #### Parameters | Name | Type | Description | | ------ | ---------------------------- | --------------------- | | origin | struct RelayPool.OriginParam | The origin parameters | ### OriginDisabled ```solidity theme={null} event OriginDisabled(uint32 chainId, address bridge, uint256 maxDebt, uint256 outstandingDebt, address proxyBridge) ``` Emitted when an origin is disabled #### Parameters | Name | Type | Description | | --------------- | ------- | ----------------------------------------- | | chainId | uint32 | The chain ID of the disabled origin | | bridge | address | The bridge address of the disabled origin | | maxDebt | uint256 | The previous maximum debt limit | | outstandingDebt | uint256 | The outstanding debt at time of disabling | | proxyBridge | address | The proxy bridge address | ### constructor ```solidity theme={null} constructor(address hyperlaneMailbox, contract ERC20 asset, string name, string symbol, address baseYieldPool, address weth, address curator) public ``` Initializes the RelayPool with core parameters *Warning: the owner should always be a timelock with significant delay* #### Parameters | Name | Type | Description | | ---------------- | -------------- | ----------------------------------------------------- | | hyperlaneMailbox | address | The Hyperlane mailbox contract address | | asset | contract ERC20 | The underlying asset for this vault | | name | string | The name of the vault token | | symbol | string | The symbol of the vault token | | baseYieldPool | address | The initial yield pool for depositing assets | | weth | address | The WETH contract address (for native currency pools) | | curator | address | The address that will own the pool after deployment | ### updateStreamingPeriod ```solidity theme={null} function updateStreamingPeriod(uint256 newPeriod) public ``` Updates the streaming period for fee accrual *Updates streamed assets before changing the period* #### Parameters | Name | Type | Description | | --------- | ------- | ----------------------------------- | | newPeriod | uint256 | The new streaming period in seconds | ### updateYieldPool ```solidity theme={null} function updateYieldPool(address newPool, uint256 minSharePriceFromOldPool, uint256 maxSharePricePriceFromNewPool) public ``` Updates the yield pool, moving all assets from the old pool to the new one *Implements share price-based slippage protection to ensure fair value transfer* #### Parameters | Name | Type | Description | | ----------------------------- | ------- | --------------------------------------------------------------------- | | newPool | address | The address of the new yield pool | | minSharePriceFromOldPool | uint256 | The minimum acceptable share price when withdrawing from the old pool | | maxSharePricePriceFromNewPool | uint256 | The maximum acceptable share price when depositing into the new pool | ### addOrigin ```solidity theme={null} function addOrigin(struct RelayPool.OriginParam origin) public ``` Adds a new authorized origin for bridging *Only callable by owner, typically a timelock contract* #### Parameters | Name | Type | Description | | ------ | ---------------------------- | --------------------------------------------------------------- | | origin | struct RelayPool.OriginParam | The origin parameters including chain ID, addresses, and limits | ### disableOrigin ```solidity theme={null} function disableOrigin(uint32 chainId, address bridge) public ``` Disables an origin by setting its max debt to zero *Only callable by the origin's curator for emergency response* #### Parameters | Name | Type | Description | | ------- | ------- | ------------------------------------------- | | chainId | uint32 | The chain ID of the origin to disable | | bridge | address | The bridge address of the origin to disable | ### increaseOutstandingDebt ```solidity theme={null} function increaseOutstandingDebt(uint256 amount, struct RelayPool.OriginSettings origin) internal ``` Increases outstanding debt for an origin *Updates both origin-specific and total outstanding debt* #### Parameters | Name | Type | Description | | ------ | ------------------------------- | ------------------------------ | | amount | uint256 | The amount to increase debt by | | origin | struct RelayPool.OriginSettings | The origin settings to update | ### decreaseOutstandingDebt ```solidity theme={null} function decreaseOutstandingDebt(uint256 amount, struct RelayPool.OriginSettings origin) internal ``` Decreases outstanding debt for an origin *Updates both origin-specific and total outstanding debt* #### Parameters | Name | Type | Description | | ------ | ------------------------------- | ------------------------------ | | amount | uint256 | The amount to decrease debt by | | origin | struct RelayPool.OriginSettings | The origin settings to update | ### maxDeposit ```solidity theme={null} function maxDeposit(address) public view returns (uint256 maxAssets) ``` Returns the maximum assets that can be deposited *Limited by the yield pool's capacity* #### Parameters | Name | Type | Description | | ---- | ------- | ----------- | | | address | | #### Return Values | Name | Type | Description | | --------- | ------- | -------------------------------------------------- | | maxAssets | uint256 | The maximum amount of assets that can be deposited | ### maxWithdraw ```solidity theme={null} function maxWithdraw(address owner) public view returns (uint256 maxAssets) ``` Returns the maximum assets that can be withdrawn by an owner *Limited to the owner's share balance converted to assets* #### Parameters | Name | Type | Description | | ----- | ------- | -------------------------------------------- | | owner | address | The address to check withdrawal capacity for | #### Return Values | Name | Type | Description | | --------- | ------- | -------------------------------------------------- | | maxAssets | uint256 | The maximum amount of assets that can be withdrawn | ### maxMint ```solidity theme={null} function maxMint(address receiver) public view returns (uint256 maxShares) ``` Returns the maximum shares that can be minted *Limited by the yield pool's deposit capacity* #### Parameters | Name | Type | Description | | -------- | ------- | ----------------------------------------- | | receiver | address | The address that would receive the shares | #### Return Values | Name | Type | Description | | --------- | ------- | ----------------------------------------------- | | maxShares | uint256 | The maximum amount of shares that can be minted | ### maxRedeem ```solidity theme={null} function maxRedeem(address owner) public view returns (uint256 maxShares) ``` Returns the maximum shares that can be redeemed by an owner *Limited by the owner's share balance and yield pool's withdrawal capacity* #### Parameters | Name | Type | Description | | ----- | ------- | -------------------------------------------- | | owner | address | The address to check redemption capacity for | #### Return Values | Name | Type | Description | | --------- | ------- | ------------------------------------------------- | | maxShares | uint256 | The maximum amount of shares that can be redeemed | ### totalAssets ```solidity theme={null} function totalAssets() public view returns (uint256) ``` Returns the total assets controlled by the pool *Includes yield pool balance, outstanding debt, minus pending fees and streaming assets* #### Return Values | Name | Type | Description | | ---- | ------- | --------------------------------- | | \[0] | uint256 | The total assets under management | ### depositAssetsInYieldPool ```solidity theme={null} function depositAssetsInYieldPool(uint256 amount) internal ``` Deposits assets into the yield pool *Internal function that approves and deposits to yield pool* #### Parameters | Name | Type | Description | | ------ | ------- | ------------------------------- | | amount | uint256 | The amount of assets to deposit | ### withdrawAssetsFromYieldPool ```solidity theme={null} function withdrawAssetsFromYieldPool(uint256 amount, address recipient) internal ``` Withdraws assets from the yield pool *Internal function that withdraws from yield pool to recipient* #### Parameters | Name | Type | Description | | --------- | ------- | ------------------------------------------- | | amount | uint256 | The amount of assets to withdraw | | recipient | address | The address to receive the withdrawn assets | ### handle ```solidity theme={null} function handle(uint32 chainId, bytes32 bridgeAddress, bytes data) external payable ``` Handles incoming cross-chain messages from Hyperlane *Only callable by Hyperlane mailbox, provides instant liquidity to recipients* #### Parameters | Name | Type | Description | | ------------- | ------- | -------------------------------------- | | chainId | uint32 | The origin chain ID | | bridgeAddress | bytes32 | The origin bridge address (as bytes32) | | data | bytes | The encoded message data | ### remainsToStream ```solidity theme={null} function remainsToStream() internal view returns (uint256) ``` Calculates remaining assets to be streamed *Returns zero if streaming period has ended* #### Return Values | Name | Type | Description | | ---- | ------- | --------------------------------------------- | | \[0] | uint256 | The amount of assets remaining to be streamed | ### updateStreamedAssets ```solidity theme={null} function updateStreamedAssets() public returns (uint256) ``` Updates the streamed assets calculation *Resets the streaming calculation to current timestamp* #### Return Values | Name | Type | Description | | ---- | ------- | ------------------------------ | | \[0] | uint256 | The new total assets to stream | ### addToStreamingAssets ```solidity theme={null} function addToStreamingAssets(uint256 amount) internal returns (uint256) ``` Adds assets to be accounted for in a streaming fashion *Adjusts streaming end time based on weighted average* #### Parameters | Name | Type | Description | | ------ | ------- | ---------------------------------------- | | amount | uint256 | The amount of assets to add to streaming | #### Return Values | Name | Type | Description | | ---- | ------- | ------------------------------ | | \[0] | uint256 | The new total assets to stream | ### claim ```solidity theme={null} function claim(uint32 chainId, address bridge) public returns (uint256 amount) ``` Claims funds from a bridge after they arrive *Decreases outstanding debt and deposits funds into yield pool* #### Parameters | Name | Type | Description | | ------- | ------- | ------------------------- | | chainId | uint32 | The origin chain ID | | bridge | address | The origin bridge address | #### Return Values | Name | Type | Description | | ------ | ------- | ---------------------------- | | amount | uint256 | The amount of assets claimed | ### sendFunds ```solidity theme={null} function sendFunds(uint256 amount, address recipient) internal ``` Sends funds to a recipient *Handles both ERC20 and native currency transfers* #### Parameters | Name | Type | Description | | --------- | ------- | -------------------------------- | | amount | uint256 | The amount to send | | recipient | address | The address to receive the funds | ### setTokenSwap ```solidity theme={null} function setTokenSwap(address newTokenSwapAddress) external ``` Sets the token swap contract address *Used for swapping non-asset tokens received by the pool* #### Parameters | Name | Type | Description | | ------------------- | ------- | ----------------------------------- | | newTokenSwapAddress | address | The new token swap contract address | ### swapAndDeposit ```solidity theme={null} function swapAndDeposit(address token, uint256 amount, uint24 uniswapWethPoolFeeToken, uint24 uniswapWethPoolFeeAsset, uint48 deadline, uint256 amountOutMinimum) public ``` Swaps tokens and deposits resulting assets *Swaps via Uniswap V3 through the token swap contract* #### Parameters | Name | Type | Description | | ----------------------- | ------- | --------------------------------------- | | token | address | The token to swap from | | amount | uint256 | The amount of tokens to swap | | uniswapWethPoolFeeToken | uint24 | The fee tier for token-WETH pool | | uniswapWethPoolFeeAsset | uint24 | The fee tier for WETH-asset pool | | deadline | uint48 | The deadline for the swap | | amountOutMinimum | uint256 | The minimum amount of assets to receive | ### collectNonDepositedAssets ```solidity theme={null} function collectNonDepositedAssets() public ``` Collects any assets not yet deposited and starts streaming them *Can be called by anyone to ensure timely asset collection* ### beforeWithdraw ```solidity theme={null} function beforeWithdraw(uint256 assets, uint256) internal ``` Hook called before withdrawing assets from the vault *Withdraws assets from yield pool before processing withdrawal* #### Parameters | Name | Type | Description | | ------ | ------- | -------------------------------- | | assets | uint256 | The amount of assets to withdraw | | | uint256 | | ### afterDeposit ```solidity theme={null} function afterDeposit(uint256 assets, uint256) internal ``` Hook called after depositing assets to the vault *Deposits assets into yield pool after receiving them* #### Parameters | Name | Type | Description | | ------ | ------- | ------------------------------ | | assets | uint256 | The amount of assets deposited | | | uint256 | | ### processFailedHandler ```solidity theme={null} function processFailedHandler(uint32 chainId, address bridge, bytes data) public ``` Processes failed Hyperlane messages manually *Only callable by owner, typically after slow bridge resolution* #### Parameters | Name | Type | Description | | ------- | ------- | ------------------------- | | chainId | uint32 | The origin chain ID | | bridge | address | The origin bridge address | | data | bytes | The encoded message data | ### receive ```solidity theme={null} receive() external payable ``` Receives native currency *Required for WETH unwrapping in native currency pools* # RelayPoolFactory Source: https://docs.relay.link/references/protocol/vaults/contracts/RelayPoolFactory # Solidity API ## RelayPoolTimelock Interface for initializing timelock contracts *Used to initialize cloned timelock instances with proper access control* ### initialize ```solidity theme={null} function initialize(uint256 minDelay, address[] proposers, address[] executors, address admin) external ``` Initializes a timelock contract with the specified parameters #### Parameters | Name | Type | Description | | --------- | ---------- | ---------------------------------------------------- | | minDelay | uint256 | The minimum delay in seconds for timelock operations | | proposers | address\[] | Array of addresses that can propose operations | | executors | address\[] | Array of addresses that can execute operations | | admin | address | The admin address (use address(0) for no admin) | ## RelayPoolFactory Factory contract for deploying RelayPool instances with associated timelocks *Deploys pools with timelock governance and tracks pools by asset* ### HYPERLANE\_MAILBOX ```solidity theme={null} address HYPERLANE_MAILBOX ``` The Hyperlane mailbox address used by all deployed pools *Immutable to ensure consistency across all pools* ### WETH ```solidity theme={null} address WETH ``` The WETH contract address for native currency pools *Used when creating pools that handle native currency* ### TIMELOCK\_TEMPLATE ```solidity theme={null} address TIMELOCK_TEMPLATE ``` The timelock template contract to be cloned *Each pool gets its own timelock instance cloned from this template* ### MIN\_TIMELOCK\_DELAY ```solidity theme={null} uint256 MIN_TIMELOCK_DELAY ``` The minimum timelock delay enforced for non-owner deployments *Owner can deploy with shorter delays for testing/special cases* ### poolsByAsset ```solidity theme={null} mapping(address => address[]) poolsByAsset ``` Mapping from asset address to array of deployed pool addresses *Multiple pools can exist for the same asset* ### UnauthorizedCaller ```solidity theme={null} error UnauthorizedCaller(address sender) ``` Error when unauthorized address attempts restricted operation #### Parameters | Name | Type | Description | | ------ | ------- | -------------------------------------------------- | | sender | address | The address that attempted the unauthorized action | ### InsufficientInitialDeposit ```solidity theme={null} error InsufficientInitialDeposit(uint256 deposit) ``` Error when initial deposit is insufficient #### Parameters | Name | Type | Description | | ------- | ------- | ---------------------------------------- | | deposit | uint256 | The insufficient deposit amount provided | ### InsufficientTimelockDelay ```solidity theme={null} error InsufficientTimelockDelay(uint256 delay) ``` Error when timelock delay is below minimum requirement #### Parameters | Name | Type | Description | | ----- | ------- | ------------------------------- | | delay | uint256 | The insufficient delay provided | ### PoolDeployed ```solidity theme={null} event PoolDeployed(address pool, address creator, address asset, string name, string symbol, address thirdPartyPool, address timelock) ``` Emitted when a new pool is deployed #### Parameters | Name | Type | Description | | -------------- | ------- | --------------------------------------------- | | pool | address | The address of the deployed RelayPool | | creator | address | The address that deployed the pool | | asset | address | The underlying asset of the pool | | name | string | The name of the pool's share token | | symbol | string | The symbol of the pool's share token | | thirdPartyPool | address | The yield pool where assets will be deposited | | timelock | address | The timelock contract governing the pool | ### constructor ```solidity theme={null} constructor(address hMailbox, address weth, address timelock, uint256 minTimelockDelay) public ``` Initializes the factory with required infrastructure addresses #### Parameters | Name | Type | Description | | ---------------- | ------- | ------------------------------------------------ | | hMailbox | address | The Hyperlane mailbox contract address | | weth | address | The WETH contract address | | timelock | address | The timelock template to be cloned for each pool | | minTimelockDelay | uint256 | The minimum delay for timelock operations | ### deployPool ```solidity theme={null} function deployPool(contract ERC20 asset, string name, string symbol, address thirdPartyPool, uint256 timelockDelay, uint256 initialDeposit, address curator) public returns (address) ``` Deploys a new RelayPool with associated timelock governance *Requires initial deposit to prevent inflation attacks, creates dedicated timelock* #### Parameters | Name | Type | Description | | -------------- | -------------- | ------------------------------------------------------------- | | asset | contract ERC20 | The ERC20 asset for the pool | | name | string | The name for the pool's share token | | symbol | string | The symbol for the pool's share token | | thirdPartyPool | address | The yield pool where idle assets will be deposited | | timelockDelay | uint256 | The delay in seconds for timelock operations | | initialDeposit | uint256 | The initial deposit amount (must be at least 1 unit of asset) | | curator | address | The address that will control the pool through the timelock | #### Return Values | Name | Type | Description | | ---- | ------- | ------------------------------------------- | | \[0] | address | The address of the newly deployed RelayPool | # RelayPoolNativeGateway Source: https://docs.relay.link/references/protocol/vaults/contracts/RelayPoolNativeGateway # Solidity API ## RelayPoolNativeGateway Gateway contract for depositing and withdrawing native ETH to/from WETH-based RelayPools *Handles wrapping/unwrapping of ETH and provides slippage protection for all operations* ### EthTransferFailed ```solidity theme={null} error EthTransferFailed() ``` Error when ETH transfer fails ### OnlyWethCanSendEth ```solidity theme={null} error OnlyWethCanSendEth() ``` Error when contract receives ETH from non-WETH address ### RemainingEth ```solidity theme={null} error RemainingEth() ``` Error when ETH remains in contract after operation ### SlippageExceeded ```solidity theme={null} error SlippageExceeded() ``` Error when slippage protection is triggered ### WETH ```solidity theme={null} contract IWETH WETH ``` The Wrapped ETH (WETH) contract *Used to wrap/unwrap native ETH for pool operations* ### constructor ```solidity theme={null} constructor(address wethAddress) public ``` Initializes the gateway with the WETH contract address #### Parameters | Name | Type | Description | | ----------- | ------- | -------------------------------------------- | | wethAddress | address | Address of the Wrapped Native token contract | ### deposit ```solidity theme={null} function deposit(address pool, address receiver, uint256 minSharesOut) external payable returns (uint256 shares) ``` Deposits native ETH into a WETH-based pool *Wraps ETH to WETH, then deposits to the pool with slippage protection* #### Parameters | Name | Type | Description | | ------------ | ------- | --------------------------------------------------------- | | pool | address | The address of the ERC4626 pool to deposit into | | receiver | address | The address that will receive the pool shares | | minSharesOut | uint256 | Minimum amount of shares to receive (slippage protection) | #### Return Values | Name | Type | Description | | ------ | ------- | ------------------------------------------------ | | shares | uint256 | The amount of pool shares minted to the receiver | ### mint ```solidity theme={null} function mint(address pool, address receiver, uint256 minSharesOut) external payable returns (uint256 shares) ``` Mints pool shares by depositing native ETH *Wraps ETH, calculates shares, then mints with slippage protection* #### Parameters | Name | Type | Description | | ------------ | ------- | --------------------------------------------------------- | | pool | address | The address of the ERC4626 pool to mint shares from | | receiver | address | The address that will receive the pool shares | | minSharesOut | uint256 | Minimum amount of shares to receive (slippage protection) | #### Return Values | Name | Type | Description | | ------ | ------- | ------------------------------------------------ | | shares | uint256 | The amount of pool shares minted to the receiver | ### withdraw ```solidity theme={null} function withdraw(address pool, uint256 assets, address receiver, uint256 maxSharesIn) external virtual returns (uint256 shares) ``` Withdraws a specific amount of native ETH from a WETH-based pool *Withdraws WETH from pool, unwraps to ETH, with slippage protection* #### Parameters | Name | Type | Description | | ----------- | ------- | ------------------------------------------------------ | | pool | address | The address of the ERC4626 pool to withdraw from | | assets | uint256 | Amount of native ETH to withdraw | | receiver | address | The address that will receive the native ETH | | maxSharesIn | uint256 | Maximum amount of shares to burn (slippage protection) | #### Return Values | Name | Type | Description | | ------ | ------- | -------------------------------- | | shares | uint256 | The amount of pool shares burned | ### redeem ```solidity theme={null} function redeem(address pool, uint256 shares, address receiver, uint256 minAssetsOut) external virtual returns (uint256 assets) ``` Redeems pool shares for native ETH *Redeems shares for WETH, unwraps to ETH, with slippage protection* #### Parameters | Name | Type | Description | | ------------ | ------- | ------------------------------------------------------ | | pool | address | The address of the ERC4626 pool to redeem from | | shares | uint256 | Amount of pool shares to redeem | | receiver | address | The address that will receive the native ETH | | minAssetsOut | uint256 | Minimum amount of ETH to receive (slippage protection) | #### Return Values | Name | Type | Description | | ------ | ------- | ----------------------------------------- | | assets | uint256 | The amount of native ETH sent to receiver | ### safeTransferETH ```solidity theme={null} function safeTransferETH(address to, uint256 value) internal ``` Safely transfers ETH to an address *Reverts if the ETH transfer fails* #### Parameters | Name | Type | Description | | ----- | ------- | ----------------------------- | | to | address | Recipient of the ETH transfer | | value | uint256 | Amount of ETH to transfer | ### receive ```solidity theme={null} receive() external payable ``` Receives ETH only from WETH contract *Required for WETH unwrapping operations* # Bridging Source: https://docs.relay.link/references/protocol/vaults/guides/bridging In this guide, we will learn how to successfully bridge funds from an origin to a vault. ## Identifying the vault and origin The first step is to identify the vault you want to bridge to. This is based on the currency and chain on which you want to receive the funds. You can chose to *hardcode* trusted vault addresses (like the ones deployed by the Relay team), or use our [Backend API](../backend) to query the vaults available on a specific chain and currency. Once you have identified the vault, you need to identify the origin on which you want to perform your deposit. There again, you can use our Backend API to query the origins available for a specific vault. You can filter these origins by their chain and currency. Please note that a vault may have "different" currencies than the ones you deposit. This is for example the case for deposits of native tokens (Ethereum) who are in fact going to a wrapped token (WETH) vault. Once you have identified the origin, you should check a few things: * that the vault has enough liquidity to handle your deposit (you can query the vault's balance on the Backend API) comparing its `outstandingDebt` and `totalAssets` values. * that the origin's specific `outstandingDebt` is sufficient to handle your deposit. This is the maximum amount of funds that can be bridged from that origin at any given time. You can query this value on the Backend API. You can use the vault's `authorizedOrigins` field to check the origins available for a specific vault (it also returns the `bridgeFee` and `cooldown` that are useful). ## Performing the deposit Finally, it is time to perform the deposit. In order to perform a deposit, you need to first query the `getFee` method on the bridge contract in order to get the fee that will be charged for the deposit itself. These are the fees collected by Hyperlane to send the message to the vault. You will need to specify the `amount`, `recipient` and `poolGas` parameters (a default of 300,000 gas should be sufficient for most cases). Once you have the fee, you will need to call the `bridge` method on the origin contract, passing the `amount`, `recipient`, `poolGas` and an extra `data` parameters. You MUST also pass a `value` matching the result of the `getFee` call from above. The `data` parameter is optional and can be used to pass extra information to the underlying bridge proxy (see [BridgeProxy](../contracts/RelayBridge)) and could be `0x0` in most cases. The `recipient` address will receive the funds on the vault chain as soon as the message has been processed by Hyperlane (and the cool down period has been respected). The `recipient` address can be the same as the one you used to deposit, or a different one. This is useful if you want to bridge funds to a different address than the one you used to deposit. ## Using the Hyperlane explorer Messages are passed between the origin and the vault using Hyperlane. You can use the [Hyperlane Explorer](https://explorer.hyperlane.xyz/) to track the messages sent from the origin to the vault. This can be useful to check if your message has been sent and is being processed by the vault. For this, check the `messageId` parameter of the `DispatchId` event emitted by `Mailbox` contract on the origin chain. Here [is an example](https://explorer.hyperlane.xyz/message/0x9122e85feb8638817b51605152992058bf3a0f683ae495240bbe8f78e7c7da2f) for this transaction [on the Zora chain](https://explorer.zora.energy/tx/0x64229c089b71bae499646abdd22df4562514eea5c264fdb07c80bdb331ab7670?tab=logs). # Interacting with Relay Vaults Source: https://docs.relay.link/references/protocol/vaults/guides/interacting How to interact with Relay Vaults In this guide, we will cover how to interact with Relay Vaults, whether you are a user, a liquidity provider (LP), a developer, or a curator. ## As a Liquidity Provider (LP) As an LP, you're most likely going to interact with Relay Vaults through the [Relay UI](https://relay.link/vaults). You can deposit and withdraw funds from the vaults, and you can also track your yields and balances. You can also interact directly with the smart contracts or using block explorers like Etherscan and Blockscout. ## As a developer As a developer, you should consider interacting with the vaults using the smart-contracts. We publish npm packages such as `@relay-vaults/abis` that includes all the ABI definitions for the vaults. We also publish `@relay-vaults/client` that includes a graphQL client to interact with the [backend API](../backend). Finally, you can also use our hardhat tasks to perform deposits and withdrawals from the vaults. These tasks are available in the [Relay Vaults GitHub repository](https://github.com/relayprotocol/relay-vaults/tree/main/smart-contracts). # Overview Source: https://docs.relay.link/references/protocol/vaults/overview Introducing Relay Vaults Relay Vaults is a separate protocol from [Relay Settlement](/references/protocol/overview), focused on liquidity provisioning rather than intent settlement. While Settlement handles the core flow of deposits, fills, and solver payments, Vaults provide the infrastructure solvers use to rebalance their capital across chains. Relay's mission is to make transacting across chains as fast, cheap, and simple as online payments. **Relay Vaults** bring us closer to that vision. Vaults are permissionless, [ERC4626](https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/) compliant, yield-bearing pools designed to provide liquidity that solvers use to facilitate faster, cheaper, and more efficient crosschain transactions. By leveraging idle capital from lending markets (e.g., Aave, Morpho), Relay Vaults enable solvers to instantly rebalance liquidity across chains. This improves crosschain withdrawals and rebalancing speed, reduces transaction fees, and ensures stable yield for depositors. ## How Relay Vaults Work Depositors provide liquidity by depositing tokens into Relay Vaults, earning a **base yield** from established lending protocols (Aave currently). Additionally, depositors earn a **boost yield** when their funds are utilized by solvers for crosschain rebalancing transactions. In essence, Vault capital accrues base yield while idle and earns additional yield when actively supporting solver operations. Relay Vaults can support multiple chains from a single deployment, enhancing liquidity availability and yield generation. The flexibility to rebalance across numerous L2 and L3 chains further increases yield potential and capital efficiency. Relay Vaults are particularly beneficial for chains that provide liquidity to a Vault deployed on a popular network, such as Arbitrum. This enables enhanced interoperability for all connected L3 chains, significantly improving crosschain liquidity and user experience on those emerging networks. Interested in integrating your chain with Relay Vaults or providing liquidity to enhance your network's interoperability? Contact our team to discuss collaboration opportunities. ## Working with Vaults Vaults are composable, making them easy to integrate with other defi protocols and applications. The Relay team has deployed Vaults initially on Ethereum Mainnet and Arbitrum. Users can interact with Vaults via our [**user interface**](http://relay.link/vaults/) or directly through smart contracts. For developers, Relay provides a backend indexing service, simplifying data querying and integration efforts. This service is accessible at [**https://vaults-api.relay.link/**](https://vaults-api.relay.link/). Relay Vaults are open-source and available on [**GitHub**](https://github.com/relayprotocol/relay-vaults). Contributions are welcome. ## Why Relay Vaults Matter Instant crosschain liquidity and efficient rebalancing significantly lower transaction costs and improve user experience. Vaults address the slow, costly rebalancing problem that often forces higher fees, particularly on smaller or emerging networks with limited interoperability infrastructure. Relay Vaults is the next step in Relay’s broader strategy to create seamless crosschain payments. Explore our first Vaults on Ethereum Mainnet and Arbitrum today, and join us in shaping the future of crosschain commerce. # Security Source: https://docs.relay.link/references/protocol/vaults/security ## Threat model Relay vaults are receiving funds from users who deposit their tokens to provide liquidity to the vaults. The contracts are **not** upgradable, meaning that once deployed, the code cannot be changed. ### Curator However, there are several configuration parameters that can be changed by the owner of the vaults (called its *curator*), such as the base yield pool, or the origins, and few other minor elements. In order to protect the user funds, each vault is deployed and "owned" by a **timelock contract**, which means that any change submitted by its curator on the contract can only happen after a delay. This allows the community to review the changes, and, if needed, Liquidity Providers can pull their liquidity out of the vaults before the changes are applied. Additionally, for vaults curated by the Relay team, the curator is a **multisig wallet** with a 3 of 5 policy. This means that any change to the vaults must be approved by at least 3 distinct members of the Relay team, providing an additional layer of security. ### Origins Another threat is one where a specific origin (and its bridge) gets compromised. This can take multiple forms, such as a bridge being exploited, or an origin chain reorg that would lead to funds that had been previously bridged, not actually being sent to the vault. In order to mitigate these risks, each origin has a **maximum debt** setting which specifies the maximum amount of funds that can be bridged from that origin at any given time (capping the loss that a vault can endure). The origins also have a specific **cool down** period which is the minimum delay between a bridging transaction and the dispersion of funds to the recipient. Finally, each origin has a specific **curator** who can instantly pause the origin in case of an emergency. This allows the Relay team to quickly react to any potential issues with a specific origin. ## Audits The Relay Vault contracts have been audited by [Spearbit](https://spearbit.com/) and you can find the [audit reports in our Github repo](https://github.com/relayprotocol/relay-vaults/blob/main/docs/README.md). # Installation Source: https://docs.relay.link/references/relay-kit/hooks/installation Installing and Configuring RelayKit Hooks ## Installation Use this lightweight React hook package to integrate Relay into your custom interface. Start by installing the required packages: ```shell yarn theme={null} yarn add react react-dom viem @tanstack/react-query @relayprotocol/relay-kit-hooks ``` ```shell npm theme={null} npm install --save react react-dom viem @tanstack/react-query @relayprotocol/relay-kit-hooks ``` ```shell pnpm theme={null} pnpm add react react-dom viem @tanstack/react-query @relayprotocol/relay-kit-hooks ``` ```shell bun theme={null} bun add react react-dom viem @tanstack/react-query @relayprotocol/relay-kit-hooks ``` If using typescript ensure that you're on v5+. Refer to the package json for the latest version requirements for the peer dependencies. ## Tanstack Query Setup The hooks require [TanStack Query](https://tanstack.com/query/latest) to be installed and configured. Refer to the [Tanstack installation instructions](https://tanstack.com/query/latest/docs/framework/react/installation). Once Tanstack is configured in your React application, that's all you need to get started. ## Hooks Each hook is configured separately to allow for as much flexibility as possible. All hooks come with a query function that can be used outside of a React context to fetch the data (useful when using SSR or in a context that doesn't use React). * [useQuote](/references/relay-kit/hooks/useQuote) * [useRelayChains](/references/relay-kit/hooks/useRelayChains) * [useRequests](/references/relay-kit/hooks/useRequests) * [useTokenList](/references/relay-kit/hooks/useTokenList) * [useTokenPrice](/references/relay-kit/hooks/useTokenPrice) * [useExecutionStatus](/references/relay-kit/hooks/useExecutionStatus) # useExecutionStatus Source: https://docs.relay.link/references/relay-kit/hooks/useExecutionStatus Fetch the execution status of a quote ## Parameters | Parameter | Description | Required | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **baseApiUrl** | Base api url for the relay api, defaults to [https://api.relay.link](https://api.relay.link) but can also be configured to [https://api.testnets.relay.link](https://api.testnets.relay.link) | ❌ | | **options** | Query parameters that map directly to the [execution status api](/references/api/get-intents-status-v3) | ❌ | | **queryOptions** | Tanstack query options. Refer to the [Tanstack](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) docs. | ❌ | ## Return Data The hook returns an object with the base [Tanstack Query response](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). The data property maps to the object returned in the aforementioned [execution status api](/references/api/get-intents-status-v3). ## Usage ```typescript theme={null} import { useExecutionStatus } from '@relayprotocol/relay-kit-hooks' const { status, details, inTxHashes, txHashes, time, originChainId, destinationChainId } = useExecutionStatus('https://api.relay.link', { requestId: '0x6a6cab2695f2dc4a67539d971760764edac9e52b0a2219a5fbb3faf2f04ac7c2' }) ``` ## Query Function ```typescript theme={null} import { queryExecutionStatus } from '@relayprotocol/relay-kit-hooks' queryExecutionStatus() ``` # useQuote Source: https://docs.relay.link/references/relay-kit/hooks/useQuote Fetch a quote and then execute it with this convenient hook ## Parameters | Parameter | Description | Required | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **client** | A RelayClient instance. You can create one using the [SDK](http://localhost:3000/references/relay-kit/sdk/createClient) or retrieve one using the `useRelayClient` hook from the UI kit. | ✅ | | **wallet** | A valid [WalletClient](https://viem.sh/docs/clients/wallet.html) from Viem or an AdaptedWallet generated from an adapter. This parameter can be left empty if just retrieving a quote, but is required for executing a quote. | ❌ | | **options** | Options that map directly to the [quote API](/references/api/get-quote-v2). | ✅ | | **onRequest** | A callback function that triggers when a request is made to fetch the quote. | ❌ | | **onResponse** | A callback function that triggers when a response is returned. | ❌ | | **queryOptions** | Tanstack query options. Refer to the [Tanstack](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) docs. | ❌ | ## Return Data The hook returns an object with the base [Tanstack Query response](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery), it also returns an `executeQuote` function which allows you to conveniently execute the quote. The function takes one parameter, an [onProgress callback](/references/relay-kit/sdk/actions/execute) that mirrors the underlying execute action. from the SDK. ## Usage ```typescript Fetching a Quote theme={null} import { useQuote } from '@relayprotocol/relay-kit-hooks' import { useWalletClient } from 'wagmi' import { getClient } from '@relayprotocol/relay-sdk' const queryOptions = {...} //Query options from tanstack const walletClient = useWalletClient() const relayClient = getClient() //Either use getClient, createClient or useRelayClient from the ui kit const { data: quote, isLoading: isFetchingQuote, executeQuote, error } = useQuote( relayClient ? relayClient : undefined, walletClient.data, { user: address, originChainId: 1, destinationChainId: 10, originCurrency: "0x0000000000000000000000000000000000000000", destinationCurrency: "0x0000000000000000000000000000000000000000", tradeType: "EXACT_INPUT", amount: "10000000" }, () => { console.log("Request Triggered!") }, () => { console.log("Response Returned!") }, queryOptions ) ``` ```typescript Executing a Quote theme={null} import { useQuote } from '@relayprotocol/relay-kit-hooks' import { useWalletClient } from 'wagmi' import { getClient } from '@relayprotocol/relay-sdk' const queryOptions = {...} //Query options from tanstack const walletClient = useWalletClient() const relayClient = getClient() //Either use getClient, createClient or useRelayClient from the ui kit const { data: quote, isLoading: isFetchingQuote, executeQuote, error } = useQuote( ... ) //Refer to the previous example ... //Then use the executeQuote function on a button for example ``` ## Query Function ```typescript theme={null} import { queryQuote } from "@relayprotocol/relay-kit-hooks"; const response = queryQuote("https://api.relay.link", { user: address, originChainId: 1, destinationChainId: 10, originCurrency: "0x0000000000000000000000000000000000000000", destinationCurrency: "0x0000000000000000000000000000000000000000", tradeType: "EXACT_INPUT", amount: "10000000", }); ``` # useRelayChains Source: https://docs.relay.link/references/relay-kit/hooks/useRelayChains Fetch all relay supported chains ## Parameters | Parameter | Description | Required | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **baseApiUrl** | Base api url for the relay api, defaults to [https://api.relay.link](https://api.relay.link) but can also be configured to [https://api.testnets.relay.link](https://api.testnets.relay.link) | ❌ | | **options** | Query parameters that map directly to the [chains api](/references/api/get-chains) | ❌ | | **queryOptions** | Tanstack query options. Refer to the [Tanstack](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) docs. | ❌ | ## Return Data The hook returns an object with the base [Tanstack Query response](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). The data property maps to the object returned in the aforementioned [chains api](/references/api/get-chains). You'll also get back chains (an array of RelayChains) and viemChains (an array of viem compatible chains). You can use the viem chains in your wagmi config. ## Usage ```typescript theme={null} import { useRelayChains } from '@relayprotocol/relay-kit-hooks' const { chains, viemChains } = useRelayChains() ``` ## Query Function ```typescript theme={null} import { queryRelayChains } from '@relayprotocol/relay-kit-hooks' queryRelayChains() ``` # useRequests Source: https://docs.relay.link/references/relay-kit/hooks/useRequests Fetch cross-chain Relay transactions ## Parameters | Parameter | Description | Required | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **baseApiUrl** | Base api url for the relay api, defaults to [https://api.relay.link](https://api.relay.link) but can also be configured to [https://api.testnets.relay.link](https://api.testnets.relay.link) | ❌ | | **options** | Query parameters that map directly to the [requests api](/references/api/get-requests) | ❌ | | **queryOptions** | Tanstack query options. Refer to the [Tanstack](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) docs. | ❌ | ## Return Data The hook returns an object with the base [Tanstack Query response](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). The data property maps to the object returned in the aforementioned [requests api](/references/api/get-requests). ## Usage ```typescript theme={null} import { useRequests } from '@relayprotocol/relay-kit-hooks' const { data: transactions } = useRequests() ``` ## Query Function ```typescript theme={null} import { queryRequests } from '@relayprotocol/relay-kit-hooks' queryRequests() ``` # useTokenList Source: https://docs.relay.link/references/relay-kit/hooks/useTokenList Curated token searching ## Parameters | Parameter | Description | Required | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **baseApiUrl** | Base api url for the relay api, defaults to [https://api.relay.link](https://api.relay.link) but can also be configured to [https://api.testnets.relay.link](https://api.testnets.relay.link) | ❌ | | **options** | Query parameters that map directly to the [currencies api](/references/api/get-currencies) | ❌ | | **queryOptions** | Tanstack query options. Refer to the [Tanstack](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) docs. | ❌ | ## Return Data The hook returns an object with the base [Tanstack Query response](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). The data property maps to the object returned in the aforementioned [currencies api](/references/api/get-currencies). ## Usage ```typescript theme={null} import { useTokenList } from '@relayprotocol/relay-kit-hooks' const { data: suggestedTokens } = useTokenList( "https://api.relay.link", { limit: 20, term: "usdc" } ) ``` ## Query Function ```typescript theme={null} import { queryTokenList } from '@relayprotocol/relay-kit-hooks' queryTokenList( "https://api.relay.link", { limit: 20, term: "usdc" } ) ``` # useTokenPrice Source: https://docs.relay.link/references/relay-kit/hooks/useTokenPrice Fetch the USD price for a token on a specific chain. ## Parameters | Parameter | Description | Required | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **baseApiUrl** | Base api url for the relay api, defaults to [https://api.relay.link](https://api.relay.link) but can also be configured to [https://api.testnets.relay.link](https://api.testnets.relay.link) | ❌ | | **options** | Query parameters that map directly to the [prices API](/references/api/get-token-price). | ✅ | | **queryOptions** | Tanstack query options. Refer to the [Tanstack](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) docs. | ❌ | ## Return Data The hook returns an object with the base [Tanstack Query response](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). The data property maps to the object returned in the aforementioned [prices API](/references/api/get-token-price). ## Usage ```typescript theme={null} import { useTokenPrice } from "@relayprotocol/relay-kit-hooks"; import { useRelayClient } from "@relayprotocol/relay-kit-ui"; import { zeroAddress } from "viem"; const { data: ethPriceResponse, isLoading, error, } = useTokenPrice( { address: zeroAddress, // Native currency (ETH) chainId: 8453, // Base chain ID }, { // Optional query options refetchInterval: 60000, // Refresh every minute } ); if (isLoading) { console.log("Loading ETH price..."); } else if (error) { console.error("Error fetching ETH price:", error); } else { console.log("ETH Price on Base:", ethPriceResponse?.price); // Access the price from the data object } ``` ## Query Function ```typescript theme={null} import { queryTokenPrice } from "@relayprotocol/relay-kit-hooks"; import { zeroAddress } from "viem"; // Example: Fetch price for ETH on Base (chain 8453) queryTokenPrice( "https://api.relay.link", // Or use baseApiUrl from useRelayClient() { address: zeroAddress, chainId: 8453, } ) .then((priceData) => { console.log("ETH Price on Base:", priceData); }) .catch((error) => { console.error("Failed to fetch ETH price:", error); }); ``` # Overview Source: https://docs.relay.link/references/relay-kit/overview Introducing RelayKit RelayKit is comprised of multiple packages all with one goal in mind: supercharge Relay integration. Below is the directory of packages:
Craft an engaging and thoughtful user experience while the SDK takes care of fetching and executing quotes
Docs | Github
}> Powerful hooks that make fetching and executing quotes in React a breeze
Docs | Github
Jumpstart your Relay integration by embedding our fully featured widget
Docs | Github
# claimAppFees Source: https://docs.relay.link/references/relay-kit/sdk/actions/claimAppFees Claim app fees for a wallet [What are app fees?](/features/app-fees) ### Parameters | Property | Description | Required | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | | **wallet** | A valid WalletClient from viem or an adapted wallet generated from an adapter that meets this [interface](https://github.com/relayprotocol/relay-kit/blob/main/packages/sdk/src/types/AdaptedWallet.ts). | ✅ | | **chainId** | Chain ID to claim fees on | ✅ | | **currency** | Token address to claim | ✅ | | **recipient** | Address to receive claimed fees (defaults to wallet address) | ❌ | | **amount** | The amount to be claimed in the currency specified. | ❌ | | **onProgress** | Callback to update UI state as execution progresses. Can also be used to get the transaction hash for a given step item. The following data points are returned: `steps`, `fees`, `breakdown`, `txHashes`, `currentStep`, `currentStepItem`, `details` | ❌ | ### Example ```typescript theme={null} import { getClient } from "@relayprotocol/relay-sdk"; import { useWalletClient } from "wagmi"; const { data: wallet } = useWalletClient(); const { data } = await getClient().actions.claimAppFees({ wallet, chainId: 8453, // Base currency: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // USDC recipient: "0x...", // Optional amount: "10000000", // Optional (10 USDC) onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { // custom handling }, }); ``` # execute Source: https://docs.relay.link/references/relay-kit/sdk/actions/execute Execute a quote and get updates on its progress ### Parameters | Property | Description | Required | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | | **quote** | A valid quote retrieved using the [getQuote](/references/relay-kit/sdk/actions/getQuote) action | ✅ | | **wallet** | A valid WalletClient from viem or an adapted wallet generated from an adapter that meets this [interface](https://github.com/reservoirprotocol/relay-sdk/blob/main/packages/sdk/src/types/AdaptedWallet.ts). | ✅ | | **depositGasLimit** | The gas limit used for the deposit transaction. | ❌ | | **onProgress** | Callback to update UI state as execution progresses. Can also be used to get the transaction hash for a given step item. The following data points are returned: `steps`, `fees`, `breakdown`, `txHashes`, `currentStep`, `currentStepItem`, `details` | ❌ | ### Example ```typescript theme={null} import { getClient, Execute, getQuote } from "@relayprotocol/relay-sdk"; import { useWalletClient } from 'wagmi' ... const wallet = useWalletClient() const options = ... //define this based on getQuote options const quote = await getClient().actions.getQuote(options) getClient().actions.execute({ quote, wallet, onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { //custom handling }, }) ``` ### Handling onProgress updates The state of progress is updated as users are moved through the flow. The onProgress callback will fire every time there are changes to the status. Below is a breakdown of the data returned and how it may be useful in representing the status: * **steps**: This is the full steps object as it's returned by the api and enhanced with some additional properties on the client. Notably the step status has been updated as well as any errors have been attached. * **fees**: This is the full fees object as it's returned by the api * **breakdown**: This is the full breakdown object as it's returned by the api * **currentStep**: We've conveniently pinpointed the current step that's being processed and made it accessible in this callback. * **currentStepItem**: Similarly to the step we've hoisted the current step item that is being processed so it's more easily accessible. A step can contain multiple items that are processed in parallel as a batch. * **txHashes**: A full list of all the transaction hashes that have been processed so far during execution. * **details**: A summary of the swap action that will be performed. This includes information pertinent to the swap: currencyIn, currencyOut, swap rate, etc. # executeGaslessBatch Source: https://docs.relay.link/references/relay-kit/sdk/actions/executeGaslessBatch Execute a gasless batch transaction using EIP-7702 delegation The `executeGaslessBatch` action takes a quote from [getQuote](/references/relay-kit/sdk/actions/getQuote), delegates the user's EOA to a batch executor using EIP-7702, batches the quote's transaction steps atomically, and submits via Relay's execute API with the sponsor covering gas costs. This means the user pays no gas fees. An API key is required for gasless batch execution. Configure it via `createClient({ apiKey: "..." })`. ### Parameters | Property | Description | Required | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **quote** | A valid quote retrieved using the [getQuote](/references/relay-kit/sdk/actions/getQuote) action | ✅ | | **walletClient** | A valid WalletClient from [viem](https://viem.sh) with an account attached | ✅ | | **executor** | Batch executor configuration. Defaults to the Calibur executor. | ❌ | | **subsidizeFees** | Whether the sponsor pays all fees. Defaults to `false`. | ❌ | | **originGasOverhead** | Gas overhead for the origin chain gasless transaction. Overrides the executor's default if provided (Calibur default: 80,000). | ❌ | | **onProgress** | Callback for each stage of the execution flow. Returns `status`, `requestId`, and `details`. See [onProgress statuses](#handling-onprogress-updates) below. | ❌ | ### Example ```typescript theme={null} import { getClient, getQuote, executeGaslessBatch } from "@relayprotocol/relay-sdk"; import { createWalletClient, custom } from "viem"; const walletClient = createWalletClient({ account: "0x...", chain: mainnet, transport: custom(window.ethereum), }); const quote = await getClient().actions.getQuote({ // ...quote options }); const result = await executeGaslessBatch({ quote, walletClient, onProgress: ({ status, requestId, details }) => { console.log(`Status: ${status}`, requestId); }, }); console.log("Request ID:", result.requestId); ``` ### Example with Subsidized Fees ```typescript theme={null} const result = await executeGaslessBatch({ quote, walletClient, subsidizeFees: true, onProgress: ({ status }) => { console.log(`Status: ${status}`); }, }); ``` ### Handling onProgress updates The `onProgress` callback fires at each stage of the gasless batch execution. The `status` field indicates which stage is currently active: * **signing\_authorization**: The user is being prompted to sign the EIP-7702 authorization to delegate their EOA to the batch executor. This step is skipped if the EOA is already delegated. * **signing\_batch**: The user is being prompted to sign the EIP-712 typed data for the batched calls. * **submitting**: The signed batch is being submitted to the Relay API. * **polling**: The request has been submitted and is being polled for completion. * **success**: The batch execution completed successfully. The `details` field contains the full status response. * **failure**: The batch execution failed or was refunded. The `details` field contains the full status response. # fastFill Source: https://docs.relay.link/references/relay-kit/sdk/actions/fastFill Fast fill a request to accelerate the destination fill The `fastFill` action allows you to accelerate the filling of a cross-chain request on the destination chain. This is useful when you want to speed up the completion of a bridge or swap operation. [Learn more about the Fast Fill API](/references/api/fast-fill) ### Parameters | Property | Description | Required | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **requestId** | The unique identifier of the request to fast fill | ✅ | | **solverInputCurrencyAmount** | The amount of input currency the solver should use for filling. This is useful if you know the finalized input amount which can help to minimize slippage. If not specified, uses the quoted amount. | ❌ | ### Example ```typescript theme={null} import { getClient } from "@relayprotocol/relay-sdk"; const requestId = "0x..."; // The request ID from a previous quote/execute const result = await getClient().actions.fastFill({ requestId, }); ``` ### Example with Solver Input Amount ```typescript theme={null} import { getClient } from "@relayprotocol/relay-sdk"; const requestId = "0x..."; // The request ID from a previous quote/execute const result = await getClient().actions.fastFill({ requestId, solverInputCurrencyAmount: "1000000000000000000", // 1 token in wei }); ``` # getAppFees Source: https://docs.relay.link/references/relay-kit/sdk/actions/getAppFees Retrieve app fee balances for a specific wallet [What are app fees?](/features/app-fees) ### Parameters | Property | Description | Required | | ---------- | ------------------------------------------------ | -------- | | **wallet** | The wallet address to fetch app fee balances for | ✅ | ### Example ```typescript theme={null} import { getClient } from "@relayprotocol/relay-sdk"; const wallet = "0x..."; // Replace with your wallet address const balances = await getClient().actions.getAppFees({ wallet, }); ``` # getQuote Source: https://docs.relay.link/references/relay-kit/sdk/actions/getQuote Get a quote for a crosschain relay (bridge, swap, call, etc) ### Arguments | Property | Description | Required | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **parameters** | Quote parameters defined below | ✅ | | **includeDefaultParameters** | Setting this to true will include default parameters, which include user and recipient. These parameters are based on the wallet and fallback to the dead address if the wallet is missing. | ❌ | | **headers** | Custom headers to pass along with the quote request, such as `x-api-key` for authentication. | ❌ | **Warning**: Never pass `x-api-key` in headers from client-side code. Only use the `headers` parameter with API keys when calling `getQuote` entirely on the server. ### Quote Parameters | Property | Description | Required | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- | | **chainId** | The chain id to deposit funds on | ✅ | | **toChainId** | The chain id to execute the txs on | ✅ | | **currency** | Address for a supported native currency or valid erc20 | ✅ | | **toCurrency** | Address for a supported native currency or valid erc20 | ✅ | | **user** | The user or sender of the bridge. This must be defined if not using `includeDefaultParameters`. | ✅ (❌ if `includeDefaultParameters` is true) | | **recipient** | The recipient of the bridge. This must be defined if not using `includeDefaultParameters`. | ✅ (❌ if `includeDefaultParameters` is true) | | **tradeType** | Either `EXACT_INPUT` for quoting via an input amount, or `EXPECTED_OUTPUT`/`EXACT_OUTPUT` for quoting via an output amount. | ✅ | | **amount** | Amount in wei, in the supplied `currency` | ❌ | | **wallet** | A valid WalletClient from viem or an adapted wallet generated from an adapter that meets this [interface](https://github.com/reservoirprotocol/relay-sdk/blob/main/packages/sdk/src/types/AdaptedWallet.ts). | ❌ | | **txs** | An array of either transaction objects (made up of a to, data and value properties) or viem request objects returned from viem's [simulateContract](https://viem.sh/docs/contract/simulateContract.html) function. | ❌ | | **options** | Additional options that map directly to the [quote API](/references/api/get-quote-v2). | ❌ | ### Native Bridge Example ```typescript theme={null} import { getClient } from "@relayprotocol/relay-sdk"; import { useWalletClient } from "wagmi"; const { data: wallet } = useWalletClient(); const quote = await getClient()?.actions.getQuote({ chainId: 1, toChainId: 10, currency: "0x0000000000000000000000000000000000000000", toCurrency: "0x0000000000000000000000000000000000000000", amount: "10000000000000000", // 0.01 ETH wallet, user: "WALLET_ADDRESS", //Replace with your wallet address recipient: "RECIPIENT_ADDRESS", //Replace with the recipient address }); ``` ### Cross-Chain Swap Example ```typescript theme={null} // Cross-Chain Swap from USDC on Ethereum to DAI on Optimism const quote = await getClient()?.actions.getQuote({ chainId: 1, toChainId: 10, currency: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC on Ethereum toCurrency: "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1", // DAI on Optimism amount: "100000", // 100 USDC wallet, user: "WALLET_ADDRESS", //Replace with your wallet address recipient: "RECIPIENT_ADDRESS", //Replace with the recipient address }); ``` ### Wrap/Unwrap Example ```typescript theme={null} // Unwrap ETH const quote = await getClient()?.actions.getQuote({ chainId: 1, toChainId: 1, currency: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH toCurrency: "0x0000000000000000000000000000000000000000", amount: "10000000000000000", // 0.01 ETH wallet, user: "WALLET_ADDRESS", //Replace with your wallet address recipient: "RECIPIENT_ADDRESS", //Replace with the recipient address }); ``` ### Send Example ```typescript theme={null} // Send ERC20 / Native Currency to another address on the same chain // Note: The recipient address must be specified for send transactions and be different from the wallet address const quote = await getClient()?.actions.getQuote({ chainId: 1, toChainId: 1, currency: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC toCurrency: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", amount: "100000", // 100 USDC wallet, user: "WALLET_ADDRESS", //Replace with your wallet address recipient: "0x0000c3caa36e2d9a8cd5269c976ede05018f0000", }); ``` ### Server-Side Example with API Key ```typescript theme={null} // Server-side only - pass API key via headers const quote = await getClient()?.actions.getQuote( { chainId: 1, toChainId: 10, currency: "0x0000000000000000000000000000000000000000", toCurrency: "0x0000000000000000000000000000000000000000", amount: "10000000000000000", // 0.01 ETH user: "WALLET_ADDRESS", recipient: "RECIPIENT_ADDRESS", }, false, { "x-api-key": process.env.RELAY_API_KEY } ); ``` **Note** - Quotes are revalidated when being filled, clients should regularly fetch fresh quotes so that users are always submitting up to date quotes. See [Getting a quote](/how-it-works/the-relay-solver#getting-a-quote) for more information on how quotes work. # Adapters Source: https://docs.relay.link/references/relay-kit/sdk/adapters An introduction to adapters and how to use them ### What is an adapter anyway? An adapter is a function that takes parameters and returns an object that adheres to this [interface](https://github.com/reservoirprotocol/relay-sdk/blob/main/packages/sdk/src/types/AdaptedWallet.ts). Let's review the interface in depth: | Property | Description | Required | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **vmType** | A string representing a supported vmType (`evm` `svm` `bvm` `suivm` `tvm`) | ✅ | | **getChainId** | An async function that returns a chain id | ✅ | | **handleSignMessageStep** | An async function that given a signature step item and a step generates a signature | ✅ | | **handleSendTransactionStep** | An async function that given a chain id, a transaction step item and a step returns a transaction hash | ✅ | | **handleConfirmTransactionStep** | An async function that given a transaction hash, a chain id, an onReplaced function and an onCancelled function returns a promise with either an evm receipt or an svm receipt | ✅ | | **address** | An async function that returns the currently connected to address, an address that can sign messages and submit transactions | ✅ | | **switchChain** | An async function that given a chain id switches to that chain, either by prompting the user or automatically switching chains | ✅ | | **transport** | An optional [transport](https://viem.sh/docs/clients/intro.html) to use when making rpc calls. | ❌ | | **getBalance** | An optional method to override the default balance fetching logic for selected tokens in the ui kit. | ❌ | | **supportsAtomicBatch** | An optional async function that takes a chain ID and returns whether the wallet supports [EIP-5792's atomic batch capability](https://www.eip5792.xyz/introduction). EVM wallets only. | ❌ | | **handleBatchTransactionStep** | An optional async function that takes a chain ID and an array of transaction step items, returning a call bundle identifier for batch processing. Only available for EVM wallets that support atomic batching. | ❌ | | **isEOA** | An optional boolean that indicates if the wallet is an EOA (Externally Owned Account). This is used to determine if the wallet is an EOA or a contract account. | ❌ | ### What adapters are available out of the box? The following adapters are officially maintained and developed by the Relay team: [SVM Adapter](https://github.com/relayprotocol/relay-kit/tree/main/packages/relay-svm-wallet-adapter): This adapter gives the sdk the ability to submit transactions on SVM (Solana Virtual Machine) networks. It does not support signing messages and under the hood it uses the [solana web3 sdk](https://solana-foundation.github.io/solana-web3.js/). [Bitcoin Adapter](https://github.com/relayprotocol/relay-kit/blob/main/packages/relay-bitcoin-wallet-adapter): This adapter gives the sdk the ability to submit transaction to the Bitcoin blockchain. Under the hood it uses [bitcoinjs-lib](https://www.npmjs.com/package/bitcoinjs-lib) to sign and submit transactions. Note that bitcoin transactions have a long finalization period and unlike quicker vms are not polled in real time for confirmation. [Viem Adapter](https://github.com/relayprotocol/relay-kit/blob/main/packages/sdk/src/utils/viemWallet.ts): This adapter is the default one used when no adapter is provided. You can also import it yourself to convert any viem wallet client into an adapted wallet ready to be used in the sdk. [Ethers Adapter](https://github.com/relayprotocol/relay-kit/tree/main/packages/relay-ethers-wallet-adapter): If your application uses ethers then you'll need this adapter to use the SDK. You'll still need to install viem as that's required for polling transactions but you can pass in your ethers signer to submit transactions and sign messages. [Sui Adapter](https://github.com/relayprotocol/relay-kit/tree/main/packages/relay-sui-wallet-adapter): This adapter gives the sdk the ability to submit transactions to the Sui blockchain. Under the hood it uses the [@mysten/sui](https://www.npmjs.com/package/@mysten/sui) sdk. [Tron Adapter](https://github.com/relayprotocol/relay-kit/tree/main/packages/relay-tron-wallet-adapter): This adapter gives the sdk the ability to submit transactions to the Tron blockchain. Under the hood it uses the [tronweb](https://www.npmjs.com/package/tronweb) sdk. ### How can I use an adapter? You can either make your own adapter, as long as it adheres to the interface above or you can use one of the officials adapters developed and maintained by the Relay team. All of our sdk methods handle a viem wallet or adapted wallet. The viem wallet adapter is used by default when a [viem wallet](https://viem.sh/docs/clients/wallet.html) is passed in for convenience. Refer below for implementation details: ```typescript Solana theme={null} import { getClient, Execute, getQuote } from "@relayprotocol/relay-sdk"; import { adaptSolanaWallet } from '@relayprotocol/relay-solana-wallet-adapter' import { Connection, Keypair, clusterApiUrl, SystemProgram, Transaction } from '@solana/web3.js'; ... //In this example we are loading a keypair as the wallet, but if you have a connector like dynamic you can just fetch the connection from that library const wallet = Keypair.generate(); const connection = new Connection(clusterApiUrl('devnet'), 'confirmed'); const walletAddress = wallet.publicKey.toString() const adaptedWallet = adaptSolanaWallet( walletAddress, 792703809, // Chain ID that Relay uses to identify Solana connection, // The Solana web3.js Connection instance for interacting with the network connection.sendTransaction // Function to sign and send a transaction, returning a promise with a base58 encoded transaction signature ) const options = ... // define this based on getQuote options const quote = await getClient().actions.getQuote(options) getClient().actions.execute({ quote, wallet: adaptedWallet, onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { // custom handling }, ... }) ``` ```typescript Viem theme={null} import { getClient, Execute, getQuote, adaptViemWallet } from "@relayprotocol/relay-sdk"; import { createWalletClient } from 'wagmi' import { mainnet } from 'viem/chains' ... const walletClient = createWalletClient({ chain: mainnet, transport: custom(window.ethereum!) }) const options = ... // define this based on getQuote options const quote = await getClient().actions.getQuote(options) getClient().actions.execute({ quote, wallet: adaptViemWallet(walletClient), onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { // custom handling }, ... }) ``` ```typescript Bitcoin theme={null} import { getClient, Execute, getQuote } from "@relayprotocol/relay-sdk"; import { adaptBitcoinWallet } from '@relayprotocol/relay-bitcoin-wallet-adapter' import { createWalletClient } from 'wagmi' import { mainnet } from 'viem/chains' ... const walletClient = createWalletClient({ chain: mainnet, transport: custom(window.ethereum!) }) const options = ... //define this based on getQuote options const quote = await getClient().actions.getQuote(options) const adaptedWallet = adaptBitcoinWallet( primaryWallet.address, async (_address, _psbt, dynamicParams) => { try { // Request the wallet to sign the PSBT (this is using dynamic but you could use whatever framework you want) const response = await primaryWallet.signPsbt(dynamicParams) if (!response) { throw 'Missing psbt response' } return response.signedPsbt } catch (e) { throw e } } ) getClient().actions.execute({ quote, wallet: adaptedWallet, onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { //custom handling }, ... }) ``` ```typescript Ethers theme={null} import { getClient } from '@relayprotocol/relay-sdk' import { adaptEthersSigner } from '@relayprotocol/relay-ethers-wallet-adapter' import { useSigner } from 'wagmi' ... const { data: signer } = useSigner() const adaptedWallet = adaptEthersSigner(signer) const options = ... //define this based on getQuote options const quote = await getClient().actions.getQuote(options) getClient().actions.execute({ quote, wallet: adaptedWallet, onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { //custom handling }, ... }) ``` ```typescript Sui theme={null} import { getFullnodeUrl, SuiClient } from '@mysten/sui/client' import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519' import { getClient, Execute, getQuote } from "@relayprotocol/relay-sdk" import { adaptSuiWallet } from '@relayprotocol/relay-sui-wallet-adapter' // Create a connection to Sui mainnet const rpcUrl = getFullnodeUrl('mainnet') const suiClient = new SuiClient({ url: rpcUrl }) const keypair = new Ed25519Keypair() const walletAddress = keypair.toSuiAddress() const adaptedWallet = adaptSuiWallet( walletAddress, 103665049, //chain id that Relay uses to identify Sui suiClient, async (tx) => { const result = await suiClient.signAndExecuteTransaction({ transaction: tx, signer: keypair, requestType: 'WaitForLocalExecution', options: { showEffects: true, } }); return result } ) const options = ... //define this based on getQuote options const quote = await getClient().actions.getQuote(options) getClient().actions.execute({ quote, wallet: adaptedWallet, onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { //custom handling }, ... }) ``` ```typescript Tron theme={null} import { TronWallet } from 'tronweb' import { getClient, Execute, getQuote } from "@relayprotocol/relay-sdk" import { adaptTronWallet } from '@relayprotocol/relay-tron-wallet-adapter' // Create a TronWeb instance, we recommend using a provider like dynamiclabs if using this on the frontend // Note: you should never share your private key with anyone or use on the frontend const tronWeb = new TronWeb({fullHost: 'xxx', privateKey: 'privateKey'}); const walletAddress = tronWeb.defaultAddress.base58 if (!tronWeb) { throw 'Unable to setup Tron wallet' } const adaptedWallet = adaptTronWallet( walletAddress, tronWeb ) const options = ... //define this based on getQuote options const quote = await getClient().actions.getQuote(options) getClient().actions.execute({ quote, wallet: adaptedWallet, onProgress: ({steps, fees, breakdown, currentStep, currentStepItem, txHashes, details}) => { //custom handling }, ... }) ``` # Typescript API Typings Source: https://docs.relay.link/references/relay-kit/sdk/api-types Typescript API Typings Typescript Types that facilitate API interaction The Relay SDK comes with built in API types to facilitate interacting with Relay APIs. The API types are useful for parsing API responses and determining what parameters an api accepts. Note that the types are exposed as part of the Relay SDK package, leaving where and how you use it up to you. There's no requirement for React, or anything else, except for Typescript. Below we'll dig into some examples: ```typescript theme={null} import { paths } from '@relayprotocol/relay-sdk' const parameters: paths['/execute/call']['post']['requestBody']['content']['application/json'] = { ... } ``` With the Typescript types imported you can easily discover what query parameters an API might allow or what response an API will return. # Chain Utils Source: https://docs.relay.link/references/relay-kit/sdk/chain-utils Utility functions for configuring chains in the Relay SDK ## Overview The `/chain-utils` subpath export contains utilities for dynamically configuring chains in your application. These functions were moved to a separate entry point to enable better tree-shaking and reduce the default SDK bundle size. ## Installation The chain utilities are included in the main SDK package. Simply import from the `/chain-utils` subpath: ```typescript theme={null} import { configureViemChain, configureDynamicChains } from '@relayprotocol/relay-sdk/chain-utils' ``` ## Functions ### configureViemChain Configures a single viem chain for use with the Relay SDK. ```typescript theme={null} import { configureViemChain } from '@relayprotocol/relay-sdk/chain-utils' import { arbitrum } from 'viem/chains' const relayChain = configureViemChain(arbitrum) ``` | Parameter | Type | Description | | --------- | ------- | ------------------------------------------ | | **chain** | `Chain` | A viem chain object to configure for Relay | **Returns:** A configured Relay chain object ready to use with the SDK. *** ### configureDynamicChains Fetches supported chains from the Relay API and configures them dynamically in the SDK. This is useful for applications that want to support all available chains without hardcoding them. ```typescript theme={null} import { configureDynamicChains } from '@relayprotocol/relay-sdk/chain-utils' const chains = await configureDynamicChains() // Returns an array of configured chains with viemChain properties ``` **Returns:** `Promise` - An array of Relay chain objects, each containing a `viemChain` property for use with wagmi/viem. # createClient Source: https://docs.relay.link/references/relay-kit/sdk/createClient This method is used to create the Relay client globally in your application. The client can then be retrieved with the `getClient` method. ## Options | Property | Description | Required | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | | **baseApiUrl** | The base API url to use when making requests. You can use the `MAINNET_RELAY_API` or the `TESTNET_RELAY_API` constants exported by the package, or pass in your own proxy API url to protect your API key on the client. | ✅ | | **apiKey** | A Relay API key to use when making requests. Only pass in this property when the Relay client instance is used exclusively server-side. Otherwise you'll leak your key to the client. | ❌ | | **chains** | A list of Relay Chains, by default a list of backup chains is configured | ❌ | | **source** | The source to associate your onchain activity with, should map to a domain | ❌ | | **logLevel** | The level at which to log, refer to the `LogLevel` enum exported by the package | ❌ | | **pollingInterval** | How often to poll status and check apis, this value is in milliseconds | ❌ | | **maxPollingAttemptsBeforeTimeout** | The source to associate your onchain activity with, should be set to a domain | ❌ | ```typescript theme={null} import { createClient, convertViemChainToRelayChain, MAINNET_RELAY_API, } from "@relayprotocol/relay-sdk"; import { mainnet } from "viem/chains"; createClient({ baseApiUrl: MAINNET_RELAY_API, source: "YOUR.SOURCE", chains: [convertViemChainToRelayChain(mainnet)], }); ``` # Installation Source: https://docs.relay.link/references/relay-kit/sdk/installation Setup the Relay SDK in your application ## Installation Install the [Relay SDK](https://github.com/relayprotocol/relay-kit) and its peer dependency [viem](https://viem.sh/) ```shell yarn theme={null} yarn add @relayprotocol/relay-sdk viem ``` ```shell npm theme={null} npm install --save @relayprotocol/relay-sdk viem ``` ```shell pnpm theme={null} pnpm add @relayprotocol/relay-sdk viem ``` ```shell bun theme={null} bun add @relayprotocol/relay-sdk viem ``` #### Environment Requirements: node 18+ typescript ^5.0.4 (if using typescript) ## Configuration To configure the SDK we first need to create a global instance of a RelayClient: ```typescript theme={null} import { createClient, convertViemChainToRelayChain, MAINNET_RELAY_API, TESTNET_RELAY_API } from '@relayprotocol/relay-sdk' import { mainnet } from 'viem/chains' createClient({ baseApiUrl: MAINNET_RELAY_API, source: "YOUR.SOURCE", chains: [convertViemChainToRelayChain(mainnet)] }); ``` You can replace the `baseApiUrl` with `TESTNET_RELAY_API` when testing with testnets. In the example above we also pass in an array of RelayChains converted from a viem chain, the sdk exports a function (`convertViemChainToRelayChain`) to easily do this. [Learn more](/references/relay-kit/sdk/createClient) about the `createClient` options. Calling `createClient` creates a singleton instance that will be used throughout your application. ### (Optional) Dynamically Configure Chains: The sdk provides a utility function for fetching the supported chains dynamically and configuring the SDK. Below is an example of how you could set that up: ```tsx theme={null} import type { AppProps } from "next/app"; import React, { ReactNode, FC, useState, useEffect } from "react"; import { RainbowKitProvider, getDefaultWallets } from "@rainbow-me/rainbowkit"; import { WagmiConfig, createConfig, configureChains, Chain } from "wagmi"; import { publicProvider } from "wagmi/providers/public"; import { alchemyProvider } from "wagmi/providers/alchemy"; import { configureDynamicChains } from "@relayprotocol/relay-sdk/chain-utils"; import { RainbowKitChain } from "@rainbow-me/rainbowkit/dist/components/RainbowKitProvider/RainbowKitChainContext"; const AppWrapper: FC = ({ children }) => { const [wagmiConfig, setWagmiConfig] = useState< ReturnType["wagmiConfig"] | undefined >(); const [chains, setChains] = useState([]); useEffect(() => { configureDynamicChains() .then((newChains) => { const { wagmiConfig, chains } = createWagmiConfig( newChains.map(({ viemChain }) => viemChain as Chain) ); setWagmiConfig(wagmiConfig); setChains(chains); }) .catch((e) => { console.error(e); const { wagmiConfig, chains } = createWagmiConfig( relayClient.chains.map(({ viemChain }) => viemChain as Chain) ); setWagmiConfig(wagmiConfig); setChains(chains); }); }, []); if (!wagmiConfig) { return null; } return ( {children} ); }; function createWagmiConfig(dynamicChains: Chain[]) { const { chains, publicClient } = configureChains(dynamicChains, [ alchemyProvider({ apiKey: ALCHEMY_KEY }), publicProvider(), ]); const { connectors } = getDefaultWallets({ appName: "Relay Demo", projectId: WALLET_CONNECT_PROJECT_ID, chains, }); const wagmiConfig = createConfig({ autoConnect: true, connectors, publicClient, }); return { wagmiConfig, chains, }; } ``` The above example demonstrates how to setup dynamic chains in a react application. At the start of your application you would basically configure the dynamic chains in the SDK by using the `configureDynamicChains` method. Then with the new chains in the promise you would use the `viemChain` property to create the wagmi config (refer to the [wagmi docs](https://1.x.wagmi.sh/react/getting-started#create-a-wagmi-config) for this). Finally take the resulting chains and configure the `RainbowKitProvider`. Although in this example we used RainbowKit + Wagmi, you're free to choose whatever libraries you'd like to connect a users wallet. You can call this method as often as you like to refresh the SDK's chains. # Installation Source: https://docs.relay.link/references/relay-kit/ui/installation Installing and Configuring RelayKit UI ## Installation Use this React ui package to smoothly embed a fully featured Relay powered interface into your application. Start by installing the required packages: ```shell yarn theme={null} yarn add viem wagmi @tanstack/react-query @relayprotocol/relay-kit-ui ``` ```shell npm theme={null} npm install viem wagmi @tanstack/react-query @relayprotocol/relay-kit-ui ``` ```shell pnpm theme={null} pnpm add viem wagmi @tanstack/react-query @relayprotocol/relay-kit-ui ``` ```shell bun theme={null} bun add viem wagmi @tanstack/react-query @relayprotocol/relay-kit-ui ``` If using typescript ensure that you're on v5+. Refer to the package json for the latest version requirements for the peer dependencies. ### Tanstack Query Setup The hooks require [TanStack Query](https://tanstack.com/query/latest) to be installed and configured. Refer to the [Tanstack installation instructions](https://tanstack.com/query/latest/docs/framework/react/installation). ## Configuration Once all dependencies are installed you can now configure and wrap your application with the `RelayKitProvider`. You'll need to also wrap your application with the QueryClientProvider and the WagmiProvider. Note the order in the snippet below. ```tsx theme={null} import { RelayKitProvider } from '@relayprotocol/relay-kit-ui' import { convertViemChainToRelayChain } from '@relayprotocol/relay-sdk' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { http, createConfig, WagmiProvider } from '@wagmi/core' import { mainnet } from '@wagmi/core/chains' import { MAINNET_RELAY_API, TESTNET_RELAY_API } from '@relayprotocol/relay-sdk' import '@relayprotocol/relay-kit-ui/styles.css' const queryClient = new QueryClient() const chains = [convertViemChainToRelayChain(mainnet)] const wagmiConfig = createConfig({ appName: 'Relay Demo', chains: [mainnet], transports: { [mainnet.id]: http(), } }) const App = () => { return ( ) } ``` If you're using the `convertViemChainToRelayChain` you'll need to install and import the `@relayprotocol/relay-sdk` to use this function. ### Styling Make sure to import the styles.css globally otherwise the components will be unstyled: ```tsx theme={null} import '@relayprotocol/relay-kit-ui/styles.css' ``` ### Configuring Chains Dynamically While you can easily supply chains that your application supports, you may want to fetch the supported Relay chains dynamically and configure them in your application. This can be done by using the [useRelayChains](/references/relay-kit/hooks/useRelayChains) hook. ```typescript theme={null} import { useRelayChains } from '@relayprotocol/relay-kit-hooks' import { RelayKitProvider } from '@relayprotocol/relay-kit-ui' import { convertViemChainToRelayChain } from '@relayprotocol/relay-sdk' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { http, createConfig, WagmiProvider } from '@wagmi/core' import { mainnet } from '@wagmi/core/chains' import { MAINNET_RELAY_API, TESTNET_RELAY_API } from '@relayprotocol/relay-sdk' const queryClient = new QueryClient() const App = () => { const [wagmiConfig, setWagmiConfig] = useState< ReturnType | undefined >() const { chains, viemChains } = useRelayChains(MAINNET_RELAY_API) useEffect(() => { if (!wagmiConfig && chains && viemChains) { setWagmiConfig( createConfig({ appName: 'Relay Demo', chains: (viemChains && viemChains.length === 0 ? [mainnet] : viemChains) as [Chain, ...Chain[]], transports: { [mainnet.id]: http(), } }) ) } }, [chains]) //Prevent loading the page until wagmi config is set, you can set a temporary config to swap out later to unblock the ui if (!wagmiConfig) { return null } return ( ) } ``` Learn more about the `RelayKitProvider` [options](/references/relay-kit/ui/relay-kit-provider). ## Review Let's review with a checklist to make sure we got everything in: 1. Install the required dependencies 2. Configure Tanstack Query and Wagmi 3. Configure `RelayKitProvider` with chains and options 4. Import the `styles.css` file to style our ui components # RelayKitProvider Source: https://docs.relay.link/references/relay-kit/ui/relay-kit-provider The provider to setup RelayKit UI ## Parameters | Parameter | Description | Required | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **options** | An object representing options that customize the underlying RelayKit SDK and RelayKit UI. A full list of options can be found below. | ✅ | | **theme** | A theme object representing overrides for the default styles | ❌ | ## Options | Option | Description | Required | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | duneConfig.apiKey | This key is used to fetch token balances to improve the general user experience and suggest relevant tokens to the user. If omitted the flow will continue to work but balances may be missing from the token selection flow. Refer to the [Dune docs](https://docs.dune.com/api-reference/overview/authentication#generate-an-api-key) on how to get an API key. | ❌ | | duneConfig.apiBaseUrl | If you prefer to obfuscate and protect your api key, you can pass in a custom endpoint which will be used to fetch the data. In your proxy you can then add the api key and do any additional checks before sending to the dune api. | ❌ | | disablePoweredByReservoir | A boolean which disables the powered by Reservoir footer | ❌ | | appName | The name of your application, used in the ui to contextualize actions | ❌ | | appFees | An array of objects representing fees. Each object should contains a `recipient` wallet address and a `fee` in bps (e.g. 100 = 1%). | ❌ | | themeScheme | The overall theme color of the app, can be `light` or `dark`. Defaults to `light`. This changes the color scheme of icons and other ui elements, in addition to the theme passed in. | ❌ | The options are also a combination of all the [parameters](/references/relay-kit/sdk/createClient#options) passed to the RelayKit SDK. An sdk instance with these options is created when using this provider. # SwapWidget Source: https://docs.relay.link/references/relay-kit/ui/swap-widget Swap Cross-Chain Instantly SwapWidget ## Parameters | Parameter | Description | Required | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | **supportedWalletVMs** | A required array of VMs that the swap widget should support. If a VM is selected that's not listed it will fall back to the (deposit address)\[[https://support.relay.link/en/articles/10269920-how-do-deposit-addresses-work](https://support.relay.link/en/articles/10269920-how-do-deposit-addresses-work)] ux. When setting this array make sure that the VMs are connectible by your wallet connection provider (dynamic, rainbow, privy, etc). | ✅ | | **fromToken** | The token to swap from. If provided, `setFromToken` must also be provided. | ❌ | | **setFromToken** | A function to update the from token. Required when `fromToken` is provided. | ❌ (✅ if fromToken is provided) | | **toToken** | The token to swap to. If provided, `setToToken` must also be provided. | ❌ | | **setToToken** | A function to update the to token. Required when `toToken` is provided. | ❌ (✅ if toToken is provided) | | **lockFromToken** | Disables selecting a from token in the ui. A `fromToken` is required in order for this setting to be applied. | ❌ | | **lockToToken** | Disables selecting a to token in the ui. A `toToken` is required in order for this setting to be applied. | ❌ | | **onFromTokenChange** | A callback triggered when the user changes the from token | ❌ | | **onToTokenChange** | A callback triggered when the user changes the to token | ❌ | | **lockChainId** | If set, locks the chain selection to the specified chain ID. Requires default tokens to be configured for this to work properly. | ❌ | | **wallet** | An [AdaptedWallet](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/sdk/src/types/AdaptedWallet.ts#L13) object representing the user's wallet. While this is not required as it's automatically pulled from wagmi for EVM chains, it's recommended to always provide this if possible. | ❌ | | **multiWalletSupportEnabled** | A boolean indicating whether multi-wallet (evm/svm) support is enabled. | ❌ | | **linkedWallets** | An array of [LinkedWallet](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/ui/src/types/index.ts#L14) objects when multi-wallet support is enabled. | ❌ (✅ if multiWalletSupportEnabled is true) | | **onSetPrimaryWallet** | A callback function to set the primary wallet when multi-wallet support is enabled. | ❌ (✅ if multiWalletSupportEnabled is true) | | **onLinkNewWallet** | A callback function to link a new wallet when multi-wallet support is enabled. | ❌ (✅ if multiWalletSupportEnabled is true) | | **defaultToAddress** | A wallet address to send the proceeds to. | ❌ | | **defaultAmount** | The default amount to swap, the amount is in the from token currency. | ❌ | | **defaultTradeType** | This can either be `EXACT_INPUT` or `EXACT_OUTPUT`, each of these refers to prefilling the output amount or the input amount and having the quote return the other side. | ❌ | | **slippageTolerance** | Slippage tolerance for the swap, if not specified then the slippage tolerance is automatically calculated to avoid front-running. This value is in basis points (1/100th of a percent), e.g. 50 for 0.5% slippage. | ❌ | | **disableInputAutoFocus** | Prevents the input field from automatically focusing when the widget loads. | ❌ | | **popularChainIds** | An array of chain IDs to override the 'Popular chains' list in the token selector. | ❌ | | **disablePasteWalletAddressOption** | Disables the ability to paste wallet addresses in the multi-wallet dropdown. | ❌ | | **singleChainMode** | When true, restricts swaps to occur only within the same chain. Works in conjunction with lockChainId. | ❌ | | **onConnectWallet** | A callback to connect the user's wallet. The widget can be used to fetch quotes for unauthenticated sessions but executing the quote requires an authenticated session. | ✅ | | **onAnalyticEvent** | A callback which sends useful [events](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/ui/src/constants/events.ts) to pipe into your existing analytics integration. | ❌ | | **onSwapValidating** | A callback that returns the quote object along with executable steps while validating the swap | ❌ | | **onSwapSuccess** | A callback that returns the quote object along with executable steps when the swap is complete | ❌ | | **onSwapError** | A callback that returns any errors that occur during the swap process. | ❌ | ## Usage ```tsx theme={null} import { SwapWidget } from '@relayprotocol/relay-kit-ui' import { useConnectModal } from '@rainbow-me/rainbowkit' import { useState } from 'react' import { useWalletClient } from 'wagmi' export default function MyComponent() { const { openConnectModal } = useConnectModal() const [fromToken, setFromToken] = useState({ chainId: 8453, address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', decimals: 6, name: 'USDC', symbol: 'USDC', logoURI: 'https://ethereum-optimism.github.io/data/USDC/logo.png' }) const [toToken, setToToken] = useState({ chainId: 10, address: '0x7f5c764cbc14f9669b88837ca1490cca17c31607', decimals: 6, name: 'USDC', symbol: 'USDC', logoURI: 'https://ethereum-optimism.github.io/data/USDC/logo.png' }) const walletClient = useWalletClient() return ( { console.log('Analytic Event', eventName, data) }} /> ) } ``` ## Slippage Configuration The `SlippageToleranceConfig` component provides a user interface for configuring slippage tolerance in basis points. This component handles all the complexity of managing slippage values including debouncing, input validation, and conversion between percentage and basis points. ### Parameters | Parameter | Description | Required | | -------------------- | -------------------------------------------------------- | -------- | | setSlippageTolerance | Callback function to update the slippage tolerance value | ✅ | | onAnalyticEvent | Optional callback for analytics events | ❌ | ### Usage ```tsx theme={null} import { SwapWidget, SlippageToleranceConfig } from '@relayprotocol/relay-kit-ui' import { useState } from 'react' export default function SwapPage() { const [slippageTolerance, setSlippageTolerance] = useState(undefined) return (
{ console.log('Analytic Event', eventName, data) }} />
) } ``` # Theming Source: https://docs.relay.link/references/relay-kit/ui/theming Customize the ui theme to fit your app ## Creating a Theme RelayKit UI can be fully themed using an intuitive set of theme tokens. You can choose to override all tokens or just a few to make the components match your application theme. You can view the full list of theme overrides [here](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/ui/src/themes/RelayKitTheme.ts#L10). ```tsx theme={null} import { RelayKitTheme } from '@relayprotocol/relay-kit-ui' const theme: RelayKitTheme = { font: "Inter", primaryColor: "#09F9E9" focusColor: "#08DECF" text: { default: "#000" subtle: "#fff" } buttons: { primary: { color: "#000" background: "#09F9E9", hover: { color: "#000" background: "#0ad1c4", } } } } ``` ### Fonts If you are overriding any fonts with custom fonts you need to configure the custom font in your application. For example if you're using nextjs you can refer to this [doc](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) or if you're pulling the fonts in via css you can refer to this [doc](https://www.w3schools.com/css/css3_fonts.asp). ### Light/Dark Mode RelayKit UI has built-in support for light and dark mode themes. The components will automatically adapt their appearance based on the theme mode you specify. To control the theme mode, simply add the `light` or `dark` class to your HTML container element: ```html theme={null} ... ... ``` ## Usage Now that your theme is configured you'll just need to pass it into the `RelayKitProvider`. ```tsx theme={null} import { RelayKitProvider, RelayKitTheme } from '@relayprotocol/relay-kit-ui' const theme: RelayKitTheme = { ... } //Previously created theme ... {YOUR_APP} ``` # Troubleshooting Source: https://docs.relay.link/references/relay-kit/ui/troubleshooting Having trouble? Start here ### WagmiProviderNotFoundError: `useConfig` must be used within `WagmiProvider` This issue is usually the result of multiple wagmi versions, make sure you only have 1 version of wagmi installed. You may also want to delete and reinstall your node modules and clear all caches. ### No QueryClient set, use QueryClientProvider to set one If you've set the QueryClientProvider with a QueryClient it could be that you have multiple versions of tanstack/react-query installed. Make sure to double check and reduce this to just a single version of the package. You may also want to delete and reinstall your node modules and clear all caches. ### The UI looks unstyled Make sure you've imported the ui package styles: ```typescript theme={null} import '@relayprotocol/relay-kit-ui/styles.css' ``` If the issue persists make sure there's no reset css or other css file overriding the styles provided by the package. ### Incompatibility If you see an error message that looks like this: ``` The types of 'utils.adaptViemWallet' are incompatible between these types ``` It could be that multiple versions of peer dependencies are installed. Review your package lock file for duplicate dependencies. ### No wallet found If you see an error message that looks like this: ``` Missing a valid wallet ``` or ``` Missing a wallet ``` It could be that you're not providing a valid wallet to the SwapWidget or that you're app is not wrapped in a WagmiProvider. Make sure to provide a wallet to the SwapWidget or double check that your SwapWidget is wrapped by a WagmiProvider. It could also mean that you have two versions of wagmi/viem installed. Make sure that you only have 1 version of wagmi/viem installed as multiple versions can cause issues. # Intent Status Source: https://docs.relay.link/references/websockets/intent-status # Brand Assets Source: https://docs.relay.link/resources/brand-assets Logos, guidelines, and assets for presenting the Relay brand clearly and consistently Guidelines and downloadable assets for using the Relay brand across products, marketing, and communications. [Download All Brand Assets](https://drive.google.com/drive/folders/1cI223F-K1IR6Vumy7xJymLOkEbyyD0r5?dmr=1\&ec=wgc-drive-hero-goto) [Relay Brand Guidelines](https://www.figma.com/proto/6Qbgd0xJHFayzrQoeN15Mn/Design-System--New-?page-id=870%3A2723\&node-id=870-3013\&viewport=434%2C-70%2C0.18\&t=Vjai7d5ZFxenfwjG-1\&scaling=contain\&content-scaling=fixed) # Integrating using AI Source: https://docs.relay.link/resources/developing-with-ai Use AI coding assistants to build with Relay faster Relay documentation is optimized for AI coding assistants. Whether you're using Claude, ChatGPT, Cursor, GitHub Copilot, or other AI tools, you can reference our docs directly to get accurate, up-to-date information about the Relay API and SDK. ## LLM-Optimized Documentation ### llms.txt Files Relay docs support the [llms.txt standard](https://llmstxt.org/), making our documentation easily accessible to AI tools: | File | URL | Description | | ----------------- | --------------------------------------- | -------------------------------------------- | | **llms.txt** | `https://docs.relay.link/llms.txt` | Navigation structure with page summaries | | **llms-full.txt** | `https://docs.relay.link/llms-full.txt` | Complete documentation content in plain text | These files are automatically kept in sync with our documentation and provide a clean, token-efficient format for LLMs. ### Copy a Single Page Copy the content of any documentation page in a clean, AI-friendly format. Click the **Copy page** button at the top of any docs page to copy its contents, then paste it directly into your AI assistant's context window. This is useful when you need focused context about a specific API endpoint or feature without providing the entire documentation. ## Using Cursor Provide Relay documentation directly to your AI coding assistant for more accurate, context-aware code generation. ### MCP Server Install the Relay docs MCP server for rich, queryable access to our documentation directly from Cursor, Claude Desktop, Windsurf, or any MCP-compatible client. **One-click install for Cursor:** Install MCP Server in Cursor **Manual install via CLI:** ```bash theme={null} npx @mintlify/mcp add docs.relay.link ``` Start the server after installation: ```bash theme={null} npm --prefix ~/.mcp/docs.relay.link start ``` The MCP server gives your AI assistant queryable access to the full Relay documentation, including API references and SDK guides. ### Cursor @Docs Add Relay's documentation as a context source in Cursor: 1. Open Cursor Settings → Features → Docs 2. Add a new documentation source with the URL `https://docs.relay.link` 3. Reference Relay docs in any Cursor chat by typing `@Docs` and selecting Relay This keeps your AI assistant up to date with the latest Relay APIs without manually copying documentation. ### Cursor Rules Set up Relay-specific rules in your project to ensure consistent AI-generated code. Create a `.cursor/rules/relay.mdc` file in your project root: ```text theme={null} --- description: Rules for building with Relay Protocol globs: --- - Use `@relayprotocol/relay-sdk` for server-side integrations - Use `@relayprotocol/relay-kit-ui` for React UI components - Always wrap components in `RelayKitProvider`, `WagmiProvider`, and `QueryClientProvider` - Use `MAINNET_RELAY_API` or `TESTNET_RELAY_API` for the base API URL - Only use API keys (`x-api-key` header) in server-side code, never expose them client-side - Use TypeScript for all Relay integrations - Refer to the Relay docs at https://docs.relay.link for the latest API reference ``` ## Using Claude Claude works well with Relay documentation out of the box. Here are a few ways to get the most out of it. ### Claude Projects Create a dedicated Claude Project for Relay development. Add `https://docs.relay.link/llms-full.txt` as project knowledge so Claude has the full documentation available in every conversation. 1. Create a new project in [Claude](https://claude.ai) 2. Add the `llms-full.txt` URL as a knowledge source 3. Set custom instructions like "Use the Relay documentation to answer questions about the Relay SDK and API" ### Claude Code Use Claude Code with the Relay MCP server for context-aware assistance directly in your terminal. Add the following to your `.mcp.json`: ```json theme={null} { "mcpServers": { "relay-docs": { "url": "https://docs.relay.link/mcp" } } } ``` ### Prompting Tips When prompting Claude (or any LLM) about Relay, keep these tips in mind: * **Specify the package** — Tell the model whether you're using the SDK (`@relayprotocol/relay-sdk`), UI Kit (`@relayprotocol/relay-kit-ui`), or Hooks (`@relayprotocol/relay-kit-hooks`) so it generates the right imports and patterns. * **Include chain context** — Mention the origin and destination chains (e.g. "bridge from Ethereum to Base") to get accurate `chainId` values and currency addresses. * **Reference the action** — Be explicit about whether you're getting a quote, executing a bridge, or checking status. Each uses a different SDK method and endpoint. * **Paste the relevant doc page** — Use the **Copy page** button on the specific docs page you're working with and paste it into the conversation for the most accurate results. # Enterprise Partner Program Source: https://docs.relay.link/resources/enterprise Learn how to become an Enterprise Partner ## What is an Enterprise Partner? As an Enterprise Partner, we will work with your team to craft a custom contract to support your use case and you will be granted access to the following exclusive features: * [Fee Sponsorship](/features/fee-sponsorship) * [Fast Fill](/features/fast-fill) * Custom SLAs * Priority Support * Increased Rate Limits * Revenue Share ## How to Become an Enterprise Partner To become an Enterprise Partner, your team must meet the following criteria: * **Volume:** Current or projected daily processing volume of \$500,000 or more. * **Application:** Submission of the [Enterprise Partnership Program form](https://forms.gle/XNeELYavjwoJwPEf7). Our team will review your application and reach out with next steps if considered. # Support Source: https://docs.relay.link/resources/support # Supported Chains Source: https://docs.relay.link/resources/supported-chains # Bounties Source: https://docs.relay.link/security/bounties Learn how Relay rewards developers who find & report vulnerabilities. We at Relay are committed to maintaining the highest security standards for our protocol and smart contracts. That's why we are launching a bug bounty program for those who help us identify vulnerabilities in the Relay Protocol. With our bounty, we hope to incentivize ethical hackers to discover and report vulnerabilities in our contracts and protocol. Rewards will be dependent on the severity of the bug. We always provide monetary rewards for bugs that fall under these categories: * Extract value from the solver * Exploiting our smart contracts in an unintended way that results in a loss of funds To be eligible for the monetary rewards, this bug must be investigated and confirmed by our team. Monetary reward amounts are 10% of the value at risk, with a maximum reward capped at \$100,000 USD. "Value at risk" is defined as the total value that could be extracted within a 10 minute period. This is because our product has automatic circuit breakers and alarms that would prevent losses that take longer to extract. ### Reward Process & Requirements To receive a bounty reward, the following requirements apply: * **KYC & Background Check**: A KYC (Know Your Customer) verification and background check are required before any reward can be issued. * **Payment**: Bug bounty rewards are issued in cryptocurrency. * **Tax Documentation**: Any tax-related documentation requirements (e.g., W-8BEN, W-9, or equivalent forms depending on jurisdiction) will be addressed as part of the KYC process. If you suspect you've encountered a bug, please reach out directly by email: [support@relay.link](emailto:support@relay.link). We review all reports closely with the team. We will get back to you within 1 to 4 days regarding the next steps. Most responses are provided within 24 hours, barring holidays and weekends. # Community Support Source: https://docs.relay.link/security/community-support # Compliance Source: https://docs.relay.link/security/compliance Relay prioritizes the security and integrity of every transaction. To meet international regulatory standards, we enforce strict sanctions screening and blocklists to ensure compliance with OFAC and other global laws. ### How it works All transactions are screened in real time against: * **Third-Party Integrations**: Professional-grade sanctions and risk databases, including HackBounty and Chainalysis, used to identify addresses linked to malicious activity and ensure regulatory compliance. * **Relay’s Internal Blocklist**: Continuously maintained and updated by our compliance team based on emerging threats and intelligence. ### For Users If a transaction is flagged for involving a sanctioned address, it will be automatically blocked, and you’ll receive a “Detected sanctioned address” notification. This protects you and helps ensure full compliance with international financial regulations. Through robust third-party integrations and internal safeguards, Relay maintains a secure and compliant environment for all users. # Contract Audits Source: https://docs.relay.link/security/contract-audits # MEV protection Source: https://docs.relay.link/security/mev-protection Learn how Relay handles MEV protection. On Relay, all relayers submit destination chain transactions through MEV protection software, like MEV-blocker and merkle. This is important to protect Relay users from MEV. These protections are provided on all major networks with MEV, including Ethereum, BNB, Polygon, and Solana. If you are interested in learning more, feel free to [reach out](https://forms.gle/XNeELYavjwoJwPEf7) . # Chains Source: https://docs.relay.link/solutions/chains Get Instant Onboarding & Swaps on your Chain Relay is designed to make transacting across chains as fast, cheap, and reliable as online payments. Relay is designed for rapid chain expansion. For chains, getting Relay means: **[Instant Onboarding](/use-cases/bridging)** - Relay support of your chain means users can instantly onboard onto your chain from any supported network and currency. You can leverage the API to build your own onboarding experience or use our \[UI Kit] to quickly build onboarding for your app. **[Same and Cross-chain Swaps](/use-cases/cross-chain-swaps)** - Relay's [Any-to-Any Swaps](/use-cases/cross-chain-swaps) means that once your chain is supported, users across the Relay ecosystem will be able to do any-to-any swaps on your network. ## Relay Benefits **Chain Support** - Relay supports [85+ chains](/references/api/api_resources/supported-chains), offering coverage across all major EVM networks, alt L1s, SVMs and many appchains. We are designed to add chains on Day 1, and work closely with leading networks to make sure we are. If there is a network you are hoping we'll support, please reach out! **Industry Leading Quote & Fill Times** - For bridging, swaps, and cross-chain executions, we are industry leading in quote and fill times. Our product is optimized to reduce latency across the user experience, meaning fast quotes and fast fills. Our p50 fill time across chain is less than 3 seconds across all networks. **SLAs & Customer Support** - Relay is your partner in delivering a world-class multichain experience. We are happy to offer SLAs on quote and fill speed, network uptime, and more. **App Fees** - Our comprehensive approach to [App Fees](/features/app-fees) lets you easily earn when using Relay. Additionally, we have designed our fee system for you to automatically collect fees in stables. ## Getting Relay on your Chain If you want Relay on your chain, please [Reach Out](https://forms.gle/XNeELYavjwoJwPEf7)! # Commerce & Payments Source: https://docs.relay.link/solutions/commerce-and-payments Use Relay to facilitate seamless payments between users and merchants across any chain Relay is crosschain payments infrastructure that enables seamless payments between users and merchants. Users pay in any token on any chain. Merchants configure exactly which token they receive as settlement — any stablecoin, native asset, or token on any supported chain. One integration to facilitate it all — instant, transparent, and low-fee. Connect users on every major network P50 fill time across all routes Volume enabled to date Processed across the network ## Watch a Live Demo