Encrypted Mempool
Encrypted-mempool transactions wrap your Move payload in a ciphertext at build time, so it stays encrypted while pending and is decrypted by validators just before execution. The flow is the same as a plaintext transaction — build → sign → submit → wait — with a few extra options passed to aptos.transaction.build.*.
Prerequisites
Section titled “Prerequisites”Encrypted mempool is currently live on devnet, with testnet support coming soon and mainnet to follow. The network you submit to must support the feature; check the ledger info first:
const ledgerInfo = await aptos.getLedgerInfo();const supportsEncrypted = Boolean(ledgerInfo.encryption_key);If the node doesn’t expose encryption_key, building with encrypted: true throws.
The senderAuthenticationKey option is optional — the SDK fetches it from chain and caches it for ~1 hour per address. Pass it explicitly to bypass the lookup, which is useful immediately after a key rotation. It accepts an AuthenticationKey instance, an AccountPublicKey (the SDK derives the auth key automatically), or a raw 32-byte hex string. For accounts that have never rotated their key, the account address bytes are identical to the authentication key bytes, so you can pass the address hex directly.
Build, sign, and submit
Section titled “Build, sign, and submit”-
Build with
encrypted: true.const transaction = await aptos.transaction.build.simple({sender: sender.accountAddress,data: {function: "0x1::aptos_account::transfer",functionArguments: [receiver.accountAddress, amount],},options: {encrypted: true,// senderAuthenticationKey is optional — SDK fetches from chain automatically},}); -
Sign and submit as usual.
const pending = await aptos.signAndSubmitTransaction({signer: sender,transaction,});await aptos.waitForTransaction({ transactionHash: pending.hash });
Simulating an encrypted transaction
Section titled “Simulating an encrypted transaction”aptos.transaction.simulate.* rejects encrypted payloads — simulation is not supported directly. It is possible to simulate indirectly using a plaintext build, but this is strongly discouraged: simulation is unencrypted, and submitting a simulation closely followed by an encrypted transaction with the same payload allows observers to correlate the two and infer the encrypted transaction’s contents.
If you must simulate to estimate gas, build a plaintext twin with the same data, simulate that, then build again with encrypted: true for submission:
build.simple({ ..., options: { /* no encrypted */ } })— same function, arguments, gas hints.aptos.transaction.simulate.simple({ ... })— tunemaxGasAmount/gasUnitPricefrom the result.build.simple({ ..., options: { encrypted: true, ...gas from step 2 } })— submit this build.
Multi-agent
Section titled “Multi-agent”senderAuthenticationKey and secondarySignerAuthenticationKeys are both optional — any omitted or undefined entry is fetched from chain. Pass them explicitly to skip the lookup (e.g. right after key rotation). Entries must be in the same order as secondarySignerAddresses.
const transaction = await aptos.transaction.build.multiAgent({ sender: sender.accountAddress, secondarySignerAddresses: [secondary.accountAddress], data: { function: "0x<module>::<contract>::<multi_agent_entry>", functionArguments: [/* ... */], }, options: { encrypted: true, senderAuthenticationKey: sender.publicKey, // optional secondarySignerAuthenticationKeys: [secondary.publicKey], // optional; entries can be undefined },});Fee payer (sponsored)
Section titled “Fee payer (sponsored)”feePayerAuthenticationKey is optional when feePayerAddress is a non-zero sponsor — omit it and the SDK fetches it from chain automatically.
const transaction = await aptos.transaction.build.simple({ sender: sender.accountAddress, withFeePayer: true, data: { function: "0x1::aptos_account::transfer", functionArguments: [receiver.accountAddress, amount], }, options: { encrypted: true, senderAuthenticationKey: sender.publicKey, // optional feePayerAuthenticationKey: sponsor.publicKey, // optional },});Orderless
Section titled “Orderless”Pass replayProtectionNonce together with encrypted: true. The transaction uses orderless replay protection while the payload stays encrypted. See Orderless Transactions.
const transaction = await aptos.transaction.build.simple({ sender: sender.accountAddress, data: { function: "0x1::aptos_account::transfer", functionArguments: [receiver.accountAddress, amount], }, options: { encrypted: true, replayProtectionNonce: 12345n, // senderAuthenticationKey is optional — SDK fetches from chain automatically },});claimedEntryFunction (optional)
Section titled “claimedEntryFunction (optional)”You can set claimedEntryFunction to intentionally expose the entry function that the transaction is targeting to pre-decryption observers — required e.g. for Gas Station. The SDK sets this automatically for sponsored or multisig builds — you typically never set it manually. The one reason to override is when you want to expose only the module name and keep the function name hidden until decryption:
options: { encrypted: true, feePayerAuthenticationKey: sponsor.publicKey, claimedEntryFunction: { module: "0x1::aptos_account", // functionName intentionally omitted },};When you do set functionName, it must match the real entry function exactly.
Caveats
Section titled “Caveats”- Keyless, federated keyless, and account abstraction signers are not supported. Encrypted transactions cannot be signed by these account types — the build/submit will throw before contacting the node.
- Encrypted multisig is feature-gated on some networks; submission may be rejected even when the SDK build succeeds. Watch your network’s release notes.
Further reading
Section titled “Further reading”- Encrypted Pending Transactions guide — product overview, visibility, AIP-144 spec, and cryptography paper.
- Aptos batch encryption (cryptography) —
aptos-coreimplementation detail (optional reading).