Call Execution Integration Guide - Relay
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 to find the right approach, or use Smart Accounts (EIP-7702 or ERC-4337) / 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 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.
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.
Request and Response Examples
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"\
}\
]\
}'
Steps for Transaction Execution
{
"steps": [\
{\
"id": "deposit",\
"action": "Confirm transaction in your wallet",\
"description": "Depositing funds to the relayer to execute the swap for ETH",\
"kind": "transaction",\
"items": [\
{\
"status": "incomplete",\
"data": {\
"from": "0x03508bb71268bba25ecacc8f620e01866650532c",\
"to": "0x4cd00e387622c35bddb9b4c962c136462338bc31",\
"data": "0x49290c1c00000000000000000000000003508bb71268bba25ecacc8f620e01866650532c0d0f5bc9e030aa965a11905d83e54a407697e6fcdba5c300875db0fbf93e186b",\
"value": "100012440985606146",\
"chainId": 1,\
"gas": "32713",\
"maxFeePerGas": "386206132",\
"maxPriorityFeePerGas": "246844318"\
},\
"check": {\
"endpoint": "/intents/status/v3?requestId=0xa8de0141e504d2f7ed6b17cfd03455ecc2b17ab4e63120e43c38dce8015c1004",\
"method": "GET"\
}\
}\
],\
"requestId": "0xa8de0141e504d2f7ed6b17cfd03455ecc2b17ab4e63120e43c38dce8015c1004",\
"depositAddress": ""\
}\
],\
"fees": {...},
"details": {...}
}
Important Considerations
- Ensure your contract is compatible with Relay. Review our Contract Compatibility overview to make any necessary changes to your smart contracts.
- Include approval transactions before any ERC20 spending calls.
Common Use Cases
- 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
// 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],
}),
};
// 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],\
}),\
},\
];