Skip to content

withExecAndTransfer

Use the withExecAndTransfer utility for interacting with destination contracts that accept token deposits, and return dynamic tokenized positions.

For example:

  • Alice deposits 1 ETH into a contract
  • Alice receives some dynamic amount of vTokens (value tokens) in return

How it Works

This utility wraps your intended destination contract call (call) within another call targeting the SolverNetExecutor contract. When the solver fulfills the intent:

  1. The SolverNetExecutor contract is called on the destination chain.
  2. The SolverNetExecutor executes the target call specified (e.g., Vault.deposit() which credits vault shares to msg.sender).
  3. After the original call completes, the SolverNetExecutor (which was the msg.sender and received the assets) checks its own balance of a specified token.
  4. It transfers the entire balance of that token it holds to the specified final recipient address (to).

Here's the relevant part of the SolverNetExecutor contract:

contract SolverNetExecutor {
    // ...
 
    /**
     * @notice Execute a call and transfer any received ERC20 tokens back to the recipient
     * @dev Intended to be used when interacting with contracts that don't allow us to specify a recipient
     *      This should be triggered by `execute` by executing an external call against this Executor contract
     * @param token  Token to transfer
     * @param to     Recipient address
     * @param target Call target address
     * @param data   Calldata for the call
     */
    function executeAndTransfer(address token, address to, address target, bytes calldata data)
        external
        payable
        onlySelf
    {
        (bool success,) = target.call{ value: msg.value }(data);
        if (!success) revert CallFailed();
 
        if (token == address(0)) SafeTransferLib.safeTransferAllETH(to);
        else token.safeTransferAll(to);
    }
 
    // ...
}
Key Points:
  • The token in the transfer configuration of withExecAndTransfer is the address of the asset you expect the executor to receive as a result of executing your call. This is the asset the executor will forward to the final to address. Use zeroAddress if the call results in native ETH being sent to the executor.

Usage

  1. Define your target call: This is the call to the target contract function (e.g., vault.deposit{ value: amount }()) that sends assets to msg.sender.
  2. Wrap it: Use withExecAndTransfer, providing your original call, the token you expect the executor to receive, and the final recipient (to).
  3. Use the wrapped call: Pass the result of withExecAndTransfer (which is a Call object itself) into the calls array for useOrder.
import { withExecAndTransfer } from '@omni-network/core';
 
 
// ABI for Vault.deposit{ value: amount }()
const vaultABI = [
    {
        inputs: [],
        name: 'deposit',
        outputs: [],
        stateMutability: 'payable',
        type: 'function',
    },
] as const;
 
const vaultAddress = "0x..." as const;      // Your tokenized vault address
const vaultTokenAddress = "0x..." as const; // The ERC20 token minted by the vault to msg.sender
const vaultDepositAmt = parseEther('1');    // Amount to deposit into the vault
const userAddress = "0x..." as const;       // The final recipient of the vault tokens
 
// 1. Define the target call (e.g., vault.deposit{ value: amount }())
const targetCall = {
    target: vaultAddress,
    abi: vaultABI,
    functionName: 'deposit',
    value: vaultDepositAmt,
};
 
// 2. Wrap the call
const wrappedCall = withExecAndTransfer({
    call: targetCall,
    transfer: {
        token: vaultTokenAddress, // Token the executor should forward
        to: userAddress,          // Final recipient
    }
});
 
// 3. Use the wrapped call in useOrder's calls array
const order = useOrder({
    // ...
    calls: [wrappedCall],
});