Skip to main content

SmartTransferAction

The SmartTransferAction enables completely gasless token transfers across multiple blockchain networks. This action allows users to send both native tokens (like ETH) and ERC-20 tokens to any address without requiring gas fees.

Overview​

Token transfers are the most fundamental blockchain operation. The SmartTransferAction makes transfers accessible to everyone by:

  • Removing the need for users to own native tokens for gas
  • Handling both native token and ERC-20 token transfers
  • Automatically determining token decimals
  • Providing transaction status monitoring
  • Supporting multiple EVM-compatible chains

All transfers are executed using the 0xGasless infrastructure, enabling users to transact without the complexity of managing gas.

Supported Networks​

  • Avalanche C-Chain
  • BNB Chain
  • Sonic
  • Base
  • Moonbeam

Input Parameters​

ParameterTypeDescriptionRequiredDefault
amountstringAmount of tokens to transferYes-
tokenAddressstringContract address or 'eth' for native transfersYes-
destinationstringRecipient addressYes-
waitbooleanWhether to wait for transaction confirmationNotrue

Code Implementation​

The core implementation handles both native token and ERC-20 token transfers:

export async function smartTransfer(
wallet: ZeroXgaslessSmartAccount,
args: z.infer<typeof SmartTransferInput>,
): Promise<string> {
try {
const isEth = args.tokenAddress.toLowerCase() === "eth";
let tx: Transaction;

if (isEth) {
// Native ETH transfer
tx = {
to: args.destination as `0x${string}`,
data: "0x",
value: parseEther(args.amount),
};
} else {
// ERC20 token transfer
const decimals = await wallet.rpcProvider.readContract({
abi: TokenABI,
address: args.tokenAddress as `0x${string}`,
functionName: "decimals",
});
const data = encodeFunctionData({
abi: TokenABI,
functionName: "transfer",
args: [
args.destination as `0x${string}`,
parseUnits(args.amount, (decimals as number) || 18),
],
});

tx = {
to: args.tokenAddress as `0x${string}`,
data,
value: 0n,
};
}

const response = await sendTransaction(wallet, tx);
if (!response || !response.success) {
return `Transaction failed: ${response?.error || "Unknown error"}`;
}

if (args.wait) {
const status = await waitForTransaction(wallet, response.userOpHash);
// Return confirmation details
}

// Return submission details
} catch (error) {
return `Error transferring the asset: ${error}`;
}
}

Basic Usage Examples​

Transfer ERC-20 Token (USDC, USDT, etc.)​

// Transfer 10 USDC to an address
const result = await agent.runAction("smart_transfer", {
amount: "10",
tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC contract address
destination: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
});

console.log(result);
// Output example:
// Successfully transferred 10 tokens from contract 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e.
// Transaction confirmed in block 12345678!

Transfer Without Waiting for Confirmation​

// Transfer tokens without waiting for confirmation
const result = await agent.runAction("smart_transfer", {
amount: "5",
tokenAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT contract address
destination: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
wait: false
});

console.log(result);
// Output includes userOpHash to check status later

Advanced Examples​

Complete Transfer Workflow​

This example demonstrates a complete workflow of checking balances, performing a transfer, and verifying the transaction:

// Step 1: Check current balance
const balanceResult = await agent.runAction("get_balance", {
tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // USDC
});
console.log("Current balance:");
console.log(balanceResult);

// Step 2: Execute the transfer
const transferResult = await agent.runAction("smart_transfer", {
amount: "10",
tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
destination: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
wait: true
});
console.log("Transfer result:");
console.log(transferResult);

// Step 3: Extract transaction hash and check details
const txHash = transferResult.match(/Transaction confirmed in block (\d+)!/)[0];
const txDetails = await agent.runAction("check_transaction", {
transactionHash: txHash
});
console.log("Transaction details:");
console.log(txDetails);

// Step 4: Check updated balance
const updatedBalance = await agent.runAction("get_balance", {
tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // USDC
});
console.log("Updated balance:");
console.log(updatedBalance);

How It Works​

The SmartTransferAction works differently depending on the type of token being transferred:

ERC-20 Token Transfers​

For ERC-20 tokens, the action:

  1. First queries the token contract to determine its decimals
  2. Encodes the transfer function call with the proper amount
  3. Creates a transaction to the token contract
// ERC20 token transfer
const decimals = await wallet.rpcProvider.readContract({
abi: TokenABI,
address: args.tokenAddress as `0x${string}`,
functionName: "decimals",
});

const data = encodeFunctionData({
abi: TokenABI,
functionName: "transfer",
args: [
args.destination as `0x${string}`,
parseUnits(args.amount, (decimals as number) || 18),
],
});

tx = {
to: args.tokenAddress as `0x${string}`, // The token contract
data, // Encoded transfer function call
value: 0n, // No ETH value sent with an ERC-20 transfer
};

Transaction Processing​

After constructing the transaction, it's processed through the 0xGasless infrastructure:

const response = await sendTransaction(wallet, tx);

// If waiting for confirmation was requested
if (args.wait) {
const status = await waitForTransaction(wallet, response.userOpHash);
// Check status and return result
}

Response Formats​

Successful Transfer (With Waiting)​

When the transfer completes successfully and you've set wait: true:

For ERC-20 tokens:

Successfully transferred 10 tokens from contract 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e.
Transaction confirmed in block 12345678!

Successful Transfer Submission (Without Waiting)​

When the transfer is submitted but you've set wait: false:

Successfully transferred 10 tokens from contract 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e.
Transaction submitted successfully!

You can either:
1. Check the status by asking:
- "What's the status of transaction 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef?"
- "check for the status of transaction hash above"
2. Or next time, instruct the agent to wait for confirmation, like: "Transfer 10 Tokens to 0x... and wait for confirmation"

Error Responses​

The action handles various error scenarios:

Transaction failed: Insufficient balance for transfer
Error transferring the asset: Error: [transaction error details]

How to Handle Decimal Precision​

The action automatically handles decimal precision for different tokens:

  • For ERC-20 tokens, it queries the contract for decimals and uses that value

This ensures that user-friendly amounts (like "10.5") are correctly converted to the token's raw units.

Best Practices​

For optimal results with the SmartTransferAction:

  1. Double-check recipient addresses before submitting transfers
  2. Verify token contract addresses when transferring ERC-20 tokens
  3. Use wait: true (the default) to ensure transfers complete successfully
  4. Check balances before transferring to ensure sufficient funds
  5. Handle errors gracefully in your application logic