On this page

ActionPool

Fund private notes, spend them with proofs, and settle external actions through ActionPool.

What ActionPool holds#

ActionPool is the immutable custody contract. ActionPoolKernel tracks asset reserves, the lean note tree, spent nullifiers and receipts for actions awaiting finalization. During construction, ActionPool deploys its spend verifier and asks the supplied factory to create its account registry. It has no upgrade initializer or unguarded deposit entrypoint.

Private balances are notes, not a balance mapping indexed by wallet address. Your client proves ownership and membership, supplies fresh nullifiers and commits to the outputs and public execution request. reserves(asset) counts credited private liabilities and leaves out unsolicited donations. Sending ERC-20 tokens straight to the Pool does not create a note or credit your account.

Pending, Active and Settled notes
Pending, Active and Settled notes

Fund a note#

The first profile deposit publishes encrypted recovery material and collects funds in one transaction. Later deposits must match the published block/hash pointer and the caller's funding nonce. Independent accounts instead identify the recovery wallet and account, registry revision and full profile hash. These checks keep a stale browser from funding the wrong profile.

The Pool must receive exactly the requested ERC-20 amount. For native ETH, use address(0) and send matching msg.value. After collecting tokens, it checks the profile or account pointer again to catch changes made by a callback. If collection, pointer validation or note append fails, the transaction rolls back both the note and nonce.

solidity
function publishAndDeposit(
    address _asset, uint256 _amount, uint256 _ownerCommitment,
    bytes32 _recoverySalt, bytes calldata _ciphertext,
    bytes32 _policyCommitment
) external payable;

function depositWithProfile(
    address _asset, uint256 _amount, uint256 _ownerCommitment,
    bytes32 _recoverySalt, uint64 _expectedBlockNumber,
    bytes24 _expectedCiphertextHash, uint64 _expectedNonce,
    bytes32 _policyCommitment
) external payable;

function depositWithAccount(
    bytes32 _accountId, uint64 _expectedRevision,
    bytes32 _expectedProfileHash, uint64 _expectedNonce,
    address _asset, uint256 _amount, uint256 _ownerCommitment,
    bytes32 _recoverySalt, bytes32 _policyCommitment
) external payable;

Spend private notes#

Call transact with one Groth16 Proof, 39 ordered public signals, a Request, two delivery envelopes, the sender-recovery capsule and any controller signatures. Request holds recipient, relayer, deadline and two output policy commitments. Before settling, the Pool checks the domain, note root, operation layout, execution digest, deadline, nullifiers, tree capacity and proof.

The two nonzero nullifiers must differ, and neither may be spent. Each nonzero output needs the delivery data committed by its proof. The spend relation also includes the full hash of the sender capsule. Public signal 38 names a controller when one is required; that controller must approve the proof, signals, request, capsule and verification-key hash under its current policy.

Settlement consumes the input notes, appends nonzero outputs and subtracts the public payout and relayer fee from reserves. To pay part of a note, create a recipient note and a change note. Recovery can restore the private keys, but it never clears spent nullifiers or removes controller approval requirements.

solidity
struct Proof { uint256[2] a; uint256[2][2] b; uint256[2] c; }
struct Request {
    address recipient;
    address relayer;
    uint256 deadline;
    bytes32[2] outputPolicyCommitments;
}
function transact(
    Proof calldata _proof, uint256[39] calldata _s,
    Request calldata _request, bytes[2] calldata _kemCiphertexts,
    bytes calldata _senderCapsule, bytes calldata _safeSignatures
) external;

Execute and finalize an action#

ActionRequest specifies adapter, operation, output asset, minimum output, owner tag, action data, recovery randomness, relayer, deadline and both output attachments. The Pool checks route admission and the adapter code hash. It approves only the input amount, measures the token changes and uses the resulting output amount for settlement.

transactAction calls the public venue and reserves tree slots for the output. The first input nullifier identifies its settlement commitment. The output becomes a spendable note only after finalizeQueuedOnchain appends it. Anyone can finalize without a second proof, even if the route is later revoked or the source policy expires.

transactActionAndFinalize executes the action and appends the output in one transaction, using the same authorization. A failed venue call, balance check or append rolls back token movement and nullifiers. Both paths use the same action digest, so someone holding a released proof can submit it through either path.

EntrypointResultCheck completion
transactAction(...)Executes the venue call and reserves an output receiptqueuedSettlementCM(firstNF) is nonzero
finalizeQueuedOnchain(uint256 _firstNF)Appends the reserved notes; anyone may call itReceipt is cleared and ActionFinalized is emitted
transactActionAndFinalize(...)Executes the action and appends its measured outputTransaction succeeds and the account recovers the output note

Roots, screening and note attachments#

The lean note tree holds at most 2^32 typed leaves, counting slots reserved for queued actions. Published nonzero canonical roots remain valid permanently. Spent nullifiers prevent replay against an old root. Appending unrelated notes therefore does not force you to rebuild an otherwise valid proof.

A screened Pending spend specifies the policy root, membership/non-membership mode and covered block. The policy must still use that root and mode, be active and unexpired, and cover at least the proved block. Increasing coverage keeps the proof usable; changing the source list may require another proof. Screening applies to direct Pending inputs, not the full ancestry of Active notes.

notePolicyCommitment stores an opaque attachment for each issued commitment, including zero, and the attachment cannot change. The Pool never executes FPL or uses that attachment as a spend rule. An action fixes its attachments during execution and installs the same values when finalized.

Spend verifier and hashes#

ActionPool deploys PinnedSpendVerifier itself. Call verifySpend with the Groth16 A/B/C points and 39 public signals in circuits/spend/public-layout.json order. Proof coordinates must be below the BN254 base-field modulus. The generated verifier also checks public scalar bounds before running the pairing check.

VK_HASH identifies the embedded verification-key encoding and is included in controller approvals. The Pool deploys the Poseidon implementation used for note and policy trees. Its domain tags are fixed; TreeConstants supplies the source-policy sentinel and empty-root values. Use a matching circuit layout, verifier key, hash artifacts and client encoding.

Read state and events#

Index Deposited for funding, EncryptedProfilePublished for encrypted profile versions, SpendRecorded for recovery data and ActionQueued/ActionFinalized for action progress. SpendRecorded contains the public signals, encoded request, delivery envelopes and capsule. Recovery can use these events even when a router or another wallet submitted the transaction.

Read or errorWhat to check
domain(), spendVerifier(), accountRegistry()The Pool's domain, verifier and account registry
knownRoot(root), spent(nullifier)Whether a root is accepted and an input has been consumed
noteCount(), reservedLeaves()Tree slots already used and reserved for queued outputs
reserves(asset), supportedToken(asset)Credited liabilities and admission for new funding or actions
InvalidOperation()Check context, current state, spent inputs and operation support
InvalidProof()The fixed spend verifier rejected the proof
TransferFailed()A token or payout call failed
BadPolicy()Check the policy schedule and screened spend inputs