Swap and Execute
Fund a destination chain and call a contract there in one operation.
swapAndExecute() funds a contract call on the destination chain, swapping only what the call still needs, then runs the call from the wallet or through Relay.
Signature
swapAndExecute(
input: SwapAndExecuteParams,
options?: SwapAndExecuteOptions,
): Promise<SwapAndExecuteIntentResult>Parameters
input
| Name | Type | Description |
|---|---|---|
tradeType | 'exactOutput' | 'exactInput' | Default 'exactOutput'. |
toChainId | number | Required. Destination chain. |
toTokenAddress | Hex | Required. Token the call spends. For native, use the native token address from the token catalog (isNative: true). |
toAmountRaw | bigint | Exact output: required, the amount the call needs. Exact input: omit. |
sources | Array<{ chainId, tokenAddress?, amountRaw? }> | Exact output: optional, limits which balances fund the call. Exact input: required, each with tokenAddress and a positive amountRaw. |
execute | SwapExecuteParams | Required. The destination call. |
execute fields:
| Name | Type | Description |
|---|---|---|
to | Hex | Required. Contract to call. |
data | Hex | Calldata. Return it from beforeExecute when it depends on the final amount. |
value | bigint | Native value sent with the call. |
gas | bigint | Required, positive. Gas estimate for the call. The SDK adds chain-specific buffers. |
gasPrice | 'low' | 'medium' | 'high' | Fee tier. Default 'medium'. |
tokenApproval | { toTokenAddress, amount, spender } | ERC-20 approval sent before the call when allowance is short. |
execute().
Exact input swaps fixed source amounts and executes whatever arrives. Sources that already are the destination token on the destination chain stay in the wallet and are added to the execution amount. The remaining sources must share one provider with the destination. If every source is local, the swap is skipped and the wallet must already hold native gas for the call. When tokenApproval.toTokenAddress is the destination token, pass amount: 0n; the SDK sets it to the final execution amount.
options
| Name | Type | Description |
|---|---|---|
hooks.onIntent | (data: SwapAndExecuteHookData) => void | Review gate. Receives intent, allow, deny, refresh(sources?), and attemptId. Without it, the operation proceeds. |
beforeExecute | (ctx: BeforeExecuteContext) => Promise<{ data?, value?, gas? }> | Returns overrides for the call. |
useProviderDestinationExecution | boolean | Default false. true requires Relay to run the call. See provider destination execution. |
onEvent | (event: IntentEvent) => void | Funding progress. See swap events. |
slippageBps | number | 'auto' | 0 to 10000. Default 50. |
fillTimeoutMinutes | number | Default 2. |
pollingIntervalMs | number | Default 2000. |
onIntent runs even when no swap is needed. intent (SwapAndExecuteIntent) contains:
executionMode:'wallet'or'provider'.executeRequirement: the call's chain,to, token amount, gas cost,nativeValue, andtokenApproval.availableandshortfall: destination token and gas, asFundingAmountvalues.swapRequired: whentrue,intent.quoteholds the funding quote.
refresh(sources?) returns a new intent with current fees and a new quote. It reuses the balances read at the start; start a new call to reread them. Omitted sources keep the current selection. For exact output, [] clears it.
BeforeExecuteContext:
| Field | Wallet mode | Provider mode |
|---|---|---|
receivedAmountRaw | Destination balance increase across funding; 0n when the swap is skipped. | The quote's guaranteed minimum output (toAmountRaw for exact output). |
executeAmountRaw | Exact input: proceeds plus selected local tokens, less native reserves. Exact output: toAmountRaw. | Fixed execution budget. |
executeAmountRaw.
Wallet mode
- Reads balances, prices the call's gas and value, and quotes only the shortfall. When balances already cover the call, it skips the swap.
- For non-native output, adds a native gas drop when the wallet lacks gas on the destination.
- After
allow(), reads the destination balance, runs the intent, and reads it again. - Calls
beforeExecute, then sends the approval (if needed) and the call from the connected wallet. - Overrides from
beforeExecutedo not change funding. The balance increase includes any unrelated transfer that lands during funding.
Provider destination execution
useProviderDestinationExecution: true, Relay runs the approval and call on the destination chain as part of the fill. The wallet sends no destination transaction.
The call's
msg.sender is Relay's router, not the user. Use a contract function that credits an explicit recipient, such as ERC-4626 deposit(assets, receiver). A function that credits msg.sender sends the position to the router.- Route support: the quote must have
quote.supportsDestinationCalls: true, and exactly one chain/token leg may fund it. Otherwise the call rejects withINVALID_INPUTbefore signing. It never falls back to wallet mode. - Funding: the quote funds the whole call. Destination wallet balances are not used, and exact-input sources that match the destination enter the quote.
- Exact-input budget: the SDK previews a Relay quote and sets
executeAmountRawto its minimum output less 1%, with the reserve capped at $1. The rest goes to the recipient. Unpriced or too-small output rejects withBACKEND_ERROR. - Exact-output budget:
executeAmountRawistoAmountRaw. - Approvals:
tokenApprovalmay only approve the output token. - Native value: allowed only for native output, and at most
executeAmountRaw. ERC-20 output with a nonzerovaluerejects. - Unused fields:
execute.gasis still required but unused; the quote includes execution costs.
In provider mode,
beforeExecute runs before review and again on every refresh. It must only build the call, with no side effects. If the final quote's minimum falls below the budget, the call rejects before signing.getBalances() by balance.providers[].id === 'relay'. Token support does not guarantee destination-call support, which is decided per quote.
| Wallet mode | Provider mode | |
|---|---|---|
| Runs the call | Connected wallet | Relay's router |
msg.sender | User | Relay's router |
| Destination balances | Used first | Ignored |
| Swap skipped | When balances cover the call | Never |
beforeExecute | Once, after funding | Before review and on each refresh |
| Destination gas | Paid by the wallet | Included in the quote |
result.execute | Present | undefined |
Example
useProviderDestinationExecution to run the same deposit from the wallet.
import { encodeFunctionData, parseAbi, parseUnits, type Hex } from 'viem';
const USDC_ARBITRUM: Hex = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
const USDC_BASE: Hex = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const VAULT: Hex = '0xYourVault';
const user: Hex = '0xUserAddress';
const vaultAbi = parseAbi([
'function deposit(uint256 assets, address receiver) returns (uint256)',
]);
const source = await client.getToken({ chainId: 42161, tokenAddress: USDC_ARBITRUM });
const result = await client.swapAndExecute(
{
tradeType: 'exactInput',
sources: [
{ chainId: 42161, tokenAddress: USDC_ARBITRUM, amountRaw: parseUnits('25', source.decimals) },
],
toChainId: 8453,
toTokenAddress: USDC_BASE,
execute: {
to: VAULT,
gas: 200_000n,
tokenApproval: { toTokenAddress: USDC_BASE, amount: 0n, spender: VAULT },
},
},
{
useProviderDestinationExecution: true,
beforeExecute: async ({ executeAmountRaw }) => ({
data: encodeFunctionData({
abi: vaultAbi,
functionName: 'deposit',
args: [executeAmountRaw, user],
}),
}),
hooks: {
onIntent: ({ intent, allow }) => {
console.log(intent.executionMode, intent.executeRequirement.token.amount);
allow(); // Call after the user confirms.
},
},
},
);
if (result.executionMode === 'wallet') {
console.log('Deposit tx', result.execute.txExplorerUrl);
} else {
console.log('Filled by Relay', result.swapResult.intentExplorerUrl);
}
console.log('Deposited', result.executeAmountRaw);Returns
type SwapAndExecuteIntentResult = { executeAmountRaw: bigint } & (
| {
executionMode: 'provider';
swapSkipped: false;
swapResult: IntentResult;
approval?: undefined;
execute?: undefined;
}
| ({ executionMode: 'wallet'; approval?: TxResult; execute: TxResult } & (
| { swapSkipped: true; swapResult?: undefined }
| { swapSkipped: false; swapResult: IntentResult }
))
);executionMode before reading execute or approval, and on swapSkipped before reading swapResult in wallet mode. executeAmountRaw is the amount the call used. Intent events cover funding only; the promise resolves after the call.
For error codes, see hooks and errors.How is this guide?