Summary

1

Get a Quote

In this step we’ll request a quote that includes all fees, transaction data, and execution steps required to call a contract on another chain.
2

Execute the Call

Once we have the quote, we’ll execute the cross-chain call by submitting transactions and monitoring progress.

Contract Compatibility

Before integrating cross-chain calls, ensure your contract is compatible with Relay. Review our Contract Compatibility overview to make any necessary changes to your smart contracts.

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.

Basic Cross-Chain Call Quote

curl -X POST "https://api.relay.link/quote" \
  -H "Content-Type: application/json" \
  -d '{
    "user": "0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3",
    "originChainId": 1,
    "destinationChainId": 8453,
    "originCurrency": "0x0000000000000000000000000000000000000000",
    "destinationCurrency": "0x0000000000000000000000000000000000000000",
    "amount": "100000000000000000",
    "tradeType": "EXACT_OUTPUT",
    "txs": [
      {
        "to": "0xContractAddress",
        "value": "100000000000000000",
        "data": "0x40c10f19000000000000000000000000742d35cc6634c0532925a3b8d9d4db0a2d7dd5b30000000000000000000000000000000000000000000000000000000000000001"
      }
    ]
  }'

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 below.
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', [
  '0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3', // recipient
  1 // amount to mint
]);

// Use this callData in your quote request
const quoteRequest = {
  user: '0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3',
  originChainId: 1,
  destinationChainId: 8453,
  originCurrency: 'eth',
  destinationCurrency: 'eth',
  amount: '100000000000000000',
  tradeType: 'EXACT_OUTPUT',
  txs: [{
    to: '0xContractAddress',
    value: '100000000000000000',
    data: callData
  }]
};

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

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: '0', // No ETH value needed for ERC20 calls
  tradeType: 'EXACT_OUTPUT',
  txs: [
    {
      to: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC contract address
      value: '0',
      data: approvalData
    },
    {
      to: '0xContractAddress',
      value: '0',
      data: contractCallData
    }
  ]
};

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: “amount parameter doesn’t match txs values” Solution: For ERC20-only calls, set amount: "0" since there’s no ETH value 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

ParameterTypeRequiredDescription
userstringYesAddress that will pay for the transaction
originChainIdnumberYesChain ID where payment originates
destinationChainIdnumberYesChain ID where contract calls execute
originCurrencystringYesCurrency contract on source chain (e.g., “0x0000000000000000000000000000000000000000”, “0x833589fcd6edb6e08f4c7c32d4f71b54bda02913”)
destinationCurrencystringYesCurrency contract on destination chain
amountstringYesTotal value of all txs combined
tradeTypestringYesMust be “EXACT_OUTPUT”
txsarrayYesArray of transaction objects
txs[].tostringYesContract address to call
txs[].valuestringYesETH value to send with call
txs[].datastringYesEncoded function call data
recipientstringNoAlternative recipient for any surplus
referrerstringNoIdentifier that can be used to monitor transactions from a specific source.
refundTostringNoAddress to send the refund to in the case of failure, if not specified the user address is used

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.

Monitor Cross-Chain Call Status

Track the progress of your cross-chain call using the status endpoint:
curl "https://api.relay.link/intents/status/v2?requestId=0x92b99e6e1ee1deeb9531b5ad7f87091b3d71254b3176de9e8b5f6c6d0bd3a331"

Status Values

StatusDescription
waitingDeposit tx for the request is yet to be indexed
pendingDeposit tx was indexed, now the fill is pending
successRelay completed successfully
failureRelay failed
refundFunds were refunded due to failure

Advanced Features

App Fees

You can include app fees in your call requests to monetize your integration:
{
  "user": "0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3",
  "originChainId": 1,
  "destinationChainId": 8453,
  "originCurrency": "eth",
  "destinationCurrency": "eth",
  "amount": "100000000000000000",
  "tradeType": "EXACT_OUTPUT",
  "txs": [
    {
      "to": "0xContractAddress",
      "value": "100000000000000000",
      "data": "0x40c10f19..."
    }
  ],
  "appFees": [
    {
      "recipient": "0xYourFeeRecipient",
      "fee": "50"
    }
  ]
}

Custom Slippage

Control slippage tolerance for your calls:
{
  "user": "0x742d35Cc6634C0532925a3b8D9d4DB0a2D7DD5B3",
  "originChainId": 1,
  "destinationChainId": 8453,
  "originCurrency": "eth",
  "destinationCurrency": "eth",
  "amount": "100000000000000000",
  "tradeType": "EXACT_OUTPUT",
  "txs": [
    {
      "to": "0xContractAddress",
      "value": "100000000000000000",
      "data": "0x40c10f19..."
    }
  ],
  "slippageTolerance": "50"  // 0.5% slippage tolerance
}

Preflight Checklist

Contract compatibility - Ensure your smart contract follows Relay compatibility guidelines ERC20 approvals - Include approval transactions before any ERC20 spending calls Verify transaction data - Confirm amount equals the sum of all txs[].value fields Check tradeType - Must be set to "EXACT_OUTPUT" for cross-chain calls Validate call data - Ensure contract function calls are properly encoded Token addresses - Use correct ERC20 contract addresses for each chain Test contract calls - Verify contract functions work as expected on destination chain Balance verification - Confirm user has sufficient funds for amount + fees Error handling - Implement proper error handling for failed contract executions Monitor progress - Use status endpoints to track execution progress Gas estimation - Account for potential gas usage variations in contract calls

Common Use Cases

NFT Minting with ETH: Mint NFTs on L2s while paying from L1
// 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]
  })
};
NFT Minting with ERC20: Mint NFTs using USDC
// 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]
    })
  }
];
DeFi Operations: Execute swaps, provide liquidity, or claim rewards on other chains
// 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
      ]
    })
  }
];
Gaming: Execute game actions, purchase items, or claim rewards across chains
// 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]
    })
  }
];