# Transfer

> Pay another account without leaving the pool. The chain shows no sender, no recipient, and no amount.

Transfers move value between accounts without anything leaving the pool. The chain records that a transaction happened and how many notes went in and out, but the sender, recipient, and amount are not revealed.

## How a transfer moves

```mermaid
sequenceDiagram
    participant Sender
    participant Relayer
    participant Relay as PrivacyPoolRelay
    participant PoolVault
    participant Recipient

    Note over Sender,Relayer: fee quote agreed (see Relaying)
    Note over Sender: builds the transact proof, boundto the relayer as processor
    Sender->>Relayer: proof + signed quote
    Relayer->>Relay: relay
    Relay->>PoolVault: transact
    Note over PoolVault: input nullifiers marked spent,output commitments inserted
    PoolVault-->>Recipient: Note event (encrypted payload)
    Note over Recipient: discovery decrypts the payload:the note is theirs
```

A transfer spends one or more of your `ACTIVE` [notes](/concepts/notes) and produces new ones: a note for the recipient and a change note back to you. The proof convinces the pool that the inputs are yours, unspent, and descended from attested deposits, without revealing which notes they are. On-chain, the [`PoolVault`](/protocol/contracts/pool-vault) marks the input nullifiers spent, inserts the output commitments into the [state tree](/concepts/state-tree), and emits a `Note` event for each published output.

You can submit the spend two ways:

-   **Relayed.** You hand the prepared proof to a relayer, which pays the gas and submits the transact, so your wallet never appears on-chain. The recipient receives the full `amount`, and the relayer's fee is charged on top and taken from your change. On the deployed relayer, that fee surfaces on-chain as the transact's public output, so a relayed transfer carries a nonzero `amountOut` equal to it. The proof is bound to the relayer you prepared for, so only that relayer can submit it, and the payload can't be altered without failing the proof.
-   **Self-submission.** You submit from your own wallet, with no relayer and no fee. A pure transfer then has an `amountOut` of zero and exposes no public output at all, at the cost of your wallet being visible as the submitter and paying the gas.

Quotes, expiry and retries, processor binding, and the relayer trust model are shared with the other operations and live in [Relaying](relaying).

## How to send

```ts
const sendAmount = "0x11c37937e08000";   // 0.005 ETH in wei
const myNotes = (await session.exportAccount()).notes
    .filter((n) => n.status === "ACTIVE" && n.tokenId === NATIVE_ETH);
const noteToSpend = myNotes.find((n) => BigInt(n.value) >= BigInt(sendAmount));
if (!noteToSpend) throw new Error("no single ACTIVE note covers the amount");

const prepared = await session.prepareTransfer({
    inputCommitments: [noteToSpend.commitment],
    amount: sendAmount,
    tokenId: NATIVE_ETH,
    recipientDiscoveryData: {
        evmAddress: recipient,
    },
});

const result = await session.relayTransfer(prepared.relayOptions[0]);
console.log("transfer landed:", result.txReceipt.txHash);

const recipientNote = prepared.executeOptions.recipientPendingNotes[0];
```

`prepareTransfer()` fetches a fee quote, builds the proof, and returns both relayed-submit options and self-submit options. If the recipient has no registered viewing key, opt into out-of-band delivery with `allowOutOfBandFallback: true` and deliver the note secret yourself.

## Constraints

-   **Circuit cap:** the `transact_NxM` family tops out at five inputs and five outputs. Beyond that you must split the spend into multiple cycles.
-   **Label-aware allocator:** the SDK produces one recipient note and one change note per distinct deposit label spent, so spending three distinct labels would require six outputs, which no circuit supports. Keep it to at most two labels per call.
-   **Recipient discoverability:** if the recipient has [registered a viewing key](../operations/register-viewing-key), the SDK uses discoverable mode and embeds an encrypted payload in the Note event. If not, the transfer throws `RecipientViewingKeyUnregistered` rather than downgrading automatically. To allow out-of-band delivery you must opt in with `allowOutOfBandFallback: true`, and then hand the recipient the noteSecret yourself.
-   **Quote expiry:** the relayer signs a fee commitment with a short expiry, and slow proof generation can outlast it. When that happens, re-run `prepareTransfer` for a fresh quote and submit again, handling both `FeeCommitmentExpired` and relayer-side rejections.
-   **Local state drift:** after a failed or partial relay flow, local note state can lag behind the chain, so always [verify spent status on-chain](../operations/verify-spent) before picking inputs.

## How the recipient receives

What the recipient has to do depends on whether they registered on the keystore. A registered recipient does nothing: the note payload travels inside the `Note` event, encrypted to their published viewing key, and their next discovery sync finds it by trial decryption. For an unregistered recipient nothing is published on-chain at all, so you must opt into out-of-band delivery and hand them the note secret yourself, over a channel you trust. Both receive paths end in [Manage notes](manage-notes): discovery for registered recipients, import for out-of-band ones.

## Behind the scenes

SDK call `session.prepareTransfer(...)`, then `session.relayTransfer(...)` or `session.executeTransfer(...)`
Contract method `PoolVault.transact(...)`. The deployed relayer submits through `PrivacyPoolRelay.relay(...)`, while self-relay calls the pool from your wallet.
Circuit `transact_NxM`, picked by the SDK based on input and output counts
Context binding The proof's `context` signal binds both the transact params and the note data (`keccak256(abi.encode(transactParams, noteData))` reduced into the field), so neither the public parameters nor the encrypted payloads can be altered after you sign without failing `PoolVault_ProofContextMismatch`.
On-chain effect Input nullifiers added to `spentNullifiers`, output commitments added to the state tree, and one `Note` event emitted per published `noteData[]` entry (out-of-band recipient outputs publish no entry, so they emit no Note event).

## What's next

-   [Generate a payment receipt](../operations/generate-receipt) for selective disclosure of this transfer to an accountant.
-   [Manage notes](manage-notes) on the recipient side: discovery or import, depending on the delivery mode.
-   [Verify spent status](../operations/verify-spent) before the next spend.
