Shadow Accounts
Shadow accounts give one user a persistent, pseudonymous identity for one dapp. They are useful for positions that live across transactions - staking, lending, rewards, and any protocol that needs to recognize a returning caller.
This page follows Privacy SDK 0.14.3-rc.8. The old subaccounts(...) API was
renamed and is not compatible with the current anonymizer. Use
shadowAccounts(...) throughout.
Snippets assume transfers, account and provider from
Getting Started.
Configure the anonymizer
Pass the deployment for the connected network when creating transfers:
const transfers = createPrivateTransfers({
account,
viewingKeyProvider,
provingProvider,
discoveryProvider,
poolContractAddress: process.env.POOL_ADDRESS!,
shadowAccountAnonymizerAddress: process.env.SHADOW_ACCOUNT_ANONYMIZER_ADDRESS!,
})
Calling shadowAccounts(...) without shadowAccountAnonymizerAddress throws.
Use the current Mainnet or Sepolia value from
Deployed Contract Addresses.
Commitments and addresses
The builder derives the partial and full commitments locally from the user, viewing key, anonymizer, dapp name, and nonce:
import { shadowAccountAddress } from "@starkware-libs/starknet-privacy-sdk"
const shadow = transfers.build().shadowAccounts("myDapp")
const partial = await shadow.partialCommitment()
const commitment = await shadow.commitment(0n)
const address = shadowAccountAddress(
commitment,
BigInt(process.env.SHADOW_ACCOUNT_ANONYMIZER_ADDRESS!),
)
shadowAccountAddress uses the exported, fixed PRIMER_CLASS_HASH, matching
the current Cairo deployment pattern. It works before the account is deployed
and does not require an RPC call. The package also exports the lower-level
shadowAccountPartialCommitment and shadowAccountCommitment helpers when you
already hold the raw felts.
Invoke through the shadow account
Fund the shadow address, create an open note for each expected output token,
then queue the calls. The SDK translates this into one ComputeAndInvoke
against the ShadowAccountAnonymizer.
import { Open } from "@starkware-libs/starknet-privacy-sdk"
const provingBlockId = (await provider.getBlockNumber()) - 10
const { callAndProof } = await transfers
.build({ autoDiscover: { notes: "refresh" } })
.with(STRK)
.withdraw({ recipient: address, amount: 5n * 10n ** 18n })
.surplusTo(account.address, false)
.with(STRK)
.transfer({ recipient: account.address, amount: Open })
.done()
.shadowAccounts("myDapp")
.invoke(0n, {
calls: [stakingContract.populate("stake", { amount: 1000n })],
collectPolicy: { type: "all" },
})
.execute({ provingBlockId })
// Submit callAndProof with the usual proofDetails + tip: 0n tail.
collectPolicy is optional in the SDK and defaults to { type: "all" }.
collectPolicy |
Amount collected into each open note |
|---|---|
{ type: "all" } |
The token's entire shadow-account balance |
{ type: "diff" } |
Only the balance gained during this interaction |
{ type: "exact", amount } |
Exactly amount |
Read deployed and undeployed accounts
The exported ShadowAccountAnonymizerABI includes these views:
get_shadow_accounts(partial, start, end, until_undeployed)resolves a nonce range and returns{ nonce, address, is_deployed }for each entry.get_shadow_account(commitment)returns the stored address of an already deployed account, or zero when it has not been deployed.get_shadow_account_class_hash()returns the class installed after the primer deployment. It is not the class hash used for address derivation.
The scan range is limited to 1,024 nonces. With until_undeployed: true, the
range stops at the first address that has not been deployed.
Things to notice
- A shadow account's balances, calls, and positions are public. Privacy comes from hiding the link to the user's main account.
- A shadow account has no keys. Only the anonymizer can execute through it.
- One transaction can contain at most one invoke-phase action, whether it is a
normal
invokeor a shadow-account invocation. - One collection policy applies to every open note settled by the invocation.
- The current anonymizer returns the shadow-account address used by deposits so the pool can apply its screening policy to that address.
Next: Proving Configuration - prepare and submit the resulting call safely.