Skip to main content

Overview

The deposit operation moves tokens from a user’s AssociatedTokenAccount (ATA) into their EncryptedTokenAccount (ETA). After a successful deposit, the balance is hidden on-chain.
1

Transfer from ATA

Tokens are moved from the user’s AssociatedTokenAccount into the pool custody account — an on-chain SPL or Token-2022 account controlled by the Umbra program.
2

Fee Deduction

Protocol fees and any Token-2022 transfer fees are subtracted from the incoming amount. The net amount is what gets credited to the ETA.
3

Credit ETA

The net amount is added to the destination user’s ETA. The balance is hidden on-chain — it can only be decrypted by the Arcium MPC network and, in Shared mode, by the user’s own X25519 key.
The user must be registered before depositing. An EncryptedUserAccount PDA must exist for the destination wallet address.
Deposit transactions are publicly visible on-chain. The depositor’s wallet address, the destination address, and the gross transfer amount are all readable from the transaction. Only the resulting ETA balance is hidden — the act of shielding itself is not private.

Usage

import { getATAIntoETADirectDepositorFunction } from "@umbra-privacy/sdk/deposit";

const deposit = getATAIntoETADirectDepositorFunction({ client });

const result = await deposit(
  destinationAddress,  // whose encrypted balance to credit
  mint,                // SPL or Token-2022 mint address
  transferAmount,      // amount in native token units (U64)
  options?,            // optional
);

Parameters

destinationAddress
Address
required
The wallet address whose ETA is credited. This is usually client.signer.address (depositing to yourself), but you can deposit into any registered user’s account — the funds will be encrypted under their X25519 key.
mint
Address
required
The SPL token mint address. Works with any standard SPL token and Token-2022 mints (see Token-2022 Support for transfer fee handling).
transferAmount
bigint
required
The gross amount to transfer, in the token’s native units (accounting for decimals). For example, 1_000_000n for 1 USDC (6 decimals). Protocol fees and Token-2022 transfer fees are subtracted from this amount before the encrypted balance is credited.
options.optionalData
OptionalData32
32 bytes of caller metadata stored with the deposit. Defaults to 32 zero bytes. Must be a pre-hashed or pre-encrypted 32-byte value — never store plaintext identifiers.
options.accountInfoCommitment
Commitment
default:"\"confirmed\""
Commitment level used for RPC account reads during deposit preparation.
options.epochInfoCommitment
Commitment
default:"\"confirmed\""
Commitment level used for fetching epoch info (Token-2022 transfer-fee schedule).
options.hooks
ATAIntoETADirectDepositHooks
Per-phase lifecycle hooks. Keys: onValidationStart, onValidationComplete, onMintFetchStart, onMintFetchComplete, onAccountFetchStart, onAccountFetchComplete, onArciumSetupStart, onArciumSetupComplete, onInstructionBuildStart, onInstructionBuildComplete, queueComputation (MpcTransactionStepHooksonPreSend, onPostSend, onMonitorStarted, onProgress, onFinalized, onRentReclaimSubmitted, onRentReclaimError), onComplete, onError. Each receives a typed event object.
There are no priorityFees, awaitCallback, skipPreflight, maxRetries, or purpose options on the depositor. Priority fees are configured at factory time via deps.microLamportsPerAcuPolicy. Callback waiting is always-on for direct deposits — the result’s callback field is populated on success.

Return Value

Returns a Promise<DepositResult>. The DepositResult object contains:
interface DepositResult {
  readonly signatures: readonly TransactionSignature[];   // every tx signature submitted, in order
  readonly queueSignature: TransactionSignature;          // the handler (queue computation) signature
  readonly callback?: CallbackOutcome;                    // present once the callback round-trip completes (always awaited)
  readonly rentClaim?: RentClaimOutcome;                  // rent reclamation outcome (best-effort)
}

type CallbackOutcome =
  | { readonly status: "finalized"; readonly signature?: TransactionSignature; readonly elapsedMs: number }
  | { readonly status: "pruned";    readonly elapsedMs: number }
  | { readonly status: "timed-out"; readonly elapsedMs: number };

type RentClaimOutcome =
  | { readonly claimed: true;  readonly signature: TransactionSignature }
  | { readonly claimed: false; readonly reason: string };
  • signatures — every transaction signature submitted by this call, in submission order (handler, callback, rent claim).
  • queueSignature — the handler (queue computation) transaction signature.
  • callback — present once the callback round-trip completes (always awaited) (the default). Discriminated on status: "finalized" means the callback landed and signature is the MPC callback tx signature; "pruned" / "timed-out" indicate the callback never landed.
  • rentClaim — present once rent reclamation has been attempted. { claimed: true } returns the rent-reclaim signature; { claimed: false } returns the reason. Rent reclamation is best-effort — the deposit itself still succeeded even when claimed: false.
Deposits follow the dual-instruction pattern - the SDK submits the handler transaction, waits for Arcium to complete the MPC computation, then waits for the callback transaction to confirm.

Encryption Mode Selection

The SDK automatically selects the correct instruction variant based on the state of the destination user’s account:
  • New MXE-only balance — destination has no X25519 key registered; first deposit.
  • Existing MXE-only balance — destination has no X25519 key; balance already exists.
  • New Shared balance — destination has X25519 key registered; first deposit.
  • Existing Shared balance — destination has X25519 key; balance already exists.
You do not need to specify the mode manually. The destination address’s isUserAccountX25519KeyRegistered flag drives the selection.

Protocol Fees

Fees are deducted from the transferAmount before crediting the ETA. With the BPS divisor of 16,384:
credited = transferAmount - baseFee - floor((transferAmount - baseFee) * commissionBps / 16384)
Protocol-fee configuration is set on-chain by the pool admin and may vary per pool. The current default is a small fixed base fee plus a 35-bps commission. See Pricing for full details.
In most cases, direct deposits from your own ATA to your own ETA carry zero protocol fees — the baseFee and commissionBps are both set to 0 for standard self-shielding. Fees may apply for deposits routed through relayers or for specific pool configurations.

Example: Self-Deposit

import { getATAIntoETADirectDepositorFunction } from "@umbra-privacy/sdk/deposit";

const deposit = getATAIntoETADirectDepositorFunction({ client });

// Deposit 100 USDC into your own ETA.
const USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const ONE_HUNDRED_USDC = 100_000_000n; // 6 decimals

const result = await deposit(client.signer.address, USDC, ONE_HUNDRED_USDC);
console.log("Queue signature:", result.queueSignature);
if (result.callback?.status === "finalized") {
  console.log("Callback signature:", result.callback.signature);
}

Example: Deposit to Another User

You can shield tokens directly into another registered user’s ETA:
const RECIPIENT = "GsbwXfJraMomNxBcpR3DBFyKCCmN9SKGzKFJBNKxRFkT";

const result = await deposit(RECIPIENT, USDC, ONE_HUNDRED_USDC);
The tokens are encrypted under the recipient’s X25519 key. Only they can withdraw them.

Error Handling

Deposit errors include a stage field identifying where the failure occurred:
import { isEncryptedDepositError } from "@umbra-privacy/sdk/errors";

try {
  await deposit(destinationAddress, mint, amount);
} catch (err) {
  if (isEncryptedDepositError(err)) {
    switch (err.stage) {
      case "validation":
        // Invalid arguments - check destinationAddress, mint, amount
        break;
      case "mint-fetch":
        // Could not fetch mint account - check RPC connectivity and mint address
        break;
      case "fee-calculation":
        // Token-2022 transfer fee calculation failed
        break;
      case "account-fetch":
        // Could not fetch the destination user account or token account
        break;
      case "transaction-send":
        // Transaction submitted but confirmation failed - may still have landed
        break;
      default:
        // Other stages: pda-derivation, instruction-build, transaction-build,
        // transaction-compile, transaction-sign, transaction-validate
        console.error("Deposit failed at stage:", err.stage, err);
    }
  }
}
See Error Handling for a full reference.