Skip to content
LogoLogo

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

demo
1
Create an account, or use an existing one.
2
Add testnet funds to your account.
3
Send a payment with a memo for reconciliation.
pnpx gitpick tempoxyz/tempo-ts/tree/main/examples/payments

Steps

Set up your project

Ensure you have Wagmi configured with Tempo. Follow either guide to get started:

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 }) },
})

Learning Resources