Skip to main content
TIP-20 is Tempo’s token standard, equivalent to ERC-20 on Ethereum. Since Tempo has no native token, all value transfers use TIP-20 tokens.

Basic Transfer

import { FC, useState } from 'react';
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { isTempoWallet } from '@dynamic-labs/tempo';

const TransferTip20Button: FC = () => {
  const { primaryWallet } = useDynamicContext();
  const [txHash, setTxHash] = useState<string | null>(null);

  const onTransfer = async () => {
    if (!primaryWallet || !isTempoWallet(primaryWallet)) {
      throw new Error('Tempo wallet not found');
    }

    // Use the sendBalance method for TIP-20 transfers
    const hash = await primaryWallet.sendBalance({
      amount: '100', // Human-readable amount
      toAddress: '0xRecipientAddress...',
      token: {
        address: '0xTokenContractAddress...',
        decimals: 6, // e.g., 6 for USDC
      },
    });

    if (hash) {
      setTxHash(hash);
      console.log('Transfer hash:', hash);
    }
  };

  return (
    <div>
      <button onClick={onTransfer}>Transfer TIP-20</button>
      {txHash && <p>Transaction: {txHash}</p>}
    </div>
  );
};

Transfer with Memo

Tempo supports transfers with an attached memo using the transferWithMemo function:
import { FC, useState } from 'react';
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { isTempoWallet } from '@dynamic-labs/tempo';
import { encodeFunctionData, Hex, parseUnits } from 'viem';

// TIP-20 ABI with transferWithMemo
const tip20Abi = [
  {
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'memo', type: 'string' },
    ],
    name: 'transferWithMemo',
    outputs: [{ name: '', type: 'bool' }],
    stateMutability: 'nonpayable',
    type: 'function',
  },
] as const;

const TransferWithMemoButton: FC = () => {
  const { primaryWallet } = useDynamicContext();
  const [txHash, setTxHash] = useState<string | null>(null);

  const onTransferWithMemo = async () => {
    if (!primaryWallet || !isTempoWallet(primaryWallet)) {
      throw new Error('Tempo wallet not found');
    }

    const publicClient = await primaryWallet.getPublicClient();
    const walletClient = await primaryWallet.getWalletClient();

    const fromAddress = primaryWallet.address as Hex;
    const toAddress = '0xRecipient...' as Hex;
    const tokenAddress = '0xToken...' as Hex;
    const feeTokenAddress = '0xFeeToken...' as Hex;
    const amount = parseUnits('100', 6); // 100 tokens with 6 decimals
    const memo = 'Payment for invoice #12345';

    // Encode transferWithMemo call
    const callData = encodeFunctionData({
      abi: tip20Abi,
      functionName: 'transferWithMemo',
      args: [toAddress, amount, memo],
    });

    // Get nonce and gas
    const nonce = await publicClient.getTransactionCount({
      address: fromAddress,
    });
    const gasPrice = await publicClient.getGasPrice();

    // Build transaction
    const tempoTx = {
      calls: [
        {
          data: callData,
          to: tokenAddress,
          value: BigInt(0),
        },
      ],
      chainId: await publicClient.getChainId(),
      gas: BigInt(300000),
      maxFeePerGas: gasPrice,
      maxPriorityFeePerGas: gasPrice / BigInt(10),
      nonce,
      feeToken: feeTokenAddress,
    };

    const hash = await walletClient.sendTransaction(tempoTx);
    setTxHash(hash);
  };

  return (
    <div>
      <button onClick={onTransferWithMemo}>Transfer with Memo</button>
      {txHash && <p>Transaction: {txHash}</p>}
    </div>
  );
};

sendBalance Parameters

ParameterTypeRequiredDescription
amountstringYesHuman-readable amount to transfer
toAddressstringYesRecipient’s wallet address
token.addressstringYesTIP-20 token contract address
token.decimalsnumberNoToken decimals (defaults to 18)