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.