SDK reference
Reference for the Senddy TypeScript packages.
@senddy/sdkis deprecated. It still encodes the removed two-step deposit and shield calls and is broken against the current pool. Use@senddy/clientfor new integrations; the sections below that still show@senddy/sdkimports are kept only for readers maintaining old code.
Installation#
npm install @senddy/client @senddy/core
# or
pnpm add @senddy/client @senddy/coreCore client#
createSenddyClient#
The high-level orchestrator: key derivation, proof generation, attestation, submission and sync.
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:
| Method | Description |
|---|---|
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 / submitTransfer | The 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 / submitSwap | The 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.
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.
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.
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#
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):
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.
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:
import { Subgraph } from '@senddy/sdk';
const graph = new Subgraph();
// Query recent commitments, nullifiers, pool state, etc.Contract Addresses#
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 contractKey Derivation#
import { deriveKeysWithEIP712 } from '@senddy/sdk';
// Derive Senddy keys from an EIP-712 signature
const keys = await deriveKeysWithEIP712(signer);Memo Encryption#
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);