> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Api reference

# API Reference

Complete API reference for the Privacy Boost WASM SDK.

## Generated Documentation

Full API documentation can be generated using `cargo doc`:

```bash theme={null}
# From rust-sdk directory
make docs-rust

# WASM-specific docs
cargo doc --no-deps -p privacy-boost-wasm
```

***

## Initialization

### init()

Initialize the WASM module. Must be called before creating SDK instances.

```typescript theme={null}
import init from '@sunnyside-io/privacy-boost-wasm';

await init();
```

**Returns:** `Promise<void>`

***

## PrivacyBoost

Main SDK class for JavaScript/TypeScript.

### Constructor

```typescript theme={null}
new PrivacyBoost(config: WasmConfig)
```

Creates a new SDK instance.

**Parameters:**

* `config` - SDK configuration object

**Example:**

```typescript theme={null}
const sdk = new PrivacyBoost({
  serverUrl: 'https://optimism.sepolia.privacyboost.io',
  wethContractAddress: "0x4200000000000000000000000000000000000006",
  appId: "app_abc123xyz",
});
```

### Properties

```typescript theme={null}
sdk.config: WasmConfig          // SDK configuration
sdk.isConnected: boolean       // Wallet connection status
sdk.isAuthenticated: boolean   // Authentication status
sdk.walletAddress: string | null  // Connected wallet address
```

### Connection & Authentication Methods

#### authenticate

```typescript theme={null}
sdk.authenticate(
  wallet: WalletAdapter,
  keySource?: KeySource | null,
  callback?: (payload: LoginPayload) => Promise<{ token: string; expiresIn: number }>
): Promise<AuthResult>
```

Connect a wallet, derive privacy keys, and authenticate with the Privacy Boost backend in a single call.

**Parameters:**

* `wallet` - JavaScript wallet adapter object
* `keySource` - Optional key derivation source. Pass `null`/`undefined` to default to `walletDerived`. Accepted values: `{ type: "walletDerived" }`, `{ type: "mnemonic", phrase: "..." }`, `{ type: "rawSeed", hex: "..." }`
* `callback` - Optional token provider function. Receives the authentication payload and returns a JWT. If omitted, the SDK calls the server directly. If it throws, `authenticate` rejects with `AUTHENTICATION_FAILED`, which is not retryable, unless the thrown error has `retryable: true` (rejects with a retryable `NETWORK_ERROR`) or `code: 'RATE_LIMITED'` with `retryAfterMs` (rejects with `RATE_LIMITED`). A `fetch` that got no response counts as retryable. Mark transient failures of your auth backend, such as a 5xx, so callers know to retry.

**Returns:** `AuthResult` with one of four statuses:

* `authenticated` - Includes `privacyAddress` and `mpk`.
* `credentialRequired` - Call `submitCredential()`.
* `mnemonicGenerated` - Show the mnemonic to the user, then call `proceedAfterMnemonic()`.
* `recoveryRequired` - Collect the recovery phrase and authenticate again with a mnemonic key source.

**Throws:**

* Error if wallet is on wrong network
* Error if signing fails
* Error if `appId` not set in config
* Error if backend unreachable

#### submitCredential

```typescript theme={null}
sdk.submitCredential(credential: string): Promise<LoginResult>
```

Submit a credential when `authenticate()` returns `CredentialRequired`.

**Returns:** `LoginResult` with `privacyAddress` and `mpk`

#### logout

```typescript theme={null}
sdk.logout(): void
```

End session completely, clear all state.

#### clearSession

```typescript theme={null}
sdk.clearSession(): void
```

Clear JWT only, keep keys for quick re-auth.

### State Accessors

```typescript theme={null}
sdk.getPrivacyAddress(): string | null
sdk.getMpk(): string | null
sdk.getState(): SDKState
```

### Balance Methods

#### getBalance

```typescript theme={null}
sdk.getBalance(tokenAddress: string): Promise<TokenBalance>
```

Get balance for a specific token.

**Parameters:**

* `tokenAddress` - ERC-20 token contract address

**Returns:** `TokenBalance` with shielded and wallet amounts

#### getAllBalances

```typescript theme={null}
sdk.getAllBalances(): Promise<TokenBalance[]>
```

Get all token balances.

### Fee Methods

#### getFees

```typescript theme={null}
sdk.getFees(tokenAddress: string): Promise<FeeRates>
```

Returns the ordinary transfer and unshield fee quote for a token.

#### getGatewayFees

```typescript theme={null}
sdk.getGatewayFees(
  tokenAddress: string,
  gatewayAddress: string,
  gatewaySelector?: string,
): Promise<FeeRates>
```

Returns the Gateway fee that replaces the ordinary unshield fee. Pass the
first four calldata bytes as `gatewaySelector` to resolve a selector-specific
override, or omit it for the Gateway-wide default.

### Vault Operations

#### deposit

```typescript theme={null}
sdk.shield(params: {
  tokenAddress: string;
  amount: string;
  onProgress?: (event: unknown) => void;
}): Promise<ShieldResult>
```

Deposit tokens into the shielded pool.

**Parameters:**

* `tokenAddress` - Token contract address
* `amount` - Amount in wei (as string for BigInt support)
* `onProgress` - Optional progress callback

**Returns:** `ShieldResult` with transaction hash

#### withdraw

```typescript theme={null}
sdk.unshield(params: {
  tokenAddress: string;
  amount: string;
  recipient: string;
  onProgress?: (event: unknown) => void;
}): Promise<UnshieldResult>
```

Withdraw tokens from the shielded pool.

**Parameters:**

* `tokenAddress` - Token contract address
* `amount` - Amount in wei (as string)
* `recipient` - Recipient Ethereum address. `recipientAddress` is accepted as a deprecated alias.
* `onProgress` - Optional progress callback

**Returns:** `UnshieldResult` with transaction hash

#### send

```typescript theme={null}
sdk.send(params: {
  tokenAddress: string;
  amount: string;
  recipient: string;
}): Promise<TransferResult>
```

Send a private transfer.

**Parameters:**

* `tokenAddress` - Token contract address
* `amount` - Amount in wei (as string)
* `recipient` - Recipient's 194-char privacy address. `to` is accepted as a deprecated alias.

#### sendBatch

```typescript theme={null}
sdk.sendBatch(params: {
  tokenAddress: string;
  recipients: Array<{ recipient: string; amount: string }>;
}): Promise<TransferResult>
```

Send one private transfer paying several recipients: one request, one fee, one
set of input notes. Output order follows the array.

**Shape limits:** the transfer's output count is the recipient count plus a
change note, and the server only proves specific `(inputs, outputs)`
combinations, so a longer recipient list limits how many input notes can be
spent — past two recipients, at most four. Requests no enabled circuit can
serve fail with `UNSUPPORTED_TRANSFER_SHAPE` before submission.

**Returns:** `TransferResult` with transaction hash

#### prepareShield

```typescript theme={null}
sdk.prepareShield(params: {
  tokenAddress: string;
  amount: string;
  recipient?: string;
}): Promise<PreparedShield>
```

Build a deposit payload **without submitting it** — for callers that relay the
transaction themselves (a gasless relayer, an EIP-7702 session key, or a plain
`eth_sendRawTransaction`). Returns the calldata to relay plus the note
commitment; nothing is sent on-chain and no wallet is used. After relaying, pass
the mined receipt to `finalizeShield` to recover the on-chain request id.

**Parameters:**

* `tokenAddress` - Token contract address (zero address = native ETH)
* `amount` - Amount in wei (as string)
* `recipient` - (Optional) recipient privacy address. Omit to shield to yourself.

**Returns:** `PreparedShield` with the calls to relay and the note commitment

### Transaction History

```typescript theme={null}
sdk.getTransactionHistory(params?: {
  txType?: string;
  tokenId?: number;
  direction?: 'incoming' | 'outgoing';
  limit?: number;
  cursor?: string;
  collapseGateway?: boolean;
}): Promise<{ data: Transaction[]; limit: number; next?: string }>
```

One page of the wallet's transactions, newest first.

**Parameters:**

* `params.txType` - Filter by type: "deposit", "withdraw", "transfer"
* `params.tokenId` - Registry token id, from `getRegisteredTokens()`. Not a
  token address: the history rows carry the circuit's token id.
* `params.direction` - "incoming" or "outgoing" relative to the wallet.
  Anything else is rejected here rather than sent on for the server to refuse.
* `params.limit` - Page size
* `params.cursor` - `next` from the previous page
* `params.collapseGateway` - Fold each Gateway operation's two rows into one

```javascript theme={null}
// Outgoing rows for one token
const tokens = await sdk.getRegisteredTokens();
const page = await sdk.getTransactionHistory({
  tokenId: Number(tokens[0].id),
  direction: 'outgoing',
  limit: 25,
});
// page.next is the cursor for the following page; undefined at the end
```

### Token Tracking

```typescript theme={null}
sdk.getTrackedTokens(): string[]
sdk.setTrackedTokens(tokens: string[]): void
sdk.addTrackedTokens(tokens: string[]): void
```

### Merkle Tree Statistics

```typescript theme={null}
sdk.getMerkleTreeStats(): Promise<MerkleTreeStats>
```

### Utilities

```typescript theme={null}
sdk.isValidPrivacyAddress(address: string): boolean
sdk.isValidAddress(address: string): boolean
sdk.toChecksumAddress(address: string): string | null
sdk.parseAmount(amount: string, decimals?: number): string
sdk.formatAmount(wei: string, decimals?: number): string
```

### Address Lookup

```typescript theme={null}
sdk.resolveIdentity(identifier: string): Promise<object>
```

Look up a user's privacy address by MPK or Ethereum address.

### Earn Catalog and Gateway Configuration

Public reads: no login, no key material. The vault catalog and history routes
are served by the deployment's earn service. A deployment without one does not
serve those routes, and the SDK surfaces that as an error carrying the 404.
`getGatewayConfig()` always answers and reports availability through its
`enabled` flag.

```typescript theme={null}
sdk.listEarnVaults(): Promise<EarnVaultsResponse>
sdk.getEarnVault(vaultAddress: string): Promise<EarnVaultDetail>
sdk.getGatewayConfig(): Promise<GatewayConfig>
sdk.getEarnVaultHistory(
  vaultAddress: string,
  metric: 'tvlUsd' | 'netApy',
  period?: '1w' | '1m' | '3m' | 'all',
): Promise<VaultHistoryResponse>
sdk.getEarnVaultCharts(
  vaultAddress: string,
  period?: '1w' | '1m' | '3m' | 'all',
): Promise<{ tvlUsd: VaultHistoryResponse; netApy: VaultHistoryResponse }>
```

`getEarnVault` returns one catalog row plus the fields only the detail route
carries: `owner`, `curator`, `guardian`, `feeRecipient`, `timelockSeconds`,
`sharePrice` and `sharePriceUsd`. APY values everywhere are fractions (0.05 is
5%), and a null point in a history series is an upstream gap, not a zero.

```javascript theme={null}
const { vaults, gateway } = await sdk.listEarnVaults();
if (gateway.canSubmit) {
  const detail = await sdk.getEarnVault(vaults[0].vaultAddress);
  console.log(detail.name, detail.apy.net, detail.sharePrice);
}
```

### Earn Deposit and Redeem

Authenticated session required. Both take the same params object and return an
`EarnResult` carrying the `requestId` to poll for settlement. `amount` is the
net amount entering the vault call in raw units (assets for a deposit, shares
for a redeem), and the Gateway fee is charged on top.

```typescript theme={null}
sdk.earnDeposit(params: {
  vaultAddress: string;
  amount: string;
  maxSlippageBps?: number;
  onProgress?: (event: ProgressEvent) => void;
}): Promise<EarnResult>
sdk.earnRedeem(params: {
  vaultAddress: string;
  amount: string;
  maxSlippageBps?: number;
  onProgress?: (event: ProgressEvent) => void;
}): Promise<EarnResult>
```

### Private Treasury (contacts and labels)

Per-wallet bookkeeping the server stores encrypted. Both listings page with an
opaque cursor: pass `undefined` for the first page and the returned
`nextCursor` for the next. A row whose `decryptionFailed` is `true` is one the
server could no longer open — show it as unreadable rather than dropping it.

```typescript theme={null}
sdk.listContacts(limit?: number, cursor?: string): Promise<ContactPage>
sdk.createContact(params: {
  privacyAddress: string;
  nickname: string;
  ethAddress?: string;
  emoji?: string;
  metadata?: Record<string, string>;
  clientId?: string;
}): Promise<Contact>
sdk.bulkCreateContacts(contacts: CreateContactParams[]): Promise<{ created; skipped }>
sdk.updateContact(id: string, patch: {
  ethAddress?: string | null;
  nickname?: string;
  emoji?: string;
  metadata?: Record<string, string> | null;
}): Promise<Contact>
sdk.deleteContact(id: string): Promise<void>

sdk.listLabels(limit?: number, cursor?: string): Promise<TransactionLabelPage>
sdk.upsertLabel(txHash: string, params: {
  contactId?: string;
  memo?: string;
  metadata?: Record<string, string>;
}): Promise<TransactionLabel>
sdk.deleteLabel(txHash: string): Promise<void>
```

`updateContact` is a JSON Merge Patch: an omitted key keeps the stored value
and an explicit `null` clears it. `nickname` and `emoji` can only be replaced,
so a `null` on either is rejected rather than quietly kept — the same rule the
native bindings enforce with their `StringPatch.Clear` variant.

`upsertLabel` is a PUT: it replaces the whole label, so a field you leave out
is stored as absent rather than left at its old value.

```javascript theme={null}
const contact = await sdk.createContact({
  privacyAddress: recipient.privacyAddress,
  nickname: 'Alice',
  metadata: { team: 'design' },
});

await sdk.upsertLabel(transfer.txHash, { contactId: contact.id, memo: 'Dinner split' });

// Clear the ETH address and leave everything else alone.
await sdk.updateContact(contact.id, { ethAddress: null });

// Page through the address book.
let cursor;
do {
  const page = await sdk.listContacts(50, cursor);
  for (const c of page.contacts) console.log(c.nickname, c.privacyAddress);
  cursor = page.nextCursor;
} while (cursor);
```

The contact store is a deployment option. A server without one answers these
with a 503 naming it, which is the feature being off rather than a failure.

### Campaign Progress

```typescript theme={null}
sdk.getStartaleCampaignProgress(): Promise<{
  hasDepositedAtLeast10Usd: boolean;
  hasMadeTransfer: boolean;
  campaignTokenId?: number;
}>
```

Lifetime mission progress for the authenticated user and app. The deposit
mission is priced at query time, so it is not monotonic: record the first
`true` and never downgrade it. `campaignTokenId` is absent only when no
campaign token is configured, in which case every shielded token counted
toward the deposit total.

### Audit Methods (Auditor Only)

```typescript theme={null}
sdk.getAuditTransactions(params?: AuditParams): Promise<AuditTransactionsResponse>
sdk.getAuditNotes(params?: AuditNotesParams): Promise<AuditNotesResponse>
sdk.getAuditBalances(params?: AuditBalancesParams): Promise<AuditBalancesResponse>
```

***

## Stateless shield helpers

Free functions on the WASM module for building a deposit payload without a
signed-in session — for a backend that holds only a recipient's privacy address.
No wallet, no private keys.

### buildShieldPayload

```typescript theme={null}
import { buildShieldPayload } from '@sunnyside-io/privacy-boost-wasm';

buildShieldPayload(params: {
  tokenAddress: string;
  amount: string;
  recipient: string;
  tokenId: number;
  shieldContractAddress: string;
  wethContractAddress?: string;
  teePublicKey: string;
  minShieldAmount?: string;
  emitApprove?: boolean;
}): PreparedShield
```

Build a recipient-targeted deposit payload from public material only. Resolve
`tokenId`, `shieldContractAddress`, and `teePublicKey` from the token catalog /
config. An ephemeral sender keypair is generated and discarded internally, and
the note is spendable only by `recipient`.

**Parameters:**

* `tokenAddress` - Asset to shield (zero address = native ETH)
* `amount` - Amount in wei (as string)
* `recipient` - Recipient privacy address (required)
* `tokenId` - Resolved on-chain token id (from the token catalog)
* `shieldContractAddress` - Shield pool contract address
* `wethContractAddress` - (Optional) required only when shielding native ETH
* `teePublicKey` - TEE public key used to encrypt the deposit
* `minShieldAmount` - (Optional) per-token minimum in wei; rejects below it
* `emitApprove` - (Optional) emit the ERC-20 approve call. Defaults to `true`.

**Returns:** `PreparedShield`

### finalizeShield

```typescript theme={null}
import { finalizeShield } from '@sunnyside-io/privacy-boost-wasm';

finalizeShield(
  receipt: TransactionReceipt,
  shieldContract: string,
  commitment: string
): ShieldResult
```

Recover the on-chain shield request id from the mined receipt of a relayed
deposit. Stateless — the caller obtained the receipt from its own relay/RPC.
Throws if the deposit reverted or the `DepositRequested` log is absent.

**Parameters:**

* `receipt` - Mined transaction receipt (viem/ethers shape: `{ transactionHash, status, logs: [{ address, topics, data }] }`)
* `shieldContract` - Shield pool contract — the `DepositRequested` emitter (from `PreparedShield.finalize`)
* `commitment` - Commitment from the `PreparedShield` being settled

**Returns:** `ShieldResult` with the on-chain `requestId`

***

## Validation Functions

```typescript theme={null}
isEvmAddress(address: string): boolean
isMpkHex(mpk: string): boolean
isPrivacyAddress(address: string): boolean
validateEvmAddress(address: string): string | null    // returns error message or null
validatePrivacyAddress(address: string): string | null
getNativeEthAddress(): string
isNativeEth(address: string): boolean
```

***

## Types

### WasmConfig

```typescript theme={null}
interface WasmConfig {
  serverUrl: string;
  appId: string;
  chainId?: number;                 // auto-discovered from server
  shieldContractAddress?: string;   // auto-discovered from server
  wethContractAddress?: string;
}
```

### LoginResult

```typescript theme={null}
interface LoginResult {
  privacyAddress: string;
  mpk: string;
}
```

### TokenBalance

```typescript theme={null}
interface TokenBalance {
  tokenAddress: string;
  shieldedBalance: string;
  walletBalance: string;
  symbol?: string;
  decimals: number;
}
```

### FeeRates

```typescript theme={null}
interface FeeRates {
  transferFeeFlat: string;
  unshieldFeeBps: number;
  feeModel?: 'user_pays' | 'app_pays';
  minTransferAmountUSD?: number;
  feeSchemaVersion?: number;
  requiresFeeSchemaVersion?: number;
  transferRequiresFeeSchemaVersion?: number;
  unshieldRequiresFeeSchemaVersion?: number;
  transferFeeMode?: 'flat' | 'bps';
  transferFeeBps?: number;
  unshieldFeeMode?: 'flat' | 'bps';
  unshieldFeeFlat?: string;
  gatewayAddress?: string;
  gatewaySelector?: string;
  gatewayFeeMode?: 'flat' | 'bps';
  gatewayFeeFlat?: string;
  gatewayFeeRate?: number;
  gatewayFeeRateDenominator?: number;
  gatewayFeeBase?: 'flat' | 'unshield_amount' | 'zero';
  gatewayRequiresFeeSchemaVersion?: number;
}
```

Gateway fields are populated only by `getGatewayFees`.

### ShieldResult

```typescript theme={null}
interface ShieldResult {
  txHash: string;
  commitment: string;
}
```

### UnshieldResult

```typescript theme={null}
interface UnshieldResult {
  txHash: string;
  amount: string;
}
```

### TransferResult

```typescript theme={null}
interface TransferResult {
  txHash: string;
}
```

### ShieldResult

Returned by `finalizeShield`.

```typescript theme={null}
interface ShieldResult {
  txHash: string;
  commitment: string;
  requestId: string;       // on-chain shield request id
  warning?: string;
  wrapTxHash?: string;     // present when native ETH was wrapped
}
```

### Call

A single on-chain call to relay. `value` and `data` are 0x-hex.

```typescript theme={null}
interface Call {
  to: string;
  value: string;
  data: string;
}
```

### PreparedShield

Returned by `prepareShield` and `buildShieldPayload`.

```typescript theme={null}
interface PreparedShield {
  approve?: Call;   // ERC-20 approve(pool, amount); omitted when managed out of band
  wrap?: Call;      // WETH deposit() wrap; present only when shielding native ETH
  shield: Call;     // the requestDeposit(...) call
  commitment: string;
  finalize: { shieldContract: string; tokenId: number };
}
```

### TransactionReceipt

The mined-receipt shape accepted by `finalizeShield` (viem/ethers compatible).

```typescript theme={null}
interface TransactionReceipt {
  txHash: string;
  status: boolean;
  logs: Array<{ address: string; topics: string[]; data: string }>;
}
```

### Transaction

```typescript theme={null}
interface Transaction {
  txHash: string;
  txType: 'deposit' | 'withdraw' | 'transfer';
  tokenAddress: string;
  amount: string;
  direction: 'in' | 'out';
  senderPubKey: string;
  receiverPubKeys: string[];
  createdAt: number;
}
```

### SDKState

```typescript theme={null}
interface SDKState {
  wallet: {
    isConnected: boolean;
    address: string | null;
    mpk: string | null;
    chainId: number;
  };
  auth: {
    isAuthenticated: boolean;
    expiresAt: number | null;
  };
  trackedTokens: string[];
}
```

### MerkleTreeStats

```typescript theme={null}
interface MerkleTreeStats {
  size: string;
  root: string;
}
```

***

## WalletAdapter Interface

```typescript theme={null}
interface WalletAdapter {
  getAddress(): Promise<string>;
  getChainId(): Promise<number>;
  signMessage(message: string): Promise<string>;
  signTypedData(typedDataJson: string): Promise<string>;
  sendTransaction(tx: {
    to: string;
    value: string;
    data: string;
  }): Promise<string>;
  waitForTransactionReceipt(txHash: string): Promise<{
    transactionHash: string;
    status: boolean;
    logs: Array<{ address: string; topics: string[]; data: string }>;
  }>;
}
```

***

## Errors

The SDK throws standard JavaScript `Error` objects. Common error messages:

| Error Message | Cause |
| - | - |
| `"Not connected"` | Wallet not connected |
| `"Not authenticated"` | User not logged in |
| `"Invalid amount"` | Amount parsing failed |
| `"Network error: ..."` | Backend communication failed |
| `"Insufficient balance"` | Not enough tokens |
| `"Invalid privacy address"` | Privacy address format wrong |
| `"Chain mismatch"` | Wallet on wrong network |

## See Also

* [Getting Started](./getting-started) - Quick start guide
* [Wallet Integration](./guides/wallet-integration) - Wallet adapter setup
* [Browser Setup](./guides/browser-setup) - Browser configuration

***

## Gift Methods

Claimable transfers ("gifts") let you send shielded funds to a wallet that has
not joined Privacy Boost yet. These methods are called directly on the
`PrivacyBoost` instance. See the
[Claimable Transfers guide](./guides/claimable-transfers) for the full flow and
the [concept page](/sdk/concepts/claimable-transfers) for the trust model.

Each fund/claim/refund call returns a `TransferResult`:

```typescript theme={null}
interface TransferResult {
  requestId: string;
  txHash: string;
  giftRecord?: GiftRecord; // present on fund — persist it to keep the gift refundable
  claimLink?: string;      // present on fund — pbgift:v1:... to share with the recipient
}
```

### giftFundToWallet

```typescript theme={null}
sdk.giftFundToWallet(params: {
  tokenAddress: string;
  amount: string;          // wei
  recipientWallet: string; // recipient's Ethereum address (may be unregistered)
  refundAfterBlock: number;
  currentBlock: number;
}): Promise<TransferResult>
```

Fund a gift bound to a wallet address. Use when the recipient has an EOA but has
not joined Privacy Boost.

### giftFund

```typescript theme={null}
sdk.giftFund(params: {
  tokenAddress: string;
  amount: string;                  // wei
  recipientPrivacyAddress: string; // recipient's 194-char privacy address
  recipientWallet: string;
  refundAfterBlock: number;
  currentBlock: number;
}): Promise<TransferResult>
```

Fund a gift when the recipient is already a Privacy Boost user.
`recipientPrivacyAddress` is their 194-char privacy address; the ciphertext is
sealed to their viewing key, while the gift itself binds to `recipientWallet`.
The field is also accepted under its original name `recipient`, which the
TypeScript SDK and the native bindings do not use because it reads too much
like `recipientWallet`.

### giftClaim

```typescript theme={null}
sdk.giftClaim(params: {
  index: number;
  acknowledgeUnknownSender: boolean; // must be true
}): Promise<TransferResult>
```

Claim a pending gift by its position in `getPendingGifts()`.

### giftClaimByCGift

```typescript theme={null}
sdk.giftClaimByCGift(params: {
  cGift: string;
  acknowledgeUnknownSender: boolean; // must be true
}): Promise<TransferResult>
```

Claim by stable commitment `cGift` — preferred when the pending list may shift as
gifts settle.

### giftClaimFromLink

```typescript theme={null}
sdk.giftClaimFromLink(params: {
  link: string;                      // pbgift:v1:...
  acknowledgeUnknownSender: boolean; // must be true
}): Promise<TransferResult>
```

Claim straight from a claim link, with no server lookup first.

### giftRefundByRecord

```typescript theme={null}
sdk.giftRefundByRecord(index: number): Promise<TransferResult>
```

Refund an unclaimed gift using the local record at the given position in
`getGiftRecords()`. The argument is a positional `u32`, not an object. Only
valid after `refundAfterBlock` has elapsed.

### giftRefund

```typescript theme={null}
sdk.giftRefund(params: {
  recipientWallet: string;
  blind: string;
  refundAfterBlock: number;
  tokenId: number;
  amount: string;
  treeNumber: number;
  leafIndex: number;
}): Promise<TransferResult>
```

Refund by supplying every gift field manually — a fallback for when the local
gift record is lost.

### getPendingGifts

```typescript theme={null}
sdk.getPendingGifts(): Promise<PendingGift[]>
```

List the pending gifts addressed to the authenticated wallet.

### getGiftRecords

```typescript theme={null}
sdk.getGiftRecords(): GiftRecord[]
```

The records of the gifts you funded in this session, newest first. These are
what make an unclaimed gift refundable. They live in memory only, so they are
gone after a reload; store each `giftRecord` yourself when the gift is funded.

Throws `NOT_AUTHENTICATED` once the session can no longer read the account,
and `SESSION_EXPIRED` when the read itself trips an idle or absolute auto-lock
that ends the session. A lock that keeps the read session still answers.
Merge what it returns into your stored records by `cGift` rather than
replacing them with it.

### decodeGiftLink

```typescript theme={null}
sdk.decodeGiftLink(link: string): GiftLinkPreview
```

Decode a claim link into a preview. Synchronous and offline — no network call,
no `await`.

<Info>
  `giftFundSecretBearer` also exists. It is a **bearer instrument** (whoever
  holds the link can claim), selected only by that explicit method. See the
  [concept page](/sdk/concepts/claimable-transfers) before considering it.
</Info>

### Gift Types

```typescript theme={null}
interface GiftRecord {
  recipientWallet: string;
  blind: string;
  refundAfterBlock: number;
  tokenId: number;
  amount: string;
  cGift: string;
  giftNpk: string;
  requestId: string;
  fundingTreeNumber?: number;
  giftLeafIndex?: number;
  status?: string;
}

interface PendingGift {
  index: number;
  treeNumber: number;
  leafIndex: number;
  cGift: string;
  tokenId: number;
  amount: string;
  refundAfterBlock: number;
  recipientWallet: string;
}

interface GiftLinkPreview {
  server: string;
  chainId: number;
  cGift: string;
  recipientWallet: string;
  tokenId: number;
  amount: string;
  refundAfterBlock: number;
}
```

***

## Portal

Portal deposit addresses give a user a reusable, public, exchange-style deposit
address. WASM exposes the portal lifecycle as instance methods on `sdk`; the
module also keeps two low-level namespace functions for custodial integrations.
See the [Portal Deposits guide](./guides/portal-deposits) and the
[concept page](/sdk/concepts/portal-deposits).

### Namespace functions

```typescript theme={null}
import {
  generatePortalEoa,
  encodePortalWithdraw,
} from '@sunnyside-io/privacy-boost-wasm';
```

#### generatePortalEoa

```typescript theme={null}
generatePortalEoa(): PortalEoa
```

Generate a fresh portal EOA — the public deposit address and its private key —
for a custodial integration that manages its own EOA key. The seed-derived
`sdk.createPortal(...)` flow does not expose a private key.

#### encodePortalWithdraw

```typescript theme={null}
encodePortalWithdraw(token: string, to: string, amount: string): string
```

Encode `PortalDelegate.withdraw(token, to, amount)` calldata for a custodial
integration that already controls its own portal EOA. For seed-derived portals,
prefer `sdk.withdrawPortal(...)`, which builds and signs the transaction in core.

### Instance methods

#### createPortal

```typescript theme={null}
sdk.createPortal(index?: number): Promise<Portal>
```

Derive portal `E`, gaslessly delegate and register it via the server relays, then
publish the discovery entry. Omitting `index` uses the next free derivation
index.

#### listPortalsPage

```typescript theme={null}
sdk.listPortalsPage(
  cursor: string | undefined,
  limit: number,
): Promise<PortalInfoPage>
```

Fetch one bounded page without accumulating earlier pages. `limit` must be
between `1` and `100`. Pass `nextCursor` back unchanged; an absent
`nextCursor` marks the final page.

#### listPortalDepositsPage

```typescript theme={null}
sdk.listPortalDepositsPage(
  portal: string,
  cursor: string | undefined,
  limit: number,
): Promise<PortalDepositPage>
```

Fetch one bounded deposit page. Cursor and limit semantics match
`listPortalsPage`.

#### getPortalStatus

```typescript theme={null}
sdk.getPortalStatus(portal: string): Promise<PortalStatus>
```

Return the registry/on-chain status for one portal.

#### sweepPortal

```typescript theme={null}
sdk.sweepPortal(portal: string, tokenId: number): Promise<string>
```

Send `requestPortalDeposit(portal, tokenId)` from the connected wallet and return
the transaction hash.

#### reclaimPortalDeposit

```typescript theme={null}
sdk.reclaimPortalDeposit(depositId: string): Promise<string>
```

Send `cancelPortalDeposit(depositId)` from the connected wallet and return the
transaction hash.

#### withdrawPortal

```typescript theme={null}
sdk.withdrawPortal(
  index: number,
  token: string,
  to: string,
  amount: string,
  nonce: string,
  gasLimit: string,
  maxFeePerGas: string,
  maxPriorityFeePerGas: string,
): string
```

Build a signed EIP-1559 transaction that withdraws raw funds resting at the
seed-derived portal `E` to `to`. Broadcast the returned raw transaction yourself.

#### portalDelegateAddress

```typescript theme={null}
sdk.portalDelegateAddress(): string | undefined
```

The portal delegate address advertised by the server's `/info`, or `undefined`
when the server does not support portals.

#### publishPortalRegistry

```typescript theme={null}
sdk.publishPortalRegistry(portal: string, index: number): Promise<void>
```

Publish the discovery registry entry (`E → blind`) so the indexer can credit
deposits. `createPortal` runs this as its final step; this method is for
re-publishing an existing portal.

### Portal Types

```typescript theme={null}
interface Portal {
  address: string;
  index: number;
  bindH: string;
}

interface PortalInfo {
  address: string;
  index: number;
  status: PortalStatus;
}

interface PortalInfoPage {
  items: PortalInfo[];
  nextCursor?: string;
}

interface PortalStatus {
  registered: boolean;
  delegated: boolean;
  published: boolean;
}

interface PortalDeposit {
  portalDepositId: string;
  portal: string;
  tokenId: number;
  grossAmount: string;
  netAmount: string;
  sweepFeeBps: number;
  feeAmount: string;
  state: 'pending_sweep' | 'credited' | 'cancellable' | 'cancelled' | 'unknown';
  requestBlock: number;
  cancellableAtBlock: number;
}

interface PortalDepositPage {
  items: PortalDeposit[];
  nextCursor?: string;
}

interface PortalEoa {
  privateKey: string; // SECRET — your app custodies and signs with it
  address: string;    // the public portal deposit address E
}
```

***

## Deposit Cancellation

`requestDeposit` moves the depositor's tokens into the pool and holds them there
until the sequencer settles the request into the note tree. Almost always that
happens within a couple of blocks. When it doesn't, because of a commitment
mismatch, a malformed ciphertext, or a stalled sequencer, the tokens stay in
escrow and the depositor reclaims them by cancelling the request on-chain.

The server will tell you a shield *failed* — `getShieldStatus(requestId)` returns
`failed` for a commitment mismatch or a malformed ciphertext — but it says
nothing about the escrow behind it: whether the pool still holds it, who may
reclaim it, and when. A stalled sequencer doesn't even produce that `failed` row.
So use `getShieldStatus` to notice something went wrong, and these two methods to
find out what you can do about it. They read the pool directly and tell you what
it would do before you ask anyone to sign.

### Instance methods

#### listShieldsByStatus

```typescript theme={null}
sdk.listShieldsByStatus(statuses: string[]): Promise<UnsettledShield[]>
```

Find a `depositRequestId` you never kept. Lists the authenticated wallet's own
shield requests in the given public statuses (`pending`, `processing`,
`preconfirmed`, `completed`, `failed`), oldest first. An empty list means the
server's default of `pending`.

Pass `failed` to list the shields whose escrow may still be sitting in the pool,
then feed each `requestId` to `getDepositCancellation`. Served by the server and
scoped to the session, so it needs no `ChainReaderDelegate` and cannot enumerate
anyone else's deposits.

`failed` is a public status the server folds several internal ones into, so it
also covers shields rejected as below-minimum or blocked. A stalled sequencer
produces no failed row at all — those stay `pending`.

**Returns:** `UnsettledShield` per row: `requestId`, `status`, and the optional
`txHash`, `blockNumber`, `createdAt` and `error`.

#### depositRequestIdsFromTransaction

```typescript theme={null}
sdk.depositRequestIdsFromTransaction(txHash: string): Promise<string[]>
```

The fallback when the app kept the shield's transaction hash but not its request
id. Reads the receipt and returns every `DepositRequested` id the pool emitted,
in log order — more than one when an aggregating relayer settled several callers
in the same transaction.

Empty means the transaction emitted no `DepositRequested` from the configured
pool, which is the answer for a hash that was never a shield rather than an
error, so candidate hashes can be swept. Reads the chain, so it needs the same
`ChainReaderDelegate` as `getDepositCancellation`.

#### getDepositCancellation

```typescript theme={null}
sdk.getDepositCancellation(depositRequestId: string): Promise<DepositCancellation>
```

Read the pool and report whether `cancelDeposit` would succeed for the connected
wallet right now, without sending anything. Costs three `eth_call`s plus one
`eth_blockNumber`, so the wallet adapter has to implement the optional
`readContract(address, data)` and `getBlockNumber()` methods. It rejects with a
message naming the missing one if it can't read.

`depositRequestId` is the `DepositRequested` event's indexed `topic1`, a
`uint256`. It's the same value `sdk.shield(...)` and
[`finalizeShield`](#finalizeshield) return as `ShieldResult.requestId`. Pass it
as a decimal string or a `0x`-hex string; the `depositRequestId` on the result is
always normalized to decimal.

**Returns:** `DepositCancellation` with the escrowed amount, the block the delay
expires at, and a `reason` you can show the user directly.

#### cancelDeposit

```typescript theme={null}
sdk.cancelDeposit(depositRequestId: string): Promise<string>
```

Send `cancelDeposit(depositRequestId)` from the connected wallet and return the
transaction hash. The full escrowed amount goes back to the depositor.

The call preflights through `getDepositCancellation` and rejects locally, before
anything is signed, when the pool would revert. The rejection message is the
status `reason`, so a stuck deposit explains itself instead of burning gas on an
opaque revert. If the adapter can't run the preflight, the transaction is
submitted anyway and the chain arbitrates, which is the same trade-off the shield
path makes for its allowance check.

### Who can cancel, and when

Only the address that submitted `requestDeposit` can cancel it, and the refund
goes to that address. For a relayer-submitted shield that's the relayer, not the
note recipient, so a user whose deposit is genuinely theirs can still get
`NotDepositor` back.

The cancel delay is measured in **blocks** and read from the pool's
`cancelDelay()`. Read the deployed value instead of hardcoding a delay, using
`cancelDelay`, `cancellableAtBlock`, and `blocksRemaining` from the returned
status.

### Cancellation states

`state` mirrors the reverts in the pool's `cancelDeposit`, reported
permanent-blocker first rather than in the pool's own check order. The pool
checks the depositor before anything else, and that check would mask the two
conditions no caller can clear.

| State | Meaning |
| - | - |
| `Cancellable` | Every precondition holds; the transaction should land. |
| `NotFound` | The pool holds no escrow under this id. It was never created, or it's already been cancelled or rescued. Settling does not clear the record, so a settled deposit reports `AlreadyProcessed` instead. |
| `AlreadyProcessed` | The sequencer already settled this deposit into the note tree, so the funds are in a note rather than in escrow. This blocks the rescue path too, so it's reported ahead of `GatewayOrigin`. |
| `GatewayOrigin` | A gateway-origin deposit. It carries a rescue commitment and has to be recovered with `rescueGatewayDeposit` by its rescue authority instead. |
| `NotDepositor` | Escrow exists but belongs to another address. |
| `TooEarly` | Escrow is reclaimable, but the cancel delay hasn't elapsed yet. The one rejecting state that resolves on its own. |

A gateway-origin deposit stores the gateway contract as its depositor, never the
wallet whose funds routed through it, so it reports `GatewayOrigin` for every
caller and at every point in the delay window. Waiting never makes it
cancellable.

### Deposit Cancellation Types

```typescript theme={null}
type DepositCancelState =
  | 'Cancellable'
  | 'NotFound'
  | 'NotDepositor'
  | 'AlreadyProcessed'
  | 'TooEarly'
  | 'GatewayOrigin';

interface DepositCancellation {
  depositRequestId: string;   // the queried id, normalized to decimal
  state: DepositCancelState;
  cancellable: boolean;       // true exactly when state is 'Cancellable'
  depositor?: string;         // absent when no escrow exists
  caller: string;             // the address the status was evaluated against
  tokenId: number;
  totalAmount: string;        // escrow that would be refunded, decimal; "0" when absent
  requestBlock: number;
  cancelDelay: number;        // the pool's cancelDelay(), in blocks
  cancellableAtBlock: number; // requestBlock + cancelDelay
  currentBlock: number;       // chain head the status was evaluated at
  blocksRemaining: number;    // 0 once the delay has elapsed
  reason: string;             // human-readable explanation of state
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.