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 = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' // the ERC-20 contract
const recipientAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
const amount = 1_000_000n // 1 USDC (6 decimals), as bigint in the smallest unit
const calldata = await encodeERC20Transfer(recipientAddress, amount)encodeERC20Transfer(to, amount) is asynchronous and takes the amount as a bigint in the token’s smallest unit.
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!}
initialConfig={{ network: { 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: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: '1', // human-readable amount, parsed with decimals
assetType: 'erc20',
tokenAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
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}</p>}
</div>
)
}SendTransactionParams for ERC-20
| Parameter | Type | Description |
|---|---|---|
to | string | Recipient address |
value | string | Human-readable amount (e.g. "1.5"); the hook applies decimals |
assetType | 'erc20' | Indicates an ERC-20 transfer |
tokenAddress | string | The ERC-20 contract address |
decimals | number | Token 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 encodeERC20Approve to encode an approve call:
import { createServerWalletManager, MemoryKeyshareStorage, encodeERC20Approve } from '@embarkai/core'
const manager = createServerWalletManager({
apiKey: process.env.EMBARK_API_KEY!,
chainId: 994873017,
storage: new MemoryKeyshareStorage(),
})
const wallet = await manager.getWallet('my-wallet')
const tokenAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
const approveCalldata = await encodeERC20Approve(
'0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // spender contract
1_000_000n,
)
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), use encodeERC20TransferFrom:
import { createServerWalletManager, MemoryKeyshareStorage, encodeERC20TransferFrom } from '@embarkai/core'
const manager = createServerWalletManager({
apiKey: process.env.EMBARK_API_KEY!,
chainId: 994873017,
storage: new MemoryKeyshareStorage(),
})
const wallet = await manager.getWallet('my-wallet')
const tokenAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
const transferFromCalldata = await encodeERC20TransferFrom(
wallet.smartAccountAddress,
'0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // recipient
1_000_000n,
)
const txHash = await wallet.sendUserOperation(
tokenAddress,
'0',
transferFromCalldata,
)For arbitrary ABIs use encodeContractCall(abi, functionName, args) from the Contract Encoding page.
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 = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' // the ERC-20 contract
// Encode and send an ERC-20 transfer
const calldata = await encodeERC20Transfer(
'0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
1_000_000n, // 1 USDC (6 decimals)
)
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
- Contract Encoding — Encode arbitrary contract calls (ERC-721, ERC-1155, custom ABIs)
- Gasless Transactions — Combine ERC-20 transfers with gas sponsorship
- Send Your First Transaction — Native token transfers
- Server Wallets — Full API reference for batch operations