Confidential Asset (CA)
The @aptos-labs/confidential-assets package provides a high-level TypeScript API for interacting with the Confidential Asset protocol on Aptos.
Installation
Section titled “Installation”npm install @aptos-labs/confidential-assets @aptos-labs/ts-sdk @aptos-labs/confidential-asset-wasm-bindingsInitialization
Section titled “Initialization”Operations in CA require ZK-proof generation and discrete log solving, both powered by a unified WASM module. Initialize it once at the top of your app:
import { initializeWasm } from "@aptos-labs/confidential-assets";
await initializeWasm();This initializes both the range proof system (for generating ZKPs) and the discrete log solver (for decrypting balances).
For native apps, you can generate Android and iOS bindings from the confidential-asset-wasm-bindings repository.
Now create the ConfidentialAsset client:
import { AptosConfig, Network } from "@aptos-labs/ts-sdk";import { ConfidentialAsset } from "@aptos-labs/confidential-assets";
const config = new AptosConfig({ network: Network.TESTNET });const confidentialAsset = new ConfidentialAsset({ config });Create Decryption Key (DK)
Section titled “Create Decryption Key (DK)”To interact with the confidential asset, create a unique key pair first.
Generate new:
import { TwistedEd25519PrivateKey } from "@aptos-labs/confidential-assets";
const dk = TwistedEd25519PrivateKey.generate();Or import an existing one:
const dk = new TwistedEd25519PrivateKey("0x...");You can also derive it from a signature (for testing purposes, don’t use in production):
import { Account } from "@aptos-labs/ts-sdk";
const user = Account.generate();const signature = user.sign(TwistedEd25519PrivateKey.decryptionKeyDerivationMessage);const dk = TwistedEd25519PrivateKey.fromSignature(signature);Register
Section titled “Register”Before using confidential balances, register an encryption key for the asset type:
const registerTxResponse = await confidentialAsset.registerBalance({ signer: user, tokenAddress: TOKEN_ADDRESS, decryptionKey: dk,});Check if a user has already registered for a specific asset type:
const isRegistered = await confidentialAsset.hasUserRegistered({ accountAddress: user.accountAddress, tokenAddress: TOKEN_ADDRESS,});Deposit
Section titled “Deposit”Deposit tokens from a non-confidential FA balance into a confidential balance:
const depositTxResponse = await confidentialAsset.deposit({ signer: user, tokenAddress: TOKEN_ADDRESS, amount: 100n,});Get User’s Balance
Section titled “Get User’s Balance”Check the user’s balance after the deposit. The getBalance method returns the decrypted pending and available balances:
const balance = await confidentialAsset.getBalance({ accountAddress: user.accountAddress, tokenAddress: TOKEN_ADDRESS, decryptionKey: dk,});
console.log("Available:", balance.availableBalance()); // bigintconsole.log("Pending:", balance.pendingBalance()); // bigintRollover
Section titled “Rollover”After depositing, the funds are in the pending balance. A user cannot spend pending balance directly —
it must be rolled over to the available balance.
const rolloverTxResponses = await confidentialAsset.rolloverPendingBalance({ signer: user, tokenAddress: TOKEN_ADDRESS, senderDecryptionKey: dk, // Required if balance might not be normalized});This may return multiple transaction responses (a normalization transaction followed by a rollover transaction) if the available balance was not already normalized.
Normalization
Section titled “Normalization”Usually you don’t need to call normalization explicitly —
rolloverPendingBalance handles it automatically when given a senderDecryptionKey.
If you want to normalize manually:
const isNormalized = await confidentialAsset.isBalanceNormalized({ accountAddress: user.accountAddress, tokenAddress: TOKEN_ADDRESS,});
if (!isNormalized) { const normalizeTxResponse = await confidentialAsset.normalizeBalance({ signer: user, senderDecryptionKey: dk, tokenAddress: TOKEN_ADDRESS, });}Withdraw
Section titled “Withdraw”Withdraw assets from a confidential balance back to a non-confidential FA balance:
const withdrawTxResponse = await confidentialAsset.withdraw({ signer: user, senderDecryptionKey: dk, tokenAddress: TOKEN_ADDRESS, amount: 50n, recipient: recipientAddress, // Optional — defaults to signer's address});If the available balance is insufficient but the total (available + pending) is enough,
use withdrawWithTotalBalance which automatically rolls over the pending balance first:
const withdrawTxResponses = await confidentialAsset.withdrawWithTotalBalance({ signer: user, senderDecryptionKey: dk, tokenAddress: TOKEN_ADDRESS, amount: 50n,});Transfer
Section titled “Transfer”For a confidential transfer, you need the recipient’s account address. The SDK automatically fetches the recipient’s on-chain encryption key:
const transferTxResponse = await confidentialAsset.transfer({ signer: user, recipient: recipientAddress, tokenAddress: TOKEN_ADDRESS, amount: 50n, senderDecryptionKey: dk, additionalAuditorEncryptionKeys: [], // Optional voluntary auditors});Like with withdrawals, you can use transferWithTotalBalance to automatically roll over the pending balance
if the available balance is insufficient:
const transferTxResponses = await confidentialAsset.transferWithTotalBalance({ signer: user, recipient: recipientAddress, tokenAddress: TOKEN_ADDRESS, amount: 50n, senderDecryptionKey: dk,});You can also look up a user’s encryption key directly:
const recipientEK = await confidentialAsset.getEncryptionKey({ accountAddress: recipientAddress, tokenAddress: TOKEN_ADDRESS,});Key Rotation
Section titled “Key Rotation”To rotate the encryption key, provide the current and new decryption keys. The SDK handles pausing incoming transfers, rolling over pending balance, rotating the key, and resuming transfers:
const newDk = TwistedEd25519PrivateKey.generate();
const rotationTxResponses = await confidentialAsset.rotateEncryptionKey({ signer: user, senderDecryptionKey: dk, newSenderDecryptionKey: newDk, tokenAddress: TOKEN_ADDRESS,});
// Save the new decryption key — the old one is no longer validconsole.log("New DK:", newDk.toString());Pause/Unpause Incoming Transfers
Section titled “Pause/Unpause Incoming Transfers”Check if a user’s incoming transfers are paused:
const isPaused = await confidentialAsset.isIncomingTransfersPaused({ accountAddress: user.accountAddress, tokenAddress: TOKEN_ADDRESS,});Auditors
Section titled “Auditors”Query auditor configuration for an asset type:
// Get effective auditor encryption key (asset-specific if set, otherwise global)const auditorEK = await confidentialAsset.getAssetAuditorEncryptionKey({ tokenAddress: TOKEN_ADDRESS,});