# How to send a stablecoin payment on Tempo

Send stablecoin payments between accounts on Tempo. Payments can include optional memos for reconciliation and tracking.

:::warning[Confirm delivery, not just transaction success]
On post-T6 networks, a blocked TIP-20 `transfer` / `transferFrom` still succeeds, but credits `ReceivePolicyGuard` at `0xB10C000000000000000000000000000000000000` instead of the intended receiver.

* Before marking a payment, withdrawal, or deposit delivered, confirm the `Transfer` recipient is the intended receiver or the master wallet for its virtual address.
* Index `ReceivePolicyGuard.TransferBlocked` so redirected funds can be surfaced and claimed later.

See [Configure Receive Policies](https://tempo.xyz/developers/docs/guide/payments/configure-receive-policies).
:::

## Send payment demo

By the end of this guide you will be able to send payments on Tempo with an optional memo.

**Interactive demo: Send a Payment**

1. Connect
2. Add funds
3. Send payment

Source: [tempoxyz/examples/tree/main/examples/payments](https://github.com/tempoxyz/examples/tree/main/examples/payments)

## Send payment implementation steps

::::steps
### Set up Wagmi for Tempo payments

Ensure that you have set up your project with Wagmi, a Tempo chain config, and a wallet connector:

* [Connection details](https://tempo.xyz/developers/docs/quickstart/connection-details)
* [TypeScript SDK](https://tempo.xyz/developers/docs/sdk/typescript)
* [Wallet integration](https://tempo.xyz/developers/docs/quickstart/wallet-developers)

### Add testnet stablecoin funds¹

Before you can send a payment, you need to fund your account. In this guide you will be sending `AlphaUSD` (`0x20c000…0001`).

The built-in Tempo testnet faucet funds accounts with `AlphaUSD`.

**Interactive demo: Add Funds**

1. Add funds

:::code-group
```tsx twoslash [AddFunds.ts]
// @noErrors
import { Hooks } from 'wagmi/tempo'
import { useConnection } from 'wagmi'

function AddFunds() {
  const { address } = useConnection()
  const { mutate, isPending } = Hooks.faucet.useFundSync()

  return (
    <button onClick={() => mutate({ account: address })} disabled={isPending}>
      Add Funds
    </button>
  )
}
```

```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts"
// @noErrors
import { tempo } from 'viem/chains'
import { createConfig, http } from 'wagmi'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  connectors: [tempoWallet()],
  chains: [tempo],
  multiInjectedProviderDiscovery: false,
  transports: {
    [tempo.id]: http(),
  },
})
```
:::

:::warning
¹ It is important to note that the `addFunds` Hook only works on testnets as a convenience feature to get
started quickly. For production, you will need to onramp & fund your account manually.
:::

### Add TIP-20 transfer logic

Now that you have `AlphaUSD` you are ready to add logic to send a payment with an optional memo.

**Interactive demo: Send Payment**

1. Add funds
2. Send payment

:::code-group
```tsx twoslash [SendPaymentWithMemo.tsx]
import { Hooks } from 'wagmi/tempo'
import { parseUnits, stringToHex, pad } from 'viem'

// @noErrors
function SendPaymentWithMemo() {
  const sendPayment = Hooks.token.useTransferSync() // [!code hl]

  return (
    <form onSubmit={
      (event) => {
        event.preventDefault()
        const formData = new FormData(event.target as HTMLFormElement)

        const recipient = (formData.get('recipient') ||
          '0x0000000000000000000000000000000000000000') as `0x${string}`
        const memo = (formData.get('memo') || '') as string

        sendPayment.mutate({ // [!code hl]
          amount: parseUnits('100', 6), // [!code hl]
          to: recipient, // [!code hl]
          token: '0x20c0000000000000000000000000000000000001', // [!code hl]
          memo: memo ? pad(stringToHex(memo), { size: 32 }) : undefined, // [!code hl]
        }) // [!code hl]
      }
    }>
      <div>
        <label htmlFor="recipient">Recipient address</label>
        <input type="text" name="recipient" placeholder="0x..." />
      </div>
      <div>
        <label htmlFor="memo">Memo (optional)</label>
        <input type="text" name="memo" placeholder="Optional memo" />
      </div>
      <button // [!code hl]
        type="submit" // [!code hl]
        disabled={sendPayment.isPending} // [!code hl]
      > {/* [!code hl] */}
        Send Payment {/* [!code hl] */}
      </button> {/* [!code hl] */}
    </form>
  )
}
```

```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts"
// @noErrors
import { tempo } from 'viem/chains'
import { createConfig, http } from 'wagmi'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  connectors: [tempoWallet()],
  chains: [tempo],
  multiInjectedProviderDiscovery: false,
  transports: {
    [tempo.id]: http(),
  },
})
```
:::

### Display the payment receipt

Now that you can send a payment, you can display the transaction receipt on success.

:::code-group
```tsx twoslash [SendPaymentWithMemo.tsx]
import { Hooks } from 'wagmi/tempo'
import { parseUnits, stringToHex, pad } from 'viem'

// @noErrors
function SendPaymentWithMemo() {
  const sendPayment = Hooks.token.useTransferSync()

  return (
    <>
      {/* ... your payment form ... */}
      {sendPayment.data && ( // [!code ++]
        <a href={`https://explore.tempo.xyz/tx/${sendPayment.data.receipt.transactionHash}`}> {/* [!code ++] */}
          View receipt {/* [!code ++] */}
        </a> {/* [!code ++] */}
      )} {/* [!code ++] */}
    </>
  )
}
```

```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts"
// @noErrors
import { tempo } from 'viem/chains'
import { createConfig, http } from 'wagmi'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  connectors: [tempoWallet()],
  chains: [tempo],
  multiInjectedProviderDiscovery: false,
  transports: {
    [tempo.id]: http(),
  },
})
```
:::

### Next steps after sending a payment

Now that you have made a payment you can

* **[Accept a payment](https://tempo.xyz/developers/docs/guide/payments/accept-a-payment)** to receive payments in your application
* Learn about [Tempo Transactions](https://tempo.xyz/developers/docs/protocol/transactions) for batching, sponsorship, scheduling, and more
* Send a payment [with a specific fee token](https://tempo.xyz/developers/docs/guide/payments/pay-fees-in-any-stablecoin)
::::

## Stablecoin payment recipes

### Basic TIP-20 transfer

Send a payment using the standard `transfer` function:

#### Viem

:::code-group
```ts twoslash [example.ts]
// @noErrors
import { parseUnits } from 'viem'
import { client } from './viem.config'

const { receipt } = await client.token.transferSync({
  amount: parseUnits('100', 6), // 100 tokens (6 decimals) // [!code hl]
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', // [!code hl]
  token: '0x20c0000000000000000000000000000000000001', // AlphaUSD // [!code hl]
})
```

```ts twoslash [viem.config.ts]
import { privateKeyToAccount } from 'viem/accounts'
import { createClient } from 'viem/tempo'

export const client = createClient({
  account: privateKeyToAccount('0x...'),
})
```
:::

#### Wagmi

```tsx twoslash
// @noErrors
import { Hooks } from 'wagmi/tempo'
import { parseUnits } from 'viem'

function SendPayment() {
  const { mutate, isPending } = Hooks.token.useTransferSync() // [!code hl]

  return (
    <button
      onClick={() =>
        mutate({ // [!code hl]
          amount: parseUnits('100', 6), // [!code hl]
          to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', // [!code hl]
          token: '0x20c0000000000000000000000000000000000001', // [!code hl]
        }) // [!code hl]
      }
      disabled={isPending}
    >
      Send Payment
    </button>
  )
}
```

#### Rust

:::code-group
```rust [example.rs]
use alloy::{
    primitives::{address, U256},
    providers::ProviderBuilder,
};
use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20};

mod provider;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = provider::get_provider().await?;

    let token = ITIP20::new( // [!code hl]
        address!("0x20c0000000000000000000000000000000000001"), // AlphaUSD // [!code hl]
        &provider, // [!code hl]
    ); // [!code hl]

    let receipt = token // [!code hl]
        .transfer( // [!code hl]
            address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"), // [!code hl]
            U256::from(100_000_000), // 100 tokens (6 decimals) // [!code hl]
        ) // [!code hl]
        .send() // [!code hl]
        .await? // [!code hl]
        .get_receipt() // [!code hl]
        .await?; // [!code hl]

    println!("Transfer successful: {:?}", receipt.transaction_hash);

    Ok(())
}
```

```rust [provider.rs]
use alloy::providers::ProviderBuilder;
use alloy::signers::local::PrivateKeySigner;
use tempo_alloy::TempoNetwork;

pub async fn get_provider() -> Result<
    impl alloy::providers::Provider<TempoNetwork>,
    Box<dyn std::error::Error>,
> {
    let signer: PrivateKeySigner = std::env::var("PRIVATE_KEY")
        .expect("PRIVATE_KEY not set")
        .parse()?;

    let provider = ProviderBuilder::new_with_network::<TempoNetwork>()
        .wallet(signer)
        .connect(&std::env::var("RPC_URL").expect("RPC_URL not set"))
        .await?;

    Ok(provider)
}
```
:::

#### Python

:::code-group
```python [example.py]
from eth_abi import encode
from pytempo import Call, TempoTransaction
from provider import w3, account

TRANSFER_SELECTOR = bytes.fromhex("a9059cbb")
ALPHA_USD = "0x20c0000000000000000000000000000000000001"

data = TRANSFER_SELECTOR + encode( # [!code hl]
    ["address", "uint256"], # [!code hl]
    ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", 100_000_000], # [!code hl]
) # [!code hl]

tx = TempoTransaction.create(
    chain_id=w3.eth.chain_id,
    gas_limit=100_000,
    max_fee_per_gas=w3.eth.gas_price * 2,
    max_priority_fee_per_gas=w3.eth.gas_price,
    nonce=w3.eth.get_transaction_count(account.address),
    calls=(
        Call.create(to=ALPHA_USD, data="0x" + data.hex()), # [!code hl]
    ),
)

signed_tx = tx.sign(account.key.hex())
tx_hash = w3.eth.send_raw_transaction(signed_tx.encode())
```

```python [provider.py]
from web3 import Web3
from eth_account import Account

w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz"))
account = Account.from_key("0x...")
```
:::

#### Go

:::code-group
```go [main.go]
package main

import (
	"context"
	"log"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/tempoxyz/tempo-go/pkg/signer"
	"github.com/tempoxyz/tempo-go/pkg/transaction"
)

func buildTransferData(to common.Address, amount *big.Int) []byte {
	data := make([]byte, 68) // 4 (selector) + 32 (address) + 32 (uint256)
	data[0], data[1], data[2], data[3] = 0xa9, 0x05, 0x9c, 0xbb
	copy(data[16:36], to.Bytes())
	amount.FillBytes(data[36:68])
	return data
}

func main() {
	sgn, _ := signer.NewSigner("0x...")
	c := newClient()
	ctx := context.Background()

	nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex())

	recipient := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")
	alphaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000001")

	tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)).
		SetNonce(nonce).
		SetGas(100_000).
		SetMaxFeePerGas(big.NewInt(25_000_000_000)).
		SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)).
		AddCall( // [!code hl]
			alphaUSD, // [!code hl]
			big.NewInt(0), // [!code hl]
			buildTransferData(recipient, big.NewInt(100_000_000)), // [!code hl]
		). // [!code hl]
		Build()

	_ = transaction.SignTransaction(tx, sgn)
	serialized, _ := transaction.Serialize(tx, nil)
	txHash, _ := c.SendRawTransaction(ctx, serialized)

	log.Printf("Transaction hash: %s", txHash)
}
```

```go [provider.go]
package main

import (
	"os"

	"github.com/tempoxyz/tempo-go/pkg/client"
)

func newClient() *client.Client {
	return client.New(os.Getenv("TEMPO_RPC_URL"))
}
```
:::

#### Cast

```bash
$ cast erc20 transfer \
  0x20c0000000000000000000000000000000000001 \
  0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \
  100000000 \
  --rpc-url $TEMPO_RPC_URL \
  --private-key $PRIVATE_KEY # [!code hl]
```

#### Solidity

```solidity
import {ITIP20} from "tempo-std/interfaces/ITIP20.sol";

contract PaymentSender {
    ITIP20 public token;

    function sendPayment(address recipient, uint256 amount) external {
        token.transfer(recipient, amount); // [!code hl]
    }
}
```

### Transfer with memo

Include a memo for payment reconciliation and tracking. The memo is a 32-byte value that can store payment references, invoice IDs, order numbers, or any other metadata.

#### Viem

:::code-group
```ts twoslash [example.ts]
// @noErrors
import { parseUnits, stringToHex, pad } from 'viem'
import { client } from './viem.config'

const invoiceId = pad(stringToHex('INV-12345'), { size: 32 }) // [!code hl]

const { receipt } = await client.token.transferSync({
  amount: parseUnits('100', 6),
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb',
  token: '0x20c0000000000000000000000000000000000001',
  memo: invoiceId, // [!code hl]
})
```

```ts twoslash [viem.config.ts]
import { privateKeyToAccount } from 'viem/accounts'
import { createClient } from 'viem/tempo'

export const client = createClient({
  account: privateKeyToAccount('0x...'),
})
```
:::

#### Wagmi

```tsx twoslash
// @noErrors
import { Hooks } from 'wagmi/tempo'
import { parseUnits, stringToHex, pad } from 'viem'

function SendPaymentWithMemo() {
  const { mutate, isPending } = Hooks.token.useTransferSync()

  return (
    <button
      onClick={() =>
        mutate({
          amount: parseUnits('100', 6),
          to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb',
          token: '0x20c0000000000000000000000000000000000001',
          memo: pad(stringToHex('INV-12345'), { size: 32 }), // [!code hl]
        })
      }
      disabled={isPending}
    >
      Send Payment
    </button>
  )
}
```

#### Rust

:::code-group
```rust [example.rs]
use alloy::{
    primitives::{address, B256, U256},
    providers::ProviderBuilder,
};
use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20};

mod provider;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = provider::get_provider().await?;

    let token = ITIP20::new(
        address!("0x20c0000000000000000000000000000000000001"),
        &provider,
    );

    let receipt = token
        .transferWithMemo( // [!code hl]
            address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"),
            U256::from(100_000_000),
            B256::left_padding_from("INV-12345".as_bytes()), // [!code hl]
        )
        .send()
        .await?
        .get_receipt()
        .await?;

    println!("Transfer successful: {:?}", receipt.transaction_hash);

    Ok(())
}
```

```rust [provider.rs]
use alloy::providers::ProviderBuilder;
use alloy::signers::local::PrivateKeySigner;
use tempo_alloy::TempoNetwork;

pub async fn get_provider() -> Result<
    impl alloy::providers::Provider<TempoNetwork>,
    Box<dyn std::error::Error>,
> {
    let signer: PrivateKeySigner = std::env::var("PRIVATE_KEY")
        .expect("PRIVATE_KEY not set")
        .parse()?;

    let provider = ProviderBuilder::new_with_network::<TempoNetwork>()
        .wallet(signer)
        .connect(&std::env::var("RPC_URL").expect("RPC_URL not set"))
        .await?;

    Ok(provider)
}
```
:::

#### Python

:::code-group
```python [example.py]
from eth_abi import encode
from pytempo import Call, TempoTransaction
from provider import w3, account

TRANSFER_WITH_MEMO_SELECTOR = bytes.fromhex("76a8ee59")
ALPHA_USD = "0x20c0000000000000000000000000000000000001"

memo = b"INV-12345" + b"\x00" * 23  # right-pad to 32 bytes # [!code hl]

data = TRANSFER_WITH_MEMO_SELECTOR + encode( # [!code hl]
    ["address", "uint256", "bytes32"], # [!code hl]
    ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", 100_000_000, memo], # [!code hl]
) # [!code hl]

tx = TempoTransaction.create(
    chain_id=w3.eth.chain_id,
    gas_limit=100_000,
    max_fee_per_gas=w3.eth.gas_price * 2,
    max_priority_fee_per_gas=w3.eth.gas_price,
    nonce=w3.eth.get_transaction_count(account.address),
    calls=(
        Call.create(to=ALPHA_USD, data="0x" + data.hex()),
    ),
)

signed_tx = tx.sign(account.key.hex())
tx_hash = w3.eth.send_raw_transaction(signed_tx.encode())
```

```python [provider.py]
from web3 import Web3
from eth_account import Account

w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz"))
account = Account.from_key("0x...")
```
:::

#### Go

:::code-group
```go [main.go]
package main

import (
	"context"
	"log"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/tempoxyz/tempo-go/pkg/signer"
	"github.com/tempoxyz/tempo-go/pkg/transaction"
)

func buildTransferWithMemoData(to common.Address, amount *big.Int, memo [32]byte) []byte {
	data := make([]byte, 100) // 4 + 32 + 32 + 32
	data[0], data[1], data[2], data[3] = 0x76, 0xa8, 0xee, 0x59
	copy(data[16:36], to.Bytes())
	amount.FillBytes(data[36:68])
	copy(data[68:100], memo[:])
	return data
}

func main() {
	sgn, _ := signer.NewSigner("0x...")
	c := newClient()
	ctx := context.Background()

	nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex())

	recipient := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")
	alphaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000001")

	var memo [32]byte // [!code hl]
	copy(memo[:], []byte("INV-12345")) // [!code hl]

	tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)).
		SetNonce(nonce).
		SetGas(100_000).
		SetMaxFeePerGas(big.NewInt(25_000_000_000)).
		SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)).
		AddCall( // [!code hl]
			alphaUSD, // [!code hl]
			big.NewInt(0), // [!code hl]
			buildTransferWithMemoData(recipient, big.NewInt(100_000_000), memo), // [!code hl]
		). // [!code hl]
		Build()

	_ = transaction.SignTransaction(tx, sgn)
	serialized, _ := transaction.Serialize(tx, nil)
	txHash, _ := c.SendRawTransaction(ctx, serialized)

	log.Printf("Transaction hash: %s", txHash)
}
```

```go [provider.go]
package main

import (
	"os"

	"github.com/tempoxyz/tempo-go/pkg/client"
)

func newClient() *client.Client {
	return client.New(os.Getenv("TEMPO_RPC_URL"))
}
```
:::

#### Cast

```bash
$ cast send \
  0x20c0000000000000000000000000000000000001 \
  "transferWithMemo(address,uint256,bytes32)" \
  0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \
  100000000 \
  $(cast --format-bytes32-string "INV-12345") \
  --rpc-url $TEMPO_RPC_URL \
  --private-key $PRIVATE_KEY # [!code hl]
```

#### Solidity

```solidity
import {ITIP20} from "tempo-std/interfaces/ITIP20.sol";

contract PaymentSender {
    ITIP20 public token;

    function sendPaymentWithMemo(
        address recipient,
        uint256 amount,
        bytes32 invoiceId
    ) external {
        token.transferWithMemo(recipient, amount, invoiceId); // [!code hl]
    }
}
```

### Batch payment transactions

Send multiple payments in a single transaction using batch transactions:

#### Viem

:::code-group
```ts twoslash [example.ts]
// @noErrors
import { encodeFunctionData, parseUnits } from 'viem'
import { Abis } from 'viem/tempo'
import { client } from './viem.config'

const token = '0x20c0000000000000000000000000000000000001' as const
const payments = [
  { to: '0x742d35cc6634c0532925a3b844bc9e7595f0bebb', amount: parseUnits('100', 6) },
  { to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', amount: parseUnits('50', 6) },
] as const

const calls = payments.map(({ to, amount }) => ({ // [!code hl]
  to: token, // [!code hl]
  data: encodeFunctionData({ // [!code hl]
    abi: Abis.tip20, // [!code hl]
    functionName: 'transfer', // [!code hl]
    args: [to, amount], // [!code hl]
  }), // [!code hl]
})) // [!code hl]

await client.sendTransaction({ calls }) // [!code hl]
```

```ts twoslash [viem.config.ts]
import { privateKeyToAccount } from 'viem/accounts'
import { createClient } from 'viem/tempo'

export const client = createClient({
  account: privateKeyToAccount('0x...'),
})
```
:::

#### Wagmi

```tsx twoslash
// @noErrors
import { useSendTransactionSync } from 'wagmi'
import { encodeFunctionData, parseUnits } from 'viem'
import { Abis } from 'viem/tempo'

function BatchPayment() {
  const { sendTransactionSync, isPending } = useSendTransactionSync()

  const tokenABI = Abis.tip20
  const token = '0x20c0000000000000000000000000000000000001'

  return (
    <button
      onClick={() =>
        sendTransactionSync({
          calls: [ // [!code hl]
            { // [!code hl]
              to: token, // [!code hl]
              data: encodeFunctionData({ // [!code hl]
                abi: tokenABI, // [!code hl]
                functionName: 'transfer', // [!code hl]
                args: ['0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', parseUnits('100', 6)], // [!code hl]
              }), // [!code hl]
            }, // [!code hl]
            { // [!code hl]
              to: token, // [!code hl]
              data: encodeFunctionData({ // [!code hl]
                abi: tokenABI, // [!code hl]
                functionName: 'transfer', // [!code hl]
                args: ['0x70997970C51812dc3A010C7d01b50e0d17dc79C8', parseUnits('50', 6)], // [!code hl]
              }), // [!code hl]
            }, // [!code hl]
          ], // [!code hl]
        })
      }
      disabled={isPending}
    >
      Send Batch Payment
    </button>
  )
}
```

#### Rust

:::code-group
```rust [example.rs]
use alloy::{
    primitives::{address, Address, U256},
    providers::{Provider, ProviderBuilder},
    sol_types::SolCall,
};
use tempo_alloy::{
    TempoNetwork, contracts::precompiles::ITIP20, primitives::transaction::Call,
    rpc::TempoTransactionRequest,
};

mod provider;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = provider::get_provider().await?;

    let recipient1 = address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb");
    let recipient2 = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
    let token_address: Address = address!("0x20c0000000000000000000000000000000000001");

    let calls = vec![ // [!code hl]
        Call { // [!code hl]
            to: token_address.into(), // [!code hl]
            input: ITIP20::transferCall { // [!code hl]
                to: recipient1, // [!code hl]
                amount: U256::from(100_000_000), // [!code hl]
            } // [!code hl]
            .abi_encode() // [!code hl]
            .into(), // [!code hl]
            value: U256::ZERO, // [!code hl]
        }, // [!code hl]
        Call { // [!code hl]
            to: token_address.into(), // [!code hl]
            input: ITIP20::transferCall { // [!code hl]
                to: recipient2, // [!code hl]
                amount: U256::from(50_000_000), // [!code hl]
            } // [!code hl]
            .abi_encode() // [!code hl]
            .into(), // [!code hl]
            value: U256::ZERO, // [!code hl]
        }, // [!code hl]
    ]; // [!code hl]

    let pending = provider
        .send_transaction(TempoTransactionRequest {
            calls,
            ..Default::default()
        })
        .await?;
    let tx_hash = pending.tx_hash();

    println!("Batch transaction sent: {tx_hash:?}");

    Ok(())
}
```

```rust [provider.rs]
use alloy::providers::ProviderBuilder;
use alloy::signers::local::PrivateKeySigner;
use tempo_alloy::TempoNetwork;

pub async fn get_provider() -> Result<
    impl alloy::providers::Provider<TempoNetwork>,
    Box<dyn std::error::Error>,
> {
    let signer: PrivateKeySigner = std::env::var("PRIVATE_KEY")
        .expect("PRIVATE_KEY not set")
        .parse()?;

    let provider = ProviderBuilder::new_with_network::<TempoNetwork>()
        .wallet(signer)
        .connect(&std::env::var("RPC_URL").expect("RPC_URL not set"))
        .await?;

    Ok(provider)
}
```
:::

#### Python

:::code-group
```python [example.py]
from eth_abi import encode
from pytempo import Call, TempoTransaction
from provider import w3, account

TRANSFER_SELECTOR = bytes.fromhex("a9059cbb")
ALPHA_USD = "0x20c0000000000000000000000000000000000001"

def build_transfer(to: str, amount: int) -> str:
    data = TRANSFER_SELECTOR + encode(
        ["address", "uint256"], [to, amount]
    )
    return "0x" + data.hex()

tx = TempoTransaction.create(
    chain_id=w3.eth.chain_id,
    gas_limit=200_000,
    max_fee_per_gas=w3.eth.gas_price * 2,
    max_priority_fee_per_gas=w3.eth.gas_price,
    nonce=w3.eth.get_transaction_count(account.address),
    calls=( # [!code hl]
        Call.create( # [!code hl]
            to=ALPHA_USD, # [!code hl]
            data=build_transfer("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", 100_000_000), # [!code hl]
        ), # [!code hl]
        Call.create( # [!code hl]
            to=ALPHA_USD, # [!code hl]
            data=build_transfer("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 50_000_000), # [!code hl]
        ), # [!code hl]
    ), # [!code hl]
)

signed_tx = tx.sign(account.key.hex())
tx_hash = w3.eth.send_raw_transaction(signed_tx.encode())
```

```python [provider.py]
from web3 import Web3
from eth_account import Account

w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz"))
account = Account.from_key("0x...")
```
:::

#### Go

:::code-group
```go [main.go]
package main

import (
	"context"
	"log"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/tempoxyz/tempo-go/pkg/signer"
	"github.com/tempoxyz/tempo-go/pkg/transaction"
)

func buildTransferData(to common.Address, amount *big.Int) []byte {
	data := make([]byte, 68)
	data[0], data[1], data[2], data[3] = 0xa9, 0x05, 0x9c, 0xbb
	copy(data[16:36], to.Bytes())
	amount.FillBytes(data[36:68])
	return data
}

func main() {
	sgn, _ := signer.NewSigner("0x...")
	c := newClient()
	ctx := context.Background()

	nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex())

	recipient1 := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")
	recipient2 := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8")
	alphaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000001")

	tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)).
		SetNonce(nonce).
		SetGas(200_000).
		SetMaxFeePerGas(big.NewInt(25_000_000_000)).
		SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)).
		AddCall(alphaUSD, big.NewInt(0), buildTransferData(recipient1, big.NewInt(100_000_000))). // [!code hl]
		AddCall(alphaUSD, big.NewInt(0), buildTransferData(recipient2, big.NewInt(50_000_000))). // [!code hl]
		Build()

	_ = transaction.SignTransaction(tx, sgn)
	serialized, _ := transaction.Serialize(tx, nil)
	txHash, _ := c.SendRawTransaction(ctx, serialized)

	log.Printf("Batch transaction sent: %s", txHash)
}
```

```go [provider.go]
package main

import (
	"os"

	"github.com/tempoxyz/tempo-go/pkg/client"
)

func newClient() *client.Client {
	return client.New(os.Getenv("TEMPO_RPC_URL"))
}
```
:::

#### Cast

```bash
$ cast batch-send \
  --rpc-url $TEMPO_RPC_URL \
  --private-key $PRIVATE_KEY \
  --call "0x20c0000000000000000000000000000000000001::transfer(address,uint256):0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb,100000000" \
  --call "0x20c0000000000000000000000000000000000001::transfer(address,uint256):0x70997970C51812dc3A010C7d01b50e0d17dc79C8,50000000" # [!code hl]
```

#### Solidity

```solidity
import {ITIP20} from "tempo-std/interfaces/ITIP20.sol";

contract BatchPaymentSender {
    ITIP20 public token;

    struct Payment {
        address recipient;
        uint256 amount;
    }

    function batchPay(Payment[] calldata payments) external {
        for (uint256 i = 0; i < payments.length; i++) {
            token.transfer(payments[i].recipient, payments[i].amount); // [!code hl]
        }
    }
}
```

Pass every encoded call together so Tempo executes all transfers in one all-or-nothing transaction.

### Index payment events

When you send a payment, the token contract emits events:

* **Transfer**: Standard ERC-20 transfer event
* **TransferWithMemo**: Additional event with memo (if using `transferWithMemo`)

You can filter these events to track payments in your off-chain systems.

## Stablecoin payment best practices

### Payment loading states

Users should see a loading state when the payment is being processed.

You can use the `isPending` property from the `useTransferSync` hook to show pending state to the user.

### Payment error handling

If an error unexpectedly occurs, you can display an error message to the user by using the `error` property from the `useTransferSync` hook.

```tsx
import { Hooks } from 'wagmi/tempo'

function SendPayment() {
  const sendPayment = Hooks.token.useTransferSync()

  return (
    <>
      {/* ... your paymentform ... */}
      {sendPayment.error && <div>Error: {sendPayment.error.message}</div>}
    </>
  )
}
```

## Stablecoin payment learning resources

* [TypeScript SDK](https://tempo.xyz/developers/docs/sdk/typescript) — Learn more about the TypeScript SDK
* [Transactions](https://tempo.xyz/developers/docs/protocol/transactions) — Learn more about transactions on Tempo
