Attach a Transfer Memo
Attach 32-byte references to TIP-20 transfers for payment reconciliation. Use memos to link onchain transactions to your internal records—customer IDs, invoice numbers, or any identifier that helps you match payments to your database.
Demo
Transfer with Memo
demoSteps
Send a transfer with memo
Use transferWithMemo to attach a reference to your payment. The memo is a 32-byte value that gets emitted in the TransferWithMemo event.
import { } from 'wagmi/tempo'
import { , } from 'viem'
import { } from 'wagmi'
export function () {
const { } = ()
const = ..()
const = () => {
.({
: '0x20c0000000000000000000000000000000000001',
: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb',
: ('100', 6),
: ('INV-12345', { : 32 }),
})
}
return (
< ={} ={.}>
{. ? 'Sending...' : 'Send Payment'}
</>
)
}Watch for transfers with memos
Listen for TransferWithMemo events to reconcile incoming payments. The memo is indexed, so you can filter by specific values.
import { } from 'wagmi'
import { } from 'viem'
import { } from 'viem/tempo'
export function ({ }: { : `0x${string}` }) {
({
: '0x20c0000000000000000000000000000000000001',
: .TIP20,
: 'TransferWithMemo',
: () => {
for (const of ) {
if (.args.to === ) {
const = (.args.memo, 'string').(/\0/g, '')
.(`Received ${.args.value} with memo: ${}`)
}
}
},
})
return <>Watching for deposits...</>
}Recipes
Exchange deposit reconciliation
As an exchange, use a single master hot wallet for all customer deposits. Customers include their customer ID as the memo, and you credit their account by parsing the event.
import { Actions } from 'viem/tempo'
import { parseUnits, stringToHex, pad } from 'viem'
// Customer deposits with their customer ID
await Actions.token.transferSync(walletClient, {
token: tokenAddress,
to: exchangeHotWallet,
amount: parseUnits('500', 6),
memo: pad(stringToHex('CUST-12345'), { size: 32 }),
})Payroll batch payments
Batch multiple payments in a single Tempo transaction with employee IDs in each memo for clear accounting records.
import { Abis } from 'viem/tempo'
import { encodeFunctionData, parseUnits, stringToHex, pad } from 'viem'
const calls = employees.map(emp => ({
to: tokenAddress,
data: encodeFunctionData({
abi: Abis.TIP20,
functionName: 'transferWithMemo',
args: [emp.wallet, parseUnits(emp.salary, 6), pad(stringToHex(emp.id), { size: 32 })]
})
}))
await walletClient.sendCalls({ calls })Refund address in memo
Include a refund address in the memo so the recipient knows where to send funds if a reversal is needed.
import { Actions } from 'viem/tempo'
import { parseUnits, stringToHex, pad } from 'viem'
const refundMemo = pad(stringToHex('REFUND 0x742d35Cc6634C0532925a3b8'), { size: 32 })
await Actions.token.transferSync(walletClient, {
token: tokenAddress,
to: merchantAddress,
amount: parseUnits('100', 6),
memo: refundMemo,
})Best Practices
Use consistent memo formats
Establish a naming convention for your memos (e.g., CUST-{id}, INV-{number}, REFUND-{id}) to make parsing and filtering reliable across your system.
Keep memos under 32 bytes
Memos are bytes32 values. Use toHex(string, { size: 32 }) to convert strings—if your string exceeds 32 bytes, it will be truncated. For longer references, store the full data offchain and use a hash or short ID as the memo.
Index memos for efficient queries
The TransferWithMemo event has memo as an indexed parameter. Use getLogs with the args filter to query transactions by memo without scanning all events.
import { parseAbiItem, stringToHex, pad } from 'viem'
const logs = await client.getLogs({
address: tokenAddress,
event: parseAbiItem('event TransferWithMemo(address indexed from, address indexed to, uint256 value, bytes32 indexed memo)'),
args: { memo: pad(stringToHex('INV-12345'), { size: 32 }) },
})