Confidential Asset (CA)
The @aptos-labs/confidential-asset package is a high-level TypeScript client for the
Confidential Asset (CA) protocol.
It handles proof generation, WASM initialization, balance decryption, and transaction building automatically.
Installation
Section titled “Installation”npm i @aptos-labs/ts-sdk @aptos-labs/confidential-assetyarn add @aptos-labs/ts-sdk @aptos-labs/confidential-assetpnpm add @aptos-labs/ts-sdk @aptos-labs/confidential-assetThe package depends on @aptos-labs/confidential-asset-bindings for cryptographic operations
(Bulletproof range proofs and discrete-log decryption). The WASM binary is loaded on demand:
- Browser: fetched from the unpkg CDN automatically.
- Node.js: loaded from local
node_modules(no network required).
import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";import { ConfidentialAsset, TwistedEd25519PrivateKey, initializeWasm, isWasmInitialized,} from "@aptos-labs/confidential-asset";
const config = new AptosConfig({ network: Network.TESTNET });const aptos = new Aptos(config);
// The CA module is deployed at 0x1 (aptos_framework).const ca = new ConfidentialAsset({ config });
// Pre-load WASM before the first user interaction to avoid latency.await initializeWasm();console.log("WASM ready:", isWasmInitialized());ConfidentialAsset constructor options:
| Option | Type | Default | Description |
|---|---|---|---|
config | AptosConfig | required | Network configuration |
confidentialAssetModuleAddress | string | "0x1" | Module address |
withFeePayer | boolean | false | Use fee-payer transactions by default |
Key Management
Section titled “Key Management”CA operations use a dedicated keypair separate from the user's Aptos account signing key.
TwistedEd25519PrivateKey(decryption key, DK) — kept privately by the user; never leaves the client.TwistedEd25519PublicKey(encryption key, EK) — stored on-chain; used by others to encrypt amounts for this user.
import { TwistedEd25519PrivateKey } from "@aptos-labs/confidential-asset";
// Generate a new decryption keyconst dk = TwistedEd25519PrivateKey.generate();
// Derive the encryption key (public)const ek = dk.publicKey();
// Serialize for secure storageconst dkHex = dk.toString();
// Restore from a saved hex stringconst restoredDk = new TwistedEd25519PrivateKey(dkHex);Quick Start
Section titled “Quick Start”import { Aptos, AptosConfig, Network, Account } from "@aptos-labs/ts-sdk";import { ConfidentialAsset, TwistedEd25519PrivateKey } from "@aptos-labs/confidential-asset";
const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));const ca = new ConfidentialAsset({ config: aptos.config });
// APT Fungible Asset metadata addressconst TOKEN = "0xa";
const alice = Account.generate();const bob = Account.generate();// Fund accounts via faucet before proceeding...
// 1. Generate decryption keys and register confidential balancesconst aliceDk = TwistedEd25519PrivateKey.generate();const bobDk = TwistedEd25519PrivateKey.generate();await ca.registerBalance({ signer: alice, tokenAddress: TOKEN, decryptionKey: aliceDk });await ca.registerBalance({ signer: bob, tokenAddress: TOKEN, decryptionKey: bobDk });
// 2. Deposit 1000 tokens from Alice's public balance → her pending balanceawait ca.deposit({ signer: alice, tokenAddress: TOKEN, amount: 1000n });
// 3. Roll over pending → availableawait ca.rolloverPendingBalance({ signer: alice, tokenAddress: TOKEN, senderDecryptionKey: aliceDk,});
// 4. Decrypt and read Alice's balanceconst balance = await ca.getBalance({ accountAddress: alice.accountAddress, tokenAddress: TOKEN, decryptionKey: aliceDk,});console.log("Available:", balance.availableBalance()); // bigintconsole.log("Pending: ", balance.pendingBalance()); // bigint
// 5. Confidential transfer of 300 tokens to Bob (amount hidden on-chain)await ca.transfer({ signer: alice, tokenAddress: TOKEN, recipient: bob.accountAddress, amount: 300n, senderDecryptionKey: aliceDk,});
// 6. Withdraw 200 tokens back to Alice's public balanceawait ca.withdraw({ signer: alice, tokenAddress: TOKEN, senderDecryptionKey: aliceDk, amount: 200n,});API Reference
Section titled “API Reference”registerBalance
Section titled “registerBalance”Creates a confidential store for (signer, token) and publishes the EK on-chain.
await ca.registerBalance({ signer: alice, tokenAddress: TOKEN, decryptionKey: aliceDk,});
// Check registration firstconst registered = await ca.hasUserRegistered({ accountAddress: alice.accountAddress, tokenAddress: TOKEN,});deposit
Section titled “deposit”Transfers tokens from the signer's public FA balance into their pending_balance.
The deposited amount is publicly visible. No ZKP required.
await ca.deposit({ signer: alice, tokenAddress: TOKEN, amount: 1000n, recipient: alice.accountAddress, // optional; defaults to signer});getBalance
Section titled “getBalance”Fetches encrypted balances from chain and decrypts them locally with the DK.
const balance = await ca.getBalance({ accountAddress: alice.accountAddress, tokenAddress: TOKEN, decryptionKey: aliceDk,});
balance.availableBalance(); // bigint — spendable amountbalance.pendingBalance(); // bigint — pending (not yet spendable)Decryption uses BSGS (Baby-Step Giant-Step) over each 16-bit chunk via the WASM module.
rolloverPendingBalance
Section titled “rolloverPendingBalance”Moves pending_balance into available_balance.
await ca.rolloverPendingBalance({ signer: alice, tokenAddress: TOKEN, senderDecryptionKey: aliceDk, // needed if normalization is required withPauseIncoming: false, // set true before key rotation});normalizeBalance
Section titled “normalizeBalance”Re-packs available_balance chunks to 16-bit bounds. Usually called automatically by rolloverPendingBalance.
const normalized = await ca.isBalanceNormalized({ accountAddress: alice.accountAddress, tokenAddress: TOKEN,});if (!normalized) { await ca.normalizeBalance({ signer: alice, tokenAddress: TOKEN, senderDecryptionKey: aliceDk, });}transfer
Section titled “transfer”Transfers a secret amount from sender's available_balance to recipient's pending_balance.
The amount is never revealed on-chain.
await ca.transfer({ signer: alice, tokenAddress: TOKEN, recipient: bob.accountAddress, amount: 300n, senderDecryptionKey: aliceDk, additionalAuditorEncryptionKeys: [], // optional voluntary auditors memo: new TextEncoder().encode("invoice #42"), // optional, max 256 bytes});To automatically include a rollover before transferring if needed, use transferWithTotalBalance:
await ca.transferWithTotalBalance({ signer: alice, tokenAddress: TOKEN, recipient: bob.accountAddress, amount: 300n, senderDecryptionKey: aliceDk,});withdraw
Section titled “withdraw”Moves tokens from available_balance back to a public FA store.
await ca.withdraw({ signer: alice, tokenAddress: TOKEN, senderDecryptionKey: aliceDk, amount: 200n, recipient: bob.accountAddress, // optional; defaults to signer});Use withdrawWithTotalBalance to automatically rollover before withdrawing if needed.
rotateEncryptionKey
Section titled “rotateEncryptionKey”Replaces the on-chain EK and re-encrypts the available_balance under the new key.
const newDk = TwistedEd25519PrivateKey.generate();
// The SDK handles: rollover + pause → rotate → resumeawait ca.rotateEncryptionKey({ signer: alice, tokenAddress: TOKEN, senderDecryptionKey: aliceDk, // old DK newSenderDecryptionKey: newDk, // new DK});
// Save newDk securely — the old DK is now invalid for this balance.Auditors
Section titled “Auditors”When a token has a configured auditor (global or asset-specific), the SDK automatically includes encrypted amount ciphertexts for that auditor in every spend operation.
// Query the asset-level auditor key (undefined if not set)const auditorEk = await ca.getAssetAuditorEncryptionKey({ tokenAddress: TOKEN });
// Add voluntary (per-transfer) auditorsawait ca.transfer({ // ...other params additionalAuditorEncryptionKeys: [auditorEk],});Fee Payer
Section titled “Fee Payer”All write methods support fee-payer transactions for gas sponsorship:
// Enable globallyconst caFeePayer = new ConfidentialAsset({ config, withFeePayer: true });
// Or per-callawait ca.deposit({ signer: alice, tokenAddress: TOKEN, amount: 1000n, withFeePayer: true });Error Reference
Section titled “Error Reference”| Error code | Meaning | Resolution |
|---|---|---|
E_CONFIDENTIAL_STORE_NOT_REGISTERED (3) | No store for this (user, token) | Call registerBalance first |
E_CONFIDENTIAL_STORE_ALREADY_REGISTERED (2) | Already registered | Check hasUserRegistered first |
E_INCOMING_TRANSFERS_PAUSED (4) | Recipient paused incoming transfers | Wait or ask recipient to resume |
E_PENDING_BALANCE_MUST_BE_ROLLED_OVER (6) | Pending count hit 65536 | Call rolloverPendingBalance |
E_NORMALIZATION_REQUIRED (7) | Normalize before rollover | Pass senderDecryptionKey to rollover, or call normalizeBalance |
E_ALREADY_NORMALIZED (8) | Already normalized | Check isBalanceNormalized first |
E_PENDING_BALANCE_NOT_ZERO_BEFORE_KEY_ROTATION (5) | Pending must be zero before rotation | SDK handles this automatically in rotateEncryptionKey |
E_SELF_TRANSFER (15) | Sender = recipient | Use a different recipient |
E_ASSET_TYPE_DISALLOWED (9) | Token not on allow-list | This token type is not yet enabled |
E_EMERGENCY_PAUSED (20) | Protocol paused by governance | Check isEmergencyPaused() and wait |
E_UNSAFE_DISPATCHABLE_FA (16) | Dispatchable FA not supported | Only non-dispatchable FA types are supported |
E_MEMO_TOO_LONG (19) | Memo > 256 bytes | Shorten the memo |
Best Practices
Section titled “Best Practices”Pre-warm WASM. Call initializeWasm() when the user opens the CA UI, before any operation, to avoid
a noticeable delay on the first proof generation.
Check rollover need before spending. If pendingBalance() is non-zero and would make the total sufficient,
use the *WithTotalBalance variants to combine rollover and spend automatically.
Verify recipient registration. Call hasUserRegistered before attempting a transfer.
Save the new key immediately after rotation. rotateEncryptionKey invalidates the old DK. Persist
the new DK and verify it can decrypt the balance before discarding the old one.
Privacy Boundaries
Section titled “Privacy Boundaries”| Hidden | Not hidden |
|---|---|
| Transfer amounts | Sender and recipient addresses |
| User balances | Total tokens in confidential mode (get_total_confidential_supply) |