> For the complete documentation index, see [llms.txt](https://docs.seismic.systems/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.seismic.systems/clients/alloy/src20/transfers.md).

# Transfers

Send SRC20 tokens privately using shielded transfers, approvals, and `transferFrom` patterns.

## Overview

SRC20 transfers work similarly to ERC20 transfers, but with calldata encryption to hide amounts from observers. Functions like `transfer`, `approve`, and `transferFrom` have shielded parameters (`suint256`), so the `sol!` macro wraps them in a `ShieldedCallBuilder` that auto-encrypts — just call `.send()` directly.

## Prerequisites

All transfer examples require a signed provider and the SRC20 interface definition:

```rust
use seismic_prelude::client::*;
use seismic_alloy_network::reth::SeismicReth;

sol! {
    #[sol(rpc)]
    interface ISRC20 {
        function balanceOf(address account) public view returns (suint256);
        function transfer(address to, suint256 amount) public returns (bool);
        function approve(address spender, suint256 amount) public returns (bool);
        function allowance(address owner, address spender) public view returns (suint256);
        function transferFrom(address from, address to, suint256 amount) public returns (bool);

        event Transfer(address indexed from, address indexed to, suint256 value);
        event Approval(address indexed owner, address indexed spender, suint256 value);
    }
}
```

## Direct Transfer

The simplest pattern: transfer tokens directly from your wallet to a recipient.

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let signer: PrivateKeySigner = "0xYOUR_PRIVATE_KEY".parse()?;
    let wallet = SeismicWallet::<SeismicReth>::from(signer);
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new()
        .wallet(wallet)
        .connect_http(url)
        .await?;

    let token: Address = "0xYOUR_TOKEN_ADDRESS".parse()?;
    let recipient: Address = "0xRECIPIENT_ADDRESS".parse()?;
    let amount = U256::from(100);
    let contract = ISRC20::new(token, &provider);

    // transfer has suint256 param — auto-encrypts
    let pending_tx = contract.transfer(recipient, amount).send().await?;
    let receipt = pending_tx.get_receipt().await?;

    println!("Transfer sent: {:?}", receipt.transaction_hash);
    println!("Status: {:?}", receipt.status());

    Ok(())
}
```

## Approval + TransferFrom

The two-step pattern for delegated transfers: first approve a spender, then the spender calls `transferFrom`.

### Step 1: Owner Approves Spender

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Owner's provider
    let owner_signer: PrivateKeySigner = "0xOWNER_PRIVATE_KEY".parse()?;
    let owner_wallet = SeismicWallet::<SeismicReth>::from(owner_signer);
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let owner_provider = SeismicProviderBuilder::new()
        .wallet(owner_wallet)
        .connect_http(url)
        .await?;

    let token: Address = "0xYOUR_TOKEN_ADDRESS".parse()?;
    let spender: Address = "0xSPENDER_ADDRESS".parse()?;
    let approval_amount = U256::from(1000);
    let contract = ISRC20::new(token, &owner_provider);

    // approve has suint256 param — auto-encrypts
    let pending_tx = contract.approve(spender, approval_amount).send().await?;
    let receipt = pending_tx.get_receipt().await?;

    println!("Approval tx: {:?}", receipt.transaction_hash);

    Ok(())
}
```

### Step 2: Spender Executes TransferFrom

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Spender's provider
    let spender_signer: PrivateKeySigner = "0xSPENDER_PRIVATE_KEY".parse()?;
    let spender_wallet = SeismicWallet::<SeismicReth>::from(spender_signer);
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let spender_provider = SeismicProviderBuilder::new()
        .wallet(spender_wallet)
        .connect_http(url)
        .await?;

    let token: Address = "0xYOUR_TOKEN_ADDRESS".parse()?;
    let owner: Address = "0xOWNER_ADDRESS".parse()?;
    let recipient: Address = "0xRECIPIENT_ADDRESS".parse()?;
    let amount = U256::from(250);
    let contract = ISRC20::new(token, &spender_provider);

    // transferFrom has suint256 param — auto-encrypts
    let pending_tx = contract.transferFrom(owner, recipient, amount).send().await?;
    let receipt = pending_tx.get_receipt().await?;

    println!("TransferFrom tx: {:?}", receipt.transaction_hash);

    Ok(())
}
```

## Check Balance Before Transfer

Always verify sufficient balance before sending a transfer to avoid wasted gas:

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let signer: PrivateKeySigner = "0xYOUR_PRIVATE_KEY".parse()?;
    let wallet = SeismicWallet::<SeismicReth>::from(signer);
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new()
        .wallet(wallet)
        .connect_http(url)
        .await?;

    let token: Address = "0xYOUR_TOKEN_ADDRESS".parse()?;
    let my_address = provider.default_signer_address();
    let recipient: Address = "0xRECIPIENT_ADDRESS".parse()?;
    let amount = U256::from(100);
    let contract = ISRC20::new(token, &provider);

    // Step 1: Check balance via signed read
    let balance = contract.balanceOf(my_address).seismic().call().await?;

    if balance._0 < amount {
        println!("Insufficient balance: have {}, need {amount}", balance._0);
        return Ok(());
    }

    // Step 2: Execute the transfer (auto-encrypts, suint256 param)
    let pending_tx = contract.transfer(recipient, amount).send().await?;
    let receipt = pending_tx.get_receipt().await?;

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

    Ok(())
}
```

## Full Approve-Check-Transfer Workflow

A complete workflow showing approval, allowance check, and delegated transfer:

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Setup owner and spender providers
    let owner_signer: PrivateKeySigner = "0xOWNER_PRIVATE_KEY".parse()?;
    let owner_wallet = SeismicWallet::<SeismicReth>::from(owner_signer);
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let owner_provider = SeismicProviderBuilder::new()
        .wallet(owner_wallet)
        .connect_http(url.clone())
        .await?;

    let spender_signer: PrivateKeySigner = "0xSPENDER_PRIVATE_KEY".parse()?;
    let spender_wallet = SeismicWallet::<SeismicReth>::from(spender_signer);
    let spender_provider = SeismicProviderBuilder::new()
        .wallet(spender_wallet)
        .connect_http(url)
        .await?;

    let token: Address = "0xYOUR_TOKEN_ADDRESS".parse()?;
    let owner_address = owner_provider.default_signer_address();
    let spender_address = spender_provider.default_signer_address();
    let recipient: Address = "0xRECIPIENT_ADDRESS".parse()?;

    let owner_contract = ISRC20::new(token, &owner_provider);
    let spender_contract = ISRC20::new(token, &spender_provider);

    // Step 1: Owner approves spender for 1000 tokens (auto-encrypts, suint256 param)
    let pending = owner_contract
        .approve(spender_address, U256::from(1000))
        .send()
        .await?;
    pending.get_receipt().await?;
    println!("Approved spender for 1000 tokens");

    // Step 2: Check allowance via signed read
    let allowance = owner_contract
        .allowance(owner_address, spender_address)
        .seismic()
        .call()
        .await?;
    println!("Current allowance: {}", allowance._0);

    // Step 3: Spender transfers 250 from owner to recipient (auto-encrypts, suint256 param)
    let pending = spender_contract
        .transferFrom(owner_address, recipient, U256::from(250))
        .send()
        .await?;
    let receipt = pending.get_receipt().await?;
    println!("TransferFrom tx: {:?}", receipt.transaction_hash);

    Ok(())
}
```

## Batch Transfers

Send tokens to multiple recipients in sequence:

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let signer: PrivateKeySigner = "0xYOUR_PRIVATE_KEY".parse()?;
    let wallet = SeismicWallet::<SeismicReth>::from(signer);
    let url = "https://testnet-1.seismictest.net/rpc".parse()?;
    let provider = SeismicProviderBuilder::new()
        .wallet(wallet)
        .connect_http(url)
        .await?;

    let token: Address = "0xYOUR_TOKEN_ADDRESS".parse()?;
    let contract = ISRC20::new(token, &provider);

    let recipients: Vec<(Address, U256)> = vec![
        ("0xADDRESS_1".parse()?, U256::from(100)),
        ("0xADDRESS_2".parse()?, U256::from(200)),
        ("0xADDRESS_3".parse()?, U256::from(300)),
    ];

    for (recipient, amount) in &recipients {
        // transfer auto-encrypts (suint256 param)
        let pending_tx = contract
            .transfer(*recipient, *amount)
            .send()
            .await?;
        let receipt = pending_tx.get_receipt().await?;

        println!(
            "Sent {amount} to {recipient}: {:?}",
            receipt.transaction_hash,
        );
    }

    Ok(())
}
```

## Notes

* All transfer amounts are auto-encrypted because `transfer`, `approve`, and `transferFrom` have shielded parameters (`suint256`)
* The provider's filler pipeline handles calldata encryption automatically
* Transaction receipts are returned as normal — only the calldata is encrypted
* `transferFrom` requires prior approval from the token owner
* Each transaction has its own encryption nonce managed by the filler pipeline

## Warnings

* **Insufficient balance** — The transaction will revert on-chain if the sender does not have enough tokens. Check the balance first to avoid wasted gas.
* **Insufficient allowance** — `transferFrom` reverts if the spender's allowance is less than the transfer amount
* **Nonce management** — When sending multiple transactions rapidly, the `NonceFiller` handles nonce assignment. Await each transaction's receipt before sending the next to avoid nonce conflicts.

## See Also

* [Token Interaction](/clients/alloy/src20/token-interaction.md) — Reading balances and metadata
* [Event Decryption](/clients/alloy/src20/event-decryption.md) — Decrypting Transfer events
* [Contract Interaction](/clients/alloy/contract-interaction.md) — General call patterns
* [SeismicSignedProvider](/clients/alloy/provider/seismic-signed-provider.md) — Required provider type


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.seismic.systems/clients/alloy/src20/transfers.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
