Skip to Content
TutorialsERC-20 Transfers

ERC-20 Transfers

Send and receive ERC-20 tokens with your smart wallet.

This tutorial covers transferring ERC-20 tokens using both the Core SDK (backend) and the UI Kit (frontend). You will use the encodeERC20Transfer helper for backend transfers and the useSendTransaction hook with assetType: 'erc20' for frontend transfers.

Prerequisites

  • Node.js 18+ installed
  • An EmbarkAI API key from the Dashboard 
  • A deployed wallet with ERC-20 tokens (see Deploy a Smart Wallet)
  • The token contract address and decimals for the ERC-20 you want to transfer

Backend Approach

Step 1: Encode the Transfer

Use encodeERC20Transfer to generate the calldata for a standard transfer(address,uint256) call:

import { createServerWalletManager, MemoryKeyshareStorage, encodeERC20Transfer, } from '@embarkai/core' const tokenAddress = '0xTokenContractAddress' const recipientAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' const amount = '1000000' // 1 USDC (6 decimals) const calldata = encodeERC20Transfer(recipientAddress, amount)

Step 2: Send the UserOperation

Pass the encoded calldata to sendUserOperation with the token contract as the to address and 0 as the value (ERC-20 transfers do not send native tokens):

const manager = createServerWalletManager({ apiKey: process.env.EMBARK_API_KEY!, chainId: 994873017, storage: new MemoryKeyshareStorage(), }) const wallet = await manager.getWallet('my-wallet') const userOpHash = await wallet.sendUserOperation( tokenAddress, // to: the token contract '0', // value: 0 (no native transfer) calldata, // data: encoded transfer call ) const receipt = await wallet.waitForUserOperationReceipt(userOpHash) console.log('Transfer TX:', receipt.transactionHash)

Frontend Approach

Step 1: Set Up the Provider

import { Provider, ConnectWalletButton } from '@embarkai/ui-kit' function App() { return ( <Provider projectId={process.env.NEXT_PUBLIC_PROJECT_ID!} chainId={994873017} > <ConnectWalletButton /> <TokenTransfer /> </Provider> ) }

Step 2: Send an ERC-20 Transfer

Use useSendTransaction with assetType: 'erc20'. The hook handles encoding internally:

import { useSendTransaction } from '@embarkai/ui-kit' function TokenTransfer() { const { sendTransaction, isLoading, userOpHash, error } = useSendTransaction() const handleTransfer = async () => { await sendTransaction({ to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', value: '1000000', // Amount in token's smallest unit assetType: 'erc20', tokenAddress: '0xTokenContractAddress', decimals: 6, // Token decimals (e.g., 6 for USDC) }) } return ( <div> <button onClick={handleTransfer} disabled={isLoading}> {isLoading ? 'Transferring...' : 'Send 1 USDC'} </button> {userOpHash && <p>UserOp: {userOpHash}</p>} {error && <p>Error: {error.message}</p>} </div> ) }

SendTransactionParams for ERC-20

ParameterTypeDescription
tostringRecipient address
valuestringAmount in the token’s smallest unit (wei equivalent)
assetType'erc20'Indicates an ERC-20 transfer
tokenAddressstringThe ERC-20 contract address
decimalsnumberToken decimal places (e.g., 18 for most tokens, 6 for USDC)

Approve + TransferFrom Pattern

Some use cases require the two-step approve/transferFrom flow — for example, when interacting with a DEX or staking contract.

Step 1: Approve the Spender

Use encodeContractCall to encode an approve call:

import { encodeContractCall } from '@embarkai/core' const approveCalldata = encodeContractCall({ abi: ['function approve(address spender, uint256 amount) returns (bool)'], functionName: 'approve', args: ['0xSpenderContractAddress', BigInt('1000000')], }) const approveHash = await wallet.sendUserOperation( tokenAddress, '0', approveCalldata, ) await wallet.waitForUserOperationReceipt(approveHash) console.log('Approval confirmed')

Step 2: Execute TransferFrom

The spender contract can now call transferFrom on behalf of your wallet. If you need to trigger this from your own wallet (e.g., in a batch), encode it similarly:

const transferFromCalldata = encodeContractCall({ abi: ['function transferFrom(address from, address to, uint256 amount) returns (bool)'], functionName: 'transferFrom', args: [wallet.smartAccountAddress, '0xRecipientAddress', BigInt('1000000')], }) const txHash = await wallet.sendUserOperation( tokenAddress, '0', transferFromCalldata, )

Complete Backend Code

import { createServerWalletManager, MemoryKeyshareStorage, encodeERC20Transfer, } from '@embarkai/core' async function main() { const manager = createServerWalletManager({ apiKey: process.env.EMBARK_API_KEY!, chainId: 994873017, storage: new MemoryKeyshareStorage(), }) const wallet = await manager.getWallet('my-wallet') const tokenAddress = '0xTokenContractAddress' // Encode and send an ERC-20 transfer const calldata = encodeERC20Transfer( '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', '1000000', // 1 USDC ) const userOpHash = await wallet.sendUserOperation( tokenAddress, '0', calldata, ) const receipt = await wallet.waitForUserOperationReceipt(userOpHash) if (receipt.success) { console.log('ERC-20 transfer confirmed:', receipt.transactionHash) } } main().catch(console.error)

Next Steps

Last updated on