SDK reference

Reference for the Senddy TypeScript packages.

@senddy/sdk is deprecated. It still encodes the removed two-step deposit and shield calls and is broken against the current pool. Use @senddy/client for new integrations; the sections below that still show @senddy/sdk imports are kept only for readers maintaining old code.

Installation#

Terminal
npm install @senddy/client @senddy/core
# or
pnpm add @senddy/client @senddy/core

Core client#

createSenddyClient#

The high-level orchestrator: key derivation, proof generation, attestation, submission and sync.

TypeScript
import { createSenddyClient, SHARED_CONTRACTS, V3_CONTRACTS, toUSDC } from '@senddy/client';

const client = createSenddyClient({
  publicClient,
  walletClient,
  chainId: 8453,
  pool: V3_CONTRACTS.Pool,
  usdc: SHARED_CONTRACTS.USDC,
  subgraphUrl: V3_CONTRACTS.Subgraph[0],
  apiUrl: 'https://api.senddy.com',
  apiKey: process.env.SENDDY_API_KEY,
});

await client.init();

Key methods:

MethodDescription
init()Derive keys, open storage, run the first sync
deposit(amount, { token })Deposit and shield in one transaction (EIP-3009 authorization)
depositWithEphemeralAccount(args)Deposit from a one-time address, through your relay submitter
transfer(to, amount, { memo, anonymous, token })Private transfer to a senddy1… address
prepareTransfer / submitTransferThe same in two steps, for a review screen
quoteSwap(tokenIn, tokenOut, amount)Route, expected output, floor and fees, without proving
swap(tokenIn, tokenOut, amount, { toleranceBps })Private swap between two assets
prepareSwap / submitSwapThe same in two steps
withdraw(to, amount, { token })Withdraw to a public address
hyperliquidInboundAddress(args)A deposit address for funds coming from Hyperliquid
solanaInboundAddress(args)A deposit address for USDC coming from Solana
chainInboundAddress({ chainId, saveIntent })A deposit address on another EVM chain (EVM_BRIDGE_CHAINS)
sweepEphemeralAddress({ intent })Shield what arrived at one of those addresses
bridgeToHyperliquid(args)Withdraw to Hyperliquid
bridgeToSolana(args)Withdraw to a Solana address
bridgeToChain({ chainId, recipient, amount, saveRefundIntent })Withdraw to another EVM chain
bridgeToLighter(args)Withdraw to a Lighter account, by wallet or account index
bridgeStatus(query)Track a bridge by deposit address or transaction
getBalance(token?)Private balance in shares and in the asset, yield included
getAssetIndex(token?) / getExitFeeBps(token?)Share value and withdrawal fee for an asset
getReceiveAddress()The user's senddy1… receive address
sync() / startAutoSync() / stopAutoSync()Pull new pool activity
on(event, cb)Subscribe to balanceChange, balancesChange, depositDetected, …
consolidate(opts)Merge fragmented notes. Runs automatically when needed
destroy()Zero keys and stop background work, on sign-out

There is one deposit path and it is atomic — no approval, no Permit2, no escrow, no separate shield step, and nothing to cancel or resume.

Proof Generation#

proveShield#

Generate a zero-knowledge proof to shield (privatize) a deposit.

TypeScript
import { proveShield } from '@senddy/sdk';

const proof = await proveShield({
  // Shield circuit inputs
});

proveSpend#

Generate a zero-knowledge proof to spend (transfer or withdraw) private notes.

TypeScript
import { proveSpend } from '@senddy/sdk';

const proof = await proveSpend({
  // Spend circuit inputs
});

Attestor Client#

createAttestorClient#

Create a client to communicate with the Senddy attestor service.

TypeScript
import { createAttestorClient } from '@senddy/sdk';

const attestor = createAttestorClient({
  url: process.env.ATTESTOR_URL,
});

// Request a one-step deposit+shield attestation
const depositAttestation = await attestor.attestDepositShield(proof);

// Request a spend attestation
const spendAttestation = await attestor.attestSpend(proof);

Address Utilities#

TypeScript
import {
  isValidSenddyAddress,
  encodeSenddyAddress,
  decodeSenddyAddress,
} from '@senddy/core';

// Validate
isValidSenddyAddress('senddy1abc...'); // true/false

// Encode public key to Senddy address
const address = encodeSenddyAddress(publicKeyBytes);

// Decode Senddy address to public key
const { publicKey } = decodeSenddyAddress('senddy1abc...');

Calldata encoding#

Helpers for building pool calldata yourself (smart-wallet batches, custom relayers):

TypeScript
import {
  encodePoolV3DepositAndShieldCalldata,     // EOAs: v, r, s
  encodePoolV3DepositAndShieldSigCalldata,  // smart wallets: ERC-1271 bytes
  depositSignatureMode,                     // picks 'vrs' vs 'bytes' for a depositor
  depositAuthorizationNonce,                // keccak(keccak(c0‖c1)‖proofHash)
  buildReceiveWithAuthorizationTypedData,
} from '@senddy/client';

The EIP-3009 authorization nonce is derived from the note commitments and the proof hash, so an authorization can only ever be used for the one shield it was signed for.

Sync Engine#

The sync engine monitors the blockchain for new commitments and nullifiers relevant to your keys.

TypeScript
import { SyncEngine, TachyonSyncEngine } from '@senddy/sdk';

// Standard sync
const sync = new SyncEngine({ /* options */ });

// Tachyon sync — O(1) spent detection via precomputed nullifiers
const tachyon = new TachyonSyncEngine({ /* options */ });

Bridging#

@senddy/sdk's CCTPBridge is gone. Every bridge now goes through Relay deposit addresses, with the @senddy/client methods in the table above. See Move money in and out.

Graph Client#

Query the Senddy subgraph for on-chain data:

TypeScript
import { Subgraph } from '@senddy/sdk';

const graph = new Subgraph();

// Query recent commitments, nullifiers, pool state, etc.

Contract Addresses#

TypeScript
import { SHARED_CONTRACTS, V3_CONTRACTS } from '@senddy/sdk';

// Access deployed contract addresses
console.log(V3_CONTRACTS.pool);     // Pool contract
console.log(V3_CONTRACTS.verifier); // Verifier contract

Key Derivation#

TypeScript
import { deriveKeysWithEIP712 } from '@senddy/sdk';

// Derive Senddy keys from an EIP-712 signature
const keys = await deriveKeysWithEIP712(signer);

Memo Encryption#

TypeScript
import {
  encodeMemoEnvelopeV4_1,
  tryDecryptAsReceiverUnified,
} from '@senddy/sdk';

// Encrypt a memo for a recipient
const envelope = encodeMemoEnvelopeV4_1(memo, recipientPubKey);

// Decrypt a received memo
const memo = tryDecryptAsReceiverUnified(envelope, myPrivateKey);