Confidential Asset (CA)
此内容尚不支持你的语言。
The @aptos-labs/confidential-asset 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-asset @aptos-labs/ts-sdk@aptos-labs/confidential-asset-bindings is a transitive dependency that ships the WebAssembly glue used
for range proof generation/verification and discrete log solving. You don’t need to install it directly.
WASM initialization
Section titled “WASM initialization”The .wasm binary is loaded lazily — it is not bundled with the SDK, so apps that never use confidential
assets don’t pay the ~774 KiB binary cost. The first call to a function that needs WASM will trigger a one-time
load (from node_modules in Node.js, or from a CDN in the browser). No explicit initializeWasm() call is
required.
For native apps, you can generate Android and iOS bindings from the confidential-asset-wasm-bindings repository.
Create the ConfidentialAsset client:
import { AptosConfig, Network } from "@aptos-labs/ts-sdk";import { ConfidentialAsset } from "@aptos-labs/confidential-asset";
const config = new AptosConfig({ network: Network.TESTNET });const confidentialAsset = new ConfidentialAsset({ config });The constructor also accepts:
confidentialAssetModuleAddress— override the framework address (defaults to0x1).withFeePayer— whentrue, every transaction submitted by this client is built with a fee-payer address.
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-asset";
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);Or derive a DK from a BIP-44 mnemonic + hardened derivation path (Ed25519 supports hardened derivation only):
const dk = TwistedEd25519PrivateKey.fromDerivationPath( "m/44'/637'/0'/0'/0'", "your twelve word mnemonic here ...",);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, recipient: someAddress, // Optional — defaults to the signer's own confidential balance});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 withPauseIncoming: false, // Set to true to also pause incoming transfers (for key rotation flows)});This may return multiple transaction responses (a normalization transaction followed by a rollover transaction) if the available balance was not already normalized.
Set withPauseIncoming: true to invoke the on-chain
rollover_pending_balance_and_pause entry function,
which atomically rolls over and pauses incoming transfers — the prerequisite for key rotation.
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 memo: new TextEncoder().encode("invoice-42"), // Optional, up to 256 bytes});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,});Read the auditor hint recorded against the user’s
confidential store. Auditors compare its (isGlobal, epoch) against the current effective auditor config
to detect stale ciphertexts:
const hint = await confidentialAsset.getEffectiveAuditorHint({ accountAddress: user.accountAddress, tokenAddress: TOKEN_ADDRESS,});
if (hint) { console.log(`Encrypted under ${hint.isGlobal ? "global" : "asset-specific"} auditor at epoch ${hint.epoch}`);}Emergency Pause
Section titled “Emergency Pause”Governance can pause all user operations on the protocol via
set_emergency_paused.
Check the current state before submitting transactions:
const paused = await confidentialAsset.isEmergencyPaused();Indexer Activities
Section titled “Indexer Activities”Query confidential asset activity for an account from the indexer. Each row is a discriminated union
keyed on event_type ("Registered", "Deposited", "Withdrawn", "Transferred", "Normalized",
"RolledOver", "KeyRotated", "IncomingTransfersPauseChanged", "AllowListingChanged",
"ConfidentialityForAssetTypeChanged", "GlobalAuditorChanged", "AssetSpecificAuditorChanged"):
const activities = await confidentialAsset.getActivities({ where: { owner_address: { _eq: user.accountAddress.toStringLong() } }, orderBy: [{ transaction_version: "desc" }], limit: 20,});
for (const a of activities) { if (a.event_type === "Transferred") { // Narrowed to TransferredActivity — counterparty_address is non-null, // event_data has amount_P / amount_R_* / memo / sender_auditor_hint. console.log(a.counterparty_address, a.event_data.memo); } else if (a.event_type === "Withdrawn") { // Narrowed to WithdrawnActivity — `amount` is plaintext. console.log(a.amount, a.event_data.auditor_hint); }}