transferWithHook
The transferWithHook
method generates a solution for performing a token transfer from a single source chain, followed by a contract call on the destination chain. The contract call can involve either a native token or a token transfer. This method returns the necessary transaction details to execute both the token transfer and the contract interaction.
Usage
- Token Transfer with Contract Call
- Native Token Transfer with Contract Call
In this example, a token transfer (e.g., USDC
) is followed by a contract call on the destination chain. You need to provide outputTokenAddress
and approvalAddress
to allow the contract to move tokens on behalf of the user.
import { Sprinter, Environment } from "@chainsafe/sprinter-sdk";
const sprinter = new Sprinter({ baseUrl: Environment.TESTNET });
const settings = {
account: "0xYourAddressHere",
destinationChain: 11155111, // Sepolia testnet
token: "USDC",
amount: 1000000, // In smallest denomination (e.g., 1 USDC = 1,000,000 in USDC with 6 decimals)
contractCall: {
contractAddress: "0xContractAddressHere",
callData: "0xSomeCallData", // Encoded contract interaction data
gasLimit: 100000,
outputTokenAddress: "0xOutputTokenAddressHere", // Where tokens will be sent
approvalAddress: "0xApprovalAddressHere", // Contract that needs approval to transfer tokens
},
recipient: "0xRecipientAddress", // Optional recipient of leftover tokens
sourceChains: [84532], // Optional: List of source chains to be considered
};
sprinter.transferWithHook(settings).then((solution) => {
console.log(solution);
});
In this example, a native token (e.g., ETH
) is transferred to a contract on the destination chain, followed by a contract call. The contract can receive the native token in addition to executing the call.
const settings = {
account: "0xYourAddressHere",
destinationChain: 11155111, // Sepolia testnet
token: "ETH",
amount: 5000000000000000000, // 5 ETH in the smallest denomination (wei)
contractCall: {
contractAddress: "0xContractAddressHere",
callData: "0xSomeCallData", // Encoded contract interaction data
gasLimit: 21000, // Standard gas limit for simple ETH transfers
},
recipient: "0xRecipientAddressHere", // The recipient of the native token transfer
sourceChains: [84532], // Optional: List of source chains to be considered
};
sprinter.transferWithHook(settings).then((solution) => {
console.log(solution);
});
You can limit the solution to a specific source chain using the sourceChains
field. For example, to use only BaseSepolia
(chain ID 84532
), provide it as an array like this:
sourceChains: [84532];
If omitted, Sprinter will consider all available source chains.
Example: Using fetchOptions
sprinter.transferWithHook(settings, { baseUrl: "https://custom.api.url" }).then((solution) => {
console.log(solution);
});
Parameters
-
settings
: (Required) An object containing the following fields:account
: The user’s address.destinationChain
: The ID of the destination chain.token
: The symbol of the token to be transferred (e.g.,USDC
,ETH
).amount
: The amount of the token to be transferred in the smallest denomination (e.g., for USDC with 6 decimals, 1 USDC = 1,000,000).contractCall
: An object containing the contract call details, depending on the type of contract call:- Native Contract Call:
contractAddress
: The contract address on the destination chain.callData
: The data to interact with the contract, in hex format.gasLimit
: The maximum amount of gas to use for the contract call.
- Token Contract Call:
contractAddress
: The contract address on the destination chain.callData
: The data to interact with the contract, in hex format.gasLimit
: The maximum amount of gas to use for the contract call.outputTokenAddress?
: (Optional) The token address where tokens will be sent after the contract call.approvalAddress?
: (Optional) The contract address that requires approval to transfer tokens (e.g., fortransferFrom
).
- Native Contract Call:
recipient?
: (Optional) The address of the recipient of any leftover tokens.sourceChains?
: (Optional) An array of source chain IDs to be considered for the transfer. If omitted, Sprinter will use all available chains for the solution.threshold?
: (Optional) The minimum amount of tokens required to trigger the transfer solution. If not met, the transfer solution will not proceed.enableSwaps
: (Optional) Defaults tofalse
. Whether to enable token swaps on the source chain.
-
fetchOptions?
: (Optional) An object containingbaseUrl
to override the default API endpoint for this request.
Creating callData
The following examples demonstrate how to create the callData
parameter required for interacting with a staking smart contract. We provide examples using different web3 libraries: Web3JS, Viem, and Ethers.
Show Example Staking Contract and ABI
pragma solidity ^0.8.0;
contract StakingContract {
mapping(address => uint256) public stakes;
uint256 public totalStakes;
function stake(uint256 amount) public {
require(amount > 0, "Amount must be greater than zero");
stakes[msg.sender] += amount;
totalStakes += amount;
}
function withdraw(uint256 amount) public {
require(amount > 0 && stakes[msg.sender] >= amount, "Invalid amount");
stakes[msg.sender] -= amount;
totalStakes -= amount;
}
function getStake(address user) public view returns (uint256) {
return stakes[user];
}
}
{
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "stake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getStake",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
}
- Web3JS
- Viem
- Ethers
import Web3 from 'web3';
import contractABI from './stakingContractABI';
const web3 = new Web3('<YOUR_INFURA_OR_ALCHEMY_URL>');
const contractAddress = '<YOUR_CONTRACT_ADDRESS>';
const stakingContract = new web3.eth.Contract(contractABI, contractAddress);
const encodedData = stakingContract.methods.stake(100).encodeABI();
console.log('Encoded Data:', encodedData);
import { encodeFunctionData } from 'viem';
import contractABI from './stakingContractABI';
const abi = contractABI;
const functionName = 'stake';
const args = [100];
const encodedData = encodeFunctionData({ abi, functionName, args });
console.log('Encoded Data:', encodedData);
import { ethers } from 'ethers';
import contractABI from './stakingContractABI';
const provider = new ethers.providers.JsonRpcProvider('<YOUR_INFURA_OR_ALCHEMY_URL>');
const contractAddress = '<YOUR_CONTRACT_ADDRESS>';
const stakingContract = new ethers.Contract(contractAddress, contractABI, provider);
const encodedData = stakingContract.interface.encodeFunctionData('stake', [100]);
console.log('Encoded Data:', encodedData);
Estimating gasLimit
The following examples demonstrate how to estimate the gasLimit
parameter required for interacting with a staking smart contract. We provide examples using different web3 libraries: Web3JS, Viem, and Ethers.
To ensure that the transaction has enough gas, we recommend using the estimated gas limit from the provider, adding 25% as a buffer, and then adding an additional 100,000 units for fail-safe calculations. This ensures the transaction won’t run out of gas, even for complex contract interactions.
Show Example Staking Contract and ABI
pragma solidity ^0.8.0;
contract StakingContract {
mapping(address => uint256) public stakes;
uint256 public totalStakes;
function stake(uint256 amount) public {
require(amount > 0, "Amount must be greater than zero");
stakes[msg.sender] += amount;
totalStakes += amount;
}
function withdraw(uint256 amount) public {
require(amount > 0 && stakes[msg.sender] >= amount, "Invalid amount");
stakes[msg.sender] -= amount;
totalStakes -= amount;
}
function getStake(address user) public view returns (uint256) {
return stakes[user];
}
}
{
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "stake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getStake",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
}
- Web3JS
- Viem
- Ethers
import Web3 from 'web3';
import contractABI from './stakingContractABI';
const web3 = new Web3('<YOUR_INFURA_OR_ALCHEMY_URL>');
const contractAddress = '<YOUR_CONTRACT_ADDRESS>';
const account = '<YOUR_ACCOUNT_ADDRESS>';
const stakingContract = new web3.eth.Contract(contractABI, contractAddress);
async function estimateGas() {
const estimatedGas = await stakingContract.methods.stake(100).estimateGas({ from: account });
const gasLimit = Math.floor(estimatedGas * 1.25) + 100000; // Add 25% and 100k for safety
console.log('Estimated Gas Limit:', gasLimit);
}
estimateGas();
import { encodeFunctionData } from 'viem';
import { estimateGas } from 'viem';
import contractABI from './stakingContractABI';
const abi = contractABI;
const functionName = 'stake';
const args = [100];
async function estimateGas() {
const estimatedGas = await estimateGas({ abi, functionName, args });
const gasLimit = Math.floor(estimatedGas * 1.25) + 200000; // Add 25% and 200k for safety
console.log('Estimated Gas Limit:', gasLimit);
}
estimateGas();
import { ethers } from 'ethers';
import contractABI from './stakingContractABI';
const provider = new ethers.providers.JsonRpcProvider('<YOUR_INFURA_OR_ALCHEMY_URL>');
const contractAddress = '<YOUR_CONTRACT_ADDRESS>';
const signer = provider.getSigner('<YOUR_ACCOUNT_ADDRESS>');
const stakingContract = new ethers.Contract(contractAddress, contractABI, signer);
async function estimateGas() {
const estimatedGas = await stakingContract.estimateGas.stake(100);
const gasLimit = Math.floor(Number(estimatedGas) * 1.25) + 200000; // Add 25% and 200k for safety
console.log('Estimated Gas Limit:', gasLimit.toString());
}
estimateGas();
Response
Returns a promise that resolves to a SolutionResponse
.
type SolutionResponse = Array<Solution> | FailedSolution;
interface Solution {
destinationChain: number;
destinationTokenAddress: string;
duration: number; // Time estimate in seconds
fee: Amount;
gasCost: Amount;
senderAddress: string;
sourceChain: number;
sourceTokenAddress: string;
amount: string;
tool: Tool;
transaction: Transaction;
approvals?: Array<Transaction>;
}
interface FailedSolution {
error: string;
}
Example Response
For better accuracy when dealing with contract calls and transactions, it’s recommended to estimate the gasPrice
and gasLimit
using your own blockchain provider. This ensures that the values reflect the current network conditions and avoid overpaying or underestimating gas fees.
- Token Transfer with Contract Call
- Native Token Transfer with Contract Call
[
{
"sourceChain": 84532,
"destinationChain": 11155111,
"sourceTokenAddress": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"destinationTokenAddress": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
"senderAddress": "0x3e101ec02e7a48d16dade204c96bff842e7e2519",
"tool": {
"name": "Sygma-Testnet",
"logoURI": "https://scan.buildwithsygma.com/assets/images/logo1.svg"
},
"gasCost": {
"amount": "221055913000",
"amountUSD": 0
},
"fee": {
"amount": "1000000000000000",
"amountUSD": 0
},
"amount": "100000000",
"duration": 60000000000,
"transaction": {
"data": "0x73c45c98000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000000000143e101ec02e7a48d16dade204c96bff842e7e251900000000000000000000000000000000000000000000000000000000000000000000000000000000000000023078000000000000000000000000000000000000000000000000000000000000",
"to": "0x9D5C332Ebe0DaE36e07a4eD552Ad4d8c5067A61F",
"from": "0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519",
"value": "0x38d7ea4c68000",
"gasPrice": "0xf433d",
"gasLimit": "0x35f48",
"chainId": 845
32
},
"approvals": [
{
"data": "0x095ea7b30000000000000000000000003b0f996c474c91de56617da13a52b22bb659d18e0000000000000000000000000000000000000000000000000000000005f5e100",
"to": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"from": "0x3E101Ec02e7a48d16dade204c96bff842e7e2519",
"value": "0x0",
"gasPrice": "0xf433d",
"gasLimit": "0xe484",
"chainId": 84532
}
]
}
]
[
{
"sourceChain": 84532,
"destinationChain": 11155111,
"sourceTokenAddress": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"destinationTokenAddress": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
"senderAddress": "0x3e101ec02e7a48d16dade204c96bff842e7e2519",
"tool": {
"name": "Sygma-Testnet",
"logoURI": "https://scan.buildwithsygma.com/assets/images/logo1.svg"
},
"gasCost": {
"amount": "221055913000",
"amountUSD": 0
},
"fee": {
"amount": "1000000000000000",
"amountUSD": 0
},
"amount": "100000000",
"duration": 60000000000,
"transaction": {
"data": "0x73c45c98000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000000000143e101ec02e7a48d16dade204c96bff842e7e251900000000000000000000000000000000000000000000000000000000000000000000000000000000000000023078000000000000000000000000000000000000000000000000000000000000",
"to": "0x9D5C332Ebe0DaE36e07a4eD552Ad4d8c5067A61F",
"from": "0x3E101Ec02e7a48D16DADE204C96bFF842E7E2519",
"value": "0x38d7ea4c68000",
"gasPrice": "0xf433d",
"gasLimit": "0x35f48",
"chainId": 84532
},
"approvals": null
}
]