# Changelog Source: https://docs.relay.link/changelog Record of new endpoints, breaking changes, deprecations, and default-behavior changes across Relay products ### Protocol **Behavior change** * On October 26, 2026 at 10 AM ET, Relay starts routing through the new contracts on all supported chains, and `contracts.erc20Router` and `contracts.approvalProxy` in Get Chains switch to them. Both pairs stay live during the transition, so if you validate contract addresses, allow both. Token approvals go to the new approval proxy, so a returning user approves once more ([migration notes](/references/api/api_resources/contract-addresses#migrating-to-the-new-router)). **Added** * Router 3.2 and a new approval proxy are deployed on all supported EVM chains. Most chains use router `0x0729e2a4716d9f7dd17858d5d504426d228697f7` and approval proxy `0x5567954cd026cebfe486062cb75848e0f1c12e58`; Cronos, Linea, Mantle and Metis use router `0x6328ea02d28131e58b28e55c6258258a30d90e05` and approval proxy `0xe4dfbd5e6784db1058124e2c87554c0591f0fb7b`. [Get Chains](/references/api/get-chains) lists the pair for each chain under `contracts["v3.2"]`. **Changed** * The router's `SolverCallExecuted` event logs a 32 byte hash of the call data instead of the call data. On the approval proxy, the Permit2 witness includes `msgValue`, `permitTransferAndMulticall` (ERC-2612) no longer has a nonce in its permit struct, and `permit3009TransferAndMulticall` takes an extra `multicallSignatures` argument, because ERC-3009 permits need a MulticallAuthorization signature that clients opt into with `useMulticallAuthorization`. Nothing changes for integrations that only use the transaction data returned by the quote API. ### API **Added** * `GET /metrics/usage`: the new optional `appId` query parameter filters usage by App — the durable identity for an integration, which persists across API key rotation. Accepts one or more `app_id_` values as a comma-separated list (OR-matched, trimmed, canonicalized to lowercase, deduplicated). Requires `x-dev-portal-key`; a plain `x-api-key` cannot use this filter and attempts return `403`. OR-matches with `apiKey` and `apiKeyId`, and the three filters share a combined cap of **50** values before Apps expand to their keys. Every supplied ID must belong to the authenticating integrator; malformed IDs return `400` and unknown or foreign IDs return `403` for the entire request. The filter resolves every current credential under an App — including inactive keys — and an owned App without credentials returns an empty `metrics` array. * `GET /metrics/usage`: each row in the response `metrics` array now includes a nullable **`appId`** alongside `apiKey` and `apiKeyId`, carrying the current App the key is attributed to. App attribution is resolved at read time, so hard-deleting a credential drops it from App totals and a reassigned credential attributes all of its retained usage to its current App. Retention windows are unchanged: 24h minutely, 7d hourly, 14d daily. See [API keys and Rate Limits](/references/api/api-keys). ### API **Breaking** * `POST /quote` and `POST /quote/v2`: the `details.fallbackType` field is removed from the response. It was only populated when a canonical+ fallback route served the quote; with that path removed, the field no longer appears on any response. **Deprecated** * `POST /quote`, `POST /quote/v2`, and `POST /price`: the `useExternalLiquidity` and `useFallbacks` request fields are now accepted but ignored. The canonical+ bridging path and the fallback-route path they selected have been removed from the quote engine. Both fields are marked `deprecated` in the request schema and can be dropped from your integration. Callers that previously sent `useExternalLiquidity: true` now receive a standard quote instead of a `400`. * `GET /config` and `GET /config/v2`: the per-chain-combination `supportsExternalLiquidity` field is marked `deprecated` and now always returns `false`. The field remains in the response shape for backwards compatibility — treat it as a constant and remove any branching on its value. ### API **Added** * [`GET /balance/{wallet}/events`](/references/api/get-balance-events): returns the history of balance movements for a wallet — fee credits, fee sponsorship payments and refunds, gasless refunds, fixed-rate settlement, fixed-BPS PnL, gas sponsorship, fast-fill holds and releases, deposits and withdrawals — each mapped to a `type` from a fixed enum. Supports filters for `type`, `requestId`, `requestIds` (at most 20 per call), `chainId`, `currency`, and a `startTimestamp`/`endTimestamp` range, with cursor pagination over the opaque `continuation` value. An API key is required. A wallet linked to your integrator returns its whole ledger, including `balanceAfter`; any other wallet returns the events your integrator caused on it, plus deposits and settled withdrawals. Events carry the `requestId` they belong to where one applies, which makes per-request fee sponsorship and fixed-rate settlement reconcilable. For current balances rather than history, use [`GET /app-fees/{wallet}/balances`](/references/api/get-app-fee-balances). * `GET /requests/v3`: the new optional `appId` query parameter filters by App — the durable identity for an integration, which persists across API key rotation and revocation. Accepts one or more `app_id_` values as a comma-separated list (OR-matched, whitespace trimmed, canonicalized to lowercase, deduplicated, capped at **50**). Every supplied ID must belong to the authenticating integrator; malformed IDs and empty entries return `400`, unknown or foreign IDs return `403`, and transient resolution failures return `500` rather than silently dropping the filter. `appId` is top-level only (not supported inside `filters` groups) and is ANDed with any `apiKeyId` / `apiKey` filter. Because the filter matches the attribution indexed at request time, results include requests created under keys that have since been revoked. * `GET /requests/v3`: response `data` object gains **`appId`**, the durable App identifier that owned the API key the request was created with. Returned only when the caller authenticates with an API key belonging to the request's owning integrator and passes `includeAuthenticatedData=true` — the same gate as `data.referrer`, `data.platformFee`, `data.apiKeyName`, and `data.blockedAddresses`. `null` on owned records that have no indexed App attribution. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#what-changed). ### RelayKit **SDK `8.0.4`, UI Kit `12.0.5`** — Patch * Block Lighter chains as an origin, add Lighter placeholder addresses, and resolve Lighter accounts against each Lighter chain's own API ([`48d611d`](https://github.com/relayprotocol/relay-kit/commit/48d611d)) **SDK `8.0.4`, UI Kit `12.0.5`** — Patch * Show an unsupported chain error instead of the locked-wallet message when a wallet rejects wallet\_addEthereumChain ([`aa83dcf`](https://github.com/relayprotocol/relay-kit/commit/aa83dcf)) **SDK `8.0.3`, UI Kit `12.0.4`** — Patch * block Solana token accounts in recipient field ([`18d40ba`](https://github.com/relayprotocol/relay-kit/commit/18d40ba)) **Bitcoin adapter `21.0.3`** — Patch * Fix Bitcoin swaps from Taproot (`bc1p`) addresses failing before broadcast. `adaptBitcoinWallet` ([`06036f8`](https://github.com/relayprotocol/relay-kit/commit/06036f8)) now initializes an ECC library before finalizing the signed PSBT, which resolves `No ECC Library provided`, and skips inputs the wallet already finalized, which resolves `Can not finalize input #0` from wallets that auto-finalize such as OKX. ### API **Behavior change** * `POST /quote/v2` with `useDepositAddress: true`, `strict: true`, and `tradeType: EXACT_INPUT`: the returned deposit address is now one-time use. The first deposit fills at market for any amount; additional deposits to the same address are refunded to `refundTo` instead of filling. Previously, `strict` was ignored for `EXACT_INPUT` on `/quote/v2` and the returned address could be reused across multiple deposits. `EXACT_OUTPUT` strict deposit addresses continue to be one-time use, as before. `refundTo` remains required for strict deposit addresses. See [Strict Deposit Addresses](/features/deposit-addresses#strict-deposit-addresses). ### API **Added** * `GET /requests/v3`: the `apiKeyId` query parameter now accepts the prefixed form `key_id_` in addition to a bare UUID, in any comma-separated combination. Both forms resolve to the same key, deduplicate against each other and against `apiKey`, and count once toward the 50-value cap. Malformed entries still return `400`. Response IDs are unchanged — the stable key identifier is still returned as a bare UUID. Prefer `apiKeyId` over `apiKey` in query parameters. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#what-changed). ### RelayKit **SDK `8.0.2`** — Patch * Surface the origin tx hash with a pending status for HyperCore origins as soon as the send lands on Hyperliquid, instead of waiting for the first Relay status that carries one. ([`3181078`](https://github.com/relayprotocol/relay-kit/commit/3181078)) **UI Kit `12.0.3`** — Patch * Display thousands separators in the Swap Widget amount inputs (e.g. `183,428.03` instead of `183428.03`). Separators are display-only; the underlying amount values are unchanged. ([`06043a4`](https://github.com/relayprotocol/relay-kit/commit/06043a4)) **UI Kit `12.0.3`** — Patch * Deposit-address flows now report the request id that owns the active lifecycle. `useDepositAddressStatus` returns a `requestId` alongside `status`, and the Swap Widget's deposit-address modal and the Onramp Widget use it for the transaction link and the `requestId` passed to render props. Previously these used the request id from the quote's deposit step, which is left permanently `pending` when the fill is regenerated against the same deposit address. Analytics `quote_id` and the MoonPay metadata post still reference the quote's request id. ([`47134a3`](https://github.com/relayprotocol/relay-kit/commit/47134a3)) **UI Kit `12.0.3`** — Patch * MAX now uses the full balance for non-native tokens. The execution and gas buffers only apply to native tokens. ([`17b0ef2`](https://github.com/relayprotocol/relay-kit/commit/17b0ef2)) **UI Kit `12.0.3`** — Patch * Fix the MoonPay checkout being cut off in the Onramp Widget. The embedded MoonPay iframe always fills its container and never reports its content height, so `overflowY: 'scroll'` on that container could never scroll — MoonPay's own layout is `overflow: hidden`, so anything past the 500px container was clipped. The widget now uses MoonPay's recommended 656px embedded height inside a scrollable wrapper that shrinks to fit the modal on short viewports. ([`8a4a40f`](https://github.com/relayprotocol/relay-kit/commit/8a4a40f)) **UI Kit `12.0.3`** — Patch * Add Robinhood chain to Phantom supported chains ([`d6344b7`](https://github.com/relayprotocol/relay-kit/commit/d6344b7)) **UI Kit `12.0.3`** — Patch * Remove the Porto wallet chain restrictions from the swap widget, since Ithaca has sunset Porto. The widget no longer treats `porto` or `portoevm` wallets as limited to a fixed set of chains. ([`8b4ada6`](https://github.com/relayprotocol/relay-kit/commit/8b4ada6)) **Hooks `5.1.0`** — Minor * Deposit-address flows now report the request id that owns the active lifecycle. `useDepositAddressStatus` returns a `requestId` alongside `status`, and the Swap Widget's deposit-address modal and the Onramp Widget use it for the transaction link and the `requestId` passed to render props. Previously these used the request id from the quote's deposit step, which is left permanently `pending` when the fill is regenerated against the same deposit address. Analytics `quote_id` and the MoonPay metadata post still reference the quote's request id. ([`47134a3`](https://github.com/relayprotocol/relay-kit/commit/47134a3)) ### API **Added** * `GET /requests/v3`: the new optional `apiKeyId` query parameter filters by API key UUID instead of by API key secret, keeping key material out of query strings, logs, and browser history. Accepts one UUID or a comma-separated list, OR-matches with `apiKey`, and can be combined with it. Every supplied ID must resolve to a key owned by the authenticating integrator; malformed UUIDs and empty entries return `400`, unknown or foreign IDs return `403`, and transient resolution failures return `503` rather than silently dropping the filter. Deduplicated values across `apiKey` and `apiKeyId` are capped at **50** — larger lists return `400` before any lookup. `referrer` filtering (top-level parameter or a `referrer` / `not:referrer` key inside `filters`) may now be paired with `apiKeyId` in place of `apiKey`. Retrieve the ID from the [Relay Dashboard](https://dashboard.relay.link) or the API key management endpoints — prefer `apiKeyId` over `apiKey` in query parameters. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#what-changed). ### API **Added** * `POST /quote` and `POST /quote/v2`: the new optional `forJitoBundle` boolean request field restricts Solana swap routing to routes that are compatible with [Jito](https://www.jito.wtf/) bundles. When set to `true`, aggregator quotes drop DEXes and instructions (such as those that touch vote accounts) that would cause a Jito bundle containing the transaction to be rejected. Only takes effect on Solana-origin routing and only when the caller sets it — the default (omitted / `false`) preserves existing route selection. Set it only when the returned transaction will actually be executed inside a Jito bundle, since it narrows the route search. See [Solana Support](/references/api/api_guides/solana). ### API **Added** * `POST /withdrawals/request`: user-triggered withdrawals to Bitcoin are supported again. The prepare response now returns `additionalData["bitcoin-vm"]` (`allocatorUtxos`, `feeUtxos`, `feeChangeAddress`, `feeRate`) alongside a `feeAuthorization` object (`validUntil` in unix seconds and a `signature`). Execute must pass the entire `additionalData` back verbatim within one hour of prepare. Tampered, missing, or expired authorizations are rejected with `400` and `errorCode: "BITCOIN_FEE_AUTHORIZATION_INVALID"`. A prepare retried in the same one-minute window with a changed `recipient` or `amount` returns `429`; a prepare that cannot be fulfilled by any relayer returns `503`. See [Programmatic Withdrawals](/references/protocol/guides/withdrawals#step-3-prepare). ### RelayKit **UI Kit `12.0.2`** — Patch * Use indicative quotes while the Swap Widget is previewing a route with placeholder sender or recipient addresses. Executable quotes are requested after valid wallet addresses are available. ([`a1f3a58`](https://github.com/relayprotocol/relay-kit/commit/a1f3a58)) ### API **Behavior change** * `GET /requests` and `GET /requests/v2`: the per-key rate limit is now half of what it was on both default and elevated keys. Default keys drop from 200 to **100 requests per minute**; elevated keys drop from 10 to **5 requests per second**. `/requests/v3` is unaffected — it keeps its dedicated bucket at 200 requests per minute (default) and 20 requests per second (elevated), and on default keys now has an explicitly separate limit from `/requests` (previously the two shared one bucket). Migrate polling and batch-fetch traffic to [`GET /requests/v3`](/references/api/get-requests) to stay under the new limits; see the [migration guide](/references/api/api_guides/migrating-to-requests-v3). See [Default Rate Limits](/references/api/api-keys#default-rate-limits) and [Elevated Rate Limits](/references/api/api-keys#elevated-rate-limits). ### API **Added** * `POST /quote`, `POST /quote/v2`, and `POST /price`: the response `errorCode` enum gains **`AMOUNT_TOO_HIGH`**, returned with `400` when the requested quote amount exceeds the per-pair maximum configured for the selected token pair. The response `message` is `Quote amount exceeds the maximum for this pair`. Reduce the requested amount (or split into smaller quotes) and retry. The same code surfaces on deposit-address create-quote requests (`POST /quote/v2` with `useDepositAddress: true`). See [Handling Quote Errors](/references/api/api_core_concepts/handling-errors). ### RelayKit **SDK `8.0.1`, UI Kit `12.0.1`, Tron adapter `10.0.1`** — Patch * Fix Tron swap UX in the TRX/USDT flow. Tron balances now read from TronGrid's ([`cbbf9c9`](https://github.com/relayprotocol/relay-kit/commit/cbbf9c9)) fullnode endpoints (`wallet/getaccount`, `wallet/triggerconstantcontract`) instead of the solidity node, so they reflect a completed swap without waiting roughly a minute for solidification or needing a manual refresh. `adaptTronWallet` confirms transactions with `getUnconfirmedTransactionInfo`, which returns the receipt seconds after inclusion, so a successful approval no longer hangs or reports a false "Transaction confirmation timed out". Same-chain swaps that the solver has to fill — Tron TRX/USDT, deposit-address routes, and forced solver execution — now show the cross-chain pending states and wait for the fill to be confirmed before reporting success. Previously any route whose origin and destination chain ids matched was assumed to settle with the user's own transaction. The new `isSolverFilledStep` export identifies these routes from the step the API returns. ### API **Added** * `GET /requests/v3`: for requests that failed with `failReason: "BLOCKED_WALLET"`, the response `data` object now includes `blockedAddresses`, an array of the wallet address(es) whose screening caused the failure. It is returned only when the caller authenticates with an API key belonging to the request's owning integrator and passes `includeAuthenticatedData=true` — the same gate as `data.referrer`, `data.platformFee`, and `data.apiKeyName`. Returns `null` on non-`BLOCKED_WALLET` failures and on rows where no blocked addresses were recorded. The field never carries screening-provider scores or reasons — addresses only. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#what-changed). ### RelayKit **SDK `8.0.0`, UI Kit `12.0.0`, Hooks `5.0.0`** — Major * `paths` and `routes` now track the current Relay API. Three endpoints are gone, request ([`c7ba781`](https://github.com/relayprotocol/relay-kit/commit/c7ba781)) amount fields are nullable, and deposit addresses, fee sponsorship and route racing gained new options. **Breaking** * `/execute/swap`, `/requests/{requestId}/signature` and `/requests/{requestId}/signature/v2` are removed from `paths` and from the exported `routes` array. Quotes come from `POST /quote/v2`. `SwapBreakdown` and `Execute['breakdown']` are unchanged — the SDK now declares that shape directly instead of deriving it from `/execute/swap`. * On `GET /requests/v2` and `GET /requests/v3`, amount-bearing fields are now `string | null` rather than `string | undefined`: `appFees[]` / `paidAppFees[]` (`recipient`, `bps`, `amount`, `amountUsd`, `amountUsdCurrent`), `data.fees.*` (`usd`, `amount`, `amountFormatted`) and `data.route.*` currency amounts (`amount`, `amountFormatted`, `amountUsd`, `minimumAmount`). This reaches `useRequests`, `queryRequests` and `useDepositAddressStatus`. Passing one of these into a `string | undefined` slot no longer compiles — use `?? undefined`, or widen the receiving type to accept `null`. * `ChainVM` gains `'hederavm'`. An exhaustive `switch` over `ChainVM` with a `never` fallthrough needs the new case. * `GET /config/v2` no longer accepts `currency: "degen"` or `"usdh"`, and `GET /prices/rates` no longer returns `DEGEN`. Remove references to those literals; `"ausd"` is now accepted. **Added** * `GET /deposit-addresses/v2` returns a deposit address with its `deposits` and `sweeps` history, and `POST /deposit-addresses/{depositAddressId}/disable/v2` disables one. `/quote` and `/quote/v2` responses now carry `requestId` and, on deposit-address steps, `depositAddressId`. * Quote request options: `subsidizationBps` (share of each sponsored fee component the sponsor covers, applied before `maxSubsidizationAmount`), `indicativeQuote`, `referencePrice`, `useRouteRacing` and `routeRacingMinUsdSize`. `subsidizationBps` is echoed back in `feeSponsorship` on quotes and requests. * `GET /requests/v3` accepts a `referrer` filter (requires `apiKey`) and returns `apiKeyName` and `blockedAddresses` to the request's creator integrator. `GET /requests/v2` responses carry a `deprecation` block with its sunset and successor. * `MANUAL_REFUND_REQUIRED` joins `refundFailReason`; `GET /intents/status/v3` now reports `failReason` and `refundFailReason`; `GET /withdrawals/status` returns `txHash`; `hederavm` joins the chain VM types. **Deprecated** * The `strict` quote parameter is ignored by `/quote/v2`; deposit-address creation follows the requested trade type. ### API **Breaking** * `POST /execute/swap` has been removed. Calls to the endpoint now return `404`. The endpoint was a legacy execution path that predated the unified quote flow and had been superseded for some time. **Replacement** * use [`POST /quote/v2`](/references/api/get-quote-v2) to request a quote and follow the returned steps to execute it. The Quote API covers bridging, swapping, and calling through a single unified surface, and is the same flow already recommended by the [`POST /price`](/references/api/get-price) and [`POST /execute`](/references/api/execute) references. See [Step Execution](/references/api/api_core_concepts/step-execution) for how to submit each step returned by the quote. ### API **Breaking** * `POST /quote` and `POST /quote/v2`: requests that include a `referrer` in the body now require a valid `x-api-key` header. Requests with a referrer but no valid key return `401` with `errorCode: "UNAUTHORIZED_QUOTE"`. Note that the Relay SDK sets `referrer` automatically, so SDK integrations must configure **`apiKey`** on `createClient` or pass the key per call via the `headers` parameter. Create and manage API keys in the [Relay Dashboard](https://dashboard.relay.link) — see [API keys and Rate Limits](/references/api/api-keys). ### RelayKit **SDK `7.0.3`, UI Kit `11.0.6`** — Patch * Fix swap showing an error after already succeeding. When the websocket confirmed a request as successful, step execution stayed blocked on the RPC receipt lookup; if that RPC call then failed, executeSteps rejected with a TransactionConfirmationError and the swap widget flipped from Success to Error. Websocket success now resolves the step directly, late receipt errors are ignored once the backend has confirmed success, and the transaction modal no longer downgrades a Success state to Error. ([`581e9d8`](https://github.com/relayprotocol/relay-kit/commit/581e9d8)) **UI Kit `11.0.6`** — Patch * Stop firing an unfiltered `GET /requests` when the swap success modal is closed. The ([`1326d6d`](https://github.com/relayprotocol/relay-kit/commit/1326d6d)) transaction lookup in `TransactionModal` is only ever filtered by `id`, but it stayed enabled on the tx hashes alone — so clearing `steps` on close dropped the `requestId` while the query was still enabled, sending a request with no filters. It is now enabled only when a `requestId` is present. ### API **Breaking** * `GET /requests/:requestId/signature` and `GET /requests/:requestId/signature/v2` have been removed. Calls to both endpoints now return `404`. Both returned a solver signature over a small subset of intent fields, which never covered the minimum output amount, required a second call after the quote, returned only partial data until the request reached a terminal state, and behaved inconsistently across chains. **Replacement** * verify a quote deterministically and offline instead: pass `includeProtocolData: true` to `POST /quote/v2`, inspect the returned `protocol.v2.orderData`, recompute the order id with `getOrderId` from the public [`@relay-protocol/settlement-sdk`](https://www.npmjs.com/package/@relay-protocol/settlement-sdk) package, and confirm the deposit transaction commits to that order id. See [Input Validation](/references/api/api_core_concepts/input-validation). Deposit address deposits are not yet covered by this flow. ### RelayKit **UI Kit `11.0.5`** — Patch * Align swap widget fee display with the Dashboard and transaction page: ([`e6ca48c`](https://github.com/relayprotocol/relay-kit/commit/e6ca48c)) "Swap Impact" is now "Swap Cost", "Relay Fee" is "Platform Fee" (with a green "(Reward)" note when Relay pays the user), "Execution Fee" is "Execution Cost", and "Network cost" is "Deposit gas". Credits render green with a leading "+", zero fees display as "$0.00" instead of "-", and sub-cent values as signed "+\< $0.01" / "-\< \$0.01". ### API **Behavior change** * `POST /authorize` (elevated API keys): rate limiting is now scoped per `(API key, wallet)` at 5 requests per 60 seconds, with a separate 30 requests per 60 seconds aggregate ceiling per API key. Previously all `/authorize` calls under one elevated API key shared a single 5-per-60-second bucket, so concurrent authorizations from different end-users could reject each other even when no individual wallet exceeded the intended threshold. Self-serve `/authorize` limits are unchanged. See [Elevated Rate Limits](/references/api/api-keys#elevated-rate-limits). ### RelayKit **UI Kit `11.0.4`** — Patch * Replace Backpack with Nightly and OKX as the Eclipse wallet connectors, since Backpack dropped Eclipse support ([`8fb110e`](https://github.com/relayprotocol/relay-kit/commit/8fb110e)) **UI Kit `11.0.4`** — Patch * Fix the swap completion view showing a provisional token and amount as ([`d097a11`](https://github.com/relayprotocol/relay-kit/commit/d097a11)) received. Destination `actual` values from `GET /requests/v3` are now only displayed once the request status is `success` — before that, the quoted output token and amount are shown instead of intermediate route values (e.g. an origin swap's USDC output on a route that delivers a different token). The transaction modal also keeps polling the request until it reaches a terminal status (`success`, `failure`, or `refund`) so the final received amount replaces the quoted one. **SVM adapter `21.0.3`** — Patch * Skip address lookup tables that can't be resolved on the connected chain instead of crashing before signing ([`8fb110e`](https://github.com/relayprotocol/relay-kit/commit/8fb110e)) ### API **Behavior change** * `POST /execute`: `executionOptions.referrer` is now optional. Gasless execution requests that omit it are accepted; requests that still supply it are handled the same as before. The `x-api-key` check already scopes the endpoint to the caller's integrator, so `referrer` no longer needs to be part of the required set. Field shape and semantics are unchanged. ### API **Added** * `GET /requests/v3`: `referrer` is again supported as a filter — both as a top-level query parameter and as a `referrer` / `not:referrer` key inside any `filters` group — with exact-match semantics. Because referrer values are guessable, a referrer filter must be paired with the top-level `apiKey` parameter, and every supplied key must belong to the authenticating integrator. Requests without the `apiKey` pairing return `400`; requests whose supplied keys are not owned by the caller (or which cannot resolve to an integrator identity) return `403`. Empty `referrer` values are rejected. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#what-changed). ### RelayKit **Added** * Pricing an XRP route before a wallet connects now works, and `isDeadAddress` recognises the Tron, Zero, and XRP placeholder addresses alongside the existing ones. — SDK `7.0.2`, UI Kit `11.0.3` ([`e9dbac1`](https://github.com/relayprotocol/relay-kit/commit/e9dbac1)) ### API **Behavior change** * `POST /quote`, `POST /quote/v2`, and `POST /price`: same-chain Solana quotes now reject at quote time when the compiled deposit transaction would exceed 1232 bytes, matching the existing behavior for Solana-origin cross-chain quotes (see the [2026-05-29 entry](#2026-05-29)). Previously same-chain oversized quotes returned `200` with steps the wallet could not serialize. * `POST /quote`, `POST /quote/v2`, and `POST /price`: the Solana deposit-transaction size gate now enforces the raw **1232-byte** wire limit. Quotes between 1173 and 1232 bytes — valid on the wire as returned — are now accepted, and the previously reserved 60-byte compute-budget headroom is advisory. Clients that prepend compute-budget instructions at send time are responsible for checking their remaining room against the returned steps. This reverses the [2026-07-09 change](#2026-07-09) that had cut the effective quote-time limit to 1172 bytes. **Added** * `POST /quote` and `POST /quote/v2`: the new optional `subsidizationBps` request field lets sponsors cover a fixed share of each selected sponsorable fee bucket instead of covering it in full. Values are integer basis points in the range `0`–`10000` (`10000` = 100%). Requires `subsidizeFees: true`. Applied per bucket **before** `maxSubsidizationAmount`, which continues to cap the sponsor's total contribution. Currently supported only on swap execution flows — bridge and deposit-address flows return `400` when set. Omitting the field preserves the previous full-coverage behavior. See [Fee Sponsorship](/features/fee-sponsorship#how-subsidizationbps-works). * `GET /requests/v2`, `GET /requests/v3`, and `GET /integrators/quote-data`: the `feeSponsorship` object now includes `subsidizationBps` on both the quoted and actual phases, reflecting the share configured at quote time when one was set. Absent when the field was not configured on the request. * `POST /quote`, `POST /quote/v2`, and `POST /price`: the response `errorCode` enum gains **`SOLANA_TX_TOO_LARGE`**, returned with `400` when the compiled Solana deposit transaction would exceed Solana's 1232-byte wire limit and cannot be signed or broadcast. The response `message` reports the measured transaction size and how many bytes it lands over, so clients can see how far over the quote is without recompiling the message themselves. See [Handling Quote Errors](/references/api/api_core_concepts/handling-errors) and [Solana Support](/references/api/api_guides/solana#transaction-size-optimization). ### Dashboard **Added** * A new Observability page shows what your integration is actually doing: request volume, error rates, and rate limit pressure over any time range, broken down by endpoint and by API key. Start from the overview, then open a single endpoint to see where throttling or errors concentrate. This is the context the rate limit alert emails were missing. When a key hits its limit you can now see how often, on which endpoint, and whether it is worth acting on — instead of an alert with nowhere to go. ### API **Added** * `GET /intents/status/v3`: the response now includes `failReason` and `refundFailReason`, matching the fields already returned on [`GET /requests/v3`](/references/api/get-requests) and the `request.status.updated` [webhook](/references/api/api_guides/webhooks) / [websocket](/references/api/api_guides/websockets) event. `failReason` returns `"N/A"` on non-failure statuses and the recorded reason (or `"UNKNOWN"`) on `failure`, `fallback`, and `refund` statuses. `refundFailReason` defaults to `"N/A"` when no refund-leg failure is recorded. Integrators polling status can now read these values directly without a follow-up call to `GET /requests/v3`. See [Handling Execution Errors](/references/api/api_core_concepts/execution-errors) for the full enum. * `GET /intents/status/v3`, `GET /requests/v3`, and the `request.status.updated` webhook: the `refundFailReason` enum gains **`MANUAL_REFUND_REQUIRED`**, returned when the refund leg could not be automatically executed and requires manual intervention (for example, when the origin funds cannot be programmatically returned to the depositor). ### API **Added** * `GET /requests/v3`: the response `data` object now includes `apiKeyName`, the display name of the API key that created the request. It is returned only when the caller authenticates with an API key belonging to the request's owning integrator and passes `includeAuthenticatedData=true` — the same gate as `data.referrer` and `data.platformFee`. `null` when the owning API key has no name set. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#what-changed). ### API **Behavior change** * `GET /requests/v2`: `429` responses now return a route-specific body — `message`, `successor`, `migrationGuide` — distinct from the generic rate-limit body used by every other endpoint. No `Retry-After` header or field is emitted. Migrate to [`GET /requests/v3`](/references/api/get-requests) — see the [sunset timeline](/references/api/api_guides/migrating-to-requests-v3#sunset-timeline). **Added** * `GET /requests/v3`: the `filters` AND/OR parameter now accepts `null` as a null-check operator on any recognised key. `{"depositAddress": null}` matches requests where the field is empty (does not exist); `{"not:depositAddress": null}` matches requests where the field is present. Null-checks work across identity, address, chain, currency, and status-style fields, including the composite `user` filter and the case-insensitive `currencyInSymbol` / `currencyOutSymbol` fields. Arrays that mix `null` with real values for the same key return `400`. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#what-changed). * `GET /requests/v2`: every response now carries `Deprecation`, `Sunset`, and `Link` headers pointing to `GET /requests/v3` and the [migration guide](/references/api/api_guides/migrating-to-requests-v3), on `2xx`, `4xx`, and `5xx` alike. The `Deprecation` header is a structured-field Date (`@`), `Sunset` is an RFC 7231 HTTP-date, and `Link` includes three relations — `deprecation`, `sunset`, and `successor-version`. The three headers are also listed in the API's `Access-Control-Expose-Headers`, so browser clients using `fetch` or `XMLHttpRequest` can read them cross-origin. * `GET /requests/v2`: the `200` response body gains a top-level `deprecation` object with `message`, `deprecatedAt`, `throttledFrom`, `sunsetAt`, `successor`, and `migrationGuide`, mirroring the header timeline for clients that read the body rather than headers. ### RelayKit **Changed** * The SDK's generated API types were regenerated to match the API schema. No runtime behavior changed. — SDK `7.0.1` ([`fb843c4`](https://github.com/relayprotocol/relay-kit/commit/fb843c4), [`a443358`](https://github.com/relayprotocol/relay-kit/commit/a443358), [`ef54ef1`](https://github.com/relayprotocol/relay-kit/commit/ef54ef1)) **UI Kit `11.0.2`** — Patch * Add Robinhood Chain, Linea, Monad, Soneium, MegaETH, and Tempo to Uniswap Wallet's supported chains ([`78bf766`](https://github.com/relayprotocol/relay-kit/commit/78bf766)) ### API **Added** * `GET /requests/v3`: the new opt-in `includeTotal` boolean query parameter (defaults to `false`) returns an exact `total` field on the response with the count of requests matching the current filters, independent of `limit` and `continuation`. The count reflects all matches across the full data window, not just the returned page. Because computing the exact count adds meaningful query latency, keep `includeTotal` off on the hot list-fetch path and enable it only when the total is needed (e.g. rendering a "X of Y results" header). Pagination behavior is unchanged. ### RelayKit **UI Kit `11.0.1`** — Patch * Normalize Uniswap Wallet connector name so its chain restrictions apply ([`4ed6905`](https://github.com/relayprotocol/relay-kit/commit/4ed6905)) ### Dashboard **Added** * Invite teammates as Support and they get the Requests surface — search, filters, saved views, request detail, and reindexing — without access to API keys, credentials, or billing. Keys surface to them as names only, so support can narrow a search to the right one without ever seeing its value. ### API **Behavior change** * `GET /requests/v3`: for requests where an origin deposit was observed on-chain but no fill was ever attempted (for example, a double-spent Bitcoin deposit that fails before solve), `data.route.actual.origin.inputCurrency` now reflects the actual deposited amount and currency instead of mirroring `data.route.quoted.origin.inputCurrency`. `data.route.actual.origin.outputCurrency` is `null` in this case — no fill occurred, so no output currency was observed — and `data.route.actual.destination` continues to fall back to `data.route.quoted.destination`. Previously the entire `data.route.actual` object silently mirrored `data.route.quoted` on these rows, hiding the real deposit amount from integrators reading the actual-side fields. Requests that reached a fill attempt are unaffected. * `GET /requests/v3`: for TON-origin requests, `data.inTxs[].txHash` is now the bare 64-character hex transaction hash, suitable for opening on Tonviewer at `/transaction/{TX_HASH}`. Previously this field returned an internal composite identifier that had to be parsed before it could be linked to an explorer. Non-TON origins and `data.outTxs[]` are unaffected. This mirrors the fix previously shipped for [`GET /requests/v2`](/references/api/get-requests-v2) on 2026-06-19. ### API **Behavior change** * `POST /quote`, `POST /quote/v2`, and `POST /price`: transient failures that previously collapsed into a generic `500` with `errorCode: "UNKNOWN_ERROR"` are now classified. Transient infrastructure failures (including raw transport timeouts) return `503` with `errorCode: "SERVICE_UNAVAILABLE"`, transient price-feed failures return `503` with `errorCode: "PRICE_FETCH_FAILED"`, and quote requests for token pairs that cannot be priced return `400` with `errorCode: "UNSUPPORTED_CURRENCY"`. Only genuinely unclassified failures still return `500 UNKNOWN_ERROR`. Both `503` codes are retryable — see [Handling Quote Errors](/references/api/api_core_concepts/handling-errors). **Added** * `POST /quote`, `POST /quote/v2`, and `POST /price`: the response `errorCode` enum gains `SERVICE_UNAVAILABLE` and `PRICE_FETCH_FAILED` for transient upstream failures where the request should be retried. * `GET /requests/v3`: the elevated per-key rate limit is now **20 requests per second**, up from the previously shared 10 rps bucket. `GET /requests` and `GET /requests/v2` continue at 10 rps. Default (non-elevated) limits are unchanged. See [Elevated Rate Limits](/references/api/api-keys#elevated-rate-limits). ### RelayKit **UI Kit `11.0.0`** — Major * Migrate wallet balance fetching from Dune (sunset) to Codex ([`e426f05`](https://github.com/relayprotocol/relay-kit/commit/e426f05)) Breaking changes: * `duneConfig` provider option removed — use `codexConfig` (`{ apiBaseUrl?, apiKey? }`). The default api base url is `https://graph.codex.io`; override it to proxy requests and protect your api key. * `useDuneBalances` hook removed — use `useCodexBalances(address, queryOptions)`. The mainnet/testnet parameter is gone; balances are fetched from Codex-supported networks and filtered by your configured chains. * `isDuneBalance` removed from `useCurrencyBalance`'s return value. * `useMultiWalletBalances` no longer takes the mainnet/testnet parameter. Other changes: * New `useSolanaBalance` hook: selected SVM tokens (Solana and Eclipse) now fetch balances directly from the chain's RPC (native via `getBalance`, SPL via `getTokenAccountsByOwner`) instead of an indexer, so post-swap balances are fresh and the aggressive periodic refetch workaround is gone. The internal eclipse-only balance hook is gone; Eclipse flows through the same path. * Native SVM balances (SOL, Eclipse ETH) in the consolidated token selector list are fetched from their RPCs and merged with Codex results, since Codex does not index native SVM balances (nor Eclipse at all). Their USD values come from Codex prices for wrapped SOL / mainnet WETH. * Spam filtering: Codex's `removeScams` plus an `isScam` drop, and a liquidity heuristic replacing Dune's spam scoring — tokens whose pool liquidity is under \$1k or below the balance's usd value have their usd value stripped so they can't crowd out real holdings in the token selector (balances still display). * Known loss: Soon balances are gone from the token selector list; Soon is no longer a supported chain. **UI Kit `11.0.0`** — Patch * Restrict Robinhood Wallet to its supported chains ([`d8c713c`](https://github.com/relayprotocol/relay-kit/commit/d8c713c)) ### API **Behavior change** * `GET /requests/v3`, `GET /intents/status/v3`, and the `request.status.updated` webhook: gasless `/execute` failure rows that previously returned `failReason: "UNKNOWN"` when a broadcast succeeded but the transaction was never included, or when the transaction was never accepted for broadcast, now return one of the two new reasons. **Added** * `GET /requests/v3`, `GET /intents/status/v3`, and the `request.status.updated` webhook: the response `failReason` enum gains **`TRANSACTION_SUBMISSION_FAILED`**, returned when the fill transaction could not be broadcast to the network (RPC unavailable, signer unavailable, or pre-broadcast rejection — no transaction hash exists), and **`TRANSACTION_NOT_INCLUDED`**, returned when the fill transaction was broadcast but never included onchain before Relay's retry and gas-bump window elapsed. See [Handling Execution Errors](/references/api/api_core_concepts/execution-errors#transaction-construction--gas-errors) for the full enum. ### RelayKit **UI Kit `10.0.1`** — Patch * Complete the RefundReason error mapping. Every failReason code in the API schema now resolves to a specific user-facing explanation instead of falling back to the generic unknown-issue message, matching the copy used on relay.link and the developer dashboard. ([`f1a9fd8`](https://github.com/relayprotocol/relay-kit/commit/f1a9fd8)) **UI Kit `10.0.1`** — Patch * Gracefully handle broken token/chain logo images. ChainTokenIcon now falls back to the token symbol avatar and ChainIcon hides itself when the logo fails to load, instead of rendering a broken-image placeholder. ([`628ad74`](https://github.com/relayprotocol/relay-kit/commit/628ad74)) **UI Kit `10.0.1`, Hooks `4.0.1`** — Patch * block token contract addresses in recipient field ([`23e4ecf`](https://github.com/relayprotocol/relay-kit/commit/23e4ecf)) ### API **Deprecated** * `GET /requests/v2` is deprecated as of today. It keeps working for now, but its rate limit will be progressively reduced each month until it is **fully retired on November 24, 2026**. Migrate to v3 before then — see the [sunset timeline](/references/api/api_guides/migrating-to-requests-v3#sunset-timeline). **Behavior change** * `POST /quote` and `POST /quote/v2`: when a blocklisted wallet recipient reaches destination simulation and the token contract rejects the transfer, the response `errorCode` is still `DESTINATION_TX_FAILED` but the `message` is now `Recipient cannot receive this token (blocked by the token contract). Try a different recipient address.` instead of the generic `Destination transaction failed`. Error classification is unchanged. **Added** * `POST /quote` and `POST /quote/v2`: an explicitly provided `recipient` is now validated before quoting. Recipients that fail address validation for the destination chain return `400` with `errorCode: "INVALID_ADDRESS"`. Recipients that resolve to a supported token contract (which cannot receive tokens — e.g. passing the destination-chain USDC contract as `recipient`) return `400` with the new `errorCode: "INVALID_RECIPIENT"`. Requests that omit `recipient` are unaffected — the implicit `recipient = user` path is not revalidated. Previously these requests surfaced as `DESTINATION_TX_FAILED` after simulation. See [Handling Quote Errors](/references/api/api_core_concepts/handling-errors). * `GET /requests/v3`: the recommended Requests API. It requires `x-api-key`, adds a broad filter/search/sort surface, and returns a cleaner, consolidated response. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3). ### RelayKit **SDK `7.0.0`, UI Kit `10.0.0`, Hooks `4.0.0`** — Major * Migrate the Requests API from v2 to v3 (breaking). ([`07dda45`](https://github.com/relayprotocol/relay-kit/commit/07dda45)) * `useRequests` / `queryRequests` and `useDepositAddressStatus` now call `GET /requests/v3` and return the v3 response shape. * `GET /requests/v3` requires a Relay API key (`x-api-key`). Since these hooks run client-side, the key is not sent from Relay Kit — you must point `baseApiUrl` at a proxy that injects `x-api-key` server-side. This is now required to use the UI kit. See the package README. * Response shape changes handled across the SDK/hooks/UI: `inTxs[].hash`/`outTxs[].hash` → `txHash`; `data.metadata.currencyIn/currencyOut/currencyGasTopup` removed — currencies for display are now derived from `data.route` (origin `inputCurrency` / destination `outputCurrency`, with `actual` → `quoted` and same-chain fallbacks); `failReason`/`refundFailReason` are `null` instead of `"N/A"`; new `submitted` status. * `hash` request filter removed; lookups by transaction hash now use the unified `term` search. * `RelayKitProvider` now warns (client-side) when `baseApiUrl` points directly at the Relay API instead of a proxy: a `console.error` if an `apiKey` is also set (it would be exposed in the browser), otherwise a `console.warn` that `/requests/v3` will fail without a proxy. Suppress with `acknowledgeApiKeyExposure: true`. * **Removed** the `secureBaseUrl` option from `RelayKitProvider` and the `useSecureBaseUrl` prop from `SwapWidget`. All Relay API traffic now flows through the single `baseApiUrl`, which must be a proxy — the proxy is responsible for any gas-sponsorship logic (and injecting `x-api-key`) server-side. **UI Kit `10.0.0`** — Patch * update wallet rejection errors in swap widget ([`b4a3f7c`](https://github.com/relayprotocol/relay-kit/commit/b4a3f7c)) ### Dashboard **Added** * The Relay Dashboard is out of pilot and open to every integrator at [dashboard.relay.link](https://dashboard.relay.link) — one operational interface for your integration, self-serve from day one. * **Org and team management** — invite teammates, assign roles, and control access. * **API keys** — create and configure keys, each with its own webhooks, allowlists, and rate limits, and get alerted by email when one hits its limit. * **App fees and fee sponsorship** — track balances, withdraw earnings, link a sponsorship wallet, and sponsor your users' fees. * **Request visibility** — compose filters across status, chains, currencies, amounts, and time; open built-in views like Failed and Refunds in one click; save your own named views with the columns you want; and drill into any request for its full route, status, and component-level fee breakdown. Provisioning a standard-tier key no longer goes through a form and a Slack thread — sign up and create one yourself. ### RelayKit **Changed** * The Base wallet can no longer send to or receive on Robinhood Chain, which it does not support. — UI Kit `9.1.4` ([`a29e409`](https://github.com/relayprotocol/relay-kit/commit/a29e409)) ### API **Behavior change** * `POST /quote/v2`: the Solana deposit-transaction size check on cross-chain Solana-origin quotes now reserves 60 bytes of headroom for the compute-budget instructions wallets and SDKs prepend at send time, so quotes are rejected when the compiled deposit transaction would exceed **1172 bytes** (1232 minus 60). Previously the check ran at the raw 1232-byte limit and could return `200` with calldata that no longer fit once the client added its compute-budget instructions. The `400` response body now names the effective limit and the reserved-bytes reason. Continue to use **`maxRouteLength`** or **`includedOriginSwapSources`** to keep transactions under the limit — see [Solana Support](/references/api/api_guides/solana#transaction-size-optimization). ### RelayKit **SDK `6.1.3`, UI Kit `9.1.3`, Hooks `3.0.24`, Bitcoin adapter `19.0.3`, Ethers adapter `31.0.3`, Lighter adapter `2.0.3`, SVM adapter `20.0.3`, TON adapter `2.0.3`, Tron adapter `8.0.3`** — Patch * Embed sourcesContent in published sourcemaps so they resolve without the unpublished src directory ([`0020895`](https://github.com/relayprotocol/relay-kit/commit/0020895)) **UI Kit `9.1.3`** — Patch * Hide non-deposit-address chains (incl. TON) from origin selector unless VM has wallet support ([`4ac9782`](https://github.com/relayprotocol/relay-kit/commit/4ac9782)) ### API **Breaking** * `POST /quote` and `POST /quote/v2`: the opt-in request flag `includeProtocolSignature` is renamed to `includeProtocolData`. The previous name is no longer honored. Integrators that were passing `includeProtocolSignature: true` to receive `protocol.v2.orderSignature` must switch to `includeProtocolData: true`. Setting this opt-in may increase quote response latency. **Added** * `POST /quote` and `POST /quote/v2`: when the quote is settled through the Relay settlement protocol, the response's `protocol.v2` block now includes `hubType` (currently always `"onchain"`) alongside `orderId`. With `includeProtocolData: true`, `protocol.v2.orderSignature` — the solver's ECDSA signature over `orderId` — is also returned, so integrators can validate an onchain `orderId` against the quoted intent using `ecrecover` without a follow-up API call. ### RelayKit **Fixed** * The placeholder address the SDK uses to price a Bitcoin route before a wallet connects has been replaced. Previewing a Bitcoin route no longer returns an error. — SDK `6.1.2` ([`88a63b6`](https://github.com/relayprotocol/relay-kit/commit/88a63b6), [`ce4d192`](https://github.com/relayprotocol/relay-kit/commit/ce4d192)) **SDK `6.1.0`, UI Kit `9.1.0`, TON adapter `2.0.0`** — Minor * Add TON support: new `@relayprotocol/relay-ton-wallet-adapter` package exporting `adaptTonWallet`, plus `tonvm` support across the SDK (vmType, TON receipt, transaction step messages, dead address) and UI kit (native TON balance, token selector). ([`4711f4b`](https://github.com/relayprotocol/relay-kit/commit/4711f4b)) **TON adapter `1.0.0`** — Minor * Add TON wallet adapter (`adaptTonWallet`, `vmType: 'tonvm'`) ### RelayKit **SDK `6.0.1`** — Patch * Convert `convertViemChainToRelayChain` from an arrow function to a function declaration (no behavior change). ([`bb7b1bb`](https://github.com/relayprotocol/relay-kit/commit/bb7b1bb)) ### RelayKit **Removed** * The SDK and UI kit no longer support Sui. Remove it from any chain configuration you pass in. — SDK `6.0.0`, UI Kit `9.0.0` ([`4149ded`](https://github.com/relayprotocol/relay-kit/commit/4149ded)) **SDK `6.0.0`, UI Kit `9.0.0`, Tron adapter `7.0.0`** — Patch * Fix native TRX handling on Tron: forward call\_value on the deposit transaction (was sending 0 TRX) and fix the native-TRX sentinel address (the wrong sentinel made native TRX fall through to a failing balanceOf call). ([`b61e540`](https://github.com/relayprotocol/relay-kit/commit/b61e540)) ### API **Breaking** * `POST /quote`, `POST /quote/v2`, and `POST /price`: `slippageTolerance` and `latePaymentSlippageTolerance` are now validated as integer strings in the range `0`–`10000` basis points. Values outside that range — including negative integers and values greater than `10000` — return `400` with `errorCode: "INVALID_SLIPPAGE_TOLERANCE"`. Previously, negative values were accepted at quote time and produced refund outcomes downstream, and values above `10000` were accepted, effectively removing the minimum-output floor. Non-integer strings on `POST /price` previously returned `500`; they now return `400` with the same `INVALID_SLIPPAGE_TOLERANCE` code as the other endpoints. See [Handling Quote Errors](/references/api/api_core_concepts/handling-errors). ### API **Behavior change** * `GET /requests/v2`: for TON-origin requests, `data.inTxs[].hash` is now the bare 64-character hex transaction hash, suitable for opening on Tonviewer at `/transaction/{TX_HASH}`. Previously this field returned an internal composite identifier that had to be parsed before it could be linked to an explorer. Non-TON origins and `data.outTxs[]` are unaffected. **Added** * `GET /chains`: the TON entry now exposes `explorerPaths.transaction` as `/transaction/{TX_HASH}`, so clients building explorer URLs can read the canonical Tonviewer path from the API rather than hardcoding `/tx/{TX_HASH}`. ### RelayKit **Added** * Pricing a TON route before a wallet connects now works, and `isDeadAddress` recognises the TON placeholder address. — SDK `5.2.8` ([`1fc8190`](https://github.com/relayprotocol/relay-kit/commit/1fc8190)) **UI Kit `8.0.11`** — Patch * Support TON destination bridging ([`d3eda06`](https://github.com/relayprotocol/relay-kit/commit/d3eda06)) ### API **Behavior change** * `GET /requests/v2`: when a deposit is detected as reorged out or canonically failed on Base, the matching entry in `data.inTxs[]` now reports `status: "failure"`. Previously the top-level request `status` could flip to `"failure"` while the corresponding `data.inTxs[].status` remained `"success"`, leaving integrators that key their deposit UI off `data.inTxs[0].status` showing a succeeded deposit. Historical rows are not backfilled; the new behavior applies to detections going forward. **Added** * `GET /requests/v2`: failed `data.inTxs[]` entries can now include a `metadata.reorg` object with detection context. Fields: `detectedAt` (unix millis), `source` (detector component), `reason` (short code), and optional `details`, `failReason`, `action` (`"fill"` or `"refund"`), `blockNumber`, `blockHash`, `receiptStatus`. The object is only populated when the failure was caught by the reorg/finality path; deposits that fail through other paths are unaffected. ### API **Behavior change** * `POST /quote/v2`: quote attempts that fail because of a transient upstream RPC error now return `errorCode: "REQUEST_TIMED_OUT"` on timeout or `errorCode: "RPC_HTTP_ERROR"` for other transient RPC failures. Previously these were misclassified as `errorCode: "DESTINATION_TX_FAILED"`, which suggested an on-chain revert and was treated as non-retryable. `DESTINATION_TX_FAILED` is now returned only for genuine destination reverts. Both new codes are retryable — see [Handling Quote Errors](/references/api/api_core_concepts/handling-errors). ### API **Breaking** * `POST /quote/v2`: Solana-origin quotes routed through a third-party aggregator now return `400` when the generated deposit transaction would exceed Solana's 1232-byte limit. Previously these requests returned `200` with calldata that could not be serialized or executed client-side. Use **`maxRouteLength`** or **`includedOriginSwapSources`** to keep transactions under the limit — see [Solana Support](/references/api/api_guides/solana#transaction-size-optimization). ### API **Behavior change** * `GET /intents/status/v3` and the `request.status.updated` webhook/websocket event: requests with a Bitcoin origin deposit now transition to `pending` as soon as the deposit is observed in the Bitcoin mempool, instead of waiting for the first block confirmation. The `pending` status no longer implies block inclusion for Bitcoin origins; block-confirmation policy continues to govern when the request progresses to `submitted`, `success`, `failure`, or `refund`. EVM and other non-Bitcoin origins are unchanged. ### API **Added** * `GET /requests/v2` and the `request.status.updated` webhook/websocket event: the `depositAddress` object now includes `depositTxHash`, the origin-chain transaction hash of the depositor's transfer into the deposit address. This is distinct from `inTxHashes`, which tracks the sweep from the deposit address to the Relay depository. The field is `null` until the depositor's transfer is observed, and is omitted on non-deposit-address requests (where `depositAddress` itself is `null`). ### API **Added** * `request.status.updated` webhook: the `data` object now includes `details`, `failReason`, and `refundFailReason`, matching the corresponding fields on [`GET /intents/status/v3`](/references/api/get-intents-status-v3). `failReason` returns `"N/A"` on non-failure statuses and the recorded reason (or `"UNKNOWN"`) on `failure`/`fallback`/`refund` statuses. `refundFailReason` defaults to `"N/A"` when no refund-leg failure is recorded. `details` is `null` when no additional context is available. ### RelayKit **Changed** * The UI kit fetches its centralized-exchange address list from `https://assets.relay.link/app/cexAddresses.json` instead of a GitHub raw URL. Update any host allowlist that covered the old address. — UI Kit `8.0.10` ([`4d73aeb`](https://github.com/relayprotocol/relay-kit/commit/4d73aeb)) ### API **Breaking** * `POST /authorize` (Hyperliquid nonce-mapping flow): the EIP-712 `RelayNonceMapping` domain `version` is bumped from `"1"` to `"2"`, and the `NonceMapping` typed-data struct gains a new `depositor` (`address`) field. The `/authorize` request body must include a matching `depositor` field. Integrators that pass the API-returned `sign` payload straight to their wallet (and POST `post.body` verbatim) require no code change. Integrators that hand-roll the authorize step must sign the new typed-data shape and forward `depositor` from `steps[0].items[0].data.sign.value.depositor`. See [Hyperliquid Support](/references/api/api_guides/hyperliquid-support) for the updated payload shapes. ### API **Behavior change** * `POST /execute`: raw-call simulation failures now return a decoded `error` label, `message`, `requestId`, and optional `details` object when Relay can decode the revert trace. The `details` object includes the top-level frame, innermost reverting frame, full reverting path, and decoded revert data for both subsidized and non-subsidized gasless execution flows. ### API **Behavior change** * `GET /intents/status/v3`: failure rows that previously returned `failReason: "UNKNOWN"` now return a specific reason where one is available. Failed same-chain swap attempts return `TRANSACTION_REVERTED`, and refund rows triggered by duplicate deposits against the same request return `DOUBLE_SPEND`. No new enum members were introduced for these two cases. **Added** * `GET /intents/status/v3`: the response `failReason` enum gains `TRANSACTION_TOO_LARGE`, returned when the serialized fill transaction exceeded the origin chain or RPC payload size limit. The enum also gains `SOLVER_BALANCE_TOO_LOW`, returned when the solver could not construct or send the fill because its source-side balance (or available UTXOs) was insufficient. ### RelayKit **Added** * The SDK exports a testnet websocket endpoint, `wss://ws.testnets.relay.link`, alongside the mainnet one. — SDK `5.2.7` ([`eab4c8f`](https://github.com/relayprotocol/relay-kit/commit/eab4c8f)) **UI Kit `8.0.9`** — Patch * Add link to withdraw page on fill failure and isWithdrawable ([`eac2776`](https://github.com/relayprotocol/relay-kit/commit/eac2776)) ### API **Behavior change** * `POST /quote/v2` with `useDepositAddress: true`, `strict: true`, and `tradeType: EXACT_OUTPUT`: a deposit larger than the quoted `amount` now fills the quoted amount and refunds the excess to `refundTo` in a separate refund leg, instead of scaling the fill up to the deposited amount. Exact payments still fill normally and underpayments still fail and refund. `EXACT_INPUT` strict deposit addresses are unaffected — an overpayment fills the full deposited amount and the excess is not refunded. `refundTo` remains required for strict deposit addresses. ### RelayKit **Fixed** * Hyperliquid's spot and perps USDC both display as `USDC`, so the suggested-token pills were indistinguishable. They now show the token name on Hyperliquid. — UI Kit `8.0.8` ([`22aa1f9`](https://github.com/relayprotocol/relay-kit/commit/22aa1f9)) ### API **Breaking** * `POST /quote/v2`: same-chain swap requests whose quoted USD value is greater than `0` and less than `$0.05` now return `400` with `errorCode: "AMOUNT_TOO_LOW"`. Previously these requests returned `200` with calldata that reverted on-chain. Wrap and unwrap quotes (e.g. same-chain `ETH` ↔ `WETH`) and quotes that include attached calls continue to be accepted at any size. ### RelayKit **UI Kit `8.0.7`** — Patch * Fix gap in token selector's suggested tokens ([`31cd7b4`](https://github.com/relayprotocol/relay-kit/commit/31cd7b4)) ### RelayKit **Added** * New `@relayprotocol/relay-lighter-wallet-adapter` package, with the SDK wallet types and transaction steps needed to execute a route through a Lighter wallet. — SDK `5.2.4`, Lighter adapter `0.1.1` ([`670737f`](https://github.com/relayprotocol/relay-kit/commit/670737f)) **SDK `5.2.4`** — Patch * add disableCapabilitiesCheck option to skip wallet.getCapabilities ([`0eedcc1`](https://github.com/relayprotocol/relay-kit/commit/0eedcc1)) ### RelayKit **SDK `5.2.3`** — Patch * Support atomic batching for zero-reset approval swap flows (e.g. USDT on Ethereum). `canBatchTransactions` and `prepareBatchTransaction` now accept any number of leading `approve` steps before the terminal `swap`/`deposit`, so wallets that support EIP-5792 collapse the `approve(0) → approve(amount) → swap` sequence into a single `wallet_sendCalls` instead of three separate prompts. ([`eeb4695`](https://github.com/relayprotocol/relay-kit/commit/eeb4695)) ### RelayKit **Added** * `GET` deposit-address status now reports a `depositing` step, so a client can show that state while a deposit is in progress. — SDK `5.2.2`, UI Kit `8.0.4`, Hooks `3.0.12` ([`8d8aa50`](https://github.com/relayprotocol/relay-kit/commit/8d8aa50)) **UI Kit `8.0.4`** — Patch * Use spotClearinghouseState for Spot USDC and unified Hyperliquid accounts. Adds a new useHyperliquidAccountMode hook to detect the user's account abstraction mode, and routes balance queries through spotClearinghouseState for unified/portfolio-margin accounts where the Perps USDC balance is reported under spot. ([`38dabc0`](https://github.com/relayprotocol/relay-kit/commit/38dabc0)) ### RelayKit **UI Kit `8.0.3`** — Patch * Namespace keyframes with relay- prefix to prevent collisions with host apps ([`d5bea75`](https://github.com/relayprotocol/relay-kit/commit/d5bea75)) **UI Kit `8.0.3`** — Patch * Add onHapticEvent callback to RelayKitProvider ([`08ecc49`](https://github.com/relayprotocol/relay-kit/commit/08ecc49)) ### RelayKit **UI Kit `8.0.2`** — Patch * Fix CSS layer scoping and priority for host app compatibility ([`26b2fc7`](https://github.com/relayprotocol/relay-kit/commit/26b2fc7)) **UI Kit `8.0.1`** — Patch * Fix CSS being tree-shaken in production builds by declaring styles.css as ([`887f0e0`](https://github.com/relayprotocol/relay-kit/commit/887f0e0)) **UI Kit `8.0.0`** — Major * Migrate styling system from Panda CSS to Tailwind CSS ([`4c6d8d2`](https://github.com/relayprotocol/relay-kit/commit/4c6d8d2)) **UI Kit `8.0.0`** — Patch * Optimize Font Awesome imports to use individual icon paths ([`0e44e49`](https://github.com/relayprotocol/relay-kit/commit/0e44e49)) ### RelayKit **Changed** * The Bitcoin wallet adapter moved to a newer `bitcoinjs-lib`. — Bitcoin adapter `17.0.2` ([`daaae5b`](https://github.com/relayprotocol/relay-kit/commit/daaae5b)) **Hooks `3.0.11`** — Patch * Add default stale time to useTokenPrice hook ([`ad4f08c`](https://github.com/relayprotocol/relay-kit/commit/ad4f08c)) ### RelayKit **Changed** * `useQuote` and the swap and token widgets threw bare strings such as `'Missing a quote'`. They now throw `Error` objects, so a `catch` block can read `error.message` and error reporters record a stack. Reading a status code from a failed request no longer throws when the error carries no response. — UI Kit `7.1.5`, Hooks `3.0.10` ([`2f0d6de`](https://github.com/relayprotocol/relay-kit/commit/2f0d6de)) **Fixed** * `useExecutionStatus` and the SDK's transaction helpers could leave a promise rejection unhandled, which terminates a Node process. Server-side rendering and scripted use were affected. Both now handle the rejection. — SDK `5.2.1`, Hooks `3.0.10` ([`7b351ab`](https://github.com/relayprotocol/relay-kit/commit/7b351ab)) ### RelayKit **SDK `5.2.0`** — Minor * Add executeGaslessBatch action to support 7702 gasless flow ([`144f7a4`](https://github.com/relayprotocol/relay-kit/commit/144f7a4)) **UI Kit `7.1.4`** — Patch * Make progress modal error scrollable ([`1ae288f`](https://github.com/relayprotocol/relay-kit/commit/1ae288f)) **UI Kit `7.1.4`** — Patch * Clearer error for locked wallet ([`3c9e0a1`](https://github.com/relayprotocol/relay-kit/commit/3c9e0a1)) ### RelayKit **Changed** * The Solana wallet adapter checks that the signature a wallet returns is base58 and throws `Invalid Solana signature: expected base58.` when it is not, instead of passing a malformed signature on. — SVM adapter `17.0.2` ([`20db8bc`](https://github.com/relayprotocol/relay-kit/commit/20db8bc)) **UI Kit `7.1.3`** — Patch * Enable multi wallet dropdown for hyperliquid ([`5b4830b`](https://github.com/relayprotocol/relay-kit/commit/5b4830b)) ### RelayKit **Added** * The chain selector accepts a `sameChainOption`, letting a user pick the origin chain as the destination for a same-chain swap. — UI Kit `7.1.2` ([`8aa2bca`](https://github.com/relayprotocol/relay-kit/commit/8aa2bca)) **Changed** * Selecting the maximum amount now holds back a small execution buffer on every origin chain, where previously only native-token routes reserved one for fees. The native gas buffer applies to EVM and SVM origins. — UI Kit `7.1.2` ([`c7da389`](https://github.com/relayprotocol/relay-kit/commit/c7da389)) * Hyperliquid operates like a centralized exchange, so wallet compatibility checks are skipped for it. Abstract Global Wallet recipients are the exception — they cannot receive a Hyperliquid deposit and are now rejected. — UI Kit `7.1.2` ([`00cad1c`](https://github.com/relayprotocol/relay-kit/commit/00cad1c)) * The token selector takes its suggested-token metadata from the API's trending currencies and each chain's `solverCurrencies`, rather than from local storage. — SDK `5.1.1`, UI Kit `7.1.2` ([`f851b3c`](https://github.com/relayprotocol/relay-kit/commit/f851b3c)) ### RelayKit **Changed** * Deposit-address transaction tracking reads from the Requests API. — UI Kit `7.1.1`, Hooks `3.0.7` ([`448ec7c`](https://github.com/relayprotocol/relay-kit/commit/448ec7c)) ### RelayKit **Breaking** * The UI kit no longer exports `useEOADetection`. `useExplicitDeposit` replaces it — update your imports if you called the hook directly. — SDK `5.1.0`, UI Kit `7.1.0` ([`56123ee`](https://github.com/relayprotocol/relay-kit/commit/56123ee)) ### RelayKit **Added** * New `fastFill` action on the SDK, with `FastFillParameters`, `FastFillBody`, and `FastFillResponse` types. A failed fast fill throws an `APIError` carrying the failure message and status, including transport failures that never returned a response. — SDK `5.0.3` ([`0f0ad9f`](https://github.com/relayprotocol/relay-kit/commit/0f0ad9f), [`7acfaed`](https://github.com/relayprotocol/relay-kit/commit/7acfaed)) **Changed** * The widget's fee breakdown reads `details.expandedPriceImpact` from the quote instead of deriving figures from `fees.relayerService` and `details.swapImpact`, and links to the fee documentation. — UI Kit `7.0.11` ([`6ff1ba6`](https://github.com/relayprotocol/relay-kit/commit/6ff1ba6)) ### RelayKit **Added** * Every request the SDK makes now sends a `relay-sdk-version` header, reporting the client version or `unknown`. — SDK `5.0.2` ([`b86a59d`](https://github.com/relayprotocol/relay-kit/commit/b86a59d)) **SDK `5.0.2`** — Patch * Add support for custom logger function in createClient initializer ([`efa0aae`](https://github.com/relayprotocol/relay-kit/commit/efa0aae)) **UI Kit `7.0.10`** — Patch * Better handling of max input for low balances ([`da08649`](https://github.com/relayprotocol/relay-kit/commit/da08649)) ### RelayKit **SDK `5.0.1`** — Patch * Improve SolverStatusTimeout error log ([`9d7aab2`](https://github.com/relayprotocol/relay-kit/commit/9d7aab2)) ### RelayKit **UI Kit `7.0.8`** — Patch * Add support for lighter evm address lookup ([`492e417`](https://github.com/relayprotocol/relay-kit/commit/492e417)) ### RelayKit **Changed** * EOA detection now allows 2.5 seconds instead of 1 second, so a slow wallet or RPC is less likely to be misread. The timeout was then reimplemented without any further change in behavior. — UI Kit `7.0.7` ([`6c804bc`](https://github.com/relayprotocol/relay-kit/commit/6c804bc), [`9cc3fac`](https://github.com/relayprotocol/relay-kit/commit/9cc3fac)) ### RelayKit **SDK `5.0.0`** — Major * Move configureViemChain, configureDynamicChains, and fetchChainConfigs to new /chain-utils subpath to reduce bundle size ([`7d8cfef`](https://github.com/relayprotocol/relay-kit/commit/7d8cfef)) **UI Kit `7.0.5`** — Patch * Remove twitter share button ([`cb3b9e9`](https://github.com/relayprotocol/relay-kit/commit/cb3b9e9)) **Hooks `3.0.2`** — Patch * Move configureViemChain, configureDynamicChains, and fetchChainConfigs to new /chain-utils subpath to reduce bundle size ([`7d8cfef`](https://github.com/relayprotocol/relay-kit/commit/7d8cfef)) ### RelayKit **SDK `4.0.1`** — Patch * Add amount param to claimAppFees action ([`dd3f0c2`](https://github.com/relayprotocol/relay-kit/commit/dd3f0c2)) **UI Kit `7.0.4`** — Patch * Allow setting 0 slippage tolerance ([`4323f0e`](https://github.com/relayprotocol/relay-kit/commit/4323f0e)) ### RelayKit **UI Kit `7.0.2`** — Patch * Clearer eoa logs ([`e2a4b65`](https://github.com/relayprotocol/relay-kit/commit/e2a4b65)) ### RelayKit **UI Kit `7.0.1`** — Patch * Add tracking for eoa detection ([`34167fa`](https://github.com/relayprotocol/relay-kit/commit/34167fa)) ### RelayKit **SDK `4.0.0`, UI Kit `7.0.0`, Hooks `3.0.0`** — Major * Upgrade to quote/v2 api ([`f544c1d`](https://github.com/relayprotocol/relay-kit/commit/f544c1d)) ### RelayKit **UI Kit `6.1.1`** — Patch * Support hyperliquid balance fetching for spot coins ([`e27e88a`](https://github.com/relayprotocol/relay-kit/commit/e27e88a)) **SVM adapter `14.0.1`** — Patch * Update solana adapter docs ([`8854ced`](https://github.com/relayprotocol/relay-kit/commit/8854ced)) ### RelayKit **SDK `3.2.0`, UI Kit `6.1.0`** — Minor * Integrate hyperliquid direct deposits ([`2519846`](https://github.com/relayprotocol/relay-kit/commit/2519846)) **SDK `3.2.0`** — Patch * Support API key parameter in `createClient` SDK method ([`b1dbfb7`](https://github.com/relayprotocol/relay-kit/commit/b1dbfb7)) **UI Kit `6.1.0`** — Patch * Update porto wallet compatibility ([`3910284`](https://github.com/relayprotocol/relay-kit/commit/3910284)) **UI Kit `6.1.0`** — Patch * Phantom support – add monad and hyperevm ([`84e79a8`](https://github.com/relayprotocol/relay-kit/commit/84e79a8)) ### RelayKit **UI Kit `6.0.10`** — Patch * Fix some deps that were incorrectly marked as peerDeps ([`d45f6be`](https://github.com/relayprotocol/relay-kit/commit/d45f6be)) **UI Kit `6.0.10`** — Patch * Stabilised ens resolution & wallet selection ([`65d0b3d`](https://github.com/relayprotocol/relay-kit/commit/65d0b3d)) ### RelayKit **UI Kit `6.0.9`** — Patch * Post-launch fixes for `TokenWidget` ([`7a9777a`](https://github.com/relayprotocol/relay-kit/commit/7a9777a)) ### RelayKit **UI Kit `6.0.8`** — Patch * Fix no routes error handling ([`e65cb19`](https://github.com/relayprotocol/relay-kit/commit/e65cb19)) ### RelayKit **SDK `3.1.1`, UI Kit `6.0.6`** — Patch * Add lighter vm and enable destination txs ([`1ed92a5`](https://github.com/relayprotocol/relay-kit/commit/1ed92a5)) **UI Kit `6.0.7`** — Patch * Stabilised tab switches ([`a33c5b0`](https://github.com/relayprotocol/relay-kit/commit/a33c5b0)) **UI Kit `6.0.6`** — Patch * Remove extra border on fee breakdown ui ([`a95b32d`](https://github.com/relayprotocol/relay-kit/commit/a95b32d)) **UI Kit `6.0.6`** — Patch * Remove canonical logic and ui ([`69fcdb7`](https://github.com/relayprotocol/relay-kit/commit/69fcdb7)) ### RelayKit **UI Kit `6.0.5`** — Patch * Mobile payment selector improvments ([`b59d911`](https://github.com/relayprotocol/relay-kit/commit/b59d911)) ### RelayKit **UI Kit `6.0.4`** — Patch * Fix mobile ux ([`09031d5`](https://github.com/relayprotocol/relay-kit/commit/09031d5)) ### RelayKit **UI Kit `6.0.3`** — Patch * Improved mobile actions ([`68788cf`](https://github.com/relayprotocol/relay-kit/commit/68788cf)) ### RelayKit **UI Kit `6.0.2`** — Patch * Improve token widget ([`9e1f80a`](https://github.com/relayprotocol/relay-kit/commit/9e1f80a)) ### RelayKit **UI Kit `6.0.1`** — Patch * Enable broader support for gas top up ([`6fc5b9b`](https://github.com/relayprotocol/relay-kit/commit/6fc5b9b)) ### RelayKit **UI Kit `6.0.0`** — Major * Add TokenWidget ([`dbb99e3`](https://github.com/relayprotocol/relay-kit/commit/dbb99e3)) ### RelayKit **UI Kit `5.1.2`** — Patch * New mobile token selector ([`9a0dc7c`](https://github.com/relayprotocol/relay-kit/commit/9a0dc7c)) ### RelayKit **SDK `3.1.0`, UI Kit `5.1.0`, Tron adapter `1.0.0`** — Minor * Add Tron adapter and balance logic ([`5108307`](https://github.com/relayprotocol/relay-kit/commit/5108307)) **UI Kit `5.1.1`** — Patch * Update mobile percentage buttons ([`70badbb`](https://github.com/relayprotocol/relay-kit/commit/70badbb)) **UI Kit `5.1.1`** — Patch * Improve mobile ui for slippage configuration ([`5c80c9a`](https://github.com/relayprotocol/relay-kit/commit/5c80c9a)) **UI Kit `5.1.1`** — Patch * Add clear input button to custom address modal ([`38ea2b6`](https://github.com/relayprotocol/relay-kit/commit/38ea2b6)) ### RelayKit **SDK `3.0.1`** — Patch * Allow getQuote to pass in headers ([`028701b`](https://github.com/relayprotocol/relay-kit/commit/028701b)) ### RelayKit **UI Kit `5.0.5`** — Patch * Improve sponsored tokens logic by switching to more flexible useSecureApi method ([`8ca6ff5`](https://github.com/relayprotocol/relay-kit/commit/8ca6ff5)) ### RelayKit **UI Kit `5.0.4`** — Patch * Improved fill time display on success screen ([`2fcce34`](https://github.com/relayprotocol/relay-kit/commit/2fcce34)) **UI Kit `5.0.4`** — Patch * Display lowercase 's' for seconds on success screen ([`d830300`](https://github.com/relayprotocol/relay-kit/commit/d830300)) **UI Kit `5.0.4`** — Patch * Improved token amount formatting on success screen ([`ae3e3bb`](https://github.com/relayprotocol/relay-kit/commit/ae3e3bb)) ### RelayKit **UI Kit `5.0.3`** — Patch * Fix starred chains feature bugs ([`d49b6e5`](https://github.com/relayprotocol/relay-kit/commit/d49b6e5)) **UI Kit `5.0.2`** — Patch * Add starred chains feature to token selector ([`7784329`](https://github.com/relayprotocol/relay-kit/commit/7784329)) ### RelayKit **SDK `3.0.0`, UI Kit `5.0.0`** — Major * Revamped transaction steps ([`d38abdf`](https://github.com/relayprotocol/relay-kit/commit/d38abdf)) **UI Kit `5.0.1`** — Patch * Improved copy for tx steps ([`e43a756`](https://github.com/relayprotocol/relay-kit/commit/e43a756)) **UI Kit `5.0.0`** — Patch * Fix duplicate deposit success event ([`3bd044c`](https://github.com/relayprotocol/relay-kit/commit/3bd044c)) ### RelayKit **SDK `2.4.6`** — Patch * Fix tenderly error api ([`5135cd1`](https://github.com/relayprotocol/relay-kit/commit/5135cd1)) **UI Kit `4.0.19`** — Patch * Save recent custom addresses to local storage ([`f54d1f3`](https://github.com/relayprotocol/relay-kit/commit/f54d1f3)) **UI Kit `4.0.19`** — Patch * Use fill time on success screen ([`cda3f6d`](https://github.com/relayprotocol/relay-kit/commit/cda3f6d)) ### RelayKit **SDK `2.4.5`, UI Kit `4.0.18`, Bitcoin adapter `10.0.6`** — Patch * Add bitcoin public key to quote params ([`e3a9e1d`](https://github.com/relayprotocol/relay-kit/commit/e3a9e1d)) **SDK `2.4.5`, UI Kit `4.0.18`** — Patch * Refactor max slippage ui to be more pronounced ([`967e95b`](https://github.com/relayprotocol/relay-kit/commit/967e95b)) **UI Kit `4.0.17`** — Patch * Add transaction count check for explicit deposits ([`eb3a8fb`](https://github.com/relayprotocol/relay-kit/commit/eb3a8fb)) **UI Kit `4.0.17`** — Patch * Update swap event data ([`1ad6ac3`](https://github.com/relayprotocol/relay-kit/commit/1ad6ac3)) ### RelayKit **UI Kit `4.0.16`** — Patch * Remove protocol v2 threshold ([`686070b`](https://github.com/relayprotocol/relay-kit/commit/686070b)) ### RelayKit **UI Kit `4.0.15`** — Patch * Update gas top up copy ([`99516bb`](https://github.com/relayprotocol/relay-kit/commit/99516bb)) **UI Kit `4.0.14`** — Patch * Update gas top up logic and ui ([`2108fb8`](https://github.com/relayprotocol/relay-kit/commit/2108fb8)) **UI Kit `4.0.14`** — Patch * Tweak logic and ui for gas top up ([`ffd4dd2`](https://github.com/relayprotocol/relay-kit/commit/ffd4dd2)) ### RelayKit **UI Kit `4.0.13`** — Patch * Increase protocol v2 threshold to \$10k ([`42ef53d`](https://github.com/relayprotocol/relay-kit/commit/42ef53d)) ### RelayKit **UI Kit `4.0.12`** — Patch * Fix explicit deposit for zero native balance wallets ([`b3a83ee`](https://github.com/relayprotocol/relay-kit/commit/b3a83ee)) **UI Kit `4.0.11`** — Patch * Increase protocolv2 threshold ([`99a32eb`](https://github.com/relayprotocol/relay-kit/commit/99a32eb)) **UI Kit `4.0.10`** — Patch * Reimplement explicitDeposit logic ([`bbee152`](https://github.com/relayprotocol/relay-kit/commit/bbee152)) ### RelayKit **UI Kit `4.0.9`** — Patch * Remove explicitDeposit parameter ([`4749b3e`](https://github.com/relayprotocol/relay-kit/commit/4749b3e)) **UI Kit `4.0.9`** — Patch * Remove gas top up re-enabling on swap success ([`0ec9be8`](https://github.com/relayprotocol/relay-kit/commit/0ec9be8)) ### RelayKit **SDK `2.4.4`** — Patch * Add EOA detection ([`7b8f325`](https://github.com/relayprotocol/relay-kit/commit/7b8f325)) **UI Kit `4.0.8`** — Patch * Fix issue detecting EOA when disconnected ([`2467b30`](https://github.com/relayprotocol/relay-kit/commit/2467b30)) **UI Kit `4.0.7`** — Patch * Fix bug where logged out scenario not fetching quotes ([`1bd5324`](https://github.com/relayprotocol/relay-kit/commit/1bd5324)) **UI Kit `4.0.6`** — Patch * Add character minimum for conversion rate tooltip ([`7e99ce4`](https://github.com/relayprotocol/relay-kit/commit/7e99ce4)) ### RelayKit **Hooks `2.0.2`** — Patch * Fix base url for query quote function ([`622c094`](https://github.com/relayprotocol/relay-kit/commit/622c094)) ### RelayKit **UI Kit `4.0.4`** — Patch * Fix fee subsidization normalization ([`d502596`](https://github.com/relayprotocol/relay-kit/commit/d502596)) ### RelayKit **UI Kit `4.0.3`** — Patch * Add button cta font styling to theme ([`4142310`](https://github.com/relayprotocol/relay-kit/commit/4142310)) ### RelayKit **SDK `2.4.3`** — Patch * Update capabilities check for batching to support MM ([`dd8a006`](https://github.com/relayprotocol/relay-kit/commit/dd8a006)) **UI Kit `4.0.2`** — Patch * Porto wallet compatibility ([`e7d50bb`](https://github.com/relayprotocol/relay-kit/commit/e7d50bb)) **UI Kit `4.0.1`** — Patch * Remove custom fonts from ui kit base theme ([`41d48e1`](https://github.com/relayprotocol/relay-kit/commit/41d48e1)) ### RelayKit **UI Kit `4.0.0`, Hooks `2.0.0`** — Major * Upgrade to React 19 ([`581937b`](https://github.com/relayprotocol/relay-kit/commit/581937b)) **UI Kit `3.0.3`** — Patch * Switch to using queryQuote instead of useQuote hook ([`8dc4d4b`](https://github.com/relayprotocol/relay-kit/commit/8dc4d4b)) **UI Kit `3.0.3`** — Patch * Add test ids for automation ([`dc26d7b`](https://github.com/relayprotocol/relay-kit/commit/dc26d7b)) ### RelayKit **UI Kit `3.0.2`** — Patch * Enable protocolv2 only if destination chain supports settlement ([`34fa345`](https://github.com/relayprotocol/relay-kit/commit/34fa345)) **UI Kit `3.0.2`** — Patch * Fix share icon color ([`de102bb`](https://github.com/relayprotocol/relay-kit/commit/de102bb)) **Bitcoin adapter `10.0.3`** — Patch * Allow default sighash when sending tx ([`7615501`](https://github.com/relayprotocol/relay-kit/commit/7615501)) ### RelayKit **UI Kit `3.0.1`** — Patch * Add data tracking for gas subsidization ([`0dae775`](https://github.com/relayprotocol/relay-kit/commit/0dae775)) ### RelayKit **SDK `2.4.2`, Bitcoin adapter `10.0.2`, Ethers adapter `22.0.2`, SVM adapter `11.0.2`** — Patch * Update relay branding ([`2ad3c52`](https://github.com/relayprotocol/relay-kit/commit/2ad3c52)) **UI Kit `3.0.0`** — Major * Update relay branding ([`2ad3c52`](https://github.com/relayprotocol/relay-kit/commit/2ad3c52)) **Hooks `1.13.0`** — Minor * Update relay branding ([`2ad3c52`](https://github.com/relayprotocol/relay-kit/commit/2ad3c52)) ### RelayKit **UI Kit `2.17.5`** — Patch * Fix modal gap on mobile ([`5c97c8d`](https://github.com/relayprotocol/relay-kit/commit/5c97c8d)) ### RelayKit **SDK `2.4.1`, UI Kit `2.17.4`, Hooks `1.12.1`** — Patch * Add gas sponsorship functionality ([`fd0b328`](https://github.com/relayprotocol/relay-kit/commit/fd0b328)) ### RelayKit **UI Kit `2.17.3`** — Patch * Always show price impact regardless of input mode ([`b2d6c8c`](https://github.com/relayprotocol/relay-kit/commit/b2d6c8c)) ### RelayKit **UI Kit `2.17.2`** — Patch * Reenable solana for protocolv2 ([`8a8d560`](https://github.com/relayprotocol/relay-kit/commit/8a8d560)) ### RelayKit **UI Kit `2.17.1`** — Patch * Disable protocol v2 on solana ([`a50bea6`](https://github.com/relayprotocol/relay-kit/commit/a50bea6)) ### RelayKit **SDK `2.4.0`, UI Kit `2.17.0`, Hooks `1.12.0`, Bitcoin adapter `10.0.0`, Ethers adapter `22.0.0`, SVM adapter `11.0.0`** — Minor * Add websocket support and refactor executeSteps ([`6400542`](https://github.com/relayprotocol/relay-kit/commit/6400542)) ### RelayKit **UI Kit `2.16.2`** — Patch * Increase prefers v2 threshold to \$100 ([`c309276`](https://github.com/relayprotocol/relay-kit/commit/c309276)) ### RelayKit **UI Kit `2.16.1`** — Patch * revert overflow fix ([`a284af5`](https://github.com/relayprotocol/relay-kit/commit/a284af5)) ### RelayKit **UI Kit `2.16.0`** — Minor * Fix token amount overflow on success screen ([`bb08004`](https://github.com/relayprotocol/relay-kit/commit/bb08004)) **UI Kit `2.16.0`** — Patch * Fix Hyperliquid wallet compatability ([`09ccc5c`](https://github.com/relayprotocol/relay-kit/commit/09ccc5c)) **UI Kit `2.16.0`** — Patch * Bump USD threshold for protocolv2 logic ([`2669722`](https://github.com/relayprotocol/relay-kit/commit/2669722)) **UI Kit `2.16.0`** — Patch * Improve ui when no route are available ([`a437ede`](https://github.com/relayprotocol/relay-kit/commit/a437ede)) ### RelayKit **UI Kit `2.15.11`** — Patch * Add refundTo for HL withdrawals ([`762dc3c`](https://github.com/relayprotocol/relay-kit/commit/762dc3c)) ### RelayKit **SDK `2.3.2`, UI Kit `2.15.10`** — Patch * Add hyperliquid usd send functionality ([`1a88de0`](https://github.com/relayprotocol/relay-kit/commit/1a88de0)) **UI Kit `2.15.10`** — Patch * Prevent onAnalyticEvent from throwing errors internally ([`dce6e56`](https://github.com/relayprotocol/relay-kit/commit/dce6e56)) ### RelayKit **SDK `2.3.1`, UI Kit `2.15.9`** — Patch * Enable protocol v2 for chains programatically ([`d1aeceb`](https://github.com/relayprotocol/relay-kit/commit/d1aeceb)) **UI Kit `2.15.9`** — Patch * Fix bug with selecting token from trending tokens list ([`cfad35b`](https://github.com/relayprotocol/relay-kit/commit/cfad35b)) **UI Kit `2.15.9`** — Patch * Add context to CURRENCY\_STEP\_CHAIN\_FILTER event ([`2bc1938`](https://github.com/relayprotocol/relay-kit/commit/2bc1938)) ### RelayKit **UI Kit `2.15.8`** — Patch * Add log to AGW check error ([`fbb50ac`](https://github.com/relayprotocol/relay-kit/commit/fbb50ac)) **UI Kit `2.15.8`** — Patch * Add usd values to swap events ([`1627058`](https://github.com/relayprotocol/relay-kit/commit/1627058)) **UI Kit `2.15.8`** — Patch * Use max slippage between user input and quote for display ([`54e4628`](https://github.com/relayprotocol/relay-kit/commit/54e4628)) **UI Kit `2.15.8`, Hooks `1.11.1`** — Patch * Add relay trending tokens to token selector ([`7985e6b`](https://github.com/relayprotocol/relay-kit/commit/7985e6b)) **UI Kit `2.15.8`** — Patch * Enable more v2 protocol chains ([`a95050e`](https://github.com/relayprotocol/relay-kit/commit/a95050e)) ### RelayKit **UI Kit `2.15.7`** — Patch * Add more protocol v2 chains ([`96351f0`](https://github.com/relayprotocol/relay-kit/commit/96351f0)) ### RelayKit **UI Kit `2.15.6`** — Patch * Address accessibility issues ([`506f105`](https://github.com/relayprotocol/relay-kit/commit/506f105)) **UI Kit `2.15.6`** — Patch * Add protocol v2 enabled chains ([`ff38840`](https://github.com/relayprotocol/relay-kit/commit/ff38840)) ### RelayKit **UI Kit `2.15.5`** — Patch * Enable preferv2 protocol for more chains (Ethereum, Polygon, BNB, OP, Zksync) ([`7e4273d`](https://github.com/relayprotocol/relay-kit/commit/7e4273d)) ### RelayKit **UI Kit `2.15.4`** — Patch * Reenable prefers protocolv2 for a subset of chains ([`1aae32c`](https://github.com/relayprotocol/relay-kit/commit/1aae32c)) ### RelayKit **SVM adapter `10.0.1`** — Patch * Add an optional payerKey to customize gas payer ([`e251ccd`](https://github.com/relayprotocol/relay-kit/commit/e251ccd)) ### RelayKit **UI Kit `2.15.3`** — Patch * Disable protocolv2 usage ([`4334b2f`](https://github.com/relayprotocol/relay-kit/commit/4334b2f)) ### RelayKit **UI Kit `2.15.2`** — Patch * Enable prefersV2 for Avalanche, unichain and Gnosis ([`be1ec2c`](https://github.com/relayprotocol/relay-kit/commit/be1ec2c)) **UI Kit `2.15.1`** — Patch * Disable gas top up by default ([`c8ef9b5`](https://github.com/relayprotocol/relay-kit/commit/c8ef9b5)) ### RelayKit **SDK `2.3.0`, UI Kit `2.15.0`, Hooks `1.11.0`, Bitcoin adapter `9.0.0`, Ethers adapter `21.0.0`, SVM adapter `10.0.0`** — Minor * Upgrade viem to >=2.26.0 ([`aee47ea`](https://github.com/relayprotocol/relay-kit/commit/aee47ea)) **SDK `2.2.0`, UI Kit `2.14.0`** — Minor * Upgrade sdk types and add custom logic for blast protocol v2 configuration ([`3dbf1be`](https://github.com/relayprotocol/relay-kit/commit/3dbf1be)) **UI Kit `2.14.0`** — Patch * Check custom address against CEX address list ([`dad85e2`](https://github.com/relayprotocol/relay-kit/commit/dad85e2)) ### RelayKit **UI Kit `2.13.3`** — Patch * Add warning for missing token price detection + add stroke to all alerts ([`1b67ee5`](https://github.com/relayprotocol/relay-kit/commit/1b67ee5)) ### RelayKit **UI Kit `2.13.2`** — Patch * Hide gas token badge for hyperliquid usdc ([`ac027bb`](https://github.com/relayprotocol/relay-kit/commit/ac027bb)) ### RelayKit **SDK `2.1.3`** — Patch * Add getAppFees and claimAppFees sdk actions ([`9e35bcd`](https://github.com/relayprotocol/relay-kit/commit/9e35bcd)) **UI Kit `2.13.0`** — Minor * Fix destination wallet alert showing without wallet selection ([`57c625b`](https://github.com/relayprotocol/relay-kit/commit/57c625b)) **UI Kit `2.13.0`** — Patch * Add themeScheme to the provider to improve SSR hydration ([`862ee79`](https://github.com/relayprotocol/relay-kit/commit/862ee79)) ### RelayKit **UI Kit `2.12.0`** — Minor * Switch to Dune's Sim api for token balances ([`e3e114e`](https://github.com/relayprotocol/relay-kit/commit/e3e114e)) **UI Kit `2.12.0`** — Patch * Hide display of batch txs on success step ([`9c81150`](https://github.com/relayprotocol/relay-kit/commit/9c81150)) **UI Kit `2.12.0`** — Patch * Improve usd fee display ([`b6e92a9`](https://github.com/relayprotocol/relay-kit/commit/b6e92a9)) **UI Kit `2.12.0`** — Patch * Adds toggle to switch between token amount and usd input ([`37479c3`](https://github.com/relayprotocol/relay-kit/commit/37479c3)) **UI Kit `2.12.0`, Hooks `1.10.4`** — Patch * Remove referrer from requests api calls ([`3471571`](https://github.com/relayprotocol/relay-kit/commit/3471571)) ### RelayKit **SDK `2.1.2`, UI Kit `2.11.3`, Hooks `1.10.2`** — Patch * Update endpoints ([`1d8da8a`](https://github.com/relayprotocol/relay-kit/commit/1d8da8a)) **SDK `2.1.2`, UI Kit `2.11.3`** — Patch * Add hypevm to vmtypes for destination ([`6049aa6`](https://github.com/relayprotocol/relay-kit/commit/6049aa6)) **UI Kit `2.11.3`** — Patch * Ellipsify conversion rate + add tooltip to prevent overflow ([`8d5e07c`](https://github.com/relayprotocol/relay-kit/commit/8d5e07c)) **UI Kit `2.11.3`** — Patch * Fix cropped token selector text ([`c3a562f`](https://github.com/relayprotocol/relay-kit/commit/c3a562f)) **UI Kit `2.11.3`** — Patch * Decrease high fee warning threshold to 1.5% ([`3aabf44`](https://github.com/relayprotocol/relay-kit/commit/3aabf44)) **Hooks `1.10.3`** — Patch * Allow headers to be configured from query functions ([`1da754c`](https://github.com/relayprotocol/relay-kit/commit/1da754c)) ### RelayKit **SDK `2.1.1`, UI Kit `2.11.2`** — Patch * Allow changing the polling interval for confirmation and improve RPC configuration ([`8b4754a`](https://github.com/relayprotocol/relay-kit/commit/8b4754a)) ### RelayKit **SDK `2.1.0`, UI Kit `2.11.0`, Hooks `1.10.0`** — Minor * Deduplicate request IDs ([`bb63fc9`](https://github.com/relayprotocol/relay-kit/commit/bb63fc9)) **SDK `2.1.0`** — Patch * Optimize same chain swapping to skip intent status if slower than receipt ([`758f8a7`](https://github.com/relayprotocol/relay-kit/commit/758f8a7)) **UI Kit `2.11.1`** — Patch * Fix solana address regex ([`f073269`](https://github.com/relayprotocol/relay-kit/commit/f073269)) **UI Kit `2.11.0`** — Patch * Add copy for new refund reason ([`73334f9`](https://github.com/relayprotocol/relay-kit/commit/73334f9)) **UI Kit `2.11.0`** — Patch * Add better handling for eclipse rpc balance errors ([`d0971ec`](https://github.com/relayprotocol/relay-kit/commit/d0971ec)) **UI Kit `2.11.0`** — Patch * Allow selecting wallet before selecting chain ([`b30f584`](https://github.com/relayprotocol/relay-kit/commit/b30f584)) **UI Kit `2.11.0`** — Patch * Add context to wallet\_selector events ([`42aa03d`](https://github.com/relayprotocol/relay-kit/commit/42aa03d)) **UI Kit `2.11.0`** — Patch * Fix distorted token images ([`dc9da43`](https://github.com/relayprotocol/relay-kit/commit/dc9da43)) **UI Kit `2.11.0`** — Patch * Capture quote to be used in TransactionModal ([`4d75f3a`](https://github.com/relayprotocol/relay-kit/commit/4d75f3a)) ### RelayKit **UI Kit `2.10.12`** — Patch * Fix tx confirmation issue preventing the error screen from showing ([`c40a8ea`](https://github.com/relayprotocol/relay-kit/commit/c40a8ea)) ### RelayKit **UI Kit `2.10.11`** — Patch * Allow slippage to be set to 0.01 in the slippage configuration component ([`df9ec72`](https://github.com/relayprotocol/relay-kit/commit/df9ec72)) **UI Kit `2.10.11`** — Patch * Hide amount usd if returned value is 0 ([`6fa5e73`](https://github.com/relayprotocol/relay-kit/commit/6fa5e73)) **UI Kit `2.10.11`** — Patch * Use eclipse RPC for widget balances ([`eaeab61`](https://github.com/relayprotocol/relay-kit/commit/eaeab61)) ### RelayKit **UI Kit `2.10.10`** — Patch * Remove max width from suggested token pills ([`260725c`](https://github.com/relayprotocol/relay-kit/commit/260725c)) **UI Kit `2.10.10`** — Patch * Fix token selector scroll bug and improve dialog animation ([`665cc92`](https://github.com/relayprotocol/relay-kit/commit/665cc92)) ### RelayKit **SDK `2.0.3`, UI Kit `2.10.9`** — Patch * Improve DEPOSIT\_SUCCESS logic by checking pending status ([`dd0f93c`](https://github.com/relayprotocol/relay-kit/commit/dd0f93c)) ### RelayKit **SDK `2.0.2`** — Patch * Sync api types ([`e169c87`](https://github.com/relayprotocol/relay-kit/commit/e169c87)) **UI Kit `2.10.8`** — Patch * Fix broken suggested token logoUrl ([`dc09164`](https://github.com/relayprotocol/relay-kit/commit/dc09164)) **UI Kit `2.10.8`** — Patch * add AVAX to ME wallet compatibility ([`43bee2a`](https://github.com/relayprotocol/relay-kit/commit/43bee2a)) **UI Kit `2.10.7`** — Patch * Fix status code data point for quote\_error ([`a4f7deb`](https://github.com/relayprotocol/relay-kit/commit/a4f7deb)) ### RelayKit **SDK `2.0.1`, UI Kit `2.10.6`, Hooks `1.9.11`, Bitcoin adapter `6.0.1`, Ethers adapter `18.0.1`, SVM adapter `7.0.1`** — Patch * Lock viem version to patch only to avoid breaking changes ([`dab775f`](https://github.com/relayprotocol/relay-kit/commit/dab775f)) **UI Kit `2.10.6`** — Patch * Add status to quote error ([`1df049b`](https://github.com/relayprotocol/relay-kit/commit/1df049b)) **UI Kit `2.10.6`** — Patch * Adjust gas top up copy ([`e572905`](https://github.com/relayprotocol/relay-kit/commit/e572905)) ### RelayKit **SDK `2.0.0`** — Major * \[BREAKING] Update sdk getQuote method to optionally include defaults ([`5759fd0`](https://github.com/relayprotocol/relay-kit/commit/5759fd0)) **SDK `1.8.1`** — Patch * Sync api types ([`e5d8e39`](https://github.com/relayprotocol/relay-kit/commit/e5d8e39)) **UI Kit `2.10.5`** — Patch * Fix receipt validation ([`2ee2f44`](https://github.com/relayprotocol/relay-kit/commit/2ee2f44)) **UI Kit `2.10.4`** — Patch * Fix gas top up bigint error ([`eed08f3`](https://github.com/relayprotocol/relay-kit/commit/eed08f3)) **Ethers adapter `18.0.0`** — Patch * \[BREAKING] Update sdk getQuote method to optionally include defaults ([`5759fd0`](https://github.com/relayprotocol/relay-kit/commit/5759fd0)) ### RelayKit **UI Kit `2.10.3`** — Patch * Fix race condition with events ([`b74d695`](https://github.com/relayprotocol/relay-kit/commit/b74d695)) ### RelayKit **UI Kit `2.10.2`** — Patch * Add quote\_request\_id to all swap events ([`09be9a1`](https://github.com/relayprotocol/relay-kit/commit/09be9a1)) **UI Kit `2.10.1`** — Patch * Fix issues with quote\_id ([`1c1c05c`](https://github.com/relayprotocol/relay-kit/commit/1c1c05c)) ### RelayKit **SDK `1.8.0`, UI Kit `2.10.0`** — Minor * Overhaul analytics events ([`d430ab3`](https://github.com/relayprotocol/relay-kit/commit/d430ab3)) **UI Kit `2.10.0`** — Patch * Add interval to regenerate hash every 15m ([`47a0157`](https://github.com/relayprotocol/relay-kit/commit/47a0157)) **UI Kit `2.10.0`** — Patch * Refresh quote data when tx modal closes ([`16beb2e`](https://github.com/relayprotocol/relay-kit/commit/16beb2e)) ### RelayKit **SDK `1.7.4`, UI Kit `2.9.14`, SVM adapter `5.0.4`** — Patch * Add sui wallet adapter ([`ee3693a`](https://github.com/relayprotocol/relay-kit/commit/ee3693a)) **UI Kit `2.9.14`** — Patch * Switch to sha256 from murmur3 to fix esm compatibility issue ([`83769eb`](https://github.com/relayprotocol/relay-kit/commit/83769eb)) **UI Kit `2.9.14`** — Patch * Fix address comparison logic in MultiWalletDropdown ([`3ae300a`](https://github.com/relayprotocol/relay-kit/commit/3ae300a)) ### RelayKit **SDK `1.7.3`** — Patch * Add ability to override default logger ([`bc85ead`](https://github.com/relayprotocol/relay-kit/commit/bc85ead)) **UI Kit `2.9.13`, Hooks `1.9.6`** — Patch * Add a quoteRequestId when requests and receiving a quote ([`052745e`](https://github.com/relayprotocol/relay-kit/commit/052745e)) **UI Kit `2.9.13`, Hooks `1.9.6`** — Patch * Improve quote and swap analytics ([`fe54a20`](https://github.com/relayprotocol/relay-kit/commit/fe54a20)) **UI Kit `2.9.13`** — Patch * Rename quote events ([`c3c4a53`](https://github.com/relayprotocol/relay-kit/commit/c3c4a53)) ### RelayKit **SDK `1.7.2`** — Patch * Fix add ethereum chain errors ([`4fb4ac3`](https://github.com/relayprotocol/relay-kit/commit/4fb4ac3)) **UI Kit `2.9.12`** — Patch * Display estimated USD value for input token before swap quote is available ([`fe2398d`](https://github.com/relayprotocol/relay-kit/commit/fe2398d)) ### RelayKit **SDK `1.7.1`, UI Kit `2.9.11`** — Patch * Add fallback mechanism when InternalJSONRPC occurrs ([`effd487`](https://github.com/relayprotocol/relay-kit/commit/effd487)) **SDK `1.7.1`, UI Kit `2.9.11`** — Patch * Sync sdk types and update sugested tokens to use logoURI from featured tokens ([`af85e2a`](https://github.com/relayprotocol/relay-kit/commit/af85e2a)) **UI Kit `2.9.11`** — Patch * Render fallback token placeholder using symbol initial ([`afdb901`](https://github.com/relayprotocol/relay-kit/commit/afdb901)) **UI Kit `2.9.11`** — Patch * Add sei to ME wallet compatibility ([`b2af4e9`](https://github.com/relayprotocol/relay-kit/commit/b2af4e9)) **UI Kit `2.9.11`** — Patch * Max Button gas estimation improvements for evm ([`6416987`](https://github.com/relayprotocol/relay-kit/commit/6416987)) ### RelayKit **SDK `1.7.0`** — Minor * Gas top up functionality ([`6940a55`](https://github.com/relayprotocol/relay-kit/commit/6940a55)) **UI Kit `2.9.10`, Hooks `1.9.3`** — Patch * Gas top up functionality ([`6940a55`](https://github.com/relayprotocol/relay-kit/commit/6940a55)) ### RelayKit **SDK `1.6.15`, UI Kit `2.9.9`, Hooks `1.9.2`** — Patch * Add additional refund reason messaging ([`293486b`](https://github.com/relayprotocol/relay-kit/commit/293486b)) **UI Kit `2.9.9`** — Patch * Add ronin wallet compatibility ([`9868461`](https://github.com/relayprotocol/relay-kit/commit/9868461)) **UI Kit `2.9.9`** — Patch * Upgrade to new dune svm balance api ([`b2187e1`](https://github.com/relayprotocol/relay-kit/commit/b2187e1)) ### RelayKit **UI Kit `2.9.8`** — Patch * Reset swap widget inputs after a successful swap ([`a8ecc87`](https://github.com/relayprotocol/relay-kit/commit/a8ecc87)) **UI Kit `2.9.8`** — Patch * Fix slice error on confirmation screens ([`d243416`](https://github.com/relayprotocol/relay-kit/commit/d243416)) **UI Kit `2.9.8`** — Patch * Handle rejecting tx in AGW ([`0e55b40`](https://github.com/relayprotocol/relay-kit/commit/0e55b40)) ### RelayKit **SDK `1.6.14`, UI Kit `2.9.6`** — Patch * Add endpoint to APIError for debugging ([`b2782e9`](https://github.com/relayprotocol/relay-kit/commit/b2782e9)) **UI Kit `2.9.7`** — Patch * Fix chain scroll bug ([`ed9cddb`](https://github.com/relayprotocol/relay-kit/commit/ed9cddb)) **UI Kit `2.9.6`** — Patch * Fix chain filter scroll into view ([`74929ec`](https://github.com/relayprotocol/relay-kit/commit/74929ec)) **UI Kit `2.9.6`** — Patch * Add additional data to token select exit event ([`975b6d1`](https://github.com/relayprotocol/relay-kit/commit/975b6d1)) ### RelayKit **UI Kit `2.9.5`** — Patch * Prevent AGW from receiving incompatible funds ([`f0ed8bc`](https://github.com/relayprotocol/relay-kit/commit/f0ed8bc)) ### RelayKit **UI Kit `2.9.4`** — Patch * Fix token symbol overflow within token selector ([`9eef113`](https://github.com/relayprotocol/relay-kit/commit/9eef113)) **UI Kit `2.9.4`** — Patch * Add privateChainIds to RelayKitProvider options ([`e5d2bb9`](https://github.com/relayprotocol/relay-kit/commit/e5d2bb9)) **UI Kit `2.9.4`** — Patch * Add dropdown-border theme element ([`be993e3`](https://github.com/relayprotocol/relay-kit/commit/be993e3)) ### RelayKit **UI Kit `2.9.3`** — Patch * Fix useCurrencyBalnce hook bug ([`f7a90ba`](https://github.com/relayprotocol/relay-kit/commit/f7a90ba)) ### RelayKit **UI Kit `2.9.2`** — Patch * Update analytic events for token selector ([`e0f74be`](https://github.com/relayprotocol/relay-kit/commit/e0f74be)) **UI Kit `2.9.2`** — Patch * Fix undefined token setting in swap widget ([`ca56384`](https://github.com/relayprotocol/relay-kit/commit/ca56384)) ### RelayKit **SDK `1.6.13`** — Patch * Breaking Changes: ([`bf8dbdb`](https://github.com/relayprotocol/relay-kit/commit/bf8dbdb)) * `relay-kit-hooks`: Updated `useTokenLists` hook to use `/currencies/v2` API with new response structure * `relay-kit-ui`: Redesigned token selector component with improved architecture * Removed chain selector in favor of unified token selector component * Removed `defaultToToken` and `defaultFromToken` props * Added `toToken`, `setToToken`, `fromToken`, `setFromToken` props * Added `disableInputAutoFocus` and `popularChainIds` configuration options These changes improve token selection UX, provide better state management, and enable more flexible chain configuration. **UI Kit `2.9.1`** — Patch * Fix token setters ([`40c1910`](https://github.com/relayprotocol/relay-kit/commit/40c1910)) **UI Kit `2.9.0`, Hooks `1.9.0`** — Minor * Breaking Changes: ([`bf8dbdb`](https://github.com/relayprotocol/relay-kit/commit/bf8dbdb)) * `relay-kit-hooks`: Updated `useTokenLists` hook to use `/currencies/v2` API with new response structure * `relay-kit-ui`: Redesigned token selector component with improved architecture * Removed chain selector in favor of unified token selector component * Removed `defaultToToken` and `defaultFromToken` props * Added `toToken`, `setToToken`, `fromToken`, `setFromToken` props * Added `disableInputAutoFocus` and `popularChainIds` configuration options These changes improve token selection UX, provide better state management, and enable more flexible chain configuration. **UI Kit `2.9.0`** — Patch * Allow dune base api to fallback to default ([`12c8e59`](https://github.com/relayprotocol/relay-kit/commit/12c8e59)) ### RelayKit **UI Kit `2.8.0`** — Minor * Refactor dune api config ([`ccff74e`](https://github.com/relayprotocol/relay-kit/commit/ccff74e)) **UI Kit `2.8.0`** — Patch * OnrampModal: reset recipient if vm changes and recipient is now invalid ([`7e7df25`](https://github.com/relayprotocol/relay-kit/commit/7e7df25)) **UI Kit `2.8.0`** — Patch * Add a prop to disable paste address wallet option ([`36bad1d`](https://github.com/relayprotocol/relay-kit/commit/36bad1d)) **UI Kit `2.8.0`** — Patch * Fix conversion rate display bug ([`cff46c3`](https://github.com/relayprotocol/relay-kit/commit/cff46c3)) **UI Kit `2.8.0`** — Patch * Add a short cache for balance queries to optimize dune usage ([`5b70dc4`](https://github.com/relayprotocol/relay-kit/commit/5b70dc4)) ### RelayKit **SDK `1.6.12`** — Patch * Fix hyperevm chain configuration ([`66b8de8`](https://github.com/relayprotocol/relay-kit/commit/66b8de8)) ### RelayKit **SDK `1.6.11`, UI Kit `2.7.18`** — Patch * Update API SDK types, fix react 19 component type issues ([`0af5808`](https://github.com/relayprotocol/relay-kit/commit/0af5808)) ### RelayKit **SDK `1.6.10`, UI Kit `2.7.16`** — Patch * Add Sui VM support ([`769d648`](https://github.com/relayprotocol/relay-kit/commit/769d648)) **UI Kit `2.7.17`** — Patch * Improve OnrampModal robustness ([`dfbca22`](https://github.com/relayprotocol/relay-kit/commit/dfbca22)) **UI Kit `2.7.17`** — Patch * Always return QR code for deposit address flow ([`16c07ed`](https://github.com/relayprotocol/relay-kit/commit/16c07ed)) ### RelayKit **UI Kit `2.7.15`** — Patch * Uniswap wallet compatability configurations ([`05733be`](https://github.com/relayprotocol/relay-kit/commit/05733be)) **UI Kit `2.7.15`** — Patch * Fix max capacity error message not shown ([`93331ae`](https://github.com/relayprotocol/relay-kit/commit/93331ae)) ### RelayKit **SDK `1.6.9`, UI Kit `2.7.14`, Hooks `1.8.5`** — Patch * Add scripts for statically bundling versions ([`bc77582`](https://github.com/relayprotocol/relay-kit/commit/bc77582)) **SDK `1.6.9`, UI Kit `2.7.14`** — Patch * Handle custom block explorer transaction page ([`cdc8d92`](https://github.com/relayprotocol/relay-kit/commit/cdc8d92)) **UI Kit `2.7.14`** — Patch * Detect changes to destination chain and select supporting wallet ([`7f6c958`](https://github.com/relayprotocol/relay-kit/commit/7f6c958)) **UI Kit `2.7.14`** — Patch * Improve Deposit address logic in TokenSelector ([`842b547`](https://github.com/relayprotocol/relay-kit/commit/842b547)) **UI Kit `2.7.14`** — Patch * Fix a bug where changing the chain to a different vm would not find the supported linked wallet ([`72f7f75`](https://github.com/relayprotocol/relay-kit/commit/72f7f75)) ### RelayKit **SDK `1.6.8`** — Patch * Update Tron dead address ([`3993eb3`](https://github.com/relayprotocol/relay-kit/commit/3993eb3)) **UI Kit `2.7.13`** — Patch * Add analytic events for chain select ([`3b733af`](https://github.com/relayprotocol/relay-kit/commit/3b733af)) **UI Kit `2.7.13`** — Patch * Rename connect CTA to "Connect Wallet" ([`38bfb4c`](https://github.com/relayprotocol/relay-kit/commit/38bfb4c)) **UI Kit `2.7.13`** — Patch * Fix approval tx display on swap confirmation screen ([`2b603a3`](https://github.com/relayprotocol/relay-kit/commit/2b603a3)) **UI Kit `2.7.12`** — Patch * Remove duplicate price impact warning ([`8376d7f`](https://github.com/relayprotocol/relay-kit/commit/8376d7f)) **UI Kit `2.7.12`** — Patch * Fix bug where deposit address origin prevented connecting a destination wallet ([`63be014`](https://github.com/relayprotocol/relay-kit/commit/63be014)) ### RelayKit **UI Kit `2.7.11`** — Patch * Fix default unverified tokens not prompting approval ([`00d34dd`](https://github.com/relayprotocol/relay-kit/commit/00d34dd)) ### RelayKit **SDK `1.6.7`, UI Kit `2.7.9`** — Patch * Tron support ([`f1f6d42`](https://github.com/relayprotocol/relay-kit/commit/f1f6d42)) **SDK `1.6.6`** — Patch * Fix sender sometimes being set to dead address ([`b0d1c88`](https://github.com/relayprotocol/relay-kit/commit/b0d1c88)) **UI Kit `2.7.10`** — Patch * Fix tvm filter ([`0cad99d`](https://github.com/relayprotocol/relay-kit/commit/0cad99d)) **Hooks `1.8.2`** — Patch * Swallow useQuote error if onError handler is passed ([`7a675e3`](https://github.com/relayprotocol/relay-kit/commit/7a675e3)) ### RelayKit **SDK `1.6.5`** — Patch * Check tx confirmation error with tenderly api to enhance error data ([`e0133b1`](https://github.com/relayprotocol/relay-kit/commit/e0133b1)) **UI Kit `2.7.7`** — Patch * Ensure quote is always fresh when TransactionModal closes ([`62952a9`](https://github.com/relayprotocol/relay-kit/commit/62952a9)) **UI Kit `2.7.6`** — Patch * Fix transaction validating event getting triggered multiple times ([`4f5f328`](https://github.com/relayprotocol/relay-kit/commit/4f5f328)) ### RelayKit **UI Kit `2.7.5`** — Patch * Dune token api chain\_ids update ([`5107aee`](https://github.com/relayprotocol/relay-kit/commit/5107aee)) **UI Kit `2.7.5`** — Patch * Remove 'Review Quote' step and consolidate tx flow into one ui state ([`a173c5a`](https://github.com/relayprotocol/relay-kit/commit/a173c5a)) ### RelayKit **UI Kit `2.7.4`** — Patch * Fixes for suggested tokens and default token ([`99c4dbd`](https://github.com/relayprotocol/relay-kit/commit/99c4dbd)) **UI Kit `2.7.3`** — Patch * Fix default selected token ([`5eefe1f`](https://github.com/relayprotocol/relay-kit/commit/5eefe1f)) **UI Kit `2.7.3`** — Patch * Wallet compatibility for major wallets and abstract address detection ([`a662444`](https://github.com/relayprotocol/relay-kit/commit/a662444)) ### RelayKit **UI Kit `2.7.2`** — Patch * Update slippage tolerance logic to handle remote swaps ([`3a6fdd7`](https://github.com/relayprotocol/relay-kit/commit/3a6fdd7)) ### RelayKit **SDK `1.6.4`** — Patch * SDK config option to omit gas fee estimates ([`0ccd0d7`](https://github.com/relayprotocol/relay-kit/commit/0ccd0d7)) **UI Kit `2.7.1`** — Patch * Fix dialog and dropdown menu incompatibility ([`5af363f`](https://github.com/relayprotocol/relay-kit/commit/5af363f)) **UI Kit `2.7.0`, Hooks `1.8.0`** — Minor * Remove usePrice hook, queryPrice action, and PriceResponse type ([`df2820a`](https://github.com/relayprotocol/relay-kit/commit/df2820a)) **UI Kit `2.7.0`** — Patch * Remap max liquidity error ([`2aaf42a`](https://github.com/relayprotocol/relay-kit/commit/2aaf42a)) ### RelayKit **SDK `1.6.3`, UI Kit `2.6.11`** — Patch * Handle confirmation error appropriately ([`a601dfa`](https://github.com/relayprotocol/relay-kit/commit/a601dfa)) ### RelayKit **UI Kit `2.6.10`** — Patch * Fix slippage displayed for same-chain-swaps ([`5be1e1d`](https://github.com/relayprotocol/relay-kit/commit/5be1e1d)) **UI Kit `2.6.9`** — Patch * Fix slippage config bug on rerender ([`6772e0f`](https://github.com/relayprotocol/relay-kit/commit/6772e0f)) **UI Kit `2.6.8`** — Patch * Add slippage configuration component ([`c876fdf`](https://github.com/relayprotocol/relay-kit/commit/c876fdf)) **UI Kit `2.6.8`** — Patch * Fix ChainTokenIcon fallback dimensions ([`914af94`](https://github.com/relayprotocol/relay-kit/commit/914af94)) **UI Kit `2.6.8`** — Patch * Fix onramping dollar formatting ([`46e6c73`](https://github.com/relayprotocol/relay-kit/commit/46e6c73)) **UI Kit `2.6.8`** — Patch * Increase width of onramp widget ([`1cabbd1`](https://github.com/relayprotocol/relay-kit/commit/1cabbd1)) ### RelayKit **UI Kit `2.6.7`** — Patch * Hide fiat currency selector ([`a5e77cc`](https://github.com/relayprotocol/relay-kit/commit/a5e77cc)) **UI Kit `2.6.6`** — Patch * Add onTokenChange callback handler to OnrampWidget ([`a90dc24`](https://github.com/relayprotocol/relay-kit/commit/a90dc24)) ### RelayKit **SDK `1.6.2`, UI Kit `2.6.3`** — Patch * Sync api types, append moonpay id to relay request ([`1bf4aa7`](https://github.com/relayprotocol/relay-kit/commit/1bf4aa7)) **SDK `1.6.2`, UI Kit `2.6.3`** — Patch * Fix solver status timeout ui bug and split error into sub error ([`4215d2e`](https://github.com/relayprotocol/relay-kit/commit/4215d2e)) **UI Kit `2.6.5`** — Patch * Fix onramping bugs ([`a89bedd`](https://github.com/relayprotocol/relay-kit/commit/a89bedd)) **UI Kit `2.6.4`** — Patch * Fix onramp modal parseUnits error with eth amount ([`7c7d3ce`](https://github.com/relayprotocol/relay-kit/commit/7c7d3ce)) **UI Kit `2.6.3`** — Patch * Fix onramping bugs: hide suggested tokens, avoid same chain swaps ([`869d750`](https://github.com/relayprotocol/relay-kit/commit/869d750)) **UI Kit `2.6.3`** — Patch * OnrampModal bugs ([`620e41d`](https://github.com/relayprotocol/relay-kit/commit/620e41d)) **UI Kit `2.6.3`** — Patch * Fix useEnsResolver hook undefined address ([`773f61e`](https://github.com/relayprotocol/relay-kit/commit/773f61e)) ### RelayKit **UI Kit `2.6.2`** — Patch * Fix default chain icons ([`9ae33f8`](https://github.com/relayprotocol/relay-kit/commit/9ae33f8)) ### RelayKit **UI Kit `2.6.1`** — Patch * Fix bugs with onramping input ([`5a8bcfc`](https://github.com/relayprotocol/relay-kit/commit/5a8bcfc)) ### RelayKit **UI Kit `2.6.0`, Hooks `1.7.0`** — Minor * OnrampWidget and useTokenPrice hook ([`dc81aac`](https://github.com/relayprotocol/relay-kit/commit/dc81aac)) **UI Kit `2.5.6`** — Patch * Fix token lock issues in SwapWidget ([`af6c612`](https://github.com/relayprotocol/relay-kit/commit/af6c612)) **UI Kit `2.5.5`** — Patch * Update logs to include steps ([`c205df0`](https://github.com/relayprotocol/relay-kit/commit/c205df0)) ### RelayKit **UI Kit `2.5.4`** — Patch * Update radix dialog version ([`72cdfad`](https://github.com/relayprotocol/relay-kit/commit/72cdfad)) **UI Kit `2.5.3`** — Patch * Add slippageTolerance prop to swap widget ([`f0c558e`](https://github.com/relayprotocol/relay-kit/commit/f0c558e)) **UI Kit `2.5.2`** — Patch * Fix theme token usage for inputs ([`27b298c`](https://github.com/relayprotocol/relay-kit/commit/27b298c)) **UI Kit `2.5.2`** — Patch * Fix recipient bug when multi wallet support is not enabled ([`2abfa1a`](https://github.com/relayprotocol/relay-kit/commit/2abfa1a)) ### RelayKit **SDK `1.6.1`** — Patch * Post same chain tx to solver to fix status timeout errors ([`5a9208f`](https://github.com/relayprotocol/relay-kit/commit/5a9208f)) ### RelayKit **SDK `1.6.0`, UI Kit `2.5.0`, Hooks `1.6.0`, Bitcoin adapter `3.0.0`, Ethers adapter `15.0.0`, SVM adapter `4.0.0`** — Minor * Add support for batch transactions ([`3ae98ed`](https://github.com/relayprotocol/relay-kit/commit/3ae98ed)) **SDK `1.6.0`, UI Kit `2.5.0`, Hooks `1.6.0`, Bitcoin adapter `3.0.0`, Ethers adapter `15.0.0`, SVM adapter `4.0.0`** — Patch * Add support for EIP-5792 batch transactions ([`80aba91`](https://github.com/relayprotocol/relay-kit/commit/80aba91)) **UI Kit `2.5.0`** — Patch * Remove usePrice hook usage from widget ([`e7f0acc`](https://github.com/relayprotocol/relay-kit/commit/e7f0acc)) ### RelayKit **SDK `1.5.0`, UI Kit `2.4.0`, Hooks `1.5.0`, Bitcoin adapter `2.0.0`, Ethers adapter `14.0.0`, SVM adapter `3.0.0`** — Minor * Upgrade viem peer dependency ([`1effc86`](https://github.com/relayprotocol/relay-kit/commit/1effc86)) **UI Kit `2.4.0`** — Patch * Add usdc to ink chain's suggested tokens ([`bbc62bc`](https://github.com/relayprotocol/relay-kit/commit/bbc62bc)) ### RelayKit **UI Kit `2.3.10`** — Patch * Add approve + swap ux flow and update cta copy in widget and modal ([`cbbe9b7`](https://github.com/relayprotocol/relay-kit/commit/cbbe9b7)) ### RelayKit **UI Kit `2.3.9`** — Patch * Extract more data out of error message when sending to analytics handler ([`93d9556`](https://github.com/relayprotocol/relay-kit/commit/93d9556)) **UI Kit `2.3.8`** — Patch * Fix quote\_error data ([`a273799`](https://github.com/relayprotocol/relay-kit/commit/a273799)) **UI Kit `2.3.8`** — Patch * Adjust how we handle approval denied and plugin closed errors ([`52ceb0c`](https://github.com/relayprotocol/relay-kit/commit/52ceb0c)) ### RelayKit **UI Kit `2.3.7`, Hooks `1.4.16`** — Patch * Add quote\_error analytics event ([`9928dbc`](https://github.com/relayprotocol/relay-kit/commit/9928dbc)) **UI Kit `2.3.7`** — Patch * Fix token selector height bug ([`bf0e5ef`](https://github.com/relayprotocol/relay-kit/commit/bf0e5ef)) ### RelayKit **SDK `1.4.10`** — Patch * Improve robustness of pre tx chain id check ([`7d6f035`](https://github.com/relayprotocol/relay-kit/commit/7d6f035)) **SDK `1.4.10`, UI Kit `2.3.6`** — Patch * Fix dead address being allowed as burn address ([`f81fbfa`](https://github.com/relayprotocol/relay-kit/commit/f81fbfa)) **UI Kit `2.3.6`** — Patch * Upgrade dune api version to v1 ([`0f3e5f5`](https://github.com/relayprotocol/relay-kit/commit/0f3e5f5)) ### RelayKit **SDK `1.4.9`, SVM adapter `2.0.9`** — Patch * Expose instructions in signature function ([`2b257cd`](https://github.com/relayprotocol/relay-kit/commit/2b257cd)) **UI Kit `2.3.5`** — Patch * Optimize token list merging and sorting logic in TokenSelector ([`9d16628`](https://github.com/relayprotocol/relay-kit/commit/9d16628)) **UI Kit `2.3.5`** — Patch * Fix token overflow ui bug on token selector ([`5193c1a`](https://github.com/relayprotocol/relay-kit/commit/5193c1a)) ### RelayKit **Hooks `1.4.13`** — Patch * Fix useRequests base url ([`91e7d85`](https://github.com/relayprotocol/relay-kit/commit/91e7d85)) ### RelayKit **UI Kit `2.3.3`** — Patch * Update token selector dimensions and breakpoints ([`73f94b2`](https://github.com/relayprotocol/relay-kit/commit/73f94b2)) **UI Kit `2.3.3`** — Patch * Fix slippage ui bugs ([`cd44e80`](https://github.com/relayprotocol/relay-kit/commit/cd44e80)) ### RelayKit **UI Kit `2.3.2`** — Patch * Fix token selection bugs with lockChainId and restrictedTokens ([`e2e93c7`](https://github.com/relayprotocol/relay-kit/commit/e2e93c7)) **UI Kit `2.3.2`** — Patch * Add additional data points to switch wallet event ([`869d7f8`](https://github.com/relayprotocol/relay-kit/commit/869d7f8)) **UI Kit `2.3.2`** — Patch * Update copy for link new wallet dropdown item ([`46c7bdd`](https://github.com/relayprotocol/relay-kit/commit/46c7bdd)) ### RelayKit **UI Kit `2.3.1`** — Patch * Optimize usePrice hook by moving baseChainId validation to SwapWidgetRenderer and removing checkExternalLiquiditySupport prop. This improves performance by skipping invalid canonical routes based on baseChainId relationships. ([`d5b3c66`](https://github.com/relayprotocol/relay-kit/commit/d5b3c66)) **UI Kit `2.3.1`** — Patch * Spacing for DepositAddressModal ([`cb7ded3`](https://github.com/relayprotocol/relay-kit/commit/cb7ded3)) **Hooks `1.4.12`** — Patch * optimize usePrice hook to skip invalid canonical routes ([`1a593b5`](https://github.com/relayprotocol/relay-kit/commit/1a593b5)) ### RelayKit **UI Kit `2.3.0`** — Minor * Deposit Address fallback functionality ([`561b396`](https://github.com/relayprotocol/relay-kit/commit/561b396)) **UI Kit `2.3.0`** — Patch * Remove trailing zeros from formatted amount ([`aa034bd`](https://github.com/relayprotocol/relay-kit/commit/aa034bd)) **UI Kit `2.3.0`** — Patch * Include additional parameters when generating tx urls ([`dc6b34c`](https://github.com/relayprotocol/relay-kit/commit/dc6b34c)) **Hooks `1.4.11`** — Patch * Deposit Address fallback functionality ([`561b396`](https://github.com/relayprotocol/relay-kit/commit/561b396)) ### RelayKit **SDK `1.4.8`** — Patch * Fix typescript error in RelayChain ([`bb5a8d0`](https://github.com/relayprotocol/relay-kit/commit/bb5a8d0)) **UI Kit `2.2.28`** — Patch * Restore slippage UI component in transaction review step ([`c7167e6`](https://github.com/relayprotocol/relay-kit/commit/c7167e6)) **UI Kit `2.2.27`** — Patch * Allow selecting same chain from chain selector ([`9e50085`](https://github.com/relayprotocol/relay-kit/commit/9e50085)) ### RelayKit **UI Kit `2.2.26`** — Patch * Hide slippage ui temporarily ([`9255bd1`](https://github.com/relayprotocol/relay-kit/commit/9255bd1)) **UI Kit `2.2.25`** — Patch * Fix slippage ui bugs ([`3a71c56`](https://github.com/relayprotocol/relay-kit/commit/3a71c56)) **UI Kit `2.2.24`** — Patch * Add slippage details to review quote screen ([`90c9ef9`](https://github.com/relayprotocol/relay-kit/commit/90c9ef9)) ### RelayKit **UI Kit `2.2.23`** — Patch * Add onSwapValidating callback ([`8d62614`](https://github.com/relayprotocol/relay-kit/commit/8d62614)) **UI Kit `2.2.23`** — Patch * Use transaction data rather than quote data on success screen ([`25d17fa`](https://github.com/relayprotocol/relay-kit/commit/25d17fa)) **UI Kit `2.2.23`** — Patch * Update SwapConfirmationStep ui to fix overflow ([`9a34e7e`](https://github.com/relayprotocol/relay-kit/commit/9a34e7e)) ### RelayKit **UI Kit `2.2.22`** — Patch * Add new theme tokens and section ids ([`68ad52a`](https://github.com/relayprotocol/relay-kit/commit/68ad52a)) **UI Kit `2.2.22`** — Patch * Fix undefined placeholder in custom address model when toChain is undefined ([`372fcd4`](https://github.com/relayprotocol/relay-kit/commit/372fcd4)) ### RelayKit **SDK `1.4.7`** — Patch * Update zero address for zero chain ([`a52745f`](https://github.com/relayprotocol/relay-kit/commit/a52745f)) **UI Kit `2.2.21`** — Patch * Ensure dead address is properly generated ([`dce3b7b`](https://github.com/relayprotocol/relay-kit/commit/dce3b7b)) **UI Kit `2.2.20`** — Patch * Fix zero quote api error message ([`04ba9cf`](https://github.com/relayprotocol/relay-kit/commit/04ba9cf)) **UI Kit `2.2.20`** — Patch * Fix high price impact warning ([`31b754f`](https://github.com/relayprotocol/relay-kit/commit/31b754f)) ### RelayKit **UI Kit `2.2.19`** — Patch * Hide route selector in fee breakdown in single chain mode ([`fa63f61`](https://github.com/relayprotocol/relay-kit/commit/fa63f61)) **UI Kit `2.2.18`** — Patch * Allow overriding chain to connector list ([`4fffded`](https://github.com/relayprotocol/relay-kit/commit/4fffded)) **UI Kit `2.2.17`** — Patch * Remove route selector from single chain mode ([`d1fd8ae`](https://github.com/relayprotocol/relay-kit/commit/d1fd8ae)) **UI Kit `2.2.17`** — Patch * Add chain icon to token selector in single chain mode ([`604b9f6`](https://github.com/relayprotocol/relay-kit/commit/604b9f6)) **UI Kit `2.2.17`** — Patch * Fix switch tokens button border theme ([`ce675ba`](https://github.com/relayprotocol/relay-kit/commit/ce675ba)) ### RelayKit **SDK `1.4.6`, UI Kit `2.2.15`, SVM adapter `2.0.6`** — Patch * Support for eclipse svm ([`69df434`](https://github.com/relayprotocol/relay-kit/commit/69df434)) **UI Kit `2.2.16`** — Patch * Add single chain mode to swap widget ([`0c92f0e`](https://github.com/relayprotocol/relay-kit/commit/0c92f0e)) **Hooks `1.4.8`** — Patch * Fix usePrice error handling ([`1c68705`](https://github.com/relayprotocol/relay-kit/commit/1c68705)) ### RelayKit **SDK `1.4.5`, UI Kit `2.2.14`** — Patch * Add suggested tokens to token selector ([`0292590`](https://github.com/relayprotocol/relay-kit/commit/0292590)) **UI Kit `2.2.14`** — Patch * Fix capacity exceeded error to find max capacity ([`bbad717`](https://github.com/relayprotocol/relay-kit/commit/bbad717)) ### RelayKit **SVM adapter `1.0.0`** — Minor * Abstract txs in Adapted Wallet + new Solana Adapter ([`a8215cf`](https://github.com/relayprotocol/relay-kit/commit/a8215cf)) ### RelayKit **SDK `1.4.4`, UI Kit `2.2.13`** — Patch * Impprove wallet switch error handling ([`2ea4fde`](https://github.com/relayprotocol/relay-kit/commit/2ea4fde)) **UI Kit `2.2.13`** — Patch * Handle max capacity error when maximum not returned in error message ([`ab5bc9b`](https://github.com/relayprotocol/relay-kit/commit/ab5bc9b)) ### RelayKit **SDK `1.4.3`, UI Kit `2.2.12`** — Patch * Update solana dead address ([`3dd7177`](https://github.com/relayprotocol/relay-kit/commit/3dd7177)) **UI Kit `2.2.11`** — Patch * Remove support for setting unverified tokens from the chain selector ([`f9b6975`](https://github.com/relayprotocol/relay-kit/commit/f9b6975)) **UI Kit `2.2.11`** — Patch * Update token uri fallback styling ([`5745538`](https://github.com/relayprotocol/relay-kit/commit/5745538)) **UI Kit `2.2.10`** — Patch * Update unverified token modal to link out to block explorer's token page ([`6c52029`](https://github.com/relayprotocol/relay-kit/commit/6c52029)) **UI Kit `2.2.10`** — Patch * Fix token selector trigger overflow ([`40aece0`](https://github.com/relayprotocol/relay-kit/commit/40aece0)) **UI Kit `2.2.10`** — Patch * Fix pending balance bug breaking ui ([`aceb483`](https://github.com/relayprotocol/relay-kit/commit/aceb483)) ### RelayKit **SDK `1.4.2`, UI Kit `2.2.9`** — Patch * Add support for unverified tokens ([`631f997`](https://github.com/relayprotocol/relay-kit/commit/631f997)) **UI Kit `2.2.9`** — Patch * Display pending balance when bitcoin tx is inflight ([`e8681aa`](https://github.com/relayprotocol/relay-kit/commit/e8681aa)) **UI Kit `2.2.9`** — Patch * Remove logic to prefill address with connected address in custom address modal ([`73525f1`](https://github.com/relayprotocol/relay-kit/commit/73525f1)) ### RelayKit **UI Kit `2.2.8`** — Patch * Double time estimate for bitcoin ([`742a49a`](https://github.com/relayprotocol/relay-kit/commit/742a49a)) **UI Kit `2.2.7`** — Patch * Make selector theme optional ([`87b0bca`](https://github.com/relayprotocol/relay-kit/commit/87b0bca)) **UI Kit `2.2.6`** — Patch * Add disclaimer messaging in confirmation modal with long canonical relays ([`a1e9418`](https://github.com/relayprotocol/relay-kit/commit/a1e9418)) **UI Kit `2.2.6`** — Patch * Add widget selector theme variable ([`bd1ec82`](https://github.com/relayprotocol/relay-kit/commit/bd1ec82)) **UI Kit `2.2.6`** — Patch * Fix 0 price impact ([`b68e6c1`](https://github.com/relayprotocol/relay-kit/commit/b68e6c1)) ### RelayKit **UI Kit `2.2.5`** — Patch * Fix parallel quoting when quote returns errror but price does not ([`47c6c04`](https://github.com/relayprotocol/relay-kit/commit/47c6c04)) **UI Kit `2.2.5`** — Patch * Remove stroke from route selector when focused ([`fc902d3`](https://github.com/relayprotocol/relay-kit/commit/fc902d3)) **UI Kit `2.2.4`** — Patch * Fix double route ui issue where price error shows up alongside valid quote data ([`321a64e`](https://github.com/relayprotocol/relay-kit/commit/321a64e)) **UI Kit `2.2.3`** — Patch * Expand bitcoin address validation to taproot and base58 ([`104fe18`](https://github.com/relayprotocol/relay-kit/commit/104fe18)) **UI Kit `2.2.2`, Hooks `1.4.2`** — Patch * Fetch price and quote in parallel to improve price accuracy in SwapWidget ([`f18951c`](https://github.com/relayprotocol/relay-kit/commit/f18951c)) ### RelayKit **SDK `1.4.1`, UI Kit `2.2.1`** — Patch * Fix bugs with bitcoin implementation ([`6237949`](https://github.com/relayprotocol/relay-kit/commit/6237949)) ### RelayKit **SDK `1.4.0`, UI Kit `2.2.0`, Bitcoin adapter `1.0.0`** — Minor * Bitcoin support ([`07917f6`](https://github.com/relayprotocol/relay-kit/commit/07917f6)) **Hooks `1.4.0`** — Minor * Remove automatic refresh of quote data after execution in useQuote hook ([`d3f975d`](https://github.com/relayprotocol/relay-kit/commit/d3f975d)) ### RelayKit **UI Kit `2.1.9`** — Patch * Added keyboard navigation to token selector ([`29bb388`](https://github.com/relayprotocol/relay-kit/commit/29bb388)) ### RelayKit **UI Kit `2.1.8`** — Patch * Add input and output amount to max capacity event ([`ac81750`](https://github.com/relayprotocol/relay-kit/commit/ac81750)) ### RelayKit **SDK `1.3.4`, UI Kit `2.1.7`** — Patch * Replace exact\_output with expected\_output tradeType ([`2134530`](https://github.com/relayprotocol/relay-kit/commit/2134530)) **UI Kit `2.1.6`** — Patch * Fix ux issues with canonical route selector ([`cea3f43`](https://github.com/relayprotocol/relay-kit/commit/cea3f43)) ### RelayKit **UI Kit `2.1.5`** — Patch * Improve price impact, handle no instant liquidity ux ([`af05332`](https://github.com/relayprotocol/relay-kit/commit/af05332)) ### RelayKit **UI Kit `2.1.4`** — Patch * Improvements on the canonical fallback ux ([`fbe5a41`](https://github.com/relayprotocol/relay-kit/commit/fbe5a41)) ### RelayKit **UI Kit `2.1.3`** — Patch * Fix disabled token selector background and issue with resetting canonical route ([`25d9c9a`](https://github.com/relayprotocol/relay-kit/commit/25d9c9a)) **UI Kit `2.1.2`** — Patch * Remove hardcoded canonical currencies ([`9b0319e`](https://github.com/relayprotocol/relay-kit/commit/9b0319e)) ### RelayKit **UI Kit `2.1.1`** — Patch * Fix canonical check amount, disable token selector when one option available ([`d7a2ced`](https://github.com/relayprotocol/relay-kit/commit/d7a2ced)) **UI Kit `2.1.0`** — Minor * Implement canonical+ support into SwapWidget, remove ChainWidget ([`466cf46`](https://github.com/relayprotocol/relay-kit/commit/466cf46)) ### RelayKit **SDK `1.3.3`** — Patch * Fix switchChain logic to take into account missing chain in wallet ([`3f88d12`](https://github.com/relayprotocol/relay-kit/commit/3f88d12)) **UI Kit `2.0.8`** — Patch * Fix token selector balance display ([`e3044ff`](https://github.com/relayprotocol/relay-kit/commit/e3044ff)) **UI Kit `2.0.7`** — Patch * Fix swap modal closing when dynamic embedded wallet tx modal is prompted and clicked on ([`49cd085`](https://github.com/relayprotocol/relay-kit/commit/49cd085)) ### RelayKit **UI Kit `2.0.5`** — Patch * Patches fixes for Solana + multi wallet dropdown ui ([`e0b311d`](https://github.com/relayprotocol/relay-kit/commit/e0b311d)) **UI Kit `2.0.4`** — Patch * Fix minor CustomAddressModal bugs ([`e763551`](https://github.com/relayprotocol/relay-kit/commit/e763551)) ### RelayKit **SDK `1.3.2`, UI Kit `2.0.3`** — Patch * Fix wallet switching in ChainWidget, fix sdk convert viem chain to RelayChain helper ([`277bfc5`](https://github.com/relayprotocol/relay-kit/commit/277bfc5)) **UI Kit `2.0.3`** — Patch * Removed max button when Solana balance is less than minimum buffer. ([`1edca7c`](https://github.com/relayprotocol/relay-kit/commit/1edca7c)) **UI Kit `2.0.2`** — Patch * Added a buffer of 0.02 minimum when selecting max for a Solana balance ([`78e5d13`](https://github.com/relayprotocol/relay-kit/commit/78e5d13)) **UI Kit `2.0.2`** — Patch * Fix solana custom address prompt ([`b1526cb`](https://github.com/relayprotocol/relay-kit/commit/b1526cb)) **UI Kit `2.0.1`** — Patch * Fix solana balance cached after swapping ([`dc387e3`](https://github.com/relayprotocol/relay-kit/commit/dc387e3)) ### RelayKit **SDK `1.3.1`** — Patch * Add solana ui support in SwapWidget ([`2c38afe`](https://github.com/relayprotocol/relay-kit/commit/2c38afe)) **UI Kit `2.0.0`** — Major * Add solana ui support in SwapWidget ([`2c38afe`](https://github.com/relayprotocol/relay-kit/commit/2c38afe)) ### RelayKit **UI Kit `1.4.1`** — Patch * Improve high price impact copy ([`c5ed1d8`](https://github.com/relayprotocol/relay-kit/commit/c5ed1d8)) ### RelayKit **SDK `1.3.0`, UI Kit `1.4.0`, Hooks `1.3.0`, Ethers adapter `12.0.0`** — Minor * Abstract txs in Adapted Wallet + new Solana Adapter ([`a8215cf`](https://github.com/relayprotocol/relay-kit/commit/a8215cf)) ### RelayKit **SDK `1.2.2`, UI Kit `1.3.20`** — Patch * Expand support for svm chains when setting addresses ([`41feac9`](https://github.com/relayprotocol/relay-kit/commit/41feac9)) **UI Kit `1.3.20`** — Patch * Remove resetting of chain filter when token selector is closed ([`ae0dd9b`](https://github.com/relayprotocol/relay-kit/commit/ae0dd9b)) **UI Kit `1.3.20`** — Patch * Fix modal's "x" button size and alignment ([`10a4d30`](https://github.com/relayprotocol/relay-kit/commit/10a4d30)) ### RelayKit **UI Kit `1.3.19`** — Patch * Chain selector ux tweaks ([`a93ae37`](https://github.com/relayprotocol/relay-kit/commit/a93ae37)) ### RelayKit **UI Kit `1.3.18`** — Patch * Update high price impact warning logic ([`2d33c4a`](https://github.com/relayprotocol/relay-kit/commit/2d33c4a)) **UI Kit `1.3.18`** — Patch * Update swap widget padding and box shadow ([`128127f`](https://github.com/relayprotocol/relay-kit/commit/128127f)) ### RelayKit **UI Kit `1.3.17`** — Patch * Fix conversion rate formatting bug ([`9fe399d`](https://github.com/relayprotocol/relay-kit/commit/9fe399d)) **UI Kit `1.3.16`** — Patch * Fix ui overflow on transaction modal ([`7079727`](https://github.com/relayprotocol/relay-kit/commit/7079727)) **UI Kit `1.3.16`** — Patch * Allow fetching solana quotes with no custom address ([`32c533e`](https://github.com/relayprotocol/relay-kit/commit/32c533e)) ### RelayKit **UI Kit `1.3.15`** — Patch * Fix search by chain displayName ([`e6f3183`](https://github.com/relayprotocol/relay-kit/commit/e6f3183)) **UI Kit `1.3.15`** — Patch * Update Swapping CTA ([`5caf19b`](https://github.com/relayprotocol/relay-kit/commit/5caf19b)) **UI Kit `1.3.14`** — Patch * Small token selector bug fixes ([`2a11467`](https://github.com/relayprotocol/relay-kit/commit/2a11467)) ### RelayKit **UI Kit `1.3.13`** — Patch * Fix token selector chain filter bug ([`f409525`](https://github.com/relayprotocol/relay-kit/commit/f409525)) **UI Kit `1.3.12`** — Patch * Use default token list when chain is filtered ([`6001f8d`](https://github.com/relayprotocol/relay-kit/commit/6001f8d)) **UI Kit `1.3.11`** — Patch * Chain selector improvements ([`2c6ead0`](https://github.com/relayprotocol/relay-kit/commit/2c6ead0)) **UI Kit `1.3.10`** — Patch * Add styling support for undefined tokens in new swap widget ([`9b623c2`](https://github.com/relayprotocol/relay-kit/commit/9b623c2)) ### RelayKit **SDK `1.2.1`, UI Kit `1.3.9`, Hooks `1.2.4`, Ethers adapter `11.0.1`** — Patch * New swap widget ui ([`7a084fa`](https://github.com/relayprotocol/relay-kit/commit/7a084fa)) ### RelayKit **SDK `1.2.0`** — Minor * Better handling of transaction in flight and post transaction ui, handle refund status when polling for transaction success ([`9e14a17`](https://github.com/relayprotocol/relay-kit/commit/9e14a17)) **UI Kit `1.3.8`** — Patch * Better handling of transaction in flight and post transaction ui, handle refund status when polling for transaction success ([`9e14a17`](https://github.com/relayprotocol/relay-kit/commit/9e14a17)) ### RelayKit **SDK `1.1.2`** — Patch * Add getPrice action ([`78c6ed0`](https://github.com/relayprotocol/relay-kit/commit/78c6ed0)) ### RelayKit **UI Kit `1.3.6`** — Patch * Fix analytics in TransactionModal ([`7a1a4c7`](https://github.com/relayprotocol/relay-kit/commit/7a1a4c7)) **UI Kit `1.3.5`** — Patch * Update balance display for solana ([`7d3f84d`](https://github.com/relayprotocol/relay-kit/commit/7d3f84d)) **UI Kit `1.3.4`** — Patch * Update solana token selector logic ([`785b732`](https://github.com/relayprotocol/relay-kit/commit/785b732)) **UI Kit `1.3.3`** — Patch * Prevent switch token button in swap widget when swapping to solana token ([`3ae5fee`](https://github.com/relayprotocol/relay-kit/commit/3ae5fee)) ### RelayKit **SDK `1.1.1`** — Patch * Update sdk api types ([`28ed450`](https://github.com/relayprotocol/relay-kit/commit/28ed450)) **UI Kit `1.3.2`** — Patch * Reduce swap conversion rate from 5 decimals to 2 decimals ([`98bf004`](https://github.com/relayprotocol/relay-kit/commit/98bf004)) **UI Kit `1.3.2`** — Patch * Link out to to chain block explorer in review quote ux ([`4473144`](https://github.com/relayprotocol/relay-kit/commit/4473144)) **UI Kit `1.3.2`** — Patch * Handle 0s time estimate ([`6363308`](https://github.com/relayprotocol/relay-kit/commit/6363308)) **UI Kit `1.3.2`** — Patch * Improve custom wallet address modal ux ([`35d5e43`](https://github.com/relayprotocol/relay-kit/commit/35d5e43)) **UI Kit `1.3.2`** — Patch * Improve tx error messaging ([`2cd9327`](https://github.com/relayprotocol/relay-kit/commit/2cd9327)) **UI Kit `1.3.2`** — Patch * Reset custom address when switching to token from solana to evm chain ([`b940e97`](https://github.com/relayprotocol/relay-kit/commit/b940e97)) **UI Kit `1.3.2`** — Patch * Price Impact ui tweaks ([`a42142d`](https://github.com/relayprotocol/relay-kit/commit/a42142d)) ### RelayKit **UI Kit `1.3.1`** — Patch * Support Solana deposits in Swap widget ([`df71a3b`](https://github.com/relayprotocol/relay-kit/commit/df71a3b)) ### RelayKit **UI Kit `1.3.0`, Hooks `1.2.0`** — Minor * Upgrade /requests api to /requests/v2 ([`b9db008`](https://github.com/relayprotocol/relay-kit/commit/b9db008)) **UI Kit `1.3.0`** — Patch * ChainWidget: Fix copy in SwapRouteSelector ([`4cd7401`](https://github.com/relayprotocol/relay-kit/commit/4cd7401)) ### RelayKit **SDK `1.1.0`, UI Kit `1.2.0`, Hooks `1.1.0`** — Minor * Switch from execute/swap to quote api ([`105d1b8`](https://github.com/relayprotocol/relay-kit/commit/105d1b8)) **SDK `1.1.0`** — Patch * Drop lodash cloneDeep in favor of native cloning ([`c158099`](https://github.com/relayprotocol/relay-kit/commit/c158099)) ### RelayKit **SDK `1.0.13`, UI Kit `1.1.18`** — Patch * Add support for iconUrl chain icon override ([`b342c8d`](https://github.com/relayprotocol/relay-kit/commit/b342c8d)) **UI Kit `1.1.18`** — Patch * Add price impact warning to review quote ux ([`efd44d6`](https://github.com/relayprotocol/relay-kit/commit/efd44d6)) ### RelayKit **UI Kit `1.1.17`** — Patch * Fix safari modal animation flickering bug ([`f66e0f5`](https://github.com/relayprotocol/relay-kit/commit/f66e0f5)) **UI Kit `1.1.17`** — Patch * Add defaultExternalChainToken to Chain Widget ([`87239fa`](https://github.com/relayprotocol/relay-kit/commit/87239fa)) ### RelayKit **SDK `1.0.12`** — Patch * Add supportsBridging to Relay Chain ([`ec9b20f`](https://github.com/relayprotocol/relay-kit/commit/ec9b20f)) **SDK `1.0.11`** — Patch * Sync api types ([`92b9c71`](https://github.com/relayprotocol/relay-kit/commit/92b9c71)) ### RelayKit **UI Kit `1.1.14`, Hooks `1.0.16`** — Patch * Fix chain widget testnet configuration ([`fac3415`](https://github.com/relayprotocol/relay-kit/commit/fac3415)) **UI Kit `1.1.13`** — Patch * Fix swap widget cta bug ([`5acf10e`](https://github.com/relayprotocol/relay-kit/commit/5acf10e)) **UI Kit `1.1.12`** — Patch * Remove compact formatting from review quote ([`23b528c`](https://github.com/relayprotocol/relay-kit/commit/23b528c)) ### RelayKit **UI Kit `1.1.11`** — Patch * Decouple dune balance loading from token selector ([`00da309`](https://github.com/relayprotocol/relay-kit/commit/00da309)) ### RelayKit **SDK `1.0.10`, UI Kit `1.1.10`, Hooks `1.0.15`, Ethers adapter `9.0.10`** — Patch * Add review quote step, refactor modal renderers **SDK `1.0.10`, Hooks `1.0.15`** — Patch * Add usePrice hook ([`2d22eec`](https://github.com/relayprotocol/relay-kit/commit/2d22eec)) **UI Kit `1.1.10`** — Patch * Fix dollar formatting sub 0 and price impact spacing ([`af694ab`](https://github.com/relayprotocol/relay-kit/commit/af694ab)) **UI Kit `1.1.10`** — Patch * Color coding price impact ([`e231382`](https://github.com/relayprotocol/relay-kit/commit/e231382)) ### RelayKit **UI Kit `1.1.9`** — Patch * Fix dropdown item hover color ([`b3432f4`](https://github.com/relayprotocol/relay-kit/commit/b3432f4)) ### RelayKit **SDK `1.0.9`, UI Kit `1.1.8`** — Patch * Better contextualize actions based on operation ([`3648ec3`](https://github.com/relayprotocol/relay-kit/commit/3648ec3)) **SDK `1.0.9`** — Patch * Add blockProductionLagging to RelayChain ([`be74a6a`](https://github.com/relayprotocol/relay-kit/commit/be74a6a)) **UI Kit `1.1.8`** — Patch * Only show fill time on success page if sub 10s ([`ea3e6c5`](https://github.com/relayprotocol/relay-kit/commit/ea3e6c5)) **UI Kit `1.1.8`** — Patch * Export TokenSelector component ([`d5b3f82`](https://github.com/relayprotocol/relay-kit/commit/d5b3f82)) ### RelayKit **UI Kit `1.1.7`** — Patch * Reset canonical selection on ChainWidget unless supported ([`7445bbf`](https://github.com/relayprotocol/relay-kit/commit/7445bbf)) **UI Kit `1.1.6`** — Patch * Add txHashes to success and error events ([`433ca57`](https://github.com/relayprotocol/relay-kit/commit/433ca57)) **UI Kit `1.1.6`** — Patch * Add transaction validating event ([`7c8fb89`](https://github.com/relayprotocol/relay-kit/commit/7c8fb89)) **UI Kit `1.1.5`** — Patch * Update chain display in token select ui ([`4cf2285`](https://github.com/relayprotocol/relay-kit/commit/4cf2285)) **UI Kit `1.1.4`** — Patch * Sort token selector by total value usd and balance ([`02c5ea0`](https://github.com/relayprotocol/relay-kit/commit/02c5ea0)) ### RelayKit **SDK `1.0.8`** — Patch * Fix json error spreading throwing error in executeSteps ([`1e83361`](https://github.com/relayprotocol/relay-kit/commit/1e83361)) **UI Kit `1.1.3`** — Patch * Add success screen for canonical ([`643c6a6`](https://github.com/relayprotocol/relay-kit/commit/643c6a6)) ### RelayKit **UI Kit `1.1.2`** — Patch * ChainWidget Canonical functionality ([`eae9b4d`](https://github.com/relayprotocol/relay-kit/commit/eae9b4d)) ### RelayKit **UI Kit `1.1.1`** — Patch * Remove log ([`3f2bd68`](https://github.com/relayprotocol/relay-kit/commit/3f2bd68)) ### RelayKit **SDK `1.0.7`, UI Kit `1.1.0`, Hooks `1.0.12`** — Patch * Add tests and fix global axios instance ([`4335c35`](https://github.com/relayprotocol/relay-kit/commit/4335c35)) **UI Kit `1.1.0`** — Minor * Refactor SwapWidget and add new ChainWidget for deposit/withdraw ux ([`0384db0`](https://github.com/relayprotocol/relay-kit/commit/0384db0)) ### RelayKit **Hooks `1.0.11`** — Patch * Fix source getting overriden in useQuote hook ([`5c1a210`](https://github.com/relayprotocol/relay-kit/commit/5c1a210)) **Hooks `1.0.10`** — Patch * Fix useQuote hook source ([`ec5e68e`](https://github.com/relayprotocol/relay-kit/commit/ec5e68e)) ### RelayKit **UI Kit `1.0.14`** — Patch * Pass source in widget ([`f7f014b`](https://github.com/relayprotocol/relay-kit/commit/f7f014b)) ### RelayKit **SDK `1.0.6`, UI Kit `1.0.13`** — Patch * Improve post transaction polling to increase speed ([`5184197`](https://github.com/relayprotocol/relay-kit/commit/5184197)) **UI Kit `1.0.13`** — Patch * Fix capabilities check when coinbase wallet connected ([`d264b99`](https://github.com/relayprotocol/relay-kit/commit/d264b99)) ### RelayKit **UI Kit `1.0.12`** — Patch * Update swap time icon color code ([`4758845`](https://github.com/relayprotocol/relay-kit/commit/4758845)) **UI Kit `1.0.12`** — Patch * Hide balance column in TokenSelector when account is not connected ([`5a6f8ca`](https://github.com/relayprotocol/relay-kit/commit/5a6f8ca)) **UI Kit `1.0.12`** — Patch * Disable useCapabilities except for on coinbase wallet ([`d2c94a3`](https://github.com/relayprotocol/relay-kit/commit/d2c94a3)) ### RelayKit **SDK `1.0.5`, UI Kit `1.0.11`, Hooks `1.0.8`** — Patch * Move wallet chain id check to further in the flow ([`1945809`](https://github.com/relayprotocol/relay-kit/commit/1945809)) **UI Kit `1.0.11`** — Patch * Fix button color when token selector is locked ([`ffb9ebe`](https://github.com/relayprotocol/relay-kit/commit/ffb9ebe)) **UI Kit `1.0.10`** — Patch * Add lock token params to swap widget" ([`b093883`](https://github.com/relayprotocol/relay-kit/commit/b093883)) **UI Kit `1.0.9`** — Patch * Update powered by reservoir to use logo ([`577658a`](https://github.com/relayprotocol/relay-kit/commit/577658a)) **UI Kit `1.0.9`** — Patch * Fix total price impact copy ([`525e523`](https://github.com/relayprotocol/relay-kit/commit/525e523)) **UI Kit `1.0.9`, Hooks `1.0.7`** — Patch * Add onSuccess and onError callbacks for SwapWidget ([`813be48`](https://github.com/relayprotocol/relay-kit/commit/813be48)) ### RelayKit **SDK `1.0.4`** — Patch * Add request to quote to be passed into execute function ([`6b5015b`](https://github.com/relayprotocol/relay-kit/commit/6b5015b)) **UI Kit `1.0.8`** — Patch * Allow same currency swapping when different recipient ([`f0ecb82`](https://github.com/relayprotocol/relay-kit/commit/f0ecb82)) **UI Kit `1.0.8`** — Patch * Fix RelayKitProvider initialization ([`7361f60`](https://github.com/relayprotocol/relay-kit/commit/7361f60)) ### RelayKit **SDK `1.0.3`** — Patch * Optimize the way we import lodash ([`772b657`](https://github.com/relayprotocol/relay-kit/commit/772b657)) **UI Kit `1.0.7`** — Patch * Add app fee to breakdown ui ([`5759936`](https://github.com/relayprotocol/relay-kit/commit/5759936)) **UI Kit `1.0.7`** — Patch * Fix info icon alignment ([`0c7e56c`](https://github.com/relayprotocol/relay-kit/commit/0c7e56c)) **UI Kit `1.0.7`** — Patch * Fix input icon position ([`a3d4c06`](https://github.com/relayprotocol/relay-kit/commit/a3d4c06)) **UI Kit `1.0.7`** — Patch * Filter token search by configured tokens ([`0a8325b`](https://github.com/relayprotocol/relay-kit/commit/0a8325b)) **Hooks `1.0.5`** — Patch * Add useRelayConfig hook ([`2922c29`](https://github.com/relayprotocol/relay-kit/commit/2922c29)) ### RelayKit **SDK `1.0.2`, UI Kit `1.0.5`, Hooks `1.0.4`, Ethers adapter `9.0.2`** — Patch * Strip layers from generated ui kit stylesheet ([`ce46e72`](https://github.com/relayprotocol/relay-kit/commit/ce46e72)) **UI Kit `1.0.6`** — Patch * Add app name and fees to RelayKitProvider options ([`7aa524c`](https://github.com/relayprotocol/relay-kit/commit/7aa524c)) **UI Kit `1.0.6`** — Patch * Add token change callbacks ([`9ad0dda`](https://github.com/relayprotocol/relay-kit/commit/9ad0dda)) **UI Kit `1.0.6`** — Patch * Fix theme override selector ([`7a073f8`](https://github.com/relayprotocol/relay-kit/commit/7a073f8)) **UI Kit `1.0.4`, Hooks `1.0.3`** — Patch * Make tanstack query a peer dependency ([`592feb1`](https://github.com/relayprotocol/relay-kit/commit/592feb1)) **UI Kit `1.0.3`** — Patch * Make wagmi a peer dependency ([`ac60282`](https://github.com/relayprotocol/relay-kit/commit/ac60282)) **UI Kit `1.0.2`, Hooks `1.0.2`** — Patch * Fix esm import errors ([`2c6d3da`](https://github.com/relayprotocol/relay-kit/commit/2c6d3da)) ### RelayKit **SDK `1.0.1`, UI Kit `1.0.1`, Hooks `1.0.1`, Ethers adapter `9.0.1`** — Patch * Fix deploy script ([`4a55654`](https://github.com/relayprotocol/relay-kit/commit/4a55654)) **SDK `1.0.0`, UI Kit `1.0.0`, Hooks `1.0.0`** — Major * Refactor SDK to simplify, add ui packages ([`ba72406`](https://github.com/relayprotocol/relay-kit/commit/ba72406)) ### RelayKit **SDK `0.7.0`** — Minor * Migrate to v2 of call and bridge api ([`a77e30f`](https://github.com/relayprotocol/relay-kit/commit/a77e30f)) ### RelayKit **SDK `0.6.2`** — Patch * Make txs a first class parameter in swap action ([`820021e`](https://github.com/relayprotocol/relay-kit/commit/820021e)) **SDK `0.6.1`** — Patch * Support swap + extra call txs ([`6ae4f8d`](https://github.com/relayprotocol/relay-kit/commit/6ae4f8d)) ### RelayKit **SDK `0.6.0`** — Minor * Upgrade getSolverCapacity method to use config v2 api ([`309af6e`](https://github.com/relayprotocol/relay-kit/commit/309af6e)) **SDK `0.6.0`** — Patch * Add square icons to Relay Chain ([`96db714`](https://github.com/relayprotocol/relay-kit/commit/96db714)) **SDK `0.6.0`** — Patch * Move sdk methods to actions ([`47a8aed`](https://github.com/relayprotocol/relay-kit/commit/47a8aed)) **SDK `0.6.0`** — Patch * Add progress state ([`31fecc5`](https://github.com/relayprotocol/relay-kit/commit/31fecc5)) **SDK `0.6.0`** — Patch * Improve typescript fee execute types ([`bce691b`](https://github.com/relayprotocol/relay-kit/commit/bce691b)) ### RelayKit **SDK `0.5.4`** — Patch * Post deposit transactions to solver ([`8ef9ba6`](https://github.com/relayprotocol/relay-kit/commit/8ef9ba6)) **SDK `0.5.4`** — Patch * Fix return types for get quote methods ([`9799870`](https://github.com/relayprotocol/relay-kit/commit/9799870)) ### RelayKit **SDK `0.5.3`** — Patch * Fix internal txhash chain id ([`dd34345`](https://github.com/relayprotocol/relay-kit/commit/dd34345)) ### RelayKit **SDK `0.5.2`** — Patch * Await tx receipt for transaction steps ([`e795842`](https://github.com/relayprotocol/relay-kit/commit/e795842)) **SDK `0.5.1`, Ethers adapter `6.0.1`** — Patch * Swaps action and getSwapQuote method ([`597371e`](https://github.com/relayprotocol/relay-kit/commit/597371e)) **SDK `0.5.0`** — Minor * Swap SDK action ([`74e12e1`](https://github.com/relayprotocol/relay-kit/commit/74e12e1)) **SDK `0.5.0`** — Patch * Sync types ([`e0f2219`](https://github.com/relayprotocol/relay-kit/commit/e0f2219)) ### RelayKit **SDK `0.4.0`** — Minor * Improve onProgress action callback to be easier to use ([`fc2f8ce`](https://github.com/relayprotocol/relay-kit/commit/fc2f8ce)) **SDK `0.4.0`** — Patch * Update sdk readme ([`84a2ea8`](https://github.com/relayprotocol/relay-kit/commit/84a2ea8)) ### RelayKit **SDK `0.3.10`** — Patch * Sync api types + add useExactInput parameter to demo ([`0dbeb4a`](https://github.com/relayprotocol/relay-kit/commit/0dbeb4a)) ### RelayKit **SDK `0.3.9`** — Patch * Remove requestId from Execute type ([`4239703`](https://github.com/relayprotocol/relay-kit/commit/4239703)) ### RelayKit **SDK `0.3.8`** — Patch * Skip check object if bridge is canonical ([`19019b4`](https://github.com/relayprotocol/relay-kit/commit/19019b4)) ### RelayKit **SDK `0.3.7`** — Patch * Sync api types ([`96d0990`](https://github.com/relayprotocol/relay-kit/commit/96d0990)) ### RelayKit **SDK `0.3.6`** — Patch * Sync API types ([`c9d17bb`](https://github.com/relayprotocol/relay-kit/commit/c9d17bb)) ### RelayKit **SDK `0.3.5`** — Patch * Add currency id to RelayChain type ([`952b5a6`](https://github.com/relayprotocol/relay-kit/commit/952b5a6)) **SDK `0.3.4`** — Patch * Sync api types ([`8bd3d17`](https://github.com/relayprotocol/relay-kit/commit/8bd3d17)) ### RelayKit **SDK `0.3.3`** — Patch * Handle inTxHashes for signature step items ([`0f291dc`](https://github.com/relayprotocol/relay-kit/commit/0f291dc)) ### RelayKit **SDK `0.3.2`** — Patch * Add isValidatingSignature to step item ([`a76f067`](https://github.com/relayprotocol/relay-kit/commit/a76f067)) ### RelayKit **SDK `0.3.1`** — Patch * Sync api types and add erc20Currencies to RelayChain ([`f9dff0c`](https://github.com/relayprotocol/relay-kit/commit/f9dff0c)) ### RelayKit **SDK `0.3.0`** — Minor * Update bridge action to use new bridge api + usdc support ([`e41c632`](https://github.com/relayprotocol/relay-kit/commit/e41c632)) **SDK `0.3.0`** — Patch * Handle rainbow wallet bug where txHash of "null" is returned when tx rejected ([`a572a48`](https://github.com/relayprotocol/relay-kit/commit/a572a48)) ### RelayKit **SDK `0.2.5`** — Patch * Omit duplicate types from call and bridge action options ([`180d1c7`](https://github.com/relayprotocol/relay-kit/commit/180d1c7)) **SDK `0.2.5`** — Patch * Return route breakdown in onProgress callback ([`4910cbf`](https://github.com/relayprotocol/relay-kit/commit/4910cbf)) **SDK `0.2.5`** — Patch * Remove tx intent trigger api call ([`f454345`](https://github.com/relayprotocol/relay-kit/commit/f454345)) ### RelayKit **SDK `0.2.4`** — Patch * Sync sdk types with api ([`ade63ae`](https://github.com/relayprotocol/relay-kit/commit/ade63ae)) **SDK `0.2.4`** — Patch * Added readme to sdk ([`2624964`](https://github.com/relayprotocol/relay-kit/commit/2624964)) ### RelayKit **SDK `0.2.3`** — Patch * Make wallet parameter optional for get quote methods ([`10c3e03`](https://github.com/relayprotocol/relay-kit/commit/10c3e03)) ### RelayKit **SDK `0.2.2`** — Patch * Add ability to pass in a gasLimit for deposit transactions ([`90e4d58`](https://github.com/relayprotocol/relay-kit/commit/90e4d58)) **SDK `0.2.2`** — Patch * Add currentStep and currentStep item to onProgress callback ([`8a5fdda`](https://github.com/relayprotocol/relay-kit/commit/8a5fdda)) ### RelayKit **SDK `0.2.1`** — Patch * Add getSolverCapacity, getCallQuote, getBridgeQuote methods ([`b59c35a`](https://github.com/relayprotocol/relay-kit/commit/b59c35a)) ### RelayKit **SDK `0.2.0`, Ethers adapter `3.0.0`** — Minor * Handle mismatched chain prior to execution ([`53f089b`](https://github.com/relayprotocol/relay-kit/commit/53f089b)) **SDK `0.2.0`** — Patch * Add simplified bridge action for convenience ([`0fbab53`](https://github.com/relayprotocol/relay-kit/commit/0fbab53)) ### RelayKit **SDK `0.1.0`, Ethers adapter `2.0.0`** — Minor * Upgrade wagmi, viem and rainbowkit to v2 ([`547425d`](https://github.com/relayprotocol/relay-kit/commit/547425d)) **SDK `0.1.0`** — Patch * Automatic source detection ([`bad65fe`](https://github.com/relayprotocol/relay-kit/commit/bad65fe)) ### RelayKit **SDK `0.0.13`** — Patch * Add source attribution parameter to call action ([`3c6c3fc`](https://github.com/relayprotocol/relay-kit/commit/3c6c3fc)) ### RelayKit **SDK `0.0.12`** — Patch * Improve chain conversion by using viem chains if available or fallback to api chain data ([`60854a6`](https://github.com/relayprotocol/relay-kit/commit/60854a6)) **Ethers adapter `1.0.0`** — Major * Relay SDK ethers wallet adapter ([`8b3f605`](https://github.com/relayprotocol/relay-kit/commit/8b3f605)) # 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, 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, BTC) | | Swaps (same-chain) | ✅ See chain-specific notes below | | Bridges | ✅ | | Calls | ✅ | | Wraps / Unwraps | ✅ | | Sends / Transfers | ❌ | | 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. **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 fees accrue off-chain to your App Balance in USDC. You don't need approval from Relay to claim your balance. Each balance returned by the [balances API](#get-app-balance) includes the chain and currency it is claimed in. Pass that `chainId` and `currency` when you claim, as the Relay App does. When you claim, the estimated network gas for the claim transaction is converted to USDC and deducted from the amount you claim, so the amount that arrives in your wallet is slightly lower. The deduction tracks gas prices rather than a fixed amount or percentage, and there is no separate service fee. To hold the funds on another chain or in another currency, bridge or swap them with Relay after you claim. The [quote](/references/api/get-quote-v2) shows Relay's fees and the amount you'll receive. Unless the transaction is gasless, your wallet also pays origin-chain gas, and an ERC-20 may need a separate approval transaction. 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": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "verified": true, "isNative": false } }, "amount": "116235557", "amountFormatted": "116.235557", "amountUsd": "116.235557", "minimumAmount": "116235557" } ``` 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": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "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: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // currency.address from the balances API 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 Bridge assets by sending funds to a deposit address — no wallet connection or signing required. Deposit addresses let users bridge or swap tokens by sending funds to an address, without connecting a wallet to the application. The integrator requests a quote with **`useDepositAddress: true`**, and the user transfers funds to the returned address. The platform tracks the deposit and execution; solvers provide cross-chain fills. This makes deposit addresses ideal for CEX withdrawals, fiat onramps, and headless systems where the sender can't sign transactions. The user just sends to an address — same UX as a normal transfer. ## How It Works 1. **Quote** — Integrator requests a quote with `useDepositAddress: true`. The response includes a `depositAddress` and `requestId`. 2. **User Deposit** — User sends funds to the deposit address (wallet transfer or exchange withdrawal). 3. **Detect + Sweep** — Relay detects the deposit onchain and sweeps funds to the depository contract. For open-ended addresses, the amount, currency, and chain are validated and the quote may be regenerated if different from the original. For strict addresses, the deposit is validated against the original order. 4. **Fill** — For cross-chain orders, a solver uses its own liquidity to deliver the requested output to the recipient. Same-chain swaps execute on the deposit chain. ### Key Parameters | Parameter | Type | Details | | - | - | - | | `useDepositAddress` | boolean | Set to `true` to receive a deposit address instead of transaction calldata. | | `user` | address | The recipient wallet on the destination chain. Can be any valid address, including the zero address. | | `recipient` | address | The address that receives funds on the destination chain. | | `refundTo` | address | Address to send refunds if the transfer fails. Required for strict deposit addresses and for routes that include a destination-side swap. Set to the origin chain's native-currency address (EVM `0x0000000000000000000000000000000000000000`, Bitcoin `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8`, Solana `11111111111111111111111111111111`) to opt into automatic refunds to the original depositor — supported on EVM chains, Bitcoin, and Solana. Omitting this on open addresses disables automatic refund. See [Refund Behavior](#refund-behavior). | | `recoveryAddress` | address | Optional origin-chain EOA controlled by the integrator. Used as a recovery address when Relay cannot safely auto-refund to the original depositor. This is additive to `refundTo`; existing `refundTo` behavior is preserved unless `recoveryAddress` is supplied. See [`recoveryAddress`](#recoveryaddress). | | `tradeType` | string | `EXACT_INPUT` or `EXPECTED_OUTPUT` for open deposit addresses. `EXACT_OUTPUT` is supported only with a strict deposit address (`strict: true`); an open-ended `EXACT_OUTPUT` request is rejected. | | `amount` | string | The quoted amount in the origin token's smallest unit. Open-ended addresses support variable deposit amounts. Strict addresses should be treated as exact-payment instructions. | | `strict` | boolean | Set to `true` for a strict deposit address tied to a specific order. Omit or set to `false` for an open deposit address. | ### Address Reuse **Open deposit addresses** can be reused for the same route (same origin currency, origin chain, destination currency, and destination chain). Each new deposit triggers a fresh quote and fill. Strict addresses should not be presented as reusable. They are bound to the original order and intended for single-use payment instructions. ## Open vs Strict Deposit Addresses Relay supports two deposit address modes that differ in how flexible they are when handling deposits. ### Open Deposit Addresses Open-ended deposit addresses are the flexible mode for supported routes. They can handle variable deposit amounts, and on some supported chain families they can also adapt to a different supported input token or a deposit on a different chain within the same VM. Adapting to a different token or a wrong chain is not always automatic — recovery may require [manual reindexing](#reindexing-stuck-deposits) before the deposit is recognized. * `refundTo` is recommended. Omitting `refundTo` disables automatic refund — there is no internal fallback. * Best for: general-purpose integrations where you want tolerance for user variability. ### Strict Deposit Addresses Strict deposit addresses are bound to the original order and should be treated as predictable payment instructions. They are not flexible intake points. * `refundTo` is **required**. The request will fail without it. * Set `strict: true` in your `/quote/v2` request. * Best for: integrations that need predictable behavior and explicit refund handling (e.g., payment processors). ### Comparison | Behavior | Open | Strict | | - | - | - | | Accepted currencies | Flexible — may adapt to a different supported token depending on chain family | Only the currency specified in the original quote | | Wrong token (solver currency) | Requote and fill on some chain families (not universal); [relay.link/withdraw](https://relay.link/withdraw) is the fallback if the auto-flow doesn't complete | Recoverable via [relay.link/withdraw](https://relay.link/withdraw) | | Wrong token (non-solver) | Not supported and not currently recoverable | Not supported and not currently recoverable | | Wrong chain (same VM) | Supported, but may require [manual reindexing](#reindexing-stuck-deposits) before the deposit is recognized; some chain families refund instead | Not supported — no automatic wrong-chain recovery | | Wrong chain (different VM) | Not supported | Not supported | | Amount mismatch | Usually requotes for actual amount; too-small deposits may refund | Underpayments refund; exact payments fill; overpayments depend on trade type — `EXACT_INPUT` fills the full deposited amount with no refund of the excess, `EXACT_OUTPUT` fills the quoted amount and refunds the excess | | `refundTo` | Recommended (omitting disables automatic refund) | Required | | Address reuse | Yes, same route | No — bound to original order | ## Example Request and Response ### Open Deposit Address Bridging 0.01 ETH from Base to Optimism using an open deposit address: ```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", "useDepositAddress": true, "refundTo": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the depository contract", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "to": "0x856533b33e4526ca3df3b4df4205955056d9d3f0", "data": "0x", "value": "100000000000000000", "chainId": 8453, "gas": "27300", "maxFeePerGas": "6500000", "maxPriorityFeePerGas": "1000000" }, "check": { "endpoint": "/intents/status/v3?requestId=0x5288854c6ff2bf4eb4f53f515e599217c20ded0654f7bbfa2a459ebfe462a57c", "method": "GET" } } ], "requestId": "0x5288854c6ff2bf4eb4f53f515e599217c20ded0654f7bbfa2a459ebfe462a57c", "depositAddress": "0x856533b33e4526ca3df3b4df4205955056d9d3f0" } ], "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": "143425703256", "amountFormatted": "0.000000143425703256", "amountUsd": "0.000302", "minimumAmount": "143425703256" }, "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": "19775083466725", "amountFormatted": "0.000019775083466725", "amountUsd": "0.041623", "minimumAmount": "19775083466725" }, "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": "773003796434", "amountFormatted": "0.000000773003796434", "amountUsd": "0.001627", "minimumAmount": "773003796434" }, "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": "19002079670291", "amountFormatted": "0.000019002079670291", "amountUsd": "0.039996", "minimumAmount": "19002079670291" }, "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": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "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": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "210.480239", "minimumAmount": "100000000000000000" }, "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": "99980224916533275", "amountFormatted": "0.099980224916533275", "amountUsd": "210.438616", "minimumAmount": "97980620418202610" }, "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": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "210.480239", "minimumAmount": "100000000000000000" }, "totalImpact": { "usd": "-0.041623", "percent": "-0.02" }, "swapImpact": { "usd": "-0.000000", "percent": "-0.00" }, "expandedPriceImpact": { "swap": { "usd": "-0.019996" }, "execution": { "usd": "-0.021627" }, "relay": { "usd": "0" }, "app": { "usd": "0" }, "sponsored": { "usd": "0" } }, "rate": "0.9998022491653327", "slippageTolerance": { "origin": { "usd": "0.000000", "value": "0", "percent": "0.00" }, "destination": { "usd": "4.187728", "value": "1999604498330665", "percent": "1.99" } }, "timeEstimate": 2, "userBalance": "0", "isFixedRate": false, "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": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "210.480239", "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": "210.480239", "minimumAmount": "100000000000000000" }, "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": "99980224916533275", "amountFormatted": "0.099980224916533275", "amountUsd": "210.438616", "minimumAmount": "97980620418202610" }, "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": "99980224916533275", "amountFormatted": "0.099980224916533275", "amountUsd": "210.438616", "minimumAmount": "97980620418202610" }, "router": "relay" } } }, "protocol": { "v2": { "orderId": "0x8391f3eb54c0999767a970a1603aaf16403a17263cdf723adec76e3fda5be275", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0x184ed77f343bff09b7255a75f0be900ac82c00c276c0a97eed2948ab3a1e8503", "inputs": [ { "payment": { "chainId": "base", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000", "weight": "1" }, "refunds": [ { "chainId": "base", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1791082506, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, { "chainId": "optimism", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1791082506, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output": { "chainId": "optimism", "payments": [ { "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "97980620418202610", "expectedAmount": "99980224916533275" } ], "calls": [], "deadline": 1791082506, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "paymentDetails": { "chainId": "base", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000" } } } } ``` ### Strict Deposit Address Same route, but using a strict deposit address. Note the addition of `strict: true` and the required `refundTo`: ```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", "useDepositAddress": true, "strict": true, "refundTo": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the depository contract", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "to": "0xEa04F61d00AFD8649B39b873306949aa11630067", "data": "0x", "value": "100000000000000000", "chainId": 8453, "gas": "27300", "maxFeePerGas": "6500000", "maxPriorityFeePerGas": "1000000" }, "check": { "endpoint": "/intents/status/v3?requestId=0xcb422162ba0a775cadcdc7983fc2108e761840446fe3f241fd8f72286fd3b4f8", "method": "GET" } } ], "requestId": "0xcb422162ba0a775cadcdc7983fc2108e761840446fe3f241fd8f72286fd3b4f8", "depositAddress": "0xEa04F61d00AFD8649B39b873306949aa11630067" } ], "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": "143437744316", "amountFormatted": "0.000000143437744316", "amountUsd": "0.000302", "minimumAmount": "143437744316" }, "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": "19782653616567", "amountFormatted": "0.000019782653616567", "amountUsd": "0.041606", "minimumAmount": "19782653616567" }, "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": "773072690010", "amountFormatted": "0.00000077307269001", "amountUsd": "0.001626", "minimumAmount": "773072690010" }, "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": "19009580926557", "amountFormatted": "0.000019009580926557", "amountUsd": "0.039980", "minimumAmount": "19009580926557" }, "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": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "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": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "210.314210", "minimumAmount": "100000000000000000" }, "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": "99980217346383433", "amountFormatted": "0.099980217346383433", "amountUsd": "210.272604", "minimumAmount": "97980612999455765" }, "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": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "210.314210", "minimumAmount": "100000000000000000" }, "totalImpact": { "usd": "-0.041606", "percent": "-0.02" }, "swapImpact": { "usd": "0.000000", "percent": "0.00" }, "expandedPriceImpact": { "swap": { "usd": "-0.019980" }, "execution": { "usd": "-0.021626" }, "relay": { "usd": "0" }, "app": { "usd": "0" }, "sponsored": { "usd": "0" } }, "rate": "0.9998021734638343", "slippageTolerance": { "origin": { "usd": "0.000000", "value": "0", "percent": "0.00" }, "destination": { "usd": "4.184425", "value": "1999604346927668", "percent": "1.99" } }, "timeEstimate": 2, "userBalance": "0", "isFixedRate": false, "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": "100000000000000000", "amountFormatted": "0.1", "amountUsd": "210.314210", "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": "210.314210", "minimumAmount": "100000000000000000" }, "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": "99980217346383433", "amountFormatted": "0.099980217346383433", "amountUsd": "210.272604", "minimumAmount": "97980612999455765" }, "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": "99980217346383433", "amountFormatted": "0.099980217346383433", "amountUsd": "210.272604", "minimumAmount": "97980612999455765" }, "router": "relay" } } }, "protocol": { "v2": { "orderId": "0x67ffde85c03352d45f9b073ef15fc73c656115d1c472de76aff9c66580eb5dc7", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0xe95244440faaedb49eddcb1dda9a5b82531b38e03c5799242638685b2fdb924e", "inputs": [ { "payment": { "chainId": "base", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000", "weight": "1" }, "refunds": [ { "chainId": "base", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1791082605, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, { "chainId": "optimism", "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1791082605, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output": { "chainId": "optimism", "payments": [ { "recipient": "0xF0AE622e463fa757Cf72243569E18Be7Df1996cd", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "97980612999455765", "expectedAmount": "99980217346383433" } ], "calls": [], "deadline": 1791082605, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "paymentDetails": { "chainId": "base", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000" } } } } ``` Key differences in the strict request: * `strict` is set to `true` * `refundTo` is required — the request fails without it * The deposit address is bound to this specific order and is not reusable ## Quote Regeneration When funds arrive at a deposit address, Relay evaluates what was sent versus what was originally quoted. How mismatches are handled depends on the deposit address mode. ### Open-Ended Addresses **Same Token, Different Amount** * **Exact match** — The original quote can be reused, in which case the fill proceeds under the original `requestId`. Relay can still requote an exact match at sweep time (see [Which Request Owns the Fill](#which-request-owns-the-fill)). * **More than quoted** — A new quote is generated for the larger amount, and the fill proceeds under a new `requestId`. * **Less than quoted** — If the smaller amount still covers fees and the minimum fill, a new quote is generated and the fill proceeds under a new `requestId`. If not, the deposit is refunded to the `refundTo` address (if set). Deposits worth \$0.05 or less are not swept at all — see [Minimum Deposit](#minimum-deposit). **Different Token (Solver Currency)** On some chain families, if the user sends a different token that is a [solver currency](#caveats), Relay can regenerate the quote using the actual currency deposited and fill the order. This is not universal across all chains — some chain families will fail or refund on token mismatch. If a different supported token was sent and nothing happens immediately, use the [reindex endpoint](#reindexing-stuck-deposits) as a fallback. **Different Chain (Same VM)** Open and custodial deposit addresses exist at the same address across chains within a VM family (e.g. all EVM chains), so a deposit can land on a chain other than the one quoted. This is **supported but not always automatic**: Relay's background monitor watches the chain the address was registered on, so a wrong-chain deposit may not be recognized until it is reindexed. If a wrong-chain deposit isn't picked up within a few minutes, trigger detection manually with the [Deposit Address Reindex](#reindexing-stuck-deposits) endpoint, setting `targetChainId` to the chain the funds actually landed on. Once detected, Relay sweeps the funds and proceeds with a fresh quote and fill. Behavior varies by chain family — some families refund a wrong-chain deposit instead of re-routing it. Strict addresses have no wrong-chain recovery (see [Strict Addresses](#strict-addresses)). **Different Chain (Different VM)** This is not possible — deposit address formats differ across VM types (e.g., EVM vs Solana vs Bitcoin), so a user cannot accidentally send to the wrong VM. ### Strict Addresses Strict addresses are bound to the original order. The handling is narrower: * **Exact amount** — The fill proceeds using the original order. * **Underpayment** — The deposit fails and is refunded to `refundTo`. The fill does not proceed. * **Overpayment (`EXACT_INPUT`)** — The full deposited amount is filled. The excess is **not** refunded — the fill scales up to cover everything that was deposited. * **Overpayment (`EXACT_OUTPUT`)** — The fill proceeds for the originally quoted amount and the excess is returned to `refundTo` as a separate refund leg. The fill itself is never scaled up. * **Wrong token or wrong chain** — Not supported. Strict addresses do not have automatic wrong-token or wrong-chain recovery paths. Strict addresses are never requoted: every outcome above, fill or refund, stays under the original `requestId`. On open-ended addresses, mismatches create a new `requestId`, and the quote-time `requestId` does not reach `success` when that happens. Track deposit-address orders by deposit address, not by the quote-time `requestId`. See [Tracking Transactions](#tracking-transactions). ## Refund Behavior What happens when a deposit can't be processed depends on the token type and the `refundTo` configuration. ### Refund Flows There are two distinct refund scenarios: 1. **Correct currency, fill failed** (e.g., slippage, network issues) — If `refundTo` is set, the deposit is automatically refunded to that address, minus the cost of gas. No additional fees are taken. 2. **Wrong currency (non-solver token)** — Not supported and not currently recoverable. If a wrong solver currency was sent and the deposit didn't auto-resolve, users can recover it at [relay.link/withdraw](https://relay.link/withdraw). A same-VM wrong-chain deposit to an open or custodial address is recoverable but may not re-route or refund on its own. Trigger [reindexing](#reindexing-stuck-deposits) with `targetChainId` set to the chain the funds landed on. Strict addresses have no automatic wrong-chain recovery. ### `refundTo` Configuration | `refundTo` value | Behavior | | - | - | | User's address | Refund directly to the user | | App-controlled address | Refund to integrator's address — your support team handles returning funds to the user | | Origin chain's native-currency address (EVM `0x0000000000000000000000000000000000000000`, Bitcoin `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8`, Solana `11111111111111111111111111111111`) | Auto-refund to the original depositor — the address that sent funds to the deposit address. Supported on EVM chains, Bitcoin, and Solana origins; rejected on other VMs. Relay makes a best effort to detect known CEX sender addresses — if the depositor is detected as a CEX address, Relay aborts auto-refund and requires manual refund / recovery instead. | | Not set (open) | Automatic refund is disabled — no internal fallback | | Not set (strict) | Not allowed — `refundTo` is required for strict deposit addresses | If users may send from a centralized exchange, **do not set `refundTo` as the user's address** — neither an explicit user address you don't control nor the native-currency-address auto-refund opt-in — the sender address will be the exchange's hot wallet, not the user's. Use an app-controlled address instead so your support team can handle the last mile. If you still want depositor auto-detection for self-custodied senders, pair the auto-refund opt-in with a [`recoveryAddress`](#recoveryaddress) as the fallback. When auto-refund to the original depositor is triggered, the request's `outTxs` will contain **two** refund transactions. The first transaction is the solver-to-depositor transfer that actually returns funds to the depositor — this is the one to track from your integration. The second is an internal protocol-settlement transaction that does not move user funds and can be ignored by integrators. ### `recoveryAddress` `recoveryAddress` is for cases where Relay cannot auto-refund. It should be an integrator-controlled EOA on the origin chain. Use `recoveryAddress` when an integrator wants to use Relay's depositor detection, but also needs a fallback recovery path if the detected depositor cannot safely receive an automatic refund. This is most relevant for deposits from custodial sources like centralized exchanges, unsupported currency deposits, or blocked depositor addresses. When `recoveryAddress` is supplied: * Relay still attempts to auto-refund where possible. * If Relay cannot auto-refund, the integrator can recover funds using `recoveryAddress`. * Existing `refundTo` behavior remains unchanged for flows that do not supply `recoveryAddress`. `recoveryAddress` never receives refunds — it is only used to withdraw funds from the depository contract in the case that we cannot fill or refund. Constraints: * Requires `useDepositAddress: true` — the request is rejected otherwise. * Must be a valid address on the origin chain and cannot be the origin chain's native-currency address. * Strict deposit addresses still require `refundTo` even when `recoveryAddress` is set. ### Recommended Setup * **Open deposit addresses**: Set `refundTo` to the user's address for automatic refunds. * **Strict deposit addresses**: Always set `refundTo`. If the sender is unknown (e.g., CEX withdrawal), use an app-controlled address so your support team can manage refunds. * **Auto-refund to depositor**: When users deposit from wallets they control, you can pass `refundTo` as the origin chain's native-currency address (EVM `0x0000000000000000000000000000000000000000`, Bitcoin `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8`, Solana `11111111111111111111111111111111`) to refund to the original depositor automatically. The first of the two resulting `outTxs` is the transfer to the depositor — track that one for refund delivery. Avoid this when deposits may originate from a centralized exchange, or pair it with a [`recoveryAddress`](#recoveryaddress) so funds Relay cannot safely auto-refund remain recoverable. ## Tracking Transactions The `requestId` in the quote response is not always the request that fills. Depending on what arrives at the address, the sweep and fill can move to a new `requestId` (see [Which Request Owns the Fill](#which-request-owns-the-fill)). Treat the deposit address as the stable identifier for the order. ### Querying by Deposit Address Poll the [Get Requests API](/references/api/get-requests) with the `depositAddress` query parameter. This is the canonical way to monitor a deposit-address order, because every request that fills from the address — original or regenerated — carries the same `depositAddress`: ```bash Request theme={null} curl -X GET "https://api.relay.link/requests/v3?depositAddress=0xe42445f3a8f05e3311beba77c260b51527b1c141&sortBy=updatedAt&sortDirection=desc&limit=20" \ -H "x-api-key: YOUR_API_KEY" ``` ```json Response expandable theme={null} { "requests": [ { "id": "0x1790166098388727d09f98e1cf17b1e1fc41cf4bd3286866c22ef5a41c5701b9", "status": "success", "user": "0xe42445f3a8f05e3311beba77c260b51527b1c141", "recipient": "0xc1D5041826d883895E1787543336f7d7BC8CCe93", "sender": "0xe42445f3a8f05e3311beba77c260b51527b1c141", "refundTo": "0xc1d5041826d883895e1787543336f7d7bc8cce93", "supersededByRequestId": null, "duplicateDeposits": null, "depositAddress": { "address": "0xe42445f3a8f05e3311beba77c260b51527b1c141", "type": "open", "depositor": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "depositTxHash": "0xf93e6174d36c103f31cdbb1e4006df949b9c0d86fa9ec74aaa6958898d91993f", "recoveryAddress": null }, "data": { "slippageTolerance": "300", "failReason": null, "refundFailReason": null, "usesExternalLiquidity": false, "timeEstimate": 1, "triggeredByFastFill": null, "inTxs": [ { "txHash": "0x8c283eea70d3d96e9d4443067184574c856cc9c9bab80b30b92383a6ac45270f", "fee": "15347069541639", "feeUsd": "0.041819", "block": 51688383, "type": "onchain", "chainId": 8453, "timestamp": 1790166113, "status": "success", "data": { "data": "0xe8017952000000000000000000000000c1d5041826d883895e1787543336f7d7bc8cce93000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000006f5a005c82b69b55cd27f11c9ac2bdf046f03fabfdd0dfd388504dcaa35dd615f20354d", "from": "0xe42445f3a8f05e3311beba77c260b51527b1c141", "to": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "value": "0" }, "stateChanges": [ { "address": "0xe42445f3a8f05e3311beba77c260b51527b1c141", "change": { "data": { "tokenAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "tokenKind": "ft" }, "kind": "token", "balanceDiff": "-116760581" } }, { "address": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "change": { "data": { "tokenAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "tokenKind": "ft" }, "kind": "token", "balanceDiff": "116760581" } } ] } ], "outTxs": [ { "txHash": "0x6ed9468c7581e2db705304450829d60203420072108501ad12a1f1df3485bcc6", "fee": "0", "block": 1158162902, "type": "onchain", "chainId": 1337, "timestamp": 1790166114, "status": "success", "data": { "action": { "hyperliquidChain": "Mainnet", "amount": "116.293228", "destinationDex": "", "fromSubAccount": "", "signatureChainId": "0x1", "destination": "0xc1d5041826d883895e1787543336f7d7bc8cce93", "type": "sendAsset", "nonce": 1790166113562, "token": "USDC:0x6d1e7cde53ba9467b783cb7c530ce054", "sourceDex": "" }, "block": 1158162902, "time": 1790166114217, "error": null, "user": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "hash": "0x6ed9468c7581e2db705304450829d60203420072108501ad12a1f1df3485bcc6" }, "stateChanges": [ { "address": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "change": { "data": { "tokenAddress": "0x00000000000000000000000000000000", "tokenKind": "ft" }, "kind": "token", "balanceDiff": "-11629322800" } }, { "address": "0xc1d5041826d883895e1787543336f7d7bc8cce93", "change": { "data": { "tokenAddress": "0x00000000000000000000000000000000", "tokenKind": "ft" }, "kind": "token", "balanceDiff": "11629322800" } } ] } ], "appFees": { "quoted": [ { "recipient": "0xa6309Ad2C4524b991Bb795227edf6482EBE927e6", "bps": "20", "amount": "233521", "amountFormatted": "0.233521" } ], "actual": [ { "recipient": "0xa6309Ad2C4524b991Bb795227edf6482EBE927e6", "bps": "20", "amount": "233521", "amountFormatted": "0.233521", "amountUsd": "0.233491" } ], "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 } } }, "feeSponsorship": null, "fees": { "quoted": { "swap": { "usd": "-0.011674", "amount": "11676", "amountFormatted": "0.011676" }, "execution": { "usd": "-0.222127", "amount": "222156", "amountFormatted": "0.222156" }, "platform": { "usd": "0.000000", "amount": "0", "amountFormatted": "0.0" }, "app": { "usd": "-0.233491", "amount": "233521", "amountFormatted": "0.233521" }, "sponsored": { "usd": "0", "amount": "0", "amountFormatted": "0.0" } }, "actual": { "swap": { "usd": "-0.011627", "amount": "11629", "amountFormatted": "0.011629" }, "execution": { "usd": "-0.222127", "amount": "222156", "amountFormatted": "0.222156" }, "platform": { "usd": "0.000000", "amount": "0", "amountFormatted": "0.0" }, "app": { "usd": "-0.233491", "amount": "233521", "amountFormatted": "0.233521" }, "sponsored": { "usd": "0", "amount": "0", "amountFormatted": "0.0" } }, "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 } } }, "route": { "quoted": { "origin": { "inputCurrency": { "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": "116760581", "amountFormatted": "116.760581", "amountUsd": "116.745519", "minimumAmount": "116760581" }, "outputCurrency": { "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": "116760581", "amountFormatted": "116.760581", "amountUsd": "116.745519", "minimumAmount": "116760581" }, "router": "relay" }, "destination": { "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": "11629322800", "amountFormatted": "116.293228", "amountUsd": "116.278226", "minimumAmount": "11280443100" }, "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": "11629322800", "amountFormatted": "116.293228", "amountUsd": "116.278226", "minimumAmount": "11280443100" }, "router": "relay" } }, "actual": { "origin": { "inputCurrency": { "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": "116760581", "amountFormatted": "116.760581", "amountUsd": "116.745519", "minimumAmount": "116760581" }, "outputCurrency": { "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": "116760581", "amountFormatted": "116.760581", "amountUsd": "116.745519", "minimumAmount": "116760581" }, "router": "relay" }, "destination": { "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": "11629322800", "amountFormatted": "116.293228", "amountUsd": "116.278226", "minimumAmount": "11629322800" }, "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": "11629322800", "amountFormatted": "116.293228", "amountUsd": "116.278226", "minimumAmount": "11280443100" }, "router": "relay" }, "rate": "0.9959973392047441" } }, "quoteOraclePrice": null, "refundCurrencyData": null, "externalMetadata": { "moonpayId": null } }, "protocol": { "orderId": "0xc82b69b55cd27f11c9ac2bdf046f03fabfdd0dfd388504dcaa35dd615f20354d", "hubType": "onchain", "isWithdrawable": false, "solver": { "address": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "protocolChainId": "base", "chainId": 8453 }, "deposit": { "origin": { "amount": "116760581", "chainId": 8453, "currency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "depositor": "0xc1d5041826d883895e1787543336f7d7bc8cce93", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "onchainId": "0xc69162545403ebb1dab64c8e189773298029d16fc9685763a9d4c4ad2cad955d", "transactionId": "0x8c283eea70d3d96e9d4443067184574c856cc9c9bab80b30b92383a6ac45270f" }, "attestation": { "execution": { "actions": [ "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000910d4e2d5fc21d3a87e6b4ec4bebd74125dbed81bf442206ca81b4b0b8b1b3f0a290c277e9b2d7269209615d306c15a8cd8d39380000000000000000000000000000000000000000000000000000000006f5a005" ], "idempotencyKey": "0x4dad103afee0424f7aff6e9a52c98b20221619eff58349a974b47557a2dfd0bf" }, "attestations": [ { "oracleChainId": "537713", "oracleContract": "0xd180dc3b8cb71b185c563b6e4857592ca93dacaf", "signerAddress": "0x2c598f73a5ab65c0510fd8efb740826d291cab0e", "signature": "0x0fa4d7de9912ee2abb0146359e5262924a0d1af291fcc8da2f0dea799d980fb7162aa9c31d89b01918b651c9bbda71ae81c3b703e206059f9eabcccc230686a71b" }, { "oracleChainId": "537713", "oracleContract": "0xd180dc3b8cb71b185c563b6e4857592ca93dacaf", "signerAddress": "0x5be2dd28f7810582ed6dc910fd8693878a1d90f2", "signature": "0x968cd778f15fd99d8a59854c4a67d01331f65600a92f44d9b4838bc3861ac4d3228712b9f55652c1e7c34d16afc1fb489ec81b88b4e35676357d1e44c4ac9be01b" } ] } }, "settlement": { "destination": { "fills": [ { "chainId": 1337, "transactionId": "0x6ed9468c7581e2db705304450829d60203420072108501ad12a1f1df3485bcc6" } ], "refunds": [] }, "attestation": { "execution": { "actions": [ "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000910d4e2d5fc21d3a87e6b4ec4bebd74125dbed81000000000000000000000000d5fa4f5834722d85d95f45387ed60ce09ba4e36fbf442206ca81b4b0b8b1b3f0a290c277e9b2d7269209615d306c15a8cd8d39380000000000000000000000000000000000000000000000000000000006f5a005" ], "idempotencyKey": "0xae0828dedaefef317505a18d03aeedaeaf1abc07217b4d5d0fcd6a3f00b0a23a" }, "attestations": [ { "oracleChainId": "537713", "oracleContract": "0xd180dc3b8cb71b185c563b6e4857592ca93dacaf", "signerAddress": "0x5be2dd28f7810582ed6dc910fd8693878a1d90f2", "signature": "0x0655f100928e26698f6e2a3109415fc7c4687c42acb6ddd4587b6d6611bf91b52b171c1bd40f8989e209c58b519f0b6e9bc69680122241b86e0084dc56b81da51c" }, { "oracleChainId": "537713", "oracleContract": "0xd180dc3b8cb71b185c563b6e4857592ca93dacaf", "signerAddress": "0xa41716ff5a7d4ad22f83b7cb74d9af9767a96a52", "signature": "0x125ce59dffd08fb8ef9a386ac339b56d9892e6f44c22bf6588bde55cf33ec1b348191daa9d1c8e948ff9563ca8713217109659457862b2321992a28e38f7eab51c" } ] } } }, "requestType": "Crosschain Swap", "features": [ "app_fees", "deposit_address" ], "createdAt": "2026-09-23T12:21:52.744Z", "updatedAt": "2026-09-23T12:21:55.515Z" } ] } ``` See [Get Requests](/references/api/get-requests) for the full response schema. `GET /requests/v2` also accepts `depositAddress`, but it is deprecated and will be retired on November 24, 2026. ### Which Request Owns the Fill Whether the fill runs under the quote-time `requestId` depends on the deposit: * **Strict addresses** — Always the original `requestId`. Strict addresses are never requoted. * **Open addresses, original `requestId`** — The first deposit matches the quote exactly (same currency and amount) and Relay does not requote it at sweep time. * **Open addresses, new `requestId`** — Any of the following can move the sweep and fill to a regenerated request: * The deposit is a different amount or a different supported token. * The deposit lands on a different EVM chain than the one quoted. * The address already received an earlier deposit. Every later deposit gets its own `requestId`. * Relay requotes at sweep time, even for an exact match. This happens on quotes with sponsored fees, and on EVM when the deposit is the native token or the quote has no explicit `refundTo`. You can't always tell in advance which case applies, so poll by `depositAddress` rather than by the quote-time `requestId`. An open address can return several requests, one per deposit. When `depositAddress.depositTxHash` is populated, use it to match each request to the transfer that funded it. `includeChildRequests=true` does not return regenerated requests. It only adds duplicate-deposit requests to an `id` lookup. The `depositAddress` query already includes regenerated requests. ### Handling Quote Regeneration After a regeneration, what the quote-time `requestId` returns depends on the origin chain: | Origin chain | Quote-time `requestId` after regeneration | | - | - | | Bitcoin | Stays `pending` in `GET /requests/v3`, with `supersededByRequestId` pointing to the regenerated request | | All other chains, including EVM and Solana | Not returned by `GET /requests/v3`. `GET /intents/status/v3` keeps returning `waiting`, even after the regenerated request succeeds | Outside Bitcoin, a quote-time `requestId` can stay `waiting` indefinitely after its funds are filled under a new `requestId`. Do not treat a long-running `waiting` status as a stuck deposit until you have checked the deposit address. **`supersededByRequestId` (Bitcoin only)** — On Bitcoin-origin requests, the top-level `supersededByRequestId` in `GET /requests/v3` (or `data.supersededByRequestId` on the legacy `GET /requests/v2`) points from the original request to the regenerated one. It holds only the most recent regenerated `requestId`: a later deposit to the same address or a retried sweep replaces it. Until broader chain support ships, the field is `null` on every other chain. ```bash theme={null} curl -X GET "https://api.relay.link/requests/v3?id=" -H "x-api-key: YOUR_API_KEY" ``` ```json theme={null} { "requests": [ { "id": "", "status": "pending", "supersededByRequestId": "" } ] } ``` Use `supersededByRequestId` as a shortcut when you only have a Bitcoin `requestId`. The target can change with later deposits, so the `depositAddress` query remains the source of truth. ### Request-Level Status Once the `depositAddress` query returns the request that owns the fill, you can poll its `requestId` with [Get Status](/references/api/get-intents-status-v3). To avoid polling, configure a [webhook](/references/api/api_guides/webhooks) instead. Webhook events are sent under the request that owns the fill, so a regenerated request's events carry the new `requestId` — match them to your order with `depositAddress.address` in the payload. ```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 } ``` ### Reindexing Stuck Deposits Relay's background monitor only checks the originally quoted input token. If a user sent a different supported (solver) token, the monitor may not detect it automatically. Use the [Deposit Address Reindex](/references/api/deposit-address-reindex) endpoint to trigger on-demand re-detection — it checks every solver-depositable currency on the chain and queues a sweep for any non-zero balances. ```bash theme={null} curl -X POST 'https://api.relay.link/transactions/deposit-address/reindex' \ -H 'Content-Type: application/json' \ -d '{ "chainId": 8453, "depositAddress": "0x1ba74e01d46372008260ec971f77eb6032b938a4" }' ``` **`chainId` is the chain the deposit address was originally registered on.** Pass `targetChainId` to run the reindex on a different chain — useful when funds were sent to the right address but on the wrong chain (e.g. the address was registered for Arbitrum but funds landed on Ethereum). This is the chain being reindexed, not the destination chain of the original quote. Pass `currency` to scope the reindex to a single currency address instead of iterating every depositable currency. Any currency registered with Relay on the target chain is accepted — including currencies outside the standard solver-depositable list (e.g. a HyperCore-registered token when the deposit address was created for HyperEVM). ```bash theme={null} curl -X POST 'https://api.relay.link/transactions/deposit-address/reindex' \ -H 'Content-Type: application/json' \ -d '{ "chainId": 42161, "depositAddress": "0x1ba74e01d46372008260ec971f77eb6032b938a4", "targetChainId": 1, "currency": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" }' ``` The deposit transaction hash cannot be used to look up the status of a deposit address bridge. Track status by the deposit address, and use a `requestId` only once the `depositAddress` query has returned it. ## Caveats ### Gas Overhead Deposit addresses add gas overhead compared to direct calldata execution because Relay must sweep funds from the deposit address: | Method | Token Type | Gas Overhead | | - | - | - | | Receiver | Native tokens (ETH, MATIC, etc.) | \~33,000 gas | | Protocol | ERC-20 tokens | \~70,000 gas | For very small amounts, the gas overhead may make deposit addresses less cost-effective than direct calldata execution. ### Minimum Deposit Relay doesn't sweep deposits worth **\$0.05 USD or less** — the request stays in `waiting` and funds remain on the address. The threshold is USD-based and converted to the deposited token at its current value; there's no fixed per-token minimum. Quotes at or below the threshold return [`AMOUNT_TOO_LOW`](/references/api/api_core_concepts/handling-errors), but open-ended addresses accept any amount, so enforce a minimum above \$0.05 in your own limits. ### Supported Currencies Only tokens listed as **solver currencies** for a given chain can be processed by deposit addresses. The input token must be a solver-depositable currency for the requested route. The destination token can differ and be completed through a destination-side swap — in that case, `refundTo` is required. Relay treats certain tokens as equivalent within currency groups (e.g., ETH and WETH) — depositing any token in a group triggers the same fill behavior. The table below is loaded live from the [chains API](/references/api/get-chains). Each chain's `solverCurrencies` array is the authoritative source for which tokens are supported. Non-solver tokens and NFTs sent to deposit addresses are **not recoverable** through normal processes. Always verify the token is a solver currency before depositing. ### Chain-Specific Notes * **Bitcoin** — Standard deposits are processed after 1 block confirmation. High-value deposits (above the per-currency threshold) wait for 2 block confirmations to reduce reorg risk. Deposits are detected via mempool monitoring but only acted on after the applicable confirmation count. * **Solana** — Supports SPL tokens through the protocol deposit method. Native SOL and SPL tokens like USDC are solver currencies on Solana. * **Hyperliquid** — Uses bridged USDC (USDC.e) with different decimal precision than native USDC on other chains. Be aware of decimal mismatches when computing amounts. ### Other Limitations * Calldata execution on destination is not allowed. # 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. Create one in the [Relay Dashboard](https://dashboard.relay.link); see [API keys and Rate Limits](/references/api/api-keys) for details. 2. **A Fee Sponsorship Wallet** — A wallet linked to your API key that funds your app balance. Link one from the [App Balance page](https://dashboard.relay.link/balance) in the Relay Dashboard by signing a message to prove ownership — no gas required. 3. **Sufficient App Balance** — Your app balance must have enough funds to cover fast fills and fee sponsorship. When a fast fill is triggered, a hold is placed against your app balance for the fill amount. This hold is released once the user's deposit lands onchain. While holds are active, they reduce the balance available for new fast fills and claims, but do not block [fee sponsorship](/features/fee-sponsorship) deductions. ### One app balance for every route Fast Fill uses a single app balance, held in USDC on Base, as collateral for all supported routes. You don't need to fund a separate balance on each origin or destination chain: a solver fills the destination with its own capital, and each hold is the USD value of the request's origin input, placed against your one app balance regardless of the chains involved. The balance belongs to your Fee Sponsorship Wallet, so API keys linked to the same wallet share it. See [Funding Your App Balance](#funding-your-app-balance) to top it up. 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. When a fast fill is triggered, the transaction is instantly filled on the destination chain and a hold is placed against your app balance for the fill amount. Once the user's deposit lands onchain and Relay indexes it, the hold is released. ### If the origin deposit never lands Fast Fill advances the destination funds before Relay confirms the origin deposit. If the deposit never lands, Relay does not release the hold. The sponsoring app bears the loss, and the held amount becomes a permanent debit against its app balance. ### Example This example demonstrates the full fast fill flow: getting a quote, submitting the transaction onchain, calling the fast fill API, and monitoring the status. ```typescript SDK expandable 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 expandable 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 fund your app balance through the Base address below. This app-balance flow uses a direct transfer to a solver address, not a Relay Protocol Depository deposit. Appending specific calldata identifies the account to credit. **How It Works** The platform indexes the transfer and credits your app balance using the appended calldata. 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. **App Balance Funding Address** Use this address only for the app-balance funding flow described here. For transaction deposits, use the steps returned by the quote API instead. | 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 APP_BALANCE_FUNDING_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: [APP_BALANCE_FUNDING_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. * Fast Fill holds, outstanding fast fills that are waiting to be indexed on the destination chain, reduce the balance available for new fast fills and claims, but do not block fee sponsorship. # 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. Create one in the [Relay Dashboard](https://dashboard.relay.link); see [API keys and Rate Limits](/references/api/api-keys) for details. 2. **A Fee Sponsorship Wallet** — A wallet linked to your API key that funds your app balance. Link one from the [App Balance page](https://dashboard.relay.link/balance) in the Relay Dashboard by signing a message to prove ownership — no gas required. 3. **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 fund your app balance through the Base address below. This app-balance flow uses a direct transfer to a solver address, not a Relay Protocol Depository deposit. Appending specific calldata identifies the account to credit. **How It Works** The platform indexes the transfer and credits your app balance using the appended calldata. 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. **App Balance Funding Address** Use this address only for the app-balance funding flow described here. For transaction deposits, use the steps returned by the quote API instead. | 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 APP_BALANCE_FUNDING_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: [APP_BALANCE_FUNDING_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-v2) 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 sponsor pays up to the cap and the user pays the remainder. | | `subsidizationBps` | `integer` | *(Optional)* The share of each selected fee bucket the sponsor covers, in basis points from `0` to `10000` (e.g., `10000` = 100%, `2500` = 25%). Requires `subsidizeFees: true`. Defaults to full coverage when omitted; the user pays the remainder. Applied per bucket before `maxSubsidizationAmount`, which still caps the sponsor's total. Only supported on swap execution flows. See [How `subsidizationBps` Works](#how-subsidizationbps-works). | | `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. | [Deposit addresses](/features/deposit-addresses) do not support `sponsoredFeeComponents` or `subsidizationBps`. `maxSubsidizationAmount` is supported by deposit addresses but if the amount exceeds the maximum the user pays all fees and nothing is subsidized. ### 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 spending cap for fee sponsorship: * If the total fees are **at or below** this amount, the sponsor covers the full cost * If the total fees **exceed** this amount, the sponsor pays up to the cap and **the user pays the remainder** This protects you from unexpectedly high costs during network congestion or volatile market conditions, while still providing partial sponsorship benefit to your users. With partial fee sponsorship, users always receive some benefit from your sponsorship even when fees spike. For example, if you set a $5.00 cap and fees total $7.00, you pay $5.00 and the user pays $2.00. Partial fee sponsorship is not supported for deposit addresses, if the fees exceed the `maxSubsidizationAmount` the user will cover the fees completely and nothing will be sponsored. ### How `subsidizationBps` Works The **`subsidizationBps`** parameter controls the **share** of each selected fee bucket the sponsor covers, expressed in basis points where `10000` equals 100%. Use it when you want to co-fund fees with your user at a fixed ratio instead of covering everything up to a USD cap. * `10000` — the sponsor covers the full bucket (same as omitting the parameter). * Values between `0` and `10000` — the sponsor covers that share of each selected bucket, and the user pays the remainder. * `0` — the sponsor covers nothing on selected buckets. The share is applied to every bucket named in `sponsoredFeeComponents` (or every eligible bucket when `sponsoredFeeComponents` is omitted). Unselected buckets remain fully user-paid, as they would without a bps share. **Interaction with `maxSubsidizationAmount`.** The bps share is applied to each bucket first, then the USD cap bounds the resulting sponsor total. `maxSubsidizationAmount` continues to limit the sponsor's exposure even when a bps share is set — if the bps-scaled sponsored amount would exceed the cap, the sponsor still pays only up to the cap and the user pays the remainder. **Example.** With `sponsoredFeeComponents: ["execution", "swap"]` and `subsidizationBps: 5000`, the sponsor covers 50% of the execution bucket and 50% of the swap bucket; the user pays the other 50% of each plus any unselected buckets. Adding `maxSubsidizationAmount: "3000000"` on top caps the sponsor's total contribution at \$3.00 regardless of the bps calculation. `subsidizationBps` is currently supported only on swap execution flows. Bridge and [deposit address](/features/deposit-addresses) flows return `400` when the field is set — use `maxSubsidizationAmount` for those flows instead. Rounding is truncated per bucket, so the sponsor's share is always at or below the requested percentage — any single-unit remainder is user-paid. *** ## 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). * **Solana ATA rent** — The `rent` component is user-paid unless you select it explicitly in `sponsoredFeeComponents` or set `subsidizeRent`. See [Solana Fee Sponsorship](#solana-fee-sponsorship). * **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. **Swap costs and app fees are sponsored by default.** With `subsidizeFees: true` and no **`sponsoredFeeComponents`**, the sponsor covers `execution`, `swap`, `relay`, and `app`: * **Swap costs** (`swap`) cover DEX fees, DEX swap impact, and solver rebalancing. To leave them with the user, omit `swap` from `sponsoredFeeComponents`. * **App fees** (`app`) are drawn from your app balance and still accrue to the recipients named in `appFees`. Omit `app` from `sponsoredFeeComponents` to keep collecting app fees from the user while sponsoring the rest. *** ## 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 [Cost & Fee Structure](/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 onchain 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-v2) — API reference for quote requests * [Cost & Fee Structure](/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? To use gasless execution you'll need an API key with a linked funding address and a funded app balance. Create an API key in the [Relay Dashboard](https://dashboard.relay.link), then see [Fee Sponsorship](/features/fee-sponsorship) for details on linking a funding address and funding your app balance. ### 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 depository contract", "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. ## Handling simulation errors Relay simulates `/execute` raw calls before submission. If the simulation reverts, the API returns `400` with a decoded error label, a message, the `requestId`, and trace details when Relay can decode the revert path. ```typescript theme={null} type ExecuteSimulationErrorResponse = { error: string; message: string; requestId: string; details?: SimulationErrorDetails; }; type SimulationErrorDetails = { topLevel: SimulationErrorFrame; innermost: SimulationErrorFrame; path: SimulationErrorFrame[]; revert: { type: "errorString" | "panic" | "customError" | "unknownCustomError" | "empty"; label: string; message: string; selector?: string; name?: string; args?: Record; rawArgsHex?: string; }; }; type SimulationErrorFrame = { type: string; address?: string; contract?: string; function?: string; selector?: string; error?: string; revertReason?: string; gasUsed?: string; }; ``` Use **`error`** and **`message`** for integration handling, and include **`requestId`** when contacting Relay support. Use **`details.innermost`** and **`details.revert`** to identify the contract, function selector, and revert data that caused the simulation failure. ```json Response theme={null} { "error": "INVALID_SIGNATURE", "message": "Invalid signature", "requestId": "0x887091202256e66960493eb09c410188307cc734c919e89696b3ae9993a7fa1d", "details": { "topLevel": { "type": "CALL", "address": "0xca11bde05977b3631167028862be2a173976ca11", "contract": "Multicall3", "function": "Multicall3.aggregate3Value" }, "innermost": { "type": "CALL", "address": "0x000000000022d473030f116ddee9f6b43ac78ba3", "contract": "Permit2", "selector": "0xda9c8680", "error": "execution reverted" }, "path": [ { "type": "CALL", "contract": "Multicall3", "function": "Multicall3.aggregate3Value" }, { "type": "CALL", "contract": "Permit2", "selector": "0xda9c8680", "error": "execution reverted" } ], "revert": { "type": "customError", "label": "INVALID_SIGNATURE", "message": "Invalid signature", "selector": "0x8baa579f", "name": "InvalidSignature" } } } ``` If Relay cannot map a custom error selector to a known contract error, the response uses a label of the form `UNKNOWN_CUSTOM_ERROR_` and includes **`rawArgsHex`** when encoded revert arguments are available. ### 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 EIP-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 walletClient.signAuthorization({ account: walletClient.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 Remove gas friction from swaps so users only pay with the input token 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?]) --> Origin Origin{Is the origin
chain Solana?} Origin -->|Yes| FeePayer Origin -->|No — EVM| 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"/] FeePayer[/"Sponsored Fee Payer

Your sponsor wallet pays fees and rent"/] click Permit "#permit-based-gasless" _blank click Smart4337 "#erc-4337-gasless" _blank click BatchExecutor "#7702-batchexecutor" _blank click FeePayer "#gasless-swaps-on-solana" _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 style FeePayer fill:#f9f0e3,stroke:#c84,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 offchain EIP-3009 or Permit2 message and submits it through the Relay platform. Solvers execute the authorized flow and pay transaction gas. USDC is fully gasless. Other ERC-20 tokens require a one-time approval transaction. Same-chain ERC-20 → ERC-20 swaps do not support the permit flow: passing **`usePermit: true`** is ignored and the quote returns `approve` and `swap` transaction steps instead of a permit signature step. Same-chain ERC-20 → native swaps do return the [`authorize2`](/references/api/api_core_concepts/step-execution#authorize2) permit step. ### 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 The signed permit authorizes the token transfer. A solver completes the destination transaction using its own capital. ### ERC-3009 permits with MulticallAuthorization From October 26, 2026, Relay routes permits through the v3.2 ApprovalProxy (see [Migrating to the new router](/references/api/api_resources/contract-addresses#migrating-to-the-new-router)). On that proxy, an ERC-3009 permit (the USDC style `TransferWithAuthorization`) needs a second signature: a `MulticallAuthorization` that ties the permit to the exact calls Relay runs with the user's tokens. Opt in by passing **`useMulticallAuthorization: true`** in the quote request. The permit signature step (`authorize1`, or `authorize2` for a same-chain swap) then holds two items. Sign and post them in this order: 1. **`MulticallAuthorization`**: EIP-712 typed data with the domain name `RelayApprovalProxy`, version `3.1`, the chain id, and the v3.2 ApprovalProxy as the verifying contract. Post it to `/execute/permits` with kind `multicall-authorization`. This item has no `check`, since nothing executes yet. 2. **ERC-3009 authorization**: the usual `TransferWithAuthorization`, posted with kind `eip3009`. Once both signatures are posted, Relay submits the permit. For a same-chain swap (`authorize2`), the response returns the swap transaction for the user to send instead. Posting the ERC-3009 signature first returns a 400. Sign both items as the quote returns them: the ERC-3009 nonce is derived from the `MulticallAuthorization`, so changing either one invalidates the pair. ```json expandable theme={null} { "status": "incomplete", "data": { "sign": { "signatureKind": "eip712", "domain": { "name": "RelayApprovalProxy", "version": "3.1", "chainId": 8453, "verifyingContract": "0x5567954cd026cebfe486062cb75848e0f1c12e58" }, "types": { "MulticallAuthorization": [ { "name": "from", "type": "address" }, { "name": "relayer", "type": "address" }, { "name": "msgValue", "type": "uint256" }, { "name": "refundTo", "type": "address" }, { "name": "nftRecipient", "type": "address" }, { "name": "metadata", "type": "bytes" }, { "name": "fundingIndex", "type": "uint256" }, { "name": "calls", "type": "Call3Value[]" }, { "name": "funding", "type": "FundingAuthorization[]" } ], "Call3Value": [ { "name": "target", "type": "address" }, { "name": "allowFailure", "type": "bool" }, { "name": "value", "type": "uint256" }, { "name": "callData", "type": "bytes" } ], "FundingAuthorization": [ { "name": "token", "type": "address" }, { "name": "from", "type": "address" }, { "name": "value", "type": "uint256" }, { "name": "validAfter", "type": "uint256" }, { "name": "validBefore", "type": "uint256" } ] }, "primaryType": "MulticallAuthorization", "value": { "from": "WALLET_ADDRESS", "relayer": "0x...", "msgValue": "0", "refundTo": "WALLET_ADDRESS", "nftRecipient": "WALLET_ADDRESS", "metadata": "0x...", "fundingIndex": 0, "calls": [ { "target": "0x...", "allowFailure": false, "value": "0", "callData": "0x..." } ], "funding": [ { "token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "from": "WALLET_ADDRESS", "value": "1000000", "validAfter": 0, "validBefore": 1764948419 } ] } }, "post": { "endpoint": "/execute/permits", "method": "POST", "body": { "kind": "multicall-authorization", "requestId": "0x...", "api": "swap" } } } } ``` Without the opt in, ERC-3009 permits keep using the v3 ApprovalProxy with a single signature during the transition. You can pass the flag before the switch: until then the step holds only the ERC-3009 item. Permit2 permits need no change. On the v3.2 ApprovalProxy the `RelayerWitness` in the quote's typed data includes `msgValue`; if you build Permit2 messages yourself, take the witness from the quote. ### 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. Set all gas fee fields to 0 and submit the `handleOps` call through the platform's [/execute](/references/api/execute) endpoint; a relayer broadcasts the transaction and pays 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)* *** ## Gasless Swaps on Solana Use this approach for Solana-origin trades, where permits do not exist. A sponsor wallet your app controls pays the transaction fees and rent, so your users need zero SOL: pass **`depositFeePayer`** in the quote, and your app co-signs each transaction as the fee payer. You need a sponsor wallet funded with SOL (your backend holds its key; it never touches the client), a Relay [API key](/references/api/api-keys), and `@solana/web3.js` on your backend. Three restrictions from [Solana Fee Sponsorship](/features/fee-sponsorship#solana-fee-sponsorship) apply to every step below: 1. **`depositFeePayer`** must be a different address from **`user`**. The quote API does not enforce this, so your app must. 2. The sponsor signs the transaction before the user. Signing in the other order lets the user modify the transaction and drain the sponsor. 3. Do not use **`depositFeePayer`** for same-chain Solana swaps with untrusted users. That combination is exploitable. ### How it works Call `/quote/v2` with **`depositFeePayer`** set to your sponsor wallet. ```bash theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "user": "USER_SOLANA_ADDRESS", "recipient": "RECIPIENT_ADDRESS", "originChainId": 792703809, "originCurrency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "destinationChainId": 8453, "destinationCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "tradeType": "EXACT_INPUT", "amount": "5000000", "depositFeePayer": "SPONSOR_SOLANA_ADDRESS" }' ``` The response's `deposit` step carries raw `instructions`, `addressLookupTableAddresses`, and a `check` endpoint for tracking. On your backend, build a versioned transaction from the returned instructions with the sponsor as **`payerKey`** (instruction `data` is hex-encoded). ```typescript expandable theme={null} import { AddressLookupTableAccount, Connection, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction, } from "@solana/web3.js"; const connection = new Connection(SOLANA_RPC_URL); const item = quote.steps[0].items[0]; // 1. Rebuild the instructions returned by the quote const instructions = item.data.instructions.map( (i) => new TransactionInstruction({ programId: new PublicKey(i.programId), keys: i.keys.map((k) => ({ pubkey: new PublicKey(k.pubkey), isSigner: k.isSigner, isWritable: k.isWritable, })), data: Buffer.from(i.data, "hex"), }) ); // 2. Resolve the address lookup tables const lookupTables = ( await Promise.all( (item.data.addressLookupTableAddresses ?? []).map((address: string) => connection .getAddressLookupTable(new PublicKey(address)) .then((res) => res.value) ) ) ).filter((t): t is AddressLookupTableAccount => t !== null); // 3. Compile with the sponsor as the fee payer const message = new TransactionMessage({ payerKey: new PublicKey(SPONSOR_SOLANA_ADDRESS), instructions, recentBlockhash: (await connection.getLatestBlockhash()).blockhash, }).compileToV0Message(lookupTables); const transaction = new VersionedTransaction(message); ``` Sign with the sponsor key on your backend, then hand the partially signed transaction to the client for the user's signature. ```typescript theme={null} // Backend: the sponsor signs first transaction.sign([sponsorKeypair]); // Client: the user signs the partially signed transaction const signed = await userWallet.signTransaction(transaction); ``` Broadcast the signed transaction, then poll the quote's `check` endpoint until the request reaches a terminal status. ```typescript theme={null} const signature = await connection.sendRawTransaction(signed.serialize()); // Poll until "success" or "failure" const status = await fetch( `https://api.relay.link${item.check.endpoint}` ).then((r) => r.json()); ``` A failed fill refunds automatically on the origin chain in the origin currency; see [Refunds](/references/api/api_core_concepts/refunds) for the conditions. ### Fees **`depositFeePayer`** covers the origin transaction only: Solana transaction fees and rent for accounts the deposit creates. It is independent from the destination side and from your [App Balance](/features/fee-sponsorship#funding-your-app-balance). To also sponsor destination gas or rent, use [`subsidizeFees` and `subsidizeRent`](/features/fee-sponsorship#sponsorship-parameters), which draw from your App Balance instead of the sponsor wallet. Each trade is one Solana deposit transaction, so the sponsor pays one fee per trade. If you also want fills to start before the deposit confirms, see [Fast Fill](/features/fast-fill). See [Solana Support](/references/api/api_guides/solana) for chain-specific parameters and transaction-size guidance. # Price Stabilization Source: https://docs.relay.link/features/price-stabilization Deliver consistent stablecoin pricing across chains Price Stabilization gives you two mechanisms for stablecoin swaps: [fee sponsorship](#fee-sponsorship), where you cover transaction fees so the user receives the full market-rate output, or [fixed rates](#fixed-rates), where you lock in a specific exchange rate (e.g. 1:1) and absorb the volatility yourself. Stablecoins don't actually trade at 1:1. When users swap between stablecoins cross-chain, the output fluctuates based on market rates, swap impact, and fees. For a \$100 USDC-to-USDT swap, even small deviations from peg mean the user might receive 99.95 or 100.05 USDT instead of a clean 100. With fixed rates, you sometimes sponsor and sometimes earn the difference, with the expectation that it nets out over time. For a cross-chain swap between major stablecoins, the cost is roughly: \$0.02 + 1bps. For \$100 USDC input, the user would get \$99.97 worth of USDT This is on top of the market rate for the pair: If 1 USDT = 1.0000 USDC, that's 99.97 USDT
If 1 USDT = 0.9995 USDC, that's 100.02 USDT
If 1 USDT = 1.0005 USDC, that's 99.92 USDT ## Price Stabilization Options Relay supports multiple options for stabilizing prices for better UX: ### Fee Sponsorship With sponsorship, you cover the fees. So for \$100 USDC in, the user gets \$100 USDT out, and you cover the \$0.03 in fees. Importantly, this doesn't mean the user gets 1:1 in our USDC:USDT example: If 1 USDT = 1.0000 USDC, that's 100 USDT out
If 1 USDT = 0.9995 USDC, that's 100.05 USDT out
If 1 USDT = 1.0005 USDC, that's 99.95 USDT out ```bash Request theme={null} curl --request POST \ --url https://api.relay.link/quote/v2 \ --header 'Content-Type: application/json' \ --header 'x-api-key: my-api-key' \ --data ' { "user": "0x03508bb71268bba25ecacc8f620e01866650532c", "originChainId": 8453, "destinationChainId": 42161, "originCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "destinationCurrency": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "amount": "100000000", "tradeType": "EXACT_INPUT", "subsidizeFees": true } ' ``` ```json Response expandable theme={null} { "steps": [ { "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": "0x095ea7b30000000000000000000000004cd00e387622c35bddb9b4c962c136462338bc310000000000000000000000000000000000000000000000000000000005f5e100", "value": "0", "chainId": 8453, "gas": "73112", "maxFeePerGas": "6500000", "maxPriorityFeePerGas": "1000000" } } ], "requestId": "0x00180d75faabaabf3dcbbf5341aa6b17d8ce6df9ea71856892f8b2729c32c167" }, { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the depository contract", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bb71268bba25ecacc8f620e01866650532c", "to": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "data": "0xe801795200000000000000000000000003508bb71268bba25ecacc8f620e01866650532c000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000005f5e1004c625ab9733d2d5702beed88f4156575e25d599d5145551d5bf2bfc358db234b", "value": "0", "chainId": 8453, "maxFeePerGas": "6500000", "maxPriorityFeePerGas": "1000000" }, "check": { "endpoint": "/intents/status/v3?requestId=0x00180d75faabaabf3dcbbf5341aa6b17d8ce6df9ea71856892f8b2729c32c167", "method": "GET" } } ], "requestId": "0x00180d75faabaabf3dcbbf5341aa6b17d8ce6df9ea71856892f8b2729c32c167", "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": "166022510785", "amountFormatted": "0.000000166022510785", "amountUsd": "0.000385", "minimumAmount": "166022510785" }, "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": "72445", "amountFormatted": "0.072445", "amountUsd": "0.072433", "minimumAmount": "72445" }, "relayerGas": { "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": "2941", "amountFormatted": "0.002941", "amountUsd": "0.002941", "minimumAmount": "2941" }, "relayerService": { "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": "69504", "amountFormatted": "0.069504", "amountUsd": "0.069492", "minimumAmount": "69504" }, "app": { "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": "0", "amountFormatted": "0.0", "amountUsd": "0", "minimumAmount": "0" }, "subsidized": { "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": "72445", "amountFormatted": "0.072445", "amountUsd": "0.072433", "minimumAmount": "72445" } }, "feeSponsorship": { "quoted": { "selectedComponents": [ "execution", "swap", "relay", "app" ], "capHit": false, "components": { "execution": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "22944", "amountFormatted": "0.022944", "amountUsd": "0.022940", "minimumAmount": "22944" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "22944", "amountFormatted": "0.022944", "amountUsd": "0.022940", "minimumAmount": "22944" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } }, "swap": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "39502", "amountFormatted": "0.039502", "amountUsd": "0.039495", "minimumAmount": "39502" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "39502", "amountFormatted": "0.039502", "amountUsd": "0.039495", "minimumAmount": "39502" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } }, "relay": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "9999", "amountFormatted": "0.009999", "amountUsd": "0.009997", "minimumAmount": "9999" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "9999", "amountFormatted": "0.009999", "amountUsd": "0.009997", "minimumAmount": "9999" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } }, "app": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } } }, "sponsoredTotal": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "72445", "amountFormatted": "0.072445", "amountUsd": "0.072433", "minimumAmount": "72445" }, "userPaysTotal": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } } }, "details": { "operation": "swap", "sender": "0x03508bb71268bba25ecacc8f620e01866650532c", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "currencyOut": { "currency": { "chainId": 42161, "address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "symbol": "USDT", "name": "Tether USD", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/39963/large/usdt.png?1724952731", "verified": true } }, "amount": "99950900", "amountFormatted": "99.9509", "amountUsd": "99.934108", "minimumAmount": "97951882" }, "refundCurrency": { "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "totalImpact": { "usd": "-0.049092", "percent": "-0.05" }, "swapImpact": { "usd": "0.023341", "percent": "0.02" }, "expandedPriceImpact": { "swap": { "usd": "-0.016154" }, "execution": { "usd": "-0.02294" }, "relay": { "usd": "-0.009997" }, "app": { "usd": "0" }, "sponsored": { "usd": "0.072433" } }, "rate": "0.9995090000000001", "slippageTolerance": { "origin": { "usd": "0.000000", "value": "0", "percent": "0.00" }, "destination": { "usd": "1.998682", "value": "1999018", "percent": "2.00" } }, "timeEstimate": 2, "userBalance": "0", "isFixedRate": false, "route": { "origin": { "inputCurrency": { "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "outputCurrency": { "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "router": "relay" }, "destination": { "inputCurrency": { "currency": { "chainId": 42161, "address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "symbol": "USDT", "name": "Tether USD", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/39963/large/usdt.png?1724952731", "verified": true } }, "amount": "99950900", "amountFormatted": "99.9509", "amountUsd": "99.934108", "minimumAmount": "97951882" }, "outputCurrency": { "currency": { "chainId": 42161, "address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "symbol": "USDT", "name": "Tether USD", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/39963/large/usdt.png?1724952731", "verified": true } }, "amount": "99950900", "amountFormatted": "99.9509", "amountUsd": "99.934108", "minimumAmount": "97951882" }, "router": "relay" } } }, "protocol": { "v2": { "orderId": "0x4c625ab9733d2d5702beed88f4156575e25d599d5145551d5bf2bfc358db234b", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0xd286b86b54a2a3d4eaa99814ea4652cd4790e6ebeaf9c802586514a52dd281c9", "inputs": [ { "payment": { "chainId": "base", "currency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "100000000", "weight": "1" }, "refunds": [ { "chainId": "base", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "minimumAmount": "0", "deadline": 1777339792, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, { "chainId": "arbitrum", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "minimumAmount": "0", "deadline": 1777339792, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output": { "chainId": "arbitrum", "payments": [ { "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "minimumAmount": "97951882", "expectedAmount": "99950900" } ], "calls": [], "deadline": 1777339792, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "paymentDetails": { "chainId": "base", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "100000000" } } } } ``` Learn more about how [fee sponsorship](/features/fee-sponsorship) works. ### Fixed Rates If your goal is to give the user 1:1, you can use fixed rates. In this case, the user would always get 100 USDT, and you would sponsor a dynamic amount. If USDT is worth less than USDC, you actually would earn the difference as fees. The figures below assume you are sponsoring fees as well (`"subsidizeFees": true`, as in the request example at the end of this section); the case without fee sponsorship is covered further down: If 1 USDT = 1.0000 USDC, user gets 100 USDT, you sponsor \$0.03
If 1 USDT = 0.9995 USDC, user gets 100 USDT, you earn \$0.02
If 1 USDT = 1.0005 USDC, user gets 100 USDT, you sponsor \$0.08 Effectively, you absorb the volatility, sometimes sponsoring, and sometimes earning fees, with the idea that it nets out over a long time period. Of course, during periods you are sponsoring, you need to be careful that it's not being arbitraged. During periods where you are sponsoring the spread, monitor for arbitrage. A fixed rate that deviates from the market can be drained by arbitrageurs. If you want, you can set rates other than 1:1. E.g. if you set it to 1:1.0005, then you are effectively setting the price to 5bps, and only sponsoring when the market rate + fees is above that. The rate is expressed as "input:output". If 1 USDT = 1.0000 USDC, user gets 99.95 USDT, you earn \$0.02
If 1 USDT = 0.9995 USDC, user gets 99.95 USDT, you earn \$0.07
If 1 USDT = 1.0005 USDC, user gets 99.95 USDT, you sponsor \$0.03 **Fixed rates without fee sponsorship.** `fixedRate` and `subsidizeFees` are independent settings — you can pass a fixed rate with `"subsidizeFees": false`. A fixed rate always pins the **pre-fee** conversion; `subsidizeFees` decides who pays the Relay fee on top of it. So with `"fixedRate": "1:1"` and `"subsidizeFees": false`, the pre-fee output is pinned at 100 USDT and the \~\$0.03 fee comes out of it. The user receives 99.97 USDT at any market rate, and your position is the spread alone: If 1 USDT = 1.0000 USDC, user gets 99.97 USDT, you neither sponsor nor earn
If 1 USDT = 0.9995 USDC, user gets 99.97 USDT, you earn \$0.05
If 1 USDT = 1.0005 USDC, user gets 99.97 USDT, you sponsor \$0.05 Turning off `subsidizeFees` moves the Relay fee onto the user; it does not turn off fixed-rate settlement. Fixed rates work for stablecoins listed as solver currencies on the requested chain, such as USDC, USDC.e, USDT, USDe, USDH, mUSD, and DAI. Solvers supply this liquidity; the platform exposes supported currencies through each chain's **`solverCurrencies`** array in the [chains API](/references/api/get-chains). Check that array for current support rather than assuming the same inventory across chains. ```bash Request theme={null} curl --request POST \ --url https://api.relay.link/quote/v2 \ --header 'Content-Type: application/json' \ --header 'x-api-key: my-api-key' \ --data ' { "user": "0x03508bb71268bba25ecacc8f620e01866650532c", "originChainId": 8453, "destinationChainId": 42161, "originCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "destinationCurrency": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "amount": "100000000", "tradeType": "EXACT_INPUT", "subsidizeFees": true, "fixedRate": "1:1" } ' ``` ```json Response expandable theme={null} { "steps": [ { "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": "0x095ea7b30000000000000000000000004cd00e387622c35bddb9b4c962c136462338bc310000000000000000000000000000000000000000000000000000000005f5e100", "value": "0", "chainId": 8453, "gas": "73112", "maxFeePerGas": "6500000", "maxPriorityFeePerGas": "1000000" } } ], "requestId": "0xc75496bd87224da685dca165d610e3950a962fc6c8b9878b12f8111be7c2e843" }, { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the depository contract", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "from": "0x03508bb71268bba25ecacc8f620e01866650532c", "to": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "data": "0xe801795200000000000000000000000003508bb71268bba25ecacc8f620e01866650532c000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000005f5e100919a226d9a06bc91d18c5fed09c48e16d5ced5d642fa8804758db5fb9a923379", "value": "0", "chainId": 8453, "maxFeePerGas": "6500000", "maxPriorityFeePerGas": "1000000" }, "check": { "endpoint": "/intents/status/v3?requestId=0xc75496bd87224da685dca165d610e3950a962fc6c8b9878b12f8111be7c2e843", "method": "GET" } } ], "requestId": "0xc75496bd87224da685dca165d610e3950a962fc6c8b9878b12f8111be7c2e843", "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": "166230396190", "amountFormatted": "0.00000016623039619", "amountUsd": "0.000386", "minimumAmount": "166230396190" }, "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": "72464", "amountFormatted": "0.072464", "amountUsd": "0.072452", "minimumAmount": "72464" }, "relayerGas": { "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": "2940", "amountFormatted": "0.00294", "amountUsd": "0.002940", "minimumAmount": "2940" }, "relayerService": { "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": "69524", "amountFormatted": "0.069524", "amountUsd": "0.069512", "minimumAmount": "69524" }, "app": { "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": "0", "amountFormatted": "0.0", "amountUsd": "0", "minimumAmount": "0" }, "subsidized": { "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": "72464", "amountFormatted": "0.072464", "amountUsd": "0.072452", "minimumAmount": "72464" } }, "feeSponsorship": { "quoted": { "selectedComponents": [ "execution", "swap", "relay", "app" ], "capHit": false, "components": { "execution": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "22943", "amountFormatted": "0.022943", "amountUsd": "0.022939", "minimumAmount": "22943" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "22943", "amountFormatted": "0.022943", "amountUsd": "0.022939", "minimumAmount": "22943" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } }, "swap": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "39522", "amountFormatted": "0.039522", "amountUsd": "0.039515", "minimumAmount": "39522" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "39522", "amountFormatted": "0.039522", "amountUsd": "0.039515", "minimumAmount": "39522" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } }, "relay": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "9999", "amountFormatted": "0.009999", "amountUsd": "0.009997", "minimumAmount": "9999" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "9999", "amountFormatted": "0.009999", "amountUsd": "0.009997", "minimumAmount": "9999" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } }, "app": { "selected": true, "total": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" }, "sponsored": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" }, "userPays": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } } }, "sponsoredTotal": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "72464", "amountFormatted": "0.072464", "amountUsd": "0.072452", "minimumAmount": "72464" }, "userPaysTotal": { "currency": { "chainId": 8453, "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }, "amount": "0", "amountFormatted": "0.0", "amountUsd": "0.000000", "minimumAmount": "0" } } }, "details": { "operation": "swap", "sender": "0x03508bb71268bba25ecacc8f620e01866650532c", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "currencyOut": { "currency": { "chainId": 42161, "address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "symbol": "USDT", "name": "Tether USD", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/39963/large/usdt.png?1724952731", "verified": true } }, "amount": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "98000000" }, "refundCurrency": { "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "totalImpact": { "usd": "0.000000", "percent": "0.00" }, "swapImpact": { "usd": "0.072452", "percent": "0.07" }, "expandedPriceImpact": { "swap": { "usd": "0.032937" }, "execution": { "usd": "-0.022939" }, "relay": { "usd": "-0.009997" }, "app": { "usd": "0" }, "sponsored": { "usd": "0.072452" } }, "rate": "1", "slippageTolerance": { "origin": { "usd": "0.000000", "value": "0", "percent": "0.00" }, "destination": { "usd": "1.999664", "value": "2000000", "percent": "2.00" } }, "timeEstimate": 2, "userBalance": "0", "isFixedRate": true, "fixedRateFee": { "usd": "-0.049092" }, "route": { "origin": { "inputCurrency": { "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "outputCurrency": { "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": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "100000000" }, "router": "relay" }, "destination": { "inputCurrency": { "currency": { "chainId": 42161, "address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "symbol": "USDT", "name": "Tether USD", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/39963/large/usdt.png?1724952731", "verified": true } }, "amount": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "98000000" }, "outputCurrency": { "currency": { "chainId": 42161, "address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "symbol": "USDT", "name": "Tether USD", "decimals": 6, "metadata": { "logoURI": "https://coin-images.coingecko.com/coins/images/39963/large/usdt.png?1724952731", "verified": true } }, "amount": "100000000", "amountFormatted": "100.0", "amountUsd": "99.983200", "minimumAmount": "98000000" }, "router": "relay" } } }, "protocol": { "v2": { "orderId": "0x919a226d9a06bc91d18c5fed09c48e16d5ced5d642fa8804758db5fb9a923379", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0x9f2e8093d5e102dadc84fab08103cacd5ba84b3dbd45afd574ed86710bc9461e", "inputs": [ { "payment": { "chainId": "base", "currency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "100000000", "weight": "1" }, "refunds": [ { "chainId": "base", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "minimumAmount": "0", "deadline": 1777339828, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, { "chainId": "arbitrum", "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "minimumAmount": "0", "deadline": 1777339828, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output": { "chainId": "arbitrum", "payments": [ { "recipient": "0x03508bb71268bba25ecacc8f620e01866650532c", "currency": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", "minimumAmount": "98000000", "expectedAmount": "100000000" } ], "calls": [], "deadline": 1777339828, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "paymentDetails": { "chainId": "base", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": "100000000" } } } } ``` **Tracking what you sponsored or earned.** Fixed-rate quotes return a `fixedRateFee` object on `details`, alongside `isFixedRate`: ```json theme={null} "isFixedRate": true, "fixedRateFee": { "usd": "-0.049092" } ``` A positive value means the fixed rate was worse for the user than the market rate, so you earn the difference. A negative value means it was better for the user, so you sponsor the difference. `fixedRateFee` is a **quote-time estimate**. The amount you are actually credited or debited is recalculated after the fill, from the executed amounts and the market rate at the time of the request, and settles against your sponsoring wallet's balance. The settled amount can differ from the quoted estimate in both size and direction, so do not treat the quote as a commitment. ## Why This Works for Stablecoins Stablecoin pairs are uniquely suited to price stabilization because the swap impact component of Relay's fees is typically sub 1bps between major stablecoins. That leaves the flat execution fee (\~\$0.02) and the Relay bps fee as the dominant costs — both small, predictable amounts that are cheap to sponsor or absorb into a fixed rate. For a full breakdown of Relay's fee components and pricing tiers, see [Cost & Fee Structure](/references/api/api_core_concepts/fees). # How Relay Works Source: https://docs.relay.link/how-relay-works Follow a transaction from quote to completion Tell Relay what your user wants to do. The [API](/references/api/overview) returns a quote and the steps to complete it, then lets you track the transaction through completion. ## Platform Execution 1. **Quote:** Your application requests an outcome. The API returns a route, expected output, fees, and the steps required to execute it. 2. **Authorize:** The user signs the transactions or messages returned in the quote. Deposit methods and transaction targets depend on the route; follow the returned steps rather than substituting a fixed address. 3. **Fill:** For a cross-chain order, a solver uses its own capital to complete the requested action on the destination chain. Same-chain swaps execute on the source chain without a cross-chain fill. 4. **Track:** Your application monitors the request through completion or refund using the [status API](/references/api/get-intents-status-v3). ## Protocol Settlement For orders using the settlement protocol, users deposit into a [Relay Depository Contract](/references/protocol/components/depository). Solvers fill on the destination chain, then submit attestations that credit their balances on the Hub. Withdrawal proofs authorize payment from the Depository. The protocol separates deposits, fills, verification, and withdrawals across distinct components. Its [security model](/references/protocol/security) explains the Oracle, Allocator, and Security Council trust assumptions. Protocol support does not mean every platform route uses that settlement flow. API endpoint versions, including `/requests/v3`, version the API—not the settlement protocol used by an order. ## The Relay Stack Request quotes, execute transactions, track status, and retrieve supported network data. Use the SDK, React hooks, and UI components to add Relay to your application. Explore settlement contracts, fill verification, and the protocol's security model. Explore supported routes and transact through the same infrastructure available to integrators. # Advanced Source: https://docs.relay.link/references/api/advanced # API keys and Rate Limits Source: https://docs.relay.link/references/api/api-keys Create API keys in the Relay Dashboard to authenticate requests and manage your rate limits Relay enforces rate limits to keep the platform reliable for every integrator. You manage API keys in the [Relay Dashboard](https://dashboard.relay.link) — create new API keys, rotate existing ones, and inspect individual requests. Default limits listed below apply to self-serve API keys. ## Creating an API key API keys are self-serve. To create one: 1. Sign in to the [Relay Dashboard](https://dashboard.relay.link). 2. Open the **API keys** tab. 3. Click **Create key**, give it a label (e.g. `production`, `staging`), and copy the key. Use a separate key per environment (production, staging, local). Per-key request history makes it easy to spot a misbehaving deploy without revoking the wrong credential. ## Tracking requests The [Relay Dashboard](https://dashboard.relay.link) is also where you observe what your integration is doing in production: * **Requests** — full history of quotes and intents made with each key, with status, route, amounts, and timing. * **Advanced filtering** — narrow the table by chain, currency, amount, status, time, and more, combining conditions with AND/OR logic to isolate exactly the requests you care about. * **Custom views** — save any combination of filters, sort order, and columns as a reusable view for your team, each with its own name, icon, and color. * **Keys** — create, rename, and revoke keys for your account. * **Webhooks** — configure a per-key [webhook endpoint](/references/api/api_guides/webhooks) so transaction status updates are pushed to your backend instead of polling. When debugging a specific transaction, paste the `requestId` from your application into the dashboard's search to jump straight to the matching record. ## Default Rate Limits The following limits apply per API key: | Endpoint | Limit | | - | - | | `/quote` | 50 requests per minute | | `/requests` | 100 requests per minute | | `/requests/v3` | 200 requests per minute | | `/transactions/status` | 200 requests per minute | | Other endpoints | 200 requests per minute | ## Elevated Rate Limits Higher limits are available on request, applied per key: | Endpoint | Limit | | - | - | | `/quote` | 10 requests per second | | `/requests/v3` | 20 requests per second | | `/requests` | 5 requests per second | | `/transactions/status` | 10 requests per second | | `/authorize` | 5 requests per minute per `(API key, wallet)`, with a 30 requests per minute aggregate ceiling per API key | | Other endpoints | 200 requests per minute | `/requests` and `/requests/v2` share the 5 rps bucket; `/requests/v3` has its own 20 rps bucket. `/authorize` is bucketed by the `wallet` field in the request body alongside your API key, so concurrent authorizations from different end-users don't compete for the same bucket. The aggregate per-key ceiling still applies across all wallets and bounds total volume of the on-chain authorization path. To request higher limits, get in touch through the support widget in the [Relay Dashboard](https://dashboard.relay.link). Already hitting a limit? See [Handling Rate Limits](/references/api/api_core_concepts/handling-rate-limits) for how to recover from `429` responses and reduce your request volume before requesting an upgrade. ## How to Use an API key Quote requests that set a `referrer` must be authenticated: pass your API key in the `x-api-key` header on `POST /quote` and `POST /quote/v2`. Requests with a referrer but no valid key return `401` with `errorCode: "UNAUTHORIZED_QUOTE"`. Starting **October 2, 2026**, every `quote/v2` request will require a valid API key. `quote/v2` remains fully supported for authenticated requests. ### HTTP requests Pass the key in the request headers on every request: ``` x-api-key: YOUR_API_KEY ``` ### SDK usage ```typescript Global API key theme={null} // Set apiKey only 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 }); ``` ```typescript SDK action API key theme={null} // Or pass the key per-call via the headers parameter. import { getClient } from "@relayprotocol/relay-sdk"; const quoteParams = { /* ... */ }; const quote = await getClient()?.actions.getQuote(quoteParams, undefined, { "x-api-key": "YOUR_API_KEY", }); ``` ### Proxy API If you're calling the SDK from the browser, stand up a proxy that appends the key server-side and point `baseApiUrl` at it. This keeps the key out of client bundles while preserving your key's rate limits. ```typescript 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 Treat your API key like a password. It's tied to your account, controls your rate limits, and every request made with it is attributed to you. If your key is leaked, anyone holding it can consume your rate limits or make requests on your behalf. Revoke and re-create the key in the [Relay Dashboard](https://dashboard.relay.link) immediately if you suspect it has been compromised. **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 the 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. * **Rotate regularly** — create a new key in the [Relay Dashboard](https://dashboard.relay.link) and revoke the old one whenever a team member with access leaves. # Contract Compatibility Source: https://docs.relay.link/references/api/api_core_concepts/contract-compatibility How to make your contract compatible with Relay The Relay platform lets users pay on Chain A for transactions on Chain B. Solvers execute cross-chain fills, so your contract must distinguish the transaction submitter from the intended user. ## General overview For the call-execution routes described here, solvers use Vectorized's [Multicaller](https://github.com/Vectorized/multicaller) contract. The multicall isolates execution from the solver's wallet. One exception is a simple bridge transaction with empty `data`. A solver can execute that transfer directly to avoid Multicaller gas overhead. ## Sender Attribution Because transactions execute through a solver or Multicaller, you can't assume `msg.sender` identifies the user. 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. # Handling Execution Errors Source: https://docs.relay.link/references/api/api_core_concepts/execution-errors Interpret fill and refund failure reasons returned after a request has been submitted Once a request is submitted, execution can still fail during the fill or, if a fill isn't possible, during the refund. These failures are surfaced as `failReason` (fill) and `refundFailReason` (refund) on the request object, distinct from the [quote errors](/references/api/api_core_concepts/handling-errors) returned before a request is ever submitted. This guide outlines the possible fill and refund failure reasons and what each one means. ## Fill Errors `failReason` explains why a fill did not complete after a request was submitted. ### Deposit & Validation Errors | Error Code | Description | | - | - | | `DEPOSIT_CHAIN_MISMATCH` | For strict deposit addresses, the deposit landed on a different chain than the one the address was registered for. | | `INCORRECT_DEPOSIT_CURRENCY` | For strict deposit addresses, the token received doesn't match the currency quoted for this request. | | `DOUBLE_SPEND` | A second deposit was detected at an address whose first deposit was already processed. | | `DEPOSITED_AMOUNT_TOO_LOW_TO_FILL` | The amount deposited is less than the quoted amount needed to complete the swap. | | `ORIGIN_CURRENCY_MISMATCH` | The currency detected on origin doesn't match the origin currency specified in the request. | | `TTL_EXPIRED` | The request's time-to-live window passed before it could be filled. | | `DEPOSIT_CONFIRMATION_TIMEOUT` | The deposit transaction wasn't confirmed within the expected window, so the request timed out and was routed to a refund. | | `DEPOSIT_REORGED` | The deposit transaction was removed from the canonical chain by a block reorganization, so the request was failed. | | `ORPHANED_DEPOSIT_REFUND` | A deposit at a deposit address couldn't be matched to a fillable request, so the funds were refunded to the depositor. | | `BLOCKED_WALLET` | An address associated with the request was flagged by wallet screening at fill time, so the fill was blocked. Funds are not automatically refunded; contact [Support](/resources/support) for review. | ### Solver Capacity & Balance Errors | Error Code | Description | | - | - | | `SOLVER_CAPACITY_EXCEEDED` | The solver's available capacity for this route has been exceeded. | | `SOLVER_BALANCE_TOO_LOW` | The solver doesn't have enough native tokens or balance to construct the fill transaction. | | `SPONSOR_BALANCE_TOO_LOW` | The sponsor covering transaction fees doesn't have sufficient balance. | | `INSUFFICIENT_FUNDS_FOR_RENT` | The user's balance isn't enough to cover rent/storage fees (Solana). | | `NEGATIVE_NEW_AMOUNT_AFTER_FEES` | After deducting fees, the resulting amount is zero or negative. | | `AMOUNT_TOO_LOW_TO_REFUND` | The amount to refund is zero or negative, or below the chain's minimum transfer threshold, so no refund could be issued. | | `GASLESS_PERMIT_BALANCE_TOO_LOW` | The balance available from a gasless permit wasn't enough to complete the request, so the funds were refunded. | ### Swap Routing & Pricing Errors | Error Code | Description | | - | - | | `NO_QUOTES` | No liquidity providers returned a valid quote for this swap at fill time. | | `NO_INTERNAL_SWAP_ROUTES_FOUND` | No internal DEX route was found between the specified currencies on this chain. | | `SWAP_IMPACT_TOO_HIGH` | The swap's price impact at fill time exceeds the acceptable threshold. | | `INSUFFICIENT_POOL_LIQUIDITY` | The DEX pool doesn't have enough reserves to complete the swap at any price. | | `SLIPPAGE` | The swap couldn't complete within the given slippage tolerance. | | `TOO_LITTLE_RECEIVED` | The onchain swap executed, but the output amount was below the guaranteed minimum. | | `GENERATE_SWAP_FAILED` | Generating the swap transaction (routing, calldata) failed before execution. | | `REVERSE_SWAP_FAILED` | The reversal swap used to return funds to the user for a refund failed to execute or quote. | ### Transaction Construction & Gas Errors | Error Code | Description | | - | - | | `TRANSACTION_TOO_LARGE` | The serialized transaction exceeds the maximum payload size for the origin chain or RPC. | | `SWAP_USES_TOO_MUCH_GAS` | The swap transaction would consume more gas than a single block allows. | | `NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE` | A refreshed permit increased the rent/storage fee beyond the acceptable limit (Solana). | | `INVALID_GAS_PRICE` | The submitted gas price is outside acceptable bounds. | | `QUOTED_GAS_LIMIT_EXCEEDED` | The gas limit required at fill time was significantly higher than the gas limit quoted, so the fill was aborted. | | `JUPITER_INVALID_TOKEN_ACCOUNT` | The token account required for the swap doesn't exist or is invalid (Solana/Jupiter). | | `TRANSACTION_SUBMISSION_FAILED` | The fill transaction could not be broadcast to the network — for example, the RPC was unavailable, the signer was unavailable, or the transaction was rejected before broadcast (nonce or fee issue). No transaction hash exists for this failure. | | `TRANSACTION_NOT_INCLUDED` | The fill transaction was broadcast successfully and a transaction hash was recorded, but the transaction was not included onchain before Relay's retry and gas-bump window elapsed. The transaction may still confirm afterwards, so the recorded hash is worth checking. | ### Onchain Execution & Revert Errors | Error Code | Description | | - | - | | `EXECUTION_REVERTED` | The swap contract call reverted during execution. | | `TRANSACTION_REVERTED` | The transaction reverted at the blockchain level. | | `MISSING_REVERT_DATA` | The transaction reverted, but the reason couldn't be decoded from the returned data. | | `CONTRACT_PAUSED` | The target contract is currently paused and not accepting calls. | | `TOKEN_NOT_TRANSFERABLE` | The token enforces a non-transferable restriction (for example, a soulbound token). | | `TRANSFER_FAILED` | A token transfer reverted at the contract level. | | `TRANSFER_FROM_FAILED` | Transferring tokens from the user to the contract failed (for example, insufficient allowance). | | `TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE` | The approved allowance is less than the amount being transferred. | | `TRANSFER_AMOUNT_EXCEEDS_BALANCE` | The sender's token balance is less than the amount being transferred. | | `INSUFFICIENT_NATIVE_TOKENS_SUPPLIED` | The native token value supplied with the transaction is less than required. | | `INCORRECT_PAYMENT` | The value or amount sent doesn't match what the swap requires. | | `ZERO_SELL_AMOUNT` | The swap was attempted with a zero input amount. | | `INVALID_SENDER` | The address executing the swap isn't authorized to do so. | | `INVALID_SIGNER` | The signer on the order doesn't match the address expected by the contract. | | `DESTINATION_TOKEN_TRANSFER_REJECTED` | A token transfer reverted during the destination-side swap — typically the token itself rejecting the transfer (transfer tax, max wallet/transaction limits, or a blacklist). | ### Order & Signature Errors | Error Code | Description | | - | - | | `ORDER_EXPIRED` | The order's expiration timestamp passed before it could be executed. | | `ORDER_IS_CANCELLED` | The order was cancelled onchain before it could be filled. | | `ORDER_ALREADY_FILLED` | The order was already fully executed and can't be filled again. | | `SIGNATURE_EXPIRED` | The signature used for token approval expired before execution. | | `INVALID_SIGNATURE` | The provided signature is invalid or doesn't match the expected signer. | | `INVALID_NONCE` | The nonce provided has already been used or isn't valid for this signer. | | `SEAPORT_INEXACT_FRACTION` | The Seaport order specifies a fractional amount that can't be fulfilled exactly. | | `SEAPORT_INVALID_FULFILLER` | The caller isn't an authorized fulfiller for this Seaport order. | | `PROTOCOL_DEADLINE_EXPIRED` | The protocol order's deadline expired before the fill could complete, so a no-fill-or-refund was submitted onchain. | ### Account Abstraction Errors | Error Code | Description | | - | - | | `ACCOUNT_ABSTRACTION_INVALID_NONCE` | The nonce in the account abstraction user operation is invalid or stale. | | `ACCOUNT_ABSTRACTION_SIGNATURE_ERROR` | The account abstraction user operation's signature is invalid. | | `ACCOUNT_ABSTRACTION_GAS_LIMIT` | The gas required for account abstraction verification exceeds the allowed limit. | ### NFT Minting Errors | Error Code | Description | | - | - | | `MINT_NOT_ACTIVE` | The NFT mint phase isn't currently active. | | `ERC_1155_TOO_MANY_REQUESTED` | The quantity requested exceeds the contract's allowed batch size. | | `MINT_QUANTITY_EXCEEDS_MAX_PER_WALLET` | The wallet has already minted the maximum quantity allowed. | | `MINT_QUANTITY_EXCEEDS_MAX_SUPPLY` | The requested quantity would exceed the collection's maximum supply. | ### Other | Error Code | Description | | - | - | | `FLUID_DEX_ERROR` | The Fluid DEX protocol returned an error during the swap. | | `MANUAL_ADMIN_REFUND` | The request was manually refunded by the Relay team via an admin action. | | `UNKNOWN` | The fill failed for a reason that didn't match a known error pattern. | | `N/A` | The fill completed without failure. | ## Refund Errors `refundFailReason` explains why a refund could not be completed after a fill failed. This field defaults to `N/A` when no refund failure has occurred. | Error Code | Description | | - | - | | `AMOUNT_TOO_LOW_TO_REFUND` | The amount to refund is zero or negative, or below the chain's minimum transfer threshold, so no refund could be issued. | | `NEGATIVE_NEW_AMOUNT_AFTER_FEES` | After deducting fees, the resulting amount is zero or negative. | | `SWAP_CURRENCY_NOT_ON_ORIGIN` | The swap's output currency isn't available to bridge back to the origin chain, so the refund can't be issued in the original currency. | | `REFUND_RECIPIENT_IS_VASP` | The refund recipient was identified as a Virtual Asset Service Provider (VASP), which requires manual review before the refund can be issued. | | `N/A` | No refund failure has occurred. | # Cost & Fee Structure Source: https://docs.relay.link/references/api/api_core_concepts/fees Learn about Relay's cost and fee structure In any given token swap or bridge powered by Relay, there are four potential fee and cost components: 1. **Execution Cost (formerly Execution Fees)** Covers execution costs including network gas on the origin and/or destination chain, and includes: * \$0.02 flat fee — always included * Destination (fill) gas estimate — always included, covers transaction fulfillment on the destination chain * Origin gas estimate — only included for gasless transactions, covers transaction submission on the origin chain on the user's behalf For regular (non-gasless) transactions, the user pays origin gas directly from their own wallet to the network — it's not part of the Execution Cost. 2. **Swap Cost (formerly Swap Fees)** Covers payments to liquidity providers that facilitate cross-asset and cross-chain token swaps, and includes: * DEX fees * DEX swap impact * Solver cross-chain rebalancing fees Note: Hyperliquid charges a \$1 activation fee for new deposits. Relay cannot change this fee — it is passed through to users. 3. **Platform Fees (formerly Relay Fees)** A flat basis point fee charged for using the Relay API gateway and related services, including via the Relay.link frontend.
Standard Fees Token Bridge & Same-Chain Wrap/Unwrap Stablecoin Swap Major Swap Minor Swap
Platform Fees charged to end users 0.00% 0.01% 0.06% 0.15%
Relay integrators can receive a revenue share on the Platform Fees. The revenue share varies by asset pair type and volume tier, as detailed below. To qualify, integrators must: 1. Pass an API key in the Relay quote 2. Complete Know Your Business (KYB) verification 3. Exceed \$10,000,000 in aggregate U.S. dollar volume over a trailing 30-day period 4. Provide an EVM wallet address to receive revenue share The revenue share cannot be used to discount the Relay product to end users.
Revenue Share Volume Tier\* (U.S. Dollars) Token Bridge & Same-Chain Wrap/Unwrap Stablecoin Swap Major Swap Minor Swap
0% 34% 25% 33.33%
0% 67% 50% 66.67%
*\* The Revenue Share Volume Tier is calculated based on the aggregate U.S. dollar value of the transactions for the trailing 30 days.* **Relay Transaction Types:** * **Token Bridge:** a token bridge transaction. Examples: * USDC on Arbitrum to USDC on Base * **Same-Chain Wrap/Unwrap:** a 1:1 conversion on the same chain between a native asset and its tokenized equivalent via a single smart contract. Wrapping locks the native asset and mints the wrapped token; unwrapping burns the wrapped token and releases the native asset. Examples: * ETH to WETH on Ethereum * SOL to WSOL on Solana * POL to WPOL on Polygon * BNB to WBNB on BNB Chain * **Stablecoin swap:** same-chain or cross-chain swaps between any pair of these stablecoins: USDC, USDC.e, USDT, USDT0, DAI, USDe, USDS, USD1, PYUSD, USDG, mUSD, USDm, USDH, pUSD, and PlumeUSD (each, a "Major Stablecoin"). Examples: * USDC to USDT on Ethereum * USDC on Optimism to USDT on Solana * **Major swap:** same-chain or cross-chain swaps between any pair of these tokens: ETH, WETH, BTC, WBTC, SOL, WSOL, POL, BNB, and PLUME (each, a "Major Token"), and between any Major Token and any Major Stablecoin. Examples: * ETH to POL on Polygon * ETH on Ethereum to SOL on Solana * **Minor swap:** same-chain or cross-chain token swaps between any pair of tokens that are not a Major Stablecoin or Major Token. Examples: * LINK to UNI on Ethereum * AVAX on Avalanche to Aster on BNB Chain 4. **App Fees** Fees added on top of a Relay quote by 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 in the API [`GET /requests/v3`](/references/api/get-requests) returns a request's fees in the `data.fees` object, with `quoted` (estimated at quote time) and `actual` (computed at fill time) values. It breaks fees down into the following components: **execution** - Execution costs covering network gas on the origin and/or destination chain plus the flat fee. **swap** - Swap costs from cross-currency and cross-chain exchange, including DEX fees, swap slippage, and solver rebalancing. **platform** - The Platform Fees, a flat basis-point fee charged by Relay for routing and meta-aggregation services. **app** - App fees added on top by the integrator, when present. **sponsored** - The portion of fees the integrator subsidizes on behalf of the user via [Fee Sponsorship](/features/fee-sponsorship). When displaying fees to users, we recommend mapping the `data.fees` fields as follows: * **execution** → Execution Cost * **swap** → Swap Cost * **platform** → Platform Fees * **app** → App Fee In the quote API and [`GET /requests/v2`](/references/api/get-requests-v2), this same breakdown is returned as the `expandedPriceImpact` object, and the **platform** component is named **relay**. ## Fee Sponsorship Integrators with [Fee Sponsorship](/features/fee-sponsorship) enabled can subsidize fees for their users. By default, setting **`subsidizeFees`** to `true` sponsors all fee components. For more granular control, use the **`sponsoredFeeComponents`** parameter in the quote request to choose which specific fee components to sponsor. This allows you to sponsor some components (e.g. `execution` and `platform`) while letting the user pay others (e.g. `swap`). Deposit addresses do not support `sponsoredFeeComponents`. See the [Fee Sponsorship Doc](/features/fee-sponsorship) for setup details. ## Deprecated: Fees Object The `fees` object is deprecated. It is still returned from the quote API and the [`GET /requests/v2`](/references/api/get-requests-v2) API for backwards compatibility, but it only reports Relay-related fees and does not reflect the full cost of a request. Do not rely on it for new integrations. To understand or display the complete fee breakdown of a request, use the [`data.fees`](#fees-in-the-api) object in `GET /requests/v3` (or `expandedPriceImpact` in the quote API and `GET /requests/v2`) instead, which covers execution, swap, platform, app, and sponsored fees. The deprecated `fees` object is segmented into the following fields: **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. # Handling Quote Errors Source: https://docs.relay.link/references/api/api_core_concepts/handling-errors Interpret and recover from quote errors in your integration 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. Looking for errors that occur after a request is submitted? See [Handling Execution Errors](/references/api/api_core_concepts/execution-errors) for fill and refund failure reasons. ## Expected Errors These are known validation and routing issues that developers should gracefully handle in their integrations: | Error Code | Description | | - | - | | `AMOUNT_TOO_HIGH` | The requested quote amount exceeds the maximum configured for the selected token pair. Reduce the amount (or split into smaller quotes) and retry. | | `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 or recipient address is not valid for the target chain. | | `INVALID_EXTRA_TXS` | The total value of extra transactions exceeds the intended output. | | `INVALID_GAS_LIMIT_FOR_DEPOSIT_SPECIFIED_TXS` | Deposit-specified transactions require a supported trade type and a configured gas limit. See the [call execution guide](/references/api/api_guides/calling-integration-guide). | | `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_RECIPIENT` | The provided `recipient` resolves to a supported token contract and cannot receive funds. Use a wallet address. | | `INVALID_SLIPPAGE_TOLERANCE` | `slippageTolerance` (or `latePaymentSlippageTolerance`) is not an integer string in the range `0`–`10000` 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. | | `PRICE_FETCH_FAILED` | The upstream price feed is temporarily unavailable. Retry with backoff. | | `REQUEST_TIMED_OUT` | An upstream RPC provider timed out before returning a result. | | `ROUTE_TEMPORARILY_RESTRICTED` | This route is temporarily restricted due to high traffic or throttling. | | `RPC_HTTP_ERROR` | An upstream RPC provider returned a transient infrastructure error. | | `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. | | `SERVICE_UNAVAILABLE` | Relay is experiencing a transient infrastructure issue. Retry with backoff. | | `SOLANA_TX_TOO_LARGE` | The compiled Solana deposit transaction exceeds Solana's 1232-byte wire limit and cannot be signed or broadcast. The response `message` reports the measured size and how many bytes it lands over. For ways to reduce this error, see [Solana Support](/references/api/api_guides/solana#transaction-size-optimization). | | `SWAP_IMPACT_TOO_HIGH` | The swap's price impact exceeds acceptable thresholds. See [Overriding Price Impact Protection](#overriding-price-impact-protection). | | `UNAUTHORIZED` | The user is not authenticated or lacks valid authorization. | | `UNAUTHORIZED_QUOTE` | The quote request includes a `referrer` but no valid `x-api-key` header. | | `UNSUPPORTED_CHAIN` | The specified chain is not supported by the platform. | | `UNSUPPORTED_CURRENCY` | The specified currency is not supported for input or output, or the token pair cannot be priced. | | `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. | `PRICE_FETCH_FAILED`, `REQUEST_TIMED_OUT`, `RPC_HTTP_ERROR`, and `SERVICE_UNAVAILABLE` all indicate transient upstream infrastructure issues, not problems with your request. `PRICE_FETCH_FAILED` and `SERVICE_UNAVAILABLE` are returned with HTTP `503`; `REQUEST_TIMED_OUT` and `RPC_HTTP_ERROR` are returned when upstream RPCs are classified as transient. Retry with backoff before surfacing a failure to the user. *** ## Overriding Price Impact Protection Relay rejects a quote with `SWAP_IMPACT_TOO_HIGH` when the swap's price impact exceeds its protection threshold. The guard protects users from receiving far less than the reference value of their input, but it also blocks swaps where high impact is genuine and expected — most commonly newly launched or thin-liquidity tokens. To accept the impact and receive the quote anyway, pass **`overridePriceImpact`** in the quote request: ```json theme={null} { "user": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "originChainId": 8453, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationChainId": 8453, "destinationCurrency": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "tradeType": "EXACT_INPUT", "amount": "10000000000000000", "overridePriceImpact": true } ``` With the override enabled, the quoted output can be far below the reference value of the input. Surface **`details.totalImpact`** from the quote response in your UI and require explicit user confirmation before execution. The override only bypasses the impact check — it does not help when no route is returned at all (`NO_SWAP_ROUTES_FOUND`). *** ## Unexpected Errors These indicate infrastructure or downstream failures and are not common: | Error Code | Description | | - | - | | `DESTINATION_TX_FAILED` | The relay transaction reverted 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" } ``` ### Solana Transaction Too Large ```json theme={null} { "message": "Generated Solana transaction is 1416 bytes, 184 over Solana's 1232 byte limit, so it cannot be signed or broadcast.", "errorCode": "SOLANA_TX_TOO_LARGE" } ``` # Handling Rate Limits Source: https://docs.relay.link/references/api/api_core_concepts/handling-rate-limits Reduce your request volume and request an elevated limit when you reach Relay's per-key rate limits Relay enforces rate limits per API key to keep the platform reliable for every integrator. When a request exceeds a limit, Relay rejects it with a `429` response. Use this guide to optimize your request workflow or replace status polling with webhooks or websockets. If you have already tried both, contact support to request an elevated rate limit. Per-endpoint limits are listed in [API keys and Rate Limits](/references/api/api-keys#default-rate-limits). To see which keys and endpoints are reaching their limits, open [Observability](https://dashboard.relay.link/observability) in the Relay Dashboard. ## Optimize your request workflow Most integrations that reach a limit are sending requests they don't need. This is the fastest change to ship, and it usually resolves the problem without any change to your architecture. Configuration data is the most common source of unnecessary traffic. Chains, currencies, and similar responses change infrequently, so fetch them once at startup and cache them rather than calling on every user interaction. Quote traffic is the next place to look. `/quote` carries the tightest default limit at 50 requests per minute. Debounce amount and token inputs so a user typing an amount produces one call rather than one per keystroke, and collapse identical in-flight requests into a single call whose result is shared across callers. Validation belongs on the client too. Amounts below Relay's minimum come back as [`AMOUNT_TOO_LOW`](/references/api/api_core_concepts/handling-errors), so a sub-cent value a user types should fail your own check before it turns into a quote call. Where your interface already knows the user's balance, apply the same treatment to amounts they can't cover. If you poll for status, widen the interval and stop polling once a request reaches a terminal status (`success`, `failure`, or `refund`). When you need many records at once, [`GET /requests/v3`](/references/api/get-requests) retrieves them in a single filtered call instead of one call per request ID. If you receive a `429` response, retry the request after an exponential backoff. This will prevent you from hammering the API with requests that will likely fail again. *** ## Replace polling with webhooks or websockets Polling for status is a major source of avoidable request volume. Relay can push status updates to you instead, which removes those calls from your budget entirely and delivers updates faster than any polling interval. [Webhooks](/references/api/api_guides/webhooks) POST `request.status.updated` events to an HTTPS endpoint on your backend as each request changes status. Configure one endpoint per API key in the [Relay Dashboard](https://dashboard.relay.link). Use webhooks when you have a server that can receive traffic. [Websockets](/references/api/api_guides/websockets) deliver the same events over an open connection to `wss://ws.relay.link`, authenticated with your API key. Use websockets when the consumer is a client or a process that can hold a connection open. Either option eliminates polling for status. Keep a low-frequency reconciliation poll if you want a safety net against missed events, but drive your primary flow from the pushed updates. *** ## Request an elevated rate limit If your workflow is already optimized and you've moved off polling, reach out to our support team to request an elevated rate limit. Elevated limits are applied per key. The [Elevated Rate Limits](/references/api/api-keys#elevated-rate-limits) table lists what's available. Get in touch through the support widget in the [Relay Dashboard](https://dashboard.relay.link). Tell us the label of the API key as it appears in the Dashboard rather than the key itself, the endpoints involved, your expected sustained and peak volume in requests per second, and what you've already optimized. # Input Validation Source: https://docs.relay.link/references/api/api_core_concepts/input-validation Verify a quote locally before you send the transaction By default, you pay for a Relay quote with a transfer that the chain does not check against the quote terms, which keeps gas cost to a minimum. You can verify a supported quote locally before you send the transaction. This verification uses the quote data, the Relay settlement SDK, and the chain metadata from `GET /chains`. It does not require another API request at quote time. With the signature API, you asked Relay for a signature over a small set of intent fields and checked that signature onchain. A signature only proves that Relay said something; it does not let you confirm that what Relay said matches the transaction you are about to send. The verification on this page inverts that: 1. Read the output amount, recipient, currency, and deadline from the order. 2. Recompute the order ID from these properties. 3. Confirm that the transaction commits to the recomputed order ID, and pays what the order says. If all three checks pass and the order settles, settlement follows the order you inspected. This method applies only to quotes that contain `protocol.v2`. It does not apply to deposit-address deposits. If your integration uses deposit addresses, contact Relay before you migrate from the signature API. ## What you can verify * The recipient is who you expect, and is on your whitelist * The minimum output amount is acceptable * The input and output currencies and chains are correct * The amount you are being asked to pay matches the quoted input * The order has not expired * The deposit transaction pays Relay's canonical depository, commits to this exact order, and transfers exactly the order's input payment ## Guide ### 1. Request a quote with protocol data Pass `includeProtocolData: true` so the response includes the full protocol order alongside the quote. ```bash theme={null} curl -X POST https://api.relay.link/quote/v2 \ -H 'Content-Type: application/json' \ -H 'x-api-key: YOUR_API_KEY' \ -d '{ "user": "0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "originChainId": 1, "destinationChainId": 8453, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0x0000000000000000000000000000000000000000", "recipient": "0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "tradeType": "EXACT_INPUT", "amount": "100000000000000000", "includeProtocolData": true }' | jq ``` The response carries a `protocol.v2` block: ```json expandable theme={null} { "protocol": { "v2": { "orderId": "0xd4f71fa132a005aa7fc1596fffaea5a168e06a6fc4960d0c3a6f61cc702a6904", "hubType": "onchain", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0xfd839ebcf726a1569c120b0038f705432a151a7f586aefce852bb7b5428d057b", "inputs": [ { "payment": { "chainId": "ethereum", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000", "weight": "1" }, "refunds": ["..."] } ], "output": { "chainId": "base", "payments": [ { "recipient": "0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "97991321030687428", "expectedAmount": "99991143908864722" } ], "calls": [], "deadline": 1788888266, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "orderSignature": "0x...", "paymentDetails": { "chainId": "ethereum", "depository": "0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency": "0x0000000000000000000000000000000000000000", "amount": "100000000000000000" } } } } ``` `orderData` is the complete, canonical description of the order. Everything else in this guide is derived from it. ### 2. Check the order properties Read the values you care about from `orderData` and compare them against your own expectations. This is the step where you decide whether the order is acceptable — the checks that follow only bind the transaction to the properties you inspect here. | What to check | Where | | - | - | | Recipient | `output.payments[].recipient` | | Guaranteed output | `output.payments[].minimumAmount` | | Output currency and chain | `output.payments[].currency`, `output.chainId` | | What you pay | `inputs[].payment.amount`, `inputs[].payment.currency` | | Expiry | `output.deadline` (unix seconds) | Compare `minimumAmount`, not `expectedAmount`. `expectedAmount` is the quoted estimate; `minimumAmount` is the floor the protocol enforces at settlement. ### 3. Recompute the order ID The examples below assume `quote` is the parsed `/quote/v2` response from step 1. Install the settlement SDK: ```bash theme={null} npm install @relay-protocol/settlement-sdk ``` `getOrderId` needs a map from protocol chain ID to VM type. Build it from `GET /chains` rather than from the quote, and cache it — it changes only when Relay adds a chain. The same response carries the canonical depository address per chain, which step 4 needs. ```ts theme={null} import type { VmType } from "@relay-protocol/settlement-sdk"; const VM_TYPES = { bvm: "bitcoin-vm", evm: "ethereum-vm", hypevm: "hyperliquid-vm", lvm: "lighter-vm", svm: "solana-vm", tonvm: "ton-vm", tvm: "tron-vm", xrpvm: "xrp-vm", } as const; const { chains } = await fetch("https://api.relay.link/chains").then((r) => r.json()); const CHAINS: Record = {}; const DEPOSITORIES: Record = {}; for (const chain of chains) { const protocolChainId = chain.protocol?.v2?.chainId; if (!protocolChainId) continue; CHAINS[protocolChainId] = VM_TYPES[chain.vmType as keyof typeof VM_TYPES]; if (chain.protocol.v2.depository) { DEPOSITORIES[protocolChainId] = chain.protocol.v2.depository.toLowerCase(); } } ``` `getOrderId` hashes the order data into the order ID. Recomputing it locally and comparing against `protocol.v2.orderId` confirms that the ID Relay returned corresponds to the properties you just inspected. ```ts theme={null} import { getOrderId } from "@relay-protocol/settlement-sdk"; const { orderId, orderData } = quote.protocol.v2; const computedOrderId = getOrderId(orderData, CHAINS); if (computedOrderId.toLowerCase() !== orderId.toLowerCase()) { throw new Error("Order ID does not match the order data"); } ``` ### 4. Confirm the transaction matches the order The deposit transaction pays the protocol depository, passes the order ID as the deposit `id`, and transfers the order's input payment. Check all three. Select the deposit transaction using the depository address from `GET /chains`, not `protocol.v2.depository` from the quote. Using the quote's own field to validate the quote proves nothing — a tampered response could name an attacker's address in both places and still pass. The order ID does not cover the currency, value, or token amount the transaction transfers, so those need checking separately — and which deposit function is correct follows from the order, not from the calldata. ```ts expandable theme={null} import { decodeFunctionData, parseAbi } from "viem"; import { getVmTypeNativeCurrency } from "@relay-protocol/settlement-sdk"; // The wallet your app will send the deposit from — the same address you passed // as `user` in the quote request. Read it from your own state, never from the // quote response, for the same reason the depository is pinned above. const expectedPayer = "WALLET_ADDRESS"; const payment = quote.protocol.v2.orderData.inputs[0].payment; // 1. Resolve the canonical depository for the payment chain. const depository = DEPOSITORIES[payment.chainId]; if (!depository) { throw new Error(`No known depository for chain ${payment.chainId}`); } // 2. Cross-check the quote's own claim against the canonical address. if (quote.protocol.v2.paymentDetails.depository.toLowerCase() !== depository) { throw new Error("Quote names an unexpected depository"); } // 3. Find the step that pays it. const depositTx = quote.steps .flatMap((step) => step.items) .map((item) => item.data) .find((tx) => tx?.to?.toLowerCase() === depository); if (!depositTx) { throw new Error("No step pays the canonical depository"); } const { functionName, args } = decodeFunctionData({ abi: parseAbi([ "function depositNative(address depositor, bytes32 id)", "function depositErc20(address depositor, address token, uint256 amount, bytes32 id)", ]), data: depositTx.data, }); // 4. The order ID must be the deposit id. if ((args[args.length - 1] as string).toLowerCase() !== computedOrderId.toLowerCase()) { throw new Error("Deposit transaction does not commit to the verified order"); } // 5. The depositor must be the account you expect to pay. if ((args[0] as string).toLowerCase() !== expectedPayer.toLowerCase()) { throw new Error("Deposit transaction credits an unexpected depositor"); } // 6. Decide which deposit function the ORDER calls for, then require the // transaction to use it. Branching on functionName alone would let a native // transfer satisfy an order whose input payment is an ERC-20. const nativeCurrency = getVmTypeNativeCurrency(CHAINS[payment.chainId]); const orderWantsNative = payment.currency.toLowerCase() === nativeCurrency.toLowerCase(); if (orderWantsNative !== (functionName === "depositNative")) { throw new Error("Deposit function does not match the order payment currency"); } // 7. The transaction must move exactly the order's input payment. if (orderWantsNative) { if (BigInt(depositTx.value) !== BigInt(payment.amount)) { throw new Error("Native value does not match the order payment amount"); } } else { if ((args[1] as string).toLowerCase() !== payment.currency.toLowerCase()) { throw new Error("Deposit token does not match the order payment currency"); } if (BigInt(args[2] as bigint) !== BigInt(payment.amount)) { throw new Error("Deposit amount does not match the order payment amount"); } if (BigInt(depositTx.value ?? 0) !== 0n) { throw new Error("ERC-20 deposit must not send native value"); } } ``` At this point the transaction is bound to the order you inspected in step 2. If all the checks pass and the order settles, settlement uses the recipient, minimum output amount, currency, deadline, and destination chain encoded in the verified order. Verification confirms the encoded order. It does not guarantee that the order will fill. ## Validating onchain If you need the check to happen inside a contract rather than in your backend, use `protocol.v2.orderSignature`. It is a solver's ECDSA signature over the 32 raw bytes of the order ID, following EIP-191. The signing address in the example below is `0xf70da97812cb96acdf810712aa562db8dfa3dbef`; the signer does not determine the quote's deposit destination. Pin the expected signer in your integration using independently verified configuration. Do not accept an arbitrary signer just because it appears in the same quote you are validating. ```ts theme={null} import { recoverMessageAddress, hexToBytes } from "viem"; const signer = await recoverMessageAddress({ message: { raw: hexToBytes(computedOrderId) }, signature: quote.protocol.v2.orderSignature, }); // 0xf70da97812CB96acDF810712Aa562db8dfA3dbEF ``` The equivalent Solidity check: ```solidity theme={null} bytes32 digest = keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", orderId) ); require(ECDSA.recover(digest, orderSignature) == EXPECTED_SOLVER_SIGNER, "bad signature"); ``` The signature only attests to the order ID. It is not a substitute for steps 2 to 4 — recompute the order ID from `orderData` so you know which order the ID refers to, and check the transaction against it. ## Deposit addresses Deposit address deposits cannot yet be verified this way. The order ID checks above bind a **transaction** to an order; a deposit address has no calldata to bind, so verifying one requires deriving the address itself from the order properties. That derivation is not yet available to integrators. If you rely on deposit addresses and need to validate them before sending funds, contact Relay so your use case can be factored into that work. ## Migrating from the signature API `GET /requests/:requestId/signature` and `GET /requests/:requestId/signature/v2` have been removed. Calls now return `404`. The signature API returned a solver signature over a small subset of intent fields — origin and destination chain, user, currency, and (on v2) destination currency. It had several problems that the deterministic flow does not: * It never covered the fields that matter most, such as the minimum output amount, so it could not tell you the trade was priced correctly. * It required a second API call after the quote, and returned only partial data until the request reached a terminal state. * Its behavior was inconsistent across chains. * It attested to a request shape that does not map cleanly onto protocol orders. To migrate, replace the `GET /requests/:requestId/signature/v2` call with `includeProtocolData: true` on your existing `/quote/v2` request, and follow steps 2 to 4 above. The verification happens locally, so you remove a network round trip in the process. # Refunds Source: https://docs.relay.link/references/api/api_core_concepts/refunds Learn how Relay handles refunds. The Relay platform tracks refunds when a bridge or swap cannot complete. Solvers can return funds on the origin chain instead of filling on the destination chain. This page describes platform refund behavior; see [protocol refunds](/references/protocol/how-it-works#refunds) for how those transfers settle. ## What is a refund? A refund returns funds to a user when a swap or bridge cannot be completed. Refunds are paid on the origin chain to the `refundTo` address supplied on the quote. If `refundTo` is not set, automatic refund is disabled. ## 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 your deposit transaction swaps a memecoin into USDC, the refundable amount is the USDC deposited after that swap, minus refund gas costs. ## 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? Automatic refunds require **`refundTo`** to be set and a refundable balance large enough to cover gas. A solver submits the refund when the route's checks permit it; completion depends on the origin chain. [Screening failures](/references/api/api_core_concepts/execution-errors) and other execution errors can prevent automatic refunds. ## 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 How step execution works when driving Relay directly from the API 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 The [Relay platform](/references/api/overview) returns the steps needed to execute a quote, such as submitting a deposit transaction or signing a message. Follow the transaction targets, calldata, and amounts returned by the [quote endpoint](/references/api/get-quote-v2); they depend on the route and deposit method. Step descriptions are display text, not a specification of fund ownership or settlement. Some API responses retain legacy “relayer” wording; use **`id`**, **`kind`**, and **`items`** to drive execution, and see [Input Validation](/references/api/api_core_concepts/input-validation) for verifying protocol deposits. 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 expandable 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. **Multiple items** A signature step can hold more than one item. Sign and post the items in the order they appear: a later item can be refused until the earlier one is posted. Not every item has a `check`; for example, the `MulticallAuthorization` item of an [ERC-3009 permit](/features/gasless-swaps#erc-3009-permits-with-multicallauthorization) has none. ### 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) | Submit the quoted deposit for cross-chain execution | Used for cross-chain swaps and bridges. | | [`approve`](#approve) | Approve the transfer of an ERC20 token | Used before `deposit` or `swap` when the quoted flow requires a token allowance. | | `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 ERC-20 → native swap permits (Permit2). Same-chain ERC-20 → ERC-20 swaps return `approve` → `swap` transactions instead; see [Gasless Swaps](/features/gasless-swaps#permit-based-gasless). | ### 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 Submit the deposit transaction returned in the quote. A solver uses its own liquidity to complete the cross-chain fill; the deposit target depends on the quoted route. The following historical response illustrates the step shape, not an address to reuse for new deposits. ```json expandable theme={null} { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the depository contract", "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 ERC-20 → native swap permits using PermitBatchWitnessTransferFrom. Same-chain ERC-20 → ERC-20 swaps do not use this step; they return `approve` and `swap` transaction steps even when **`usePermit: true`** is set. ```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 An origin operation is the source-chain part of a bridge or swap. Its output determines the amount available for the cross-chain order; a solver uses its own capital to fill on the destination chain. There are three possible outcomes: 1. **Exact**: When the origin operation matches the solver's input exactly, the destination operation proceeds as initially quoted. 2. **Surplus**: The origin operation produces more than the quoted input amount. 3. **Shortage**: The origin operation produces less than the quoted input amount. ## 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 deposited amount is 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 deposited amount is 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, bridge, or cross-chain call. 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` when a precise output amount is required — for example, bridging an exact amount to a destination address, guaranteeing a merchant receives a specific payment amount, or executing a destination call that requires a fixed input such as minting with an exact ERC20 amount. Destination calls also support `EXACT_INPUT` when you want to cap spend and accept a variable output. # 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 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 The EVM protocol deposit flow sends funds to Relay Depository contracts. 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 to the Depository with the 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 expandable 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 to the Depository with the 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. For Bitcoin origin deposits, the request transitions to `pending` as soon as the deposit appears in the Bitcoin mempool — earlier than the equivalent transition on EVM origins. Block-confirmation policy still governs the move to `submitted`/`success`. ### Example: Withdraw from Bitcoin to Base Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ```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 depository contract", "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 depository contract", "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 in the typical case. For high-value deposits — those whose origin amount exceeds a per-currency threshold — Relay waits for **2 block confirmations** instead, to reduce the risk of a reorg invalidating the fill. Bitcoin's block time averages \~10 minutes but varies significantly; blocks can arrive within minutes of each other or take 30-50 minutes. The `timeEstimate` field on the quote response reflects whichever rule applies — expect roughly one block of wait time for standard deposits and roughly two for high-value ones. When polling for deposit address transactions, allow for at least 2-3 blocks worth of time (3-4 for high-value deposits) before considering a transaction stalled. You can monitor block times at [mempool.space](https://mempool.space). ### Manually Reindexing a Bitcoin Deposit If a Bitcoin deposit is not picked up by automatic indexing, you can trigger a manual reindex by calling [`POST /transactions/index`](/references/api/transactions-index) with `chainId: 8253038` and the Bitcoin `txHash`. Relay verifies the transaction is included in a block before reindexing — calls for transactions that have not yet been confirmed in a block return `404`. See the [Transaction Indexing guide](/references/api/api_guides/transaction-indexing) for the full request shape. ### 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 depository contract", "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. Relay supports three [trade types](/references/api/api_core_concepts/trade-types) for bridging: * **`EXACT_INPUT`** — specify the amount you want to send; the output amount may vary based on fees and slippage. * **`EXPECTED_OUTPUT`** — specify the approximate amount you want to receive; the input amount is calculated accordingly and the output may vary slightly due to slippage. * **`EXACT_OUTPUT`** — specify the exact amount you want to receive; the input amount is calculated accordingly. If the exact output cannot be filled, the request fails and the user is refunded on the origin chain. Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ```bash EXACT_INPUT 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" }' ``` ```bash EXACT_OUTPUT 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" }' ``` ```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). Create an API key in the [Relay Dashboard](https://dashboard.relay.link). ## 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. To avoid polling entirely, configure a [**webhook**](/references/api/api_guides/webhooks) in the Relay Dashboard to push status updates to your backend, or subscribe to Relay’s [**websocket server**](/references/api/api_guides/websockets) for a real-time status stream. 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/handling-errors): Handle quote errors when using the Relay API. * [**Cost & Fee Structure**](/references/api/api_core_concepts/fees): Understand the fees associated with using the Relay API. # Base Builder Codes Source: https://docs.relay.link/references/api/api_guides/builder-codes Attribute onchain activity by appending builder codes to your Relay transactions Base builder codes are a way to attribute onchain activity. Each code enhances onchain transactions with offchain metadata and makes your application more discoverable. To learn more about builder codes, see the [Base documentation](https://docs.base.org/base-chain/builder-codes/builder-codes). *** ## How to add a builder code A builder code is an [ERC-8021](https://eip.tools/eip/8021) `dataSuffix` appended to the end of a deposit transaction's calldata. The called contract ignores the trailing bytes, but indexers read them to credit your application. Relay matches an onchain transaction to its quote by `requestId`. Appending bytes to arbitrary calldata changes the transaction, so the solver can no longer match it and the request stops tracking — even though the transaction succeeds onchain. To avoid this, request an [explicit deposit](/references/api/get-quote-v2#body-explicit-deposit) quote: set **`explicitDeposit`** to `true`. The explicit flow routes funds through the Relay Depository contract that carries the order ID as an argument, so the solver tracks the deposit by that order ID rather than by exact calldata — trailing suffix bytes don't affect it. Builder codes only work for quotes that route through the Relay Depository contract; same-chain swaps don't use the deposit flow and can't carry a builder code. ### Example Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ```typescript expandable theme={null} import { createWalletClient, custom, concatHex, toHex, stringToHex, size, type Hex, } from "viem"; import { base } from "viem/chains"; // ERC-8021 suffix = 1-byte length + builder code (ASCII) + 1-byte version + 16-byte marker. const ERC8021_MARKER = "0x80218021802180218021802180218021" as const; // 0x8021 repeated 8× const ERC8021_VERSION = "0x00" as const; function buildDataSuffix(builderCode: string): Hex { const codeHex = stringToHex(builderCode); const lenByte = toHex(size(codeHex), { size: 1 }); return concatHex([lenByte, codeHex, ERC8021_VERSION, ERC8021_MARKER]); } async function fetchQuote(req: Record) { const res = await fetch("https://api.relay.link/quote/v2", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(req), }); if (!res.ok) throw new Error(`Relay quote failed: ${res.status} ${await res.text()}`); return res.json(); } // Append the suffix to the deposit step only — that is the depositErc20 call the // Depository tracks by order ID, so trailing bytes don't break requestId matching. function applyDataSuffix(quote: any, dataSuffix: Hex) { return { ...quote, steps: quote.steps.map((step: any) => step.id !== "deposit" ? step : { ...step, items: step.items.map((item: any) => ({ ...item, data: { ...item.data, data: concatHex([item.data.data, dataSuffix]) }, })), } ), }; } const walletClient = createWalletClient({ chain: base, transport: custom(window.ethereum) }); const [account] = await walletClient.getAddresses(); // 1. Request an explicit-deposit quote (required for builder codes). const quote = await fetchQuote({ user: account, recipient: account, originChainId: 8453, // Base destinationChainId: 42161, // Arbitrum originCurrency: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base destinationCurrency: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum amount: "1000000", // 1 USDC (6 decimals) tradeType: "EXACT_INPUT", protocolVersion: "v2", explicitDeposit: true, }); // 2. Append your builder code to the deposit calldata. const dataSuffix = buildDataSuffix("YOUR_BUILDER_CODE"); const quoteWithSuffix = applyDataSuffix(quote, dataSuffix); // 3. Execute each transaction step in order (approval first, then deposit). for (const step of quoteWithSuffix.steps) { if (step.kind !== "transaction") continue; for (const { data: tx } of step.items) { await walletClient.sendTransaction({ account, to: tx.to, data: tx.data, value: BigInt(tx.value ?? 0), chainId: tx.chainId, }); } } ``` *** ## Caveats * Builder codes require an explicit deposit: set **`explicitDeposit`** to `true` and **`protocolVersion`** to `"v2"` in your quote request. * Only quotes that route through the Relay Depository contract (`depositErc20`) support builder codes. Same-chain swaps do not. * Append the suffix to the `deposit` step only. # 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 onchain 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. Cross-chain calls support both `EXACT_INPUT` and `EXACT_OUTPUT`. Use `EXACT_OUTPUT` when the destination call requires a precise output amount. Use `EXACT_INPUT` when you want to cap how much the user spends and can tolerate variable output. ### Using `EXACT_INPUT` with proxy contracts For `EXACT_INPUT` calls, integrators usually need their own proxy contract on the destination chain. Because Relay delivers a variable output amount, you cannot always precompute the final calldata at quote time. Depositing into Aave is a good example. With `EXACT_OUTPUT`, the request can include two calls directly: approve the Aave pool, then call `supply()` with a fixed amount. With `EXACT_INPUT`, the destination amount is variable, so route the calls through your own Aave-specific proxy contract instead. A simple proxy contract can: * pull the full approved token balance from the caller that initiated execution, such as Relay's router in a Relay deposit flow * approve the Aave pool contract * call `supply()` using its full current balance In that flow, your Relay request should include: * an approval to your proxy contract, usually for `MAX_UINT256` * a call to your proxy contract, such as `execute()` Your proxy contract then reads its live balance at execution time and builds the final Aave interaction from that balance. The request below is schematic. In practice, `txs[].data` must contain ABI-encoded calldata. Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ```bash theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "WALLET", "originChainId": 42161, "destinationChainId": 137, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", "amount": "100000000000000", "tradeType": "EXACT_INPUT", "txs": [ { "to": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", "data": "approve(PROXY_CONTRACT, MAX_UINT256)", "value": "0" }, { "to": "PROXY_CONTRACT", "data": "execute()", "value": "0" } ] }' ``` In `execute()`, your proxy can read its token balance, approve the Aave pool, and call `supply(asset, balance, onBehalfOf, referralCode)` with the live balance. ```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", // use EXACT_INPUT to cap spend and accept variable 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 depository contract", "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" } } } } ``` Create an API key in the [Relay Dashboard](https://dashboard.relay.link). ### 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 | `EXACT_INPUT` (quote by input amount) or `EXACT_OUTPUT` (quote by required 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** - Use `"EXACT_OUTPUT"` for a precise output, or `"EXACT_INPUT"` to cap spend **Plan for `EXACT_INPUT` execution** - Use a destination proxy contract that reads the delivered balance and builds follow-up calldata at runtime **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/handling-errors): Handle quote errors when using the Relay API. * [**Cost & Fee Structure**](/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 * 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 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**](/references/api/api_core_concepts/step-execution) documentation. When you're ready to execute swaps, refer to the [**Get Quote**](/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](/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 Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ```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":"2", "chainId":1, "verifyingContract":"0x0000000000000000000000000000000000000000" }, "types":{ "NonceMapping":[ { "name":"chainId", "type":"string" }, { "name":"wallet", "type":"address" }, { "name":"depositor", "type":"address" }, { "name":"id", "type":"bytes32" }, { "name":"nonce", "type":"uint256" } ] }, "value":{ "chainId":"hyperliquid", "wallet":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "depositor":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "nonce":1770329064777, "id":"0x3bfdb54176b619a1f1f59f091a0708d0216a9f74b985b34d092cdcc4f9f4f9f3" }, "primaryType":"NonceMapping" }, "post":{ "endpoint":"/authorize", "method":"POST", "body":{ "type":"nonce-mapping", "walletChainId":1337, "wallet":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "depositor":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "nonce":1770329064777, "id":"0x3bfdb54176b619a1f1f59f091a0708d0216a9f74b985b34d092cdcc4f9f4f9f3", "signatureChainId":1 } } } } ], "requestId":"0x02e937b4d022321b56673e12a42064dde45f25b2f7da0db5eb6b4fa5b747f64a" }, { "id":"deposit", "action":"Confirm transaction in your wallet", "description":"Depositing funds to the depository contract", "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 Recommended**: The `signatureChainId` and `domain.chainId` fields are used for EIP-712 signature authorization only — they do not affect fund routing. The `destinationChainId` in your quote request determines where funds are delivered. The API returns Ethereum mainnet (chainId: 1) by default. We recommend overriding both `item.data.sign.domain.chainId` and `item.data.post.body.signatureChainId` to match the user's active chain ID (Base, Optimism, etc.) for wallet and signature correctness. 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": "2", "chainId": 1, "verifyingContract": "0x0000000000000000000000000000000000000000" }, "types": { "NonceMapping": [ { "name": "chainId", "type": "string" }, { "name": "wallet", "type": "address" }, { "name": "depositor", "type": "address" }, { "name": "id", "type": "bytes32" }, { "name": "nonce", "type": "uint256" } ] }, "value": { "chainId": "hyperliquid", "wallet": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "depositor": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "nonce": 1765463670254, "id": "0x7b3e352d6bb600f7d937250968dc9b9c7deaf34b3787fb2312fc7374603f1667" }, "primaryType": "NonceMapping" }, "post": { "endpoint": "/authorize", "method": "POST", "body": { "type": "nonce-mapping", "walletChainId": 1337, "wallet": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "depositor": "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 depository contract", "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/relayprotocol/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 and withdraw from Lighter using the Relay API Relay supports depositing and withdrawing to Lighter from any supported chain. You can try depositing today using the [Relay App](https://relay.link/bridge/lighter) (note: withdrawing is not yet supported in the app). This document details how to integrate Lighter into your application. ## 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**](/references/api/api_core_concepts/step-execution) documentation. When you're ready to execute deposits or withdrawals, refer to the [**Get Quote**](/references/api/get-quote-v2) API endpoint. If you're integrating via the SDK, the [Lighter wallet adapter](/references/relay-kit/sdk/adapters) wraps this flow — it resolves the user's Lighter account index, manages the API key lifecycle, and submits the L2 transfer on your behalf. ## Lighter-specific API Parameters | **Parameter** | **Input** | **Description** | | - | - | - | | **toChainId** / **fromChainId** | `3586256` | Lighter Chain ID. Use `toChainId` for deposits and `fromChainId` for withdrawals. | | **recipient** | Lighter account index | The Lighter account index to credit — not a wallet address. | | **user** | Lighter account index | On withdrawals, the Lighter account index to debit. | 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). ## How to deposit Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ```bash Request theme={null} curl 'https://api.relay.link/quote/v2' \ -H 'content-type: application/json' \ -d '{ "user": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "originChainId": 8453, "destinationChainId": 3586256, "originCurrency": "0x0000000000000000000000000000000000000000", "destinationCurrency": "0", "recipient": "509564", "tradeType": "EXACT_INPUT", "amount": "1000000000000000000" }' ``` ```json Response expandable theme={null} { "steps":[ { "id":"deposit", "action":"Confirm transaction in your wallet", "description":"Depositing funds to the depository contract", "kind":"transaction", "items":[ { "status":"incomplete", "data":{ "from":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "to":"0x4cd00e387622c35bddb9b4c962c136462338bc31", "data":"0x49290c1c000000000000000000000000f3d63166f0ca56c3c1a3508fce03ff0cf3fb691eb86a968b9c157fc6ab0e30ac13b7c9e8fe5ed28007b43da606737bca5f00cf6d", "value":"1000000000000000000", "chainId":8453, "gas":"32713", "maxFeePerGas":"6500000", "maxPriorityFeePerGas":"1000000" }, "check":{ "endpoint":"/intents/status/v3?requestId=0x63bf829d9cead2d0dc94113cc368cdc2e2f25f4a9d5c24d4123002e83cf504c1", "method":"GET" } } ], "requestId":"0x63bf829d9cead2d0dc94113cc368cdc2e2f25f4a9d5c24d4123002e83cf504c1", "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":"160689399841", "amountFormatted":"0.000000160689399841", "amountUsd":"0.000372", "minimumAmount":"160689399841" }, "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":"703637317433828", "amountFormatted":"0.000703637317433828", "amountUsd":"1.630976", "minimumAmount":"703637317433828" }, "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":"0", "amountFormatted":"0.0", "amountUsd":"0", "minimumAmount":"0" }, "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":"703637317433828", "amountFormatted":"0.000703637317433828", "amountUsd":"1.630976", "minimumAmount":"703637317433828" }, "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":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "recipient":"509564", "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":"1000000000000000000", "amountFormatted":"1.0", "amountUsd":"2317.921203", "minimumAmount":"1000000000000000000" }, "currencyOut":{ "currency":{ "chainId":3586256, "address":"0", "symbol":"USDC (Perp)", "name":"USD Coin (Perp)", "decimals":6, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"2312305224", "amountFormatted":"2312.305224", "amountUsd":"2311.766457", "minimumAmount":"2266059120" }, "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":"1000000000000000000", "amountFormatted":"1.0", "amountUsd":"2317.921203", "minimumAmount":"1000000000000000000" }, "totalImpact":{ "usd":"-6.154746", "percent":"-0.27" }, "swapImpact":{ "usd":"-4.523770", "percent":"-0.20" }, "expandedPriceImpact":{ "swap":{ "usd":"-4.743994" }, "execution":{ "usd":"-0.02" }, "relay":{ "usd":"-1.390752" }, "app":{ "usd":"0" }, "sponsored":{ "usd":"0" } }, "rate":"2312.305224", "slippageTolerance":{ "origin":{ "usd":"0.000000", "value":"0", "percent":"0.00" }, "destination":{ "usd":"46.004152", "value":"46246104", "percent":"1.99" } }, "timeEstimate":2, "userBalance":"0", "isFixedRate":false, "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":"1000000000000000000", "amountFormatted":"1.0", "amountUsd":"2317.921203", "minimumAmount":"1000000000000000000" }, "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":"1000000000000000000", "amountFormatted":"1.0", "amountUsd":"2317.921203", "minimumAmount":"1000000000000000000" }, "router":"relay" }, "destination":{ "inputCurrency":{ "currency":{ "chainId":3586256, "address":"0", "symbol":"USDC (Perp)", "name":"USD Coin (Perp)", "decimals":6, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"2312305224", "amountFormatted":"2312.305224", "amountUsd":"2311.766457", "minimumAmount":"2266059120" }, "outputCurrency":{ "currency":{ "chainId":3586256, "address":"0", "symbol":"USDC (Perp)", "name":"USD Coin (Perp)", "decimals":6, "metadata":{ "logoURI":"https://ethereum-optimism.github.io/data/USDC/logo.png", "verified":true } }, "amount":"2312305224", "amountFormatted":"2312.305224", "amountUsd":"2311.766457", "minimumAmount":"2266059120" }, "router":"relay" } } }, "protocol":{ "v2":{ "orderId":"0xb86a968b9c157fc6ab0e30ac13b7c9e8fe5ed28007b43da606737bca5f00cf6d", "orderData":{ "version":"v1", "solverChainId":"base", "solver":"0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt":"0xa1fc4e8df304b84b48fe04ec56e5e131e3a2dff4760d792845069edadd1c30f3", "inputs":[ { "payment":{ "chainId":"base", "currency":"0x0000000000000000000000000000000000000000", "amount":"1000000000000000000", "weight":"1" }, "refunds":[ { "chainId":"base", "recipient":"0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e", "currency":"0x0000000000000000000000000000000000000000", "minimumAmount":"0", "deadline":1777644048, "extraData":"0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, { "chainId":"lighter", "recipient":"509564", "currency":"0", "minimumAmount":"0", "deadline":1777644048, "extraData":"0x" } ] } ], "output":{ "chainId":"lighter", "payments":[ { "recipient":"509564", "currency":"0", "minimumAmount":"2266059120", "expectedAmount":"2312305224" } ], "calls":[ ], "deadline":1777644048, "extraData":"0x" }, "fees":[ ] }, "paymentDetails":{ "chainId":"base", "depository":"0x4cd00e387622c35bddb9b4c962c136462338bc31", "currency":"0x0000000000000000000000000000000000000000", "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](/references/api/api_core_concepts/step-execution) flow to complete the transaction. ## How to withdraw ```bash Request theme={null} curl 'https://api.relay.link/quote/v2' \ -H 'content-type: application/json' \ -d '{ "user": "509564", "originChainId": 3586256, "destinationChainId": 8453, "originCurrency": "0", "destinationCurrency": "0x0000000000000000000000000000000000000000", "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "tradeType": "EXACT_INPUT", "amount": "1000000" }' ``` ```json Response expandable theme={null} { "steps": [ { "id": "deposit", "action": "Confirm transaction in your wallet", "description": "Depositing funds to the depository contract", "kind": "transaction", "items": [ { "status": "incomplete", "data": { "action": { "type": "transfer", "parameters": { "toAccountIndex": 723071, "assetIndex": 3, "fromRouteType": 0, "toRouteType": 0, "amount": 1000000, "usdcFee": 3000000, "memo": "0xfc88e4016659ca122b91dae1aedbb9038e09693a7ce33a1ed1a053d71bdb6aa2" } } }, "check": { "endpoint": "/intents/status/v3?requestId=0x67ef65da60de7815d1fa42546dbee934b97d67f18bf5eb536ab616449a436aa3", "method": "GET" } } ], "requestId": "0x67ef65da60de7815d1fa42546dbee934b97d67f18bf5eb536ab616449a436aa3", "depositAddress": "" } ], "fees": { "gas": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "3000000", "amountFormatted": "3.0", "amountUsd": "2.999505", "minimumAmount": "3000000" }, "relayer": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "22319", "amountFormatted": "0.022319", "amountUsd": "0.022315", "minimumAmount": "22319" }, "relayerGas": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "1518", "amountFormatted": "0.001518", "amountUsd": "0.001518", "minimumAmount": "1518" }, "relayerService": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "20801", "amountFormatted": "0.020801", "amountUsd": "0.020798", "minimumAmount": "20801" }, "app": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "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": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "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": "509564", "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "currencyIn": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "1000000", "amountFormatted": "1.0", "amountUsd": "0.999835", "minimumAmount": "1000000" }, "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": "422039092891785", "amountFormatted": "0.000422039092891785", "amountUsd": "0.977171", "minimumAmount": "404777693992511" }, "refundCurrency": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "1000000", "amountFormatted": "1.0", "amountUsd": "0.999835", "minimumAmount": "1000000" }, "totalImpact": { "usd": "-0.022664", "percent": "-2.27" }, "swapImpact": { "usd": "-0.000349", "percent": "-0.03" }, "expandedPriceImpact": { "swap": { "usd": "-0.000548" }, "execution": { "usd": "-0.021517" }, "relay": { "usd": "-0.000599" }, "app": { "usd": "0" }, "sponsored": { "usd": "0" } }, "rate": "0.000422039092891785", "slippageTolerance": { "origin": { "usd": "0.000000", "value": "0", "percent": "0.00" }, "destination": { "usd": "0.039869", "value": "17261398899274", "percent": "4.08" } }, "timeEstimate": 3, "userBalance": "0", "isFixedRate": false, "route": { "origin": { "inputCurrency": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "1000000", "amountFormatted": "1.0", "amountUsd": "0.999835", "minimumAmount": "1000000" }, "outputCurrency": { "currency": { "chainId": 3586256, "address": "0", "symbol": "USDC (Perp)", "name": "USD Coin (Perp)", "decimals": 6, "metadata": { "logoURI": "https://ethereum-optimism.github.io/data/USDC/logo.png", "verified": true } }, "amount": "1000000", "amountFormatted": "1.0", "amountUsd": "0.999835", "minimumAmount": "1000000" }, "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": "422039092891785", "amountFormatted": "0.000422039092891785", "amountUsd": "0.977171", "minimumAmount": "404777693992511" }, "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": "422039092891785", "amountFormatted": "0.000422039092891785", "amountUsd": "0.977171", "minimumAmount": "404777693992511" }, "router": "relay" } } }, "protocol": { "v2": { "orderId": "0xfc88e4016659ca122b91dae1aedbb9038e09693a7ce33a1ed1a053d71bdb6aa2", "orderData": { "version": "v1", "solverChainId": "base", "solver": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "salt": "0x686091e7a544ba90af1b4d25ee805b7520b7a1f76bea9097503d3565ef099c27", "inputs": [ { "payment": { "chainId": "lighter", "currency": "0", "amount": "1000000", "weight": "1" }, "refunds": [ { "chainId": "lighter", "recipient": "509564", "currency": "0", "minimumAmount": "0", "deadline": 1777647416, "extraData": "0x" }, { "chainId": "base", "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "0", "deadline": 1777647416, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" } ] } ], "output": { "chainId": "base", "payments": [ { "recipient": "0x03508bB71268BBA25ECaCC8F620e01866650532c", "currency": "0x0000000000000000000000000000000000000000", "minimumAmount": "404777693992511", "expectedAmount": "422039092891785" } ], "calls": [], "deadline": 1777647416, "extraData": "0x000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f" }, "fees": [] }, "paymentDetails": { "chainId": "lighter", "depository": "723071", "currency": "0", "amount": "1000000" } } } } ``` The Quote API returns a transaction step with Lighter-specific execution data: ```json theme={null} { "data": { "action": { "type": "transfer", "parameters": { "toAccountIndex": 723071, "assetIndex": 3, "fromRouteType": 0, "toRouteType": 0, "amount": 1000000, "usdcFee": 3000000, "memo": "0xfc88e4016659ca122b91dae1aedbb9038e09693a7ce33a1ed1a053d71bdb6aa2" } } } } ``` The fields returned inside `data.action` are: | **Property** | **Type** | **Description** | | - | - | - | | **type** | string | The Lighter action to perform. For withdrawals this is always `transfer`. | | **toAccountIndex** | number | The Lighter account index to transfer funds to (the relayer's account that executes the fill). | | **assetIndex** | number | Identifier of the asset being transferred on Lighter (e.g. `3` for USDC). | | **fromRouteType** | number | The route type to debit from on the sender's Lighter account. | | **toRouteType** | number | The route type to credit on the recipient Lighter account. | | **amount** | number | Amount of the asset to transfer, in the asset's smallest unit. | | **usdcFee** | number | Fee paid in USDC, in the asset's smallest unit, covering Lighter execution costs. | | **memo** | string | Hex-encoded Relay `requestId` used to correlate the Lighter transfer with the Relay order. | In order to interact with the Lighter API you must first [request a Lighter API key](https://apidocs.lighter.xyz/docs/api-keys) which is scoped to the specific account. You'll then need to use the data returned by the Relay Quote API along with the API key from Lighter to [send transactions](https://apidocs.lighter.xyz/docs/trading). There is a binary used for signing which the SDK packages and lazily loads for you. ## SDK To use the SDK with Lighter, install and configure the [Lighter wallet adapter](/references/relay-kit/sdk/adapters#what-adapters-are-available-out-of-the-box). ```bash theme={null} yarn add @relayprotocol/relay-lighter-wallet-adapter @relayprotocol/relay-sdk viem ``` If you want the adapter to run its own bootstrap (fresh keygen, `accountApiKey`, or `storage` paths), you also need the Lighter SDK: ```bash theme={null} yarn add @relay-protocol/lighter-ts-sdk ``` For implementation details and code samples, see the [Adapters documentation](/references/relay-kit/sdk/adapters). # Migrating to Requests v3 Source: https://docs.relay.link/references/api/api_guides/migrating-to-requests-v3 What changed in GET /requests/v3 and how to upgrade from v2. `GET /requests/v3` is the latest version of the Requests API. It gives you a richer query surface with advanced filtering across status, chains, amounts, addresses, time, and more, plus a cleaner and more consistent response. This guide covers every change and how to upgrade. `GET /requests/v2` remains available during a deprecation window (see the [sunset timeline](#sunset-timeline) below) — migrate before it's retired. This migration changes the platform API response and query surface. The `v3` endpoint suffix does not mean a request uses a particular version of [Relay Protocol](/references/protocol/overview). `x-api-key` is now required. Every v3 request authenticates against an active API key. See [API Keys](/references/api/api-keys) for details. ## Sunset timeline `GET /requests/v2` is **deprecated as of July 22, 2026**. It keeps working, but migrate to `GET /requests/v3` before it's fully retired on **November 24, 2026**. | Date | What changes | | - | - | | **Jul 22, 2026** | v2 is deprecated; v3 is the recommended version. v2 keeps working. | | **Sep 1, 2026 onward** | v2's rate limit is progressively reduced each month; requests over the limit receive `429`. | | **Nov 24, 2026** | v2 is fully retired — all traffic must be on v3. | ## Detecting deprecation programmatically Every response from `GET /requests/v2` carries in-band deprecation signals so clients can surface or log the sunset timeline without hard-coding dates. * **`Deprecation`** header — RFC 8594 structured-field Date, e.g. `@1784678400`. * **`Sunset`** header — RFC 7231 HTTP-date, e.g. `Tue, 24 Nov 2026 00:00:00 GMT`. * **`Link`** header — three lines with `rel="deprecation"`, `rel="sunset"`, and `rel="successor-version"` pointing to this guide and to `GET /requests/v3`. These headers are attached to every v2 response (`2xx`, `4xx`, and `5xx`) and are listed in `Access-Control-Expose-Headers`, so browser clients using `fetch` or `XMLHttpRequest` can read them cross-origin: ```javascript theme={null} const res = await fetch("https://api.relay.link/requests/v2?..."); const sunset = res.headers.get("sunset"); const deprecation = res.headers.get("deprecation"); ``` Successful (`200`) responses additionally include a top-level `deprecation` object: ```json theme={null} "deprecation": { "message": "GET /requests/v2 is deprecated. Migrate to GET /requests/v3. Relay reduces the v2 rate limit in stages from September 1, 2026. Relay retires v2 on November 24, 2026.", "deprecatedAt": "2026-07-22T00:00:00Z", "throttledFrom": "2026-09-01T00:00:00Z", "sunsetAt": "2026-11-24T00:00:00Z", "successor": "https://api.relay.link/requests/v3", "migrationGuide": "https://docs.relay.link/references/api/api_guides/migrating-to-requests-v3" } ``` When v2 rate-limits your traffic, `429` responses return a route-specific body that also points at v3: ```json theme={null} { "message": "You reached the rate limit for GET /requests/v2. Relay reduces this limit in stages. Relay retires v2 on November 24, 2026. Migrate to GET /requests/v3.", "successor": "https://api.relay.link/requests/v3", "migrationGuide": "https://docs.relay.link/references/api/api_guides/migrating-to-requests-v3" } ``` No `Retry-After` is emitted on `429` from `GET /requests/v2`. For guidance on reducing your request volume, see [Handling Rate Limits](/references/api/api_core_concepts/handling-rate-limits). ## Response shape The same request shown in full for v2 and v3, plus a focused diff of what moved. ```json expandable theme={null} { "id": "0x0000348634...", "status": "success", "user": "0xe7a2052a...", "recipient": "0x07082933...", "depositAddress": null, "moonpayId": null, "referrer": "my-app|user-123", "data": { "slippageTolerance": "0", "failReason": "N/A", "refundFailReason": "N/A", "subsidizedRequest": true, "price": "10000000000", "currency": "usdc", "currencyObject": { "chainId": 8453, "address": "0x833589...", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://...", "verified": true } }, "feeCurrency": "usdc.e", "feeCurrencyObject": { "chainId": 137, "address": "0x2791bc...", "symbol": "USDC.e", "name": "USDCoin (bridged)", "decimals": 6, "metadata": { "logoURI": "https://...", "verified": true } }, "appFeeCurrencyObject": { "chainId": 137, "address": "0x2791bc...", "symbol": "USDC.e", "name": "USDCoin (bridged)", "decimals": 6 }, "fees": { "gas": "1160", "fixed": "0", "price": "1999930" }, "feesUsd": { "gas": "0.001159", "fixed": "0.000000", "price": "1.999162" }, "appFees": [ { "recipient": "0xdfd877...", "bps": "5", "amount": "5000000", "amountUsd": "4.997980", "amountUsdCurrent": "4.998080" } ], "paidAppFees": [ { "recipient": "0xdfd877...", "bps": "5", "amount": "5000000", "amountUsd": "4.997980", "amountUsdCurrent": "4.998080" } ], "subsidizedFee": { "origin": { "amount": "7001090", "address": "0x2791bc...", "chainId": 137 }, "sponsor": { "amount": "7000949", "address": "0x833589...", "chainId": 8453 }, "amountUsd": "6.998261", "amountUsdCurrent": "6.998401" }, "expandedPriceImpact": { "quoted": { "swap": { "usd": "-1.999380" }, "execution": { "usd": "-0.001160" }, "relay": { "usd": "0.000000" }, "app": { "usd": "-4.998625" }, "sponsored": { "usd": "6.999165" } }, "actual": { "swap": { "usd": "-1.999380" }, "execution": { "usd": "-0.001160" }, "relay": { "usd": "0.000000" }, "app": { "usd": "-4.998625" }, "sponsored": { "usd": "6.999165" } } }, "feeSponsorship": { "quoted": { "selectedComponents": ["execution", "swap", "relay", "app"], "capHit": false, "components": { "execution": { "selected": true, "total": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "1160", "amountFormatted": "0.00116", "amountUsd": "0.001160", "minimumAmount": "1160" }, "sponsored": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "1160", "amountUsd": "0.001160" }, "userPays": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "0", "amountUsd": "0" } }, "swap": { "selected": true, "total": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "1999380", "amountUsd": "1.999380" }, "sponsored": { "amount": "1999380", "amountUsd": "1.999380" }, "userPays": { "amount": "0", "amountUsd": "0" } }, "relay": { "selected": true, "total": { "amount": "0", "amountUsd": "0.000000" }, "sponsored": { "amount": "0", "amountUsd": "0" }, "userPays": { "amount": "0", "amountUsd": "0" } }, "app": { "selected": true, "total": { "amount": "5000000", "amountUsd": "4.998625" }, "sponsored": { "amount": "5000000", "amountUsd": "4.998625" }, "userPays": { "amount": "0", "amountUsd": "0" } } }, "sponsoredTotal": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "7001090", "amountFormatted": "7.00109", "amountUsd": "6.999165" }, "userPaysTotal": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "0", "amountUsd": "0" } }, "actual": { "selectedComponents": ["execution", "swap", "relay", "app"], "capHit": false, "components": { "execution": "…", "swap": "…", "relay": "…", "app": "…" }, "sponsoredTotal": { "amount": "7001090", "amountUsd": "6.999165" }, "userPaysTotal": { "amount": "0", "amountUsd": "0" } } }, "inTxs": [ { "hash": "0xcd4d70...", "fee": "139725000000000", "block": 87433122, "type": "onchain", "chainId": 137, "timestamp": 1779747672, "status": "success", "data": {}, "stateChanges": [] } ], "outTxs": [ { "hash": "0x1c0089...", "fee": "433800000000", "block": 46479163, "type": "onchain", "chainId": 8453, "timestamp": 1779747673, "status": "success", "data": {}, "stateChanges": [] } ], "usesExternalLiquidity": false, "timeEstimate": 4, "metadata": { "sender": "0xE7a205...", "recipient": "0x070829...", "currencyIn": { "currency": { "chainId": 137, "address": "0x2791bc...", "symbol": "USDC.e", "name": "USDCoin (bridged)", "decimals": 6, "metadata": { "logoURI": "https://...", "verified": true } }, "amount": "10000000000", "amountFormatted": "10000.0", "amountUsd": "9997.600000", "amountUsdCurrent": "9996.000000", "minimumAmount": "10000000000" }, "currencyOut": { "currency": { "chainId": 8453, "address": "0x833589...", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://...", "verified": true } }, "amount": "10000000000", "amountFormatted": "10000.0", "amountUsd": "9997.600000", "amountUsdCurrent": "9996.000000", "minimumAmount": "10000000000" }, "rate": "1", "route": { "origin": { "inputCurrency": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "10000000000" }, "outputCurrency": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "10000000000" }, "router": "relay" }, "destination": { "inputCurrency": { "currency": { "chainId": 8453, "symbol": "USDC", "decimals": 6 }, "amount": "10000000000" }, "outputCurrency": { "currency": { "chainId": 8453, "symbol": "USDC", "decimals": 6 }, "amount": "10000000000" }, "router": "relay" } } } }, "protocol": { "orderId": "0x...", "hubType": "onchain", "isWithdrawable": false, "solver": { "address": "0x...", "protocolChainId": "8453", "chainId": 8453 }, "deposit": { "origin": { "amount": "10000000000", "chainId": 137, "currency": "0x2791bc...", "depositor": "0xe7a205...", "depository": "0x...", "onchainId": "0x...", "transactionId": "0xcd4d70..." } }, "settlement": { "destination": { "fills": [ { "chainId": 8453, "transactionId": "0x1c0089..." } ], "refunds": [] } } }, "createdAt": "2026-05-25T22:21:11.161Z", "updatedAt": "2026-05-25T22:21:51.291Z" } ``` ```json expandable theme={null} { "id": "0x0000348634...", "status": "success", "user": "0xe7a2052a...", "sender": "0xE7a205...", "recipient": "0x07082933...", "refundTo": null, "depositAddress": null, "requestType": "Bridge", "data": { "slippageTolerance": "0", "failReason": null, "refundFailReason": null, "failedTxHash": null, "failedTxBlockNumber": null, "failedCallData": null, "usesExternalLiquidity": false, "timeEstimate": 4, "triggeredByFastFill": null, "inTxs": [ { "txHash": "0xcd4d70...", "fee": "139725000000000", "block": 87433122, "type": "onchain", "chainId": 137, "timestamp": 1779747672, "status": "success", "data": {}, "stateChanges": [] } ], "outTxs": [ { "txHash": "0x1c0089...", "fee": "433800000000", "block": 46479163, "type": "onchain", "chainId": 8453, "timestamp": 1779747673, "status": "success", "data": {}, "stateChanges": [] } ], "appFees": { "quoted": [ { "recipient": "0xdfd877...", "bps": "5", "amount": "5000000", "amountFormatted": "5.0" } ], "actual": [ { "recipient": "0xdfd877...", "bps": "5", "amount": "5000000", "amountFormatted": "5.0", "amountUsd": "4.997980" } ], "currency": { "chainId": 137, "address": "0x2791bc...", "symbol": "USDC.e", "name": "USDCoin (bridged)", "decimals": 6, "metadata": { "logoURI": "https://...", "verified": true } } }, "fees": { "quoted": { "swap": { "usd": "-1.999380", "amount": "1999380", "amountFormatted": "1.99938" }, "execution": { "usd": "-0.001160", "amount": "1160", "amountFormatted": "0.00116" }, "platform": { "usd": "0.000000", "amount": "0", "amountFormatted": "0" }, "app": { "usd": "-4.998625", "amount": "5000000", "amountFormatted": "5.0" }, "sponsored": { "usd": "6.999165", "amount": "7001090", "amountFormatted": "7.00109" } }, "actual": { "swap": { "usd": "-1.999380" }, "execution": { "usd": "-0.001160" }, "platform": { "usd": "0.000000" }, "app": { "usd": "-4.998625" }, "sponsored": { "usd": "6.999165" } }, "currency": { "chainId": 137, "address": "0x2791bc...", "symbol": "USDC.e", "name": "USDCoin (bridged)", "decimals": 6, "metadata": { "logoURI": "https://...", "verified": true } } }, "feeSponsorship": { "quoted": { "selectedComponents": ["execution", "swap", "platform", "app"], "maxSubsidizationAmount": null, "capHit": false, "components": { "execution": { "selected": true, "total": { "amount": "1160", "amountFormatted": "0.00116", "amountUsd": "0.001160", "minimumAmount": "1160" }, "sponsored": { "amount": "1160", "amountUsd": "0.001160" }, "userPays": { "amount": "0", "amountUsd": "0" } }, "swap": { "selected": true, "total": { "amount": "1999380", "amountUsd": "1.999380" }, "sponsored": { "amount": "1999380", "amountUsd": "1.999380" }, "userPays": { "amount": "0", "amountUsd": "0" } }, "platform": { "selected": true, "total": { "amount": "0", "amountUsd": "0" }, "sponsored": { "amount": "0", "amountUsd": "0" }, "userPays": { "amount": "0", "amountUsd": "0" } }, "app": { "selected": true, "total": { "amount": "5000000", "amountUsd": "4.998625" }, "sponsored": { "amount": "5000000", "amountUsd": "4.998625" }, "userPays": { "amount": "0", "amountUsd": "0" } } }, "sponsoredTotal": { "amount": "7001090", "amountFormatted": "7.00109", "amountUsd": "6.999165" }, "userPaysTotal": { "amount": "0", "amountFormatted": "0", "amountUsd": "0" } }, "actual": { "selectedComponents": ["execution", "swap", "platform", "app"], "capHit": false, "components": { "execution": "…", "swap": "…", "platform": "…", "app": "…" }, "sponsoredTotal": { "amount": "7001090", "amountUsd": "6.999165" }, "userPaysTotal": { "amount": "0", "amountUsd": "0" }, "sponsorPayment": { "amount": "7000949", "address": "0x833589...", "chainId": 8453 } }, "currency": { "chainId": 137, "address": "0x2791bc...", "symbol": "USDC.e", "decimals": 6 } }, "route": { "includedSwapSources": [], "includedOriginSwapSources": [], "includedDestinationSwapSources": [], "quoted": { "origin": { "inputCurrency": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "10000000000" }, "outputCurrency": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "10000000000" }, "router": "relay" }, "destination": { "inputCurrency": { "currency": { "chainId": 8453, "symbol": "USDC", "decimals": 6 }, "amount": "10000000000" }, "outputCurrency": { "currency": { "chainId": 8453, "symbol": "USDC", "decimals": 6 }, "amount": "10000000000" }, "router": "relay" } }, "actual": { "rate": "1", "origin": { "inputCurrency": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "10000000000" }, "outputCurrency": { "currency": { "chainId": 137, "symbol": "USDC.e", "decimals": 6 }, "amount": "10000000000" }, "router": "relay" }, "destination": { "inputCurrency": { "currency": { "chainId": 8453, "symbol": "USDC", "decimals": 6 }, "amount": "10000000000" }, "outputCurrency": { "currency": { "chainId": 8453, "address": "0x833589...", "symbol": "USDC", "name": "USD Coin", "decimals": 6, "metadata": { "logoURI": "https://...", "verified": true } }, "amount": "10000000000", "amountFormatted": "10000.0", "amountUsd": "9997.600000" }, "router": "relay" } } }, "platformFee": null, "quoteOraclePrice": null, "referrer": "my-app|user-123", "refundCurrencyData": null, "externalMetadata": { "moonpayId": null } }, "protocol": { "orderId": "0x...", "hubType": "onchain", "isWithdrawable": false, "solver": { "address": "0x...", "protocolChainId": "8453", "chainId": 8453 }, "deposit": { "origin": { "amount": "10000000000", "chainId": 137, "currency": "0x2791bc...", "depositor": "0xe7a205...", "depository": "0x...", "onchainId": "0x...", "transactionId": "0xcd4d70..." } }, "settlement": { "destination": { "fills": [ { "chainId": 8453, "transactionId": "0x1c0089..." } ], "refunds": [] } } }, "features": ["app_fees"], "createdAt": "2026-05-25T22:21:11.161Z", "updatedAt": "2026-05-25T22:21:51.291Z" } ``` ```jsonc expandable theme={null} { "id": "0x0000348634...", "status": "success", "moonpayId": null, // [!code --] "sender": "0xE7a205...", // [!code ++] promoted from data.metadata.sender "refundTo": null, // [!code ++] "requestType": "Bridge", // [!code ++] "depositAddress": null, // now an object: { address, type, depositor, depositTxHash, recoveryAddress } "referrer": "my-app|user-123", // [!code --] moved to data.referrer (authenticated) "features": ["app_fees"], // [!code ++] "data": { "failReason": "N/A", // [!code --] "refundFailReason": "N/A", // [!code --] "failReason": null, // [!code ++] "refundFailReason": null, // [!code ++] "subsidizedRequest": true, // [!code --] "price": "10000000000", // [!code --] removed; read data.route.actual.destination.inputCurrency "fees": { "gas": "1160", "fixed": "0", "price": "1999930" }, // [!code --] "feesUsd": { "gas": "0.001159" }, // [!code --] "currency": "usdc", // [!code --] "currencyObject": { }, // [!code --] "feeCurrency": "usdc.e", // [!code --] "feeCurrencyObject": { }, // [!code --] "appFees": [ { "bps": "5", "amount": "5000000", "amountUsd": "4.99" } ], // [!code --] "paidAppFees": [ { "bps": "5", "amount": "5000000", "amountUsd": "4.99" } ], // [!code --] "appFees": { // [!code ++] "quoted": [ { "bps": "5", "amount": "5000000" } ], // [!code ++] "actual": [ { "bps": "5", "amount": "5000000", "amountUsd": "4.99" } ], // [!code ++] "currency": { "symbol": "USDC.e", "decimals": 6 } // [!code ++] }, // [!code ++] "subsidizedFee": { "amountUsd": "6.99" }, // [!code --] "expandedPriceImpact": { "quoted": { "relay": { "usd": "0" } } }, // [!code --] "fees": { // [!code ++] "quoted": { "swap": {}, "execution": {}, "platform": {}, "app": {}, "sponsored": {} }, // [!code ++] "actual": { }, // [!code ++] "currency": { "symbol": "USDC.e", "decimals": 6 } // [!code ++] }, // [!code ++] "feeSponsorship": { "quoted": { "selectedComponents": ["execution","swap","relay","app"], "components": { "relay": {} } } // [!code --] "quoted": { "selectedComponents": ["execution","swap","platform","app"], "components": { "platform": {} } }, // [!code ++] "actual": { "sponsorPayment": { "amount": "...", "chainId": 8453 } }, // [!code ++] "currency": { "symbol": "USDC.e", "decimals": 6 } // [!code ++] }, "inTxs": [ { "hash": "0xcd4d70..." } ], // [!code --] "outTxs": [ { "hash": "0x1c0089..." } ], // [!code --] "inTxs": [ { "txHash": "0xcd4d70..." } ], // [!code ++] "outTxs": [ { "txHash": "0x1c0089..." } ], // [!code ++] "metadata": { "sender": "…", "currencyIn": {}, "currencyOut": {}, "rate": "1", "route": {} }, // [!code --] "route": { // [!code ++] "includedSwapSources": [], // [!code ++] "quoted": { "origin": {}, "destination": {} }, // [!code ++] "actual": { "rate": "1", "origin": {}, "destination": {} } // [!code ++] }, // [!code ++] "referrer": "my-app|user-123", // [!code ++] authenticated; moved from root "externalMetadata": { "moonpayId": null } // [!code ++] } } ``` The full OpenAPI schema lives at [api.relay.link/documentation/json](https://api.relay.link/documentation/json). **`data.fees` has changed meaning.** The key `fees` is reused for a different shape. In v2, `data.fees` was raw wei (`gas`/`fixed`/`price`/`gateway`). In v3 that object is removed, and `data.fees` now holds the per-component USD breakdown that v2 called `expandedPriceImpact`. Code reading `data.fees.gas` will break — read `data.fees.quoted` / `data.fees.actual` instead. ## What changed Every individual change, grouped by request side and response side. ### Request & querying ```bash theme={null} GET /requests/v3 Headers: x-api-key: # now required [!code ++] ``` The `x-api-key` request header is now required. Every request authenticates against an active API key. See [API Keys](/references/api/api-keys) for details. **Access scope.** v3 returns requests owned by your integrator, plus requests that were created without an API key. Any of your API keys can access all v3 data for your other keys — if you use more than one key, you do not have to change your integration. v3 exposes a broad filter surface. All parameters are optional and combine with AND semantics unless noted. | Category | Parameters | | - | - | | **Search** | `term` (request id, wallet, deposit address, every tx hash), `id`, `orderId`, `depositTxHash`, `fillTxHash`, `refundTxHash` | | **Chain** | `originChainId`, `destinationChainId`, `chainId` (either direction), `privateChainsToInclude` | | **Address** | `user`, `depositAddress`, `recipient` | | **Status / routing** | `status`, `failReason`, `refundFailReason`, `apiKeyId`, `appId`, `requestType`, `referrer` | | **Currency** | `currencyInChainId`, `currencyOutChainId`, `currencyInAddress`, `currencyOutAddress`, `currencyInSymbol`, `currencyOutSymbol` | | **Ranges** | `startTimestamp`, `endTimestamp`, `startBlock`, `endBlock`, `amountUsdMin`/`Max`, `appFeesAmountUsdMin`/`Max`, `fillTimeMin`/`Max` | | **Other** | `features`, `includeChildRequests`, `filters` (AND/OR), `sortBy`, `sortDirection`, `includeAuthenticatedData`, `includeTotal` | `term` is a single unified search across request id, wallet, deposit address, and every transaction hash (deposit, fill, refund, failed, chain-entry). It replaces the v2 pattern of running separate `user` / `hash` / `id` lookups and merging the results. **Sorting.** `sortBy` accepts `createdAt` (default), `updatedAt`, or `amountUsd`. `sortDirection` is `asc` or `desc` (defaults to `desc` for time fields, `asc` for `amountUsd`). **Advanced filtering (AND / OR / negation / null-checks).** The `filters` parameter takes a JSON-encoded array of groups. Fields **within** a group are AND'd; the groups themselves are OR'd. Any key can be negated with a `not:` prefix, a field's value may be an array (OR within that field), and a value of `null` is a null-check ("is empty" / "is not empty") instead of an equality test. Top-level parameters still apply. ```bash theme={null} # "(depositAddress=0xabc AND originChainId=8453) OR (depositAddress=0x123 AND originChainId=1)" GET /requests/v3?filters=[{"depositAddress":"0xabc","originChainId":8453},{"depositAddress":"0x123","originChainId":1}] # negation + array value: everything except failures and refunds GET /requests/v3?filters=[{"not:status":["failure","refund"]}] # null-checks: requests with no deposit address, or with any deposit address GET /requests/v3?filters=[{"depositAddress":null}] GET /requests/v3?filters=[{"not:depositAddress":null}] ``` OR groups support identity, address, chain, currency, and status-style fields. Range filters and `features` are top-level only. Null-checks work on any recognised `filters` key, including the composite `user` field and the case-insensitive `currencyInSymbol` / `currencyOutSymbol` fields. `null` cannot be combined with real values in an array for the same key — that returns `400`. **API key filtering.** To limit results to specific API keys, use the top-level `apiKeyId` parameter with a maximum of 50 comma-separated IDs that your integrator owns. Each entry may be a bare UUID or the prefixed form `key_id_` — both forms resolve to the same key, deduplicate against each other, and count once toward the 50-value cap. Responses continue to return the bare UUID form. It does not give access to more requests. We no longer recommend the `apiKey` filter, because it puts your API key in the URL. **App filtering (owner-scoped).** An **App** is the durable identity for an integration — one App can own many API keys over its lifetime, and rotating or revoking a key does not change the App that owns the historical requests created under it. Use the top-level `appId` query parameter to narrow results to one or more Apps you own, with up to **50** comma-separated `app_id_` values (whitespace trimmed, canonicalized to lowercase, deduplicated). Multiple values OR-match. Every supplied ID must belong to the authenticating integrator — unknown, foreign, or partially owned lists return `403`; malformed IDs, empty entries, or more than 50 values return `400`; transient resolution failures return `500` rather than silently dropping the filter. `appId` cannot appear inside a `filters` group — it is top-level only. It is ANDed with any `apiKeyId` / `apiKey` filter, so it narrows the result set rather than expanding access. Matching uses the indexed App attribution written at request time, so an `appId` filter includes requests created under keys that have since been revoked. ```bash theme={null} # requests for one App you own GET /requests/v3?appId=app_id_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa # multiple Apps, OR-matched GET /requests/v3?appId=app_id_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa,app_id_bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb ``` Requests indexed before App attribution was recorded do not match any `appId` filter and are omitted from `appId`-filtered results until the attribution is populated for them. They remain visible under the existing visibility rules when queried without an `appId` filter. **Referrer filtering.** The `referrer` filter — as a top-level query parameter or as a `referrer` / `not:referrer` key inside any `filters` group — matches the exact value supplied at quote time. Empty `referrer` values are rejected. * **With `apiKeyId` (or `apiKey`)**: results include matching requests for the selected keys **plus** matching public keyless requests (requests created without an API key), regardless of their age. Every supplied ID must belong to the authenticating integrator — unowned IDs (or callers that cannot resolve to an integrator identity) return `403`. * **Without `apiKeyId` / `apiKey`**: results include only matching public keyless requests, regardless of age. No pairing is required and no `400` is returned for an unpaired referrer filter. Scope inside `filters` groups: * A **top-level** `referrer` applies the referrer scope (selected keys + public keyless, or public keyless only) to every OR branch. * Without a top-level `referrer`, a `referrer` / `not:referrer` key inside a group scopes **that group only** to the selected keys plus public keyless requests; other groups retain their existing scope. Existing visibility restrictions still apply — public keyless requests are not reassigned to your integrator. ```bash theme={null} # matches requests for the given key AND public keyless requests GET /requests/v3?referrer=my-app|user-123&apiKeyId=YOUR_API_KEY_ID # no apiKeyId: matches only public keyless requests GET /requests/v3?referrer=my-app|user-123 # per-group scope: the referrer branch adds public keyless rows, # the other branch keeps its existing scope GET /requests/v3?filters=[{"referrer":"my-app|user-123"},{"originChainId":8453}]&apiKeyId=YOUR_API_KEY_ID # negation also works inside filters, with or without apiKeyId GET /requests/v3?filters=[{"not:referrer":"other-app"}] ``` **Removed & changed parameters.** * `hash` is removed; use `term`, or the targeted `depositTxHash` / `fillTxHash` / `refundTxHash`. **Total match count.** Pass `includeTotal=true` to receive the exact total count of matching requests as `total` on the response (independent of `limit` and `continuation`). Omit it — or set `includeTotal=false` — when you only need the current page; computing the total adds meaningful query latency, so it is opt-in. ### Response fields The `currency` (string) + `currencyObject` (object) duality is removed everywhere, along with `feeCurrency`/`feeCurrencyObject` and `appFeeCurrencyObject`. Every currency is a single object: `{ chainId, address, symbol, name, decimals, metadata }`. Amount objects carry that currency plus `amount`, `amountFormatted`, `amountUsd`, and where relevant `amountUsdCurrent`, `minimumAmount`. ```jsonc theme={null} "currency": "usdc", // [!code --] "currencyObject": { "chainId": 137, "symbol": "USDC", "decimals": 6 } // [!code --] // read the currency object directly off the amount, [!code ++] // e.g. data.route.actual.origin.inputCurrency.currency [!code ++] ``` `data.expandedPriceImpact` is renamed to **`data.fees`** (and the old wei `data.fees` / `data.feesUsd` are removed). Each phase (`quoted`, `actual`) breaks fees into `swap`, `execution`, `platform`, `app`, and `sponsored`, with the shared currency hoisted to `data.fees.currency`. The `relay` component is renamed to **`platform`**, and per-component values now include `amount` and `amountFormatted` in addition to `usd`. ```jsonc theme={null} "expandedPriceImpact": { "quoted": { "swap": { "usd": "-1.99" }, "relay": { "usd": "0" } } } // [!code --] "fees": { // [!code ++] "quoted": { "swap": { "usd": "-1.99", "amount": "...", "amountFormatted": "..." }, "platform": { "usd": "0" } }, // [!code ++] "actual": { "...": "..." }, // [!code ++] "currency": { "symbol": "USDC.e" } // [!code ++] } // [!code ++] ``` `data.subsidizedRequest` and `data.subsidizedFee` are removed. `data.feeSponsorship` is now the only sponsorship source — determine whether a request was sponsored from `feeSponsorship.{quoted,actual}.sponsoredTotal.amountUsd`. The `relay` bucket is renamed to **`platform`**, the shared `currency` is hoisted to `feeSponsorship.currency`, and the `actual` phase gains a `sponsorPayment` object. ```jsonc theme={null} "subsidizedRequest": true, // [!code --] "subsidizedFee": { "amountUsd": "6.99" }, // [!code --] "feeSponsorship": { "quoted": { "selectedComponents": ["execution","swap","relay","app"], "components": { "relay": {} } } // [!code --] "quoted": { "selectedComponents": ["execution","swap","platform","app"], "components": { "platform": {} } }, // [!code ++] "actual": { "sponsorPayment": { "amount": "...", "chainId": 8453 } }, // [!code ++] "currency": { "symbol": "USDC.e" } // [!code ++] } ``` The parallel `appFees` (quoted) and `paidAppFees` (actual) arrays are consolidated into one object with explicit `quoted` / `actual` keys and a hoisted `currency`. ```jsonc theme={null} "appFees": [ { "bps": "5", "amount": "5000000" } ], // [!code --] "paidAppFees": [ { "bps": "5", "amount": "5000000", "amountUsd": "4.99" } ], // [!code --] "appFees": { // [!code ++] "quoted": [ { "bps": "5", "amount": "5000000" } ], // [!code ++] "actual": [ { "bps": "5", "amount": "5000000", "amountUsd": "4.99" } ], // [!code ++] "currency": { "symbol": "USDC.e" } // [!code ++] } // [!code ++] ``` The `data.metadata` catch-all is removed. `sender` is promoted to the **root** (next to `recipient`), and everything else moves into **`data.route`**, which now has two phases: * `data.route.quoted` — the route as quoted at request time. * `data.route.actual` — what actually executed. Carries `rate`, and `actual` falls back to `quoted` when there's no execution data yet (e.g. same-chain swaps). Each phase has `origin` and `destination` sides, and each side has `inputCurrency` / `outputCurrency` (each a full amount object) plus `router` (on the `actual` route, `router` reflects the provider that actually filled the leg, not always `"relay"`). `includedSwapSources` / `includedOriginSwapSources` / `includedDestinationSwapSources` are hoisted to the top of `data.route`. There is **no `data.swap`** and **no `data.quotedOutputAmount`** — read amounts from the route instead: * Amount deposited: `data.route.actual.origin.inputCurrency` (→ `quoted`). * Amount received: `data.route.actual.destination.outputCurrency` (→ `quoted`). For same-chain swaps there is no destination side, so fall back to `origin.outputCurrency`. ```jsonc theme={null} "metadata": { "sender": "…", "currencyIn": {}, "currencyOut": {}, "rate": "1", "route": {} } // [!code --] // sender → promoted to the root [!code ++] "route": { // [!code ++] "includedSwapSources": [], // [!code ++] "quoted": { "origin": {}, "destination": {} }, // [!code ++] "actual": { "rate": "1", "origin": {}, "destination": {} } // [!code ++] } // [!code ++] ``` Inside `inTxs[]` and `outTxs[]`, the `hash` field is renamed to `txHash`. The array names themselves are unchanged. ```jsonc theme={null} "inTxs": [ { "hash": "0xcd4d70..." } ], // [!code --] "outTxs": [ { "hash": "0x1c0089..." } ] // [!code --] "inTxs": [ { "txHash": "0xcd4d70..." } ], // [!code ++] "outTxs": [ { "txHash": "0x1c0089..." } ] // [!code ++] ``` The response `status` enum is now `pending`, `waiting`, `depositing`, `submitted`, `success`, `failure`, `refund`. `depositing` and `submitted` are new; the v2 `delayed` value is gone. The `status` **filter** accepts `success`, `failure`, `refund`, `pending`, `depositing`. ```jsonc theme={null} status ∈ { pending, waiting, delayed, success, failure, refund } // [!code --] status ∈ { pending, waiting, depositing, submitted, success, failure, refund } // [!code ++] ``` * `moonpayId` moves from the root to **`data.externalMetadata.moonpayId`**. * `sender` is **promoted to the root** (next to `recipient`); it previously lived in `data.metadata.sender`. * `referrer` moves into **`data.referrer`** and is returned **only when `includeAuthenticatedData=true`**. It remains a supported query filter (see [Filtering, search & sorting](#what-changed)); when paired with `apiKeyId` (or `apiKey`), the IDs must belong to the authenticating integrator, and results include matching requests for those keys plus matching public keyless requests. Without `apiKeyId` / `apiKey`, referrer filters return only matching public keyless requests. * **`refundTo`** (refund target for deposit-address transactions) is **added** at the root. * **`requestType`** (`Bridge`, `Crosschain Swap`, `Same-chain Swap`, `Send`, `Wrap`, `Unwrap`, `Call`) is **added** at the root. * **`features`** (e.g. `gasless_execution`, `fast_fill`, `gas_top_up`, `app_fees`, `deposit_address`) is **added** at the root. * **`depositAddress`** is now a structured object: `{ address, type: "strict" | "open", depositor, depositTxHash, recoveryAddress }`. `recoveryAddress` is the origin-chain address that can recover deposit-address funds if the recipient wallet is later blocked (`null` when not set). * **`supersededByRequestId`** is **promoted to the root**; it previously lived at `data.supersededByRequestId` in v2. When a deposit-address request is regenerated (currently Bitcoin/BVM only), the original request's `supersededByRequestId` points to the most recent regenerated `requestId` that owns the sweep and fill. `null` otherwise, including on regenerations from every other chain — track those by `depositAddress` (see [Handling Quote Regeneration](/features/deposit-addresses#handling-quote-regeneration)). * **`data.quoteOraclePrice`** is **added** — the oracle price snapshot at quote time (`{ rate, observedAt, inputCurrency, outputCurrency }`), returned only for fixed-BPS oracle integrators; `null` otherwise. * **`data.apiKeyName`** is **added** — the display name of the API key that created the request. Returned **only when `includeAuthenticatedData=true`** and the caller authenticates as the request's owning integrator (same gate as `data.referrer` and `data.platformFee`); `null` when the API key has no name set. * **`data.appId`** is **added** — the durable App identifier (`app_id_`) for the App that owned the API key the request was created with. Returned **only when `includeAuthenticatedData=true`** and the caller authenticates as the request's owning integrator (same gate as `data.referrer`, `data.platformFee`, `data.apiKeyName`, and `data.blockedAddresses`). `null` for owned records that have no indexed App attribution. The field is never populated by guessing from the request's name, referrer, caller, or organization. * **`data.blockedAddresses`** is **added** — an array of the wallet address(es) that failed screening and caused a `BLOCKED_WALLET` failure. Returned **only when `includeAuthenticatedData=true`** and the caller authenticates as the request's owning integrator (same gate as `data.referrer`, `data.platformFee`, and `data.apiKeyName`); `null` on non-`BLOCKED_WALLET` failures and when no addresses were recorded. Screening-provider scores and reasons are never returned. * `data.price` is **removed** — read the quoted output amount from `data.route.actual.destination.inputCurrency` (distinct from the actual received amount in `data.route.actual.destination.outputCurrency`). * `failReason` / `refundFailReason` use **`null`** instead of the string `"N/A"`. ## Field migration reference | v2 | v3 | | - | - | | `data.expandedPriceImpact` | `data.fees` (component `relay` → `platform`) | | `data.metadata.sender` | `sender` (root) | | `data.metadata.recipient` | `recipient` (root) | | `data.metadata.{currencyIn,currencyOut,rate,currencyGasTopup}` | `data.route.{quoted,actual}.*` | | `data.metadata.route` | `data.route.{quoted,actual}` | | `data.appFees` (quoted) + `data.paidAppFees` (actual) | `data.appFees.{quoted,actual,currency}` | | `inTxs[].hash` / `outTxs[].hash` | `inTxs[].txHash` / `outTxs[].txHash` | | `feeSponsorship…components.relay` | `feeSponsorship…components.platform` | | `moonpayId` (root) | `data.externalMetadata.moonpayId` | | `depositAddress` (nullable) | `depositAddress` object `{address,type,depositor,depositTxHash}` | | `data.supersededByRequestId` | `supersededByRequestId` (root) | | Removed in v3 | Replacement | | - | - | | `data.fees` (wei `gas`/`fixed`/`price`/`gateway`) | `data.fees` (now USD price-impact) | | `data.feesUsd` | `data.fees` | | `data.currency` / `data.currencyObject` | currency object on each amount | | `data.feeCurrency` / `data.feeCurrencyObject` | currency hoisted into fee objects | | `data.appFeeCurrencyObject` | `data.appFees.currency` | | `data.subsidizedRequest` / `data.subsidizedFee` | `data.feeSponsorship` | | `data.metadata` (catch-all) | `data.route.{quoted,actual}` (`sender` promoted to root) | | `data.price` | read `data.route.actual.destination.inputCurrency` | | `referrer` (query parameter, cross-integrator) | supported as a filter. With `apiKeyId` (or `apiKey`) owned by the authenticating integrator, matches those keys plus public keyless requests; without a key filter, matches only public keyless requests. Still returned in the response body only when `includeAuthenticatedData=true` | | `?hash` | `term` / `depositTxHash` / `fillTxHash` / `refundTxHash` | | status `delayed` | `depositing` / `submitted` | | Added in v3 | Notes | | - | - | | `requestType` | request type (`Bridge`, `Crosschain Swap`, `Same-chain Swap`, `Send`, `Wrap`, `Unwrap`, `Call`) | | `sender` (root) | promoted from `data.metadata.sender` | | `features` (root) | request feature flags (`gasless_execution`, `fast_fill`, `app_fees`, …) | | `refundTo` | refund target for deposit-address flows | | `depositAddress.recoveryAddress` | origin-chain address that can recover deposit-address funds if the recipient is later blocked | | `data.quoteOraclePrice` | oracle price snapshot at quote time (`rate`, `observedAt`, `inputCurrency`, `outputCurrency`); fixed-BPS oracle integrators only | | `data.apiKeyName` | display name of the API key that created the request; returned only with `includeAuthenticatedData=true` for the owning integrator | | `data.appId` | durable App identifier (`app_id_`) that owned the request's API key, including after that key is revoked; returned only with `includeAuthenticatedData=true` for the owning integrator | | `data.blockedAddresses` | address(es) that failed screening on `BLOCKED_WALLET` failures; returned only with `includeAuthenticatedData=true` for the owning integrator | | `data.route.actual` | executed route (with `rate`); falls back to `quoted` | | `data.externalMetadata` | partner correlation IDs (e.g. `moonpayId`) | | `feeSponsorship.actual.sponsorPayment` | what the sponsor actually paid | | `total` (response, opt-in) | total count of requests matching the filters; returned only when `includeTotal=true` | | broad filter/sort surface | see [Filtering, search & sorting](#what-changed) | ## Migration checklist Point requests at `GET /requests/v3` and send `x-api-key` (now required). Results include only requests that your integrator owns, from all of your API keys. `referrer` filtering is still supported — pair it with `apiKeyId` (owned by your integrator) to also match requests for those keys, or omit the key filter to match only public keyless requests. Set `includeAuthenticatedData=true` only from a trusted server context to receive authenticated fields (see the note at the end). Stop reading `data.fees.gas` / `feesUsd`. Read the per-component USD breakdown from `data.fees.quoted` / `data.fees.actual`, and rename the `relay` component to `platform`. Replace `data.metadata.*` reads: `sender` is now at the root, and currencies/rate come from `data.route.{quoted,actual}` (prefer `actual`, fall back to `quoted`). Read the deposited amount from `route.actual.origin.inputCurrency` and the received amount from `route.actual.destination.outputCurrency` (fall back to `origin.outputCurrency` for same-chain swaps). Drop `currency`/`currencyObject` string handling — read the single currency object. Read `data.appFees.quoted` / `data.appFees.actual`. Replace `subsidizedRequest`/`subsidizedFee` with `data.feeSponsorship`, and rename its `relay` bucket to `platform`. Rename `inTxs[].hash` / `outTxs[].hash` to `txHash`. Handle the new `depositing` / `submitted` statuses (and remove `delayed`). Treat `failReason` / `refundFailReason` as `null` rather than `"N/A"`. Replace separate `user`/`hash`/`id` lookups with a single `term` search where it fits, and take advantage of the new filter/sort parameters. If you filter by `apiKey`, replace it with `apiKeyId`. Pagination is unchanged — keep passing `continuation` back for the next page (`limit` max 50). Authenticated data is only returned when you pass `includeAuthenticatedData=true` and authenticate with an API key from the integrator that owns those requests. # Smart Accounts in Relay Source: https://docs.relay.link/references/api/api_guides/smart-accounts Sponsor gas, batch actions, and execute across chains with ERC-4337 and EIP-7702 Let users transact across chains from the assets they already hold. Combine smart accounts with Relay to: * **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**\ Solvers can deliver 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 help your app preserve user identity, batch calls, and manage gas across chains: | 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: * A solver delivers ETH to their EOA on Chain B * That EOA uses a signed 7702 tx to **batch logic** on Chain B: approve → mint * `msg.sender` = the user, not the solver 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` *** The Relay platform supports both ERC-4337 and EIP-7702 for gas sponsorship, batching, and user-authorized execution. 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 Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ```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 depository contract", "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** wire limit. Relay gates every Solana-origin quote — same-chain and cross-chain — against this raw limit at quote time, so a quote that returns successfully is guaranteed to be signable and broadcastable as-is. When the compiled deposit transaction would exceed 1232 bytes, `POST /quote`, `POST /quote/v2`, and `POST /price` return a `400` response with `errorCode: "SOLANA_TX_TOO_LARGE"` and a `message` that reports the measured size and the number of bytes over the limit: ```json theme={null} { "message": "Generated Solana transaction is 1416 bytes, 184 over Solana's 1232 byte limit, so it cannot be signed or broadcast.", "errorCode": "SOLANA_TX_TOO_LARGE" } ``` Re-quoting the same route with the same input typically returns another oversized route. To recover, request a v1 transaction, restrict routing (see the mitigations below), or retry with a different input amount. Wallets and SDKs that prepend compute-budget instructions at send time (roughly 60 bytes) are responsible for checking that their additions still fit under 1232 bytes. A quote that fits the wire limit exactly leaves no room for client-added instructions — clients that add them should measure the returned steps and skip the injection if there is not enough room. Pass **`useV1Transaction: true`** in your [quote request](/references/api/get-quote-v2) to receive the deposit transaction in Solana's [v1 transaction format](https://solana.com/news/transaction-v1-and-the-alt-trade-off) (SIMD-0385), which raises the size limit from 1,232 bytes to 4,096 bytes. v1 transactions don't use address lookup tables, and the compute unit limit is set in the step's `transactionConfig` instead of through ComputeBudget instructions. The parameter is best effort: when it is honored, the returned step data sets `transactionVersion` to `1`; otherwise the quote returns a standard transaction under the 1,232-byte limit. Only set it if your wallet or signing stack can sign and send v1 transactions. Pass **`includedOriginSwapSources`** in your quote request to restrict routing to a single, size-conservative source: ```bash theme={null} curl -X POST "https://api.relay.link/quote/v2" \ -H "Content-Type: application/json" \ -d '{ "user": "GTtzwxqy67xx9DVESJjx28TgXqpc8xTqtiytgNMaQBTE", "originChainId": 792703809, "originCurrency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "destinationChainId": 792703809, "destinationCurrency": "So11111111111111111111111111111111111111112", "recipient": "GTtzwxqy67xx9DVESJjx28TgXqpc8xTqtiytgNMaQBTE", "tradeType": "EXACT_INPUT", "amount": "10000000", "includedOriginSwapSources": ["jupiter"] }' ``` 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`. | 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/relayprotocol/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/relayprotocol/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. If you receive a `404` for a freshly broadcast Bitcoin transaction, wait for at least one block confirmation and retry. ## 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. # Webhooks Source: https://docs.relay.link/references/api/api_guides/webhooks Simplify your Integration with Low-Latency Relay Webhooks Building applications at scale requires integrating with Relay's system at instant speed. Rather than polling the API for status updates, webhooks push transaction lifecycle events directly to your server the moment they happen. Configure a webhook endpoint and Relay will notify your backend in real time as quotes are processed — lightweight, no polling, low latency. ## Configuring a Webhook Webhooks are self-serve in the [Relay Dashboard](https://dashboard.relay.link). Each API key can have a single webhook endpoint, and any requests tied to that key will post events to it. To configure one: 1. Sign in to the [Relay Dashboard](https://dashboard.relay.link). 2. Open the **API keys** tab. 3. Open the actions menu on the key you want to configure and select **Configure Webhook**. 4. Enable the webhook and enter your **Webhook URL** — an HTTPS endpoint to POST to when the status of a request changes (e.g. `https://my.server/receive-relay-event`). 5. Optionally add **Custom Headers** to include on every webhook request (e.g. `Authorization: Bearer 0x123`), then save. ## Webhook Events Relay Webhooks 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 | ### Example Payload ```json theme={null} { "event": "request.status.updated", "timestamp": 1774993296140, "data": { "status": "refund", "inTxHashes": [ "0x..." ], "txHashes": [], "updatedAt": 1774993296121, "originChainId": 8453, "destinationChainId": 42161, "depositAddress": { "address": "0x..", "depositAddressType": "open", // open / strict "depositor": "0x..", //nullable "depositTxHash": "0x..", //nullable — origin-chain tx hash of the depositor's transfer into the deposit address }, //nullable "requestId": "0x...", "referrer": "your-referrer", //nullable — echoes the referrer supplied on the originating quote request "details": null, "failReason": "DOUBLE_SPEND", "refundFailReason": "N/A" } } ``` The `referrer` field echoes the value supplied on the originating quote request, or `null` if no referrer was provided. Use it to attribute webhook events back to the request source (campaign, partner, or integration surface) without an extra lookup. The `details`, `failReason`, and `refundFailReason` fields mirror the same fields returned by [`GET /intents/status/v3`](/references/api/get-intents-status-v3): | **Field** | **Description** | | - | - | | `details` | Additional status context (e.g. decoded revert data on failures). `null` when no extra detail is available. | | `failReason` | Reason a fill failed. Returns `"N/A"` on non-failure statuses. On `failure`, `fallback`, and `refund` statuses, returns the recorded reason or `"UNKNOWN"` when no specific reason is available. See [`/intents/status/v3`](/references/api/get-intents-status-v3) for the enum. | | `refundFailReason` | Reason a refund leg failed. Defaults to `"N/A"` when no refund-leg failure is recorded. See [`/intents/status/v3`](/references/api/get-intents-status-v3) for the enum. | For deposit-address requests, events are sent under the `requestId` that owns the fill. If Relay regenerates the quote, events carry the new `requestId` and nothing is sent for the quote-time one. Match these events to your order with `depositAddress.address`, and to the funding transfer with `depositAddress.depositTxHash`. See [Tracking Transactions](/features/deposit-addresses#tracking-transactions). By default we post all status updates. If you're only interested in certain statuses you can filter on the `status` property in your endpoint. ## Verification Each webhook request includes two headers for verification: | **Header** | **Description** | | - | - | | `X-Signature-Timestamp` | Unix timestamp of when the webhook was sent | | `X-Signature-SHA256` | HMAC-SHA256 signature of the request | To verify a webhook is authentic, compute the HMAC-SHA256 hash of `${timestamp}.${body}` using your API key as the secret, and compare it to the `X-Signature-SHA256` header value. ```javascript Example Verification theme={null} import crypto from 'crypto'; function verifyWebhook(req, apiKey) { const timestamp = req.headers['x-signature-timestamp']; const signature = req.headers['x-signature-sha256']; const body = JSON.stringify(req.body); const expected = crypto .createHmac('sha256', apiKey) .update(`${timestamp}.${body}`) .digest('hex'); const signatureBuffer = Buffer.from(signature, 'hex'); const expectedBuffer = Buffer.from(expected, 'hex'); return ( signatureBuffer.length === expectedBuffer.length && crypto.timingSafeEqual(signatureBuffer, expectedBuffer) ); } ``` Requests that fail verification should be rejected with a non-2xx status code. ## Webhook Delivery & Reliability Relay's built-in webhook service includes up to 10 retries with exponential backoff. For most integrations, this is sufficient. For production deployments requiring guaranteed delivery, routing, fan-out, or detailed observability, we recommend using a webhook gateway like [Hookdeck](https://hookdeck.com). A gateway sits between Relay and your server, giving you: * **Guaranteed delivery** with configurable retry policies * **Routing & fan-out** to multiple destinations from a single source * **Event inspection & replay** for debugging * **Rate limiting** to match your server's capacity To set this up, use your Hookdeck ingestion URL as the Relay webhook `endpoint` and route events to your server from there. See [Hookdeck's getting started guide](https://hookdeck.com/docs) for setup instructions. # 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. Websockets stream statuses to a connected client. To have Relay push status updates to your backend instead, configure a [webhook](/references/api/api_guides/webhooks). ## 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 uses an API key for authentication and 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}`); ``` **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 Find platform execution contracts and distinguish them from solver addresses This page lists platform execution contracts and solver addresses used by existing integrations. For settlement contracts, see [Relay Protocol Addresses](/references/protocol/addresses). Always use the transaction targets returned by your quote rather than sending funds to an address from this reference. 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 These addresses identify solvers in existing integrations, not a protocol-wide operator or a universal deposit destination. Retrieve current chain data from [Get Chains](/references/api/get-chains); use the [signature validation guide](/references/api/api_core_concepts/input-validation#validating-onchain) when checking an order commitment. | 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 | | v3.2 Router | Logs a hash of the call data in SolverCallExecuted | 0x6328ea02d28131e58b28e55c6258258a30d90e05 | 0x0729e2a4716d9f7dd17858d5d504426d228697f7 | n/a | | v3.2 ApprovalProxy | Permit2 witness includes msgValue; ERC-3009 permits need a MulticallAuthorization | 0xe4dfbd5e6784db1058124e2c87554c0591f0fb7b | 0x5567954cd026cebfe486062cb75848e0f1c12e58 | n/a | ### Migrating to the new router Relay switches to the v3.2 Router and ApprovalProxy on Monday, October 26, 2026 at 10 AM ET. * Both the v3 and v3.2 pairs stay live during the transition. If you validate transaction targets or approval spenders against known addresses, allow both pairs. * [Get Chains](/references/api/get-chains) already returns the new pair under `contracts["v3.2"]`. At the switch, `contracts.erc20Router` and `contracts.approvalProxy` change to the v3.2 addresses, and the Mainnet Contracts table below updates with them. * Token approvals do not carry over from the v3 ApprovalProxy, so a returning user approves the v3.2 ApprovalProxy once. * ERC-3009 permits on the v3.2 ApprovalProxy need a second signature, which clients opt into. See [Gasless Swaps](/features/gasless-swaps#erc-3009-permits-with-multicallauthorization). * If you call the v3.2 ApprovalProxy directly: `permitTransferAndMulticall` (ERC-2612) no longer has a nonce in its permit struct, and `permit3009TransferAndMulticall` takes an extra `multicallSignatures` argument. `transferAndMulticall`, `permit2TransferAndMulticall` and the Router's `multicall` keep their signatures, and the Permit2 witness now includes `msgValue`. * ABIs for the v3.2 contracts are available on request. ### 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 expandable 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. # Attest Deposit Source: https://docs.relay.link/references/api/attest-deposit post /withdrawals/attest-deposit Attest a deposit transaction so its funds become claimable via the withdrawal flow [How do programmatic withdrawals work?](/references/protocol/guides/withdrawals) Unlike the other withdrawal endpoints, `chainId` here is the numeric Relay chain id. These endpoints power [relay.link/withdraw](https://relay.link/withdraw) and are not yet a versioned API surface. # 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 [What are deposit addresses?](/features/deposit-addresses) # 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. Polling is not the only way to track status. Configure a [webhook](/references/api/api_guides/webhooks) in the Relay Dashboard to have status updates pushed to your backend, or subscribe to [websockets](/references/api/api_guides/websockets) for a real-time stream. 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 | | `refund` | Successfully refunded | | `failure` | Unsuccessful fill | For deposit-address requests, `waiting` on the quote-time `requestId` does not mean the deposit is missing. If Relay regenerated the quote, the fill runs under a new `requestId`, and the quote-time one can stay `waiting` after the fill succeeds. Check the order with [Get Requests](/references/api/get-requests) using `depositAddress`. See [Tracking Transactions](/features/deposit-addresses#tracking-transactions). # 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 Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. # Get Requests Source: https://docs.relay.link/references/api/get-requests get /requests/v3 This API returns all the cross-chain transactions. # Get Requests (v2) Source: https://docs.relay.link/references/api/get-requests-v2 get /requests/v2 Deprecated. Returns cross-chain transactions. Use GET /requests/v3 for new integrations. `GET /requests/v2` is **deprecated as of July 22, 2026** and is being sunset: its rate limit is reduced each month until it is **fully retired on November 24, 2026**. New integrations should use [`GET /requests/v3`](/references/api/get-requests), which requires authentication and offers a richer filter surface and a cleaner response. See the [migration guide](/references/api/api_guides/migrating-to-requests-v3#sunset-timeline) for the full timeline and how to upgrade. # 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. # Get Usage Source: https://docs.relay.link/references/api/get-usage get /metrics/usage Get API call counts for the authenticated API key, or for owned keys and Apps when using x-dev-portal-key. Rows are grouped by API key, endpoint, status code, and error code. # Get Recovery Balances Source: https://docs.relay.link/references/api/get-withdrawal-balances get /withdrawals/balances List credited hub balances for a recovery owner [How do programmatic withdrawals work?](/references/protocol/guides/withdrawals) Lists balances already credited to the recovery owner's account on the hub. Deposits awaiting attestation or recovery are excluded — see [Attest Deposit](/references/api/attest-deposit) to credit eligible deposits. Discovery is eventually consistent. `ownerChainId` is the numeric Relay chain id of the recovery owner's account; [Request Withdrawal](/references/api/request-withdrawal) takes protocol chain slugs. A returned balance does not guarantee withdrawal eligibility; the withdrawal flow checks eligibility and the executable amount. # Get Withdrawal Status Source: https://docs.relay.link/references/api/get-withdrawal-status get /withdrawals/status Poll the status of a user-triggered withdrawal job [How do programmatic withdrawals work?](/references/protocol/guides/withdrawals) Pass the `jobId` returned by [Request Withdrawal](/references/api/request-withdrawal) as `id`. On most chains a `ready` status includes a `transaction` the owner wallet must broadcast. Status entries are retained for 24 hours. # API Overview Source: https://docs.relay.link/references/api/overview Build payments, swaps, bridges, and cross-chain calls with the Relay API Accept payments, move funds, and execute calls across supported chains with the Relay API. Request a quote, execute the returned transaction steps, and track the result. Start with the [Quickstart](/references/api/quickstart), or explore the endpoints below. For the underlying settlement mechanics and security model, see [Relay Protocol](/references/protocol/overview). 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 [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. Swap, pay, and transact across supported chains with the [Relay API](/references/api/overview). In this walkthrough, you'll bridge ETH from Base to Arbitrum: get a quote, execute the returned steps, and track the result. 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 Create an API key in the [Relay Dashboard](https://dashboard.relay.link).
See [API keys and Rate Limits](/references/api/api-keys) for usage and limits. Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests.
## 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": "Depositing funds to the depository contract", "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 the deposit is indexed, a solver prepares and submits the destination-chain fill. The platform reports progress through the status API. 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 } ``` Deposit-address quotes (`useDepositAddress: true`) are the exception. The fill can run under a new `requestId`, for example when the deposit differs from the quote or the address is reused. When that happens, the quoted `requestId` never reaches `success`, and outside Bitcoin it stays `waiting`. Track those orders by `depositAddress` with [Get Requests](/references/api/get-requests). See [Tracking Transactions](/features/deposit-addresses#tracking-transactions). Poll this endpoint once per second. To avoid polling entirely, configure a [**webhook**](/references/api/api_guides/webhooks) in the Relay Dashboard to push status updates to your backend, or subscribe to Relay’s [**websocket server**](/references/api/api_guides/websockets) for a real-time status stream. ## Status Lifecycle These statuses track the application's transaction, not the protocol's internal settlement and withdrawal lifecycle. Refer to [Get Status](/references/api/get-intents-status-v3) for the full list. * `waiting`: The deposit transaction has not yet been indexed. Execute the deposit steps returned in the quote. * `depositing`: The origin deposit is being processed after submission through the `/execute` API. * `pending`: The deposit was indexed and the request is awaiting completion of the destination fill. * `success`: The destination fill completed and the requested output reached the recipient. A request can also fail or enter a refund flow if the fill cannot complete. See [Refunds](/references/api/api_core_concepts/refunds) for the conditions and recovery paths.
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. # Request Withdrawal Source: https://docs.relay.link/references/api/request-withdrawal post /withdrawals/request Prepare or execute a user-triggered withdrawal from the Relay Depository [How do programmatic withdrawals work?](/references/protocol/guides/withdrawals) Call this endpoint twice per withdrawal: first without `signature` to prepare (returns the `nonce`, validated `amount`, and any `additionalData` to sign), then with the `nonce` and owner `signature` to execute (returns a `jobId`). `chainId` and `ownerChainId` are protocol chain slugs, not numeric ids. These endpoints power [relay.link/withdraw](https://relay.link/withdraw) and are not yet a versioned API surface. # 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. The list below is fetched live from the [Chains API](https://api.relay.link/chains) and stays in sync as new chains are added. # 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 | 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/relay-settlement) 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/relay-settlement) 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/relay-settlement) 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/relay-settlement) 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 use the [Relay platform API](/references/api/overview) for quotes, transaction steps, and status. This guide covers orders that use Relay Protocol settlement; it does not describe every platform route. This page explains how the protocol surfaces in API responses and how to trace an order through the settlement flow. ## orderId vs. requestId Requests using protocol settlement have two identifiers with different purposes. The `requestId` is the Relay-level intent identifier. It covers the overall intent — including any origin or destination swaps — and is what most of the API works in: the [quote api](/references/api/get-quote-v2) returns it as `steps[].requestId`, and status polling, webhooks, and the [requests api](/references/api/get-requests) all key off it. It is not derivable from on-chain data alone. The `orderId` is the protocol-level deposit identifier. It is the `bytes32 id` argument the [Depository](/references/protocol/components/depository) receives in `depositNative` / `depositErc20` (EVM) and `deposit_native` / `deposit_token` (Solana), and it is emitted in the matching deposit event. You can read it directly from on-chain data without an API call, which makes it the right identifier for indexers and merchant-style flows that react to deposits before talking to the Relay API. If you only have an `orderId` from on-chain data, look it up via the [requests api](/references/api/get-requests) to find the corresponding Relay request and its `requestId`. ## Protocol Deposits via the API When a [quote](/references/api/get-quote-v2) includes protocol data, use **`protocol.v2`** to inspect the order and its deposit details. The following is an abbreviated example; API field names and endpoint versions do not identify a protocol rollout version. ```json theme={null} { "protocol": { "v2": { "orderId": "0x1234...abcd", "hubType": "onchain", "paymentDetails": { "chainId": "optimism", "depository": "0xDepositoryAddress...", "currency": "0xUSDCAddress...", "amount": "1000000" } } } } ``` The key fields: * **`orderId`** — A unique identifier for this order, used to track it through the settlement flow * **`paymentDetails.chainId`** — The protocol chain identifier for the origin chain, as a string slug (`"optimism"`, `"arbitrum"`, `"base"`), not the numeric EVM chain ID * **`paymentDetails`** — The Depository address, token, and amount for the deposit Use the actual transaction steps to execute a quote. For checking the order data and deposit transaction together, see [Input Validation](/references/api/api_core_concepts/input-validation). ## 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 it on the origin chain's block explorer, and you can also read the `orderId` directly from the deposit transaction without calling the Relay API. **EVM chains** — the Depository emits one of two events on every deposit: ```solidity theme={null} event RelayNativeDeposit(address from, uint256 amount, bytes32 id) event RelayErc20Deposit(address from, address token, uint256 amount, bytes32 id) ``` Neither event indexes its arguments, so `id` is read from the log's `data` field. The same event fires whether the depositor called the Depository directly or routed through a Relay router (e.g. `ERC20Router`, `Multicaller`), making event parsing the universal path. See [Contracts Addresses](/references/protocol/depository/addresses) for the Depository address on every supported chain. **Solana** — the Depository program encodes the `id` directly in the instruction data passed to `deposit_native` / `deposit_token`. See [Contracts Addresses](/references/protocol/depository/addresses) for the program ID. The layout is: | Offset (bytes) | Length | Field | | - | - | - | | 0 | 8 | Anchor discriminator | | 8 | 8 | `amount` (`u64`, little-endian) | | 16 | 32 | `id` (`[u8; 32]`) | ### 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 `protocol.v2.paymentDetails.depository` and `protocol.v2.paymentDetails.chainId` 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 — the same protocol chain identifier returned in `paymentDetails.chainId` * `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 = "arbitrum"` * `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 = "0xe6a139cd260c28b3a41eafe27d6c008f316db488" ``` You can inspect that balance owner on the Relay Chain Explorer here: * [Relay Chain order address: `0xe6a139cd260c28b3a41eafe27d6c008f316db488`](https://explorer.chain.relay.link/address/0xe6a139cd260c28b3a41eafe27d6c008f316db488) ### 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) # Withdrawing with a Safe Source: https://docs.relay.link/references/protocol/guides/safe-withdrawals Claim and withdraw Relay Hub funds when the recovery owner is a Safe multisig When funds held on the Relay Hub belong to a Safe (multisig) rather than a regular wallet, you can still withdraw them through [relay.link/withdraw](https://relay.link/withdraw) — the Safe signs the withdrawal authorization through the Safe app's message flow instead of a direct wallet signature. This guide walks through the full flow. This flow is verified in production with both 1-of-1 and multi-owner Safes on EVM chains. The oracle validates the Safe's authorization as a contract signature (ERC-1271), so any contract wallet producing a valid ERC-1271 signature can withdraw — this walkthrough covers the Safe flow specifically. For the API-driven flow with a regular wallet, see [Programmatic Withdrawals](/references/protocol/guides/withdrawals). ## Before you start * Open your Safe at [app.safe.global](https://app.safe.global) on the chain where you want to receive funds. * Have a signer wallet (for example MetaMask) that is an owner of the Safe, unlocked and set to that same chain. * Keep two browser tabs open side by side: the Safe app and [relay.link/withdraw](https://relay.link/withdraw). You will switch between them several times. * Enable **blind signing** in the Safe settings. The withdrawal authorization is a raw hash, and the Safe cannot display its contents. * The Safe itself needs no ETH. Gas for the final transaction is paid by the signer wallet. ## Connect the Safe to Relay 1. **Connect your signer wallet to the Safe app.** In the Safe app, click **Connect Wallet** and pick your signer wallet. The account shown in the top right must be one of the Safe owners. 2. **Choose Safe as your wallet on relay.link/withdraw.** Open [relay.link/withdraw](https://relay.link/withdraw), click **Connect**, and pick **Safe** in the wallet list. Do not pick your signer wallet here — Relay would look up the signer's balance instead of the Safe's. 3. **Pair through WalletConnect.** Relay shows a QR code. Click **Copy QR URI**, switch to the Safe app, open the WalletConnect icon in the top bar, and paste the URI. Relay should now show the Safe address in its top right corner. ## Sign the withdrawal authorization 4. **Fill in the withdrawal.** In Relay, open the **Withdraw** tab and select the chain and token. The **Withdraw to Address** field defaults to the connected Safe, which is what you want. Check the available balance, then click **Withdraw**. 5. **Confirm the message in the Safe app.** A **Confirm message** modal appears containing a 32-byte hash — the withdrawal authorization Relay asked your Safe to sign. The blind-signing warning is Safe's standard notice for any raw hash and is expected here. Keep this modal open until every required owner has confirmed; closing it aborts the request and you must restart from step 4. 6. **Switch network if prompted, then sign.** If a **Change your wallet network** banner appears, switch and approve in your signer wallet — the **Sign** button only activates once the signer is on the right network. 7. **Reach the signature threshold.** The Safe **Messages** tab lists the message as **Confirmed** once enough owners have signed. For thresholds above 1, each additional owner opens their own Safe app session, goes to **Transactions → Messages**, opens the message, and clicks **Sign**. The Relay tab keeps waiting until the threshold is reached. ## Submit the on-chain transaction 8. **Submit from Relay.** Once the signature is collected, the Relay progress bar moves to **Submit**. Click **Submit Transaction**. The prepared withdrawal backing this step expires about one hour after it is prepared — this applies to submitting the transaction, not to collecting signatures in the previous steps. If it expires, the funds return to your hub balance and you can start again from step 4. 9. **Review the transaction in the Safe app.** A **Confirm transaction** modal appears. Before signing, check three things: the call is **execute** on **RelayDepository**, your Safe address appears in the decoded request as the recipient of the funds, and the amount matches what you entered in Relay — for token withdrawals the decoded amount is shown in raw base units (for example, a 6-decimal USDC amount has six extra digits). Then sign and approve in your signer wallet. 10. **Execute from the queue if needed.** If the transaction lands in the Safe **Queue** instead of executing immediately, open the Queue tab and click **Execute** on the RelayDepository entry. For thresholds above 1, the other owners confirm here first; any owner can then execute. ## Confirm the funds arrived The Safe shows **Transaction was successful** with the transaction hash, and the block explorer's internal transactions show the transfer from the Relay Depository to your Safe address. ## If the signing prompt never appears * **Wrong network on the signer wallet.** The Safe **Sign** button stays disabled until the wallet is on the Safe's chain. * **Connected wallet is not an owner.** Check the connected account against the Safe's owner list under Settings. * **Threshold above 1.** The first owner signs in the modal; the others sign from **Transactions → Messages** in their own Safe app session while the Relay tab stays open. * **Modal was closed.** Closing the Confirm message modal aborts the request. Start again from step 4. * **Relay connected to the signer wallet instead of the Safe.** Disconnect in Relay and reconnect choosing Safe. The **Withdraw to Address** field must show the Safe address. * **Browser blocking pop-ups.** Allow pop-ups for app.safe.global so the signer wallet can open. # 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 Relay Oracle attests to onchain facts used in Relay Protocol settlement and serves as: * An operationally stateless oracle service. * A Dockerized oracle 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 signatures from peer oracles. 6. The caller can then use those signatures in the wider settlement flow. The oracle service can be run as a single instance or as multiple cooperating instances. ## What the oracle service actually does The oracle service exposes HTTP endpoints for: * Health checks. * Chain discovery. * Status and observability. * Attestation requests. * Swagger-based API documentation. The main utility routes are: * `GET /lives/v1` * `GET /status/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` * `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 oracle 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 oracle service also supports peering across multiple oracle instances to collect signatures. When an incoming request sets `requestPeerSignatures=true` and `PEERS` is configured, the oracle starts its local attestation and every configured peer request concurrently. When `ORACLE_SIGNERS` is also configured, the oracle service returns early when a response group reaches the configured threshold of unique allowlisted signatures. The local oracle is not privileged on that path, so a quorum can be satisfied entirely by remote peers. The oracle service combines responses only when their attestation payloads, signing domains, and non-signature array order match. It skips responses that fail the endpoint schema. During aggregation, the oracle service excludes duplicate signatures, signatures from non-allowlisted signers when `ORACLE_SIGNERS` is configured, and signers missing from any signature array in a multi-message response. To enable quorum aggregation, define an allowlist of trusted signer addresses and an early-return signature target: * `ORACLE_SIGNERS` is a semicolon-separated allowlist of authorized signer addresses. * `ORACLE_SIGNERS_THRESHOLD` is the target number of unique allowlisted signatures for early return (defaults to `1`). It does not prevent the oracle service from returning fewer signatures. Each instance applies its own `ORACLE_SIGNERS` list, so operators are not required to configure identical lists. A signature counts only at instances that list its signer, and the signer authorized onchain decides which signatures settlement accepts. `ORACLE_SIGNERS_THRESHOLD` counts the local oracle's signature along with peer signatures. A threshold of `1` can be met by the local oracle alone, so a 2-of-3 configuration uses `2`. `ORACLE_SIGNERS_THRESHOLD` controls early return but does not enforce quorum. During peer aggregation, Relay Oracle can return the strongest valid partial group with a success response when no group reaches the threshold, and logs the matched and required counts. Callers must verify the unique allowlisted signature count before relying on the response, and operators should alert on quorum-shortfall warnings. For example, a 2-of-3 configuration looks like this: ```sh theme={null} ORACLE_SIGNERS=0x1111111111111111111111111111111111111111;0x2222222222222222222222222222222222222222;0x3333333333333333333333333333333333333333 ORACLE_SIGNERS_THRESHOLD=2 ``` The oracle service fails at startup when `ORACLE_SIGNERS_THRESHOLD` is not an integer or is less than `1`. When `ORACLE_SIGNERS` is configured, the oracle service also fails at startup when: * `ORACLE_SIGNERS_THRESHOLD` exceeds the number of unique entries in `ORACLE_SIGNERS`. * Any entry in `ORACLE_SIGNERS` is not a valid nonzero address. * The local signer address is not included in `ORACLE_SIGNERS`. When `ORACLE_SIGNERS` is omitted, the threshold is ignored for quorum purposes, so a threshold set without an allowlist doesn't enforce anything. The oracle service logs a startup warning when a threshold is set without the allowlist. Without the allowlist, the oracle service waits for the local operation and every configured peer, bounded by `PEER_REQUEST_TIMEOUT_MS`, instead of completing early at a threshold. Only peer responses that match the local unsigned payload are aggregated, and the request fails if the local attestation fails. With an allowlist, a failed local attestation is logged but does not by itself fail the request. The oracle service can return a full or partial response using only allowlisted peer signatures. If the local attestation fails, the request fails only when no valid allowlisted group is available. Peer calls are best-effort attempts. If a peer times out, errors, fails response validation, or returns a payload that does not match any signable group, it is skipped and a warning is emitted. ## Status and observability `GET /status/v1` returns the instance's `status`, build identifiers (`version`, `image`, `gitCommit`), `uptimeSeconds`, lowercase `oracleSigner` address, `peerCount`, and `quorum` configuration. Unavailable information is `null`. `peerCount` includes all configured peers, including unreachable ones. `status` is `ok` when the signer lookup succeeds, or `partial` when it fails or exceeds five seconds. It describes the local signer check, not a test signature or overall peer health. Without a configured signer allowlist, `quorum` is `{ "signers": [], "threshold": null }`. Build identifiers use the first available value in these sequences: * `version`: `VERSION`, build manifest version, package version, `IMAGE_TAG`, then the resolved `gitCommit`. * `image`: `IMAGE_DIGEST`, `IMAGE_TAG`, then the build manifest image. * `gitCommit`: `GIT_COMMIT`, then the build manifest commit. `GET /lives/v1?withReport=true` uses the same version resolution. Add `withPeers=true` to `/status/v1` or `/chains/v1` to check every configured peer. The response includes one `peers` entry per peer, including failed checks. Without this option, the `peers` field is omitted. In `/status/v1?withPeers=true`, each peer entry contains the same status fields plus `url`, `checkResult`, `latencyMs`, `retryCount`, and `httpStatus`. Unavailable fields are `null`, including `quorum` when the peer's configuration is unknown. Peer URLs omit credentials, query parameters, and fragments, and invalid URLs are omitted entirely. `checkResult` is `success` when a valid report is collected, or a failure reason such as `timeout`, `authentication-error`, or `invalid-response`. `latencyMs` measures the successful request, and `httpStatus` records the final request's HTTP status when available. `retryCount` counts retries after transient failures, excluding fallback requests. For older peers, status checks fall back to `/lives/v1?withReport=true`, then `/lives/v1`. Legacy reports supply limited information, with their old `version` value treated as `image` metadata. In `/chains/v1?withPeers=true`, peer entries contain only `url`, `checkResult`, and `chainDifferences`, relative to the top-level local `chains` list: * `local-only`: the chain exists locally but not on the peer. Includes only `id` and `status`. * `peer-mismatch`: the chain exists on both sides. Includes `id`, `status`, and only changed peer values for `vmType` or `depository`. `depository: null` means the peer has no depository configured. * `peer-only`: the chain exists only on the peer. Includes `id`, `status`, `vmType`, and `depository` when present. `chainDifferences: []` means the chain sets match, while `null` means the comparison failed. A successful comparison can still report differences. Peer checks run concurrently, honor `PEER_REQUEST_TIMEOUT_MS` and `PEER_RESPONSE_MAX_BYTES`, retry transient failures, and stop when the caller disconnects. Status responses and chain responses with `withPeers=true` set `cache-control: no-store`. ## 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. * Enforce access control at an API gateway or reverse proxy when public access is not intended. * Use HTTPS for any publicly reachable deployment. * Use dedicated peer API keys to authenticate requests between oracle instances. * 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 service on a platform that can run a Docker container and expose it over HTTP. 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 oracle 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=10000 ORACLE_SIGNERS=0x1111111111111111111111111111111111111111;0x2222222222222222222222222222222222222222;0x3333333333333333333333333333333333333333 ORACLE_SIGNERS_THRESHOLD=2 ``` 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` defines the keys accepted on status, chains, and attestation routes. At least one key must be configured for the oracle to start. * `PEER_REQUEST_TIMEOUT_MS` defaults to `10000`. * `ORACLE_SIGNERS` is the semicolon-separated allowlist used to count unique oracle signatures during quorum aggregation. * `ORACLE_SIGNERS_THRESHOLD` sets the target number of unique allowlisted signatures for early return. It defaults to `1`, must be an integer of at least `1`, and is validated against the size of `ORACLE_SIGNERS` when that allowlist is configured. It does not prevent a successful response with fewer signatures. 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 Status, chains, and attestation routes require a valid `x-api-key`. Requests to these routes without a valid key return `401 Unauthorized`. The health and documentation routes do not require an API key: * `/` * `/documentation` and its subpaths * `/lives/v1`, including `withReport=true` Registered peers have no automatic exemption. Each peer request must carry a key accepted by the receiving oracle. `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 to authenticate peer requests. 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. Configure API keys for trusted callers and any additional edge access controls. 6. Expose the oracle 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 oracle service Build and run the oracle 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 oracle 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 the following. Include a valid `x-api-key` for status, chains, and attestation requests: * `GET /lives/v1` returns `{"status":"ok"}`. * `GET /status/v1` returns `status: "ok"`, the expected `version`, `image`, and `gitCommit` for the running build, and your local `oracleSigner` address. * `GET /chains/v1` returns the chains expected for your selected `ENVIRONMENT`. * `GET /documentation` loads the Swagger UI. * Your logs show the oracle service started successfully. * Your logs show the signer address you expect, and that address matches one of the entries in `ORACLE_SIGNERS` if the allowlist is configured. Then test at least one real attestation flow with a valid `x-api-key`. ## Operational monitoring At minimum, monitor: * Container restarts. * `GET /status/v1` reporting `status: "partial"` and `oracleSigner: null`. * Request latency. * Error rate by endpoint. * Unexpected `401 Unauthorized` responses. * Peer timeout, peer mismatch, and quorum-shortfall warnings. * Peer `checkResult` drift over time via `GET /status/v1?withPeers=true` and `GET /chains/v1?withPeers=true`. * 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. # Programmatic Withdrawals Source: https://docs.relay.link/references/protocol/guides/withdrawals Reclaim depository funds programmatically using the public withdrawal endpoints ## Overview Sometimes a deposit can't be filled: it was flagged by screening, sent in the wrong currency or on the wrong chain, or the order failed after the deposit landed. Those funds sit in the [Depository](/references/protocol/components/depository) until the recovery owner reclaims them. [relay.link/withdraw](https://relay.link/withdraw) is a frontend for the withdrawal flow, and your app can drive the same flow directly through the API. This is useful if you want to handle recovery inside your own app instead of sending users to the self-serve UI, or run it programmatically on a server. It works especially well when you control the recovery owner's wallet and can complete the signing flow yourself. These endpoints power the Relay withdrawal UI. They are stable enough to build against, but they are not yet a versioned API surface, so request and response shapes may change without a deprecation cycle. No API key is required for these withdrawal endpoints. The recovery owner (the **`owner`**) authorizes withdrawals with a wallet signature; reading balances requires no signature. The base URL is `https://api.relay.link`. For deposit-address flows, a configured **`recoveryAddress`** is recorded as the protocol depositor. It can differ from the wallet that sent the funds, so use that recovery address as the owner when preparing and signing a withdrawal. | Endpoint | Purpose | | - | - | | [`POST /withdrawals/attest-deposit`](/references/api/attest-deposit) | Claim a stuck or flagged deposit so its balance becomes withdrawable | | [`GET /withdrawals/balances`](/references/api/get-withdrawal-balances) | List balances already credited to a recovery owner on the hub | | [`POST /withdrawals/request`](/references/api/request-withdrawal) | Prepare a withdrawal (no signature), then execute it (with signature) | | [`GET /withdrawals/status`](/references/api/get-withdrawal-status) | Poll the withdrawal job to completion | ## Supported Chains and Signers User-triggered withdrawals are supported on EVM chains, Hyperliquid, Solana, Tron, TON, and Bitcoin. Lighter withdrawals are rejected with a `400`. Recovering Lighter funds currently goes through [support](/resources/support). The **`owner`** must be the recovery owner recorded by the protocol. An EOA signs the digest directly with its own key. A smart contract wallet authorizes through a contract signature instead: the oracle validates ERC-1271 signatures, so any contract that produces a valid `isValidSignature` result can withdraw. The step-by-step flow below covers direct EOA signatures. For a Safe, use the withdrawal UI, which drives the contract-signature flow end to end — see [Withdrawing with a Safe](/references/protocol/guides/safe-withdrawals). Other ERC-1271 wallets don't have a documented flow yet; contact [support](/resources/support) if you need to withdraw with one. Exchange-custodied addresses and other signers that can't produce either kind of signature can't complete this flow; if the owner's key is inaccessible, contact [support](/resources/support). ## Withdrawal Flow ### Step 1: Check Eligibility Look up the request with the [requests API](/references/api/get-requests): ```bash theme={null} curl "https://api.relay.link/requests/v3?id={requestId}" ``` To enumerate candidates for a wallet instead of checking a single request, query `GET /requests/v3?user={wallet}&status=failure` and filter on the protocol object. In each returned request, the `protocol` object tells you what you need: * **`protocol.isWithdrawable`** — `true` means the deposit is recoverable via this flow * **`protocol.deposit.origin.depositor`** — the recovery owner that must sign (the `owner` below) * **`protocol.deposit.origin`** — also carries the deposit's `chainId`, `currency`, `amount`, and `transactionId` for the next steps ### Step 2: Attest the Deposit If the deposit hasn't been claimed onto the protocol hub yet, attest it: ```bash theme={null} curl -X POST "https://api.relay.link/withdrawals/attest-deposit" \ -H "Content-Type: application/json" \ -d '{ "chainId": 8453, "transactionId": "0xDEPOSIT_TX_HASH" }' ``` Here, `chainId` is the numeric Relay chain id of the deposit chain. Balance discovery also uses numeric chain ids, while withdrawal preparation and execution use protocol slugs. A successful attestation returns `{ "success": true }`. The call is idempotent and safe to retry. A `503` (for example "Transaction not yet finalized" or "Recovery in progress") means wait and retry. A `400` is terminal for that transaction. ### Discover Credited Balances (optional) Once funds are credited to the recovery owner's account on the hub, you can enumerate those balances: ```bash theme={null} curl "https://api.relay.link/withdrawals/balances?recoveryAddress=0xRECOVERY_ADDRESS&ownerChainId=8453&limit=20" ``` * **`recoveryAddress`** — the recovery owner's address; for deposit-address flows, use the configured recovery address, which may differ from the wallet that sent the funds * **`ownerChainId`** — the numeric Relay chain id that identifies the recovery owner's protocol account (the same EVM address on different owner chains derives different hub accounts) * **`limit`** — page size, default `20`, max `50` * **`continuation`** — opaque cursor returned by the previous page; keep calling until it is `null` Each entry in `balances` includes the raw `amount` (integer string), an optional `currency` block (`chainId`, `address`, `name`, `symbol`, `decimals`), plus `amountFormatted` and `amountUsd` when metadata and price are available. `currency` (and the enriched fields) can be `null` when the asset can't be verified against public solver configuration — the raw `hubTokenId` and `amount` are still returned. This endpoint is public; an optional `x-api-key` uses the standard rate-limit tiers. It is a read-only discovery step — it does not attest, reserve, enqueue, or execute anything. Use it to build a picklist before prepare; a null `currency` is not a usable withdrawal target. A returned balance does not guarantee that a withdrawal is supported or permitted. Discovery does not check withdrawal support, compliance restrictions, or signer eligibility; the withdrawal flow checks eligibility and the executable amount. ### Step 3: Prepare Call the request endpoint without a signature to get signing parameters: ```bash theme={null} curl -X POST "https://api.relay.link/withdrawals/request" \ -H "Content-Type: application/json" \ -d '{ "chainId": "base", "currency": "0x0000000000000000000000000000000000000000", "amount": "211891421", "ownerChainId": "base", "owner": "0xRECOVERY_ADDRESS", "recipient": "0xRECIPIENT_ADDRESS" }' ``` * **`chainId`** / **`ownerChainId`** — protocol chain slugs (`base`, `bnb`, `solana`, `bitcoin`), not numeric ids. Read them from `chains[].protocol.v2.chainId` in the [chains API](/references/api/get-chains). * **`currency`** — the token address on the withdrawal chain, or the zero address for the native token. For Bitcoin use the native BTC identifier (see [Bitcoin Support](/references/api/api_guides/bitcoin)). * **`amount`** — raw base units as an integer string * **`recipient`** — where the funds go. May differ from `owner`. The response looks like `{ "nonce": "0x…", "amount": "211891421", "additionalData": { … } }`. Use the returned `amount` in every following step — it's validated against the available hub balance, and requesting more than is available returns a `400`. `additionalData` may be absent; when present, pass it through untouched. The `nonce` is deterministic per one-minute window for a given (chain, owner, currency, recipient) tuple and expires quickly, so prepare, sign, and execute in one sitting. If the job later reports `expired`, restart from this step. On Bitcoin, prepare additionally returns `additionalData["bitcoin-vm"]` describing the fee side (`allocatorUtxos`, `feeUtxos`, `feeChangeAddress`, `feeRate`) plus a `feeAuthorization` object (`validUntil` and a `signature`). Pass the entire `additionalData` back verbatim on execute — a tampered, missing, or expired authorization is rejected with `400 BITCOIN_FEE_AUTHORIZATION_INVALID`. The authorization is valid for one hour; if it expires before the withdrawal is submitted, re-prepare. Same-minute retries return the cached prepare response; a change to `recipient` or `amount` within the same minute returns `429`. ### Step 4: Sign the Digest Build a SHA-256 digest over the stable-stringified request and sign it with the owner wallet: ```typescript theme={null} import stringify from "json-stable-stringify"; import { sha256, toHex } from "viem"; const digest = sha256( toHex( stringify({ operation: "withdrawal", chainId, // protocol slug, as sent in the prepare step currency, amount, // the amount RETURNED by prepare ownerChainId, owner, recipient, nonce, // returned by prepare additionalData, // exactly as returned by prepare; omit if absent })! ) ).slice(2); // hex digest, no 0x prefix ``` The digest includes `operation: "withdrawal"`, the `nonce`, and `additionalData` exactly as prepare returned them. Key order doesn't matter (`json-stable-stringify` sorts keys), but changing any value — including signing your original `amount` instead of the returned one — produces an invalid signature. Sign the digest with the owner key and submit the signature as 0x-prefixed hex: | Chain family | How to sign | | - | - | | EVM / Hyperliquid | `personal_sign` over the raw digest bytes — `walletClient.signMessage({ message: { raw: '0x' + digest } })` | | Solana | `signMessage(digest)` (the digest as a UTF-8 string), then hex-encode the ed25519 signature | | Tron | `tronWeb.trx.signMessageV2(digest)` | | TON | TonConnect `signData({ type: "text", text: digest })`, then hex-encode the signature | | Bitcoin | Sign the digest with the owner's BTC key as a Bitcoin message signature and submit it as 0x-prefixed hex | On TON, the wallet's `signData` response also includes a `timestamp` and `domain`. Submit them in the execute call as `additionalData["ton-vm"] = { timestamp, domain }`. They are needed for verification but are not part of the signed digest above. ### Step 5: Execute Repeat the same call with the `nonce`, `additionalData`, and `signature` added: ```bash theme={null} curl -X POST "https://api.relay.link/withdrawals/request" \ -H "Content-Type: application/json" \ -d '{ "chainId": "base", "currency": "0x0000000000000000000000000000000000000000", "amount": "211891421", "ownerChainId": "base", "owner": "0xRECOVERY_ADDRESS", "recipient": "0xRECIPIENT_ADDRESS", "nonce": "0xNONCE_FROM_PREPARE", "additionalData": {}, "signature": "0xOWNER_SIGNATURE" }' ``` Success returns `{ "jobId": "…", "status": "processing" }`. Only one withdrawal per (chain, owner, currency) balance can be in flight at a time — a second request returns a `409` with code `WITHDRAWAL_IN_PROGRESS` and the `existingJobId` to poll instead. ### Step 6: Poll for Status ```bash theme={null} curl "https://api.relay.link/withdrawals/status?id={jobId}" ``` | Status | What to do | | - | - | | `processing`, `initiating`, `attesting` | Keep polling (\~5s; back off to \~15s on a `429`) | | `ready` | The response includes a `transaction` — broadcast it from the owner wallet (next step), then keep polling | | `executed` | Done — the funds are with the recipient | | `expired` | The nonce or job expired and the funds returned to the hub balance. Restart from the prepare step. | | `failed` | See `reason`, then restart from the prepare step | Status entries are retained for 24 hours. An unknown or evicted id reports `processing`, so don't poll ids older than a day expecting a terminal state. ### Step 7: Broadcast the Transaction `ready` is not done. On TON the solver broadcasts for you and the status moves to `executed` on its own, but on every other supported chain, `ready` hands you a `transaction` object that the owner wallet must sign and broadcast on-chain, paying its own gas. Depending on the chain this is an EVM transaction request, Solana instructions, or a Tron `TriggerSmartContract` payload. A job stuck at `ready` because the transaction was never broadcast is the most common integration mistake. After broadcasting, keep polling until the status reaches `executed`. If the owner is on a different VM than the withdrawal chain, the returned transaction is built for the `recipient` to broadcast instead. ## Caveats * Attestation and balance discovery use numeric chain ids. Withdrawal preparation and execution use protocol slugs (`base`, not `8453`). * Sign and execute with the `amount` that prepare returned, not your original input. * `ready` is not done — broadcast the returned transaction and keep polling. * Move quickly between prepare, sign, and execute. The nonce is short-lived; on `expired`, re-prepare. * One withdrawal at a time per (chain, owner, currency). A second in-flight request returns a `409` with the existing `jobId`, and the balance unlocks when the job reaches a terminal state. Building with an AI assistant? Use the **Copy page** button at the top of this page to hand it to your agent, or see [Integrating using AI](/resources/developing-with-ai) to connect the Relay docs MCP server and llms.txt. # How It Works Source: https://docs.relay.link/references/protocol/how-it-works End-to-end walkthrough of the settlement protocol flows Relay Protocol settles cross-chain intents. Users express what they want (e.g., bridge 1 ETH from Optimism to Base), and solvers fill those orders using their own capital. The protocol then settles solvers — verifying fills and reimbursing them from the user's escrowed deposit. This page describes the settlement protocol, not every route available through the [Relay platform](/how-relay-works). Each diagram follows one order and its solver; solvers are participants, not a single protocol-wide operator. 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 agrees to an order** — The user specifies what they want (e.g., bridge 1 ETH from Optimism to Base). An application obtains a solver's commitment; quoting and order distribution sit outside the settlement protocol. 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** — The Oracle returns a signed EIP-712 attestation. The Oracle contract verifies an authorized signer; when a signer contract is used, that contract enforces its configured threshold. See the [Oracle trust model](/references/protocol/security#oracle). 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 Protocol Source: https://docs.relay.link/references/protocol/overview A crosschain settlement protocol optimized for speed, cost, and reliability Relay Protocol coordinates settlement for cross-chain transactions. Its settlement system, Relay Settlement, connects user deposits, fill verification, balance accounting, and withdrawals. A solver fills an order using its own capital; the protocol verifies the fill and accounts for settlement. The [Relay platform](/what-is-relay) provides quotes, routing, and application tooling. This section explains the protocol's settlement mechanics and security model. ## Design Goals Relay Settlement is optimized on five 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 Relay Settlement handles settlement, not quoting or order distribution. It does not run an orderflow auction. ## Verification and Security A solver fills an order, the [Oracle](/references/protocol/components/oracle) attests the deposit and fill, and the [Allocator](/references/protocol/components/allocator) authorizes withdrawals. The protocol's [security model](/references/protocol/security) describes its deployed signers, withdrawal permissions, and [Security Council](/references/protocol/components/security-council). Review these verification and recovery mechanisms before integrating. ## 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 records fills in real time on the Hub. A solver can batch authorized withdrawals to balance transaction costs against capital needs. ## 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 separates deposits, fill verification, balance accounting, and withdrawal authorization. User deposits remain in Depository contracts until an authorized withdrawal; solvers fill orders using their own capital. 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 * **Deposit duration** — Funds typically remain in the contracts 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-vaults/blob/main/docs/report-cantinacode-relay-protocol-rereview-0310.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/relay-settlement/blob/main/docs/audits/Relay-Settlement-Protocol-Zellic-Audit.pdf) | | April 2026 | Oracle | [Zellic](https://www.zellic.io/) | [View Report](https://github.com/relayprotocol/relay-settlement/blob/main/docs/audits/Relay%20Protocol%20Oracle%20-%20Zellic%20Audit%20Report.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/relay-settlement) — 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 Relay Vaults are being wound down and new deposits are no longer supported. To exit an existing position, see [Withdrawing from Relay Vaults](/references/protocol/vaults/guides/withdrawing). In this guide, we cover how liquidity providers (LPs) interact with Relay Vaults. ## As a Liquidity Provider (LP) As an LP, you can withdraw your funds and track your balances through the [Relay UI](https://relay.link/vaults) until December 31, 2026. After that date, follow [Withdrawing from Relay Vaults](/references/protocol/vaults/guides/withdrawing) to withdraw directly onchain. You can also interact directly with the smart contracts or using block explorers like Etherscan and Blockscout. # Withdrawing from Relay Vaults Source: https://docs.relay.link/references/protocol/vaults/guides/withdrawing Withdraw your liquidity from Relay Vaults directly onchain, without the Relay UI Relay Vaults are being wound down. New deposits are no longer supported: do not deposit into the vaults. The contracts themselves still accept deposits, so anything sent to them must be withdrawn the same way as any other position. Relay Vaults are [ERC4626](https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/) contracts, so you can withdraw by calling the vault contract directly, up to the liquidity currently available (see [Limits](#limits)). You do not need the Relay UI, the Relay API, or any other Relay service, only a wallet and an RPC endpoint or block explorer. The [withdrawal UI](https://relay.link/vaults) stays available until **December 31, 2026**. After that date, use the steps on this page. ## Vault addresses Both vaults hold WETH and issue `WETH-REL` shares with 18 decimals. | Chain | Chain ID | Vault | Asset (WETH) | Native gateway | | - | - | - | - | - | | Ethereum | `1` | `0x57b68c4ea221ee8da6eb14ebdfccee5177567771` | `0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` | `0xed8a2a23bf9d6818651527daaf3bf4ffeb53945f` | | Arbitrum | `42161` | `0xc7273772b87860097373d91b3880fd4cc61fba2d` | `0x82af49447d8a07e3bd95bd0d56f35241523fbab1` | `0x67defe8d90c8cd013fd527669e76e0be420f1208` | ABIs are published in the [`@relay-vaults/abis`](https://www.npmjs.com/package/@relay-vaults/abis) package (`RelayPool`, `RelayPoolNativeGateway`). See the [RelayPool](/references/protocol/vaults/contracts/RelayPool) and [RelayPoolNativeGateway](/references/protocol/vaults/contracts/RelayPoolNativeGateway) references for the full interfaces. ## Check your balance Read these view functions on the vault contract, from a block explorer's **Read Contract** tab or any RPC client: * **`balanceOf(you)`** — your share balance. * **`convertToAssets(shares)`** — the WETH value of those shares. * **`yieldPool()`** — the yield pool that holds the vault's liquid WETH. Then call **`maxWithdraw(vault)`** on the yield pool address. This is the WETH the vault can pay out right now, across all LPs. If `convertToAssets(balanceOf(you))` is at or below the yield pool's `maxWithdraw(vault)`, you can withdraw your whole position in one transaction. Otherwise, convert that WETH amount to shares by calling `convertToShares(amount)` on the **vault**, not the yield pool, and redeem slightly fewer shares than the result: the limit is shared by all LPs and can move before your transaction lands. The example below keeps a 0.1% margin. See [Limits](#limits). If you have trouble withdrawing, reach out to [Relay support](/resources/support). ## Withdraw as WETH Withdrawing as WETH takes one transaction on the vault and needs no approval. 1. Pick the shares to redeem: your `balanceOf(you)`, or the liquidity cap in shares from [Check your balance](#check-your-balance) if that is lower. 2. Call `redeem(shares, receiver, owner)` with `receiver` set to the address that should receive WETH, and `owner` set to your address. To withdraw an exact WETH amount instead, call `withdraw(assets, receiver, owner)` with `assets` at or below both your position's value and the yield pool's `maxWithdraw(vault)`. ## Withdraw as native ETH The `RelayPoolNativeGateway` redeems your shares, unwraps the WETH, and sends ETH to the receiver. It takes two transactions because the gateway redeems on your behalf and needs a share allowance. 1. Pick the shares to redeem: your `balanceOf(you)`, or the liquidity cap in shares from [Check your balance](#check-your-balance) if that is lower. On the **vault**, call `approve(gateway, shares)`. 2. On the **gateway**, call `redeem(vault, shares, receiver, minAssetsOut)`. Set `minAssetsOut` slightly below `previewRedeem(shares)` to protect against rounding. The gateway also exposes `withdraw(vault, assets, receiver, maxSharesIn)` for an exact ETH amount. Approve at least `maxSharesIn` shares first. ## Example The script below redeems as many of your shares as current liquidity allows, as WETH, using [viem](https://viem.sh). To withdraw on Arbitrum, import `arbitrum` from `viem/chains`, use it in place of `mainnet`, and set `VAULT` to the Arbitrum vault address. ```typescript theme={null} import { createPublicClient, createWalletClient, http, parseAbi, formatEther } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains' const VAULT = '0x57b68c4ea221ee8da6eb14ebdfccee5177567771' const abi = parseAbi([ 'function balanceOf(address) view returns (uint256)', 'function yieldPool() view returns (address)', 'function maxWithdraw(address) view returns (uint256)', 'function convertToShares(uint256) view returns (uint256)', 'function previewRedeem(uint256) view returns (uint256)', 'function redeem(uint256 shares, address receiver, address owner) returns (uint256)', ]) // Never paste a funded private key into a file you might commit const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const publicClient = createPublicClient({ chain: mainnet, transport: http() }) const walletClient = createWalletClient({ account, chain: mainnet, transport: http() }) // 1. Read your shares and the vault's available liquidity const balance = await publicClient.readContract({ address: VAULT, abi, functionName: 'balanceOf', args: [account.address] }) const yieldPool = await publicClient.readContract({ address: VAULT, abi, functionName: 'yieldPool' }) const liquid = await publicClient.readContract({ address: yieldPool, abi, functionName: 'maxWithdraw', args: [VAULT] }) // 2. Redeem your full balance, or only the shares current liquidity covers. // Keep a 0.1% margin: other LPs share the same liquidity and it can move before inclusion. const liquidShares = await publicClient.readContract({ address: VAULT, abi, functionName: 'convertToShares', args: [liquid * 999n / 1000n] }) const shares = balance < liquidShares ? balance : liquidShares if (shares === 0n) { throw new Error('Nothing to redeem right now. If you still hold shares, retry when liquidity returns.') } const assets = await publicClient.readContract({ address: VAULT, abi, functionName: 'previewRedeem', args: [shares] }) console.log(`Redeeming ${formatEther(shares)} shares for ~${formatEther(assets)} WETH`) // 3. Redeem to your own address const hash = await walletClient.writeContract({ address: VAULT, abi, functionName: 'redeem', args: [shares, account.address, account.address], }) await publicClient.waitForTransactionReceipt({ hash }) ``` You can also use the `pool:withdraw` Hardhat task from the [relay-vaults repository](https://github.com/relayprotocol/relay-vaults/tree/main/smart-contracts). Set `DEPLOYER_PRIVATE_KEY` to the LP wallet's key and pass the vault address explicitly, for example `yarn run hardhat pool:withdraw --network ethereum --pool 0x57b68c4ea221ee8da6eb14ebdfccee5177567771`. When prompted, enter an amount at or below the yield pool's `maxWithdraw(vault)`: `all` redeems your entire balance and reverts while liquidity is short. ## Limits * **Liquidity cap** — withdrawals are limited to the yield pool's `maxWithdraw(vault)`. Funds still outstanding in bridges cannot be withdrawn until they return to the vault, and high utilization in the underlying lending market (Aave) can also lower the limit temporarily. If liquidity is short, withdraw what is available and keep the remaining shares: they stay redeemable once liquidity returns. The limit is shared by all LPs, so if a withdrawal at the limit reverts with `ERC4626: withdraw more than max`, retry with a slightly smaller amount. If you cannot withdraw your full position, reach out to [Relay support](/resources/support). * **No cooldown** — withdrawals take effect in the same transaction. There is no lock-up or withdrawal queue. * **No pause** — the vault contracts have no pause function and no withdrawal allowlist. The vault owner can still move the vault's assets to a different yield pool with `updateYieldPool`; see [RelayPool](/references/protocol/vaults/contracts/RelayPool). * **Gas** — you pay gas in ETH on the vault's chain (Ethereum or Arbitrum). The native ETH path costs two transactions. * **Yield** — yield streams into the vault over a streaming period (currently 7 days), so `convertToAssets` can increase slightly between reads. # Overview Source: https://docs.relay.link/references/protocol/vaults/overview Introducing Relay Vaults Relay Vaults are being wound down and new deposits are no longer supported. To exit an existing position, see [Withdrawing from Relay Vaults](/references/protocol/vaults/guides/withdrawing). 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. 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. ## 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. LPs can withdraw through the [**user interface**](https://relay.link/vaults/) until December 31, 2026, or directly through the smart contracts (see [Withdrawing from Relay Vaults](/references/protocol/vaults/guides/withdrawing)). 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. To exit a position on Ethereum Mainnet or Arbitrum, follow [Withdrawing from Relay Vaults](/references/protocol/vaults/guides/withdrawing). # 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 { data, isLoading, error } = useExecutionStatus( 'https://api.relay.link', { requestId: '0x6a6cab2695f2dc4a67539d971760764edac9e52b0a2219a5fbb3faf2f04ac7c2' } ) const { status, details, inTxHashes, txHashes, time, originChainId, destinationChainId } = data ?? {} ``` ## 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 `useRequests` and `queryRequests` read from `GET /requests/v3`, which requires a Relay API key sent via the `x-api-key` header. Because these hooks run in the browser, never ship your API key to the client — point **`baseApiUrl`** at a server-side [proxy](/references/api/api-keys#proxy-api) that injects `x-api-key`. ## Parameters | Parameter | Description | Required | | - | - | - | | **baseApiUrl** | Base API URL for the Relay API. `/requests/v3` requires an `x-api-key`, so point this at a server-side [proxy](/references/api/api-keys#proxy-api) that injects the key rather than at `https://api.relay.link` directly. | ❌ | | **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-v2) | ❌ | | **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-v2). ## 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 Add swaps and bridging to your app with the SDK, React hooks, and UI components Add swaps and bridging to your app with RelayKit. Use the SDK and React hooks to build your own experience, or embed ready-made UI components to get started. RelayKit handles fetching quotes, executing transaction steps, and tracking requests.
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` | ❌ | | **disableCapabilitiesCheck** | Skip `wallet.getCapabilities` calls used for EIP-5792 atomic-batch detection and smart-wallet detection. Set this for wallets with a broken `getCapabilities` implementation that hangs or never resolves. When true, execution falls back to sequential transactions. | ❌ | ## 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/relayprotocol/relay-kit/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` | ❌ | | **disableCapabilitiesCheck** | Skip `wallet.getCapabilities` calls used for EIP-5792 atomic-batch detection and smart-wallet detection. Set this for wallets with a broken `getCapabilities` implementation that hangs or never resolves. When true, execution falls back to sequential transactions. | ❌ | ## Example ```typescript theme={null} import { getClient, Execute, getQuote } from "@relayprotocol/relay-sdk"; import { useWalletClient } from 'wagmi' ... const { data: 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 cross-chain 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. Starting **October 2, 2026**, `POST /quote/v2` will require a valid API key on every request. Pass your key in the `x-api-key` header — see [API keys and Rate Limits](/references/api/api-keys#how-to-use-an-api-key). Requests that include a `referrer` already require the key today. `quote/v2` remains fully supported for authenticated requests. ## 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 address depositing funds on the origin chain. Must be a valid address for the origin chain's virtual machine (e.g. a Solana address for a Solana origin, an EVM address for an EVM origin). Required unless `includeDefaultParameters` is true. | ✅ (❌ if `includeDefaultParameters` is true) | | **recipient** | The address receiving funds on the destination chain. Must be a valid address for the destination chain's virtual machine. Required unless `includeDefaultParameters` is true. | ✅ (❌ 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/relayprotocol/relay-kit/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). | ❌ | | **disableCapabilitiesCheck** | Skip `wallet.getCapabilities` calls used for EIP-5792 atomic-batch detection and smart-wallet detection. Set this for wallets with a broken `getCapabilities` implementation that hangs or never resolves. When true, execution falls back to sequential transactions. | ❌ | On cross-virtual-machine routes, `user` and `recipient` live on different chains and use different formats. For example, a Solana → Base quote takes a Solana address as **`user`** and an EVM address as **`recipient`**. Supplying an address in the wrong format returns `Invalid address
for chain `. ## 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 [Step Execution](/references/api/api_core_concepts/step-execution) for more information on executing quotes through the platform. # 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/relayprotocol/relay-kit/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` `tvm` `lvm` `tonvm`) | ✅ | | **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. [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. [Lighter Adapter](https://github.com/relayprotocol/relay-kit/tree/main/packages/relay-lighter-wallet-adapter): This adapter gives the SDK the ability to deposit to [Lighter](https://lighter.exchange/) from any Relay-supported chain. It owns the full Lighter session lifecycle — resolving the user's Lighter account index, generating an in-memory API key, and registering it via `changeApiKey` on first transfer. Integrators who already run their own Lighter session (backend-provisioned key, wallet-level integration) can pass a pre-built `signerClient` to bypass the bootstrap entirely. [TON Adapter](https://github.com/relayprotocol/relay-kit/tree/main/packages/relay-ton-wallet-adapter): This adapter gives the SDK the ability to submit transactions on the TON blockchain. The user's key stays in their wallet, so the adapter never signs — you pass a `sendTransaction` callback (for example wrapping Dynamic's TON connector or TonConnect UI) that signs and broadcasts the request, while the adapter handles request building and on-chain confirmation. Confirmation reads use a read-only [`@ton/ton`](https://github.com/ton-org/ton) `TonClient` pointed at the TON chain's `httpRpcUrl` from the Relay client's chain configuration; to use a custom endpoint (e.g. a keyed provider in production), override that chain's `httpRpcUrl` when configuring the SDK client rather than passing it to the adapter. TON support requires the `@ton/core` and `@ton/ton` peer dependencies in addition to `viem` and `@relayprotocol/relay-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 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 Lighter theme={null} import { getClient, getQuote } from '@relayprotocol/relay-sdk' import { adaptLighterWallet } from '@relayprotocol/relay-lighter-wallet-adapter' import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains' // Lighter deposits are authorized by an L1 (EVM) signature, so the adapter // only needs the user's connected EVM address and a signMessage callback. const walletClient = createWalletClient({ chain: mainnet, transport: custom(window.ethereum!) }) const [address] = await walletClient.requestAddresses() const adaptedWallet = adaptLighterWallet({ l1Address: address, signL1Message: (message) => walletClient.signMessage({ account: address, message }) }) const options = ... // define this based on getQuote options (destinationChainId: 3586256, recipient: ) const quote = await getClient().actions.getQuote(options) getClient().actions.execute({ quote, wallet: adaptedWallet, onProgress: ({ steps, fees, breakdown, currentStep, currentStepItem, txHashes, details }) => { // custom handling } }) ``` ```typescript TON theme={null} import { getClient, Execute, getQuote } from "@relayprotocol/relay-sdk" import { adaptTonWallet } from '@relayprotocol/relay-ton-wallet-adapter' // The user's key lives in their TON wallet, so the adapter never signs. You // provide a sendTransaction callback that forwards the TonConnect-style request // to your wallet provider (e.g. Dynamic's TON connector or tonConnectUI), signs // + broadcasts it, and returns the result. The adapter accepts a signed // external-message { boc } (what most TonConnect wallets return) or a // { transactionHash }. The confirmation RPC is read from the TON chain's // httpRpcUrl in your Relay client config — there is no adapter-level endpoint. const adaptedWallet = adaptTonWallet(walletAddress, async (request) => { const { boc } = await wallet.sendTransaction(request) return { boc } }) 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 }, ... }) ``` ### Viem adapter options `adaptViemWallet` accepts an optional second argument for adapter-level configuration: | Property | Description | | - | - | | **disableCapabilitiesCheck** | Skip `wallet.getCapabilities` calls used for EIP-5792 atomic-batch detection and smart-wallet detection. Set this for wallets with a broken `getCapabilities` implementation that hangs or never resolves. When true, execution falls back to sequential transactions. | ```typescript theme={null} import { adaptViemWallet } from "@relayprotocol/relay-sdk"; const adaptedWallet = adaptViemWallet(walletClient, { disableCapabilitiesCheck: true, }); ``` The same flag is available directly on `getQuote`, `execute`, and `claimAppFees` when you pass a raw viem `WalletClient` — the SDK forwards it to `adaptViemWallet` internally. # 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 Create the global Relay client used by every SDK action 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 { mainnet } from "viem/chains"; 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([mainnet]); 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 ( ) } ``` The widgets read transaction history from `GET /requests/v3`, which requires a Relay API key. Set **`baseApiUrl`** to a server-side [proxy](/references/api/api-keys#proxy-api) that injects `x-api-key` — do not pass the key from the browser. When `baseApiUrl` points directly at the Relay API, `RelayKitProvider` logs a client-side warning. 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 | | - | - | - | | codexConfig.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 [Codex docs](https://docs.codex.io) on how to get an API key. | ❌ | | codexConfig.apiBaseUrl | The base url for the Codex GraphQL API. If omitted, the default `https://graph.codex.io` is used. 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 Codex 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. | ❌ | | acknowledgeApiKeyExposure | Silences the client-side warnings shown when **`baseApiUrl`** points directly at the Relay API. `GET /requests/v3` requires an API key, so production integrations should proxy requests through their own backend and never ship the key to the browser. Set to `true` only if you understand the exposure (e.g. a public, rate-limited key). | ❌ | 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/relayprotocol/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/relayprotocol/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/relayprotocol/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 expandable theme={null} import { adaptViemWallet } from '@relayprotocol/relay-sdk' 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/relayprotocol/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 Stream transaction statuses for Relay transactions. # 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. # Support Source: https://docs.relay.link/resources/support # Supported Chains Source: https://docs.relay.link/resources/supported-chains # Bug Bounty Program Source: https://docs.relay.link/security/bounties How Relay classifies and rewards vulnerability reports. Relay runs a bug bounty program to make responsible disclosure financially rational for security researchers. Rewards scale with how much damage a bug can cause and how feasible it is to exploit in practice. We run monitoring and circuit breakers across the stack, so the likelihood tiers and modifiers reflect real response capacity — not a hypothetical one. This framework applies across Relay's protocol and services. Relay Vaults is a separate protocol and is not covered by this program (see [Out of scope](#out-of-scope)). ## How payouts are calculated Every valid report goes through five steps: 1. Classify **severity** (S1–S4) — how bad is the damage if exploited? 2. Classify **likelihood** (L1–L3) — how feasible is exploitation in practice? 3. Read the **base payout** from the matrix. 4. Apply **modifiers** (×0.5 to ×1.5) from the fixed list. 5. Round to the nearest \$500 (ties round up). **Floor: \$500. Cap: \$100,000.** ### Severity Severity captures what happens if the bug is exploited — damage magnitude and how fast it occurs. | Tier | Rule of thumb | Definition | | - | - | - | | **S4 — Critical** | Can drain everything immediately | Total fund theft or protocol insolvency with no time to react. | | **S3 — High** | Large or fast theft, or permanent freeze | Large-scale fund theft or permanent freeze. Requires some setup, but fast once triggered. | | **S2 — Medium** | Partial or slow damage | Bounded drain (capped percentage, not total) or slow drain with a response window. Temporary freeze. | | **S1 — Low** | Annoying but not financially dangerous | Griefing, minor economic damage, DoS with no fund loss, or an admin-only bug with no external exploit path. | Four rules resolve the most common classification calls: * **Theft outranks freeze.** Stealing funds is more severe than locking them, even at a larger nominal amount. * **Instant outranks slow.** A slow drain gives us time to detect and pause; an instant drain does not. * **If damage is capped**, severity is based on the cap — not total TVL. * **Admin-only bugs are S1.** Functions behind `onlyOwner` or an equivalent role do not escalate above S1 regardless of the potential damage. They are only eligible for a bounty when there is an external path to reach the role (see below). ### Likelihood Likelihood captures how probable exploitation is in practice. | Tier | Rule of thumb | Criteria | | - | - | - | | **L3 — High** | Anyone can do it anytime | No prerequisites. Callable by any EOA with publicly available calldata. | | **L2 — Medium** | Needs setup (flash loans, timing, conditions) | Requires specific conditions: flash loan infra, a particular chain state, MEV infrastructure, or another protocol being in a particular state. | | **L1 — Low** | Needs a privileged role or rare situation | Requires privileged access (operator, solver, oracle), very specific market conditions, or a multi-step attack depending on several parties. | We reduce likelihood by one tier (floored at L1) when the exploit freezes rather than steals (unless the freeze is a step toward theft). ## Base payout matrix | | **L3 — High** | **L2 — Medium** | **L1 — Low** | | - | - | - | - | | **S4 — Critical** | \$100,000 | \$50,000 | \$20,000 | | **S3 — High** | \$30,000 | \$15,000 | \$7,500 | | **S2 — Medium** | \$10,000 | \$5,000 | \$2,000 | | **S1 — Low** | \$2,000 | \$1,000 | \$500 | The floor of \$500 applies only when a report demonstrates an external exploit path — an attacker without a privileged role can reach the bug. A report is not eligible for a bounty when: * The only exploit path requires a privileged role (owner, operator, solver, oracle). * The reporter does not demonstrate that an unprivileged party can reach that role or induce the privileged party to act. * The bug would not fire under normal operation. These are real bugs and we still want them reported — they just do not qualify as vulnerabilities under this framework. Strong write-ups in this category are candidates for a discretionary bonus. ## Modifiers After landing on a base payout, apply modifiers multiplicatively. Modifiers are drawn from the fixed list below. **Quick checklist:** * ×1.5 — permanently unrecoverable damage * ×1.25 — hard to fix / hard to detect * ×0.75 — expensive to exploit, or pausable with active monitoring * ×0.5 — damage capped to a sub-pool **Full trigger definitions:** | Modifier | Trigger | Factor | | - | - | - | | Permanently unrecoverable | Damage cannot be reversed after exploit — freeze with no pause or upgrade, or theft with funds laundered/bridged before response is possible | ×1.5 | | High remediation cost | Fix requires a full contract redeploy across multiple chains | ×1.25 | | Silent / undetectable | No onchain event emitted, no alert surface, could run unnoticed for days | ×1.25 | | High attacker cost | Exploit requires significant capital at risk or fees that materially reduce net gain | ×0.75 | | Pausable + monitored | Contract can be paused. Active monitoring with sub-5-minute response is in place and would meaningfully reduce loss against this specific exploit | ×0.75 | | Bounded damage pool | Damage capped to a sub-pool, not full TVL (e.g. fee buffer only) | ×0.5 | The **permanently unrecoverable** modifier (×1.5) applies when damage from an exploit cannot be reversed once it happens. This is different from how quickly the damage occurs (which is captured by severity tiers). This modifier is used if the funds or system state cannot be restored after the exploit, such as when funds are stolen and removed before any response is possible (e.g., laundered or bridged out), or when a freeze is permanent with no way to pause or upgrade the contract. Modifiers stack. The final amount is floored at \$500, capped at \$100,000, and rounded to the nearest \$500. **Worked example.** S3/L2 base = \$15,000. Pausable (×0.75) and bounded to the fee buffer (×0.5) → \$15,000 × 0.75 × 0.5 = \$5,625 → **\$5,500**. ## Worked examples ### Flash loan reentrancy on withdrawal An attacker uses a flash loan to reenter the withdrawal function and drain depository contract funds in a single transaction. No special permissions required. * **Severity: S4** — instant, total fund theft. * **Likelihood: L2** — requires flash loan infrastructure and a specific reentrancy window. * **Base: \$50,000.** Permanently unrecoverable (×1.5) — funds laundered in the same tx before any response is possible. **Final: \$75,000.** ### Fee parameter drift A rounding error lets any filler extract \~2 bps above their entitled fee per fill. Callable by any EOA, damage accumulates slowly, bounded to the fee buffer. * **Severity: S2** — slow drain, bounded to the fee pool. * **Likelihood: L3** — any EOA, any time. * **Base: \$10,000.** Bounded damage pool (×0.5) and pausable + monitored (×0.75) → \$3,750 → **\$4,000**. ### Permanent pool freeze via griefing A crafted cross-chain message bricks a specific pool's accounting, locking all funds in it. The attacker has no way to extract — it is a pure grief and costs gas. * **Severity: S3** — permanent freeze of a pool. * **Likelihood: L2** — downgraded one tier from L3 because the exploit freezes rather than steals. * **Base: \$15,000.** Permanently unrecoverable (×1.5) → **\$22,500**. ## Edge cases * **"The fix is already deployed."** Payouts are assessed as of report submission, not review. A valid report does not lose value because we moved fast. * **"The bug only exists under specific token configurations."** If the configuration is live in production, the report is treated as real. If only theoretical, likelihood drops to L1. * **"The reporter also proposed the fix."** Does not change the tier, but is strong grounds for a discretionary bonus. * **Duplicate reports.** The first submission gets the full payout. A duplicate submitted within 7 days receives 10% of the first report's payout as a courtesy payment. Duplicates submitted after 7 days are not eligible for payment. * **Previously known issues.** We treat a finding as already known only when, before your submission, the same root cause was recorded in a dated internal record showing we were already tracking it for remediation, such as an internal ticket or a triaged finding. Raw automated scanner output or an untriaged internal note does not count. When a qualifying record predates your report, the duplicate schedule above applies (10% within 7 days of that record, not eligible after). We will not reduce a payout based on internal discovery we cannot date to before your submission, and, as above, a report never loses value because we detected, triaged, or fixed the issue after you submitted it. ## Discretionary bonuses Separate from the matrix, we may award bonuses for: * Exceptional write-ups with clear root cause and a suggested fix. * Novel attack vectors that reveal a systemic pattern. * Proactive disclosure during an active incident. Bonuses are recognition, not entitlement, and are not pre-committed. ## PoC requirements | Severity | Requirement | | - | - | | **S4, S3** | Runnable PoC required. Explanations without code are not accepted. | | **S2** | PoC strongly preferred. A detailed technical explanation is accepted when a PoC is not feasible. | | **S1** | Description sufficient with clear reproduction steps. | ## Out of scope The following are not eligible for a reward: * Attacks the reporter has already exploited. * Issues requiring phishing, social engineering, or a compromised private key. * Findings in third-party protocols we compose with, unless the root cause is in Relay's code. * DoS with no fund loss and no path to fund loss. * Theoretical issues with no viable PoC against in-scope assets. * Relay Vaults. The Vaults contracts (RelayPool, RelayBridge, RelayPoolFactory, RelayBridgeFactory, RelayPoolNativeGateway), the vaults API and the vaults user interface are not in-scope assets. Reports against Relay Vaults are not eligible for a reward. ## Reward process and requirements * **KYC and background check.** Required before any reward can be issued. * **Payment.** Rewards are paid in cryptocurrency. * **Tax documentation.** W-8BEN, W-9, or the equivalent for your jurisdiction is handled as part of KYC. ## Reporting a vulnerability Email [support@relay.link](mailto:support@relay.link) with a description, reproduction steps, and a runnable PoC where applicable. Most responses are provided within 24 hours, and we will get back to you with next steps within 1 to 4 business days. # Community Support Source: https://docs.relay.link/security/community-support # Transaction Screening Source: https://docs.relay.link/security/compliance Understand address screening and blocked transactions on the Relay platform The [Relay platform](/what-is-relay) screens transaction addresses using third-party risk data and an internal blocklist. Screening can prevent a quote or fill from proceeding. ## How It Works Screening uses: * **Third-party data** — Risk and sanctions databases, including HackBounty and Chainalysis, identify flagged addresses. * **Internal blocklist** — The platform maintains additional address restrictions based on threat reports and investigations. ## Blocked Transactions A flagged address can produce a “Detected sanctioned address” notification or a blocked-wallet error. See [Unsupported Exchanges](/security/unsupported-exchanges) before sending from an exchange. A fill-time screening failure does not trigger an automatic refund. See [Execution Errors](/references/api/api_core_concepts/execution-errors) and contact [Support](/resources/support) for review. # 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) . # Unsupported Exchanges Source: https://docs.relay.link/security/unsupported-exchanges Exchanges that are not supported by Relay The Relay platform does not support transfers involving the exchanges below. This list is not exhaustive and can change as screening data and the platform blocklist are updated. * @CryptoBot * @wallet * Bit Pin * BitcoinVN * Bitnest * ChangeHero * ChangeNOW * CoinEx * Crypto HK * Cryptomus * FixedFloat * Heleket * HTX (Huobi Global) * Kraken Market * N.Exchange * Sideshift.ai * Tornado.cash * Wallex.ir * Exmo.me ### What this means for you * **Withdrawals from a blocked exchange:** Do not withdraw funds from a blocked exchange directly to a Relay deposit address. The deposit will be blocked and the transaction cannot be completed. * **Sending to a blocked exchange:** Quotes where the recipient address is associated with a blocked exchange will be rejected with a `BLOCKED_WALLET_ADDRESS` error. ### Our policy Blocking decisions use risk and sanctions databases together with the platform's internal blocklist. The platform does not make case-by-case exceptions for sanctioned entities. See [Transaction Screening](/security/compliance) for details. For additional information, contact us via [Support](/resources/support). # Chains Source: https://docs.relay.link/solutions/chains Connect your chain to users and balances across the onchain economy Let users reach your chain with the assets they already hold elsewhere. Once your chain is supported, developers can add swaps, payments, and cross-chain actions through the Relay APIs: **[Instant Onboarding](/use-cases/bridging)** - Let users fund accounts on your chain from supported assets and networks. Build your own onboarding experience with the API or embed [RelayKit UI components](/references/relay-kit/overview). **[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 Let customers pay from their onchain balance and receive the asset your business uses Let customers pay from their existing onchain balance, while your business receives the asset it uses. Relay's APIs connect checkout to supported assets and chains through one integration, so customers don't need to bridge or swap separately before paying. Connect users on every major network P50 fill time across all routes Volume enabled to date Processed across the network ## Watch a Live Demo