Embedded Crosschain Swap Actions
This guide helps developers understand the fundamentals of preparing for and constructing the request body for embedded crosschain swap actions. This enables developers to add custom actions (like mint, deposit, etc) that execute seamlessly on the destination chain immediately after a swap, all within a single transaction built using the /swap/approval API.
Executing Destination Chain Actions
At a high level, this is how the embedded crosschain swap actions system works:
User submits the intent using the Swap API where they define the actions they want to execute on the destination chain along with the input/output tokens.
To be repaid for the fill, the relayer is required to include the same message field in their fill.
When this message field is non-empty and the recipient is a contract, the SpokePool calls a special handler function on the recipient handler contract with the message (and a few other fields).
To execute the destination chain actions, the entry point is the handleV3AcrossMessage() in the MulticallHandler.sol contract. But if there were any actions using the dynamic balance feature, then one of the calls executed by handleV3AcrossMessage will be to makeCallWithBalance() .
Now, let's dive a little deeper !
Before starting the integration, please ensure that you fill this form and get your integrator ID. This will allow us to support you better as you progress towards the production-release and accelerate co-marketing efforts from our side.
Once you fill out the form, we’ll get in touch and provide your integrator ID. In the meantime, you can proceed with the integration by following this guide and simply add the integrator ID once it’s shared with you.
The Action Object Structure
The goal is to build an actions object that defines a "post-swap" operation. All actions are submitted as objects in an array, in the request body for the /swap/approval API. Here is the basic structure of a single action:
{
  "actions":[
    {
      "target": "0x...", // The contract or recipient address
      "functionSignature": "function transfer(address,uint256)", // The function to call, or "" for native transfers
      "args": [
        // Array of arguments for the function, or [] for native transfers
        {
          "value": "1000", // Static value for an argument
          "populateDynamically": false, // Whether to use the swap balance instead
          "balanceSourceToken": "0x..." // Token to get the balance from if dynamic
        }
      ],
      "value": "0", // Static msg.value to send, in wei
      "isNativeTransfer": false, // True for simple native currency transfers
      "populateCallValueDynamically": false // True to use the entire swapped native balance as msg.value
    }
  ]
}
when using tradeType=exactOutput with the Swap API some routes may return more tokens than expected. So if you’re using exactOutput with embedded actions, and you need those actions to execute with the exact amount you specify, you should not use dynamic balance, instead, set the values explicitly to the exact amount.
Breaking Down the Action Object
To fill in the structure above, you must define its core components by making a series of decisions. Each decision corresponds to a field in the action object.
1. Set the Target, Function Signature, Action Type and Value
First, determine your goal. This defines the destination (target) and the type of action.
Is this a simple native token transfer? If so, set the
isNativeTransferflag totrue. Thetargetwill be the recipient's address.Are you calling a smart contract function? If so, set
isNativeTransfertofalse. Thetargetwill be the contract's address.Native Value (
value): If the action needs to send native currency (msg.value), decide how to set this amount:For a static amount, provide it in the top-level
valuefield.To use the entire swapped balance, set
populateCallValueDynamicallytotrue.
2. Specify the Function
If you are calling a contract (isNativeTransfer is false), you must provide the full functionSignature string (e.g., "transfer(address,uint256)"). For native transfers, this must be an empty string ("").
3. Provide Parameters for the Function Signature
Finally, define the parameters for the action.
Function Arguments (
args): For contract calls, you must provide anargsarray. For each argument, decide if its value is static or dynamic:For a static value, provide it in the
valuefield and ensurepopulateDynamicallyisfalse.For a dynamic value, set
populateDynamicallytotrueand specify which token's balance to use in thebalanceSourceTokenfield. Thevaluefield will be ignored.
Examples
Tracking Deposits
To track the lifecycle of a deposit you can use the GET /deposit/status endpoint with the following parameters:
originChainId: chainId where the deposit originated from. Use this in conjunction with thedepositIdparameter.depositId: The deposit id that is emitted from theDepositV3function call as aV3FundsDepositedevent. Use this in conjunction with theoriginChainIdparameter.depositTxHash: The deposit transaction hash that is emitted from the DepositV3 function call as a V3FundsDeposited event. If you are using this, you do not need the above parameters.
The recommended solution for tracking all Across deposits originating from your integration, for a single user, is to store the user's depositId and originChainId or the depositTxHash from each transaction originating from your app, and then get the status of each via the above endpoint. 
In case you face any errors or need support, please feel free to reach out to us.
Last updated



