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:

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:

In that flow, your Relay request should include:

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

Common Use Cases

// 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],\
    }),\
  },\
];