Skip to main content
Destination payload lets you execute arbitrary contract calls on the destination chain after a crosschain transfer completes. This is useful for flows such as depositing to a vault, repaying a loan, or interacting with a protocol immediately after bridging or swapping.
Destination payload execution is available on Socket Swap V3 through /v3/swap/quote.

How it works

When building a Socket request, you can provide calldata that will be executed on the destination chain after assets are delivered. It works for both same-chain swaps and cross-chain swaps. Provide these parameters in your quote request:
  • destinationPayload: ABI-encoded calldata for the target contract on the destination chain
  • receiverAddress: The execution target that will receive the calldata and perform the call
  • destinationGasLimit: Gas budget on destination to execute the payload
interface IBungeeExecutor {
    function executeData(
        bytes32 requestHash,
        uint256[] calldata amounts,
        address[] calldata tokens,
        bytes memory callData
    ) external payable;
}

Implementation example

const SOCKET_API_BASE_URL = "https://dedicated-backend.socket.tech";
const SOCKET_AFFILIATE_ID = "YOUR_AFFILIATE_ID";
const CONTRACT_ADDRESS = "0x1111111111111111111111111111111111111111";

async function getQuoteWithDestinationPayload() {
  const quoteParams = {
    userOps: "tx",
    userAddress: "0xYourUsersAddress",
    originChainId: "8453",  // Base
    destinationChainId: "8453",  // Base
    inputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",  // USDC on Base
    outputToken: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2", // USDT on Base
    inputAmount: "10000000",  // 10 USDC (6 decimals)
    receiverAddress: CONTRACT_ADDRESS, // contract implementing IBungeeExecutor.executeData
    destinationPayload: "0x1234acbd", // calldata for the receiver contract
    destinationGasLimit: "100000",    // gas ceiling for destination execution
  };

  const url = `${SOCKET_API_BASE_URL}/v3/swap/quote`;
  const queryParams = new URLSearchParams(quoteParams);
  const response = await fetch(`${url}?${queryParams}`, {
    headers: {
      affiliate: SOCKET_AFFILIATE_ID,
    },
  });
  const data = await response.json();
  const serverReqId = response.headers.get("server-req-id");

  if (!data.success) {
    throw new Error(
      `Quote error: ${data.statusCode}: ${data.message}. server-req-id: ${serverReqId}`
    );
  }
}

Important Notes

  • The destinationPayload is the callData parameter that will be passed to the executeData function
  • The receiverAddress needs to be a contract that implements the IBungeeExecutor interface
  • Ensure the receiver contract trusts/can handle calls from the Socket executor
  • Complex receivers or multi-token logic require higher gas limits
  • Execution may fail due to out of gas on destination — increase destinationGasLimit
  • Re-check encodeAbiParameters types match the receiver decode logic to avoid encoding errors

Complete Examples

import {
  createWalletClient,
  createPublicClient,
  http,
  encodeAbiParameters,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

const PRIVATE_KEY = process.env.PRIVATE_KEY;
// Example receiver contract on Base that implements executeData(...)
// https://basescan.org/address/0xC0b43F2B38CA47CC9e1b9697296716ebCF3D8177#code
const CONTRACT_ADDRESS = '0xC0b43F2B38CA47CC9e1b9697296716ebCF3D8177';

const account = privateKeyToAccount(PRIVATE_KEY);

const publicClient = createPublicClient({ chain: base, transport: http() });
const walletClient = createWalletClient({ account, chain: base, transport: http() });

const SOCKET_API_BASE_URL = "https://dedicated-backend.socket.tech";
const SOCKET_AFFILIATE_ID = "YOUR_AFFILIATE_ID";

const quoteParams = {
  userOps: "tx",
  userAddress: account.address,
  originChainId: 8453,
  inputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
  destinationChainId: 8453,
  outputToken: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2", // USDT on Base
  inputAmount: "3000000", // 3 USDC
};

async function main() {
  quoteParams.destinationPayload = encodeAbiParameters(
    [{ type: 'address' }],
    [account.address] // payload: forward funds to this address
  );
  quoteParams.destinationGasLimit = "100000";
  quoteParams.receiverAddress = CONTRACT_ADDRESS;

  const url = `${SOCKET_API_BASE_URL}/v3/swap/quote?${new URLSearchParams(quoteParams)}`;
  const response = await fetch(url, {
    headers: {
      affiliate: SOCKET_AFFILIATE_ID,
    },
  });
  const data = await response.json();

  if (!data.success || !data.result?.routes?.length) {
    throw new Error(`Quote failed: ${data.message}`);
  }

  const route = data.result.routes[0];
  console.log("Quote ID:", route.quoteId);

  // Approve route.approval if present, submit route.txData.object, then poll /v3/swap/status with route.quoteId.
}

main();
For Example Purposes OnlyThe smart contract below is for example purposes only. Please audit your contracts for production usage.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

import {Ownable} from "solady/auth/Ownable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

interface IBungeeExecutor {
    function executeData(
        bytes32 requestHash,
        uint256[] calldata amounts,
        address[] calldata tokens,
        bytes memory callData
    ) external payable;
}

contract ExecuteDestinationPayload is IBungeeExecutor, Ownable {
    address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    event DestinationPayload(
        bytes32 indexed requestHash,
        uint256 amount,
        address indexed token,
        address indexed recipient
    );

    error InvalidArrayLength();
    error ZeroAmount();
    error ZeroAddress();

    constructor() {
        _initializeOwner(msg.sender);
    }

    function executeData(
        bytes32 requestHash,
        uint256[] calldata amounts,
        address[] calldata tokens,
        bytes memory callData
    ) external payable override {
        if (amounts.length != 1 || tokens.length != 1) {
            revert InvalidArrayLength();
        }

        address recipient = abi.decode(callData, (address));
        address token = tokens[0];
        uint256 amount = amounts[0];

        if (token == NATIVE_TOKEN) {
            SafeTransferLib.safeTransferETH(recipient, amount);
        } else {
            SafeTransferLib.safeTransfer(token, recipient, amount);
        }

        emit DestinationPayload(requestHash, amount, token, recipient);
    }

    receive() external payable {}
}