diff --git a/content/contracts-compact/access-control.mdx b/content/contracts-compact/0.1.x/access-control.mdx
similarity index 100%
rename from content/contracts-compact/access-control.mdx
rename to content/contracts-compact/0.1.x/access-control.mdx
diff --git a/content/contracts-compact/api/access-control.mdx b/content/contracts-compact/0.1.x/api/access-control.mdx
similarity index 99%
rename from content/contracts-compact/api/access-control.mdx
rename to content/contracts-compact/0.1.x/api/access-control.mdx
index 1686289b..84568fec 100644
--- a/content/contracts-compact/api/access-control.mdx
+++ b/content/contracts-compact/0.1.x/api/access-control.mdx
@@ -49,7 +49,7 @@ More complex role relationships can be created by using [_setRoleAdmin](#AccessC
## AccessControl [toc] [#AccessControl]
```ts
diff --git a/content/contracts-compact/api/fungible-token.mdx b/content/contracts-compact/0.1.x/api/fungible-token.mdx
similarity index 99%
rename from content/contracts-compact/api/fungible-token.mdx
rename to content/contracts-compact/0.1.x/api/fungible-token.mdx
index b458cd81..1571706f 100644
--- a/content/contracts-compact/api/fungible-token.mdx
+++ b/content/contracts-compact/0.1.x/api/fungible-token.mdx
@@ -11,7 +11,7 @@ This module provides the full FungibleToken module API.
## FungibleToken [toc] [#FungibleToken]
```ts
diff --git a/content/contracts-compact/api/multi-token.mdx b/content/contracts-compact/0.1.x/api/multi-token.mdx
similarity index 99%
rename from content/contracts-compact/api/multi-token.mdx
rename to content/contracts-compact/0.1.x/api/multi-token.mdx
index 5444b95b..55545dfe 100644
--- a/content/contracts-compact/api/multi-token.mdx
+++ b/content/contracts-compact/0.1.x/api/multi-token.mdx
@@ -11,7 +11,7 @@ This module provides the full MultiToken module API.
## MultiToken [toc] [#MultiToken]
```ts
diff --git a/content/contracts-compact/api/non-fungible-token.mdx b/content/contracts-compact/0.1.x/api/non-fungible-token.mdx
similarity index 99%
rename from content/contracts-compact/api/non-fungible-token.mdx
rename to content/contracts-compact/0.1.x/api/non-fungible-token.mdx
index 4bfa6409..cc0dd3a3 100644
--- a/content/contracts-compact/api/non-fungible-token.mdx
+++ b/content/contracts-compact/0.1.x/api/non-fungible-token.mdx
@@ -11,7 +11,7 @@ This module provides the full NonFungibleToken module API.
## NonFungibleToken [toc] [#NonFungibleToken]
```ts
diff --git a/content/contracts-compact/api/ownable.mdx b/content/contracts-compact/0.1.x/api/ownable.mdx
similarity index 99%
rename from content/contracts-compact/api/ownable.mdx
rename to content/contracts-compact/0.1.x/api/ownable.mdx
index 8d4cd78d..124819c8 100644
--- a/content/contracts-compact/api/ownable.mdx
+++ b/content/contracts-compact/0.1.x/api/ownable.mdx
@@ -11,7 +11,7 @@ For an overview of the module, read the [Ownable guide](../ownable).
## Ownable [toc] [#Ownable]
```ts
@@ -250,7 +250,7 @@ import "./node_modules/@openzeppelin/compact-contracts/access/Ownable";
## ZOwnablePK [toc] [#ZOwnablePK]
```ts
diff --git a/content/contracts-compact/api/security.mdx b/content/contracts-compact/0.1.x/api/security.mdx
similarity index 97%
rename from content/contracts-compact/api/security.mdx
rename to content/contracts-compact/0.1.x/api/security.mdx
index 5920fea5..e1404bb3 100644
--- a/content/contracts-compact/api/security.mdx
+++ b/content/contracts-compact/0.1.x/api/security.mdx
@@ -11,7 +11,7 @@ This package provides the API for all Security modules.
## Initializable [toc] [#Initializable]
```ts
@@ -91,7 +91,7 @@ None.
## Pausable [toc] [#Pausable]
```ts
diff --git a/content/contracts-compact/api/shielded-access-control.mdx b/content/contracts-compact/0.1.x/api/shielded-access-control.mdx
similarity index 99%
rename from content/contracts-compact/api/shielded-access-control.mdx
rename to content/contracts-compact/0.1.x/api/shielded-access-control.mdx
index d9cf7ced..4662d66a 100644
--- a/content/contracts-compact/api/shielded-access-control.mdx
+++ b/content/contracts-compact/0.1.x/api/shielded-access-control.mdx
@@ -77,7 +77,7 @@ More complex role relationships can be created by using [_setRoleAdmin](#Shielde
## ShieldedAccessControl [toc] [#ShieldedAccessControl]
```ts
diff --git a/content/contracts-compact/api/utils.mdx b/content/contracts-compact/0.1.x/api/utils.mdx
similarity index 96%
rename from content/contracts-compact/api/utils.mdx
rename to content/contracts-compact/0.1.x/api/utils.mdx
index be5c264a..7de76b9d 100644
--- a/content/contracts-compact/api/utils.mdx
+++ b/content/contracts-compact/0.1.x/api/utils.mdx
@@ -5,13 +5,13 @@ title: Utils
This package provides the API for all Utils modules.
- For an overview of the module, read the [Utils guide](/contracts-compact/utils).
+ For an overview of the module, read the [Utils guide](../utils).
## Utils [toc] [#Utils]
```ts
diff --git a/content/contracts-compact/extensibility.mdx b/content/contracts-compact/0.1.x/extensibility.mdx
similarity index 100%
rename from content/contracts-compact/extensibility.mdx
rename to content/contracts-compact/0.1.x/extensibility.mdx
diff --git a/content/contracts-compact/fungible-token.mdx b/content/contracts-compact/0.1.x/fungible-token.mdx
similarity index 100%
rename from content/contracts-compact/fungible-token.mdx
rename to content/contracts-compact/0.1.x/fungible-token.mdx
diff --git a/content/contracts-compact/0.1.x/index.mdx b/content/contracts-compact/0.1.x/index.mdx
new file mode 100644
index 00000000..1963a214
--- /dev/null
+++ b/content/contracts-compact/0.1.x/index.mdx
@@ -0,0 +1,175 @@
+---
+title: Contracts for Compact
+---
+
+{/* links */}
+[Midnight]: https://midnight.network/
+[nvm]: https://github.com/nvm-sh/nvm
+[yarn]: https://yarnpkg.com/getting-started/install
+[compact-dev-tools]: https://docs.midnight.network/blog/compact-developer-tools
+
+**A library for secure smart contract development** written in Compact for [Midnight].
+This library consists of modules to build custom smart contracts.
+
+
+This repo contains highly experimental code.
+Expect rapid iteration. **Use at your own risk.**
+
+
+## Usage
+
+Make sure you have [nvm] and [yarn] installed on your machine.
+
+Follow Midnight's [Compact Developer Tools][compact-dev-tools] installation guide and confirm that `compact` is in the `PATH` env variable.
+
+```bash
+$ compact compile --version
+
+Compactc version: {{compact_compiler_version}}
+{{compact_compiler_version}}
+```
+
+### Installation
+
+Create a directory for your project.
+
+```bash
+mkdir my-project
+cd my-project
+```
+
+Install the library.
+
+```bash
+yarn add @openzeppelin/compact-contracts
+```
+
+### Write a custom contract using library modules
+
+In the root of `my-project`, create a custom contract using OpenZeppelin Compact modules.
+Import the modules through `./node_modules/@openzeppelin/compact-contracts/...`.
+
+```typescript
+// MyContract.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ _name: Opaque<"string">,
+ _symbol: Opaque<"string">,
+ _decimals: Uint<8>,
+ _recipient: Either, ContractAddress>,
+ _amount: Uint<128>,
+ _initOwner: Either, ContractAddress>,
+) {
+ Ownable_initialize(_initOwner);
+ FungibleToken_initialize(_name, _symbol, _decimals);
+ FungibleToken__mint(_recipient, _amount);
+}
+
+/** IFungibleToken */
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
+export circuit transfer(
+ to: Either, ContractAddress>,
+ value: Uint<128>,
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transfer(to, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_approve(spender, value);
+}
+
+/** IOwnable */
+
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ return Ownable_transferOwnership(newOwner);
+}
+
+export circuit renounceOwnership(): [] {
+ return Ownable_renounceOwnership();
+}
+
+/** IPausable */
+
+export circuit pause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__pause();
+}
+
+export circuit unpause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__unpause();
+}
+```
+
+### Compile the contract
+
+In the project root, compile the contract.
+
+```bash
+% compact compile MyContract.compact artifacts/MyContract
+Compiling 11 circuits:
+ circuit "allowance" (k=11, rows=1352)
+ circuit "approve" (k=13, rows=3075)
+ circuit "balanceOf" (k=10, rows=673)
+ circuit "decimals" (k=6, rows=28)
+ circuit "name" (k=6, rows=28)
+ circuit "pause" (k=13, rows=2365)
+ circuit "symbol" (k=6, rows=28)
+ circuit "totalSupply" (k=6, rows=28)
+ circuit "transfer" (k=13, rows=3990)
+ circuit "transferFrom" (k=13, rows=4972)
+ circuit "unpause" (k=13, rows=2362)
+Overall progress [====================] 11/11
+```
diff --git a/content/contracts-compact/multi-token.mdx b/content/contracts-compact/0.1.x/multi-token.mdx
similarity index 100%
rename from content/contracts-compact/multi-token.mdx
rename to content/contracts-compact/0.1.x/multi-token.mdx
diff --git a/content/contracts-compact/non-fungible-token.mdx b/content/contracts-compact/0.1.x/non-fungible-token.mdx
similarity index 100%
rename from content/contracts-compact/non-fungible-token.mdx
rename to content/contracts-compact/0.1.x/non-fungible-token.mdx
diff --git a/content/contracts-compact/ownable.mdx b/content/contracts-compact/0.1.x/ownable.mdx
similarity index 100%
rename from content/contracts-compact/ownable.mdx
rename to content/contracts-compact/0.1.x/ownable.mdx
diff --git a/content/contracts-compact/security.mdx b/content/contracts-compact/0.1.x/security.mdx
similarity index 100%
rename from content/contracts-compact/security.mdx
rename to content/contracts-compact/0.1.x/security.mdx
diff --git a/content/contracts-compact/shielded-access-control.mdx b/content/contracts-compact/0.1.x/shielded-access-control.mdx
similarity index 100%
rename from content/contracts-compact/shielded-access-control.mdx
rename to content/contracts-compact/0.1.x/shielded-access-control.mdx
diff --git a/content/contracts-compact/utils.mdx b/content/contracts-compact/0.1.x/utils.mdx
similarity index 100%
rename from content/contracts-compact/utils.mdx
rename to content/contracts-compact/0.1.x/utils.mdx
diff --git a/content/contracts-compact/utils/constants.js b/content/contracts-compact/0.1.x/utils/constants.js
similarity index 100%
rename from content/contracts-compact/utils/constants.js
rename to content/contracts-compact/0.1.x/utils/constants.js
diff --git a/content/contracts-compact/utils/replacements.ts b/content/contracts-compact/0.1.x/utils/replacements.ts
similarity index 80%
rename from content/contracts-compact/utils/replacements.ts
rename to content/contracts-compact/0.1.x/utils/replacements.ts
index 6072127f..598cd7f6 100644
--- a/content/contracts-compact/utils/replacements.ts
+++ b/content/contracts-compact/0.1.x/utils/replacements.ts
@@ -1,7 +1,7 @@
import { COMPACT_COMPILER_VERSION, COMPACT_LANGUAGE_VERSION } from "./constants";
export const REPLACEMENTS = {
- include: ['**/content/contracts-compact/**/*.mdx'],
+ include: ['**/content/contracts-compact/0.1.x/**/*.mdx'],
replacements: {
compact_compiler_version: COMPACT_COMPILER_VERSION,
compact_language_version: COMPACT_LANGUAGE_VERSION
diff --git a/content/contracts-compact/zk-circuits-101.mdx b/content/contracts-compact/0.1.x/zk-circuits-101.mdx
similarity index 100%
rename from content/contracts-compact/zk-circuits-101.mdx
rename to content/contracts-compact/0.1.x/zk-circuits-101.mdx
diff --git a/content/contracts-compact/0.3.x/access-control.mdx b/content/contracts-compact/0.3.x/access-control.mdx
new file mode 100644
index 00000000..b4ec0879
--- /dev/null
+++ b/content/contracts-compact/0.3.x/access-control.mdx
@@ -0,0 +1,281 @@
+---
+title: AccessControl
+---
+
+{/* links */}
+[Role-Based Access Control (RBAC)]: https://en.wikipedia.org/wiki/Role-based_access_control
+[principle of least privilege]: https://en.wikipedia.org/wiki/Principle_of_least_privilege
+
+[FungibleToken]: ./fungible-token.mdx
+[Ownable]: ./ownable.mdx
+[Utils]: ./utils.mdx
+[AccessControl]: api/access-control
+[ShieldedAccessControl]: ./shielded-access-control.mdx
+
+[assertOnlyRole]: api/access-control#AccessControl-assertOnlyRole
+[hasRole]: api/access-control#AccessControl-hasRole
+[grantRole]: api/access-control#AccessControl-grantRole
+[_grantRole]: api/access-control#AccessControl-_grantRole
+[_unsafeGrantRole]: api/access-control#AccessControl-_unsafeGrantRole
+[revokeRole]: api/access-control#AccessControl-revokeRole
+[_setRoleAdmin]: api/access-control#AccessControl-_setRoleAdmin
+[computeAccountId]: api/utils#Utils-computeAccountId
+
+This module provides a role-based access control mechanism,
+where roles can be used to represent a set of permissions providing the flexibility to create different levels of account authorization.
+
+Roles can be enforced using the [assertOnlyRole] circuit.
+Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.
+
+
+ For a privacy-preserving variant that hides role holder identities on-chain, see [ShieldedAccessControl].
+
+
+## Role-Based Access Control
+
+While the simplicity of _ownership_ can be useful for simple systems or quick prototyping,
+different levels of authorization are often needed.
+You may want for an account to have permission to ban users from a system, but not create new tokens.
+[Role-Based Access Control (RBAC)] offers flexibility in this regard.
+
+In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions.
+An account may have, for example, 'moderator', 'minter' or 'admin' roles,
+which you will then check for instead of simply using `assertOnlyOwner`.
+This check can be enforced through the [assertOnlyRole] circuit.
+Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.
+
+Most software uses access control systems that are role-based:
+some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges.
+
+### Identity Model
+
+AccessControl uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it via the `wit_AccessControlSK` witness. The module computes an account identifier
+as `H(secretKey)` (a `persistentHash` of the key), which is a commitment that hides the key while providing a stable,
+pseudonymous on-chain identity.
+
+What is public and what is private:
+
+* **On the ledger**: `_operatorRoles`, a public map from role identifier to account identifier to a Boolean,
+ and `_adminRoles`. Every grant, revoke, and role check discloses the role identifier and the account identifier it touches,
+ so an observer can see which identifiers hold which roles.
+* **Held privately by each account**: the 32-byte `secretKey`, supplied through `wit_AccessControlSK` at proving time.
+ The key never reaches the ledger; only `H(secretKey)` does.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+The same secret key produces the same identity across all contracts.
+Users who desire cross-contract unlinkability can use different secret keys per contract at the wallet layer.
+
+To derive an account identifier off-chain (e.g. before passing it to a grant operation),
+use [computeAccountId] from the [Utils] module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+### Using `AccessControl`
+
+The Compact contracts library provides `AccessControl` for implementing role-based access control.
+Its usage is straightforward: for each role that you want to define,
+you will create a new role identifier that is used to grant, revoke, and check if an account has that role.
+
+Here's a simple example of using `AccessControl` with [FungibleToken] to define a 'minter' role,
+which allows accounts that have this role to create new tokens:
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+
+/**
+ * Initialize FungibleToken and MINTER_ROLE
+ */
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ minter: Either, ContractAddress>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ AccessControl__grantRole(MINTER_ROLE, minter);
+}
+
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(MINTER_ROLE);
+ FungibleToken__mint(recipient, value);
+}
+
+```
+
+Make sure you fully understand how [AccessControl] works before using it on your system,
+or copy-pasting the examples from this guide.
+
+
+While clear and explicit, this isn't anything we wouldn't have been able to achieve with [Ownable].
+Indeed, where `AccessControl` shines is in scenarios where granular permissions are required,
+which can be implemented by defining _multiple_ roles.
+
+
+Let's augment our FungibleToken example by also defining a 'burner' role, which lets accounts destroy tokens.
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+export sealed ledger BURNER_ROLE: Bytes<32>;
+
+/**
+ * Initialize FungibleToken and MINTER_ROLE
+ */
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ minter: Either, ContractAddress>,
+ burner: Either, ContractAddress>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE"));
+ AccessControl__grantRole(MINTER_ROLE, minter);
+ AccessControl__grantRole(BURNER_ROLE, burner);
+}
+
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(MINTER_ROLE);
+ FungibleToken__mint(recipient, value);
+}
+
+export circuit burn(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(BURNER_ROLE);
+ FungibleToken__burn(recipient, value);
+}
+
+```
+
+So clean! By splitting concerns this way,
+more granular levels of permission may be implemented than were possible with the simpler _ownership_ approach to access control.
+Limiting what each component of a system is able to do is known as the [principle of least privilege],
+and is a good security practice.
+Note that each account may still have more than one role, if so desired.
+
+### Granting and Revoking Roles
+
+The FungibleToken example above uses [_grantRole],
+an internal circuit that is useful when programmatically assigning roles (such as during construction).
+But what if we later want to grant the 'minter' role to additional accounts?
+
+By default, **accounts with a role cannot grant it or revoke it from other accounts**:
+all having a role does is making the [hasRole] check pass.
+To grant and revoke roles dynamically, you will need help from the _role's admin_.
+
+Every role has an associated admin role,
+which grants permission to call the [grantRole] and [revokeRole] circuits.
+A role can be granted or revoked by using these if the calling account has the corresponding admin role.
+Multiple roles may have the same admin role to make management easier.
+A role's admin can even be the same role itself,
+which would cause accounts with that role to be able to also grant and revoke it.
+
+This mechanism can be used to create complex permissioning structures resembling organizational charts,
+but it also provides an easy way to manage simpler applications.
+`AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`,
+which acts as the **default admin role for all roles**.
+An account with this role will be able to manage any other role,
+unless [_setRoleAdmin] is used to select a new admin role.
+
+Since it is the admin for all roles by default,
+and in fact it is also its own admin, this role carries significant risk.
+
+Let's take a look at the FungibleToken example, this time taking advantage of the default admin role:
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+export sealed ledger BURNER_ROLE: Bytes<32>;
+
+/**
+ * Initialize FungibleToken and MINTER_ROLE
+ */
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ defaultAdmin: Either, ContractAddress>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE"));
+ // Grant the default admin role to the provided account identifier.
+ // This account will be able to grant and revoke any roles.
+ // The account identifier should be derived off-chain using Utils_computeAccountId(secretKey).
+ AccessControl__grantRole(AccessControl_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+}
+
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(MINTER_ROLE);
+ FungibleToken__mint(recipient, value);
+}
+
+export circuit burn(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(BURNER_ROLE);
+ FungibleToken__burn(recipient, value);
+}
+
+```
+
+Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles.
+However, because those roles' admin role is the default admin role, and _that_ role was granted to `defaultAdmin`,
+that same account can call [grantRole] to give minting or burning permission, and [revokeRole] to remove it.
+
+The `defaultAdmin` account identifier should be derived off-chain using [computeAccountId] from the [Utils] module
+(`Utils_computeAccountId(secretKey)`),
+where `secretKey` is a 32-byte cryptographically secure random value held by the deployer.
+Pass it as `left, ContractAddress>(accountId)`.
+
+Dynamic role allocation is often a desirable property,
+for example in systems where trust in a participant may vary over time.
+It can also be used to support use cases such as KYC,
+where the list of role-bearers may not be known up-front,
+or may be prohibitively expensive to include in a single transaction.
+
+### Unsafe circuits
+
+This module offers a circuit that allows access control permissions to be granted to contract addresses, [_unsafeGrantRole].
+
+Note that the circuit name is very explicit ("unsafe").
+Until contract-to-contract calls are supported,
+there is no direct way for a contract to call permissioned circuits of other contracts or grant/revoke role permissions.
+
+
+The unsafe circuits are planned to become deprecated once contract-to-contract calls become available.
+
diff --git a/content/contracts-compact/0.3.x/api/access-control.mdx b/content/contracts-compact/0.3.x/api/access-control.mdx
new file mode 100644
index 00000000..4e8d4fb9
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/access-control.mdx
@@ -0,0 +1,286 @@
+---
+title: AccessControl API
+---
+
+This page provides the full AccessControl module API.
+
+Roles are referred to by their `Bytes<32>` identifier.
+These should be exposed in the top-level contract and be unique.
+The best way to achieve this is by using `export sealed ledger` hash digests that are initialized in the top-level contract:
+
+```typescript
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+
+export sealed ledger MY_ROLE: Bytes<32>;
+
+constructor() {
+ MY_ROLE = persistentHash>(pad(32, "MY_ROLE"));
+}
+```
+
+To restrict access to a circuit, use [assertOnlyRole](#AccessControl-assertOnlyRole):
+
+
+```typescript
+circuit foo(): [] {
+ AccessControl_assertOnlyRole(MY_ROLE);
+}
+```
+
+Roles can be granted and revoked dynamically via the [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole) circuits.
+Each role has an associated admin role,
+and only accounts that have a role's admin role can call [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole).
+
+By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE` (zero bytes),
+which means that only accounts with this role will be able to grant or revoke other roles.
+More complex role relationships can be created by using [_setRoleAdmin](#AccessControl-_setRoleAdmin).
+
+
+ The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role.
+ Extra precautions should be taken to secure accounts that have been granted it.
+
+
+
+ For an overview of the module, read the [AccessControl guide](../access-control).
+
+
+## AccessControl [toc] [#AccessControl]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl";
+```
+
+---
+
+### Ledger [toc] [#AccessControl-Ledger]
+### Ledger [!toc] [#AccessControl-Ledger]
+
+#### _operatorRoles [toc] [#AccessControl-_operatorRoles]
+#### [!toc] [#AccessControl-_operatorRoles]
+
+ Mapping from a role identifier -> account -> its permissions.
+
+
+#### _adminRoles [toc] [#AccessControl-_adminRoles]
+#### [!toc] [#AccessControl-_adminRoles]
+
+ Mapping from a role identifier to an admin role identifier.
+
+
+### Witnesses [toc] [#AccessControl-Witnesses]
+### Witnesses [!toc] [#AccessControl-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_AccessControlSK [toc] [#AccessControl-wit_AccessControlSK]
+#### [!toc] [#AccessControl-wit_AccessControlSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#AccessControl-Circuits]
+### Circuits [!toc] [#AccessControl-Circuits]
+
+#### DEFAULT_ADMIN_ROLE [toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
+#### [!toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
+
+ The default admin role for all roles. Only accounts with this role will be able
+ to grant or revoke other roles unless custom admin roles are created via [_setRoleAdmin](#AccessControl-_setRoleAdmin).
+
+
+#### hasRole [toc] [#AccessControl-hasRole]
+#### [!toc] [#AccessControl-hasRole]
+
+ Returns `true` if `account` has been granted `roleId`.
+
+
+#### assertOnlyRole [toc] [#AccessControl-assertOnlyRole]
+#### [!toc] [#AccessControl-assertOnlyRole]
+
+ Reverts if the caller cannot prove ownership of `roleId`.
+ In the case of an external (non-contract) caller, the caller’s identity is
+ derived from the [wit_AccessControlSK](#AccessControl-wit_AccessControlSK) witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * The caller must have `roleId`.
+
+
+#### _checkRole [toc] [#AccessControl-_checkRole]
+#### [!toc] [#AccessControl-_checkRole]
+
+ Reverts if `account` is missing `roleId`.
+
+ Requirements:
+
+ * `account` must have `roleId`.
+
+
+#### getRoleAdmin [toc] [#AccessControl-getRoleAdmin]
+#### [!toc] [#AccessControl-getRoleAdmin]
+
+ Returns the admin role that controls `roleId` or
+ a byte array with all zero bytes if `roleId` doesn't exist. See [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole).
+
+ To change a role's admin use [_setRoleAdmin](#AccessControl-_setRoleAdmin).
+
+
+#### grantRole [toc] [#AccessControl-grantRole]
+#### [!toc] [#AccessControl-grantRole]
+
+ Grants `roleId` to `account`.
+
+ Requirements:
+
+ * `account` must not be a ContractAddress.
+ * The caller must have `roleId`'s admin role.
+
+
+#### revokeRole [toc] [#AccessControl-revokeRole]
+#### [!toc] [#AccessControl-revokeRole]
+
+ Revokes `roleId` from `account`.
+
+ Requirements:
+
+ * The caller must have `roleId`'s admin role.
+
+
+#### renounceRole [toc] [#AccessControl-renounceRole]
+#### [!toc] [#AccessControl-renounceRole]
+
+ Revokes `roleId` from the calling account.
+
+
+ Roles are often managed via [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole): this circuit's
+ purpose is to provide a mechanism for accounts to lose their privileges
+ if they are compromised (such as when a trusted device is misplaced).
+
+ The caller's identity is derived from the [wit_AccessControlSK](#AccessControl-wit_AccessControlSK) witness as
+ `persistentHash(secretKey)`. The `callerConfirmation` parameter must match
+ the caller's computed account identifier to prevent accidental renunciation.
+
+
+ Requirements:
+
+ * The caller's computed account identifier must match `callerConfirmation`.
+ * The caller must not be a `ContractAddress`.
+
+
+#### _setRoleAdmin [toc] [#AccessControl-_setRoleAdmin]
+#### [!toc] [#AccessControl-_setRoleAdmin]
+
+ Sets `adminRole` as `roleId`'s admin role.
+
+
+#### _grantRole [toc] [#AccessControl-_grantRole]
+#### [!toc] [#AccessControl-_grantRole]
+
+ Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
+ Internal circuit without access restriction.
+
+ Requirements:
+
+ * `account` must not be a ContractAddress.
+
+
+#### _unsafeGrantRole [toc] [#AccessControl-_unsafeGrantRole]
+#### [!toc] [#AccessControl-_unsafeGrantRole]
+
+ Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
+ Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress.
+
+
+ External smart contracts cannot call the access control contract at this time, so granting a role to a ContractAddress may
+ render a circuit permanently inaccessible.
+
+
+
+#### _revokeRole [toc] [#AccessControl-_revokeRole]
+#### [!toc] [#AccessControl-_revokeRole]
+
+ Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked.
+ Internal circuit without access restriction.
+
diff --git a/content/contracts-compact/0.3.x/api/confidential-fungible-token.mdx b/content/contracts-compact/0.3.x/api/confidential-fungible-token.mdx
new file mode 100644
index 00000000..ff4a2660
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/confidential-fungible-token.mdx
@@ -0,0 +1,747 @@
+---
+title: ConfidentialFungibleToken API
+---
+
+This module provides the full ConfidentialFungibleToken module API.
+
+
+ For an overview of the module, read the [ConfidentialFungibleToken guide](../confidential-fungible-token).
+ The `ElGamal_Ciphertext` and `EcdhMask_Ciphertext` types used below are documented in the [Crypto API](./crypto).
+
+
+The page covers the following modules:
+
+- [ConfidentialFungibleToken](#ConfidentialFungibleToken): the token with ElGamal-encrypted balances.
+- [ConfidentialFungibleTokenPublicSupply](#ConfidentialFungibleTokenPublicSupply): an extension that tracks a public total supply.
+
+## ConfidentialFungibleToken [toc] [#ConfidentialFungibleToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/ConfidentialFungibleToken";
+```
+
+---
+
+### Types [toc] [#ConfidentialFungibleToken-Types]
+### Types [!toc] [#ConfidentialFungibleToken-Types]
+
+#### EscrowEntry [toc] [#ConfidentialFungibleToken-EscrowEntry]
+#### [!toc] [#ConfidentialFungibleToken-EscrowEntry]
+
+ Per-(owner, spender) encrypted escrow. Stored as two
+ ciphertexts of the same value: one to the spender, one to the owner.
+ No public cap.
+
+
+### Ledger [toc] [#ConfidentialFungibleToken-Ledger]
+### Ledger [!toc] [#ConfidentialFungibleToken-Ledger]
+
+#### _balances [toc] [#ConfidentialFungibleToken-_balances]
+#### [!toc] [#ConfidentialFungibleToken-_balances]
+
+ Dual-balance grief fix. `_balances` is the SPENDABLE (confirmed) balance that
+ debits draw from; `_pending` is the incoming pool that credits land in. Only
+ the owner moves pending -> spendable, via [sweep](#ConfidentialFungibleToken-sweep). So a third party spamming
+ dust credits cannot churn a victim's spendable ciphertext and invalidate the
+ victim's in-flight spend proofs (a liveness grief). A wallet's total balance
+ is [balanceOf](#ConfidentialFungibleToken-balanceOf) (spendable) + [pendingOf](#ConfidentialFungibleToken-pendingOf) (pending).
+
+
+#### _pending [toc] [#ConfidentialFungibleToken-_pending]
+#### [!toc] [#ConfidentialFungibleToken-_pending]
+
+ Mapping from account identifiers to their pending balance ciphertext, where incoming credits land.
+ Only the owner moves pending value into `_balances`, with [sweep](#ConfidentialFungibleToken-sweep).
+
+
+#### _encryptionKeys [toc] [#ConfidentialFungibleToken-_encryptionKeys]
+#### [!toc] [#ConfidentialFungibleToken-_encryptionKeys]
+
+ Mapping from account identifiers to their ElGamal public key, set by [register](#ConfidentialFungibleToken-register).
+ Membership in this map is what "registered" means throughout the module.
+
+
+#### _memos [toc] [#ConfidentialFungibleToken-_memos]
+#### [!toc] [#ConfidentialFungibleToken-_memos]
+
+ Mapping from account identifiers to their memo list. Every credit pushes one memo carrying the credited amount,
+ encrypted to the recipient's key, to the front of the list. Memos record credits only; debits leave none.
+
+
+#### _escrow [toc] [#ConfidentialFungibleToken-_escrow]
+#### [!toc] [#ConfidentialFungibleToken-_escrow]
+
+ Mapping from owner accounts to spender accounts to their [EscrowEntry](#ConfidentialFungibleToken-EscrowEntry).
+
+
+#### _escrowSpendEpochs [toc] [#ConfidentialFungibleToken-_escrowSpendEpochs]
+#### [!toc] [#ConfidentialFungibleToken-_escrowSpendEpochs]
+
+ Per-(owner, spender) escrow-spend counter, folded into the randomness
+ `_spendEscrow` derives so repeat spends by one spender against one owner
+ never share a one-time pad. It restarts for each pair, which is why both
+ counterparties are bound in too; see `SpendRandomnessPreimage`.
+
+ Kept out of `EscrowEntry`: [approve](#ConfidentialFungibleToken-approve) replaces that entry wholesale, so a
+ counter living there would restart on every re-approve.
+
+ Note that this exposes a per-pair spend count which is already derivable
+ from tx history.
+
+
+#### _creditEpochs [toc] [#ConfidentialFungibleToken-_creditEpochs]
+#### [!toc] [#ConfidentialFungibleToken-_creditEpochs]
+
+ Per-account credit counter. `_credit` uses it as the nonce that keeps each
+ credit's randomness distinct, so it MUST only ever increase. No circuit
+ resets it. It is kept out of `_memos` for that reason: the memo list is
+ prunable by its owner, and a nonce that could be walked back would let a
+ reused seed repeat an earlier credit's randomness. The cost is that an
+ account's lifetime credit count is now permanently readable from state.
+
+ `Counter` is chosen for no-reset monotonicity, not commutativity. Its
+ `increment` emits no pinned read, but `_credit` reads the value back to
+ derive the nonce, and that read pins the recipient's key. Credits to one
+ recipient still serialize; see the Concurrency note above.
+
+
+#### _isInitialized [toc] [#ConfidentialFungibleToken-_isInitialized]
+#### [!toc] [#ConfidentialFungibleToken-_isInitialized]
+
+ Boolean indicating if initialized.
+
+
+#### _name [toc] [#ConfidentialFungibleToken-_name]
+#### [!toc] [#ConfidentialFungibleToken-_name]
+
+ The immutable token name.
+
+
+#### _symbol [toc] [#ConfidentialFungibleToken-_symbol]
+#### [!toc] [#ConfidentialFungibleToken-_symbol]
+
+ The immutable token symbol.
+
+
+#### _decimals [toc] [#ConfidentialFungibleToken-_decimals]
+#### [!toc] [#ConfidentialFungibleToken-_decimals]
+
+ The immutable token decimals.
+
+
+### Witnesses [toc] [#ConfidentialFungibleToken-Witnesses]
+### Witnesses [!toc] [#ConfidentialFungibleToken-Witnesses]
+
+The TypeScript witnesses in the repository are test-only and are not published; a consumer authors its own.
+
+#### wit_ConfidentialTokenSK [toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenSK]
+#### [!toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenSK]
+
+ Returns the caller's account secret. Verified in-circuit by
+ matching `persistentHash(SK)` against the public accountId.
+
+
+#### wit_ConfidentialTokenEK [toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenEK]
+#### [!toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenEK]
+
+ Returns the caller's ElGamal secret. Verified in-circuit by
+ `ElGamal_assertDecryptsTo`, which re-derives the pk and compares
+ against the stored pk.
+
+
+#### wit_PlaintextBalance [toc] [#ConfidentialFungibleToken-wit_PlaintextBalance]
+#### [!toc] [#ConfidentialFungibleToken-wit_PlaintextBalance]
+
+ Returns the wallet's cached plaintext for the given
+ ciphertext. Called whenever the circuit needs to verify a ciphertext's
+ plaintext value (sender's main balance, escrow ciphertext during spend,
+ prior escrow during refund, etc.).
+
+ The ciphertext is passed explicitly so the wallet can route to the right
+ cached plaintext by ciphertext identity. This avoids call-order
+ conventions (which would be brittle to circuit refactors) and lets the
+ wallet maintain a `Map` lookup.
+
+ Verified in-circuit by `ElGamal_assertDecryptsTo`: a wrong plaintext
+ fails the assertion `Dec(ct, EK) == claimedValue`.
+
+
+#### wit_RandomnessSeed [toc] [#ConfidentialFungibleToken-wit_RandomnessSeed]
+#### [!toc] [#ConfidentialFungibleToken-wit_RandomnessSeed]
+
+ Returns a single 32-byte seed from which all per-operation
+ randomness (and each memo's ephemeral) is derived in-circuit.
+
+ Seed freshness is load-bearing for confidentiality on EVERY path (see the
+ module header). The derivation is deterministic and public, so an observer
+ who knows the seed can strip a memo's mask and read the delivered amount, and
+ recover balance/escrow amounts from the public ciphertexts. Seed REUSE leaks
+ plaintext differences and the circuit does NOT prevent it on most paths.
+ `_spendEscrow` is an exception (its randomness folds the spend epoch and both
+ counterparties); the credit path folds the recipient's `_creditEpochs`
+ counter as defense in depth, and that covers credits alone. The seed MUST be
+ fresh, high-entropy, and secret for every invocation.
+
+
+### Circuits [toc] [#ConfidentialFungibleToken-Circuits]
+### Circuits [!toc] [#ConfidentialFungibleToken-Circuits]
+
+#### MAX_TRANSFER_VALUE [toc] [#ConfidentialFungibleToken-MAX_TRANSFER_VALUE]
+#### [!toc] [#ConfidentialFungibleToken-MAX_TRANSFER_VALUE]
+
+ Maximum allowed value per single transfer / mint / burn /
+ approve: the `Uint<128>` maximum. The prior `2^48` cap existed only to keep
+ wallet-side discrete-log recovery tractable; with the ECDH memo delivering
+ values directly (see `crypto/EcdhMask`), that constraint is gone. Values stay
+ well under the Jubjub scalar-field order, so homomorphic sums do not wrap.
+
+
+ This equals the argument type's own maximum, so the per-op check is
+ redundant today. KEEP IT: it compares against a hard constant rather than
+ the argument type, so it still catches a later widening of the transfer
+ type — which is what the balance-claim width is sized against.
+
+
+
+#### initialize [toc] [#ConfidentialFungibleToken-initialize]
+#### [!toc] [#ConfidentialFungibleToken-initialize]
+
+ Initializes the token's name, symbol, and decimals. Intended to
+ be called once, from the deploying contract's constructor.
+
+ Requirements:
+
+ * Contract is not already initialized.
+
+
+#### balanceOf [toc] [#ConfidentialFungibleToken-balanceOf]
+#### [!toc] [#ConfidentialFungibleToken-balanceOf]
+
+ Returns `account`'s balance ciphertext, or the default (zero)
+ ciphertext if the account is unregistered. The amount is encrypted; only the
+ account (or an authorized viewer) can decrypt it.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### pendingOf [toc] [#ConfidentialFungibleToken-pendingOf]
+#### [!toc] [#ConfidentialFungibleToken-pendingOf]
+
+ Returns `account`'s PENDING balance ciphertext (incoming credits
+ not yet swept into spendable), or Enc(0) if unregistered. The spendable
+ balance is [balanceOf](#ConfidentialFungibleToken-balanceOf); a wallet's total is the sum of the two. See the
+ dual-balance note on the ledger declarations.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### allowance [toc] [#ConfidentialFungibleToken-allowance]
+#### [!toc] [#ConfidentialFungibleToken-allowance]
+
+ Returns the escrow entry (the encrypted allowance) that `owner`
+ granted `spender`. When no allowance exists, returns a well-formed
+ encrypted-zero entry (its encrypted amounts decrypt to 0, analogous to an
+ ERC-20 zero allowance) rather than `default`: the returned
+ Jubjub points are on-curve, so the view is safe to prove (e.g. through a
+ contract-to-contract call) and safe for a wallet to decrypt, whereas a zeroed
+ default holds off-curve points. The cap is encrypted, not public.
+
+
+ "No allowance" and "an allowance of 0" both read as encrypted-zero;
+ a caller that must distinguish them reads `_escrow` membership off-chain. This
+ getter grants nothing, the spend path ([transferFrom](#ConfidentialFungibleToken-transferFrom)/`burnFrom`) gates on
+ `_escrow` membership independently, so a non-existent allowance is unspendable.
+
+
+
+#### isRegistered [toc] [#ConfidentialFungibleToken-isRegistered]
+#### [!toc] [#ConfidentialFungibleToken-isRegistered]
+
+ Returns whether `account` has registered an encryption key.
+
+
+#### register [toc] [#ConfidentialFungibleToken-register]
+#### [!toc] [#ConfidentialFungibleToken-register]
+
+ Registers the caller: derives their encryption public key from
+ [wit_ConfidentialTokenEK](#ConfidentialFungibleToken-wit_ConfidentialTokenEK) and initializes their balance to an encryption of
+ zero. Registration is a prerequisite for sending or receiving.
+
+
+ Returns the caller's `accountId`. A composing contract can gate on
+ the returned value (e.g. assert it is KYC-approved) to restrict who may
+ register. See the module header note on caller-identity gating.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is not already registered.
+
+
+#### _mint [toc] [#ConfidentialFungibleToken-_mint]
+#### [!toc] [#ConfidentialFungibleToken-_mint]
+
+ Mints `value` to `account`: `_credit` under its intent name.
+ The building block for a composing contract's gated `mint`; it performs no
+ supply accounting. Pair it with a supply tracker (see
+ `extensions/ConfidentialFungibleTokenPublicSupply`) if the deployment tracks
+ `totalSupply`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * `account` is registered.
+
+
+#### _burn [toc] [#ConfidentialFungibleToken-_burn]
+#### [!toc] [#ConfidentialFungibleToken-_burn]
+
+ Burns `value` from the caller's own balance: `_debit` under
+ its intent name. The building block for a composing contract's `burn`; it
+ performs no supply accounting. Pair it with a supply tracker (see
+ `extensions/ConfidentialFungibleTokenPublicSupply`) if the deployment tracks
+ `totalSupply`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * The caller is registered and has a balance of at least `value`.
+
+
+#### transfer [toc] [#ConfidentialFungibleToken-transfer]
+#### [!toc] [#ConfidentialFungibleToken-transfer]
+
+ Transfers `value` from the caller to `to`: debits the caller,
+ credits `to`, and pushes an encrypted memo to `to`. The amount is hidden;
+ the (sender, recipient) pair is public.
+
+
+ Returns the caller's (sender's) `accountId` for caller-side gating.
+ The sender is derived from the same authentication `_debit` performs, so the
+ returned id is exactly the account whose funds moved.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * The caller is registered with a balance of at least `value`.
+ * `to` is registered and is not the caller (no self-transfer).
+
+
+#### _move [toc] [#ConfidentialFungibleToken-_move]
+#### [!toc] [#ConfidentialFungibleToken-_move]
+
+ The conserving value-movement primitive: debits the caller and
+ credits `to` by the same `value`, net zero. It NEVER changes supply, so it is
+ the safe surface for composition and the primitive this supply-free base
+ exposes. [transfer](#ConfidentialFungibleToken-transfer) is [_move](#ConfidentialFungibleToken-_move) plus a self-transfer guard; supply-changing
+ operations (`mint`/`burn`) live in a separate layer outside this conserving
+ surface, and only they may break conservation.
+
+
+ Like [transfer](#ConfidentialFungibleToken-transfer), the credit lands in the recipient's pending pool
+ (see the dual-balance note); the caller sweeps their own incoming value.
+
+
+ Self-move (`to` == caller) is PERMITTED here, unlike [transfer](#ConfidentialFungibleToken-transfer), which
+ rejects it. A self-move is conserving and safe: it debits the caller's
+ spendable and credits the caller's own pending (net zero, distinct ledger
+ cells; the value is recovered with [sweep](#ConfidentialFungibleToken-sweep)). [transfer](#ConfidentialFungibleToken-transfer)'s self-transfer guard
+ is therefore a UX choice, not a safety requirement: it spares users a
+ self-transfer with no foreseeable benefit (a shuffle of spendable into
+ pending plus a self-memo). This primitive stays permissive so a composer can
+ set its own policy; add a `to != caller` check in the wrapper to reject a
+ self-move.
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is registered with a spendable balance of at least `value`.
+ * `to` is registered.
+
+
+#### sweep [toc] [#ConfidentialFungibleToken-sweep]
+#### [!toc] [#ConfidentialFungibleToken-sweep]
+
+ Sweeps the caller's incoming `pending` pool into their
+ `spendable` balance. Credits land in `pending` (see the dual-balance note);
+ this [sweep](#ConfidentialFungibleToken-sweep) is the ONLY path from pending into spendable, and only the owner
+ can invoke it (the caller proves BOTH witness secrets: the account secret
+ identifies the account, the encryption secret proves the caller can read it).
+ So a third party spamming credits cannot force a victim's spendable ciphertext
+ to change, which is what would otherwise invalidate the victim's in-flight
+ spend proofs (a liveness grief).
+
+
+ Purely homomorphic: adds the two ciphertexts (same key) and resets
+ pending to Enc(0). No plaintext is needed; the wallet already learned the
+ pending amounts from its memos.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is registered.
+ * The caller holds the encryption secret registered for their account.
+
+
+#### approve [toc] [#ConfidentialFungibleToken-approve]
+#### [!toc] [#ConfidentialFungibleToken-approve]
+
+ Sets `spender`'s allowance over the caller's balance to `value`
+ via an escrow: the caller's main balance is debited by `value`, and matching
+ encryptions are stored for the spender and the owner. Re-approving first
+ refunds any prior escrow, so allowances replace rather than stack. The cap
+ is not public.
+
+
+ Returns the caller's (approver's) `accountId` for caller-side gating.
+
+
+ Also stores an owner-readable `ownerMemo` (ECDH one-time pad; see
+ `crypto/EcdhMask`) in the escrow entry, carrying the current remaining
+ allowance (= `value` here, refreshed to the reduced remaining on every
+ spend). This is what lets the owner prove the post-refund balance on a later
+ re-approve or revoke after a partial spend; without it a partial spend would
+ strand the escrow. See the `EscrowEntry` field doc.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * `spender` is registered, is not the zero account, and is not the caller.
+ * The caller is registered with a balance of at least `value`.
+
+
+#### transferFrom [toc] [#ConfidentialFungibleToken-transferFrom]
+#### [!toc] [#ConfidentialFungibleToken-transferFrom]
+
+ Spends `value` from `fromAddress`'s escrow (granted to the
+ caller via [approve](#ConfidentialFungibleToken-approve)) and credits it to `to`, reducing both escrow copies by
+ `value`. The amount is hidden.
+
+
+ Returns the caller's (spender's) `accountId` for caller-side gating.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * `to` is registered and is not the zero account.
+ * An escrow from `fromAddress` to the caller exists with at least `value`.
+
+
+#### clearMemos [toc] [#ConfidentialFungibleToken-clearMemos]
+#### [!toc] [#ConfidentialFungibleToken-clearMemos]
+
+ Clears the caller's memo list, letting a wallet that has folded
+ its memos into its local balance cache prune on-chain memo storage.
+
+ `expectedEpoch` is the caller's view of its `_creditEpochs` value at the
+ moment it read the list. The call is rejected unless that still matches, so
+ a credit landing between the read and this transaction is never silently
+ discarded. The wallet retries against the memos it has now seen. A wallet
+ therefore reads its memo list and its epoch together, folds the memos into
+ its cache, and passes that epoch back here.
+
+
+ Clearing memos is destructive and can permanently lock funds. The
+ on-chain balance ciphertext is not directly decryptable (discrete-log
+ bound), so a wallet reconstructs its balance by replaying memos. Clearing
+ them without a durable off-chain record of the balance loses the ability to
+ recover it; and because spending requires proving the balance plaintext, the
+ funds become unspendable, not merely undisplayable. Clear only memos already
+ folded into a durable cache/backup. The epoch check only rules out a credit
+ the caller has not seen; it does not make clearing safe for memos the caller
+ never folded in.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is registered.
+ * The caller holds the encryption secret registered for their account.
+ * The caller has a memo list.
+ * `expectedEpoch` equals the caller's current credit epoch.
+
+
+#### computeAccountId [toc] [#ConfidentialFungibleToken-computeAccountId]
+#### [!toc] [#ConfidentialFungibleToken-computeAccountId]
+
+ Derives the public `accountId` from a secret key, as
+ `persistentHash(sk)`. Pure, so a wallet can compute its own account
+ identifier off-chain (no proof) before transacting.
+
+ Delegates to `Utils.computeAccountId` rather than reimplementing the
+ hash. The identifier is deliberately GLOBAL. The same key material yields
+ the same id in every module that derives it.
+
+
+#### _burnFrom [toc] [#ConfidentialFungibleToken-_burnFrom]
+#### [!toc] [#ConfidentialFungibleToken-_burnFrom]
+
+ Burns `value` from `fromAddress` by consuming an escrow that
+ owner granted to the caller: `_spendEscrow` under its intent name, with no
+ recipient credit. The building block for a composing contract's
+ `burnFrom`; it performs no supply accounting (see [_burn](#ConfidentialFungibleToken-_burn)).
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * An escrow from `fromAddress` to the caller exists with at least `value`.
+
+
+---
+
+## ConfidentialFungibleTokenPublicSupply [toc] [#ConfidentialFungibleTokenPublicSupply]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/ConfidentialFungibleTokenPublicSupply";
+```
+
+Optional standalone extension that adds public supply accounting to a ConfidentialFungibleToken. It imports no token
+module and needs no initialization. "Public" supply means the total, and therefore each mint and burn amount, is
+disclosed on chain through the delta; balances stay confidential. The composing contract pairs each accounting block
+with the matching token operation.
+
+---
+
+### Ledger [toc] [#ConfidentialFungibleTokenPublicSupply-Ledger]
+### Ledger [!toc] [#ConfidentialFungibleTokenPublicSupply-Ledger]
+
+#### _totalSupply [toc] [#ConfidentialFungibleTokenPublicSupply-_totalSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-_totalSupply]
+
+ The public circulating supply. Mint and burn disclose their amounts
+ through the delta on this value.
+
+
+### Witnesses [toc] [#ConfidentialFungibleTokenPublicSupply-Witnesses]
+### Witnesses [!toc] [#ConfidentialFungibleTokenPublicSupply-Witnesses]
+
+None.
+
+### Circuits [toc] [#ConfidentialFungibleTokenPublicSupply-Circuits]
+### Circuits [!toc] [#ConfidentialFungibleTokenPublicSupply-Circuits]
+
+#### _addSupply [toc] [#ConfidentialFungibleTokenPublicSupply-_addSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-_addSupply]
+
+ Increases [totalSupply](#ConfidentialFungibleTokenPublicSupply-totalSupply) by `value`. Call alongside the token
+ module's `_mint`. The added amount is disclosed via the supply delta.
+
+ Requirements:
+
+ * `totalSupply + value` does not overflow `Uint<128>`.
+
+
+#### _subSupply [toc] [#ConfidentialFungibleTokenPublicSupply-_subSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-_subSupply]
+
+ Decreases [totalSupply](#ConfidentialFungibleTokenPublicSupply-totalSupply) by `value`. Call alongside the token
+ module's `_burn` or `_burnFrom`. The removed amount is disclosed via the
+ supply delta.
+
+ Requirements:
+
+ * [totalSupply](#ConfidentialFungibleTokenPublicSupply-totalSupply) is at least `value`.
+
+
+#### totalSupply [toc] [#ConfidentialFungibleTokenPublicSupply-totalSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-totalSupply]
+
+ Returns the public total token supply.
+
diff --git a/content/contracts-compact/0.3.x/api/crypto.mdx b/content/contracts-compact/0.3.x/api/crypto.mdx
new file mode 100644
index 00000000..dc5b7561
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/crypto.mdx
@@ -0,0 +1,523 @@
+---
+title: Crypto API
+---
+
+This page provides the full API for the Crypto modules.
+
+
+ For an overview of the modules, read the [Crypto guide](../crypto).
+
+
+The page covers the following modules:
+
+- [ElGamal](#ElGamal): exponential ElGamal encryption over Jubjub with homomorphic addition and re-randomization.
+- [EcdhMask](#EcdhMask): an ECDH-derived mask for encrypting a value to a recipient's public key.
+
+## ElGamal [toc] [#ElGamal]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal";
+```
+
+---
+
+### Types [toc] [#ElGamal-Types]
+### Types [!toc] [#ElGamal-Types]
+
+#### Ciphertext [toc] [#ElGamal-Ciphertext]
+#### [!toc] [#ElGamal-Ciphertext]
+
+ Single-receiver ElGamal ciphertext over Jubjub.
+ c1 = g^r
+ c2 = pk^r * m (lifted: m = g^v)
+
+
+### Ledger [toc] [#ElGamal-Ledger]
+### Ledger [!toc] [#ElGamal-Ledger]
+
+None.
+
+### Witnesses [toc] [#ElGamal-Witnesses]
+### Witnesses [!toc] [#ElGamal-Witnesses]
+
+None.
+
+### Circuits [toc] [#ElGamal-Circuits]
+### Circuits [!toc] [#ElGamal-Circuits]
+
+#### secretToScalar [toc] [#ElGamal-secretToScalar]
+#### [!toc] [#ElGamal-secretToScalar]
+
+ Maps a 32-byte secret to a valid Jubjub scalar. See the
+ module-level hash-to-scalar note for why `degradeToTransient` is required.
+
+ Domain separation here is load-bearing, not cosmetic. This output is
+ the private key protecting every ciphertext held under an account. Public
+ account identifiers derived from `Utils.computeAccountId`, for example,
+ are computed as `persistentHash([secretKey])`, and that identifier
+ is stored in the clear as a ledger map key. Untagged, this circuit
+ would compute that same hash, so a secret used in both roles would
+ have its encryption key recoverable from a published identifier by applying
+ `degradeToTransient`. The tag makes the two derivations unrelated, so a
+ wallet MAY safely derive its account secret and its encryption secret from
+ common key material.
+
+ The tag is FIRST, deliberately. [expandRandomness](#ElGamal-expandRandomness) hashes
+ `[seed, tag]` with both slots caller-supplied, so tagging this circuit as
+ `[secret, tag]` would make `secretToScalar(x)` identical to
+ `expandRandomness(x, )` reachable by passing this domain string
+ to the parameter that exists to receive domain strings. Leading with the
+ tag means reproducing this output through [expandRandomness](#ElGamal-expandRandomness) would require
+ passing the domain constant as the *seed*. Do not normalise the orderings.
+
+
+#### derivePk [toc] [#ElGamal-derivePk]
+#### [!toc] [#ElGamal-derivePk]
+
+ Derives the ElGamal public key `pk = g^secretToScalar(ek)`
+ from a secret `ek`.
+
+
+#### expandRandomness [toc] [#ElGamal-expandRandomness]
+#### [!toc] [#ElGamal-expandRandomness]
+
+ Deterministically expands a 32-byte seed into a Jubjub scalar
+ tagged by a distinguishing string, so a single witness seed can yield many
+ distinct randomness values within one circuit invocation. Distinct tags
+ always produce distinct outputs (collision resistance of the hash); the
+ caller is responsible for using distinct tags.
+
+
+#### encryptZero [toc] [#ElGamal-encryptZero]
+#### [!toc] [#ElGamal-encryptZero]
+
+ The identity ciphertext `Enc(0)` (both components are the
+ curve identity). Useful for initializing a fresh balance.
+
+
+ This is a fixed, deterministic value `(O, O)` — NOT a hiding
+ encryption. It is publicly recognizable, so a slot left at [encryptZero](#ElGamal-encryptZero) is
+ distinguishable as never-written. Rerandomize (or credit with fresh `r`)
+ before treating it as private.
+
+
+
+#### encryptPoint [toc] [#ElGamal-encryptPoint]
+#### [!toc] [#ElGamal-encryptPoint]
+
+ Encrypts an arbitrary message point `m` under `pk` with
+ randomness `r`, producing the ciphertext `(g^r, pk^r * m)`. This is the
+ general (non-lifted) ElGamal encryption; [encrypt](#ElGamal-encrypt) is the lifted special
+ case `encryptPoint(pk, g^value, r)`.
+
+
+ `m` (like `pk`) is constrained into the prime-order subgroup by the
+ runtime (see the module-level subgroup note), so no in-circuit membership
+ check is performed. `pk` is asserted non-identity and `r` non-zero — the
+ two inputs that would zero the mask and expose `m` (see the weak-keys and
+ randomness notes). Decrypting `m` is only feasible when it is a known or
+ small-exponent point; the caller owns that concern (the lifted [encrypt](#ElGamal-encrypt)
+ bounds it via the discrete-log range).
+
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### encrypt [toc] [#ElGamal-encrypt]
+#### [!toc] [#ElGamal-encrypt]
+
+ Produces a fresh ciphertext of `value` under `pk` with
+ randomness `r`, lifting `value` to the point `g^value`. Randomness is taken
+ as a `Field` directly to avoid a redundant `Bytes<32> -> Field` conversion
+ at every call site; [expandRandomness](#ElGamal-expandRandomness) produces `Field` for the same reason.
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### negate [toc] [#ElGamal-negate]
+#### [!toc] [#ElGamal-negate]
+
+ Negates a ciphertext componentwise: if `ct` encrypts `v` then
+ the result encrypts `-v` under the same key (with negated randomness).
+ This is the building block [sub](#ElGamal-sub) rests on.
+
+
+#### add [toc] [#ElGamal-add]
+#### [!toc] [#ElGamal-add]
+
+ Homomorphically adds two ciphertexts: `Enc(a) + Enc(b)`
+ decrypts to `a + b`. This is the defining additive-homomorphism primitive
+ and combines two independently produced ciphertexts componentwise.
+
+
+ Both inputs MUST be encrypted under the same public key for the
+ result to decrypt under that key's secret; combining ciphertexts under
+ different keys yields a ciphertext no single key can open. The randomness
+ of the result is the sum of the inputs' randomness, so the result is a
+ valid fresh ciphertext under that key.
+
+
+
+#### sub [toc] [#ElGamal-sub]
+#### [!toc] [#ElGamal-sub]
+
+ Homomorphically subtracts two ciphertexts: `Enc(a) - Enc(b)`
+ decrypts to `a - b`. Implemented as `add(a, negate(b))`.
+
+
+ Same-key requirement as [add](#ElGamal-add). The caller MUST ensure the
+ underlying plaintext subtraction does not underflow; this circuit cannot
+ check it (see [subEncrypted](#ElGamal-subEncrypted)).
+
+
+
+#### scalarMul [toc] [#ElGamal-scalarMul]
+#### [!toc] [#ElGamal-scalarMul]
+
+ Homomorphically multiplies the plaintext of `ct` by a public
+ scalar `k`: if `ct` encrypts `v` then the result encrypts `k * v` under the
+ same key. Both components are scaled by `k`, so the randomness scales to
+ `k * r` and the result is a valid fresh ciphertext under that key.
+
+ This is the scalar-multiplication leg of the homomorphism, and the only way
+ to express linear combinations: a weighted sum `Enc(3a + 5b)` is
+ `add(scalarMul(ca, 3), scalarMul(cb, 5))`, which repeated [add](#ElGamal-add) cannot do
+ for large weights.
+
+
+ `k` MUST be a valid Jubjub scalar (less than the subgroup order);
+ larger values fault `ecMul` (see the module-level hash-to-scalar note). The
+ caller also remains responsible for the discrete-log bound — `k * v` must
+ stay within the range a recovery search can clear.
+
+
+
+#### addEncrypted [toc] [#ElGamal-addEncrypted]
+#### [!toc] [#ElGamal-addEncrypted]
+
+ Homomorphically adds `value` to the plaintext encrypted by
+ `old`, producing a fresh ciphertext under `pk` with new randomness `r`.
+ Convenience over [add](#ElGamal-add) for folding a known plaintext into a ciphertext:
+ `add(old, encrypt(pk, value, r))`.
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### subEncrypted [toc] [#ElGamal-subEncrypted]
+#### [!toc] [#ElGamal-subEncrypted]
+
+ Homomorphically subtracts `value` from the plaintext encrypted
+ by `old`. Convenience over [sub](#ElGamal-sub): `sub(old, encrypt(pk, value, r))`.
+
+
+ The caller MUST ensure the underlying plaintext subtraction does
+ not underflow; this circuit cannot check it. Callers verify sufficiency by
+ reading the plaintext via their own witness and asserting it with
+ [assertDecryptsTo](#ElGamal-assertDecryptsTo) before calling.
+
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### rerandomize [toc] [#ElGamal-rerandomize]
+#### [!toc] [#ElGamal-rerandomize]
+
+ Rerandomizes `ct`: returns a fresh ciphertext of the same
+ plaintext under the same key `pk`, using new randomness `r`. The output is
+ unlinkable to the input (it is a uniformly fresh encryption of the same
+ value) yet decrypts identically. Equivalent to homomorphically adding an
+ encryption of zero.
+
+
+ `r = 0` is rejected (it would be a no-op via the [encryptPoint](#ElGamal-encryptPoint)
+ zero-randomness assert). One silent failure mode remains that the circuit
+ cannot catch: a `pk` that is not the key `ct` was encrypted under corrupts
+ the plaintext (the result no longer decrypts to the original value). Pass
+ fresh randomness and the matching `pk`.
+
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### assertKeyPair [toc] [#ElGamal-assertKeyPair]
+#### [!toc] [#ElGamal-assertKeyPair]
+
+ Asserts that `ek` is the secret for `pk`, i.e.
+ `derivePk(ek) == pk`. Lets a caller prove ownership of a public key
+ without touching any ciphertext or plaintext.
+
+ Requirements:
+
+ * `ek` is the secret for `pk` (`derivePk(ek) == pk`).
+
+
+#### assertDecryptsToPoint [toc] [#ElGamal-assertDecryptsToPoint]
+#### [!toc] [#ElGamal-assertDecryptsToPoint]
+
+ Asserts that `ct` decrypts under `(pk, ek)` to the message
+ point `m`, and that `ek` is the secret for `pk`. This is the general
+ (non-lifted) verification; [assertDecryptsTo](#ElGamal-assertDecryptsTo) is the lifted special case
+ with `m = g^claimedValue`.
+
+ Requirements:
+
+ * `ek` is the secret for `pk`.
+ * `ct` decrypts under `(pk, ek)` to `m`.
+
+
+#### assertDecryptsTo [toc] [#ElGamal-assertDecryptsTo]
+#### [!toc] [#ElGamal-assertDecryptsTo]
+
+ Asserts that `ct` decrypts under `(pk, ek)` to `claimedValue`,
+ and that `ek` is the secret for `pk`. This is the in-circuit verification
+ path an importing module uses to bind its witness-supplied `ek` and cached
+ plaintext to publicly stored state: a wrong `ek` fails the key check, a
+ wrong plaintext fails the decryption check. Lifted special case of
+ [assertDecryptsToPoint](#ElGamal-assertDecryptsToPoint) with the message point `g^claimedValue`.
+
+
+ `claimedValue` is the widest `Uint` Compact offers, so this parameter
+ never bounds a caller. A narrower value costs proportionally fewer rows: the
+ range constraint belongs to where the value is declared, not to this
+ signature.
+
+
+
+ The claim domain must stay below the Jubjub subgroup order `l`:
+ verification compares `g^claimedValue`, so two claims congruent mod `l` are
+ indistinguishable. `Uint<248>` clears `l` (~2^251.86) by under 4 bits, and
+ that width is set by Compact's cap rather than by this bound. 251 is the
+ widest sound `Uint` if the cap ever rises.
+
+
+ Requirements:
+
+ * `ek` is the secret for `pk`.
+ * `ct` decrypts under `(pk, ek)` to `claimedValue`.
+
+
+---
+
+## EcdhMask [toc] [#EcdhMask]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/crypto/EcdhMask";
+```
+
+---
+
+### Types [toc] [#EcdhMask-Types]
+### Types [!toc] [#EcdhMask-Types]
+
+#### Ciphertext [toc] [#EcdhMask-Ciphertext]
+#### [!toc] [#EcdhMask-Ciphertext]
+
+ An ECDH one-time-pad ciphertext: the ephemeral public key and
+ the field-masked value.
+
+
+### Ledger [toc] [#EcdhMask-Ledger]
+### Ledger [!toc] [#EcdhMask-Ledger]
+
+None.
+
+### Witnesses [toc] [#EcdhMask-Witnesses]
+### Witnesses [!toc] [#EcdhMask-Witnesses]
+
+None.
+
+### Circuits [toc] [#EcdhMask-Circuits]
+### Circuits [!toc] [#EcdhMask-Circuits]
+
+#### kdf [toc] [#EcdhMask-kdf]
+#### [!toc] [#EcdhMask-kdf]
+
+ Derives the field mask from the ECDH shared secret point,
+ domain-separated by a caller-supplied `domain` tag. Hashes the point to
+ `Bytes<32>`, then re-hashes it with the domain tag and truncates into the
+ field via `degradeToTransient`. The consumer chooses `domain` (so this
+ module is not tied to any one protocol) and MUST use the same value on
+ encrypt and decrypt.
+
+ Uses `persistentHash` deliberately: the recipient reproduces this mask
+ to decrypt, possibly across a platform upgrade, so it must be upgrade-stable.
+
+
+#### encrypt [toc] [#EcdhMask-encrypt]
+#### [!toc] [#EcdhMask-encrypt]
+
+ Encrypts `value` to `recipientPk` under ephemeral scalar `e`.
+
+
+ `e` MUST be fresh per call (see the module freshness precondition).
+
+
+ Requirements:
+
+ * `recipientPk` is not the identity point.
+ * `e` is non-zero, a valid Jubjub scalar (`< ℓ`), secret, and fresh per call.
+
+
+#### decrypt [toc] [#EcdhMask-decrypt]
+#### [!toc] [#EcdhMask-decrypt]
+
+ Recovers the value from a ciphertext using the recipient's
+ secret scalar. Pure and off-chain (no proof needed to read a received value).
+
diff --git a/content/contracts-compact/0.3.x/api/fungible-token.mdx b/content/contracts-compact/0.3.x/api/fungible-token.mdx
new file mode 100644
index 00000000..d0120079
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/fungible-token.mdx
@@ -0,0 +1,525 @@
+---
+title: FungibleToken API
+---
+
+This module provides the full FungibleToken module API.
+
+
+ For an overview of the module, read the [FungibleToken guide](../fungible-token).
+
+
+## FungibleToken [toc] [#FungibleToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken";
+```
+
+---
+
+### Ledger [toc] [#FungibleToken-Ledger]
+### Ledger [!toc] [#FungibleToken-Ledger]
+
+#### _isInitialized [toc] [#FungibleToken-_isInitialized]
+#### [!toc] [#FungibleToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _balances [toc] [#FungibleToken-_balances]
+#### [!toc] [#FungibleToken-_balances]
+
+ Mapping from account addresses to their token balances.
+
+
+#### _allowances [toc] [#FungibleToken-_allowances]
+#### [!toc] [#FungibleToken-_allowances]
+
+ Mapping from owner accounts to spender accounts and their allowances.
+
+
+#### _totalSupply [toc] [#FungibleToken-_totalSupply]
+#### [!toc] [#FungibleToken-_totalSupply]
+
+ The total token supply.
+
+
+#### _name [toc] [#FungibleToken-_name]
+#### [!toc] [#FungibleToken-_name]
+
+ The immutable token name.
+
+
+#### _symbol [toc] [#FungibleToken-_symbol]
+#### [!toc] [#FungibleToken-_symbol]
+
+ The immutable token symbol.
+
+
+#### _decimals [toc] [#FungibleToken-_decimals]
+#### [!toc] [#FungibleToken-_decimals]
+
+ The immutable token decimals.
+
+
+### Witnesses [toc] [#FungibleToken-Witnesses]
+### Witnesses [!toc] [#FungibleToken-Witnesses]
+
+#### wit_FungibleTokenSK [toc] [#FungibleToken-wit_FungibleTokenSK]
+#### [!toc] [#FungibleToken-wit_FungibleTokenSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#FungibleToken-Circuits]
+### Circuits [!toc] [#FungibleToken-Circuits]
+
+#### initialize [toc] [#FungibleToken-initialize]
+#### [!toc] [#FungibleToken-initialize]
+
+ Initializes the contract by setting the name, symbol, and decimals.
+ This MUST be called in the implementing contract's constructor. Failure to do so
+ can lead to an irreparable contract.
+
+
+#### name [toc] [#FungibleToken-name]
+#### [!toc] [#FungibleToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#FungibleToken-symbol]
+#### [!toc] [#FungibleToken-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#FungibleToken-decimals]
+#### [!toc] [#FungibleToken-decimals]
+
+ Returns the number of decimals used to get its user representation.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### totalSupply [toc] [#FungibleToken-totalSupply]
+#### [!toc] [#FungibleToken-totalSupply]
+
+ Returns the value of tokens in existence.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### balanceOf [toc] [#FungibleToken-balanceOf]
+#### [!toc] [#FungibleToken-balanceOf]
+
+ Returns the value of tokens owned by `account`.
+
+ Manually checks if `account` is a key in the map and returns 0 if it is not.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transfer [toc] [#FungibleToken-transfer]
+#### [!toc] [#FungibleToken-transfer]
+
+ Moves a `value` amount of tokens from the caller's account to `to`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not a ContractAddress.
+ * `to` is not the zero address.
+ * The caller has a balance of at least `value`.
+
+
+#### _unsafeTransfer [toc] [#FungibleToken-_unsafeTransfer]
+#### [!toc] [#FungibleToken-_unsafeTransfer]
+
+ Unsafe variant of [transfer](#FungibleToken-transfer) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not the zero address.
+ * The caller has a balance of at least `value`.
+
+
+#### allowance [toc] [#FungibleToken-allowance]
+#### [!toc] [#FungibleToken-allowance]
+
+ Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner`
+ through [transferFrom](#FungibleToken-transferFrom). This value changes when [approve](#FungibleToken-approve) or [transferFrom](#FungibleToken-transferFrom) are called.
+
+ Manually checks if `owner` and `spender` are keys in the map and returns 0 if they are not.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### approve [toc] [#FungibleToken-approve]
+#### [!toc] [#FungibleToken-approve]
+
+ Sets a `value` amount of tokens as allowance of `spender` over the caller's tokens.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `spender` is not the zero address.
+
+
+#### transferFrom [toc] [#FungibleToken-transferFrom]
+#### [!toc] [#FungibleToken-transferFrom]
+
+ Moves `value` tokens from `fromAddress` to `to` using the allowance mechanism.
+ `value` is the deducted from the caller's allowance.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have a balance of at least `value`.
+ * `to` is not the zero address.
+ * `to` is not a ContractAddress.
+ * The caller has an allowance of `fromAddress`'s tokens of at least `value`.
+
+
+#### _unsafeTransferFrom [toc] [#FungibleToken-_unsafeTransferFrom]
+#### [!toc] [#FungibleToken-_unsafeTransferFrom]
+
+ Unsafe variant of [transferFrom](#FungibleToken-transferFrom) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have a balance of at least `value`.
+ * `to` is not the zero address.
+ * The caller has an allowance of `fromAddress`'s tokens of at least `value`.
+
+
+#### _transfer [toc] [#FungibleToken-_transfer]
+#### [!toc] [#FungibleToken-_transfer]
+
+ Moves a `value` amount of tokens from `fromAddress` to `to`.
+ This circuit is equivalent to [transfer](#FungibleToken-transfer), and can be used to
+ e.g. implement automatic token fees, slashing mechanisms, etc.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have at least a balance of `value`.
+ * `to` must not be the zero address.
+ * `to` must not be a ContractAddress.
+
+
+#### _unsafeUncheckedTransfer [toc] [#FungibleToken-_unsafeUncheckedTransfer]
+#### [!toc] [#FungibleToken-_unsafeUncheckedTransfer]
+
+ Unsafe variant of [transferFrom](#FungibleToken-transferFrom) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `to` is not the zero address.
+
+
+#### _mint [toc] [#FungibleToken-_mint]
+#### [!toc] [#FungibleToken-_mint]
+
+ Creates a `value` amount of tokens and assigns them to `account`,
+ by transferring it from the zero address. Relies on the `update` mechanism.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `account` is not a ContractAddress.
+ * `account` is not the zero address.
+
+
+#### _unsafeMint [toc] [#FungibleToken-_unsafeMint]
+#### [!toc] [#FungibleToken-_unsafeMint]
+
+ Unsafe variant of [_mint](#FungibleToken-_mint) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `account` is not the zero address.
+
+
+#### _burn [toc] [#FungibleToken-_burn]
+#### [!toc] [#FungibleToken-_burn]
+
+ Destroys a `value` amount of tokens from `account`, lowering the total supply.
+ Relies on the `_update` mechanism.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `account` is not the zero address.
+ * `account` must have at least a balance of `value`.
+
+
+#### _approve [toc] [#FungibleToken-_approve]
+#### [!toc] [#FungibleToken-_approve]
+
+ Sets `value` as the allowance of `spender` over the `owner`'s tokens.
+ This circuit is equivalent to [approve](#FungibleToken-approve), and can be used to
+ e.g. set automatic allowances for certain subsystems, etc.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `owner` is not the zero address.
+ * `spender` is not the zero address.
+
+
+#### _spendAllowance [toc] [#FungibleToken-_spendAllowance]
+#### [!toc] [#FungibleToken-_spendAllowance]
+
+ Updates `owner`'s allowance for `spender` based on spent `value`.
+ Does not update the allowance value in case of infinite allowance.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `spender` must have at least an allowance of `value` from `owner`.
+
+
diff --git a/content/contracts-compact/0.3.x/api/multi-token.mdx b/content/contracts-compact/0.3.x/api/multi-token.mdx
new file mode 100644
index 00000000..e7942a17
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/multi-token.mdx
@@ -0,0 +1,445 @@
+---
+title: MultiToken API
+---
+
+This module provides the full MultiToken module API.
+
+
+ For an overview of the module, read the [MultiToken guide](../multi-token).
+
+
+## MultiToken [toc] [#MultiToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken";
+```
+
+---
+
+### Ledger [toc] [#MultiToken-Ledger]
+### Ledger [!toc] [#MultiToken-Ledger]
+
+#### _isInitialized [toc] [#MultiToken-_isInitialized]
+#### [!toc] [#MultiToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _balances [toc] [#MultiToken-_balances]
+#### [!toc] [#MultiToken-_balances]
+
+ Mapping from token ID to account balances.
+
+
+#### _operatorApprovals [toc] [#MultiToken-_operatorApprovals]
+#### [!toc] [#MultiToken-_operatorApprovals]
+
+ Mapping from account to operator approvals.
+
+
+#### _uri [toc] [#MultiToken-_uri]
+#### [!toc] [#MultiToken-_uri]
+
+ Base URI for computing token URIs.
+ Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/`id`.json
+
+
+### Witnesses [toc] [#MultiToken-Witnesses]
+### Witnesses [!toc] [#MultiToken-Witnesses]
+
+#### wit_MultiTokenSK [toc] [#MultiToken-wit_MultiTokenSK]
+#### [!toc] [#MultiToken-wit_MultiTokenSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#MultiToken-Circuits]
+### Circuits [!toc] [#MultiToken-Circuits]
+
+#### initialize [toc] [#MultiToken-initialize]
+#### [!toc] [#MultiToken-initialize]
+
+ Initializes the contract by setting the base URI for all tokens.
+
+ Requirements:
+
+ * Must be called in the contract's constructor.
+
+
+#### uri [toc] [#MultiToken-uri]
+#### [!toc] [#MultiToken-uri]
+
+ This implementation returns the same URI for *all* token types. It relies
+ on the token type ID substitution mechanism defined in the EIP:
+ https://eips.ethereum.org/EIPS/eip-1155#metadata.
+ Clients calling this function must replace the `\{id\}` substring with the
+ actual token type ID.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### balanceOf [toc] [#MultiToken-balanceOf]
+#### [!toc] [#MultiToken-balanceOf]
+
+ Returns the amount of `id` tokens owned by `account`.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### setApprovalForAll [toc] [#MultiToken-setApprovalForAll]
+#### [!toc] [#MultiToken-setApprovalForAll]
+
+ Enables or disables approval for `operator` to manage all of the caller's assets.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_MultiTokenSK](#MultiToken-wit_MultiTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `operator` is not zero.
+
+
+#### isApprovedForAll [toc] [#MultiToken-isApprovedForAll]
+#### [!toc] [#MultiToken-isApprovedForAll]
+
+ Queries if `operator` is an authorized operator for `account`.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transferFrom [toc] [#MultiToken-transferFrom]
+#### [!toc] [#MultiToken-transferFrom]
+
+ Transfers ownership of `value` amount of `id` tokens from `fromAddress` to `to`.
+ The caller must be `fromAddress` or approved to transfer on their behalf.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_MultiTokenSK](#MultiToken-wit_MultiTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not a ContractAddress.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * Caller must be `fromAddress` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _transfer [toc] [#MultiToken-_transfer]
+#### [!toc] [#MultiToken-_transfer]
+
+ Transfers ownership of `value` amount of `id` tokens from `fromAddress` to `to`.
+ Does not impose restrictions on the caller, making it suitable for composition
+ in higher-level contract logic.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not a ContractAddress.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _unsafeTransferFrom [toc] [#MultiToken-_unsafeTransferFrom]
+#### [!toc] [#MultiToken-_unsafeTransferFrom]
+
+ Unsafe variant of [transferFrom](#MultiToken-transferFrom) which allows transfers to contract addresses.
+ The caller must be `fromAddress` or approved to transfer on their behalf.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_MultiTokenSK](#MultiToken-wit_MultiTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * Caller must be `fromAddress` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _unsafeTransfer [toc] [#MultiToken-_unsafeTransfer]
+#### [!toc] [#MultiToken-_unsafeTransfer]
+
+ Unsafe variant of [_transfer](#MultiToken-_transfer) which allows transfers to contract addresses.
+ Does not impose restrictions on the caller, making it suitable as a low-level
+ building block for advanced contract logic.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _setURI [toc] [#MultiToken-_setURI]
+#### [!toc] [#MultiToken-_setURI]
+
+ Sets a new URI for all token types, by relying on the token type ID
+ substitution mechanism defined in the MultiToken standard.
+ See https://eips.ethereum.org/EIPS/eip-1155#metadata.
+
+
+ By this mechanism, any occurrence of the `\{id\}` substring in either the
+ URI or any of the values in the JSON file at said URI will be replaced by
+ clients with the token type ID.
+
+ For example, the `https://token-cdn-domain/\{id\}.json` URI would be
+ interpreted by clients as
+ `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
+ for token type ID 0x4cce0.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#MultiToken-_mint]
+#### [!toc] [#MultiToken-_mint]
+
+ Creates a `value` amount of tokens of type `id`, and assigns them to `to`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+
+
+#### _unsafeMint [toc] [#MultiToken-_unsafeMint]
+#### [!toc] [#MultiToken-_unsafeMint]
+
+ Unsafe variant of [_mint](#MultiToken-_mint) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not zero.
+
+
+#### _burn [toc] [#MultiToken-_burn]
+#### [!toc] [#MultiToken-_burn]
+
+ Destroys a `value` amount of tokens of type `id` from `fromAddress`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _setApprovalForAll [toc] [#MultiToken-_setApprovalForAll]
+#### [!toc] [#MultiToken-_setApprovalForAll]
+
+ Enables or disables approval for `operator` to manage all of `owner`'s assets.
+
+
+ This circuit does not check for access permissions but can be useful as a building block
+ for more complex contract logic.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `owner` is not zero.
+ * `operator` is not zero.
+
+
diff --git a/content/contracts-compact/0.3.x/api/multisig.mdx b/content/contracts-compact/0.3.x/api/multisig.mdx
new file mode 100644
index 00000000..487d3469
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/multisig.mdx
@@ -0,0 +1,1318 @@
+---
+title: Multisig API
+---
+
+This page provides the full API for the Multisig modules.
+
+
+ For an overview of the modules, read the [Multisig guide](../multisig).
+
+
+The page covers the following modules:
+
+- [Signer](#Signer): the registry of who may approve and how many approvals are required, generic over the signer identity type.
+- [ProposalManager](#ProposalManager): the lifecycle of transfer proposals, independent of the token kind.
+- [ShieldedTreasury](#ShieldedTreasury): holds one shielded coin per token color on the ledger and sends from it.
+- [ShieldedTreasuryStateless](#ShieldedTreasuryStateless): receives and sends shielded coins without keeping any ledger state.
+- [UnshieldedTreasury](#UnshieldedTreasury): holds unshielded tokens with a tracked per-color balance.
+- [ForwarderPrivate](#ForwarderPrivate): forwards shielded deposits to a parent stored as a commitment.
+- [ForwarderShielded](#ForwarderShielded): forwards shielded deposits to a public parent in the same transaction.
+- [ForwarderUnshielded](#ForwarderUnshielded): forwards unshielded deposits to a public parent in the same transaction.
+
+## Signer [toc] [#Signer]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Signer">;
+```
+
+`Signer` is generic over the signer identity type `T`, chosen at import time (for example `Bytes<32>`, `JubjubPoint`, or `Either`).
+The module never resolves the caller; the consuming contract passes an identity in.
+
+---
+
+### Ledger [toc] [#Signer-Ledger]
+### Ledger [!toc] [#Signer-Ledger]
+
+#### _signers [toc] [#Signer-_signers]
+#### [!toc] [#Signer-_signers]
+
+ The set of registered signer identities.
+
+
+#### _signerCount [toc] [#Signer-_signerCount]
+#### [!toc] [#Signer-_signerCount]
+
+ The number of registered signers (at most 255).
+
+
+#### _threshold [toc] [#Signer-_threshold]
+#### [!toc] [#Signer-_threshold]
+
+ The minimum number of approvals required. Zero until it is set.
+
+
+#### _isInitialized [toc] [#Signer-_isInitialized]
+#### [!toc] [#Signer-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#Signer-Witnesses]
+### Witnesses [!toc] [#Signer-Witnesses]
+
+None.
+
+### Circuits [toc] [#Signer-Circuits]
+### Circuits [!toc] [#Signer-Circuits]
+
+#### initialize [toc] [#Signer-initialize]
+#### [!toc] [#Signer-initialize]
+
+ Initializes the signer module with the given threshold
+ and an initial set of signers.
+ If used, it should only be called in the contract's constructor.
+
+ Requirements:
+
+ * If used, can only be called once (in the constructor).
+ * `thresh` must not be zero.
+ * `thresh` must not exceed the number of `signers`.
+ * `signers` must not contain duplicates.
+
+
+#### assertSigner [toc] [#Signer-assertSigner]
+#### [!toc] [#Signer-assertSigner]
+
+ Asserts that the given caller is an active signer.
+
+ Requirements:
+
+ * `caller` must be a member of the signers registry.
+
+
+#### assertThresholdMet [toc] [#Signer-assertThresholdMet]
+#### [!toc] [#Signer-assertThresholdMet]
+
+ Asserts that the given approval count meets the threshold.
+
+
+ `approvalCount` is supplied by the consuming contract and is
+ compared against the threshold only. This circuit never consults the signer
+ registry. Ensuring the count reflects distinct, currently-registered signers
+ is the consuming contract's responsibility, and a contract whose approval
+ accounting is not itself gated by [assertSigner](#Signer-assertSigner) gets no protection here.
+
+ The count is deliberately left unbounded by `_signerCount`: approvals
+ collected before a signer was removed must still count toward a threshold
+ the remaining signers satisfy, and bounding the count would reject those.
+ See the stale-signer-set guidance in the module header.
+
+
+ Requirements:
+
+ * Ledger threshold must be set (not be zero). An unconfigured module has a
+ zero threshold, so this rejects before any approval count is compared.
+ * `approvalCount` must be >= threshold.
+
+
+#### getSignerCount [toc] [#Signer-getSignerCount]
+#### [!toc] [#Signer-getSignerCount]
+
+ Returns the current signer count. Returns zero when no
+ signers have been registered.
+
+
+#### getThreshold [toc] [#Signer-getThreshold]
+#### [!toc] [#Signer-getThreshold]
+
+ Returns the approval threshold. Returns zero when the
+ threshold has not been set.
+
+
+#### isSigner [toc] [#Signer-isSigner]
+#### [!toc] [#Signer-isSigner]
+
+ Returns whether the given account is an active signer.
+
+
+#### _addSigner [toc] [#Signer-_addSigner]
+#### [!toc] [#Signer-_addSigner]
+
+ Adds a new signer to the registry.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `signer` must not already be an active signer.
+
+
+#### _removeSigner [toc] [#Signer-_removeSigner]
+#### [!toc] [#Signer-_removeSigner]
+
+ Removes a signer from the registry.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `signer` must be an active signer.
+ * Removal must not drop signer count below threshold.
+
+
+#### _changeThreshold [toc] [#Signer-_changeThreshold]
+#### [!toc] [#Signer-_changeThreshold]
+
+ Updates the approval threshold.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `newThreshold` must not be zero.
+ * `newThreshold` must not exceed the current signer count.
+
+
+#### _setThreshold [toc] [#Signer-_setThreshold]
+#### [!toc] [#Signer-_setThreshold]
+
+ Sets the approval threshold without checking
+ against the current signer count.
+
+
+ This is intended for use during contract construction
+ or custom setup flows where signers may not yet be registered.
+
+ Leaving `newThreshold` above the signer count is permitted and expected
+ mid-setup, and is not self-enforcing: [assertThresholdMet](#Signer-assertThresholdMet) compares
+ `approvalCount` against the threshold alone, so a caller supplying a matching
+ count satisfies it even against an empty registry. Contracts must not gate
+ operations on [assertThresholdMet](#Signer-assertThresholdMet) while setup is incomplete. Lower the
+ threshold with [_setThreshold](#Signer-_setThreshold); [_changeThreshold](#Signer-_changeThreshold) cannot lower one while the
+ signer count is zero, because that would require a zero threshold.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy. Use [_changeThreshold](#Signer-_changeThreshold) for
+ operational threshold changes with signer count validation.
+
+
+ Requirements:
+
+ * `newThreshold` must not be zero.
+
+
+---
+
+## ProposalManager [toc] [#ProposalManager]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ProposalManager";
+```
+
+---
+
+### Types [toc] [#ProposalManager-Types]
+### Types [!toc] [#ProposalManager-Types]
+
+#### ProposalStatus [toc] [#ProposalManager-ProposalStatus]
+#### [!toc] [#ProposalManager-ProposalStatus]
+
+ Proposal lifecycle states. `Inactive` is the zero value and is
+ never stored: [_createProposal](#ProposalManager-_createProposal) writes `Active`, and every view asserts the
+ proposal exists before reading, so a stored proposal can never read
+ `Inactive`.
+
+
+#### RecipientKind [toc] [#ProposalManager-RecipientKind]
+#### [!toc] [#ProposalManager-RecipientKind]
+
+ Says how a recipient's 32 address bytes are to be read: as a Zswap coin public key, a user address, or a contract address.
+ `Contract` is accepted by both [toShieldedRecipient](#ProposalManager-toShieldedRecipient) and [toUnshieldedRecipient](#ProposalManager-toUnshieldedRecipient),
+ so the tag alone does not determine the rail.
+
+
+#### Recipient [toc] [#ProposalManager-Recipient]
+#### [!toc] [#ProposalManager-Recipient]
+
+ A typed recipient. Construct it with the recipient helper circuits rather than directly.
+
+
+#### Proposal [toc] [#ProposalManager-Proposal]
+#### [!toc] [#ProposalManager-Proposal]
+
+ A transfer proposal: recipient, token color, amount, and lifecycle status.
+
+
+### Ledger [toc] [#ProposalManager-Ledger]
+### Ledger [!toc] [#ProposalManager-Ledger]
+
+#### _nextProposalId [toc] [#ProposalManager-_nextProposalId]
+#### [!toc] [#ProposalManager-_nextProposalId]
+
+ Counter of proposals created. Its value is the id of the most recent proposal.
+
+
+#### _proposals [toc] [#ProposalManager-_proposals]
+#### [!toc] [#ProposalManager-_proposals]
+
+ Mapping from proposal id to proposal. Proposals are public: every recipient address, color, amount, and status is readable off-chain.
+
+
+### Witnesses [toc] [#ProposalManager-Witnesses]
+### Witnesses [!toc] [#ProposalManager-Witnesses]
+
+None.
+
+### Circuits [toc] [#ProposalManager-Circuits]
+### Circuits [!toc] [#ProposalManager-Circuits]
+
+#### shieldedUserRecipient [toc] [#ProposalManager-shieldedUserRecipient]
+#### [!toc] [#ProposalManager-shieldedUserRecipient]
+
+ Constructs a shielded user recipient.
+
+
+#### unshieldedUserRecipient [toc] [#ProposalManager-unshieldedUserRecipient]
+#### [!toc] [#ProposalManager-unshieldedUserRecipient]
+
+ Constructs an unshielded user recipient.
+
+
+#### contractRecipient [toc] [#ProposalManager-contractRecipient]
+#### [!toc] [#ProposalManager-contractRecipient]
+
+ Constructs a contract recipient.
+
+
+#### toShieldedRecipient [toc] [#ProposalManager-toShieldedRecipient]
+#### [!toc] [#ProposalManager-toShieldedRecipient]
+
+ Converts a Recipient to a shielded send recipient.
+ Handles both ShieldedUser and Contract kinds.
+
+ Requirements:
+
+ * Recipient kind must be ShieldedUser or Contract.
+
+
+#### toUnshieldedRecipient [toc] [#ProposalManager-toUnshieldedRecipient]
+#### [!toc] [#ProposalManager-toUnshieldedRecipient]
+
+ Converts a Recipient to an unshielded send recipient.
+ Handles both UnshieldedUser and Contract kinds.
+
+ Requirements:
+
+ * Recipient kind must be UnshieldedUser or Contract.
+
+
+#### assertProposalExists [toc] [#ProposalManager-assertProposalExists]
+#### [!toc] [#ProposalManager-assertProposalExists]
+
+ Asserts that a proposal exists.
+
+ Requirements:
+
+ * Proposal with `id` must have been created.
+
+
+#### assertProposalActive [toc] [#ProposalManager-assertProposalActive]
+#### [!toc] [#ProposalManager-assertProposalActive]
+
+ Asserts that a proposal exists and is active.
+
+ Requirements:
+
+ * Proposal must exist.
+ * Proposal status must be Active.
+
+
+#### _createProposal [toc] [#ProposalManager-_createProposal]
+#### [!toc] [#ProposalManager-_createProposal]
+
+ Creates a new proposal.
+
+
+ The recipient is NOT validated beyond being well-typed. Two
+ consequences the consumer must handle:
+
+ * A `kind` the execution rail cannot accept (an `UnshieldedUser` recipient
+ on a shielded treasury, say) creates a proposal that passes here and then
+ reverts in [toShieldedRecipient](#ProposalManager-toShieldedRecipient) at execution. It stays `Active` and
+ unexecutable on that rail until [_cancelProposal](#ProposalManager-_cancelProposal) retires it.
+ * A zero or otherwise unspendable `address` is accepted, and funds sent to
+ it on execution are unrecoverable.
+
+
+
+ `_nextProposalId` is incremented and then read back for the map key,
+ making this a pinned read-modify-write.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `amount` must be greater than 0.
+
+
+#### _cancelProposal [toc] [#ProposalManager-_cancelProposal]
+#### [!toc] [#ProposalManager-_cancelProposal]
+
+ Cancels a proposal.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Proposal must be active.
+
+
+#### _markExecuted [toc] [#ProposalManager-_markExecuted]
+#### [!toc] [#ProposalManager-_markExecuted]
+
+ Marks a proposal as executed.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Proposal must be active.
+
+
+#### getProposal [toc] [#ProposalManager-getProposal]
+#### [!toc] [#ProposalManager-getProposal]
+
+ Returns the full proposal data.
+
+
+#### getProposalRecipient [toc] [#ProposalManager-getProposalRecipient]
+#### [!toc] [#ProposalManager-getProposalRecipient]
+
+ Returns the recipient of a proposal.
+
+
+#### getProposalAmount [toc] [#ProposalManager-getProposalAmount]
+#### [!toc] [#ProposalManager-getProposalAmount]
+
+ Returns the amount of a proposal.
+
+
+#### getProposalColor [toc] [#ProposalManager-getProposalColor]
+#### [!toc] [#ProposalManager-getProposalColor]
+
+ Returns the token color of a proposal.
+
+
+#### getProposalStatus [toc] [#ProposalManager-getProposalStatus]
+#### [!toc] [#ProposalManager-getProposalStatus]
+
+ Returns the status of a proposal.
+
+
+---
+
+## ShieldedTreasury [toc] [#ShieldedTreasury]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ShieldedTreasury";
+```
+
+---
+
+### Ledger [toc] [#ShieldedTreasury-Ledger]
+### Ledger [!toc] [#ShieldedTreasury-Ledger]
+
+#### _coins [toc] [#ShieldedTreasury-_coins]
+#### [!toc] [#ShieldedTreasury-_coins]
+
+ One stored coin per token color. Every coin held (nonce, color, value, and Merkle index) is readable off-chain in plaintext.
+
+
+#### _received [toc] [#ShieldedTreasury-_received]
+#### [!toc] [#ShieldedTreasury-_received]
+
+ Cumulative value received per token color.
+
+
+#### _sent [toc] [#ShieldedTreasury-_sent]
+#### [!toc] [#ShieldedTreasury-_sent]
+
+ Cumulative value sent per token color.
+
+
+### Witnesses [toc] [#ShieldedTreasury-Witnesses]
+### Witnesses [!toc] [#ShieldedTreasury-Witnesses]
+
+None.
+
+### Circuits [toc] [#ShieldedTreasury-Circuits]
+### Circuits [!toc] [#ShieldedTreasury-Circuits]
+
+#### _deposit [toc] [#ShieldedTreasury-_deposit]
+#### [!toc] [#ShieldedTreasury-_deposit]
+
+ Receives a shielded coin into the treasury.
+
+ The coin is first claimed at the protocol level via `receiveShielded`,
+ which allocates the Merkle tree index required by `insertCoin`.
+ The coin is then merged with any existing coin of the same color,
+ or inserted as a new entry if no coin of that color exists.
+
+ Zero-value deposits are permitted and are not always a no-op. On a color the
+ treasury does not yet hold, a zero-value coin is inserted and occupies that
+ color's single slot until a later deposit merges into it; if a coin of that
+ color already exists, the zero merges into it via `mergeCoinImmediate` and
+ the stored value is unchanged. They may serve as signaling mechanisms when
+ events are supported. They are not useful as privacy decoys, this
+ treasury's holdings are public (see the module header).
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Deposit must not cause received total overflow.
+
+
+#### _send [toc] [#ShieldedTreasury-_send]
+#### [!toc] [#ShieldedTreasury-_send]
+
+ Sends shielded tokens from the treasury.
+
+ Looks up the stored coin by color, verifies sufficient value,
+ and executes the shielded send. `sendShielded` already routes any
+ change back to this contract as a self-owned output. Unlike the
+ stateless variant, this treasury RETAINS the change: it is recorded in
+ the UTXO map for the next spend. The returned `result.change` therefore
+ belongs to the treasury and must not be re-spent by the caller (doing
+ so would double-spend the recorded coin).
+
+
+ The returned `ShieldedSendResult` carries the sent and change coins
+ in plaintext, so sinking it (returning it from an entry point, writing it
+ to ledger, passing it cross-contract) discloses them. `_coins` publishes only
+ the retained change coin, and nothing at all when the send leaves no change,
+ so the sent coin's plaintext is disclosed beyond anything in public state.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * A coin of the given `color` must exist in the treasury.
+ * The coin's value must be >= `amount`.
+ * Send must not cause sent total overflow.
+
+
+#### getTokenBalance [toc] [#ShieldedTreasury-getTokenBalance]
+#### [!toc] [#ShieldedTreasury-getTokenBalance]
+
+ Returns the current token balance for a color.
+ Reads the actual coin value from the UTXO map.
+
+
+#### getReceivedTotal [toc] [#ShieldedTreasury-getReceivedTotal]
+#### [!toc] [#ShieldedTreasury-getReceivedTotal]
+
+ Returns the cumulative received total for a color.
+
+
+#### getSentTotal [toc] [#ShieldedTreasury-getSentTotal]
+#### [!toc] [#ShieldedTreasury-getSentTotal]
+
+ Returns the cumulative sent total for a color.
+
+
+#### getReceivedMinusSent [toc] [#ShieldedTreasury-getReceivedMinusSent]
+#### [!toc] [#ShieldedTreasury-getReceivedMinusSent]
+
+ Returns the difference between cumulative received
+ and cumulative sent totals for a color. Should equal
+ [getTokenBalance](#ShieldedTreasury-getTokenBalance) if accounting is consistent.
+
+
+ The subtraction is unguarded. [_send](#ShieldedTreasury-_send) cannot drive the sent total
+ past the received total. Change re-insertion never increments `_received`
+ so the invariant holds for any consumer that goes through [_deposit](#ShieldedTreasury-_deposit) and
+ [_send](#ShieldedTreasury-_send). A consumer that writes the exported `_coins` directly can break
+ it, and this circuit then fails as an unprovable range violation rather
+ than a named assert.
+
+
+
+---
+
+## ShieldedTreasuryStateless [toc] [#ShieldedTreasuryStateless]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ShieldedTreasuryStateless";
+```
+
+---
+
+### Ledger [toc] [#ShieldedTreasuryStateless-Ledger]
+### Ledger [!toc] [#ShieldedTreasuryStateless-Ledger]
+
+None. The module keeps no coin map, balances, or totals; the operator tracks coins off-chain.
+
+### Witnesses [toc] [#ShieldedTreasuryStateless-Witnesses]
+### Witnesses [!toc] [#ShieldedTreasuryStateless-Witnesses]
+
+None.
+
+### Circuits [toc] [#ShieldedTreasuryStateless-Circuits]
+### Circuits [!toc] [#ShieldedTreasuryStateless-Circuits]
+
+#### _deposit [toc] [#ShieldedTreasuryStateless-_deposit]
+#### [!toc] [#ShieldedTreasuryStateless-_deposit]
+
+ Receives a shielded coin into the treasury.
+
+
+#### _send [toc] [#ShieldedTreasuryStateless-_send]
+#### [!toc] [#ShieldedTreasuryStateless-_send]
+
+ Sends shielded tokens using a caller-provided coin.
+
+ The coin is supplied by the caller rather than looked up from
+ on-chain state; the token color is derived from `coin.color`.
+ Executes the shielded send and returns the result untouched.
+ `sendShielded` already routes any change back to this contract as a
+ self-owned output, so `result.change` is a live, spendable coin. The
+ implementing contract chooses what to do with it: retain it off-chain and
+ pass it back as the `coin` for a later spend, or spend it onward to a
+ different recipient in the same transaction via `sendImmediateShielded`.
+ (Re-spending change is only meaningful when it goes somewhere other than
+ self — `sendShielded` already routes it to self.) No balance or
+ received/sent accounting is tracked on the ledger.
+
+
+ The returned `ShieldedSendResult` holds the sent and change coins
+ in plaintext (`nonce`, `color`, `value`). Consuming it in-circuit is safe
+ (passing `result.change` to `sendImmediateShielded`, for instance). Returning
+ it from your entry point, writing it to ledger, or passing it to another
+ contract publishes those coin details, including the amount left over from
+ the coin that was spent. Note the compiler will NOT stop you: the
+ `disclose` calls below clear the witness taint, so such a return compiles
+ with no `disclose` of your own and no marker at the disclosure site. If you
+ do not need in-circuit change handling, ignore the result. The change is
+ recoverable off-chain from the transaction's private proving state.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `coin.value` must be >= `amount`.
+
+
+---
+
+## UnshieldedTreasury [toc] [#UnshieldedTreasury]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/UnshieldedTreasury";
+```
+
+---
+
+### Ledger [toc] [#UnshieldedTreasury-Ledger]
+### Ledger [!toc] [#UnshieldedTreasury-Ledger]
+
+#### _balances [toc] [#UnshieldedTreasury-_balances]
+#### [!toc] [#UnshieldedTreasury-_balances]
+
+ Tracked balance per token color. It is the authority for the module's sufficiency and overflow guards and stays in step with the protocol balance only while every credit and debit goes through `_deposit` and `_send`.
+
+
+### Witnesses [toc] [#UnshieldedTreasury-Witnesses]
+### Witnesses [!toc] [#UnshieldedTreasury-Witnesses]
+
+None.
+
+### Circuits [toc] [#UnshieldedTreasury-Circuits]
+### Circuits [!toc] [#UnshieldedTreasury-Circuits]
+
+#### _deposit [toc] [#UnshieldedTreasury-_deposit]
+#### [!toc] [#UnshieldedTreasury-_deposit]
+
+ Receives unshielded tokens into the treasury.
+
+ The token receive is executed at the protocol level first via
+ `receiveUnshielded`. The balance map is then updated.
+
+ Zero-value deposits are permitted. While currently a no-op
+ economically, they may serve as signaling mechanisms when events
+ are supported. On a color the treasury has not held, a zero-value
+ operation creates a zero-valued `_balances` entry, which
+ [getTokenBalance](#UnshieldedTreasury-getTokenBalance) reports the same as absence.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Deposit must not cause balance overflow.
+
+
+#### _send [toc] [#UnshieldedTreasury-_send]
+#### [!toc] [#UnshieldedTreasury-_send]
+
+ Sends unshielded tokens from the treasury. Zero-amount sends
+ are permitted and are value-preserving no-ops.
+
+
+ Recipients are not validated. An unspendable recipient (such as a
+ zero address) is accepted and permanently removes the funds from the
+ contract. This is not a sanctioned burn — no unshielded equivalent of
+ `shieldedBurnAddress` is exposed to contracts — so treat recipient
+ validation as the consuming contract's responsibility.
+
+
+
+ Sending to this contract's own address zeroes the tracked balance
+ while the funds stay held and spendable at the protocol level, after which
+ [_send](#UnshieldedTreasury-_send) will refuse to touch them. Recovery is only possible outside this
+ module: a consumer may reconcile the exported `_balances`, or spend the
+ funds through its own `sendUnshielded` call.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * The treasury must hold sufficient balance for the given token color.
+
+
+#### getTokenBalance [toc] [#UnshieldedTreasury-getTokenBalance]
+#### [!toc] [#UnshieldedTreasury-getTokenBalance]
+
+ Returns the balance for a given token color.
+
+
+---
+
+## ForwarderPrivate [toc] [#ForwarderPrivate]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ForwarderPrivate";
+```
+
+---
+
+### Ledger [toc] [#ForwarderPrivate-Ledger]
+### Ledger [!toc] [#ForwarderPrivate-Ledger]
+
+#### _parentCommitment [toc] [#ForwarderPrivate-_parentCommitment]
+#### [!toc] [#ForwarderPrivate-_parentCommitment]
+
+ Commitment to the `(parent, opSecret)` pair, `H(pad(32, "ForwarderPrivate:commitment") || parent || opSecret)`. Immutable after initialization.
+
+
+#### _isInitialized [toc] [#ForwarderPrivate-_isInitialized]
+#### [!toc] [#ForwarderPrivate-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#ForwarderPrivate-Witnesses]
+### Witnesses [!toc] [#ForwarderPrivate-Witnesses]
+
+None.
+
+### Circuits [toc] [#ForwarderPrivate-Circuits]
+### Circuits [!toc] [#ForwarderPrivate-Circuits]
+
+#### initialize [toc] [#ForwarderPrivate-initialize]
+#### [!toc] [#ForwarderPrivate-initialize]
+
+ Initializes the forwarder with a parent commitment.
+ Called once from the preset constructor. The commitment is
+ immutable after init.
+
+ Requirements:
+
+ * Contract must not be initialized.
+ * `parentCommitment` must not be the zero bytes. A zero commitment
+ is the sole drain gate and would leave every deposited coin
+ permanently unrecoverable (no preimage exists for `default>`
+ under the domain-tagged hash).
+
+
+#### _deposit [toc] [#ForwarderPrivate-_deposit]
+#### [!toc] [#ForwarderPrivate-_deposit]
+
+ Receives a shielded coin into the forwarder's custody.
+ No ledger write — coins dwell at the contract address until drained.
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+#### _drain [toc] [#ForwarderPrivate-_drain]
+#### [!toc] [#ForwarderPrivate-_drain]
+
+ Spends a previously-deposited shielded coin to the parent,
+ a coin public key. The caller proves knowledge of `(parent, opSecret)`
+ matching the stored commitment; the coin is sent as a plain shielded note,
+ so the parent stays hidden on-chain. If `coin.value` exceeds `value`,
+ `sendShielded` routes the change back to this contract as a self-owned
+ output; [_drain](#ForwarderPrivate-_drain) returns it untouched via `result.change`. The
+ implementing contract chooses what to do with it: leave it dwelling at
+ the contract for a future drain, or spend it onward to a different
+ recipient in the same transaction via `sendImmediateShielded`.
+
+ The parent is a `ZswapCoinPublicKey`, never a `ContractAddress`: a shielded
+ send to a contract publishes that contract's address in cleartext on the
+ transaction (the protocol must route the coin to a named contract), which
+ would defeat the private-parent guarantee. A coin public key is encrypted
+ inside the Zswap output and never appears on the public transcript.
+
+ Requirements:
+
+ * Contract must be initialized.
+ * `parent` must not be the zero key.
+ * `_calculateParentCommitment(parent.bytes, opSecret) == _parentCommitment`.
+ * `coin.value` must be >= `value` (enforced by `sendShielded`).
+
+
+ **Losing the operational secret is permanent fund loss.** It
+ is the sole drain authorization, with no rotation, revocation, or
+ recovery path. If the operator misplaces it, every shielded coin
+ accumulated at this contract is forever inaccessible, equivalent to
+ losing a hot-wallet private key. Back it up offline before the first
+ deposit and treat it with the same hygiene as a signing key.
+
+
+
+#### _calculateParentCommitment [toc] [#ForwarderPrivate-_calculateParentCommitment]
+#### [!toc] [#ForwarderPrivate-_calculateParentCommitment]
+
+ Computes the parent commitment from `(parentAddr, opSecret)`.
+ Pure circuit — used off-chain by the deployer to compute the
+ constructor argument, and inside [_drain](#ForwarderPrivate-_drain) for the preimage check.
+ Callable without initialization.
+
+ The first hash input is a fixed domain tag
+ (`pad(32, "ForwarderPrivate:commitment")`). The tag prevents
+ preimage collisions with other `persistentHash` users in the
+ system that hash two `Bytes<32>` values — a colliding preimage
+ crafted under a different domain cannot satisfy this commitment.
+
+
+---
+
+## ForwarderShielded [toc] [#ForwarderShielded]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ForwarderShielded";
+```
+
+---
+
+### Ledger [toc] [#ForwarderShielded-Ledger]
+### Ledger [!toc] [#ForwarderShielded-Ledger]
+
+#### _parent [toc] [#ForwarderShielded-_parent]
+#### [!toc] [#ForwarderShielded-_parent]
+
+ The parent recipient, stored generically as
+ `Either` for forward compatibility.
+ [initialize](#ForwarderShielded-initialize) only ever writes the `left` (coin public key) arm today. See
+ the module header.
+
+
+#### _isInitialized [toc] [#ForwarderShielded-_isInitialized]
+#### [!toc] [#ForwarderShielded-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#ForwarderShielded-Witnesses]
+### Witnesses [!toc] [#ForwarderShielded-Witnesses]
+
+None.
+
+### Circuits [toc] [#ForwarderShielded-Circuits]
+### Circuits [!toc] [#ForwarderShielded-Circuits]
+
+#### initialize [toc] [#ForwarderShielded-initialize]
+#### [!toc] [#ForwarderShielded-initialize]
+
+ Initializes the forwarder with a parent recipient.
+ Called once from the preset constructor. The coin public key is
+ stored in the `left` arm of the generic `_parent` ledger field.
+
+ Requirements:
+
+ * Contract must not be initialized.
+ * `parent` must not be the zero key. A zero parent would forward
+ every deposit to an unspendable key with no recovery path.
+
+
+ Beyond the zero-key check, the caller must ensure `parent` can
+ spend forwarded coins. An unspendable parent strands every deposit with
+ no recovery path.
+
+
+
+#### _deposit [toc] [#ForwarderShielded-_deposit]
+#### [!toc] [#ForwarderShielded-_deposit]
+
+ Receives a shielded coin and atomically forwards it to
+ `_parent`. The coin is claimed at the protocol level via
+ `receiveShielded`, then immediately re-sent via
+ `sendImmediateShielded` to the stored parent recipient (always the
+ `left` / coin-public-key arm today).
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+---
+
+## ForwarderUnshielded [toc] [#ForwarderUnshielded]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ForwarderUnshielded";
+```
+
+---
+
+### Ledger [toc] [#ForwarderUnshielded-Ledger]
+### Ledger [!toc] [#ForwarderUnshielded-Ledger]
+
+#### _parent [toc] [#ForwarderUnshielded-_parent]
+#### [!toc] [#ForwarderUnshielded-_parent]
+
+ The parent recipient, stored generically as
+ `Either` for forward compatibility.
+ [initialize](#ForwarderUnshielded-initialize) only ever writes the `right` (user address) arm today. See
+ the module header.
+
+
+#### _isInitialized [toc] [#ForwarderUnshielded-_isInitialized]
+#### [!toc] [#ForwarderUnshielded-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#ForwarderUnshielded-Witnesses]
+### Witnesses [!toc] [#ForwarderUnshielded-Witnesses]
+
+None.
+
+### Circuits [toc] [#ForwarderUnshielded-Circuits]
+### Circuits [!toc] [#ForwarderUnshielded-Circuits]
+
+#### initialize [toc] [#ForwarderUnshielded-initialize]
+#### [!toc] [#ForwarderUnshielded-initialize]
+
+ Initializes the forwarder with a parent recipient.
+ Called once from the preset constructor. The user address is stored
+ in the `right` arm of the generic `_parent` ledger field.
+
+ Requirements:
+
+ * Contract must not be initialized.
+ * `parent` must not be the zero address. A zero parent would forward
+ every deposit to an unspendable address with no recovery path.
+
+
+ Beyond the zero-address check, the caller must ensure `parent`
+ can spend forwarded amounts. An unspendable parent strands every
+ deposit with no recovery path.
+
+
+
+#### _deposit [toc] [#ForwarderUnshielded-_deposit]
+#### [!toc] [#ForwarderUnshielded-_deposit]
+
+ Receives an unshielded amount of `color` and atomically
+ forwards it to `_parent` via `sendUnshielded` (always the `right` /
+ user-address arm today).
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
diff --git a/content/contracts-compact/0.3.x/api/native-shielded-token.mdx b/content/contracts-compact/0.3.x/api/native-shielded-token.mdx
new file mode 100644
index 00000000..496db788
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/native-shielded-token.mdx
@@ -0,0 +1,1695 @@
+---
+title: NativeShieldedToken API
+---
+
+This module provides the full NativeShieldedToken module API: the two token flavors, their shared core,
+the supply and derived-nonce extensions, and the owner-gated preset.
+
+
+ For an overview of the modules, read the [NativeShieldedToken guide](../native-shielded-token).
+
+
+The page covers the following modules:
+
+- **Token modules**
+ - [NativeShieldedToken](#NativeShieldedToken): a single token type issued and burned by the contract.
+ - [NativeShieldedTokenFamily](#NativeShieldedTokenFamily): many token types, keyed by domain, under one contract.
+ - [NativeShieldedTokenCore](#NativeShieldedTokenCore): the shared core behind the two flavors; not composed directly.
+- **Extensions**
+ - [NativeShieldedTokenPublicSupply](#NativeShieldedTokenPublicSupply): on-chain minted and burned totals for a `NativeShieldedToken`.
+ - [NativeShieldedTokenFamilyPublicSupply](#NativeShieldedTokenFamilyPublicSupply): per-domain on-chain totals for a `NativeShieldedTokenFamily`.
+ - [NativeShieldedTokenPublicSupplyCore](#NativeShieldedTokenPublicSupplyCore): the shared core behind the two supply extensions; not composed directly.
+ - [NativeShieldedTokenDerivedNonce](#NativeShieldedTokenDerivedNonce): mint coin nonces derived from a counter.
+- **Preset**
+ - [ZOwnableNativeShieldedToken](#ZOwnableNativeShieldedToken): a single token whose `ZOwnablePK` owner mints and burns, with a public minted total.
+
+These modules export no structs or enums of their own. Coins are the standard library's `ShieldedCoinInfo`
+(a `nonce`, `color`, and `value`) and `QualifiedShieldedCoinInfo` (the same plus the Merkle-tree index `mt_index`
+of a coin the contract holds).
+
+## NativeShieldedToken [toc] [#NativeShieldedToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedToken";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedToken-Ledger]
+### Ledger [!toc] [#NativeShieldedToken-Ledger]
+
+#### _domain [toc] [#NativeShieldedToken-_domain]
+#### [!toc] [#NativeShieldedToken-_domain]
+
+ Domain separator fixed at construction; with the contract
+ address it determines this token's color.
+
+
+#### _name [toc] [#NativeShieldedToken-_name]
+#### [!toc] [#NativeShieldedToken-_name]
+
+ The immutable token name. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _symbol [toc] [#NativeShieldedToken-_symbol]
+#### [!toc] [#NativeShieldedToken-_symbol]
+
+ The immutable token symbol. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _decimals [toc] [#NativeShieldedToken-_decimals]
+#### [!toc] [#NativeShieldedToken-_decimals]
+
+ The immutable token decimals. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _isInitialized [toc] [#NativeShieldedToken-_isInitialized]
+#### [!toc] [#NativeShieldedToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#NativeShieldedToken-Witnesses]
+### Witnesses [!toc] [#NativeShieldedToken-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedToken-Circuits]
+### Circuits [!toc] [#NativeShieldedToken-Circuits]
+
+#### initialize [toc] [#NativeShieldedToken-initialize]
+#### [!toc] [#NativeShieldedToken-initialize]
+
+ Initializes the module's domain and metadata.
+ This MUST be called in the implementing contract's constructor, and it
+ MUST be the only token operation performed there. Failure to call it can
+ lead to an irreparable contract; perform mint, burn, and [tokenColor](#NativeShieldedToken-tokenColor) only
+ in post-deployment circuits, never in the constructor.
+
+
+#### isInitialized [toc] [#NativeShieldedToken-isInitialized]
+#### [!toc] [#NativeShieldedToken-isInitialized]
+
+ Returns whether the contract has been initialized.
+
+
+#### name [toc] [#NativeShieldedToken-name]
+#### [!toc] [#NativeShieldedToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#NativeShieldedToken-symbol]
+#### [!toc] [#NativeShieldedToken-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#NativeShieldedToken-decimals]
+#### [!toc] [#NativeShieldedToken-decimals]
+
+ Returns the number of decimals used to get its user representation.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#NativeShieldedToken-tokenColor]
+#### [!toc] [#NativeShieldedToken-tokenColor]
+
+ Returns this token's coin color:
+ `tokenType(_domain, kernel.self())`, computed at call time.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#NativeShieldedToken-_mint]
+#### [!toc] [#NativeShieldedToken-_mint]
+
+ Mints `amount` of the token to `recipient`, using a
+ caller-supplied nonce.
+
+ The caller is fully responsible for nonce uniqueness. Reusing a
+ nonce for the same (value, recipient) produces a duplicate commitment,
+ which the protocol rejects. With a secret random nonce this is the
+ recipient-private mint. For mints that require no caller-managed nonce,
+ compose the `NativeShieldedTokenDerivedNonce` extension and pass its
+ `_deriveNonce()` output.
+
+
+ The returned coin info is the only copy available to the
+ recipient; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-minted coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `recipient` is not zero.
+
+
+#### _mintToSelf [toc] [#NativeShieldedToken-_mintToSelf]
+#### [!toc] [#NativeShieldedToken-_mintToSelf]
+
+ Mints `amount` to this contract itself, claiming the coin in
+ the same call. Forwards to `NativeShieldedTokenCore._mint` with
+ `right(kernel.self())`.
+
+ Nonce uniqueness is the caller's responsibility, as in [_mint](#NativeShieldedToken-_mint). Spend
+ the coin later with [_burnFromSelf](#NativeShieldedToken-_burnFromSelf), once it carries an `mt_index`.
+
+
+ The consumer SHOULD persist the returned coin info in its own
+ ledger state: it is the contract's holding and is not otherwise
+ recoverable.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _burn [toc] [#NativeShieldedToken-_burn]
+#### [!toc] [#NativeShieldedToken-_burn]
+
+ Burns `amount` from `coin`, a coin provided within the
+ current transaction (e.g. paid in by the caller's wallet), and routes the
+ remaining change to `refundTo`.
+
+ To destroy `coin` in full, pass `coin.value` as `amount`; the change
+ branch will not fire and `none` is returned. The `refundTo` value is
+ inert in that case but must still be supplied and non-zero.
+
+ The coin is received by the contract and spent in the same
+ transaction, so the spend goes through `sendImmediateShielded` (the
+ transient path). For coins the contract already holds, use
+ [_burnFromSelf](#NativeShieldedToken-_burnFromSelf) instead.
+
+
+ The returned refund coin info is the only copy available to
+ `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-sent coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token color.
+ * `amount` is less than or equal to `coin.value`.
+ * `refundTo` is not zero (the zero key is the burn address; a zero
+ `refundTo` would silently burn the change as well).
+
+
+#### _burnFromSelf [toc] [#NativeShieldedToken-_burnFromSelf]
+#### [!toc] [#NativeShieldedToken-_burnFromSelf]
+
+ Burns `amount` from `coin`, a coin this contract already
+ holds (a Merkle-tree entry with a valid `mt_index`). Any change is
+ auto-received by the contract and returned.
+
+ The consumer SHOULD persist the returned change coin info in its own
+ ledger state: the change replaces `coin` as the contract's holding, and
+ its info is not otherwise recoverable.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token color.
+ * `amount` is less than or equal to `coin.value`.
+
+
+---
+
+## NativeShieldedTokenFamily [toc] [#NativeShieldedTokenFamily]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedTokenFamily";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenFamily-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenFamily-Ledger]
+
+#### _name [toc] [#NativeShieldedTokenFamily-_name]
+#### [!toc] [#NativeShieldedTokenFamily-_name]
+
+ The immutable family name shared by all token types. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _symbol [toc] [#NativeShieldedTokenFamily-_symbol]
+#### [!toc] [#NativeShieldedTokenFamily-_symbol]
+
+ The immutable family symbol shared by all token types. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _decimals [toc] [#NativeShieldedTokenFamily-_decimals]
+#### [!toc] [#NativeShieldedTokenFamily-_decimals]
+
+ The immutable family-wide decimals. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _isInitialized [toc] [#NativeShieldedTokenFamily-_isInitialized]
+#### [!toc] [#NativeShieldedTokenFamily-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#NativeShieldedTokenFamily-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenFamily-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenFamily-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenFamily-Circuits]
+
+#### initialize [toc] [#NativeShieldedTokenFamily-initialize]
+#### [!toc] [#NativeShieldedTokenFamily-initialize]
+
+ Initializes the module's family metadata.
+ This MUST be called in the implementing contract's constructor, and it
+ MUST be the only token operation performed there. Failure to call it can
+ lead to an irreparable contract; perform mint, burn, and [tokenColor](#NativeShieldedTokenFamily-tokenColor) only
+ in post-deployment circuits, never in the constructor.
+
+
+#### isInitialized [toc] [#NativeShieldedTokenFamily-isInitialized]
+#### [!toc] [#NativeShieldedTokenFamily-isInitialized]
+
+ Returns whether the contract has been initialized.
+
+
+#### name [toc] [#NativeShieldedTokenFamily-name]
+#### [!toc] [#NativeShieldedTokenFamily-name]
+
+ Returns the family name shared by all token types of this
+ contract. Per-type identity belongs in the consumer's own state; see the
+ module notes.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#NativeShieldedTokenFamily-symbol]
+#### [!toc] [#NativeShieldedTokenFamily-symbol]
+
+ Returns the family symbol shared by all token types of this
+ contract.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#NativeShieldedTokenFamily-decimals]
+#### [!toc] [#NativeShieldedTokenFamily-decimals]
+
+ Returns the family-wide number of decimals used to get the
+ user representation.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#NativeShieldedTokenFamily-tokenColor]
+#### [!toc] [#NativeShieldedTokenFamily-tokenColor]
+
+ Returns the coin color (token type) for `domain` under this
+ contract's address, so integrators do not have to re-derive it by hand.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#NativeShieldedTokenFamily-_mint]
+#### [!toc] [#NativeShieldedTokenFamily-_mint]
+
+ Mints `amount` of the token identified by `domain` to
+ `recipient`, using a caller-supplied nonce.
+
+ The caller is fully responsible for nonce uniqueness. Reusing a
+ nonce for the same (domain, value, recipient) produces a duplicate
+ commitment, which the protocol rejects. With a secret random nonce this
+ is the recipient-private mint. For mints that require no caller-managed
+ nonce, compose the `NativeShieldedTokenDerivedNonce` extension and pass
+ its `_deriveNonce()` output.
+
+
+ The returned coin info is the only copy available to the
+ recipient; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-minted coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `recipient` is not zero.
+
+
+#### _mintToSelf [toc] [#NativeShieldedTokenFamily-_mintToSelf]
+#### [!toc] [#NativeShieldedTokenFamily-_mintToSelf]
+
+ Mints `amount` of the token identified by `domain` to this
+ contract itself, claiming the coin in the same call. Forwards to
+ `NativeShieldedTokenCore._mint` with `right(kernel.self())`.
+
+ Nonce uniqueness is the caller's responsibility, as in [_mint](#NativeShieldedTokenFamily-_mint). Spend
+ the coin later with [_burnFromSelf](#NativeShieldedTokenFamily-_burnFromSelf), once it carries an `mt_index`.
+
+
+ The consumer SHOULD persist the returned coin info in its own
+ ledger state: it is the contract's holding and is not otherwise
+ recoverable.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _burn [toc] [#NativeShieldedTokenFamily-_burn]
+#### [!toc] [#NativeShieldedTokenFamily-_burn]
+
+ Burns `amount` from `coin`, a coin provided within the
+ current transaction (e.g. paid in by the caller's wallet), and routes the
+ remaining change to `refundTo`.
+
+ To destroy `coin` in full, pass `coin.value` as `amount`; the change
+ branch will not fire and `none` is returned. The `refundTo` value is
+ inert in that case but must still be supplied and non-zero.
+
+ The coin is received by the contract and spent in the same
+ transaction, so the spend goes through `sendImmediateShielded` (the
+ transient path). For coins the contract already holds, use
+ [_burnFromSelf](#NativeShieldedTokenFamily-_burnFromSelf) instead.
+
+
+ The returned refund coin info is the only copy available to
+ `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-sent coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+ * `refundTo` is not zero (the zero key is the burn address; a zero
+ `refundTo` would silently burn the change as well).
+
+
+#### _burnFromSelf [toc] [#NativeShieldedTokenFamily-_burnFromSelf]
+#### [!toc] [#NativeShieldedTokenFamily-_burnFromSelf]
+
+ Burns `amount` from `coin`, a coin this contract already
+ holds (a Merkle-tree entry with a valid `mt_index`). Any change is
+ auto-received by the contract and returned.
+
+ The consumer SHOULD persist the returned change coin info in its own
+ ledger state: the change replaces `coin` as the contract's holding, and
+ its info is not otherwise recoverable.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+
+
+---
+
+## NativeShieldedTokenCore [toc] [#NativeShieldedTokenCore]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedTokenCore";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenCore-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenCore-Ledger]
+
+#### _isInitialized [toc] [#NativeShieldedTokenCore-_isInitialized]
+#### [!toc] [#NativeShieldedTokenCore-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _name [toc] [#NativeShieldedTokenCore-_name]
+#### [!toc] [#NativeShieldedTokenCore-_name]
+
+ The immutable token name.
+
+
+#### _symbol [toc] [#NativeShieldedTokenCore-_symbol]
+#### [!toc] [#NativeShieldedTokenCore-_symbol]
+
+ The immutable token symbol.
+
+
+#### _decimals [toc] [#NativeShieldedTokenCore-_decimals]
+#### [!toc] [#NativeShieldedTokenCore-_decimals]
+
+ The immutable token decimals.
+
+
+### Witnesses [toc] [#NativeShieldedTokenCore-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenCore-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenCore-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenCore-Circuits]
+
+#### initialize [toc] [#NativeShieldedTokenCore-initialize]
+#### [!toc] [#NativeShieldedTokenCore-initialize]
+
+ Initializes the contract-wide metadata.
+ This MUST be called in the implementing contract's constructor (via
+ the wrapping flavor's [initialize](#NativeShieldedTokenCore-initialize)). Failure to do so can lead to an
+ irreparable contract.
+
+
+#### assertInitialized [toc] [#NativeShieldedTokenCore-assertInitialized]
+#### [!toc] [#NativeShieldedTokenCore-assertInitialized]
+
+ Asserts that the contract has been initialized, throwing an
+ error if not.
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+#### assertNotInitialized [toc] [#NativeShieldedTokenCore-assertNotInitialized]
+#### [!toc] [#NativeShieldedTokenCore-assertNotInitialized]
+
+ Asserts that the contract has not been initialized, throwing
+ an error if it has.
+
+ Requirements:
+
+ * Contract must not be initialized.
+
+
+#### isInitialized [toc] [#NativeShieldedTokenCore-isInitialized]
+#### [!toc] [#NativeShieldedTokenCore-isInitialized]
+
+ Returns whether the contract has been initialized.
+ Does not gate on init (it reports it), so it is safe to call before
+ [initialize](#NativeShieldedTokenCore-initialize).
+
+
+#### name [toc] [#NativeShieldedTokenCore-name]
+#### [!toc] [#NativeShieldedTokenCore-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#NativeShieldedTokenCore-symbol]
+#### [!toc] [#NativeShieldedTokenCore-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#NativeShieldedTokenCore-decimals]
+#### [!toc] [#NativeShieldedTokenCore-decimals]
+
+ Returns the number of decimals used to get its user representation.
+ This is a display convention only. The protocol stores and operates
+ on integer values without reference to decimals.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#NativeShieldedTokenCore-tokenColor]
+#### [!toc] [#NativeShieldedTokenCore-tokenColor]
+
+ Returns the coin color for `domain` under this contract's
+ address: `tokenType(domain, kernel.self())`, computed at call time.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#NativeShieldedTokenCore-_mint]
+#### [!toc] [#NativeShieldedTokenCore-_mint]
+
+ Mints `amount` of the token identified by `domain` to
+ `recipient`, using a caller-supplied nonce.
+
+ The caller is fully responsible for nonce uniqueness. Reusing a
+ nonce for the same (domain, value, recipient) produces a duplicate
+ commitment, which the protocol rejects. A `right(kernel.self())` recipient
+ is claimed via `receiveShielded` in this same call, so the consumer SHOULD
+ persist the returned coin info: it is the contract's holding and is not
+ otherwise recoverable.
+
+
+ The returned coin info is the only copy available to the
+ recipient; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-minted coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `recipient` is not zero.
+ * `recipient`, if a contract address, is this contract's own address.
+
+
+#### _burn [toc] [#NativeShieldedTokenCore-_burn]
+#### [!toc] [#NativeShieldedTokenCore-_burn]
+
+ Burns `amount` from `coin`, a coin provided within the
+ current transaction (e.g. paid in by the caller's wallet), and routes the
+ remaining change to `refundTo`.
+
+ To destroy `coin` in full, pass `coin.value` as `amount`; the change
+ branch will not fire and `none` is returned. The `refundTo` value is
+ inert in that case but must still be supplied and non-zero.
+
+ The coin is received by the contract and spent in the same
+ transaction, so the spend goes through `sendImmediateShielded` (the
+ transient path). For coins the contract already holds, use
+ [_burnFromSelf](#NativeShieldedTokenCore-_burnFromSelf) instead. A `right(kernel.self())` refund is claimed via
+ `receiveShielded` in this same call.
+
+
+ The returned refund coin info is the only copy available to
+ `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-sent coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+ * `refundTo` is not zero (the zero key is the burn address; a zero
+ `refundTo` would silently burn the change as well).
+ * `refundTo`, if a contract address, is this contract's own address.
+
+
+#### _burnFromSelf [toc] [#NativeShieldedTokenCore-_burnFromSelf]
+#### [!toc] [#NativeShieldedTokenCore-_burnFromSelf]
+
+ Burns `amount` from `coin`, a coin this contract already
+ holds (a Merkle-tree entry with a valid `mt_index`). Any change is
+ auto-received by the contract and returned.
+
+ The consumer SHOULD persist the returned change coin info in its own
+ ledger state: the change replaces `coin` as the contract's holding, and
+ its info is not otherwise recoverable.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+
+
+---
+
+## NativeShieldedTokenPublicSupply [toc] [#NativeShieldedTokenPublicSupply]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenPublicSupply";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenPublicSupply-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenPublicSupply-Ledger]
+
+#### _totalMinted [toc] [#NativeShieldedTokenPublicSupply-_totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#NativeShieldedTokenPublicSupply-_totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#NativeShieldedTokenPublicSupply-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenPublicSupply-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenPublicSupply-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenPublicSupply-Circuits]
+
+#### _addMinted [toc] [#NativeShieldedTokenPublicSupply-_addMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_addMinted]
+
+ Adds `amount` to the exact minted total. Call once per
+ successful `NativeShieldedToken._mint`.
+
+
+#### _addBurned [toc] [#NativeShieldedTokenPublicSupply-_addBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_addBurned]
+
+ Adds `amount` to the contract-mediated burned total. Call once
+ per successful `NativeShieldedToken._burn` / `_burnFromSelf`.
+ Enforces `burned <= minted`; an unpaired or duplicate burn reverts.
+
+
+#### totalMinted [toc] [#NativeShieldedTokenPublicSupply-totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupply-totalMinted]
+
+ Returns the exact amount ever minted.
+
+
+#### totalBurned [toc] [#NativeShieldedTokenPublicSupply-totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupply-totalBurned]
+
+ Returns the contract-mediated amount burned.
+
+ This is a lower bound: coins sent directly to the burn address
+ without going through the contract are not counted.
+
+
+
+#### totalSupply [toc] [#NativeShieldedTokenPublicSupply-totalSupply]
+#### [!toc] [#NativeShieldedTokenPublicSupply-totalSupply]
+
+ Returns `totalMinted() - totalBurned()`.
+
+ This is an UPPER BOUND on circulating supply, not an exact value:
+ burns that bypass the contract are invisible. See the module notes.
+
+
+
+---
+
+## NativeShieldedTokenFamilyPublicSupply [toc] [#NativeShieldedTokenFamilyPublicSupply]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenFamilyPublicSupply";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenFamilyPublicSupply-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenFamilyPublicSupply-Ledger]
+
+#### _totalMinted [toc] [#NativeShieldedTokenFamilyPublicSupply-_totalMinted]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#NativeShieldedTokenFamilyPublicSupply-_totalBurned]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#NativeShieldedTokenFamilyPublicSupply-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenFamilyPublicSupply-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenFamilyPublicSupply-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenFamilyPublicSupply-Circuits]
+
+#### _addMinted [toc] [#NativeShieldedTokenFamilyPublicSupply-_addMinted]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_addMinted]
+
+ Adds `amount` to the exact minted total for `domain`. Call
+ once per successful `NativeShieldedTokenFamily._mint`.
+
+
+#### _addBurned [toc] [#NativeShieldedTokenFamilyPublicSupply-_addBurned]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_addBurned]
+
+ Adds `amount` to the contract-mediated burned total for
+ `domain`. Call once per successful `NativeShieldedTokenFamily._burn` /
+ `_burnFromSelf`.
+ Enforces per-domain `burned <= minted`; an unpaired, duplicate, or
+ wrong-domain burn reverts.
+
+
+#### totalMinted [toc] [#NativeShieldedTokenFamilyPublicSupply-totalMinted]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-totalMinted]
+
+ Returns the exact amount ever minted for `domain`.
+
+
+#### totalBurned [toc] [#NativeShieldedTokenFamilyPublicSupply-totalBurned]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-totalBurned]
+
+ Returns the contract-mediated amount burned for `domain`.
+
+ This is a lower bound: coins sent directly to the burn address
+ without going through the contract are not counted.
+
+
+
+#### totalSupply [toc] [#NativeShieldedTokenFamilyPublicSupply-totalSupply]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-totalSupply]
+
+ Returns `totalMinted(domain) - totalBurned(domain)`.
+
+ This is an UPPER BOUND on circulating supply, not an exact value:
+ burns that bypass the contract are invisible. See the module notes.
+
+
+
+---
+
+## NativeShieldedTokenPublicSupplyCore [toc] [#NativeShieldedTokenPublicSupplyCore]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenPublicSupplyCore";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenPublicSupplyCore-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenPublicSupplyCore-Ledger]
+
+#### _totalMinted [toc] [#NativeShieldedTokenPublicSupplyCore-_totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#NativeShieldedTokenPublicSupplyCore-_totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#NativeShieldedTokenPublicSupplyCore-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenPublicSupplyCore-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenPublicSupplyCore-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenPublicSupplyCore-Circuits]
+
+#### _addMinted [toc] [#NativeShieldedTokenPublicSupplyCore-_addMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_addMinted]
+
+ Adds `amount` to the exact minted total for `domain`. Call
+ once per successful token `_mint`.
+ Checks for overflow in order to output a readable error message.
+
+
+#### _addBurned [toc] [#NativeShieldedTokenPublicSupplyCore-_addBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_addBurned]
+
+ Adds `amount` to the contract-mediated burned total for
+ `domain`. Call once per successful token `_burn` / `_burnFromSelf`.
+ Enforces the per-domain `burned <= minted` invariant: a wrong-domain,
+ duplicate, or unpaired burn reverts rather than corrupting [totalSupply](#NativeShieldedTokenPublicSupplyCore-totalSupply).
+ The guard also keeps the add and the [totalSupply](#NativeShieldedTokenPublicSupplyCore-totalSupply) subtraction within
+ bounds, so no separate overflow/underflow check is needed.
+
+
+#### totalMinted [toc] [#NativeShieldedTokenPublicSupplyCore-totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-totalMinted]
+
+ Returns the exact amount ever minted for `domain`.
+
+
+#### totalBurned [toc] [#NativeShieldedTokenPublicSupplyCore-totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-totalBurned]
+
+ Returns the contract-mediated amount burned for `domain`.
+
+ This is a lower bound: coins sent directly to the burn address
+ without going through the contract are not counted.
+
+
+
+#### totalSupply [toc] [#NativeShieldedTokenPublicSupplyCore-totalSupply]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-totalSupply]
+
+ Returns `totalMinted(domain) - totalBurned(domain)`, or 0 if
+ [totalBurned](#NativeShieldedTokenPublicSupplyCore-totalBurned) somehow exceeds [totalMinted](#NativeShieldedTokenPublicSupplyCore-totalMinted).
+
+ This is an UPPER BOUND on circulating supply, not an exact value:
+ burns that bypass the contract are invisible. See the module notes.
+
+ The `burned <= minted` invariant enforced by [_addBurned](#NativeShieldedTokenPublicSupplyCore-_addBurned) already makes
+ `burned > minted` unreachable through the accounting API, so the clamp never
+ fires in normal operation. It is kept so the getter is a total function that
+ reads cleanly (returning 0) rather than reverting on an out-of-bound state.
+
+
+---
+
+## NativeShieldedTokenDerivedNonce [toc] [#NativeShieldedTokenDerivedNonce]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenDerivedNonce";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenDerivedNonce-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenDerivedNonce-Ledger]
+
+#### _counter [toc] [#NativeShieldedTokenDerivedNonce-_counter]
+#### [!toc] [#NativeShieldedTokenDerivedNonce-_counter]
+
+ Monotonic index feeding the derived-nonce derivation.
+
+
+### Witnesses [toc] [#NativeShieldedTokenDerivedNonce-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenDerivedNonce-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenDerivedNonce-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenDerivedNonce-Circuits]
+
+#### _deriveNonce [toc] [#NativeShieldedTokenDerivedNonce-_deriveNonce]
+#### [!toc] [#NativeShieldedTokenDerivedNonce-_deriveNonce]
+
+ Advances the counter and returns the next derived coin nonce,
+ for use as the `nonce` argument of a native token mint.
+
+
+ The derivation input is public ledger state, so mints using this
+ nonce are recipient-public; see the module notes.
+
+
+
+---
+
+## ZOwnableNativeShieldedToken [toc] [#ZOwnableNativeShieldedToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/presets/ZOwnableNativeShieldedToken";
+```
+
+---
+
+### Ledger [toc] [#ZOwnableNativeShieldedToken-Ledger]
+### Ledger [!toc] [#ZOwnableNativeShieldedToken-Ledger]
+
+#### _ownerCommitment [toc] [#ZOwnableNativeShieldedToken-_ownerCommitment]
+#### [!toc] [#ZOwnableNativeShieldedToken-_ownerCommitment]
+
+ Stores the current hashed commitment representing the owner.
+ This commitment is derived from the public identifier (e.g., `SHA256(pk, nonce)`),
+ the `instanceSalt`, the transfer `counter`, and a domain separator.
+
+ A commitment of `default>` (i.e., zero) indicates the contract is unowned.
+
+
+#### _counter [toc] [#ZOwnableNativeShieldedToken-_counter]
+#### [!toc] [#ZOwnableNativeShieldedToken-_counter]
+
+ Internal transfer counter used to prevent commitment reuse.
+
+ Increments by 1 on every successful ownership transfer. Combined with `id` and
+ `instanceSalt` to compute unique owner commitments over time.
+
+
+#### _instanceSalt [toc] [#ZOwnableNativeShieldedToken-_instanceSalt]
+#### [!toc] [#ZOwnableNativeShieldedToken-_instanceSalt]
+
+ A per-instance value provided at initialization used to namespace
+ commitments for this contract instance.
+
+ This salt prevents commitment collisions across contracts that might otherwise use
+ the same owner identifiers or domain parameters. It is immutable after initialization.
+
+
+#### _ownableIsInitialized [toc] [#ZOwnableNativeShieldedToken-_ownableIsInitialized]
+#### [!toc] [#ZOwnableNativeShieldedToken-_ownableIsInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _domain [toc] [#ZOwnableNativeShieldedToken-_domain]
+#### [!toc] [#ZOwnableNativeShieldedToken-_domain]
+
+ Domain separator fixed at construction; with the contract
+ address it determines this token's color.
+
+
+#### _name [toc] [#ZOwnableNativeShieldedToken-_name]
+#### [!toc] [#ZOwnableNativeShieldedToken-_name]
+
+ The immutable token name. Re-exported from `NativeShieldedToken`.
+
+
+#### _symbol [toc] [#ZOwnableNativeShieldedToken-_symbol]
+#### [!toc] [#ZOwnableNativeShieldedToken-_symbol]
+
+ The immutable token symbol. Re-exported from `NativeShieldedToken`.
+
+
+#### _decimals [toc] [#ZOwnableNativeShieldedToken-_decimals]
+#### [!toc] [#ZOwnableNativeShieldedToken-_decimals]
+
+ The immutable token decimals. Re-exported from `NativeShieldedToken`.
+
+
+#### _isInitialized [toc] [#ZOwnableNativeShieldedToken-_isInitialized]
+#### [!toc] [#ZOwnableNativeShieldedToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _totalMinted [toc] [#ZOwnableNativeShieldedToken-_totalMinted]
+#### [!toc] [#ZOwnableNativeShieldedToken-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#ZOwnableNativeShieldedToken-_totalBurned]
+#### [!toc] [#ZOwnableNativeShieldedToken-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#ZOwnableNativeShieldedToken-Witnesses]
+### Witnesses [!toc] [#ZOwnableNativeShieldedToken-Witnesses]
+
+None declared by this module. The composed `ZOwnablePK` requires
+[wit_secretNonce](./ownable#ZOwnablePK-wit_secretNonce), which every owner-gated circuit calls through
+`assertOnlyOwner`. The implementing DApp supplies it; the TypeScript witnesses in the library repository are
+test-only and are not published.
+
+### Circuits [toc] [#ZOwnableNativeShieldedToken-Circuits]
+### Circuits [!toc] [#ZOwnableNativeShieldedToken-Circuits]
+
+#### initialize [toc] [#ZOwnableNativeShieldedToken-initialize]
+#### [!toc] [#ZOwnableNativeShieldedToken-initialize]
+
+ Sets the owner and the token's domain and metadata.
+
+
+ Call from the implementing contract's constructor only. It writes
+ sealed fields, so implementing contracts MUST NOT expose it as a
+ post-deploy circuit.
+
+
+
+ `ownerId` must be `_computeOwnerId(pk, secretNonce)` for the coin
+ public key the owner's wallet will supply as `ownPublicKey()`. The circuit
+ cannot check this at deploy; any other key leaves the contract with no
+ usable owner and no recovery.
+
+
+ Requirements:
+
+ * Contract is not initialized.
+ * `ownerId` is not zero.
+
+
+#### mint [toc] [#ZOwnableNativeShieldedToken-mint]
+#### [!toc] [#ZOwnableNativeShieldedToken-mint]
+
+ Mints `amount` to `recipient` and adds it to the minted total.
+ Owner only.
+
+ Requirements:
+
+ * Caller is the owner.
+ * `recipient` is not zero.
+
+
+#### burn [toc] [#ZOwnableNativeShieldedToken-burn]
+#### [!toc] [#ZOwnableNativeShieldedToken-burn]
+
+ Burns `amount` of `coin`, paid into the tx by the caller's
+ wallet, and refunds the rest to `refundTo`. Owner only.
+
+ Requirements:
+
+ * Caller is the owner.
+ * `coin.color` is this token's color.
+ * `amount` \<= `coin.value`.
+ * `refundTo` is not zero.
+
+
+#### transferOwnership [toc] [#ZOwnableNativeShieldedToken-transferOwnership]
+#### [!toc] [#ZOwnableNativeShieldedToken-transferOwnership]
+
+ Transfers ownership to `newOwnerId`. Owner only.
+
+
+ `newOwnerId` must be `_computeOwnerId(pk, secretNonce)` for the
+ coin public key the new owner's wallet will supply as `ownPublicKey()`.
+ The circuit cannot check this; any other key leaves the contract with no
+ usable owner and no recovery.
+
+
+ Requirements:
+
+ * Caller is the owner.
+ * `newOwnerId` is not zero.
+
+
+#### renounceOwnership [toc] [#ZOwnableNativeShieldedToken-renounceOwnership]
+#### [!toc] [#ZOwnableNativeShieldedToken-renounceOwnership]
+
+ Clears the owner. Owner only.
+
+
+ Irreversible: mint and burn stop forever.
+
+
+ Requirements:
+
+ * Caller is the owner.
+
+
+#### owner [toc] [#ZOwnableNativeShieldedToken-owner]
+#### [!toc] [#ZOwnableNativeShieldedToken-owner]
+
+ Returns the current owner commitment.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _computeOwnerId [toc] [#ZOwnableNativeShieldedToken-_computeOwnerId]
+#### [!toc] [#ZOwnableNativeShieldedToken-_computeOwnerId]
+
+ Returns the owner id `H(pk, nonce)`.
+
+ Requirements:
+
+ * `pk` is a coin public key.
+
+
+#### totalMinted [toc] [#ZOwnableNativeShieldedToken-totalMinted]
+#### [!toc] [#ZOwnableNativeShieldedToken-totalMinted]
+
+ Returns the total amount ever minted.
+
+
+#### name [toc] [#ZOwnableNativeShieldedToken-name]
+#### [!toc] [#ZOwnableNativeShieldedToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#ZOwnableNativeShieldedToken-symbol]
+#### [!toc] [#ZOwnableNativeShieldedToken-symbol]
+
+ Returns the token symbol.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#ZOwnableNativeShieldedToken-decimals]
+#### [!toc] [#ZOwnableNativeShieldedToken-decimals]
+
+ Returns the token decimals.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#ZOwnableNativeShieldedToken-tokenColor]
+#### [!toc] [#ZOwnableNativeShieldedToken-tokenColor]
+
+ Returns this contract's token color,
+ `tokenType(tokenDomain, kernel.self())`.
+
+ Requirements:
+
+ * Contract is initialized.
+
diff --git a/content/contracts-compact/0.3.x/api/non-fungible-token.mdx b/content/contracts-compact/0.3.x/api/non-fungible-token.mdx
new file mode 100644
index 00000000..a1ef742c
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/non-fungible-token.mdx
@@ -0,0 +1,662 @@
+---
+title: NonFungibleToken API
+---
+
+This module provides the full NonFungibleToken module API.
+
+
+ For an overview of the module, read the [NonFungibleToken guide](../non-fungible-token).
+
+
+## NonFungibleToken [toc] [#NonFungibleToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken";
+```
+
+---
+
+### Ledger [toc] [#NonFungibleToken-Ledger]
+### Ledger [!toc] [#NonFungibleToken-Ledger]
+
+#### _name [toc] [#NonFungibleToken-_name]
+#### [!toc] [#NonFungibleToken-_name]
+
+ / Public state
+
+
+#### _symbol [toc] [#NonFungibleToken-_symbol]
+#### [!toc] [#NonFungibleToken-_symbol]
+
+ The immutable token symbol.
+
+
+#### _isInitialized [toc] [#NonFungibleToken-_isInitialized]
+#### [!toc] [#NonFungibleToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _owners [toc] [#NonFungibleToken-_owners]
+#### [!toc] [#NonFungibleToken-_owners]
+
+ Mapping from token IDs to their owner addresses.
+
+
+#### _balances [toc] [#NonFungibleToken-_balances]
+#### [!toc] [#NonFungibleToken-_balances]
+
+ Mapping from account addresses to their token balances.
+
+
+#### _tokenApprovals [toc] [#NonFungibleToken-_tokenApprovals]
+#### [!toc] [#NonFungibleToken-_tokenApprovals]
+
+ Mapping from token IDs to approved addresses.
+
+
+#### _operatorApprovals [toc] [#NonFungibleToken-_operatorApprovals]
+#### [!toc] [#NonFungibleToken-_operatorApprovals]
+
+ Mapping from owner addresses to operator approvals.
+
+
+#### _tokenURIs [toc] [#NonFungibleToken-_tokenURIs]
+#### [!toc] [#NonFungibleToken-_tokenURIs]
+
+ Mapping from token IDs to their metadata URIs.
+
+
+### Witnesses [toc] [#NonFungibleToken-Witnesses]
+### Witnesses [!toc] [#NonFungibleToken-Witnesses]
+
+#### wit_NonFungibleTokenSK [toc] [#NonFungibleToken-wit_NonFungibleTokenSK]
+#### [!toc] [#NonFungibleToken-wit_NonFungibleTokenSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#NonFungibleToken-Circuits]
+### Circuits [!toc] [#NonFungibleToken-Circuits]
+
+#### initialize [toc] [#NonFungibleToken-initialize]
+#### [!toc] [#NonFungibleToken-initialize]
+
+ Initializes the contract by setting the name and symbol.
+
+ This MUST be called in the implementing contract's constructor.
+ Failure to do so can lead to an irreparable contract.
+
+ Requirements:
+
+ * Contract is not initialized.
+
+
+#### balanceOf [toc] [#NonFungibleToken-balanceOf]
+#### [!toc] [#NonFungibleToken-balanceOf]
+
+ Returns the number of tokens in `owner`'s account.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### ownerOf [toc] [#NonFungibleToken-ownerOf]
+#### [!toc] [#NonFungibleToken-ownerOf]
+
+ Returns the owner of the `tokenId` token.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `tokenId` must exist.
+
+
+#### name [toc] [#NonFungibleToken-name]
+#### [!toc] [#NonFungibleToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### symbol [toc] [#NonFungibleToken-symbol]
+#### [!toc] [#NonFungibleToken-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### tokenURI [toc] [#NonFungibleToken-tokenURI]
+#### [!toc] [#NonFungibleToken-tokenURI]
+
+ Returns the token URI for the given `tokenId`. Returns the empty
+ string if a tokenURI does not exist.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `tokenId` must exist.
+
+
+ Native strings and string operations aren't supported within the Compact language,
+ e.g. concatenating a base URI + token ID is not possible like in other NFT implementations.
+ Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs.
+ It's up to the implementation to decide on how to handle this.
+
+
+
+#### _setTokenURI [toc] [#NonFungibleToken-_setTokenURI]
+#### [!toc] [#NonFungibleToken-_setTokenURI]
+
+ Sets the URI as [tokenURI](#NonFungibleToken-tokenURI) for the given `tokenId`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `tokenId` must exist.
+
+
+ The URI for a given NFT is usually set when the NFT is minted.
+
+
+
+#### approve [toc] [#NonFungibleToken-approve]
+#### [!toc] [#NonFungibleToken-approve]
+
+ Gives permission to `to` to transfer `tokenId` token to another account.
+ The approval is cleared when the token is transferred.
+
+ Only a single account can be approved at a time, so approving the zero value clears previous approvals.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The caller must either own the token or be an approved operator.
+ * `tokenId` must exist.
+
+
+#### getApproved [toc] [#NonFungibleToken-getApproved]
+#### [!toc] [#NonFungibleToken-getApproved]
+
+ Returns the account approved for `tokenId` token.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+
+
+#### setApprovalForAll [toc] [#NonFungibleToken-setApprovalForAll]
+#### [!toc] [#NonFungibleToken-setApprovalForAll]
+
+ Approve or remove `operator` as an operator for the caller.
+ Operators can call [transferFrom](#NonFungibleToken-transferFrom) for any token owned by the caller.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `operator` cannot be zero.
+
+
+#### isApprovedForAll [toc] [#NonFungibleToken-isApprovedForAll]
+#### [!toc] [#NonFungibleToken-isApprovedForAll]
+
+ Returns if the `operator` is allowed to manage all of the assets of `owner`.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### transferFrom [toc] [#NonFungibleToken-transferFrom]
+#### [!toc] [#NonFungibleToken-transferFrom]
+
+ Transfers `tokenId` token from `fromAddress` to `to`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract interactions
+ are supported in Compact. This restriction prevents assets from being inadvertently locked in contracts that cannot
+ currently handle token receipt.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Requirements:
+
+ * The contract is initialized.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+ * `tokenId` token must be owned by `fromAddress`.
+ * If the caller is not `fromAddress`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
+
+
+#### _unsafeTransferFrom [toc] [#NonFungibleToken-_unsafeTransferFrom]
+#### [!toc] [#NonFungibleToken-_unsafeTransferFrom]
+
+ Transfers `tokenId` token from `fromAddress` to `to`. It does NOT check if the recipient is a ContractAddress.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract calls
+ are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * The contract is initialized.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `tokenId` token must be owned by `fromAddress`.
+ * If the caller is not `fromAddress`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
+
+
+#### _ownerOf [toc] [#NonFungibleToken-_ownerOf]
+#### [!toc] [#NonFungibleToken-_ownerOf]
+
+ Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### _getApproved [toc] [#NonFungibleToken-_getApproved]
+#### [!toc] [#NonFungibleToken-_getApproved]
+
+ Returns the approved address for `tokenId`. Returns the zero value if `tokenId` is not minted.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### _isAuthorized [toc] [#NonFungibleToken-_isAuthorized]
+#### [!toc] [#NonFungibleToken-_isAuthorized]
+
+ Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
+ particular (ignoring whether it is owned by `owner`).
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+ This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
+ assumption.
+
+
+
+#### _checkAuthorized [toc] [#NonFungibleToken-_checkAuthorized]
+#### [!toc] [#NonFungibleToken-_checkAuthorized]
+
+ Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `spender` has approval from `owner` for `tokenId` OR `spender` has approval to manage all of `owner`'s assets.
+
+
+ This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
+ assumption.
+
+
+
+#### _mint [toc] [#NonFungibleToken-_mint]
+#### [!toc] [#NonFungibleToken-_mint]
+
+ Mints `tokenId` and transfers it to `to`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must not exist.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+
+
+#### _unsafeMint [toc] [#NonFungibleToken-_unsafeMint]
+#### [!toc] [#NonFungibleToken-_unsafeMint]
+
+ Mints `tokenId` and transfers it to `to`. It does NOT check if the recipient is a ContractAddress.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must not exist.
+ * `to` is not zero.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+
+#### _burn [toc] [#NonFungibleToken-_burn]
+#### [!toc] [#NonFungibleToken-_burn]
+
+ Destroys `tokenId`.
+ The approval is cleared when the token is burned.
+ This circuit does not check if the sender is authorized to operate on the token.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+
+
+#### _transfer [toc] [#NonFungibleToken-_transfer]
+#### [!toc] [#NonFungibleToken-_transfer]
+
+ Transfers `tokenId` from `fromAddress` to `to`.
+ As opposed to [transferFrom](#NonFungibleToken-transferFrom), this imposes no restrictions on the caller's identity.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from being inadvertently
+ locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * The contract is initialized.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+ * `tokenId` token must be owned by `fromAddress`.
+
+
+#### _unsafeTransfer [toc] [#NonFungibleToken-_unsafeTransfer]
+#### [!toc] [#NonFungibleToken-_unsafeTransfer]
+
+ Transfers `tokenId` from `fromAddress` to `to`.
+ As opposed to [_unsafeTransferFrom](#NonFungibleToken-_unsafeTransferFrom), this imposes no restrictions on the caller's identity.
+ It does NOT check if the recipient is a ContractAddress.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `to` is not zero.
+ * `tokenId` token must be owned by `fromAddress`.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+
+#### _approve [toc] [#NonFungibleToken-_approve]
+#### [!toc] [#NonFungibleToken-_approve]
+
+ Approve `to` to operate on `tokenId`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+ * If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
+ or approved to operate on the token (by the owner).
+
+
+#### _unsafeApprove [toc] [#NonFungibleToken-_unsafeApprove]
+#### [!toc] [#NonFungibleToken-_unsafeApprove]
+
+ Approve `to` to operate on `tokenId`, requiring the token to exist
+ only when `isExistenceRequired` is true.
+
+
+ Passing false skips the check that `tokenId` was minted. Both
+ [_unsafeTransferFrom](#NonFungibleToken-_unsafeTransferFrom) and `_update` read that invariant back, so a planted approval
+ can mint a token nobody authorized or outlive a later mint. Pass false only where
+ the owner has already been read, as `_update` does.
+
+
+ Counterpart to Solidity's `_approve(to, tokenId, auth, bool emitEvent)`, where
+ the flag also suppresses the `Approval` event. Compact has no events, so it carries
+ the existence check alone.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist, unless `isExistenceRequired` is false and `auth` is zero.
+ * If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
+ or approved to operate on the token (by the owner).
+
+
+#### _setApprovalForAll [toc] [#NonFungibleToken-_setApprovalForAll]
+#### [!toc] [#NonFungibleToken-_setApprovalForAll]
+
+ Approve `operator` to operate on all of `owner` tokens.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `owner` is not zero.
+ * `operator` is not zero.
+
+
+#### _requireOwned [toc] [#NonFungibleToken-_requireOwned]
+#### [!toc] [#NonFungibleToken-_requireOwned]
+
+ Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
+ Returns the owner.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+
+
diff --git a/content/contracts-compact/0.3.x/api/ownable.mdx b/content/contracts-compact/0.3.x/api/ownable.mdx
new file mode 100644
index 00000000..31a29a89
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/ownable.mdx
@@ -0,0 +1,512 @@
+---
+title: Ownable API
+---
+
+This module provides the full Ownable module API.
+
+
+For an overview of the module, read the [Ownable guide](../ownable).
+
+
+The page covers the following modules:
+
+- [Ownable](#Ownable): a single owner identified by a public account identifier.
+- [ZOwnablePK](#ZOwnablePK): a single owner identified by a shielded commitment.
+
+## Ownable [toc] [#Ownable]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable";
+```
+
+---
+
+### Ledger [toc] [#Ownable-Ledger]
+### Ledger [!toc] [#Ownable-Ledger]
+
+#### _isInitialized [toc] [#Ownable-_isInitialized]
+#### [!toc] [#Ownable-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _owner [toc] [#Ownable-_owner]
+#### [!toc] [#Ownable-_owner]
+
+ Either a `Bytes<32>` account identifier or a `ContractAddress` representing the owner.
+ The value is stored canonicalized (the inactive branch of the `Either` is zeroed).
+ A `Bytes<32>` value of zero means the contract is unowned.
+
+
+### Witnesses [toc] [#Ownable-Witnesses]
+### Witnesses [!toc] [#Ownable-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_OwnableSK [toc] [#Ownable-wit_OwnableSK]
+#### [!toc] [#Ownable-wit_OwnableSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#Ownable-Circuits]
+### Circuits [!toc] [#Ownable-Circuits]
+
+#### initialize [toc] [#Ownable-initialize]
+#### [!toc] [#Ownable-initialize]
+
+ Initializes the contract by setting the `initialOwner`.
+ This must be called in the contract's constructor.
+
+ Requirements:
+
+ * Contract is not already initialized.
+ * `initialOwner` is not a ContractAddress.
+ * `initialOwner` is not the zero address.
+
+
+#### owner [toc] [#Ownable-owner]
+#### [!toc] [#Ownable-owner]
+
+ Returns the current contract owner.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transferOwnership [toc] [#Ownable-transferOwnership]
+#### [!toc] [#Ownable-transferOwnership]
+
+ Transfers ownership of the contract to `newOwner`.
+
+
+ Ownership transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact.
+ This restriction prevents permanently disabling access to a circuit.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+ * `newOwner` is not a ContractAddress.
+ * `newOwner` is not the zero address.
+
+
+#### _unsafeTransferOwnership [toc] [#Ownable-_unsafeTransferOwnership]
+#### [!toc] [#Ownable-_unsafeTransferOwnership]
+
+ Unsafe variant of [transferOwnership](#Ownable-transferOwnership).
+
+
+ Ownership transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported.
+ Ownership privileges sent to a contract address may become uncallable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+ * `newOwner` is not the zero address.
+
+
+#### renounceOwnership [toc] [#Ownable-renounceOwnership]
+#### [!toc] [#Ownable-renounceOwnership]
+
+ Leaves the contract without an owner.
+ It will not be possible to call [assertOnlyOwner](#Ownable-assertOnlyOwner) circuits anymore.
+ Can only be called by the current owner.
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+
+
+#### assertOnlyOwner [toc] [#Ownable-assertOnlyOwner]
+#### [!toc] [#Ownable-assertOnlyOwner]
+
+ Throws if called by any account other than the owner.
+ Use this to restrict access of specific circuits to the owner.
+
+ In the case of an external (non-contract) caller, the caller’s identity is
+ derived from the [wit_OwnableSK](#Ownable-wit_OwnableSK) witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+
+
+#### _transferOwnership [toc] [#Ownable-_transferOwnership]
+#### [!toc] [#Ownable-_transferOwnership]
+
+ Transfers ownership of the contract to `newOwner` without
+ enforcing permission checks on the caller.
+
+
+ Ownership transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact.
+ This restriction prevents circuits from being inadvertently locked in contracts.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `newOwner` is not a ContractAddress.
+
+
+#### _unsafeUncheckedTransferOwnership [toc] [#Ownable-_unsafeUncheckedTransferOwnership]
+#### [!toc] [#Ownable-_unsafeUncheckedTransferOwnership]
+
+ Unsafe variant of [_transferOwnership](#Ownable-_transferOwnership).
+
+
+ Ownership transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported.
+ Ownership privileges sent to a contract address may become uncallable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+---
+
+## ZOwnablePK [toc] [#ZOwnablePK]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK";
+```
+
+---
+
+### Ledger [toc] [#ZOwnablePK-Ledger]
+### Ledger [!toc] [#ZOwnablePK-Ledger]
+
+#### _isInitialized [toc] [#ZOwnablePK-_isInitialized]
+#### [!toc] [#ZOwnablePK-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _ownerCommitment [toc] [#ZOwnablePK-_ownerCommitment]
+#### [!toc] [#ZOwnablePK-_ownerCommitment]
+
+ Stores the current hashed commitment representing the owner.
+ This commitment is derived from the public identifier (e.g., `SHA256(pk, nonce)`),
+ the `instanceSalt`, the transfer `counter`, and a domain separator.
+
+ A commitment of `default>` (i.e., zero) indicates the contract is unowned.
+
+
+#### _counter [toc] [#ZOwnablePK-_counter]
+#### [!toc] [#ZOwnablePK-_counter]
+
+ Internal transfer counter used to prevent commitment reuse.
+
+ Increments by 1 on every successful ownership transfer. Combined with `id` and
+ `instanceSalt` to compute unique owner commitments over time.
+
+
+#### _instanceSalt [toc] [#ZOwnablePK-_instanceSalt]
+#### [!toc] [#ZOwnablePK-_instanceSalt]
+
+ A per-instance value provided at initialization used to namespace
+ commitments for this contract instance.
+
+ This salt prevents commitment collisions across contracts that might otherwise use
+ the same owner identifiers or domain parameters. It is immutable after initialization.
+
+
+### Witnesses [toc] [#ZOwnablePK-Witnesses]
+### Witnesses [!toc] [#ZOwnablePK-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_secretNonce [toc] [#ZOwnablePK-wit_secretNonce]
+#### [!toc] [#ZOwnablePK-wit_secretNonce]
+
+ A private per-user nonce used in deriving the shielded owner identifier.
+
+ Combined with the user's public key as `SHA256(pk, nonce)` to produce an obfuscated,
+ unlinkable identity commitment. Users are encouraged to rotate this value on ownership changes.
+
+
+### Circuits [toc] [#ZOwnablePK-Circuits]
+### Circuits [!toc] [#ZOwnablePK-Circuits]
+
+#### initialize [toc] [#ZOwnablePK-initialize]
+#### [!toc] [#ZOwnablePK-initialize]
+
+ Initializes the contract by setting the initial owner via `ownerId`
+ and storing the `instanceSalt` that acts as a privacy additive for preventing
+ duplicate commitments among other contracts implementing ZOwnablePK.
+
+
+ The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm.
+ Using any other algorithm will result in a permanent loss of contract access.
+
+
+ Requirements:
+
+ * Contract is not initialized.
+ * `ownerId` is not zero.
+ * `instanceSalt` is not zero.
+
+
+#### owner [toc] [#ZOwnablePK-owner]
+#### [!toc] [#ZOwnablePK-owner]
+
+ Returns the current commitment representing the contract owner.
+ The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transferOwnership [toc] [#ZOwnablePK-transferOwnership]
+#### [!toc] [#ZOwnablePK-transferOwnership]
+
+ Transfers ownership to `newOwnerId`.
+ `newOwnerId` must be precalculated and given to the current owner off chain.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller is the current owner.
+ * `newOwnerId` is not an empty array.
+
+
+#### renounceOwnership [toc] [#ZOwnablePK-renounceOwnership]
+#### [!toc] [#ZOwnablePK-renounceOwnership]
+
+ Leaves the contract without an owner.
+ It will not be possible to call [assertOnlyOwner](#ZOwnablePK-assertOnlyOwner) circuits anymore.
+ Can only be called by the current owner.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller is the current owner.
+
+
+#### assertOnlyOwner [toc] [#ZOwnablePK-assertOnlyOwner]
+#### [!toc] [#ZOwnablePK-assertOnlyOwner]
+
+ Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match
+ the stored owner commitment.
+ Use this to only allow the owner to call specific circuits.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller's id (`SHA256(pk, nonce)`) when used in [_computeOwnerCommitment](#ZOwnablePK-_computeOwnerCommitment) equals
+ the stored `_ownerCommitment`, thus verifying themselves as the owner.
+
+
+#### _computeOwnerCommitment [toc] [#ZOwnablePK-_computeOwnerCommitment]
+#### [!toc] [#ZOwnablePK-_computeOwnerCommitment]
+
+ Computes the owner commitment from the given `id` and `counter`.
+
+ ## Owner ID (`id`)
+ The `id` is expected to be computed off-chain as:
+ `id = SHA256(pk, nonce)`
+
+ * `pk`: The owner's public key.
+ * `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer.
+
+ ## Commitment Derivation
+ `commitment = SHA256(id, instanceSalt, counter, domain)`
+
+ * `id`: See above.
+ * `instanceSalt`: A unique per-deployment salt, stored during initialization.
+ This prevents commitment collisions across deployments.
+ * `counter`: Incremented with each ownership transfer, ensuring uniqueness
+ even with repeated `id` values. Cast to `Field` then `Bytes<32>` for hashing.
+ * `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent
+ hash collisions when extending the module or using similar commitment schemes.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _computeOwnerId [toc] [#ZOwnablePK-_computeOwnerId]
+#### [!toc] [#ZOwnablePK-_computeOwnerId]
+
+ Computes the unique identifier (`id`) of the owner from their
+ public key and a secret nonce.
+
+ ## ID Derivation
+ `id = SHA256(pk, nonce)`
+
+ * `pk`: The public key of the caller. This is passed explicitly to allow
+ for off-chain derivation, testing, or scenarios where the caller is
+ different from the subject of the computation.
+ We recommend using an Air-Gapped Public Key.
+ * `nonce`: A secret nonce tied to the identity. The generation strategy is
+ left to the user, offering different security/convenience trade-offs.
+
+ The result is a 32-byte commitment that uniquely identifies the owner.
+ This value is later used in owner commitment hashing,
+ and acts as a privacy-preserving alternative to a raw public key.
+
+
+ This module allows ownership to be tied to an identity commitment derived
+ from a public key and secret nonce.
+ While typically used with user public keys, this mechanism may also
+ support contract addresses as identifiers in future contract-to-contract
+ interactions. Both are treated as 32-byte values (`Bytes<32>`).
+
+
+ Requirements:
+
+ * `pk` is not a ContractAddress.
+
+
+#### _transferOwnership [toc] [#ZOwnablePK-_transferOwnership]
+#### [!toc] [#ZOwnablePK-_transferOwnership]
+
+ Transfers ownership to owner id `newOwnerId` without
+ enforcing permission checks on the caller.
+
+ Requirements:
+
+ * Contract is initialized.
+
diff --git a/content/contracts-compact/0.3.x/api/security.mdx b/content/contracts-compact/0.3.x/api/security.mdx
new file mode 100644
index 00000000..7321d7e0
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/security.mdx
@@ -0,0 +1,386 @@
+---
+title: Security
+---
+
+This package provides the API for all Security modules.
+
+
+ For an overview of the module, read the [Security guide](../security).
+
+
+The page covers the following modules:
+
+- [Initializable](#Initializable): a one-time initialization guard.
+- [Pausable](#Pausable): an emergency stop mechanism.
+- [Allowlist](#Allowlist): a permit-list keyed by account identifier.
+- [Blocklist](#Blocklist): a deny-list keyed by account identifier.
+
+## Initializable [toc] [#Initializable]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Initializable";
+```
+
+
+ Use this module only at the top contract layer, and import it into at most one module per contract.
+ When two modules in the same directory both import Initializable, the compiler collapses their
+ `_isInitialized` flags into a single shared ledger slot
+ ([compact#270](https://github.com/LFDT-Minokawa/compact/issues/270)): initializing one marks the other
+ as initialized. The OpenZeppelin modules do not import this module; each tracks its own `_isInitialized` flag inline.
+
+
+---
+
+### Ledger [toc] [#Initializable-Ledger]
+### Ledger [!toc] [#Initializable-Ledger]
+
+#### _isInitialized [toc] [#Initializable-_isInitialized]
+#### [!toc] [#Initializable-_isInitialized]
+
+ Boolean indicating if initialized.
+
+
+### Witnesses [toc] [#Initializable-Witnesses]
+### Witnesses [!toc] [#Initializable-Witnesses]
+
+None.
+
+### Circuits [toc] [#Initializable-Circuits]
+### Circuits [!toc] [#Initializable-Circuits]
+
+#### initialize [toc] [#Initializable-initialize]
+#### [!toc] [#Initializable-initialize]
+
+ Initializes the state thus ensuring the calling circuit can only be called once.
+
+ Requirements:
+
+ * Contract must not be initialized.
+
+
+#### assertInitialized [toc] [#Initializable-assertInitialized]
+#### [!toc] [#Initializable-assertInitialized]
+
+ Asserts that the contract has been initialized, throwing an error if not.
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+#### assertNotInitialized [toc] [#Initializable-assertNotInitialized]
+#### [!toc] [#Initializable-assertNotInitialized]
+
+ Asserts that the contract has not been initialized, throwing an error if it has.
+
+ Requirements:
+
+ * Contract must not be initialized.
+
+
+---
+
+## Pausable [toc] [#Pausable]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable";
+```
+
+---
+
+### Ledger [toc] [#Pausable-Ledger]
+### Ledger [!toc] [#Pausable-Ledger]
+
+#### _isPaused [toc] [#Pausable-_isPaused]
+#### [!toc] [#Pausable-_isPaused]
+
+ Boolean indicating if paused.
+
+
+### Witnesses [toc] [#Pausable-Witnesses]
+### Witnesses [!toc] [#Pausable-Witnesses]
+
+None.
+
+### Circuits [toc] [#Pausable-Circuits]
+### Circuits [!toc] [#Pausable-Circuits]
+
+#### isPaused [toc] [#Pausable-isPaused]
+#### [!toc] [#Pausable-isPaused]
+
+ Returns true if the contract is paused, and false otherwise.
+
+
+#### assertPaused [toc] [#Pausable-assertPaused]
+#### [!toc] [#Pausable-assertPaused]
+
+ Makes a circuit only callable when the contract is paused.
+
+ Requirements:
+
+ * Contract must be paused.
+
+
+#### assertNotPaused [toc] [#Pausable-assertNotPaused]
+#### [!toc] [#Pausable-assertNotPaused]
+
+ Makes a circuit only callable when the contract is not paused.
+
+ Requirements:
+
+ * Contract must not be paused.
+
+
+#### _pause [toc] [#Pausable-_pause]
+#### [!toc] [#Pausable-_pause]
+
+ Triggers a stopped state.
+
+ Requirements:
+
+ * Contract must not be paused.
+
+
+#### _unpause [toc] [#Pausable-_unpause]
+#### [!toc] [#Pausable-_unpause]
+
+ Lifts the pause on the contract.
+
+ Requirements:
+
+ * Contract must be paused.
+
+
+---
+
+## Allowlist [toc] [#Allowlist]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Allowlist";
+```
+
+A minimal permit-list keyed by a `Bytes<32>` account identifier.
+The mutators `_allow` and `_disallow` are ungated: the implementing contract must protect them with its own access control
+and decides where `assertAllowed` applies.
+Membership is public and the approved set is publicly enumerable.
+
+---
+
+### Ledger [toc] [#Allowlist-Ledger]
+### Ledger [!toc] [#Allowlist-Ledger]
+
+#### _allowed [toc] [#Allowlist-_allowed]
+#### [!toc] [#Allowlist-_allowed]
+
+ Approved accounts. Absent means not allowed.
+
+
+### Witnesses [toc] [#Allowlist-Witnesses]
+### Witnesses [!toc] [#Allowlist-Witnesses]
+
+None.
+
+### Circuits [toc] [#Allowlist-Circuits]
+### Circuits [!toc] [#Allowlist-Circuits]
+
+#### isAllowed [toc] [#Allowlist-isAllowed]
+#### [!toc] [#Allowlist-isAllowed]
+
+ Returns whether `account` is currently allowed.
+
+
+#### assertAllowed [toc] [#Allowlist-assertAllowed]
+#### [!toc] [#Allowlist-assertAllowed]
+
+ Asserts that `account` is allowed. The composing contract
+ decides when this gate applies.
+
+ Requirements:
+
+ * `account` is a member of the allowlist.
+
+
+#### _allow [toc] [#Allowlist-_allow]
+#### [!toc] [#Allowlist-_allow]
+
+ Adds `account` to the allowlist.
+
+
+#### _disallow [toc] [#Allowlist-_disallow]
+#### [!toc] [#Allowlist-_disallow]
+
+ Removes `account` from the allowlist.
+
+
+---
+
+## Blocklist [toc] [#Blocklist]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Blocklist";
+```
+
+A minimal deny-list keyed by a `Bytes<32>` account identifier.
+The mutators `_block` and `_unblock` are ungated: the implementing contract must protect them with its own access control
+and decides where `assertNotBlocked` applies.
+Membership is public and the blocked set is publicly enumerable.
+
+---
+
+### Ledger [toc] [#Blocklist-Ledger]
+### Ledger [!toc] [#Blocklist-Ledger]
+
+#### _blocked [toc] [#Blocklist-_blocked]
+#### [!toc] [#Blocklist-_blocked]
+
+ Blocked accounts. Absent means not blocked.
+
+
+### Witnesses [toc] [#Blocklist-Witnesses]
+### Witnesses [!toc] [#Blocklist-Witnesses]
+
+None.
+
+### Circuits [toc] [#Blocklist-Circuits]
+### Circuits [!toc] [#Blocklist-Circuits]
+
+#### isBlocked [toc] [#Blocklist-isBlocked]
+#### [!toc] [#Blocklist-isBlocked]
+
+ Returns whether `account` is currently blocked.
+
+
+#### assertNotBlocked [toc] [#Blocklist-assertNotBlocked]
+#### [!toc] [#Blocklist-assertNotBlocked]
+
+ Asserts that `account` is not blocked. The composing contract
+ decides when this gate applies.
+
+ Requirements:
+
+ * `account` is not a member of the blocklist.
+
+
+#### _block [toc] [#Blocklist-_block]
+#### [!toc] [#Blocklist-_block]
+
+ Adds `account` to the blocklist.
+
+
+#### _unblock [toc] [#Blocklist-_unblock]
+#### [!toc] [#Blocklist-_unblock]
+
+ Removes `account` from the blocklist.
+
diff --git a/content/contracts-compact/0.3.x/api/shielded-access-control.mdx b/content/contracts-compact/0.3.x/api/shielded-access-control.mdx
new file mode 100644
index 00000000..f17fdce3
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/shielded-access-control.mdx
@@ -0,0 +1,613 @@
+---
+title: ShieldedAccessControl API
+---
+
+This page provides the full ShieldedAccessControl module API.
+
+This module provides a shielded role-based access control (RBAC) mechanism,
+where roles can be used to represent a set of permissions.
+Roles are stored as Merkle tree commitments to avoid disclosing information about role holders.
+Role commitments are created with the following hashing scheme,
+where `||` denotes concatenation and all values are `Bytes<32>`:
+
+```
+roleCommitment := SHA256( role || accountId || instanceSalt || commitmentDomain )
+
+accountId := SHA256( secretKey || instanceSalt || accountIdDomain )
+
+roleNullifier := SHA256( roleCommitment || nullifierDomain )
+
+commitmentDomain := pad(32, "ShieldedAccessControl:commitment")
+accountIdDomain := pad(32, "ShieldedAccessControl:accountId")
+nullifierDomain := pad(32, "ShieldedAccessControl:nullifier")
+```
+
+In this RBAC model, role commitments behave like private bearer tokens.
+Possession of a valid, non-revoked role commitment grants authorization.
+Revocation permanently burns the role instance, requiring explicit new issuance under a new account identifier.
+Users must rotate their secret key to generate a new `accountId` to be re-authorized.
+
+A single `secretKey` may be used for all roles within a contract instance.
+This is safe because the `role` identifier is mixed into the commitment (not the `accountId`),
+so different roles produce different commitments and different nullifiers even when the underlying `accountId` is the same.
+
+Roles are referred to by their `Bytes<32>` identifier.
+These should be exposed in the top-level contract and be unique.
+The best way to achieve this is by using `export sealed ledger` hash digests that are initialized in the top-level contract:
+
+```typescript
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl"
+ prefix ShieldedAccessControl_;
+
+export sealed ledger MY_ROLE: Bytes<32>;
+
+constructor(instanceSalt: Bytes<32>, defaultAdmin: ShieldedAccessControl_AccountIdentifier) {
+ MY_ROLE = persistentHash>(pad(32, "MY_ROLE"));
+ ShieldedAccessControl_initialize(instanceSalt);
+ ShieldedAccessControl__grantRole(ShieldedAccessControl_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+}
+```
+
+To restrict access to a circuit, use [assertOnlyRole](#ShieldedAccessControl-assertOnlyRole):
+
+```typescript
+circuit foo(): [] {
+ ShieldedAccessControl_assertOnlyRole(MY_ROLE as ShieldedAccessControl_RoleIdentifier);
+}
+```
+
+Roles can be granted and revoked dynamically via the [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole) circuits.
+Each role has an associated admin role,
+and only accounts that have a role's admin role can call [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole).
+
+By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`,
+which means that only accounts with this role will be able to grant or revoke other roles.
+More complex role relationships can be created by using [_setRoleAdmin](#ShieldedAccessControl-_setRoleAdmin).
+
+
+ The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role.
+ Extra precautions should be taken to secure accounts that have been granted it.
+
+
+
+ For an overview of the module, read the [ShieldedAccessControl guide](../shielded-access-control).
+
+
+## ShieldedAccessControl [toc] [#ShieldedAccessControl]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl";
+```
+
+---
+
+### Types [toc] [#ShieldedAccessControl-Types]
+### Types [!toc] [#ShieldedAccessControl-Types]
+
+#### UpdateType [toc] [#ShieldedAccessControl-UpdateType]
+#### [!toc] [#ShieldedAccessControl-UpdateType]
+
+ Enum indicating whether a role update is a grant or a revocation.
+
+
+#### RoleCommitment [toc] [#ShieldedAccessControl-RoleCommitment]
+#### [!toc] [#ShieldedAccessControl-RoleCommitment]
+
+ A Merkle tree leaf committing a `(role, accountId)` pairing. Computed as `SHA256(role || accountId || instanceSalt || commitmentDomain)`.
+
+
+#### RoleIdentifier [toc] [#ShieldedAccessControl-RoleIdentifier]
+#### [!toc] [#ShieldedAccessControl-RoleIdentifier]
+
+ A unique identifier for a role.
+
+
+#### AccountIdentifier [toc] [#ShieldedAccessControl-AccountIdentifier]
+#### [!toc] [#ShieldedAccessControl-AccountIdentifier]
+
+ A privacy-preserving identity commitment. Computed as `SHA256(secretKey || instanceSalt || accountIdDomain)`.
+
+
+#### RoleNullifier [toc] [#ShieldedAccessControl-RoleNullifier]
+#### [!toc] [#ShieldedAccessControl-RoleNullifier]
+
+ A one-time burn token that permanently invalidates a role commitment on revocation. Computed as `SHA256(roleCommitment || nullifierDomain)`.
+
+
+### Ledger [toc] [#ShieldedAccessControl-Ledger]
+### Ledger [!toc] [#ShieldedAccessControl-Ledger]
+
+#### _isInitialized [toc] [#ShieldedAccessControl-_isInitialized]
+#### [!toc] [#ShieldedAccessControl-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _operatorRoles [toc] [#ShieldedAccessControl-_operatorRoles]
+#### [!toc] [#ShieldedAccessControl-_operatorRoles]
+
+ A Merkle tree of role commitments stored as SHA256(role | accountId | instanceSalt | commitmentDomain)
+ Role commitments are derived from a public role identifier (e.g., `persistentHash>(pad(32, "MY_ROLE")`),
+ an account identifier (e.g., `SHA256(secretKey, instanceSalt, accountIdDomain)`), the `instanceSalt`, and a domain separator.
+
+
+#### _adminRoles [toc] [#ShieldedAccessControl-_adminRoles]
+#### [!toc] [#ShieldedAccessControl-_adminRoles]
+
+ Mapping from a role identifier to an admin role identifier.
+
+
+#### _roleCommitmentNullifiers [toc] [#ShieldedAccessControl-_roleCommitmentNullifiers]
+#### [!toc] [#ShieldedAccessControl-_roleCommitmentNullifiers]
+
+ A set of nullifiers used to prove a role has been revoked
+
+
+#### _grantedRoleNullifiers [toc] [#ShieldedAccessControl-_grantedRoleNullifiers]
+#### [!toc] [#ShieldedAccessControl-_grantedRoleNullifiers]
+
+ Nullifiers of every pairing ever granted. Makes a repeated grant a no-op so
+ one pairing can never consume more than one `_operatorRoles` slot, and gates revocation
+ so a nullifier can only exist for a granted pairing.
+
+
+#### _instanceSalt [toc] [#ShieldedAccessControl-_instanceSalt]
+#### [!toc] [#ShieldedAccessControl-_instanceSalt]
+
+ A per-instance value provided at initialization used to namespace
+ commitments for this contract instance.
+
+ This salt prevents commitment collisions across contracts that might otherwise use
+ the same identifiers or domain parameters. It should be a cryptographically strong random value.
+ If two deployments share the same instanceSalt and a user reuses their secretKey across both,
+ the resulting accountId and all role commitments will be identical in both contracts.
+ An observer who sees the same nullifier appear in both contracts' nullifier sets can conclude
+ that it's the same user.
+ It is immutable after initialization.
+
+
+### Witnesses [toc] [#ShieldedAccessControl-Witnesses]
+### Witnesses [!toc] [#ShieldedAccessControl-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_getRoleCommitmentPath [toc] [#ShieldedAccessControl-wit_getRoleCommitmentPath]
+#### [!toc] [#ShieldedAccessControl-wit_getRoleCommitmentPath]
+
+ Returns a path to a role commitment in the `_operatorRoles` Merkle tree if one exists. Otherwise, returns an invalid path.
+
+
+#### wit_secretKey [toc] [#ShieldedAccessControl-wit_secretKey]
+#### [!toc] [#ShieldedAccessControl-wit_secretKey]
+
+ Returns the user's secret key used in deriving the shielded account identifier.
+
+ The same key can be used across multiple roles within a contract instance. If a role is
+ revoked and re-granted, a new secret key must be generated to produce a new `accountId`.
+
+
+### Circuits [toc] [#ShieldedAccessControl-Circuits]
+### Circuits [!toc] [#ShieldedAccessControl-Circuits]
+
+#### initialize [toc] [#ShieldedAccessControl-initialize]
+#### [!toc] [#ShieldedAccessControl-initialize]
+
+ Initializes the contract by storing the `instanceSalt` that acts as a privacy additive
+ for preventing duplicate commitments among other contracts implementing ShieldedAccessControl.
+
+
+ The `instanceSalt` must be calculated prior to contract deployment using a cryptographically
+ secure random number generator e.g. crypto.getRandomValues() to maintain strong privacy guarantees
+
+
+ Requirements:
+
+ * Contract is not initialized.
+
+
+#### DEFAULT_ADMIN_ROLE [toc] [#ShieldedAccessControl-DEFAULT_ADMIN_ROLE]
+#### [!toc] [#ShieldedAccessControl-DEFAULT_ADMIN_ROLE]
+
+ The default admin role for all roles. Only accounts with this role will be able to grant or revoke other roles
+ unless custom admin roles are created.
+
+ The Compact language does not support constant declarations,
+ so DEFAULT_ADMIN_ROLE is implemented as a circuit that returns a constant value by necessity.
+
+
+#### assertOnlyRole [toc] [#ShieldedAccessControl-assertOnlyRole]
+#### [!toc] [#ShieldedAccessControl-assertOnlyRole]
+
+ Reverts if caller cannot provide a valid proof of ownership for `role`.
+
+ Requirements:
+
+ * caller must prove ownership of `role`.
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+
+
+#### canProveRole [toc] [#ShieldedAccessControl-canProveRole]
+#### [!toc] [#ShieldedAccessControl-canProveRole]
+
+ Returns `true` if a caller proves ownership of `role` and is not revoked. MAY return false for a legitimately credentialed
+ caller if the proving environment supplies an invalid Merkle path. This circuit will never return true for an
+ unauthorized caller.
+
+ Requirements:
+
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+
+
+#### grantRole [toc] [#ShieldedAccessControl-grantRole]
+#### [!toc] [#ShieldedAccessControl-grantRole]
+
+ Grants `role` to `accountId` by inserting a role commitment unique to the
+ `(role, accountId)` pairing into the `_operatorRoles` Merkle tree. Granting an already active
+ pairing is a no-op. Once revoked, a role cannot be re-granted to the same `accountId`. A new
+ `accountId` must be generated to be re-authorized for a revoked `role`.
+
+ Requirements:
+
+ * caller must prove they're an admin for `role`.
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+ * A role identifier.
+
+
+#### revokeRole [toc] [#ShieldedAccessControl-revokeRole]
+#### [!toc] [#ShieldedAccessControl-revokeRole]
+
+ Permanently revokes `role` from `accountId` by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
+ `role`.
+
+ Requirements:
+
+ * caller must prove they're an admin for `role`.
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * The `(role, accountId)` pairing must have been granted.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+ * A role identifier.
+
+
+#### renounceRole [toc] [#ShieldedAccessControl-renounceRole]
+#### [!toc] [#ShieldedAccessControl-renounceRole]
+
+ Revokes `role` from the calling account by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be
+ re-authorized for `role`.
+
+
+ Roles are often managed via [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole): this circuit's
+ purpose is to provide a mechanism for accounts to lose their privileges
+ if they are compromised (such as when a trusted device is misplaced).
+ Consumers with no need for self-revocation should not export this circuit.
+
+
+
+ Outside observers may be able to use timing and pattern analysis to weaken pseudonymity
+ guarantees if renounceRole is used in tandem with other on-chain actions.
+
+
+ Requirements:
+
+ * The caller must provide a valid `accountId` for the role.
+ * The `(role, accountId)` pairing must have been granted.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A nullifier for the respective role commitment.
+
+
+#### getRoleAdmin [toc] [#ShieldedAccessControl-getRoleAdmin]
+#### [!toc] [#ShieldedAccessControl-getRoleAdmin]
+
+ Returns the admin role that controls `role`. Returns [DEFAULT_ADMIN_ROLE](#ShieldedAccessControl-DEFAULT_ADMIN_ROLE) for
+ roles with no explicitly set admin. Since [DEFAULT_ADMIN_ROLE](#ShieldedAccessControl-DEFAULT_ADMIN_ROLE) is the zero byte array,
+ there is no distinction between a nonexistent `role` and one whose admin is [DEFAULT_ADMIN_ROLE](#ShieldedAccessControl-DEFAULT_ADMIN_ROLE).
+ See [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole).
+
+ To change a role's admin use [_setRoleAdmin](#ShieldedAccessControl-_setRoleAdmin).
+
+ Disclosures:
+
+ * A role identifier.
+
+
+#### _grantRole [toc] [#ShieldedAccessControl-_grantRole]
+#### [!toc] [#ShieldedAccessControl-_grantRole]
+
+ Grants `role` to `accountId` by inserting a role commitment unique to the
+ `(role, accountId)` pairing into the `_operatorRoles` Merkle tree. Granting an already active
+ pairing is a no-op. Once revoked, a role cannot be re-granted to the same `accountId`. A new
+ `accountId` must be generated to be re-authorized for a revoked `role`.
+
+ Internal circuit without access restriction.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to grant
+ roles without authorization. It must be wrapped with appropriate access control.
+
+
+
+ The `_operatorRoles` Merkle tree has a fixed capacity of 2^20 leaf slots, one per
+ distinct pairing. Deployers should monitor slot consumption off-chain.
+
+
+ Requirements:
+
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+
+
+#### _revokeRole [toc] [#ShieldedAccessControl-_revokeRole]
+#### [!toc] [#ShieldedAccessControl-_revokeRole]
+
+ Permanently revokes `role` from `accountId` by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
+ `role`.
+
+ Internal circuit without access restriction.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to revoke
+ roles without authorization. It must be wrapped with appropriate access control.
+
+
+ Requirements:
+
+ * The `(role, accountId)` pairing must have been granted.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A nullifier for the respective role commitment.
+
+
+#### _setRoleAdmin [toc] [#ShieldedAccessControl-_setRoleAdmin]
+#### [!toc] [#ShieldedAccessControl-_setRoleAdmin]
+
+ Sets `adminId` as `role`'s admin identifier. Users with valid admin identifiers
+ may grant and revoke access to the specified `role`. Internal circuit without access restriction.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to assign
+ arbitrary admin roles without authorization. It must be wrapped with appropriate access control.
+
+ Disclosures:
+
+ * The role identifier
+ * The admin identifier
+
+
+
+#### computeRoleCommitment [toc] [#ShieldedAccessControl-computeRoleCommitment]
+#### [!toc] [#ShieldedAccessControl-computeRoleCommitment]
+
+ Computes the role commitment from the given `accountId` and `role`.
+
+
+ This circuit does not perform an initialization check. It's only meant to be used as
+ an internal helper in the Shielded Access Control module. Using this circuit outside of the
+ module may cause undefined behavior and break security guarantees.
+
+ ## Account ID (`accountId`)
+ The `accountId` is expected to be computed as:
+ `accountId = SHA256(secretKey, instanceSalt, accountIdDomain)`
+
+ * `secretKey`: A 32-byte cryptographically secure random value.
+
+ ## Role Commitment Derivation
+ `roleCommitment = SHA256(role, accountId, instanceSalt, commitmentDomain)`
+
+ * `accountId`: See above.
+ * `role`: A unique role identifier.
+ * `instanceSalt`: A unique per-deployment salt, stored during initialization.
+ This prevents commitment collisions across deployments.
+ * `commitmentDomain`: Domain separator `"ShieldedAccessControl:commitment"` (padded to 32 bytes) to prevent
+ hash collisions when extending the module or using similar commitment schemes.
+
+
+
+#### computeNullifier [toc] [#ShieldedAccessControl-computeNullifier]
+#### [!toc] [#ShieldedAccessControl-computeNullifier]
+
+ Computes the role nullifier for a given `roleCommitment`.
+
+ ## Role Nullifier Derivation
+ `roleNullifier = SHA256(roleCommitment, nullifierDomain)`
+
+ * `roleCommitment`: See [computeRoleCommitment](#ShieldedAccessControl-computeRoleCommitment).
+ * `nullifierDomain`: Domain separator `"ShieldedAccessControl:nullifier"` (padded to 32 bytes) to prevent
+ hash collisions when extending the module or using similar commitment schemes.
+
+
+#### computeAccountId [toc] [#ShieldedAccessControl-computeAccountId]
+#### [!toc] [#ShieldedAccessControl-computeAccountId]
+
+ Computes an `accountId` without on-chain state, allowing a user to derive
+ their shielded identity commitment before submitting it in a grant or revoke operation.
+ This is the off-chain counterpart to `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+
+ OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
+ permanently compromise the privacy guarantees of this system:
+
+ * **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
+ application logs, analytics pipelines, or any observable side-channel.
+ * **Store offline or in secure enclaves** — hardware security modules (HSMs),
+ air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
+ * **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
+ or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
+ * **Treat key loss as identity loss** — a lost key cannot be recovered. Back up
+ keys securely before using them in role commitments.
+ * **Avoid calling this circuit in untrusted environments** — executing this in an
+ unverified browser extension, compromised runtime, or shared machine may expose
+ the key to a malicious observer.
+
+ ## ID Derivation
+ `accountId = SHA256(secretKey, instanceSalt, accountIdDomain)`
+
+ See `_computeAccountId` for further details.
+
+
diff --git a/content/contracts-compact/0.3.x/api/utils.mdx b/content/contracts-compact/0.3.x/api/utils.mdx
new file mode 100644
index 00000000..06957fb9
--- /dev/null
+++ b/content/contracts-compact/0.3.x/api/utils.mdx
@@ -0,0 +1,199 @@
+---
+title: Utils
+---
+
+This package provides the API for all Utils modules.
+
+
+ For an overview of the module, read the [Utils guide](../utils).
+
+
+## Utils [toc] [#Utils]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils";
+```
+
+---
+
+### Ledger [toc] [#Utils-Ledger]
+### Ledger [!toc] [#Utils-Ledger]
+
+None.
+
+### Witnesses [toc] [#Utils-Witnesses]
+### Witnesses [!toc] [#Utils-Witnesses]
+
+None.
+
+### Circuits [toc] [#Utils-Circuits]
+### Circuits [!toc] [#Utils-Circuits]
+
+#### isKeyOrAddressZero [toc] [#Utils-isKeyOrAddressZero]
+#### [!toc] [#Utils-isKeyOrAddressZero]
+
+ Returns whether `keyOrAddress` is the zero address.
+
+
+ Midnight's burn address is represented as left\(default\)
+ in Compact, so we've chosen to represent the zero address as this structure as well.
+
+
+
+#### isKeyZero [toc] [#Utils-isKeyZero]
+#### [!toc] [#Utils-isKeyZero]
+
+ Returns whether `key` is the zero address.
+
+
+#### isKeyOrAddressEqual [toc] [#Utils-isKeyOrAddressEqual]
+#### [!toc] [#Utils-isKeyOrAddressEqual]
+
+ Returns whether `keyOrAddress` is equal to `other`. Assumes that a ZswapCoinPublicKey
+ and a ContractAddress can never be equal
+
+
+#### isContractAddress [toc] [#Utils-isContractAddress]
+#### [!toc] [#Utils-isContractAddress]
+
+ Returns whether `keyOrAddress` is a ContractAddress type.
+
+
+#### emptyString [toc] [#Utils-emptyString]
+#### [!toc] [#Utils-emptyString]
+
+ A helper function that returns the empty string: "".
+
+
+#### canonicalize [toc] [#Utils-canonicalize]
+#### [!toc] [#Utils-canonicalize]
+
+ Zeroes out the unused side of an `Either` value. Prevents crafted
+ inputs where both `left` and `right` fields carry data from bypassing checks that
+ only inspect the active side.
+
+
+#### zeroAccount [toc] [#Utils-zeroAccount]
+#### [!toc] [#Utils-zeroAccount]
+
+ Returns the canonical zero `Either, ContractAddress>` value: the
+ left variant (`Bytes<32>`) set to zero. Used as the sentinel for mint/burn operations
+ (a zero `from` signals a mint, a zero `to` signals a burn) and for cleared approvals.
+
+ The left variant is chosen so that [isTargetZero](#Utils-isTargetZero) checks against `default>`,
+ and to avoid triggering contract-address guards (e.g. circuits that check `!to.is_left`
+ before delegating to an `_unsafe` variant). A right variant with `default`
+ would also pass [isTargetZero](#Utils-isTargetZero), but the left variant keeps guards and error messages consistent.
+
+
+#### isTargetZero [toc] [#Utils-isTargetZero]
+#### [!toc] [#Utils-isTargetZero]
+
+ Returns `true` if `target`'s active branch (as indicated by `is_left`)
+ holds the zero value.
+
+
+#### computeAccountId [toc] [#Utils-computeAccountId]
+#### [!toc] [#Utils-computeAccountId]
+
+ Computes an account identifier without on-chain state, allowing a user to derive
+ their identity commitment before submitting an operation.
+
+
+ OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
+ permanently compromise the security of this system:
+
+ * **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
+ application logs, analytics pipelines, or any observable side-channel.
+ * **Store offline or in secure enclaves** — hardware security modules (HSMs),
+ air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
+ * **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
+ or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
+ * **Treat key loss as identity loss** — a lost key cannot be recovered.
+ * **Avoid calling this circuit in untrusted environments** — executing this in an
+ unverified browser extension, compromised runtime, or shared machine may expose
+ the key to a malicious observer.
+
+ ## ID Derivation
+ `accountId = persistentHash(secretKey)`
+
+
+ The absence of a domain-separation tag is DELIBERATE. This identifier
+ is global by design: the same key material yields the same identity in
+ every module deriving one, so a user who wishes to carry one identity
+ across modules can. Modules wanting a per-deployment, unlinkable identity
+ should not use this circuit.
+
+ NOT FOR PRIVATE DERIVATION. This returns an identifier, not a secret,
+ and the construction is untagged. A circuit deriving a PRIVATE value from a
+ secret MUST use its own domain-separation tag.
+
+
+#### selfAsRecipient [toc] [#Utils-selfAsRecipient]
+#### [!toc] [#Utils-selfAsRecipient]
+
+ Returns the current contract's address as an
+ `Either` for use as a
+ recipient in shielded send operations (deposits and receiving change).
+
+
+#### UINT128_MAX [toc] [#Utils-UINT128_MAX]
+#### [!toc] [#Utils-UINT128_MAX]
+
+ The maximum value representable by a `Uint<128>`.
+
+
+ TODO: Remove once a math module providing this constant is available.
+
+
diff --git a/content/contracts-compact/0.3.x/confidential-fungible-token.mdx b/content/contracts-compact/0.3.x/confidential-fungible-token.mdx
new file mode 100644
index 00000000..0c2b324e
--- /dev/null
+++ b/content/contracts-compact/0.3.x/confidential-fungible-token.mdx
@@ -0,0 +1,407 @@
+---
+title: ConfidentialFungibleToken
+---
+
+{/* links */}
+[FungibleToken]: ./fungible-token.mdx
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[Ownable]: ./ownable.mdx
+[crypto guide]: ./crypto
+[ElGamal]: api/crypto#ElGamal
+[EcdhMask]: api/crypto#EcdhMask
+[ConfidentialFungibleToken API]: api/confidential-fungible-token
+[EscrowEntry]: api/confidential-fungible-token#ConfidentialFungibleToken-EscrowEntry
+[wit_ConfidentialTokenSK]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_ConfidentialTokenSK
+[wit_ConfidentialTokenEK]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_ConfidentialTokenEK
+[wit_PlaintextBalance]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_PlaintextBalance
+[wit_RandomnessSeed]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_RandomnessSeed
+[MAX_TRANSFER_VALUE]: api/confidential-fungible-token#ConfidentialFungibleToken-MAX_TRANSFER_VALUE
+[initialize]: api/confidential-fungible-token#ConfidentialFungibleToken-initialize
+[balanceOf]: api/confidential-fungible-token#ConfidentialFungibleToken-balanceOf
+[pendingOf]: api/confidential-fungible-token#ConfidentialFungibleToken-pendingOf
+[allowance]: api/confidential-fungible-token#ConfidentialFungibleToken-allowance
+[isRegistered]: api/confidential-fungible-token#ConfidentialFungibleToken-isRegistered
+[register]: api/confidential-fungible-token#ConfidentialFungibleToken-register
+[_mint]: api/confidential-fungible-token#ConfidentialFungibleToken-_mint
+[_burn]: api/confidential-fungible-token#ConfidentialFungibleToken-_burn
+[_burnFrom]: api/confidential-fungible-token#ConfidentialFungibleToken-_burnFrom
+[transfer]: api/confidential-fungible-token#ConfidentialFungibleToken-transfer
+[_move]: api/confidential-fungible-token#ConfidentialFungibleToken-_move
+[sweep]: api/confidential-fungible-token#ConfidentialFungibleToken-sweep
+[approve]: api/confidential-fungible-token#ConfidentialFungibleToken-approve
+[transferFrom]: api/confidential-fungible-token#ConfidentialFungibleToken-transferFrom
+[clearMemos]: api/confidential-fungible-token#ConfidentialFungibleToken-clearMemos
+[computeAccountId]: api/confidential-fungible-token#ConfidentialFungibleToken-computeAccountId
+[ConfidentialFungibleTokenPublicSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply
+[_addSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply-_addSupply
+[_subSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply-_subSupply
+[totalSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply-totalSupply
+
+ConfidentialFungibleToken is an account-based fungible token whose balances live on the public ledger
+as [ElGamal] ciphertexts over the Jubjub curve, so no amount is ever written in the clear.
+Accounts are identified the same way as in [FungibleToken], by a 32-byte `accountId` derived from a secret key,
+but every value a circuit moves is encrypted under the account holder's own encryption key,
+and the caller proves in zero knowledge that it knows the plaintext of the balance it spends from.
+Transfers hide the amount; the (sender, recipient) pair is public.
+Total-supply tracking is optional and lives in the separate [ConfidentialFungibleTokenPublicSupply] extension.
+
+The encryption primitives (`ElGamal` and `EcdhMask`) are documented in the [crypto guide].
+This page covers how the token composes them.
+
+## The account model
+
+### What is on the public ledger
+
+Every ledger field of the module is exported, so an observer can read all of the following directly from state:
+
+* **Encryption keys** (`_encryptionKeys`): `accountId` to the account's ElGamal public key, published by [register].
+* **Spendable balances** (`_balances`): one ElGamal ciphertext per registered account.
+ Debits (transfers out, approvals, burns) draw from this cell.
+* **Pending balances** (`_pending`): a second ciphertext per account where incoming credits land.
+ Only the owner moves pending into spendable, with [sweep].
+* **Memos** (`_memos`): per account, a list of [EcdhMask] ciphertexts, one per credit received.
+ Each one carries the credited amount encrypted to the recipient's key; this is how a recipient learns what it was sent.
+* **Escrow entries** (`_escrow`): per (owner, spender), an [EscrowEntry] holding the encrypted allowance.
+* **Credit counters** (`_creditEpochs`): per account, the lifetime number of credits received. It never resets.
+* **Escrow-spend counters** (`_escrowSpendEpochs`): per (owner, spender), the number of times the escrow was drawn on. It never resets.
+* **Metadata** (`_name`, `_symbol`, `_decimals`) and the `_isInitialized` flag.
+
+The module keeps no nullifier sets; the two counters above are the only monotonic state it writes,
+and both exist to keep per-operation randomness distinct (see below), not to prevent replay.
+
+What an observer learns from this is the set of registered `accountId`s, which accounts transact with which,
+how many credits each account has received, and how often each escrow has been spent from.
+What an observer does not learn is any balance, any transferred amount, or any allowance cap,
+unless the deployment also tracks public supply (see [Minting, burning and supply](#minting-burning-and-supply)).
+
+### What the user holds privately
+
+Four witnesses feed the circuits. Each is listed with how (or whether) the circuit verifies it.
+
+* [wit_ConfidentialTokenSK] returns the **account secret**. The circuit computes `accountId = H(SK)`
+ and uses that id for every ledger lookup, so a wrong secret resolves to a different account and the call fails.
+* [wit_ConfidentialTokenEK] returns the **encryption secret**. The circuit re-derives the ElGamal public key from it
+ and compares against the key stored at registration, either through the balance proof or directly on
+ [sweep] and [clearMemos], which have no plaintext to prove.
+* [wit_PlaintextBalance] returns the wallet's **cached plaintext** for a given ciphertext.
+ The ciphertext is passed in as the argument so the wallet can route by ciphertext identity rather than by call order.
+ The circuit verifies the claim with `ElGamal_assertDecryptsTo`; a wrong value fails the proof.
+* [wit_RandomnessSeed] returns a 32-byte **randomness seed** from which every per-operation random scalar
+ and every memo ephemeral is expanded in-circuit. Unlike the other three, this witness is **not** verified.
+
+The same key material may back both the account secret and the encryption secret, or both may be derived from one master secret:
+the account id is an untagged hash of the SK, while the encryption scalar is a domain-separated hash of the EK,
+so publishing the id does not reveal the encryption key.
+
+
+ Seed freshness is load-bearing for confidentiality on every path. The seed-to-randomness expansion is deterministic
+ and public, so anyone who knows a seed can strip the memo mask and read the delivered amount, and can recover a balance
+ or escrow amount from its public ciphertext. Reusing a seed across two operations leaks the difference between their amounts,
+ and the circuit does not prevent reuse. A wallet must supply a fresh, high-entropy, secret seed on every invocation.
+ On `transferFrom` and `burnFrom` the seed is the spender's but the ciphertexts it randomizes belong to the owner,
+ so a careless spender leaks a counterparty's balance, not their own.
+
+
+The TypeScript witnesses in the repository are test-only and are not published with the package;
+in particular the test seed witness returns a fixed value. A consumer authors its own witnesses,
+backed by the user's wallet, and the seed witness must draw from a cryptographically secure random source.
+
+### Balance claims and the per-operation bound
+
+Every value-bearing circuit takes a `Uint<128>` amount and checks it against [MAX_TRANSFER_VALUE].
+A balance, however, is the homomorphic sum of every credit it has ever received and has no running total to bound it,
+so the plaintext a wallet claims through [wit_PlaintextBalance] is a `Uint<248>`, wider than any single amount.
+A balance accumulated past the per-operation bound therefore stays spendable.
+Escrow claims are narrowed back to `Uint<128>`, since an allowance is set by one `approve` and only ever reduced.
+
+## Registration
+
+[register] derives the caller's ElGamal public key from the encryption secret, stores it under the caller's `accountId`,
+and initializes both the spendable and the pending balance to an encryption of zero.
+It discloses the `accountId` and the public key. Registration is required before an account can send **or** receive:
+a credit needs the recipient's public key to encrypt under.
+
+Before any on-chain call, a wallet derives its own id with the pure [computeAccountId] circuit (no proof needed):
+
+```ts
+const accountId = Token_computeAccountId(secretKey);
+```
+
+The id is global: the same secret produces the same `accountId` in every module that derives it this way
+(`Utils_computeAccountId` is the shared implementation).
+
+## Transfers, pending balances and memos
+
+[transfer] does three things in one proof:
+
+1. **Debit.** The caller proves the plaintext of its spendable balance, the circuit checks it is at least `value`,
+ and the balance ciphertext is replaced by a homomorphic subtraction of `value` under fresh randomness.
+2. **Credit.** The recipient's *pending* ciphertext is replaced by a homomorphic addition of `value`.
+ The recipient's credit counter is incremented and folded into the randomness, so two credits never share randomness
+ even if a sender reuses a seed.
+3. **Memo.** An [EcdhMask] ciphertext carrying `value`, encrypted to the recipient's public key, is pushed to the front of
+ the recipient's memo list.
+
+What reaches the ledger: the sender's `accountId`, the recipient's `accountId`, two new ciphertexts and one memo.
+The amount is not disclosed. Self-transfer is rejected.
+
+### How a recipient learns of incoming funds
+
+The on-chain balance ciphertext is exponential ElGamal (it encrypts `g^value`), so a wallet cannot cheaply decrypt it.
+Instead the wallet scans its own `_memos` list off-chain, decrypts each new entry directly with its encryption secret
+(no discrete-log search and no bounded-value table), and adds the amounts to a local plaintext cache.
+
+Memos record credits only. Debits (sends, approvals, burns) move value with no memo, so summing memos overcounts an account
+that has ever spent. The wallet's running cache, not memo replay, is the authoritative balance,
+and it is what the wallet returns from [wit_PlaintextBalance].
+
+### Sweeping pending into spendable
+
+Credits land in `_pending`, not in `_balances`, so a third party cannot change the ciphertext an owner is about to prove against.
+Without this split an attacker could send dust to invalidate a victim's in-flight spend proofs.
+The owner calls [sweep] when ready: it homomorphically adds the two ciphertexts, resets pending to an encryption of zero,
+and needs no plaintext (the wallet already learned the pending amounts from its memos).
+Only the owner can sweep, since the circuit checks both the account secret and the encryption secret.
+A wallet's total holding is `balanceOf + pendingOf`.
+
+### The conserving primitive
+
+[_move] is [transfer] without the self-transfer guard: it debits the caller and credits `to` by the same amount, net zero,
+and never changes supply. It is the building block for custom value flows (fees, splits, settlement),
+and it stays permissive so a composing contract can set its own policy.
+
+### Pruning memos
+
+A wallet that has folded its memos into a durable cache can call [clearMemos] to empty its list on chain.
+The call takes the caller's view of its credit counter and is rejected if a credit landed in between,
+so a memo the wallet has not yet seen is never discarded.
+
+
+ Clearing memos is destructive. Spending requires proving the balance plaintext, and the plaintext can only be
+ reconstructed from the memos or from the wallet's cache. Clear only memos already folded into a durable, backed-up cache;
+ otherwise the balance becomes unspendable, not merely undisplayable.
+
+
+## Approvals and escrow
+
+An allowance is not a public cap. [approve] moves `value` out of the caller's spendable balance into an escrow entry
+that holds two encryptions of the same amount, one under the spender's key and one under the owner's,
+plus an owner-readable memo of the current remaining allowance:
+
+```
+EscrowEntry { spenderCt, ownerCt, ownerMemo }
+```
+
+Re-approving first refunds any prior escrow for that spender back to the owner's balance (a homomorphic add of `ownerCt`,
+no plaintext needed) and then escrows the new amount, so allowances replace rather than stack.
+Revoking is `approve(spender, 0)`. What reaches the ledger: the owner's and spender's ids, the owner's new balance ciphertext,
+and the entry; the cap is not disclosed.
+
+[transferFrom] is called by the spender. It proves the plaintext of `spenderCt`, checks it is at least `value`,
+reduces **both** escrow copies by `value`, refreshes `ownerMemo` with the new remaining amount
+(computed in-circuit from the verified plaintext, so the spender cannot forge it), increments the (owner, spender) spend counter,
+and credits `to` exactly as [transfer] does. [_burnFrom] is the same escrow spend with no credit.
+
+### What a partial spend leaves behind
+
+After a spender draws part of an allowance, the ledger holds a reduced `spenderCt` and `ownerCt` that still encrypt the same
+remaining amount, a fresh `ownerMemo` stating that remainder, and an incremented spend counter.
+The owner receives no balance memo (its spendable balance did not change) and instead decrypts `ownerMemo`
+to learn how much is left in escrow. That is what lets the owner prove its post-refund balance on the next `approve`,
+when the remainder flows back into `_balances`. Without the memo a partial spend would strand the escrow.
+
+[allowance] returns the entry, or a well-formed encrypted-zero entry when none exists. "No allowance" and "an allowance of zero"
+read the same through the getter; a caller that must distinguish them reads `_escrow` membership off chain.
+Granting nothing is safe because the spend path gates on escrow membership independently.
+
+## Minting, burning and supply
+
+The base module ships three supply-changing building blocks and no supply accounting:
+
+* [_mint] credits `value` to `account` (with a memo), exactly like the credit half of a transfer.
+* [_burn] debits `value` from the caller's spendable balance.
+* [_burnFrom] consumes `value` from an escrow the owner granted the caller.
+
+None of them touches a total. Pair them with [ConfidentialFungibleTokenPublicSupply] when the deployment tracks supply:
+its `_totalSupply` ledger field is a public `Uint<128>`, and [_addSupply] / [_subSupply] disclose each mint and burn amount
+through the delta on that value. Balances stay confidential; only the aggregate is visible. This is the right default
+for issuers who must attest to a circulating supply. The extension imports no token module, needs no initialization,
+and the pairing of each token operation with its accounting block is the composing contract's responsibility.
+
+
+ Never re-export `_mint`, `_burn`, or `_burnFrom` from a deployed contract. Compact cannot restrict a re-export,
+ so a raw `_mint` is a permissionless mint, a raw `_burn` a permissionless burn, and a raw `_burnFrom` destroys escrowed
+ value with no supply decrement. Expose only the conserving surface (`transfer`, `_move`, `approve`, `transferFrom`, `sweep`)
+ plus your own gated `mint` / `burn` / `burnFrom` wrappers.
+
+
+## Gating on the caller
+
+A composing contract that enforces policy on the caller (freeze lists, KYC, pause) cannot read the caller's identity itself:
+the caller is derived from a witness inside the module, and a second witness call in the wrapper could return a different secret.
+For that reason every caller-authenticating circuit ([register], [_burn], [transfer], [_move], [approve], [transferFrom],
+[sweep], [_burnFrom]) returns the `accountId` it authenticated. Gate on the returned value after the call;
+an assertion that fails anywhere in a circuit invalidates the whole transaction, so a post-call check still blocks the operation.
+The returned id is always the caller (sender, approver, spender, or registrant), never `to` or `fromAddress`,
+which are explicit arguments a wrapper gates directly.
+
+Accounts are externally owned. A contract has no private state in which to hold an encryption secret,
+so it could be credited but could never prove a balance or scan memos; the module therefore keys everything by a `Bytes<32>`
+account id and offers no `ContractAddress` variant. A protocol that needs to hold these tokens uses an account it controls
+through `approve` / `transferFrom`.
+
+## Usage
+
+The following contract composes the token with the public-supply extension and [Ownable],
+pairing every supply change with its accounting block and restricting `mint` to the contract owner.
+It re-exports the module's ledger so wallets can read ciphertexts and memos off chain,
+and it exposes the conserving surface plus gated supply wrappers.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/ConfidentialFungibleToken"
+ prefix Token_;
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/ConfidentialFungibleTokenPublicSupply"
+ prefix Supply_;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal"
+ prefix ElGamal_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+// Types wallets need to read balances and escrow entries.
+export { Token_EscrowEntry, ElGamal_Ciphertext };
+
+// Surface the module's ledger so wallets can scan memos and read ciphertexts
+// without a proof.
+export {
+ Token__balances,
+ Token__pending,
+ Token__encryptionKeys,
+ Token__memos,
+ Token__escrow,
+ Token__creditEpochs,
+ Token__name,
+ Token__symbol,
+ Token__decimals,
+ Supply__totalSupply
+};
+
+constructor(
+ name_: Opaque<"string">,
+ symbol_: Opaque<"string">,
+ decimals_: Uint<8>,
+ issuer: Either, ContractAddress>
+) {
+ Token_initialize(name_, symbol_, decimals_);
+ Ownable_initialize(issuer);
+}
+
+// Views
+
+export circuit totalSupply(): Uint<128> {
+ return Supply_totalSupply();
+}
+
+export circuit balanceOf(account: Bytes<32>): ElGamal_Ciphertext {
+ return Token_balanceOf(account);
+}
+
+export circuit pendingOf(account: Bytes<32>): ElGamal_Ciphertext {
+ return Token_pendingOf(account);
+}
+
+export circuit allowance(owner: Bytes<32>, spender: Bytes<32>): Token_EscrowEntry {
+ return Token_allowance(owner, spender);
+}
+
+export circuit isRegistered(account: Bytes<32>): Boolean {
+ return Token_isRegistered(account);
+}
+
+export pure circuit computeAccountId(sk: Bytes<32>): Bytes<32> {
+ return Token_computeAccountId(sk);
+}
+
+// Registration and wallet maintenance
+
+export circuit register(): Bytes<32> {
+ return Token_register();
+}
+
+export circuit sweep(): Bytes<32> {
+ return Token_sweep();
+}
+
+export circuit clearMemos(expectedEpoch: Uint<64>): [] {
+ Token_clearMemos(expectedEpoch);
+}
+
+// Conserving value operations
+
+export circuit transfer(to: Bytes<32>, value: Uint<128>): Bytes<32> {
+ return Token_transfer(to, value);
+}
+
+export circuit approve(spender: Bytes<32>, value: Uint<128>): Bytes<32> {
+ return Token_approve(spender, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Bytes<32>,
+ to: Bytes<32>,
+ value: Uint<128>
+): Bytes<32> {
+ return Token_transferFrom(fromAddress, to, value);
+}
+
+// Supply operations: each token building block is paired with its
+// accounting block, so sum(balances) == totalSupply holds by construction.
+
+export circuit mint(account: Bytes<32>, value: Uint<128>): [] {
+ Ownable_assertOnlyOwner();
+ Supply__addSupply(value);
+ Token__mint(account, value);
+}
+
+export circuit burn(value: Uint<128>): Bytes<32> {
+ const accountId = Token__burn(value);
+ Supply__subSupply(value);
+ return accountId;
+}
+
+export circuit burnFrom(fromAddress: Bytes<32>, value: Uint<128>): Bytes<32> {
+ const spenderId = Token__burnFrom(fromAddress, value);
+ Supply__subSupply(value);
+ return spenderId;
+}
+```
+
+A few points about this layout:
+
+* `mint` is gated on the contract owner. `burn` and `burnFrom` need no extra gate: the module already authenticates
+ the caller as the account (or the approved spender) whose value is destroyed, and both return that `accountId`
+ for any further policy check.
+* `name`, `symbol`, and `decimals` are read from the exported ledger rather than through getter circuits.
+* The raw `_mint`, `_burn`, and `_burnFrom` building blocks are not re-exported; see the callout above.
+
+### Wallet-side flow
+
+A wallet integrating this contract typically:
+
+1. Derives `accountId` with `computeAccountId(sk)` and calls `register()` once.
+2. Scans `_memos[accountId]` after each block, decrypts new entries with the encryption secret, and updates its plaintext cache.
+3. Calls `sweep()` to make pending credits spendable.
+4. Supplies, through its witnesses, the account secret, the encryption secret, the cached plaintext for whichever ciphertext
+ the circuit asks about, and a fresh seed on every call.
+5. After a partial `transferFrom` against an allowance it granted, decrypts the escrow entry's `ownerMemo` to learn the remainder.
+
+## Known limitations
+
+* Two credits to the same recipient in the same block conflict, because each credit reads and advances the recipient's
+ credit counter. Credits to one account serialize.
+* Anyone can append memos to any registered account by sending it dust, and only the account owner can prune the list.
+* The value circuits are large. A deployment that exposes the full surface may exceed a network's per-transaction byte budget;
+ confirm deployability against the target network and expose only the operations the contract needs.
+* Contract-to-contract holdings are out of scope, as described under [Gating on the caller](#gating-on-the-caller).
diff --git a/content/contracts-compact/0.3.x/crypto.mdx b/content/contracts-compact/0.3.x/crypto.mdx
new file mode 100644
index 00000000..602e1867
--- /dev/null
+++ b/content/contracts-compact/0.3.x/crypto.mdx
@@ -0,0 +1,264 @@
+---
+title: Crypto
+---
+
+{/* links */}
+[ConfidentialFungibleToken guide]: ./confidential-fungible-token
+[ElGamal API]: api/crypto#ElGamal
+[EcdhMask API]: api/crypto#EcdhMask
+[secretToScalar]: api/crypto#ElGamal-secretToScalar
+[derivePk]: api/crypto#ElGamal-derivePk
+[expandRandomness]: api/crypto#ElGamal-expandRandomness
+[encrypt]: api/crypto#ElGamal-encrypt
+[encryptPoint]: api/crypto#ElGamal-encryptPoint
+[encryptZero]: api/crypto#ElGamal-encryptZero
+[add]: api/crypto#ElGamal-add
+[sub]: api/crypto#ElGamal-sub
+[scalarMul]: api/crypto#ElGamal-scalarMul
+[addEncrypted]: api/crypto#ElGamal-addEncrypted
+[subEncrypted]: api/crypto#ElGamal-subEncrypted
+[rerandomize]: api/crypto#ElGamal-rerandomize
+[assertKeyPair]: api/crypto#ElGamal-assertKeyPair
+[assertDecryptsTo]: api/crypto#ElGamal-assertDecryptsTo
+[assertDecryptsToPoint]: api/crypto#ElGamal-assertDecryptsToPoint
+[kdf]: api/crypto#EcdhMask-kdf
+[EcdhMask.encrypt]: api/crypto#EcdhMask-encrypt
+[EcdhMask.decrypt]: api/crypto#EcdhMask-decrypt
+
+The Crypto directory provides two stateless encryption primitives over Midnight's embedded Jubjub curve:
+ElGamal, an additively homomorphic encryption of small integers, and EcdhMask,
+a one-time pad derived from an ECDH key exchange that delivers a value to a recipient directly.
+Both are pure modules: they hold no ledger state, declare no witnesses,
+and take their keys, plaintexts, and randomness as explicit arguments.
+The importing contract decides what is stored, what is disclosed, and where its secrets come from.
+
+The [ConfidentialFungibleToken guide] shows the two primitives working together end to end:
+ElGamal keeps every balance as a ciphertext that the contract can update without decrypting it,
+and EcdhMask hands each recipient the plaintext amount of an incoming transfer.
+
+## ElGamal
+
+The [ElGamal API] implements single-receiver ElGamal encryption over Jubjub.
+A ciphertext is a pair of curve points `(c1, c2)` with `c1 = g^r` and `c2 = pk^r * m`,
+where `g` is the Jubjub generator, `pk` is the recipient's public key, `r` is the encryption randomness,
+and `m` is the message point.
+
+The module offers the scheme in two forms.
+
+* ***Exponential (lifted) form*** ([encrypt], [assertDecryptsTo]): a `Uint<128>` value `v` is encrypted as the point `g^v`.
+Because the message sits in the exponent, ciphertexts combine arithmetically:
+[add] turns ciphertexts of `a` and `b` into a ciphertext of `a + b`, [sub] yields `a - b`,
+and [scalarMul] multiplies the plaintext by a public scalar.
+[addEncrypted] and [subEncrypted] fold a known plaintext into an existing ciphertext in one call.
+Recovering `v` from `g^v` is a discrete-log search, so the lifted form only works for values small enough to search;
+the caller is responsible for keeping every encrypted value inside that bound.
+* ***General point form*** ([encryptPoint], [assertDecryptsToPoint]): an arbitrary Jubjub point is encrypted as is.
+There is no bound to respect, at the cost of decryption recovering only a point, never a scalar.
+
+[rerandomize] produces a fresh ciphertext of the same plaintext under the same key
+(it homomorphically adds an encryption of zero), so a stored ciphertext can be made unlinkable to its previous form
+without changing what it encrypts.
+[encryptZero] returns the fixed pair of identity points `(O, O)`, a convenient initial value for a fresh slot.
+
+
+`encryptZero()` is a constant, not a hiding encryption.
+A slot still holding it is publicly recognizable as never written.
+Credit it with fresh randomness, or rerandomize it, before treating it as private.
+
+
+### Decryption by assertion
+
+No circuit returns a plaintext: a circuit cannot run the discrete-log search that recovers `v` from `g^v`.
+Instead, the owner's wallet keeps the plaintext of every ciphertext it holds
+and supplies it to the circuit through a witness, and the circuit calls [assertDecryptsTo]
+to prove that the claimed plaintext is correct: it re-derives `pk` from the secret `ek`,
+computes `c2 - ek * c1`, and asserts that the result equals `g^claimedValue`.
+A wrong secret fails the key check; a wrong plaintext fails the decryption check.
+[assertKeyPair] performs the key check alone, which lets a caller prove ownership of a public key
+without touching any ciphertext.
+
+`claimedValue` is a `Uint<248>` so that a balance accumulated through many homomorphic additions
+never outgrows the claim. The comparison happens on the curve, so two claims that are congruent modulo
+the Jubjub subgroup order `ℓ` are indistinguishable; `Uint<248>` stays below `ℓ`, so the claim domain is sound.
+
+### Keys and randomness
+
+The secret in this scheme is a 32-byte value `ek`. Everything else is derived from it.
+
+* [secretToScalar] maps the secret to a Jubjub scalar: `degradeToTransient(H("ElGamal:secretToScalar" || ek))`,
+where the tag is the 32-byte padded string and `H` is `persistentHash`.
+`degradeToTransient` truncates the hash to 31 bytes, which guarantees a valid Jubjub scalar.
+The tag is placed first so this derivation can never coincide with a call to [expandRandomness].
+It also keeps the encryption scalar unrelated to an account identifier computed as `H(secret)`,
+so a wallet may derive both roles from common key material.
+* [derivePk] computes the public key `pk = g^secretToScalar(ek)`. The public key is safe to publish
+and is normally stored on the ledger next to the ciphertexts encrypted under it.
+* [expandRandomness] derives encryption randomness from a seed: `degradeToTransient(H(seed || tag))`.
+One secret seed per circuit invocation can therefore yield as many distinct scalars as the circuit needs,
+one per `tag`.
+
+Every encryption takes its randomness `r` as a `Field` argument, and the module holds no record of which values have been used.
+The caller owns three obligations that no circuit can check:
+
+* `r` must be a valid Jubjub scalar (strictly less than `ℓ`); larger values fault `ecMul`.
+[expandRandomness] and [secretToScalar] always satisfy this.
+* `r` must be secret. Anyone who learns `r` for a ciphertext computes `pk^r` and strips the mask.
+* `r` must be fresh for every ciphertext. Reusing `r` under the same `pk` repeats `c1`,
+and `c2 - c2'` then reveals the difference of the two plaintexts.
+In practice this means the witness seed must be fresh per invocation and the tag distinct per encryption within an invocation.
+
+The module does reject the two inputs that would zero the mask altogether: `pk` equal to the identity point and `r = 0`.
+Both are asserted in [encryptPoint], which every encryption path goes through.
+
+## EcdhMask
+
+The [EcdhMask API] is the direct-decrypt counterpart to ElGamal. Where exponential ElGamal lets a contract add ciphertexts
+but makes recovery a search, EcdhMask delivers a `Uint<128>` value to whoever holds the secret behind a Jubjub public key,
+with no bound on the value and no search to recover it. The construction, for a recipient key `pk = g^ek` and a fresh ephemeral scalar `e`:
+
+```
+E = g^e (ephemeral public key, published with the ciphertext)
+S = pk^e (ECDH shared secret point)
+mask = kdf(S, domain)
+ct = value + mask (one-time pad in the field)
+```
+
+[kdf] hashes the shared point and the consumer's domain tag: `degradeToTransient(H(H(S) || domain))`,
+with `H` again `persistentHash`. The consumer chooses `domain` and must use the same value on both sides.
+[EcdhMask.encrypt] returns the `Ciphertext { ephemeralPk: E, ct }`; the recipient recovers `S = E^ek` and subtracts the mask with [EcdhMask.decrypt],
+passing its secret as the scalar produced by ElGamal's [secretToScalar].
+`decrypt` is a pure circuit meant for wallets and off-chain code: reading a received value needs no proof,
+and the on-chain path only ever calls `encrypt`.
+
+The same Jubjub key may serve both primitives, as the confidential token does.
+The schemes share only the public key, the KDF is domain-separated and keyed by the secret point,
+and neither exposes a decryption oracle.
+
+
+The ephemeral scalar `e` is a one-time pad key. It must be unique per ciphertext AND secret.
+Reusing `e` to the same recipient repeats the mask, and subtracting the two ciphertexts recovers both values.
+A unique but predictable `e` (a counter, a public nonce) is just as fatal, because `pk` is public and anyone can recompute `pk^e`.
+Derive `e` the way you derive ElGamal randomness: from a secret, per-invocation seed, with a distinct tag per memo.
+
+
+The pad is malleable and the module adds no authentication. That is safe when the ciphertext is computed inside the same circuit
+that commits to the value elsewhere and is immutable once written, which is the intended on-chain use.
+A consumer with mutable storage or an in-transit attacker in its threat model must add its own authentication.
+As with ElGamal, the weak inputs that would zero the shared secret are rejected: `encrypt` asserts that `recipientPk` is not the identity
+and that the ephemeral point `g^e` is not the identity (which covers `e = 0` and any `e` congruent to zero modulo `ℓ`).
+
+## Jubjub facts a consumer must respect
+
+Both modules rely on two properties of Midnight's embedded-curve arithmetic.
+
+* ***Every point is constrained into the prime-order subgroup.*** The runtime assigns each `JubjubPoint` reaching a curve operation
+by cofactor clearing at the constraint level, so an off-curve, low-order, or mixed-order point makes the circuit unsatisfiable.
+This holds for points a caller passes as arguments or witnesses (a recipient `pk`, the message point of `encryptPoint`),
+which is why the modules perform no in-circuit membership check and why the homomorphic algebra is sound.
+The identity point is a valid subgroup member, so it is NOT caught by this guarantee; that is why the modules assert against it explicitly.
+This is a substantiated-but-unaudited assumption:
+it was verified in the source of the pinned Midnight runtime and is regression-tested by the library's runtime canary test,
+but compactc and midnight-ledger are unaudited, so re-verify it before using the modules on a newer runtime.
+* ***Negation is scalar multiplication by `ℓ - 1`, never by `-1`.*** A `Field` literal `-1` is the BLS scalar modulus minus one,
+which exceeds the Jubjub scalar order and faults `ecMul`. For any `P` in the subgroup, `(ℓ - 1) * P = -P`, and that identity is what
+`negate`, `sub`, and the decryption check build on. Do not multiply by `ℓ` itself either: it is out of range and faults as well.
+A consumer that needs to negate a point of its own should follow the same pattern.
+
+## Usage
+
+Both modules are imported like any other module; prefix them with `ElGamal_` and `EcdhMask_` to avoid circuit signature clashes.
+Each module exports a `Ciphertext` struct, which the prefix turns into `ElGamal_Ciphertext` and `EcdhMask_Ciphertext`.
+
+The witnesses below are declared by the importing contract; this library ships no TypeScript witness implementations
+(the ones in the repository are test-only), so consumers author their own.
+
+### An encrypted single-owner balance
+
+The contract stores the owner's public key and one ElGamal ciphertext on the ledger.
+The owner's wallet holds the encryption secret, the current plaintext, and a fresh randomness seed per call,
+and supplies them through witnesses.
+`credit` takes a public `value` and adds it to the ciphertext without knowing the balance;
+`debit` proves the claimed balance is correct before subtracting.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal"
+ prefix ElGamal_;
+
+export ledger _ownerPk: JubjubPoint;
+export ledger _balance: ElGamal_Ciphertext;
+
+// The owner's 32-byte encryption secret.
+witness wit_EncryptionKey(): Bytes<32>;
+// The wallet's cached plaintext for `ct`.
+witness wit_Plaintext(ct: ElGamal_Ciphertext): Uint<248>;
+// A fresh, secret 32-byte seed for every call.
+witness wit_RandomnessSeed(): Bytes<32>;
+
+export circuit register(): [] {
+ const pk = ElGamal_derivePk(wit_EncryptionKey());
+ _ownerPk = disclose(pk);
+ _balance = ElGamal_encryptZero();
+}
+
+export circuit credit(value: Uint<128>): [] {
+ const r = ElGamal_expandRandomness(wit_RandomnessSeed(), pad(32, "credit"));
+ _balance = disclose(ElGamal_addEncrypted(_balance, _ownerPk, value, r));
+}
+
+export circuit debit(value: Uint<128>): [] {
+ const ek = wit_EncryptionKey();
+ const ct = _balance;
+ const plaintext = wit_Plaintext(ct);
+ // Binds the private `ek` and `plaintext` to the public `_ownerPk` and `ct`.
+ ElGamal_assertDecryptsTo(ct, _ownerPk, ek, plaintext);
+ assert(plaintext >= value, "Balance: insufficient balance");
+
+ const r = ElGamal_expandRandomness(wit_RandomnessSeed(), pad(32, "debit"));
+ _balance = disclose(ElGamal_subEncrypted(ct, _ownerPk, value, r));
+}
+```
+
+What reaches the ledger: the public key and each new ciphertext. The `value` argument of an exported circuit is public,
+so these circuits hide the running balance, not the individual amounts;
+the confidential token layers escrow and memos on top of this pattern to hide amounts as well.
+The secret, the plaintext, and the seed never leave the witness, and `assertDecryptsTo` is what makes a dishonest plaintext unprovable.
+
+### Delivering a value with a memo
+
+A sender who credits a recipient's ciphertext has no way to tell the recipient how much arrived, since the recipient would
+otherwise face a discrete-log search over its own balance. An EcdhMask memo solves that: the circuit encrypts the amount
+to the recipient's public key and appends it to a per-recipient list the recipient's wallet reads and decrypts off chain.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal"
+ prefix ElGamal_;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/EcdhMask"
+ prefix EcdhMask_;
+
+export ledger _recipientPks: Map, JubjubPoint>;
+export ledger _memos: Map, List>;
+
+witness wit_RandomnessSeed(): Bytes<32>;
+
+export circuit notify(recipient: Bytes<32>, value: Uint<128>): [] {
+ const pk = _recipientPks.lookup(disclose(recipient));
+ // Secret, fresh per call, distinct tag per memo within a call.
+ const e = ElGamal_expandRandomness(wit_RandomnessSeed(), pad(32, "memo"));
+ const memo = EcdhMask_encrypt(pk, value, e, pad(32, "MyApp:memo:v1"));
+
+ if (!_memos.member(disclose(recipient))) {
+ _memos.insert(disclose(recipient), default>);
+ }
+ _memos.lookup(disclose(recipient)).pushFront(disclose(memo));
+}
+```
+
+The recipient's wallet recovers the amount with the pure circuit
+`EcdhMask_decrypt(memo, ElGamal_secretToScalar(ek), pad(32, "MyApp:memo:v1"))`, using the same domain tag.
+Only the memo (the ephemeral point and the masked field element) reaches the ledger.
diff --git a/content/contracts-compact/0.3.x/extensibility.mdx b/content/contracts-compact/0.3.x/extensibility.mdx
new file mode 100644
index 00000000..f030f54e
--- /dev/null
+++ b/content/contracts-compact/0.3.x/extensibility.mdx
@@ -0,0 +1,175 @@
+---
+title: Extensibility
+---
+
+## The Module/Contract Pattern
+
+We use the term **modular composition by delegation** to describe the practice of having contracts call into module-defined circuits to implement behavior.
+Rather than inheriting or overriding functionality,
+a contract delegates responsibility to the module by explicitly invoking its exported circuits.
+
+The idea is that there are two types of compact files: modules and contracts.
+To minimize risk, boilerplate, and avoid naming clashes, we follow these rules:
+
+### Modules
+
+Modules expose functionality through three circuit types:
+
+1. `internal`: private helpers → used to break up logic within the module.
+2. `public`: composable building blocks → intended for contracts to use in complex flows (`_mint`, `_burn`).
+3. `external`: standalone circuits → safe to expose as-is (`transfer`, `approve`).
+
+Modules must:
+
+* Export only `public` and `external` circuits.
+* Prefix `public` circuits with `_` (e.g., `FungibleToken._mint`).
+* Avoid `_` prefix for `external` circuits (e.g., `FungibleToken.transfer`).
+* Avoid defining or calling constructors or `initialize()` directly.
+* Optionally define an `initialize()` circuit for internal setup—but execution must be delegated to the contract.
+
+
+Compact files must contain only one top-level module and all logic must be defined **inside** the module declaration.
+
+
+### Contracts
+
+Contracts compose behavior by explicitly invoking the relevant circuits from imported modules.
+Therefore, contracts:
+
+* Can import from modules.
+* Should add prefix to imports (`import "FungibleToken" prefix FungibleToken_;`).
+* Should re-expose external module circuits through wrapper circuits to control naming and layering.
+Avoid raw re-exports to prevent name clashes.
+* Should implement constructor that calls `initialize` from imported modules.
+* Must not call initializers outside of the constructor.
+
+This pattern balances modularity with local control, avoids tight coupling, and works within Compact’s language constraints.
+As Compact matures, this pattern will likely evolve as well.
+
+### Example contract implementing modules
+
+```ts
+// FungibleTokenMintablePausableOwnableContract
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ _name: Opaque<"string">,
+ _symbol: Opaque<"string">,
+ _decimals: Uint<8>,
+ _owner: Either, ContractAddress>
+) {
+ FungibleToken_initialize(_name, _symbol, _decimals);
+ Ownable_initialize(_owner);
+}
+
+/** IFungibleTokenMetadata */
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+/** IFungibleToken */
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(
+ account: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
+export circuit transfer(
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transfer(to, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(
+ spender: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_approve(spender, value);
+}
+
+/** IMintable */
+
+export circuit mint(
+ account: Either, ContractAddress>,
+ value: Uint<128>
+): [] {
+ Pausable_assertNotPaused();
+ Ownable_assertOnlyOwner();
+ FungibleToken__mint(account, value);
+}
+
+/** IPausable */
+
+export circuit isPaused(): Boolean {
+ return Pausable_isPaused();
+}
+
+export circuit pause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__pause();
+}
+
+export circuit unpause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__unpause();
+}
+
+/** IOwnable */
+
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+export circuit transferOwnership(
+ newOwner: Either, ContractAddress>
+): [] {
+ Ownable_transferOwnership(newOwner);
+}
+
+export circuit renounceOwnership(): [] {
+ Ownable_renounceOwnership();
+}
+
+```
diff --git a/content/contracts-compact/0.3.x/fungible-token.mdx b/content/contracts-compact/0.3.x/fungible-token.mdx
new file mode 100644
index 00000000..93324a76
--- /dev/null
+++ b/content/contracts-compact/0.3.x/fungible-token.mdx
@@ -0,0 +1,205 @@
+---
+title: FungibleToken
+---
+
+{/* links */}
+[fungible tokens]: /contracts/5.x/tokens#different-kinds-of-tokens
+[EIP-20]: https://eips.ethereum.org/EIPS/eip-20
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[_mint]: api/fungible-token#FungibleToken-_mint
+[transfer]: api/fungible-token#FungibleToken-transfer
+[approve]: api/fungible-token#FungibleToken-approve
+[transferFrom]: api/fungible-token#FungibleToken-transferFrom
+[wit_FungibleTokenSK]: api/fungible-token#FungibleToken-wit_FungibleTokenSK
+[computeAccountId]: api/utils#Utils-computeAccountId
+
+FungibleToken is a specification for [fungible tokens],
+a type of token where all the units are exactly equal to each other.
+This module is an approximation of [EIP-20] written in the Compact programming language for the Midnight network.
+
+## ERC20 Compatibility
+
+Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard.
+Some features and behaviors are either not possible, not possible yet,
+or changed because of the vastly different tech stack and Compact language constraints.
+
+***Notable changes***
+
+* ***`Uint<128>` as value type*** - Since 256-bit unsigned integers are not supported,
+the library uses the Compact type `Uint<128>`.
+
+***Features and specifications NOT supported***
+
+* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future.
+* ***Uint256 type*** - There's ongoing research on ways to support uint256 in the future.
+* ***Interface*** - Compact currently does not have a way to define a contract interface.
+This library offers modules of contracts with free floating circuits;
+nevertheless, there are no means of enforcing that all circuits are provided.
+
+## Contract-to-contract calls
+
+Contract-to-contract calls are currently not supported in the Compact language.
+Due to this limitation, the current iteration of FungibleToken disallows transfers and mints to the `ContractAddress` type.
+Transferring tokens to a contract may result in those tokens being locked forever.
+The FungibleToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts.
+
+
+The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning
+`transfer`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type.
+
+
+## Identity Model
+
+FungibleToken uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it through the [wit_FungibleTokenSK] witness. The module computes the caller's account identifier
+as `persistentHash(secretKey)`, a commitment that hides the key while providing a stable, pseudonymous on-chain identity.
+That identifier is the `Bytes<32>` (left) branch of the `Either, ContractAddress>` account type
+used by [transfer], [approve], and [transferFrom] to locate the caller's balance and allowances.
+
+What is public on the ledger:
+
+* `_balances`, keyed by account identifier (or contract address), and `_totalSupply`.
+* `_allowances`, keyed by owner and then spender.
+* `_name`, `_symbol`, and `_decimals`.
+
+What the caller holds privately:
+
+* The 32-byte `secretKey`. It never reaches the ledger; only its hash does.
+
+A transfer discloses the caller's account identifier, the recipient, and the amount, because the balance map is updated with those values.
+An approval discloses the owner's identifier, the spender, and the allowance.
+
+Because the identifier is `H(secretKey)` with no per-deployment salt, the same key produces the same identity in every contract.
+Users who want cross-contract unlinkability can use a different secret key per contract at the wallet layer.
+
+To derive an account identifier off-chain (for example, to pass as the `recipient` of a mint), use [computeAccountId] from the Utils module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+The TypeScript witness implementation lives with the DApp, not with the library.
+The witnesses in the `compact-contracts` repository are test-only and are not published;
+consumers author their own `wit_FungibleTokenSK` that returns the caller's 32-byte secret key.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of the tokens held under that identity,
+ and key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+## Usage
+
+Import the FungibleToken module into the implementing contract.
+It's recommended to prefix the module with `FungibleToken_` to avoid circuit signature clashes.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+}
+
+```
+
+Next, expose the circuits that users may call in the contract.
+
+This library enables extensibility by following the rules of the [Module/Contract Pattern].
+Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts.
+Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens.
+
+```typescript
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+(...)
+```
+
+The following example is a simple token contract with a fixed supply that's minted to the passed recipient upon construction.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ recipient: Either, ContractAddress>,
+ fixedSupply: Uint<128>,
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ FungibleToken__mint(recipient, fixedSupply);
+}
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
+export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean {
+ return FungibleToken_transfer(to, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>,
+): Boolean {
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
+ return FungibleToken_approve(spender, value);
+}
+
+```
diff --git a/content/contracts-compact/0.3.x/index.mdx b/content/contracts-compact/0.3.x/index.mdx
new file mode 100644
index 00000000..ae64ef1b
--- /dev/null
+++ b/content/contracts-compact/0.3.x/index.mdx
@@ -0,0 +1,180 @@
+---
+title: Contracts for Compact
+---
+
+{/* links */}
+[Midnight]: https://midnight.network/
+[nvm]: https://github.com/nvm-sh/nvm
+[yarn]: https://yarnpkg.com/getting-started/install
+[compact-dev-tools]: https://docs.midnight.network/blog/compact-developer-tools
+
+**A library for secure smart contract development** written in Compact for [Midnight].
+This library consists of modules to build custom smart contracts.
+
+
+Pre-release versions (`-alpha`, `-rc`) contain experimental, unaudited code.
+Expect rapid iteration. **Use at your own risk.**
+For production, use a stable release (no `-alpha` or `-rc` suffix) on the Midnight ledger it targets.
+See the [release and audit table](https://github.com/OpenZeppelin/compact-contracts/blob/v0.3.0/README.md#security) in the repository.
+
+
+## Usage
+
+Make sure you have [nvm] and [yarn] installed on your machine.
+
+Follow Midnight's [Compact Developer Tools][compact-dev-tools] installation guide and confirm that `compact` is in the `PATH` env variable.
+
+```bash
+$ compact compile --version
+
+Compactc version: {{compact_compiler_version}}
+{{compact_compiler_version}}
+```
+
+### Installation
+
+Create a directory for your project.
+
+```bash
+mkdir my-project
+cd my-project
+```
+
+Install the library.
+
+```bash
+yarn add @openzeppelin/compact-contracts
+```
+
+### Write a custom contract using library modules
+
+In the root of `my-project`, create a custom contract using OpenZeppelin Compact modules.
+Import the modules through `./node_modules/@openzeppelin/compact-contracts/...`.
+
+```typescript
+// MyContract.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ _name: Opaque<"string">,
+ _symbol: Opaque<"string">,
+ _decimals: Uint<8>,
+ _recipient: Either, ContractAddress>,
+ _amount: Uint<128>,
+ _initOwner: Either, ContractAddress>,
+) {
+ Ownable_initialize(_initOwner);
+ FungibleToken_initialize(_name, _symbol, _decimals);
+ FungibleToken__mint(_recipient, _amount);
+}
+
+/** IFungibleToken */
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
+export circuit transfer(
+ to: Either, ContractAddress>,
+ value: Uint<128>,
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transfer(to, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_approve(spender, value);
+}
+
+/** IOwnable */
+
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ return Ownable_transferOwnership(newOwner);
+}
+
+export circuit renounceOwnership(): [] {
+ return Ownable_renounceOwnership();
+}
+
+/** IPausable */
+
+export circuit pause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__pause();
+}
+
+export circuit unpause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__unpause();
+}
+```
+
+### Compile the contract
+
+In the project root, compile the contract.
+
+```bash
+% compact compile MyContract.compact artifacts/MyContract
+Compiling 14 circuits:
+ circuit "allowance" (k=11, rows=1352)
+ circuit "approve" (k=13, rows=3080)
+ circuit "balanceOf" (k=10, rows=673)
+ circuit "decimals" (k=6, rows=28)
+ circuit "name" (k=6, rows=28)
+ circuit "owner" (k=7, rows=76)
+ circuit "pause" (k=13, rows=2365)
+ circuit "renounceOwnership" (k=13, rows=2364)
+ circuit "symbol" (k=6, rows=28)
+ circuit "totalSupply" (k=6, rows=28)
+ circuit "transfer" (k=13, rows=3990)
+ circuit "transferFrom" (k=13, rows=4977)
+ circuit "transferOwnership" (k=13, rows=2959)
+ circuit "unpause" (k=13, rows=2362)
+Overall progress [====================] 14/14
+```
diff --git a/content/contracts-compact/0.3.x/multi-token.mdx b/content/contracts-compact/0.3.x/multi-token.mdx
new file mode 100644
index 00000000..4a567e74
--- /dev/null
+++ b/content/contracts-compact/0.3.x/multi-token.mdx
@@ -0,0 +1,213 @@
+---
+title: MultiToken
+---
+
+{/* links */}
+[EIP-1155]: https://eips.ethereum.org/EIPS/eip-1155
+[ERC165]: https://eips.ethereum.org/EIPS/eip-165
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[_mint]: ./api/multi-token#MultiToken-_mint
+[setApprovalForAll]: ./api/multi-token#MultiToken-setApprovalForAll
+[transferFrom]: ./api/multi-token#MultiToken-transferFrom
+[wit_MultiTokenSK]: ./api/multi-token#MultiToken-wit_MultiTokenSK
+[computeAccountId]: ./api/utils#Utils-computeAccountId
+
+MultiToken is a specification for contracts that manage multiple token types.
+This module is an approximation of [EIP-1155] written in the Compact programming language for the Midnight network.
+
+## ERC1155 Compatibility
+
+Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard.
+Some features and behaviors are either not possible, not possible yet,
+or changed because of the vastly different tech stack and Compact language constraints.
+
+**Notable changes**
+
+* **`Uint<128>` as value and id type** - Since 256-bit unsigned integers are not supported,
+the library uses the Compact type `Uint<128>`.
+
+**Features and specifications NOT supported**
+
+* **Events** - Midnight does not currently support events, but this is planned on being supported in the future.
+* **Uint256 type** - There's ongoing research on ways to support uint256 in the future.
+* **Interface** - Compact currently does not have a way to define a contract interface.
+This library offers modules of contracts with free floating circuits;
+nevertheless, there's no means of enforcing that all circuits are provided.
+* **Batch mint, burn, transfer** - Without support for dynamic arrays,
+batching transfers is difficult to do without a hacky solution.
+For instance, we could change the `to` and `fromAddress` parameters to be vectors.
+This would change the signature and would be both difficult to use and easy to misuse.
+* **Querying batched balances** - This can be somewhat supported.
+The issue, without dynamic arrays, is that the module circuit must use `Vector` for accounts and ids;
+therefore, the implementing contract must explicitly define the number of balances to query in the circuit i.e.
+
+```ts
+balanceOfBatch_10(
+ accounts: Vector<10, Either, ContractAddress>>,
+ ids: Vector<10, Uint<128>>
+): Vector<10, Uint<128>>
+```
+
+Since this module does not offer mint or transfer batching, balance batching is also not included at this time.
+
+* **Introspection** - Compact currently cannot support contract-to-contract queries for introspection.
+[ERC165] (or an equivalent thereof) is NOT included in the contract.
+* **Safe transfers** - The lack of an introspection mechanism means safe transfers of any kind can not be supported.
+
+## Contract-to-contract calls
+
+Contract-to-contract calls are currently not supported in the Compact language.
+Due to this limitation, the current iteration of MultiToken disallows transfers and mints to the `ContractAddress` type.
+Transferring tokens to a contract may result in those tokens being locked forever.
+The MultiToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts.
+
+
+The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning
+`transferFrom`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type.
+
+
+## Identity Model
+
+MultiToken uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it through the [wit_MultiTokenSK] witness. The module computes the caller's account identifier
+as `persistentHash(secretKey)`, a commitment that hides the key while providing a stable, pseudonymous on-chain identity.
+That identifier is the `Bytes<32>` (left) branch of the `Either, ContractAddress>` account type
+and is what [setApprovalForAll] and [transferFrom] check ownership and operator rights against.
+
+What is public on the ledger:
+
+* `_balances`, keyed by token id and then by account identifier (or contract address).
+* `_operatorApprovals`, keyed by owner and then operator.
+* `_uri`.
+
+What the caller holds privately:
+
+* The 32-byte `secretKey`. It never reaches the ledger; only its hash does.
+
+A transfer discloses the sender, the recipient, the token id, and the amount, because the balance map is updated with those values.
+An operator approval discloses the caller's identifier, the operator, and the flag.
+
+Because the identifier is `H(secretKey)` with no per-deployment salt, the same key produces the same identity in every contract.
+Users who want cross-contract unlinkability can use a different secret key per contract at the wallet layer.
+
+To derive an account identifier off-chain (for example, to pass as the `recipient` of a mint), use [computeAccountId] from the Utils module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+The TypeScript witness implementation lives with the DApp, not with the library.
+The witnesses in the `compact-contracts` repository are test-only and are not published;
+consumers author their own `wit_MultiTokenSK` that returns the caller's 32-byte secret key.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of the tokens held under that identity,
+ and key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+## Usage
+
+Import the MultiToken module into the implementing contract.
+It's recommended to prefix the module with `MultiToken_` to avoid circuit signature clashes.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken"
+ prefix MultiToken_;
+
+constructor(
+ uri: Opaque<"string">,
+) {
+ MultiToken_initialize(uri);
+}
+
+```
+
+Next, expose the circuits that users may call in the contract.
+
+This library enables extensibility by following the rules of the [Module/Contract Pattern].
+Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts.
+Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens.
+
+```typescript
+export circuit uri(id: Uint<128>): Opaque<"string"> {
+ return MultiToken_uri(id);
+}
+
+export circuit balanceOf(account: Either, ContractAddress>, id: Uint<128>): Uint<128> {
+ return MultiToken_balanceOf(account, id);
+}
+
+(...)
+```
+
+The following example is a simple multi-token contract that creates both a fixed-supply fungible token and an NFT using the same contract.
+
+```typescript
+// MultiTokenTwoTokenTypes.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken"
+ prefix MultiToken_;
+
+constructor(
+ _uri: Opaque<"string">,
+ recipient: Either, ContractAddress>,
+ fungibleFixedSupply: Uint<128>,
+) {
+ // `initialize` sets the URI (base) for all tokens minted from this contract
+ MultiToken_initialize(_uri);
+
+ // Token id `123` is a fungible token (with a fixed supply of `fungibleFixedSupply`)
+ const fungibleTokenId = 123;
+ MultiToken__mint(recipient, fungibleTokenId, fungibleFixedSupply);
+
+ // Token id `987` is a non-fungible token (so the supply is only 1)
+ const nonFungibleTokenId = 987;
+ MultiToken__mint(recipient, nonFungibleTokenId, 1);
+}
+
+export circuit uri(id: Uint<128>): Opaque<"string"> {
+ return MultiToken_uri(id);
+}
+
+export circuit balanceOf(account: Either, ContractAddress>, id: Uint<128>): Uint<128> {
+ return MultiToken_balanceOf(account, id);
+}
+
+export circuit setApprovalForAll(
+ operator: Either, ContractAddress>,
+ approved: Boolean
+): [] {
+ return MultiToken_setApprovalForAll(operator, approved);
+}
+
+export circuit isApprovedForAll(
+ account: Either, ContractAddress>,
+ operator: Either, ContractAddress>
+): Boolean {
+ return MultiToken_isApprovedForAll(account, operator);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ id: Uint<128>,
+ value: Uint<128>,
+): [] {
+ return MultiToken_transferFrom(fromAddress, to, id, value);
+}
+
+```
diff --git a/content/contracts-compact/0.3.x/multisig.mdx b/content/contracts-compact/0.3.x/multisig.mdx
new file mode 100644
index 00000000..bfe4077c
--- /dev/null
+++ b/content/contracts-compact/0.3.x/multisig.mdx
@@ -0,0 +1,355 @@
+---
+title: Multisig
+---
+
+{/* links */}
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[Ownable]: ./ownable
+[Signer API]: api/multisig#Signer
+[initialize]: api/multisig#Signer-initialize
+[assertSigner]: api/multisig#Signer-assertSigner
+[assertThresholdMet]: api/multisig#Signer-assertThresholdMet
+[isSigner]: api/multisig#Signer-isSigner
+[_addSigner]: api/multisig#Signer-_addSigner
+[_removeSigner]: api/multisig#Signer-_removeSigner
+[_changeThreshold]: api/multisig#Signer-_changeThreshold
+[_setThreshold]: api/multisig#Signer-_setThreshold
+[ProposalManager API]: api/multisig#ProposalManager
+[_createProposal]: api/multisig#ProposalManager-_createProposal
+[_cancelProposal]: api/multisig#ProposalManager-_cancelProposal
+[_markExecuted]: api/multisig#ProposalManager-_markExecuted
+[toShieldedRecipient]: api/multisig#ProposalManager-toShieldedRecipient
+[toUnshieldedRecipient]: api/multisig#ProposalManager-toUnshieldedRecipient
+[ShieldedTreasury API]: api/multisig#ShieldedTreasury
+[ShieldedTreasury _deposit]: api/multisig#ShieldedTreasury-_deposit
+[ShieldedTreasury _send]: api/multisig#ShieldedTreasury-_send
+[ShieldedTreasuryStateless API]: api/multisig#ShieldedTreasuryStateless
+[ShieldedTreasuryStateless _send]: api/multisig#ShieldedTreasuryStateless-_send
+[UnshieldedTreasury API]: api/multisig#UnshieldedTreasury
+[UnshieldedTreasury _deposit]: api/multisig#UnshieldedTreasury-_deposit
+[UnshieldedTreasury _send]: api/multisig#UnshieldedTreasury-_send
+[ForwarderShielded API]: api/multisig#ForwarderShielded
+[ForwarderUnshielded API]: api/multisig#ForwarderUnshielded
+[ForwarderPrivate API]: api/multisig#ForwarderPrivate
+[_drain]: api/multisig#ForwarderPrivate-_drain
+[_calculateParentCommitment]: api/multisig#ForwarderPrivate-_calculateParentCommitment
+[presets directory]: https://github.com/OpenZeppelin/compact-contracts/tree/v0.3.0/contracts/src/multisig/presets
+
+The multisig directory provides building blocks for M-of-N governance over funds held by a contract:
+a `Signer` registry with an approval threshold, a `ProposalManager` that tracks transfer proposals,
+three treasuries that hold and move shielded or unshielded tokens,
+and three forwarders that pass every deposit on to a parent.
+The modules are independent of one another.
+A contract composes the ones it needs, following the [Module/Contract Pattern],
+and supplies its own authorization policy on top.
+The package also ships reference compositions of these modules; see [Presets](#presets).
+
+None of the multisig modules declares a witness.
+Every input is a circuit parameter, so what reaches the public ledger is decided by the `disclose` calls inside each circuit.
+The sections below name what each module publishes.
+
+## Signer
+
+The [Signer API] keeps the registry of who may approve and how many approvals are required.
+It is generic over the signer identity type `T`, which the consuming contract chooses at import time:
+`Bytes<32>` for a commitment such as the hash of a public key,
+`JubjubPoint` for an aggregated key,
+or `Either` for a caller identity.
+The module never resolves who the caller is.
+The contract passes an identity in, and Signer checks it against the registry.
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Signer">
+ prefix Signer_;
+```
+
+### Public ledger state
+
+Everything the module keeps is public:
+`_signers` (the full set of registered identities), `_signerCount`, `_threshold`, and `_isInitialized`.
+[isSigner], [_addSigner], and [_removeSigner] disclose the identity they are given, since it is the key of the set lookup or insertion,
+and [_setThreshold] discloses the new threshold.
+The signer count and threshold are `Uint<8>`, so a registry holds at most 255 signers.
+
+### Who may call what
+
+* [initialize] registers an initial signer vector and sets the threshold.
+ It runs once and MUST only be called from the constructor; calling it later would corrupt the signer set.
+ A contract may skip it entirely and configure signers through its own flow with [_addSigner] and [_setThreshold].
+* The guards [assertSigner] and [assertThresholdMet] and the views `getSignerCount`, `getThreshold`, and [isSigner] are open to anyone
+ and deliberately do not check initialization.
+ Safety comes from the state rather than from a flag:
+ an empty registry makes [isSigner] false for every input, so [assertSigner] rejects every caller,
+ and a zero threshold makes [assertThresholdMet] reject before it compares any approval count.
+ A contract that needs a "configured" signal beyond that tracks one itself.
+* [_addSigner], [_removeSigner], [_changeThreshold], and [_setThreshold] have no access control.
+ The consuming contract must gate them behind its own authorization policy.
+ Left ungated, the first caller registers their own signer set and takes control of the contract.
+
+[assertThresholdMet] compares the supplied count against the threshold and nothing else; it never consults the registry.
+The contract's own approval accounting must be gated by [assertSigner] and must count distinct signers.
+The count is intentionally not bounded by `_signerCount`,
+so approvals collected before a signer was removed still count toward a threshold the remaining signers satisfy.
+
+[_changeThreshold] refuses a threshold above the current signer count.
+[_setThreshold] skips that check so a custom setup flow can set the threshold before any signer is registered.
+A contract must not gate operations on [assertThresholdMet] while such a setup is incomplete.
+
+Reconfigurations that take several transactions (removing a signer, then lowering the threshold) pass through intermediate states
+where the module's checks hold but the contract's intended configuration is incomplete.
+Perform them atomically in a single circuit, or keep a configuration nonce that invalidates proposals created under a stale signer set.
+
+### Usage
+
+The example registers three `Bytes<32>` signer identities at deployment and lets an owner manage the registry afterwards.
+The [Ownable] module authenticates the owner through its `wit_OwnableSK` witness,
+which the consuming contract supplies in its own TypeScript
+(the witness implementations in the library repository are test-only and are not published).
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Signer">
+ prefix Signer_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(
+ initialOwner: Either, ContractAddress>,
+ signers: Vector<3, Bytes<32>>,
+ thresh: Uint<8>
+) {
+ Ownable_initialize(initialOwner);
+ Signer_initialize<3>(signers, thresh);
+}
+
+export circuit addSigner(signer: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Signer__addSigner(signer);
+}
+
+export circuit removeSigner(signer: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Signer__removeSigner(signer);
+}
+
+export circuit changeThreshold(newThreshold: Uint<8>): [] {
+ Ownable_assertOnlyOwner();
+ Signer__changeThreshold(newThreshold);
+}
+
+export circuit isSigner(account: Bytes<32>): Boolean {
+ return Signer_isSigner(account);
+}
+
+export circuit getThreshold(): Uint<8> {
+ return Signer_getThreshold();
+}
+```
+
+## ProposalManager
+
+The [ProposalManager API] tracks the lifecycle of transfer proposals independently of the token kind.
+A `Proposal` records a `Recipient`, a token `color`, an `amount`, and a `ProposalStatus` (`Active`, `Executed`, or `Cancelled`;
+`Inactive` is the zero value and is never stored).
+A `Recipient` pairs 32 address bytes with a `RecipientKind` tag (`ShieldedUser`, `UnshieldedUser`, or `Contract`)
+that says how those bytes are to be read.
+Build recipients with `shieldedUserRecipient`, `unshieldedUserRecipient`, and `contractRecipient`,
+and convert them at execution time with [toShieldedRecipient] (accepts `ShieldedUser` and `Contract`)
+or [toUnshieldedRecipient] (accepts `UnshieldedUser` and `Contract`).
+Because `Contract` is accepted by both, the tag alone does not decide the rail;
+a contract composing both treasuries chooses which one a proposal executes on.
+
+### Public ledger state
+
+Proposals are public.
+`_proposals` is exported ledger state, so every proposal's recipient address, color, amount, and status is readable off-chain,
+and `_nextProposalId` reveals how many have been created.
+This holds whichever treasury the contract composes: pairing the module with `ShieldedTreasuryStateless` still publishes
+the full intent of every transfer.
+The transfer is private; the plan is not.
+
+### Who may call what
+
+[_createProposal], [_cancelProposal], and [_markExecuted] have no access control and must be gated by the contract.
+The guards `assertProposalExists` and `assertProposalActive`, the recipient helpers, and the getters are open.
+[_createProposal] increments the counter and reads it back as the map key,
+so two transactions that create proposals against the same ledger state conflict and only one of them lands.
+
+[_createProposal] validates nothing about the recipient beyond its type.
+A kind the execution rail cannot accept (an `UnshieldedUser` recipient on a shielded treasury)
+creates a proposal that stays `Active` and unexecutable until [_cancelProposal] retires it,
+and a zero or otherwise unspendable address is accepted, so funds sent to it on execution are unrecoverable.
+
+## Treasuries
+
+Each treasury exposes an ungated `_deposit` and `_send` pair that the contract gates with its own policy.
+They differ in what they keep on the ledger and in who handles the change of a shielded spend.
+
+### ShieldedTreasury
+
+The [ShieldedTreasury API] stores one coin per token color in `_coins`, a map from color to `QualifiedShieldedCoinInfo`,
+together with cumulative `_received` and `_sent` totals per color.
+[ShieldedTreasury _deposit] claims the incoming coin with `receiveShielded`
+and merges it into the stored coin of the same color (`mergeCoinImmediate`), or inserts it when the color is new,
+so the contract never has to choose between several UTXOs of one color at spend time.
+[ShieldedTreasury _send] looks the coin up by color, checks it covers the amount, and calls `sendShielded`,
+which routes any change back to the contract as a self-owned output.
+The treasury retains that change: it is written back to `_coins` for the next spend,
+and the color's entry is removed when the send leaves none.
+
+The holdings of this treasury are public.
+`_coins` is exported, so every coin it holds (nonce, color, value, and Merkle index) is readable in plaintext,
+as are the received and sent totals.
+"Shielded" refers to the token rail: individual transfers are shielded, but what the contract holds is not.
+Deposits disclose the incoming coin; sends disclose the color, recipient, and amount.
+
+
+ `_send` returns a `ShieldedSendResult` whose `sent` and `change` coins are in plaintext.
+ Consume it inside the circuit or drop it; returning it from an entry point, writing it to the ledger,
+ or passing it to another contract publishes the coin details.
+ The change coin already belongs to the treasury and is recorded in `_coins`:
+ spending it again in the same transaction double-spends the recorded coin.
+
+
+### ShieldedTreasuryStateless
+
+The [ShieldedTreasuryStateless API] keeps no ledger state at all: no coin map, no balances, no totals.
+`_deposit` calls `receiveShielded` and nothing else.
+The operator discovers the coin's Merkle index from `ZswapOutput` events through the indexer,
+builds the `QualifiedShieldedCoinInfo` off-chain, and passes it to [ShieldedTreasuryStateless _send] at spend time,
+so coin data never reaches the public ledger and there is no on-chain balance to query.
+Sufficiency is enforced by `sendShielded` rather than by the module,
+and a double spend or a coin the contract does not own is rejected by the protocol when the transaction is applied.
+
+`sendShielded` routes change back to the contract, and [ShieldedTreasuryStateless _send] returns it untouched in `result.change`
+as a live, spendable coin.
+The contract decides what to do with it: keep it off-chain and pass it back as the `coin` of a later spend,
+or route it onward to a different recipient in the same transaction with `sendImmediateShielded`.
+The only rule is that the change must never be re-spent and still treated as held.
+Writing it to the ledger would publish its nonce, color, and value.
+
+Deposits disclose the incoming coin; sends disclose the spent coin, recipient, and amount.
+A coin commitment is a hash of the coin's data, so a receive can be linked to its later spend.
+
+### UnshieldedTreasury
+
+The [UnshieldedTreasury API] tracks a per-color balance in `_balances`.
+[UnshieldedTreasury _deposit] calls `receiveUnshielded` and adds to the balance;
+[UnshieldedTreasury _send] subtracts and calls `sendUnshielded`.
+The sufficiency and overflow guards read `_balances`, not the protocol balance,
+which is fixed at the start of execution and cannot see a deposit made earlier in the same transaction.
+Route every unshielded credit and debit through the module:
+a direct `receiveUnshielded` or `sendUnshielded` leaves `_balances` out of step and `getTokenBalance` misreports.
+
+Unshielded transfers are fully public: depositor, recipient, color, and amount all appear on the transcript.
+Recipients are not validated, so an unspendable recipient permanently removes the funds,
+and a send to the contract's own address zeroes the tracked balance while the funds stay held at the protocol level.
+
+### Usage
+
+The example composes `ShieldedTreasury` with [Ownable]: anyone may fund the treasury, only the owner may spend from it.
+The send result is dropped because the change is already recorded in the treasury's coin map.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ShieldedTreasury"
+ prefix Treasury_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(initialOwner: Either, ContractAddress>) {
+ Ownable_initialize(initialOwner);
+}
+
+export circuit deposit(coin: ShieldedCoinInfo): [] {
+ Treasury__deposit(coin);
+}
+
+export circuit withdraw(
+ recipient: Either,
+ color: Bytes<32>,
+ amount: Uint<128>
+): [] {
+ Ownable_assertOnlyOwner();
+ Treasury__send(recipient, color, amount);
+}
+
+export circuit getTokenBalance(color: Bytes<32>): Uint<128> {
+ return Treasury_getTokenBalance(color);
+}
+
+export circuit getReceivedTotal(color: Bytes<32>): Uint<128> {
+ return Treasury_getReceivedTotal(color);
+}
+
+export circuit getSentTotal(color: Bytes<32>): Uint<128> {
+ return Treasury_getSentTotal(color);
+}
+```
+
+## Forwarders
+
+A forwarder is bound to a single parent at deployment and passes every deposit on to it.
+The modules are permissionless: the recipient is fixed, so any caller may deposit without compromising the parent.
+None of them exposes a setter; a contract that needs to change the parent adds its own circuit, or deploys a new forwarder.
+
+### ForwarderShielded and ForwarderUnshielded
+
+The [ForwarderShielded API] and [ForwarderUnshielded API] forward atomically:
+`_deposit` claims the incoming value (`receiveShielded` or `receiveUnshielded`) and re-sends it to the parent in the same transaction
+(`sendImmediateShielded` or `sendUnshielded`).
+Each keeps two ledger fields, `_parent` and `_isInitialized`, and every circuit except `initialize` asserts initialization.
+The parent is stored as a generic `Either` so the ledger layout can later accept a contract address without a state migration,
+but `initialize` accepts only a `ZswapCoinPublicKey` (shielded, stored in the `left` arm)
+or a `UserAddress` (unshielded, stored in the `right` arm) today:
+an atomic forward can only reach a recipient that needs no in-transaction claim,
+and a third-party contract would never claim the output.
+
+`initialize` rejects only the all-zero key or address.
+The deployer must set a parent that can actually spend the forwarded coins; forwarding to a key no one controls strands every deposit.
+The shielded forwarder discloses the incoming coin; the unshielded forwarder discloses depositor, recipient, color, and amount.
+
+### ForwarderPrivate
+
+The [ForwarderPrivate API] hides the parent.
+The ledger holds `_parentCommitment`, computed off-chain by the deployer as
+`H(pad(32, "ForwarderPrivate:commitment") || parent || opSecret)` with the pure [_calculateParentCommitment],
+plus `_isInitialized`.
+`_deposit` claims the coin and writes nothing; coins dwell at the contract address until drained.
+[_drain] takes the coin, the parent coin public key, the operational secret, and the value to send:
+it checks that the pair hashes to the stored commitment, then sends the value to the parent with `sendShielded`.
+The parent is a `ZswapCoinPublicKey` and never a `ContractAddress`:
+a coin-public-key recipient is encrypted inside the Zswap output and never appears on the public transcript,
+whereas a shielded send to a contract publishes that contract's address.
+Any change is routed back to the contract and returned untouched in `result.change`,
+to leave dwelling for a future drain or to route onward with `sendImmediateShielded`.
+
+Knowledge of the preimage is the sole authorization gate.
+Two forwarders bound to the same parent with different operational secrets produce different commitments and are unlinkable on-chain.
+Deposits disclose the incoming coin; drains disclose the spent coin and the value.
+
+
+ Losing the operational secret is permanent fund loss.
+ It is the only drain authorization, with no rotation, revocation, or recovery path:
+ every coin accumulated at the contract becomes inaccessible.
+ Back it up offline before the first deposit and treat it with the hygiene of a signing key.
+
+
+## Presets
+
+The package ships six standalone contracts under its [presets directory]:
+`ShieldedMultiSig`, `ShieldedMultiSigV2`, `ShieldedMultiSigV3`, and one forwarder preset per forwarder module.
+They are reference compositions that show how the modules above fit together, and they are compiled and deployed as-is rather than imported.
+They are not covered by the API reference.
+
+
+ The three `ShieldedMultiSig` presets do not verify real signatures.
+ `ShieldedMultiSigV2` and `ShieldedMultiSigV3` call a `stubVerifySignature` that accepts every signature
+ (to be replaced with `ecdsaVerify` and `keccak256` once those primitives are available),
+ and `ShieldedMultiSig` identifies the caller by `ownPublicKey()` alone.
+ Do not deploy them as multisigs.
+
diff --git a/content/contracts-compact/0.3.x/native-shielded-token.mdx b/content/contracts-compact/0.3.x/native-shielded-token.mdx
new file mode 100644
index 00000000..a7b867b2
--- /dev/null
+++ b/content/contracts-compact/0.3.x/native-shielded-token.mdx
@@ -0,0 +1,375 @@
+---
+title: NativeShieldedToken
+---
+
+{/* links */}
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[ZOwnablePK]: ./ownable
+[wit_secretNonce]: api/ownable#ZOwnablePK-wit_secretNonce
+[ZOwnablePK__computeOwnerId]: api/ownable#ZOwnablePK-_computeOwnerId
+[NativeShieldedToken]: api/native-shielded-token#NativeShieldedToken
+[NativeShieldedTokenFamily]: api/native-shielded-token#NativeShieldedTokenFamily
+[NativeShieldedTokenCore]: api/native-shielded-token#NativeShieldedTokenCore
+[tokenColor]: api/native-shielded-token#NativeShieldedToken-tokenColor
+[_mint]: api/native-shielded-token#NativeShieldedToken-_mint
+[_mintToSelf]: api/native-shielded-token#NativeShieldedToken-_mintToSelf
+[_burn]: api/native-shielded-token#NativeShieldedToken-_burn
+[_burnFromSelf]: api/native-shielded-token#NativeShieldedToken-_burnFromSelf
+[Core__mint]: api/native-shielded-token#NativeShieldedTokenCore-_mint
+[NativeShieldedTokenPublicSupply]: api/native-shielded-token#NativeShieldedTokenPublicSupply
+[NativeShieldedTokenFamilyPublicSupply]: api/native-shielded-token#NativeShieldedTokenFamilyPublicSupply
+[_addMinted]: api/native-shielded-token#NativeShieldedTokenPublicSupply-_addMinted
+[_addBurned]: api/native-shielded-token#NativeShieldedTokenPublicSupply-_addBurned
+[totalMinted]: api/native-shielded-token#NativeShieldedTokenPublicSupply-totalMinted
+[totalBurned]: api/native-shielded-token#NativeShieldedTokenPublicSupply-totalBurned
+[totalSupply]: api/native-shielded-token#NativeShieldedTokenPublicSupply-totalSupply
+[NativeShieldedTokenDerivedNonce]: api/native-shielded-token#NativeShieldedTokenDerivedNonce
+[_deriveNonce]: api/native-shielded-token#NativeShieldedTokenDerivedNonce-_deriveNonce
+[ZOwnableNativeShieldedToken]: api/native-shielded-token#ZOwnableNativeShieldedToken
+[mint]: api/native-shielded-token#ZOwnableNativeShieldedToken-mint
+[burn]: api/native-shielded-token#ZOwnableNativeShieldedToken-burn
+
+A native shielded token is a token whose units are Midnight's own shielded coins (Zswap UTXOs)
+rather than entries in a contract-owned map.
+The issuing contract mints coins of its own color and burns them;
+holding, spending, and transferring happen in the holder's wallet through Zswap, outside the contract.
+This family of modules implements the MIP-0011 native shielded token standard for the Midnight network:
+a single-token flavor ([NativeShieldedToken]), a multi-token flavor ([NativeShieldedTokenFamily]),
+the shared core both are built on ([NativeShieldedTokenCore]), opt-in supply accounting,
+an opt-in derived-nonce helper, and an owner-gated preset ([ZOwnableNativeShieldedToken]).
+
+## How a native shielded token works
+
+**Coins, not balances.** A unit of the token is a shielded coin with a `color`, a `value`, and a `nonce`.
+The ledger stores a commitment to each coin and a nullifier once it is spent; it never stores who holds what.
+There is no `balanceOf`, `allowance`, or contract-mediated `transfer`:
+once a user holds a coin, the contract cannot observe or restrict its movement,
+and holders move coins between wallets with ordinary Zswap transactions.
+
+**Color.** Every token type is identified by its coin color, `tokenType(domain, contractAddress)`,
+where `domain` is a 32-byte discriminator and `contractAddress` is the issuing contract.
+Because the contract address is part of the derivation, only the issuing contract can mint coins of its colors.
+The modules compute the color at call time with [tokenColor] and never store it.
+
+**Minting.** The contract calls `mintShieldedToken`, which creates a new coin for a recipient.
+The protocol caps a single mint at `Uint<64>`.
+
+**Burning.** The contract burns by sending value to `shieldedBurnAddress()`.
+Burn amounts are `Uint<128>`, because `sendShielded` accepts that width; the asymmetry with mints is a protocol property.
+
+**Metadata.** `name`, `symbol`, and `decimals` are contract-wide, fixed at construction, and public.
+`decimals` is a display convention only; the protocol operates on integer values.
+
+## What is public and what is private
+
+The token modules declare no witnesses; the only secrets are the ones that travel inside coins.
+
+Public on the ledger as state:
+
+* `_name`, `_symbol`, `_decimals`, and `_isInitialized` (and `_domain` for the single-token flavor).
+* The supply counters, if a supply extension is composed.
+
+Public in every transaction:
+
+* Each mint's color and amount, through the protocol's shielded-mint effect.
+* The coin commitments created and the nullifiers spent.
+* Which circuit ran.
+
+Private:
+
+* The recipient and the nonce of a mint, provided the nonce is secret and random.
+ Anyone who knows the nonce can recompute the commitment for candidate recipient keys.
+* The amount, the coin, and the refund recipient of a burn, as long as the observer does not know the coin's nonce.
+ A change output does reveal that a burn was partial.
+
+
+A contract-initiated mint or refund carries a coin ciphertext only when the DApp can resolve the recipient's
+encryption key at proving time (its own, or one supplied through `additionalCoinEncPublicKeyMappings`).
+Without that, the recipient's wallet cannot discover the coin by scanning the chain, and the `ShieldedCoinInfo`
+a circuit returns is the only copy of the coin: the DApp must capture it and deliver it to the recipient
+(or persist it, when the contract is the recipient). Dropping it strands the value irrecoverably.
+
+
+## Single token or a family
+
+Both user-facing modules are thin wrappers over [NativeShieldedTokenCore], which owns the initialization flag,
+the metadata, and the mint and burn logic. They differ only in where the domain comes from.
+
+* [NativeShieldedToken] issues one token type. The domain is passed to `initialize`, stored as `sealed ledger _domain`,
+ and forwarded to every core operation, so no circuit takes a domain parameter and a caller cannot misuse one.
+* [NativeShieldedTokenFamily] issues many token types from one contract. Every circuit takes a `domain` parameter,
+ and `tokenType(domain, kernel.self())` discriminates the types.
+ Midnight has no contract-to-contract calls and no way for a contract to deploy another,
+ so a protocol that issues many related tokens (one liquidity-share token per pair, for instance)
+ must do so from a single contract. Metadata is family-wide; per-type identity belongs in the consumer's own state.
+
+
+Pick one flavor per contract. Both wrap the same core, so importing both into one contract shares a single
+initialization flag and one set of `name`, `symbol`, and `decimals`.
+
+
+The core itself is not meant to be composed directly. Its `_mint` and `_burn` take an
+`Either` recipient; the flavors narrow that to a `ZswapCoinPublicKey`
+and expose [_mintToSelf] for the contract's own coins.
+
+## Minting and who can receive a coin
+
+[_mint] takes a `ZswapCoinPublicKey` recipient, a `Uint<64>` amount, and a caller-supplied `Bytes<32>` nonce,
+and returns the new coin's `ShieldedCoinInfo`. The recipient must not be the zero key.
+
+**Recipients.** A coin can be minted to any user's coin public key, or to the issuing contract itself through [_mintToSelf],
+which mints to `kernel.self()` and claims the coin with `receiveShielded` in the same call.
+No other contract can receive a mint: the ledger only accepts a contract-addressed commitment when that contract claims it
+in the same transaction, and no primitive can claim a receive on another contract's behalf.
+The core enforces this directly ([Core__mint] rejects any `ContractAddress` other than the contract's own),
+and the flavors do not expose the address arm at all.
+A coin minted to the contract is the contract's holding: persist the returned coin info in your own ledger state,
+and spend it later with [_burnFromSelf] once it carries a Merkle-tree index.
+
+**Nonces.** The caller is responsible for nonce uniqueness. Reusing a nonce for the same (domain, value, recipient)
+produces a duplicate commitment, which the ledger rejects.
+With a secret, cryptographically random nonce the mint is recipient-private.
+If you do not want to manage nonces, compose [NativeShieldedTokenDerivedNonce] and pass its [_deriveNonce] output,
+at the cost of making those mints recipient-public (see below).
+
+The modules include no access control. Gate `_mint` and `_mintToSelf` with an access-control module
+following the [Module/Contract Pattern]; exposing them as-is lets anyone mint.
+
+## Burning and change
+
+There are two burn paths, chosen by where the coin comes from.
+
+[_burn] destroys value from a coin that the caller's wallet pays into the current transaction.
+The contract receives the coin and spends `amount` of it to the burn address through `sendImmediateShielded`,
+the required path for a coin that was created in the same transaction.
+If `amount` is less than `coin.value`, the remainder is sent to `refundTo` as a new coin and returned as `some(refund)`;
+pass `coin.value` as `amount` to burn the coin in full, in which case `none` is returned.
+`refundTo` must always be supplied and non-zero, even on a full burn: the zero key is the burn address,
+so a zero `refundTo` would silently burn the change as well.
+
+[_burnFromSelf] destroys value from a coin the contract already holds, identified by a `QualifiedShieldedCoinInfo`
+(a coin with a valid Merkle-tree index). It spends through `sendShielded`; any change is received by the contract
+automatically and returned so the consumer can persist it. The change coin replaces the original as the contract's holding,
+and its info is not otherwise recoverable.
+
+Both paths put the spent coin's nullifier and the new output commitments on the ledger.
+Neither puts the burned amount on the ledger: the `disclose()` calls in the module are compiler permission markers
+for the coin primitives, not disclosure sinks.
+
+## Supply accounting (optional)
+
+The token modules track no supply totals. [NativeShieldedTokenPublicSupply] (for the single-token flavor) and
+[NativeShieldedTokenFamilyPublicSupply] (for the family) add on-chain counters.
+They import no token module: the consuming contract pairs every mint with [_addMinted] and every burn with [_addBurned],
+under the same domain for the family. The three getters carry different guarantees:
+
+* [totalMinted] is exact. Every mint of this contract's colors goes through the contract.
+* [totalBurned] is a lower bound. A holder can send coins to the burn address without calling the contract, and those burns are invisible.
+* [totalSupply] is `totalMinted - totalBurned`, an upper bound on circulating supply.
+
+`_addBurned` enforces `burned <= minted`, so an unpaired, duplicate, or wrong-domain burn reverts instead of pushing
+the supply negative. Nothing catches drift on the mint side: a missing `_addMinted` undercounts, after which
+`_addBurned` rejects legitimate burns, and a duplicate `_addMinted` lets burns exceed what was minted.
+The pairing is an invariant of the consuming contract.
+
+
+Composing a supply extension makes contract-mediated burn amounts public: the `_addBurned` write is what puts the
+burned amount into the public transcript. Mint amounts are already public through the shielded-mint effect, so
+`_addMinted` adds accounting, not disclosure. Compose supply tracking only when on-chain auditability is worth more
+than burn-amount privacy.
+
+
+## Derived nonces (optional)
+
+[NativeShieldedTokenDerivedNonce] owns a monotonic `_counter` and exposes [_deriveNonce], which increments the counter
+and returns `evolveNonce(_counter, pad(32, "NativeShieldedToken:nonce"))`.
+Uniqueness comes from the counter; the fixed tag separates these nonces from any other `evolveNonce` use in the contract.
+One counter serves the whole contract, so derived nonces never repeat across token types.
+It needs no initialization and pairs with either flavor.
+
+Derived nonces are predictable from public state, so mints that use them are recipient-public:
+anyone can recompute the commitment for candidate recipient keys.
+They are also open to collision griefing: an actor with access to a caller-nonce mint can pre-mint a colliding commitment
+and make the matching derived-nonce mint fail. Gate both mint paths at the same trust level, and do not rely on retrying:
+the nonce is a pure function of the counter, and a reverted mint does not advance it, so an identical retry collides again.
+A mint with different inputs advances the counter and clears the way.
+
+## Usage
+
+### An owner-gated single token
+
+[ZOwnableNativeShieldedToken] composes [ZOwnablePK] (a shielded owner), [NativeShieldedToken] (one color), and
+[NativeShieldedTokenPublicSupply] (a public minted total). Only the owner can [mint] and [burn];
+the preset deliberately never calls `_addBurned`, so burn amounts stay private and `_totalBurned` stays empty.
+The following contract is the published `ZOwnableNativeShieldedTokenExample`.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/presets/ZOwnableNativeShieldedToken"
+ prefix ZOwnableNativeShieldedToken_;
+
+export { ZswapCoinPublicKey, ContractAddress, Either, Maybe, ShieldedCoinInfo };
+
+// Surface the preset's state unprefixed so `ledger()` readers use the keys the
+// modules declare.
+import {
+ _ownerCommitment,
+ _counter,
+ _instanceSalt,
+ _ownableIsInitialized,
+ _domain,
+ _name,
+ _symbol,
+ _decimals,
+ _isInitialized,
+ _totalMinted,
+ _totalBurned,
+} from "./node_modules/@openzeppelin/compact-contracts/token/presets/ZOwnableNativeShieldedToken";
+export {
+ _ownerCommitment,
+ _counter,
+ _instanceSalt,
+ _ownableIsInitialized,
+ _domain,
+ _name,
+ _symbol,
+ _decimals,
+ _isInitialized,
+ _totalMinted,
+ _totalBurned,
+};
+
+constructor(
+ ownerId: Bytes<32>,
+ instanceSalt: Bytes<32>,
+ tokenDomain: Bytes<32>,
+ name_: Opaque<"string">,
+ symbol_: Opaque<"string">,
+ decimals_: Uint<8>,
+) {
+ ZOwnableNativeShieldedToken_initialize(
+ ownerId,
+ instanceSalt,
+ tokenDomain,
+ name_,
+ symbol_,
+ decimals_,
+ );
+}
+
+export circuit mint(recipient: ZswapCoinPublicKey, amount: Uint<64>, nonce: Bytes<32>): ShieldedCoinInfo {
+ return ZOwnableNativeShieldedToken_mint(recipient, amount, nonce);
+}
+
+export circuit burn(coin: ShieldedCoinInfo, amount: Uint<64>, refundTo: ZswapCoinPublicKey): Maybe {
+ return ZOwnableNativeShieldedToken_burn(coin, amount, refundTo);
+}
+
+export circuit transferOwnership(newOwnerId: Bytes<32>): [] {
+ ZOwnableNativeShieldedToken_transferOwnership(newOwnerId);
+}
+
+export circuit renounceOwnership(): [] {
+ ZOwnableNativeShieldedToken_renounceOwnership();
+}
+
+export circuit owner(): Bytes<32> {
+ return ZOwnableNativeShieldedToken_owner();
+}
+
+export pure circuit _computeOwnerId(pk: Either, nonce: Bytes<32>): Bytes<32> {
+ return ZOwnableNativeShieldedToken__computeOwnerId(pk, nonce);
+}
+
+export circuit totalMinted(): Uint<128> {
+ return ZOwnableNativeShieldedToken_totalMinted();
+}
+
+export circuit tokenColor(): Bytes<32> {
+ return ZOwnableNativeShieldedToken_tokenColor();
+}
+```
+
+The owner is identified by a commitment, not a key. Before deploying, compute
+`ownerId = _computeOwnerId(pk, secretNonce)` off-chain, where `pk` is the coin public key the owner's wallet
+will supply as `ownPublicKey()` and `secretNonce` is fresh random bytes the owner keeps private.
+The contract cannot check this value at deploy time; any other value leaves the contract with no usable owner and no recovery.
+Every owner transaction then proves knowledge of the pair: the wallet supplies the coin public key, and the
+[wit_secretNonce] witness (inherited from [ZOwnablePK]) supplies the nonce.
+The TypeScript witness implementations in the library repository are test-only and are not published;
+you author the witness for your own DApp.
+The proof server sees `wit_secretNonce`, so run it where the owner controls it.
+
+On the client side, pass fresh random bytes as `nonce` on every `mint`, and persist the `ShieldedCoinInfo` values that
+`mint` and `burn` return: midnight-js encrypts an output to its recipient only when it can resolve the recipient's
+encryption key (its own, or one supplied in `additionalCoinEncPublicKeyMappings`).
+
+`initialize` writes sealed fields, so it belongs in the constructor and must not be exposed as a post-deploy circuit.
+`transferOwnership` is one step and `renounceOwnership` stops minting and burning forever.
+
+### A token family with supply tracking and derived nonces
+
+This contract issues one token type per `domain`, counts supply per domain, and lets the owner mint without
+managing nonces. Burns are open to any holder, since a holder can always burn by sending to the burn address directly;
+routing burns through the contract keeps `totalBurned` closer to the truth.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK"
+ prefix ZOwnablePK_;
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedTokenFamily"
+ prefix Family_;
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenFamilyPublicSupply"
+ prefix Supply_;
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenDerivedNonce"
+ prefix Derived_;
+
+export { ZswapCoinPublicKey, Maybe, ShieldedCoinInfo };
+
+constructor(
+ ownerId: Bytes<32>,
+ instanceSalt: Bytes<32>,
+ name_: Opaque<"string">,
+ symbol_: Opaque<"string">,
+ decimals_: Uint<8>,
+) {
+ ZOwnablePK_initialize(ownerId, instanceSalt);
+ Family_initialize(name_, symbol_, decimals_);
+}
+
+// Owner only. Derived nonces make these mints recipient-public.
+export circuit mint(domain: Bytes<32>, recipient: ZswapCoinPublicKey, amount: Uint<64>): ShieldedCoinInfo {
+ ZOwnablePK_assertOnlyOwner();
+ const coin = Family__mint(domain, recipient, amount, Derived__deriveNonce());
+ Supply__addMinted(domain, amount);
+ return coin;
+}
+
+// Any holder. `coin` is paid into the transaction by the caller's wallet;
+// the change, if any, goes to `refundTo`. Publishes `amount`.
+export circuit burn(
+ domain: Bytes<32>,
+ coin: ShieldedCoinInfo,
+ amount: Uint<128>,
+ refundTo: ZswapCoinPublicKey
+): Maybe {
+ const refund = Family__burn(domain, coin, amount, refundTo);
+ Supply__addBurned(domain, amount);
+ return refund;
+}
+
+export circuit tokenColor(domain: Bytes<32>): Bytes<32> {
+ return Family_tokenColor(domain);
+}
+
+export circuit totalSupply(domain: Bytes<32>): Uint<128> {
+ return Supply_totalSupply(domain);
+}
+```
+
+A `mint` and a `burn` under the same `domain` both touch that domain's supply entry, so only one of them lands per block;
+operations on different domains do not conflict.
diff --git a/content/contracts-compact/0.3.x/non-fungible-token.mdx b/content/contracts-compact/0.3.x/non-fungible-token.mdx
new file mode 100644
index 00000000..5cb1346d
--- /dev/null
+++ b/content/contracts-compact/0.3.x/non-fungible-token.mdx
@@ -0,0 +1,216 @@
+---
+title: NonFungibleToken
+---
+
+{/* links */}
+[non-fungible tokens]: /contracts/5.x/tokens#different-kinds-of-tokens
+[EIP-721]: https://eips.ethereum.org/EIPS/eip-721
+[ERC-165]: https://eips.ethereum.org/EIPS/eip-165
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[_mint]: ./api/non-fungible-token#NonFungibleToken-_mint
+[approve]: ./api/non-fungible-token#NonFungibleToken-approve
+[setApprovalForAll]: ./api/non-fungible-token#NonFungibleToken-setApprovalForAll
+[transferFrom]: ./api/non-fungible-token#NonFungibleToken-transferFrom
+[wit_NonFungibleTokenSK]: ./api/non-fungible-token#NonFungibleToken-wit_NonFungibleTokenSK
+[computeAccountId]: ./api/utils#Utils-computeAccountId
+
+NonFungibleToken is a specification for [non-fungible tokens],
+a type of token where all the units are unique and distinct from each other.
+This module is an approximation of [EIP-721] written in the Compact programming language for the Midnight network.
+
+## ERC721 Compatibility
+
+Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard.
+Some features and behaviors are either not possible, not possible yet,
+or changed because of the vastly different tech stack and Compact language constraints.
+
+***Notable changes***
+
+* ***`Uint<128>` tokenIds*** - Since 256-bit unsigned integers are not supported,
+the library uses the Compact type `Uint<128>`.
+* ***No _baseURI() support*** - Native strings and string operations are not supported within the Compact language,
+so concatenating a base URI + token ID is not possible like in other NFT implementations.
+Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs.
+It's up to the implementation to decide on how to handle this.
+
+***Features and specifications NOT supported***
+
+* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future.
+* ***Uint256 type*** - There's ongoing research on ways to support uint256 in the future.
+* ***Interface*** - Compact currently does not have a way to define a contract interface.
+This library offers modules of contracts with free floating circuits;
+nevertheless, there are no means of enforcing that all circuits are provided.
+* ***ERC-165 Standard*** - Since Compact doesn't provide a way to define a contract interface,
+it's not possible to implement an [ERC-165] like interface standard at this time.
+* ***Safe Transfers*** - It's not possible to implement safe transfers without an [ERC-165]-like
+interface standard at this time.
+
+## Contract-to-contract calls
+
+Contract-to-contract calls are currently not supported in the Compact language.
+Due to this limitation, the current iteration of NonFungibleToken disallows transfers and mints to the `ContractAddress` type.
+Transferring tokens to a contract may result in those tokens being locked forever.
+The NonFungibleToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts.
+
+
+The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning
+`_transfer`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type.
+
+
+## Identity Model
+
+NonFungibleToken uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it through the [wit_NonFungibleTokenSK] witness. The module computes the caller's account identifier
+as `persistentHash(secretKey)`, a commitment that hides the key while providing a stable, pseudonymous on-chain identity.
+That identifier is the `Bytes<32>` (left) branch of the `Either, ContractAddress>` account type
+and is what [approve], [setApprovalForAll], and [transferFrom] check ownership and operator rights against.
+
+What is public on the ledger:
+
+* `_owners`, keyed by token id, and `_balances`, keyed by account identifier (or contract address).
+* `_tokenApprovals`, keyed by token id, and `_operatorApprovals`, keyed by owner and then operator.
+* `_tokenURIs`, `_name`, and `_symbol`.
+
+What the caller holds privately:
+
+* The 32-byte `secretKey`. It never reaches the ledger; only its hash does.
+
+A transfer discloses the token id, the previous owner, and the recipient, because the owner and balance maps are updated with those values.
+A single-token approval discloses the token id and the approved account; an operator approval discloses the caller's identifier, the operator, and the flag.
+
+Because the identifier is `H(secretKey)` with no per-deployment salt, the same key produces the same identity in every contract.
+Users who want cross-contract unlinkability can use a different secret key per contract at the wallet layer.
+
+To derive an account identifier off-chain (for example, to pass as the `recipient` of a mint), use [computeAccountId] from the Utils module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+The TypeScript witness implementation lives with the DApp, not with the library.
+The witnesses in the `compact-contracts` repository are test-only and are not published;
+consumers author their own `wit_NonFungibleTokenSK` that returns the caller's 32-byte secret key.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of the tokens held under that identity,
+ and key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+## Usage
+
+Import the NonFungibleToken module into the implementing contract.
+It's recommended to prefix the module with `NonFungibleToken_` to avoid circuit signature clashes.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken"
+ prefix NonFungibleToken_;
+
+constructor(name: Opaque<"string">, symbol: Opaque<"string">) {
+ NonFungibleToken_initialize(name, symbol);
+}
+
+```
+
+Next, expose the circuits that users may call in the contract.
+
+This library enables extensibility by following the rules of the [Module/Contract Pattern].
+Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts.
+Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens.
+
+```typescript
+export circuit name(): Opaque<"string"> {
+ return NonFungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return NonFungibleToken_symbol();
+}
+
+(...)
+```
+
+The following example is a simple non-fungible token contract that mints an NFT to the passed recipient upon construction.
+
+```typescript
+// SimpleNonFungibleToken.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken"
+ prefix NonFungibleToken_;
+
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ recipient: Either, ContractAddress>,
+ tokenURI: Opaque<"string">,
+) {
+ const tokenId = 1 as Uint<128>;
+ NonFungibleToken_initialize(name, symbol);
+ NonFungibleToken__mint(recipient, tokenId);
+ NonFungibleToken__setTokenURI(tokenId, tokenURI);
+}
+
+export circuit balanceOf(owner: Either, ContractAddress>): Uint<128> {
+ return NonFungibleToken_balanceOf(owner);
+}
+
+export circuit ownerOf(tokenId: Uint<128>): Either, ContractAddress> {
+ return NonFungibleToken_ownerOf(tokenId);
+}
+
+export circuit name(): Opaque<"string"> {
+ return NonFungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return NonFungibleToken_symbol();
+}
+
+export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> {
+ return NonFungibleToken_tokenURI(tokenId);
+}
+
+export circuit approve(to: Either, ContractAddress>, tokenId: Uint<128>): [] {
+ NonFungibleToken_approve(to, tokenId);
+}
+
+export circuit getApproved(tokenId: Uint<128>): Either, ContractAddress> {
+ return NonFungibleToken_getApproved(tokenId);
+}
+
+export circuit setApprovalForAll(
+ operator: Either, ContractAddress>,
+ approved: Boolean
+): [] {
+ NonFungibleToken_setApprovalForAll(operator, approved);
+}
+
+export circuit isApprovedForAll(
+ owner: Either, ContractAddress>,
+ operator: Either, ContractAddress>
+): Boolean {
+ return NonFungibleToken_isApprovedForAll(owner, operator);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ tokenId: Uint<128>
+): [] {
+ NonFungibleToken_transferFrom(fromAddress, to, tokenId);
+}
+
+```
diff --git a/content/contracts-compact/0.3.x/ownable.mdx b/content/contracts-compact/0.3.x/ownable.mdx
new file mode 100644
index 00000000..2c25eb5b
--- /dev/null
+++ b/content/contracts-compact/0.3.x/ownable.mdx
@@ -0,0 +1,439 @@
+---
+title: Ownable
+---
+
+{/* links */}
+[initialize]: api/ownable#Ownable-initialize
+[transferOwnership]: api/ownable#Ownable-transferOwnership
+[_transferOwnership]: api/ownable#Ownable-_transferOwnership
+[_unsafeTransferOwnership]: api/ownable#Ownable-_unsafeTransferOwnership
+[_unsafeUncheckedTransferOwnership]: api/ownable#Ownable-_unsafeUncheckedTransferOwnership
+[computeAccountId]: api/utils#Utils-computeAccountId
+[assertOnlyOwner]: api/ownable#Ownable-assertOnlyOwner
+[Ownable API]: api/ownable#Ownable
+
+[ZOwnablePK_initialize]: api/ownable#ZOwnablePK-initialize
+[ZOwnablePK_transferOwnership]: api/ownable#ZOwnablePK-transferOwnership
+[ZOwnablePK_assertOnlyOwner]: api/ownable#ZOwnablePK-assertOnlyOwner
+[_computeOwnerId]: api/ownable#ZOwnablePK-_computeOwnerId
+[wit_secretNonce]: api/ownable#ZOwnablePK-wit_secretNonce
+
+[ed25519]: https://ed25519.cr.yp.to/
+[rfc6979]: https://datatracker.ietf.org/doc/html/rfc6979
+[AccessControl]: ./access-control.mdx
+[Utils]: ./utils.mdx
+
+## Access Control Defined
+
+Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts.
+The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things.
+It is therefore critical to understand how you implement it, lest someone else steals your whole system.
+This library provides a variety of access control modules to suit your application and privacy needs.
+
+## Ownership and `Ownable`
+
+The most common and basic form of access control is the concept of ownership:
+there's an account that is the owner of a contract and can do administrative tasks on it.
+This approach is perfectly reasonable for contracts that have a single administrative user.
+
+This library provides an Ownable module for implementing ownership in your contracts.
+The initial owner must be set by using the [initialize] circuit during construction.
+This can later be changed with [transferOwnership].
+
+
+ For more granular, role-based access control, see [AccessControl].
+
+
+### Identity Model
+
+Ownable uses a witness-derived identity scheme. The owner proves knowledge of a secret key
+by injecting it via the `wit_OwnableSK` witness. The module computes an account identifier
+as `H(secretKey)` (a `persistentHash` of the key), which is a commitment that hides the key while providing a stable,
+pseudonymous on-chain identity.
+
+What is public and what is private:
+
+* **On the ledger**: `_owner` holds the account identifier `H(secretKey)` (or a contract address), and `_isInitialized`.
+* **Held privately by the owner**: the 32-byte `secretKey`, supplied through `wit_OwnableSK` at proving time.
+ The key never reaches the ledger; [assertOnlyOwner] recomputes the identifier inside the proof and compares it against `_owner`.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+To derive an account identifier off-chain (e.g. for the `initialOwner` parameter during construction),
+use [computeAccountId] from the [Utils] module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The same value can be computed directly in TypeScript with the Compact runtime:
+
+```typescript
+import {
+ CompactTypeBytes,
+ CompactTypeVector,
+ persistentHash,
+} from '@midnight-ntwrk/compact-runtime';
+
+const computeAccountId = (secretKey: Uint8Array): Uint8Array => {
+ const rt_type = new CompactTypeVector(1, new CompactTypeBytes(32));
+ return persistentHash(rt_type, [secretKey]);
+};
+```
+
+The `secretKey` is a 32-byte cryptographically secure random value that the owner keeps private.
+Because the identifier has no per-deployment salt, the same key produces the same identity across all contracts.
+Owners who want cross-contract unlinkability should use a different key per contract.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of ownership.
+ Key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+### Ownership Transfers
+
+Ownership can only be transferred to `Bytes<32>` account identifiers through the main transfer circuits
+([transferOwnership] and [_transferOwnership]).
+
+In other words, ownership transfers to contract addresses are disallowed through these circuits.
+This is because Compact currently does not support contract-to-contract calls, which means if a contract is granted ownership,
+the owner contract cannot directly call the protected circuit.
+
+### Unsafe Circuits
+
+This module offers circuits that allow ownership to be granted to contract addresses
+([_unsafeTransferOwnership] and [_unsafeUncheckedTransferOwnership]).
+
+Note that the circuit names are very explicit ("unsafe").
+Until contract-to-contract calls are supported,
+there is no direct way for a contract to call circuits of other contracts or transfer ownership back to a user.
+
+
+ The unsafe circuits are planned to become deprecated once contract-to-contract calls become available.
+
+
+### Usage
+
+Import the Ownable module into the implementing contract.
+It's recommended to prefix the module with `Ownable_` to avoid circuit signature clashes.
+
+The `initialOwner` should be an account identifier derived off-chain using `Utils_computeAccountId(secretKey)`:
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(
+ initialOwner: Either, ContractAddress>
+) {
+ Ownable_initialize(initialOwner);
+}
+```
+
+To protect a circuit so that only the contract owner may call it,
+insert the `assertOnlyOwner` circuit in the beginning of the circuit body like this:
+
+```ts
+export circuit mySensitiveCircuit(): [] {
+ Ownable_assertOnlyOwner();
+
+ // Do something
+}
+```
+
+Contracts may expose [transferOwnership] to allow the owner to transfer ownership.
+The `newOwner` should be an account identifier derived by the new owner using `Utils_computeAccountId(secretKey)`:
+
+```ts
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ Ownable_transferOwnership(newOwner);
+}
+```
+
+Here's a complete contract showcasing how to integrate the Ownable module and protect sensitive circuits.
+
+```ts
+// SimpleOwnable.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+/**
+ * Set `initialOwner` as the owner of the contract.
+ * `initialOwner` should be derived off-chain using Utils_computeAccountId(secretKey).
+ */
+constructor(initialOwner: Either, ContractAddress>) {
+ Ownable_initialize(initialOwner);
+}
+
+/**
+ * The current owner of the contract.
+ */
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+/**
+ * Transfers ownership of the contract.
+ * Can only be called by the current owner.
+ * `newOwner` should be derived off-chain by the new owner using Utils_computeAccountId(secretKey).
+ */
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ Ownable_transferOwnership(newOwner);
+}
+
+/**
+ * Leaves the contract without an owner.
+ * Can only be called by the current owner.
+ * Renouncing ownership means `mySensitiveCircuit` can never be called again.
+ */
+export circuit renounceOwnership(): [] {
+ Ownable_renounceOwnership();
+}
+
+/**
+ * This is the protected circuit that only the current owner can call.
+ */
+export circuit mySensitiveCircuit(): [] {
+ // Protects the circuit
+ Ownable_assertOnlyOwner();
+
+ // Do something
+}
+```
+
+
+ For more complex logic,
+ contracts may transfer ownership to another user irrespective of the caller by leveraging [_transferOwnership].
+ This is generally more useful when contract addresses are the owner or when a contract has a unique deployment process.
+
+
+## Shielded Ownership and `ZOwnablePK`
+
+Privacy-preserving access control is a fundamental building block for confidential smart contracts on Midnight.
+While the `Ownable` module stores the owner's account identifier on-chain,
+many applications require administrative control without revealing who holds that authority.
+
+### Privacy-First Ownership
+
+The `ZOwnablePK` module implements shielded ownership meaning administrative control without identity disclosure.
+The owner's public key is never revealed on-chain;
+instead, the contract stores only a cryptographic commitment that proves ownership without exposing the underlying identity.
+
+This is useful in scenarios such as private treasuries, anonymous governance,
+or any application where revealing the administrator's identity may compromise the system's confidentiality.
+
+What is public and what is private:
+
+* **On the ledger**: `_ownerCommitment`, the transfer `_counter`, the sealed `_instanceSalt`, and `_isInitialized`.
+ Ownership changes disclose only the new commitment, never the owner identifier that produced it.
+* **Held privately by the owner**: the secret nonce, supplied through the [wit_secretNonce] witness,
+ and the public key, supplied through `ownPublicKey()`. [ZOwnablePK_assertOnlyOwner] recomputes the commitment
+ from both inside the proof and compares it against `_ownerCommitment`.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+### Commitment Scheme
+
+The `ZOwnablePK` module employs a two-layer cryptographic commitment scheme designed to provide privacy,
+unlinkability, and collision resistance across deployments and ownership transfers.
+
+#### Owner ID Computation
+
+The foundation of the system is the owner identifier, computed as:
+
+```typescript
+id = SHA256(pk || nonce)
+```
+
+Where `pk` is the owner's public key and `nonce` is a secret value that may be either randomly generated for maximum privacy
+or deterministically derived for recoverability.
+This identifier serves as a privacy-preserving alternative to exposing the raw public key,
+ensuring the owner's identity remains confidential.
+
+#### Owner Commitment Computation
+
+The final ownership commitment stored on-chain is computed as:
+
+```typescript
+commitment = SHA256(id || instanceSalt || counter || pad(32, "ZOwnablePK:shield:"))
+```
+
+This multi-element hash provides several security properties:
+
+* `id`: The privacy-preserving owner identifier described above.
+* `instanceSalt`: A unique per-deployment salt that prevents commitment collisions across different contract instances,
+even when the same owner and nonce are used. It must be nonzero, and it should be a fresh random value per deployment:
+a reused salt links the same owner across deployments.
+* `counter`: Incremented with each ownership transfer to ensure unlinkability.
+Each transfer produces a completely different commitment even with the same underlying owner.
+* `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions
+with other commitment schemes and enables safe protocol extensions.
+
+#### Security Properties
+
+This commitment scheme ensures that:
+
+* Public keys are never revealed on-chain.
+* Observers cannot correlate past and future ownership.
+* Cross-contract collisions are prevented through instance-specific salting.
+
+
+ `ownPublicKey()` is a witness: it returns the caller's Zswap coin public key, and nothing in the circuit proves
+ that the caller holds the matching private key. The secret nonce is therefore the owner credential:
+ anyone holding the nonce and the owner's public key can pass [ZOwnablePK_assertOnlyOwner].
+ Generate the nonce as 32 bytes from a CSPRNG (or a derivation with equivalent entropy) and keep it private.
+
+
+### Nonce Generation Strategies
+
+The choice of nonce generation strategy represents a fundamental trade-off between simplicity/security and recoverability.
+Both approaches are valid, and the best choice depends on your specific threat model and operational requirements.
+
+#### Random Nonce
+
+Generating a cryptographically strong random nonce provides the strongest privacy guarantees:
+
+```typescript
+const randomNonce = crypto.getRandomValues(new Uint8Array(32));
+const ownerId = ZOwnablePK._computeOwnerId(publicKey, randomNonce);
+```
+
+This approach is easy to generate and ensures maximum unlinkability.
+However, it requires secure backup of both the public key and the nonce.
+**Loss of either component results in permanent, irrecoverable loss of ownership.**
+
+#### Deterministic Nonce
+
+Deriving the nonce deterministically enables recovery through derivation schemes. Some examples:
+
+* `H(passphrase + context)` — recoverable from passphrase only, but passphrase becomes critical single point of failure.
+* `H(publicKey + userPassphrase + context)` — requires both public key and passphrase.
+* `H(signature + context) where signature = sign(context)` — leverages wallet without exposing private key.
+
+
+ When using signature-based nonce derivation,
+ ensure the wallet/library uses deterministic signatures ([ed25519] or [rfc6979] for ECDSA).
+ Non-deterministic signatures will generate different nonces on each signing, making recovery impossible.
+ Test the implementation by signing the same message twice then verify that the signatures match.
+
+
+**Context-Dependent Derivations:**
+
+* Include contract address, deployment timestamp, user ID, etc.
+* Trade-off: more context is more unique but harder to recreate.
+
+
+ Approaches that avoid private key exposure (public key + passphrase, signature-based)
+ are generally recommended for operational security.
+
+
+**Security Considerations**
+
+The `ZOwnablePK` module remains agnostic to nonce generation methods,
+placing the security/convenience decision entirely with the user.
+Key considerations include:
+
+* **Backup requirements**: Random nonces require additional secure storage.
+* **Recovery scenarios**: Deterministic nonces enable recovery.
+* **Cross-contract correlation**: Reusing nonce strategies may reduce privacy across deployments.
+* **Rotation costs**: Changing nonces requires ownership transfer transactions with associated costs.
+
+Users should carefully evaluate their threat model, operational requirements, and privacy needs when selecting a nonce generation strategy,
+as this choice cannot be easily changed without transferring ownership.
+
+### Usage
+
+Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits.
+It's recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes.
+
+```typescript
+// MyZOwnablePKContract.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK"
+ prefix ZOwnablePK_;
+
+constructor(
+ initOwnerId: Bytes<32>,
+ instanceSalt: Bytes<32>,
+) {
+ ZOwnablePK_initialize(initOwnerId, instanceSalt);
+}
+
+export circuit owner(): Bytes<32> {
+ return ZOwnablePK_owner();
+}
+
+export circuit transferOwnership(newOwnerId: Bytes<32>): [] {
+ return ZOwnablePK_transferOwnership(disclose(newOwnerId));
+}
+
+export circuit renounceOwnership(): [] {
+ return ZOwnablePK_renounceOwnership();
+}
+```
+
+[ZOwnablePK_initialize] rejects a zero `ownerId` and a zero `instanceSalt`.
+
+Similar to the [Ownable](#usage) module,
+circuits can be protected so that only the contract owner may call them by adding `assertOnlyOwner` as the first line in the circuit body:
+
+```typescript
+export circuit mySensitiveCircuit(): [] {
+ ZOwnablePK_assertOnlyOwner();
+
+ // Do something
+}
+```
+
+This covers the basics for creating a contract, but before deploying the contract,
+the owner's id must be derived for the commitment scheme because it's required to deploy the contract.
+
+First, the owner needs to generate a secret nonce that's stored in the owner's private state.
+See [Nonce Generation Strategies](#nonce-generation-strategies).
+
+Once the owner has the secret nonce generated, they can derive their owner ID:
+
+```typescript
+import {
+ CompactTypeBytes,
+ CompactTypeVector,
+ persistentHash,
+} from '@midnight-ntwrk/compact-runtime';
+import { getRandomValues } from 'node:crypto';
+
+// Owner ID
+const generateId = (
+ pk: Uint8Array,
+ nonce: Uint8Array,
+): Uint8Array => {
+ const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32));
+ return persistentHash(rt_type, [pk, nonce]);
+};
+
+// Instance salt for the constructor (must be nonzero)
+const generateInstanceSalt = (): Uint8Array => {
+ return getRandomValues(new Uint8Array(32));
+}
+```
+
+
+ Another way to get the user ID is to expose [_computeOwnerId] in the contract and call this circuit off-chain through a contract simulator.
+
diff --git a/content/contracts-compact/0.3.x/security.mdx b/content/contracts-compact/0.3.x/security.mdx
new file mode 100644
index 00000000..b2824704
--- /dev/null
+++ b/content/contracts-compact/0.3.x/security.mdx
@@ -0,0 +1,282 @@
+---
+title: Security
+---
+
+{/* links */}
+[Ownable]: ./ownable
+[Utils guide]: ./utils#account-identifiers
+[assertPaused]: ./api/security#Pausable-assertPaused
+[assertNotPaused]: ./api/security#Pausable-assertNotPaused
+[Initializable API]: ./api/security#Initializable
+[Allowlist API]: ./api/security#Allowlist
+[Blocklist API]: ./api/security#Blocklist
+[isAllowed]: ./api/security#Allowlist-isAllowed
+[assertAllowed]: ./api/security#Allowlist-assertAllowed
+[_allow]: ./api/security#Allowlist-_allow
+[_disallow]: ./api/security#Allowlist-_disallow
+[isBlocked]: ./api/security#Blocklist-isBlocked
+[assertNotBlocked]: ./api/security#Blocklist-assertNotBlocked
+[_block]: ./api/security#Blocklist-_block
+[_unblock]: ./api/security#Blocklist-_unblock
+[compact#270]: https://github.com/LFDT-Minokawa/compact/issues/270
+
+The following documentation provides context, reasoning, and examples of modules found in the Security directory.
+
+## Initializable
+
+The Initializable module provides a simple mechanism that mimics the functionality of a constructor.
+More specifically, it enables logic to be performed once and only once to set up a contract's initial state.
+This is useful when a constructor cannot be used,
+for example when there are circular dependencies at construction time.
+
+The initializable pattern ensures that implementing contracts:
+
+- Don't allow circuit calls until the contract is initialized.
+- Can only initialize the contract once.
+
+
+ Use this module only at the top contract layer, and import it into at most one module per contract.
+ The Compact compiler collapses a shared import's ledger state inconsistently depending on directory layout
+ ([compact#270]): when two modules in the same directory both import Initializable,
+ their `_isInitialized` flags share a single ledger slot, so initializing one marks the other as initialized
+ and the other can never be initialized on its own.
+ For this reason the OpenZeppelin modules do not import Initializable; each one tracks its own
+ `_isInitialized` ledger flag inline.
+
+
+### Usage
+
+```ts
+// CustomContractStateSetup.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Initializable"
+ prefix Initializable_;
+
+export ledger _fieldAfterDeployment: Field;
+
+export circuit doSomethingBeforeInitialized(): [] {
+ // Ensure action can only be done prior to custom initialization
+ Initializable_assertNotInitialized();
+ // Do the thing
+}
+
+export circuit setFieldAfterDeployment(f: Field): [] {
+ // Initialize so the circuit cannot be called again
+ Initializable_initialize();
+ _fieldAfterDeployment = disclose(f);
+}
+
+export circuit checkFieldAfterDeployment(): Field {
+ // Can only be called after the contract is initialized
+ Initializable_assertInitialized();
+ return _fieldAfterDeployment;
+}
+```
+
+See the [Initializable API] for the full list of circuits.
+
+## Pausable
+
+The Pausable module allows contracts to implement an emergency stop mechanism.
+This can be useful for scenarios such as preventing trades until the end of an evaluation period
+or having an emergency switch to freeze all transactions in the event of a large bug.
+
+To become pausable, the contract should include `pause` and `unpause` circuits (which should be protected).
+For circuits that should be available only when paused or not,
+insert calls to [assertPaused] and [assertNotPaused] respectively.
+
+### Usage
+
+For example (using the [Ownable] module for access control):
+
+```ts
+// OwnablePausable.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(initOwner: Either, ContractAddress>) {
+ Ownable_initialize(initOwner);
+}
+
+export circuit pause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__pause();
+}
+
+export circuit unpause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__unpause();
+}
+
+export circuit whenNotPaused(): [] {
+ Pausable_assertNotPaused();
+ // Do something
+}
+
+export circuit whenPaused(): [] {
+ Pausable_assertPaused();
+ // Do something
+}
+```
+
+## Allowlist
+
+The Allowlist module is a minimal, reusable permit-list keyed by a `Bytes<32>` account identifier.
+It answers a single question: is this account a member?
+It backs use cases such as KYC allowlists, permissioned mints, and membership gates.
+
+The module is a membership mechanism, not a policy.
+It holds no access control and no notion of when the list is enforced:
+
+- The mutators [_allow] and [_disallow] are ungated building blocks.
+ The implementing contract must protect them with its own access control module,
+ in the same way a token protects `_mint` and `_burn`.
+- The implementing contract decides where [assertAllowed] applies,
+ for example only on transfers, only on mints, or only while an "eligibility required" flag is set.
+- [isAllowed] reads membership without asserting it.
+
+### What is public
+
+The approved set lives in the public ledger field `_allowed: Set>`.
+Membership is publicly readable and the approved set is publicly enumerable.
+Every circuit discloses the `account` it is called with, so the identifier reaches the ledger as a public input.
+This is the right posture for transparent or public-graph assets.
+An asset whose transaction graph is meant to stay private should not use a public allowlist,
+since the published candidate set would bound its anonymity set.
+
+The `Bytes<32>` keys are account identifiers as derived by `Utils_computeAccountId`;
+see the [Utils guide] for how to derive one off-chain.
+
+### Usage
+
+The following example is a token whose recipients must be approved by the owner before they can receive tokens.
+Membership changes are gated behind [Ownable], and the gate is enforced on `transfer`:
+
+```ts
+// PermissionedToken.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Allowlist"
+ prefix Allowlist_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ initialOwner: Either, ContractAddress>,
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+) {
+ Ownable_initialize(initialOwner);
+ FungibleToken_initialize(name, symbol, decimals);
+}
+
+// Membership changes are protected by the owner.
+export circuit allow(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Allowlist__allow(account);
+}
+
+export circuit disallow(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Allowlist__disallow(account);
+}
+
+export circuit isAllowed(account: Bytes<32>): Boolean {
+ return Allowlist_isAllowed(account);
+}
+
+// The implementing contract decides where the gate applies.
+export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean {
+ assert(to.is_left, "PermissionedToken: recipient must be an account identifier");
+ Allowlist_assertAllowed(to.left);
+ return FungibleToken_transfer(to, value);
+}
+```
+
+See the [Allowlist API] for the full list of circuits.
+
+## Blocklist
+
+The Blocklist module is a minimal, reusable deny-list keyed by a `Bytes<32>` account identifier.
+It answers a single question: is this account blocked?
+It backs use cases such as sanctions lists, account freezes, and abuse denials.
+
+As with Allowlist, the module is a membership mechanism, not a policy:
+
+- The mutators [_block] and [_unblock] are ungated building blocks that the implementing contract
+ must protect with its own access control module.
+- The implementing contract decides where [assertNotBlocked] applies.
+- [isBlocked] reads membership without asserting it.
+
+### What is public
+
+The blocked set lives in the public ledger field `_blocked: Set>`.
+Membership is publicly readable and the blocked set is publicly enumerable,
+and every circuit discloses the `account` it is called with.
+As with Allowlist, this is the right posture for transparent or public-graph assets only.
+
+### Usage
+
+The following example lets the owner freeze accounts, and refuses transfers to a frozen recipient:
+
+```ts
+// FreezableToken.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Blocklist"
+ prefix Blocklist_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ initialOwner: Either, ContractAddress>,
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+) {
+ Ownable_initialize(initialOwner);
+ FungibleToken_initialize(name, symbol, decimals);
+}
+
+// Freezing and unfreezing are protected by the owner.
+export circuit block(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Blocklist__block(account);
+}
+
+export circuit unblock(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Blocklist__unblock(account);
+}
+
+export circuit isBlocked(account: Bytes<32>): Boolean {
+ return Blocklist_isBlocked(account);
+}
+
+// The implementing contract decides where the gate applies.
+export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean {
+ assert(to.is_left, "FreezableToken: recipient must be an account identifier");
+ Blocklist_assertNotBlocked(to.left);
+ return FungibleToken_transfer(to, value);
+}
+```
+
+See the [Blocklist API] for the full list of circuits.
diff --git a/content/contracts-compact/0.3.x/shielded-access-control.mdx b/content/contracts-compact/0.3.x/shielded-access-control.mdx
new file mode 100644
index 00000000..578933c0
--- /dev/null
+++ b/content/contracts-compact/0.3.x/shielded-access-control.mdx
@@ -0,0 +1,226 @@
+---
+title: ShieldedAccessControl
+---
+
+{/* links */}
+[Role-Based Access Control (RBAC)]: https://en.wikipedia.org/wiki/Role-based_access_control
+[AccessControl]: ./access-control.mdx
+[ShieldedAccessControl API]: api/shielded-access-control
+
+[assertOnlyRole]: api/shielded-access-control#ShieldedAccessControl-assertOnlyRole
+[canProveRole]: api/shielded-access-control#ShieldedAccessControl-canProveRole
+[grantRole]: api/shielded-access-control#ShieldedAccessControl-grantRole
+[_grantRole]: api/shielded-access-control#ShieldedAccessControl-_grantRole
+[revokeRole]: api/shielded-access-control#ShieldedAccessControl-revokeRole
+[_revokeRole]: api/shielded-access-control#ShieldedAccessControl-_revokeRole
+[renounceRole]: api/shielded-access-control#ShieldedAccessControl-renounceRole
+[_setRoleAdmin]: api/shielded-access-control#ShieldedAccessControl-_setRoleAdmin
+[getRoleAdmin]: api/shielded-access-control#ShieldedAccessControl-getRoleAdmin
+[computeAccountId]: api/shielded-access-control#ShieldedAccessControl-computeAccountId
+[DEFAULT_ADMIN_ROLE]: api/shielded-access-control#ShieldedAccessControl-DEFAULT_ADMIN_ROLE
+[_operatorRoles]: api/shielded-access-control#ShieldedAccessControl-_operatorRoles
+[_grantedRoleNullifiers]: api/shielded-access-control#ShieldedAccessControl-_grantedRoleNullifiers
+[_roleCommitmentNullifiers]: api/shielded-access-control#ShieldedAccessControl-_roleCommitmentNullifiers
+[wit_secretKey]: api/shielded-access-control#ShieldedAccessControl-wit_secretKey
+[wit_getRoleCommitmentPath]: api/shielded-access-control#ShieldedAccessControl-wit_getRoleCommitmentPath
+
+This module provides a **privacy-preserving** role-based access control mechanism.
+Unlike [AccessControl], which stores role assignments in a public map,
+ShieldedAccessControl stores roles as Merkle tree commitments which hides _who_ holds a role
+while still allowing on-chain verification of authorization.
+
+If your contract does not require privacy for role holders, the simpler [AccessControl] module is recommended.
+
+## Why Shielded Access Control?
+
+In many applications, revealing which accounts hold administrative or privileged roles
+creates a security and privacy risk. ShieldedAccessControl addresses this by ensuring
+that role holders are never identified on-chain. Instead, callers prove ownership of a role
+using zero-knowledge proofs against Merkle tree commitments.
+
+This is useful in scenarios such as:
+
+- **Private governance**: voters or committee members prove eligibility without revealing identity.
+- **Confidential operations**: admins can manage a system without their addresses being publicly linked to the contract.
+- **Pseudonymous authorization**: role holders maintain a stable pseudonymous identity for the lifetime of their role, but that identity cannot be linked to a real-world identity without key compromise.
+
+## How It Works
+
+ShieldedAccessControl is built on three cryptographic primitives:
+
+**Role commitments** are Merkle tree leaves that bind a role to an account without revealing either.
+When a role is granted, a commitment is inserted into a Merkle tree:
+
+```
+roleCommitment = SHA256(role || accountId || instanceSalt || commitmentDomain)
+```
+
+**Account identifiers** are privacy-preserving identity commitments derived from a secret key
+and a per-deployment instance salt. A single secret key can be used across multiple roles within the same contract:
+
+```
+accountId = SHA256(secretKey || instanceSalt || accountIdDomain)
+```
+
+**Role nullifiers** are one-time burn tokens. When a role is revoked, its nullifier is inserted into a set,
+permanently invalidating the corresponding commitment:
+
+```
+roleNullifier = SHA256(roleCommitment || nullifierDomain)
+```
+
+The `instanceSalt` is a per-deployment random value that prevents commitment collisions across contracts
+and ensures that the same secret key produces different identities in different deployments.
+
+### Public ledger and private witnesses
+
+What is on the ledger:
+
+- [_operatorRoles]: the Merkle tree of role commitments. Inserts expose only the internal leaf hash, not the commitment value.
+- [_grantedRoleNullifiers]: the nullifier of every `(role, accountId)` pairing ever granted.
+- [_roleCommitmentNullifiers]: the nullifier of every pairing that has been revoked.
+- `_adminRoles`, the sealed `_instanceSalt`, and `_isInitialized`.
+
+What each role holder keeps private, and supplies through witnesses at proving time:
+
+- The `secretKey`, via [wit_secretKey]. It is hashed with `_instanceSalt` inside the proof and never reaches the ledger.
+- The Merkle path to their role commitment, via [wit_getRoleCommitmentPath].
+ The consumer's witness implementation builds this path from the exported `_operatorRoles` state.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+## Using `ShieldedAccessControl`
+
+Usage follows the same pattern as [AccessControl] which is to define role identifiers,
+grant them during construction, and enforce them with [assertOnlyRole].
+The key difference is that role assignments are stored as Merkle tree commitments rather than in a public map,
+and revocation uses a nullifier scheme rather than simply flipping a boolean.
+Account identifiers also incorporate a per-instance salt, preventing cross-contract correlation of the same secret key.
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl"
+ prefix SAC_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+
+constructor(
+ instanceSalt: Bytes<32>,
+ defaultAdmin: SAC_AccountIdentifier,
+ minter: SAC_AccountIdentifier
+) {
+ SAC_initialize(instanceSalt);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ // Grant the default admin role and the minter role
+ SAC__grantRole(SAC_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+ SAC__grantRole(MINTER_ROLE as SAC_RoleIdentifier, minter);
+}
+
+export circuit mint(/* ... */): [] {
+ SAC_assertOnlyRole(MINTER_ROLE as SAC_RoleIdentifier);
+ // ... minting logic
+}
+```
+
+
+ The `instanceSalt` must be nonzero and generated using a cryptographically secure random number generator
+ (e.g. `crypto.getRandomValues()`) prior to deployment. Weak or predictable salts weaken privacy guarantees.
+
+
+### Deriving Account Identifiers
+
+Before an account can be granted a role, the grantor needs the grantee's `AccountIdentifier`.
+This is derived off-chain using [computeAccountId]:
+
+```ts
+const accountId = SAC_computeAccountId(secretKey, instanceSalt);
+```
+
+The `secretKey` is a 32-byte cryptographically secure random value that the user keeps private.
+The `instanceSalt` is the same value used during contract initialization.
+
+
+ The `secretKey` must never be logged, persisted in plaintext, or exposed in browser devtools.
+ Loss of the key means loss of all roles held under that identity. Key exposure allows impersonation
+ and retroactive deanonymization.
+
+
+### Granting and Revoking Roles
+
+Role management works similarly to [AccessControl]. Every role has an associated admin role.
+Only accounts that can prove they hold the admin role may call [grantRole] and [revokeRole].
+By default, all roles are administered by [DEFAULT_ADMIN_ROLE].
+Custom admin relationships can be set using [_setRoleAdmin].
+
+Each `(role, accountId)` pairing has exactly one lifecycle:
+
+- **Grant**: the first grant inserts the role commitment into the Merkle tree and records the pairing's nullifier
+ in [_grantedRoleNullifiers]. Granting a pairing that is already active is a no-op, so a pairing never consumes
+ more than one tree slot.
+- **Revoke**: revoking (or renouncing) inserts the nullifier into [_roleCommitmentNullifiers].
+ A pairing that was never granted cannot be revoked; the call reverts with `role was never granted`.
+
+There is one important difference from unshielded AccessControl:
+**revocation is permanent for a given identity**. When a role is revoked,
+its nullifier is burned, and the same `(role, accountId)` pairing can never be granted again.
+To be re-authorized, the user must generate a new `secretKey` to produce a new `accountId`.
+
+Other roles held under the old secret key remain valid. The user will need to retain both keys
+until all roles under the old key are no longer needed.
+
+### Renouncing Roles
+
+[renounceRole] allows a role holder to voluntarily give up their own role.
+This is useful if a trusted device is compromised and the holder wants to immediately invalidate their credentials.
+The caller must confirm their `accountId` to prevent accidental renunciation,
+and the pairing must have been granted. Consumers with no need for self-revocation should not export this circuit.
+
+### Proving Role Ownership
+
+Two circuits are available for verifying role ownership:
+
+- [assertOnlyRole]: reverts if the caller cannot prove they hold the role. Use this to gate access to circuits.
+- [canProveRole]: returns a boolean without reverting. Useful for conditional logic where lacking a role is not an error.
+
+Both circuits disclose a nullifier, which means every call made under the same role instance
+can be linked by an outside observer. This provides **pseudonymity, not anonymity**.
+
+## Privacy Considerations
+
+ShieldedAccessControl provides strong privacy guarantees, but it's important to understand what is and isn't hidden:
+
+**What observers CAN see:**
+- When roles are granted and revoked
+- All role identifiers and which are admin identifiers (disclosed via [getRoleAdmin], [_setRoleAdmin], [grantRole], and [revokeRole])
+- How many admins exist
+- All role nullifiers (disclosed via `Set` operations in every grant, revoke, and role-proof transaction)
+- The same nullifier across calls, allowing them to link all actions performed under the same role instance over time (pseudonymity, not anonymity)
+- Correlation between a grant and its revocation (the same nullifier appears in both transactions)
+- Which role is being granted or revoked when the admin-checked circuits ([grantRole], [revokeRole]) are used, since those disclose the role identifier in the same transaction
+- The number of distinct `(role, accountId)` pairings ever granted across all roles; per-role counts are also inferable when the admin-checked circuits are used
+
+**What observers CANNOT see:**
+- The identity of any role holder, so long as secret keys are kept private and generated using cryptographically secure random values
+- The value of role commitments stored in the Merkle tree (MerkleTree inserts hash the value internally; only the internal leaf hash appears as a public input, unlike Set operations where values are directly visible)
+
+Users can be retroactively deanonymized if their secret key is exposed.
+
+
+ The internal circuits ([_grantRole], [_revokeRole]) do not disclose the role identifier, offering stronger privacy
+ when wrapped with custom authorization logic. The trade-off is that the implementing contract takes full
+ responsibility for access control.
+
+
+## Security Considerations
+
+- The `_operatorRoles` Merkle tree has a **fixed capacity of 2^20 leaf slots**. Each distinct `(role, accountId)` pairing consumes one slot, re-granting an active pairing is a no-op, and slots cannot be reclaimed after revocation. Once exhausted, the contract can never grant a new role. Monitor slot consumption off-chain (`firstFree()` on the exported tree in TypeScript, `isFull()` in a circuit) and plan capacity accordingly.
+- A single secret key compromise exposes **all roles** held by that user within the contract instance. Users requiring compartmentalization between roles should use separate keys per role. On compromise, coordinate with admins to revoke every role under the compromised `accountId` and re-grant under a new identity.
+- A pairing can only be revoked after it was granted, so the nullifier set holds at most one entry per granted pairing. Admin trust is still a fundamental assumption: an admin can revoke any granted pairing, and revocation is irreversible.
+- The SHA256 hashing function is used throughout.
+
+
+ For the full API reference, see the [ShieldedAccessControl API].
+
diff --git a/content/contracts-compact/0.3.x/utils.mdx b/content/contracts-compact/0.3.x/utils.mdx
new file mode 100644
index 00000000..a7589eaa
--- /dev/null
+++ b/content/contracts-compact/0.3.x/utils.mdx
@@ -0,0 +1,114 @@
+---
+title: Utils
+---
+
+{/* links */}
+[Utils API]: ./api/utils#Utils
+[computeAccountId]: ./api/utils#Utils-computeAccountId
+[zeroAccount]: ./api/utils#Utils-zeroAccount
+[isTargetZero]: ./api/utils#Utils-isTargetZero
+[selfAsRecipient]: ./api/utils#Utils-selfAsRecipient
+[Ownable]: ./ownable
+[AccessControl]: ./access-control
+
+The Utils module provides miscellaneous circuits and common utilities for Compact contract development.
+Most of them are pure circuits with no ledger state, so they can be called from a circuit,
+from a constructor, or off-chain through the compiled contract.
+
+## Account identifiers
+
+The [Ownable] and [AccessControl] modules, and the token modules, identify a caller by a `Bytes<32>` account identifier
+rather than by a public key.
+The caller proves knowledge of a 32-byte secret key through a witness, and the module derives the identifier as:
+
+```
+accountId = H(secretKey)
+```
+
+where `H` is `persistentHash`.
+[computeAccountId] is the shared derivation: it is a pure circuit, so the same function that the modules
+evaluate inside a proof can be evaluated ahead of time to produce the identifier a contract expects as input,
+for example the `initialOwner` of an Ownable contract, the `defaultAdmin` of an AccessControl contract,
+or the recipient of a token mint.
+
+The witnesses that supply the secret key are authored by the implementing contract's TypeScript side.
+The witnesses in the library repository are test-only and are not published with the package.
+
+### Deriving an identifier off-chain
+
+Any of the following produce the same identifier for the same key:
+
+* Call the pure circuit through the compiled contract: `pureCircuits.computeAccountId(secretKey)`
+ (exposed by any contract that re-exports it as a `pure circuit`).
+* Compute it directly with `@midnight-ntwrk/compact-runtime`:
+
+```typescript
+import {
+ CompactTypeBytes,
+ CompactTypeVector,
+ persistentHash,
+} from '@midnight-ntwrk/compact-runtime';
+
+const accountIdOf = (secretKey: Uint8Array): Uint8Array =>
+ persistentHash(new CompactTypeVector(1, new CompactTypeBytes(32)), [secretKey]);
+```
+
+The secret key must be a 32-byte cryptographically secure random value (`crypto.getRandomValues()` or equivalent).
+
+
+ The secret key is the identity. Never log or persist it in plaintext, keep it in offline or enclave storage
+ where possible, and avoid evaluating the derivation in an untrusted runtime. A lost key cannot be recovered,
+ and an exposed key allows impersonation.
+
+
+### Scope of the identifier
+
+The derivation is deliberately untagged and global: the same secret key yields the same identifier in every
+module and every contract that derives one, so a user who wants to carry a single identity across contracts can.
+Users who want unlinkable identities across contracts should use a different secret key per contract.
+A module that needs a per-deployment, unlinkable identity should not use this circuit.
+
+The output is an identifier, not a secret. Because the construction carries no domain-separation tag,
+it must not be reused to derive private values: a circuit that derives a private value from a secret
+must use its own domain-separation tag.
+
+## Zero accounts
+
+[zeroAccount] returns the canonical zero `Either, ContractAddress>` (the left variant set to zero),
+which the token modules use as the mint and burn sentinel and for cleared approvals.
+[isTargetZero] checks whether the active side of such a value is zero.
+
+## Contract as recipient
+
+[selfAsRecipient] wraps the executing contract's own address as an `Either`,
+the recipient shape expected by shielded send operations, for deposits into the contract and for receiving change.
+
+## Usage
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export circuit performActionWhenEqual(
+ a: Either,
+ b: Either,
+): [] {
+ const isEqual = Utils_isKeyOrAddressEqual(a, b);
+ if (isEqual) {
+ // Do something
+ } else {
+ // Do something else
+ }
+}
+
+// Re-export the derivation so the identifier can be computed off-chain
+// through the compiled contract.
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+See the [Utils API] for the full list of circuits.
diff --git a/content/contracts-compact/0.3.x/utils/constants.js b/content/contracts-compact/0.3.x/utils/constants.js
new file mode 100644
index 00000000..fb6add47
--- /dev/null
+++ b/content/contracts-compact/0.3.x/utils/constants.js
@@ -0,0 +1,2 @@
+export const COMPACT_COMPILER_VERSION = "0.31.0";
+export const COMPACT_LANGUAGE_VERSION = "0.23.0";
diff --git a/content/contracts-compact/0.3.x/utils/replacements.ts b/content/contracts-compact/0.3.x/utils/replacements.ts
new file mode 100644
index 00000000..c38c569d
--- /dev/null
+++ b/content/contracts-compact/0.3.x/utils/replacements.ts
@@ -0,0 +1,9 @@
+import { COMPACT_COMPILER_VERSION, COMPACT_LANGUAGE_VERSION } from "./constants";
+
+export const REPLACEMENTS = {
+ include: ['**/content/contracts-compact/0.3.x/**/*.mdx'],
+ replacements: {
+ compact_compiler_version: COMPACT_COMPILER_VERSION,
+ compact_language_version: COMPACT_LANGUAGE_VERSION
+ }
+}
diff --git a/content/contracts-compact/0.3.x/zk-circuits-101.mdx b/content/contracts-compact/0.3.x/zk-circuits-101.mdx
new file mode 100644
index 00000000..5e954d15
--- /dev/null
+++ b/content/contracts-compact/0.3.x/zk-circuits-101.mdx
@@ -0,0 +1,36 @@
+---
+title: ZK Circuits 101
+---
+
+{/* links */}
+[the math behind the scenes needs it that way]: https://zkhack.dev/whiteboard/module-three/
+
+## What are ZK Circuits?
+
+Compact code is compiled into arithmetic circuits, which are mathematical representations of the contract's logic.
+These circuits are made up of arithmetic gates.
+The gates enforce the rules and constraints defined in the Compact program.
+At a high level, ZK circuits can be thought of as a magic puzzle board that proves a series of steps was followed correctly - like a recipe.
+The puzzle board is laid out in a grid like a giant sheet of graph paper with a certain number of rows.
+Each row is a space where a step or rule that needs to be followed is written.
+These steps/rules correspond to the gates that make up the arithmetic circuit.
+
+## Circuit Size and Domain
+
+The size of the board is called the **domain size** which is referred to as `k` in the documentation.
+It’s always a power of 2 (like 256, 512, 1024, etc.),
+because [the math behind the scenes needs it that way].
+Now, just because the board has 1024 rows doesn’t mean it uses all of them.
+Maybe the recipe takes only 563 steps, so only 563 rows are filled and the rest are left blank.
+These filled-in rows are called **used rows**.
+So "k = 10, rows = 563" in the API Reference documentation that means "this circuit has a size of 2^10 = 1024 rows and only uses 563 rows".
+
+## Why Circuit Size Matters
+
+Why is this important? Well, when writing ZK circuits the size and number of rules to follow should be as small as possible.
+The number of rules in a zero-knowledge circuit directly impacts both the prover time
+(how long it takes to generate a proof) and, to a lesser extent, the proof size and verifier time.
+Larger circuits with more rules require more computation to generate a proof,
+which can make proof generation slower and more resource-intensive.
+This is especially relevant for privacy-preserving blockchains like Midnight,
+where proof generation is often the most computationally expensive part of a transaction.
diff --git a/content/contracts-compact/0.4.x/access-control.mdx b/content/contracts-compact/0.4.x/access-control.mdx
new file mode 100644
index 00000000..b4ec0879
--- /dev/null
+++ b/content/contracts-compact/0.4.x/access-control.mdx
@@ -0,0 +1,281 @@
+---
+title: AccessControl
+---
+
+{/* links */}
+[Role-Based Access Control (RBAC)]: https://en.wikipedia.org/wiki/Role-based_access_control
+[principle of least privilege]: https://en.wikipedia.org/wiki/Principle_of_least_privilege
+
+[FungibleToken]: ./fungible-token.mdx
+[Ownable]: ./ownable.mdx
+[Utils]: ./utils.mdx
+[AccessControl]: api/access-control
+[ShieldedAccessControl]: ./shielded-access-control.mdx
+
+[assertOnlyRole]: api/access-control#AccessControl-assertOnlyRole
+[hasRole]: api/access-control#AccessControl-hasRole
+[grantRole]: api/access-control#AccessControl-grantRole
+[_grantRole]: api/access-control#AccessControl-_grantRole
+[_unsafeGrantRole]: api/access-control#AccessControl-_unsafeGrantRole
+[revokeRole]: api/access-control#AccessControl-revokeRole
+[_setRoleAdmin]: api/access-control#AccessControl-_setRoleAdmin
+[computeAccountId]: api/utils#Utils-computeAccountId
+
+This module provides a role-based access control mechanism,
+where roles can be used to represent a set of permissions providing the flexibility to create different levels of account authorization.
+
+Roles can be enforced using the [assertOnlyRole] circuit.
+Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.
+
+
+ For a privacy-preserving variant that hides role holder identities on-chain, see [ShieldedAccessControl].
+
+
+## Role-Based Access Control
+
+While the simplicity of _ownership_ can be useful for simple systems or quick prototyping,
+different levels of authorization are often needed.
+You may want for an account to have permission to ban users from a system, but not create new tokens.
+[Role-Based Access Control (RBAC)] offers flexibility in this regard.
+
+In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions.
+An account may have, for example, 'moderator', 'minter' or 'admin' roles,
+which you will then check for instead of simply using `assertOnlyOwner`.
+This check can be enforced through the [assertOnlyRole] circuit.
+Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more.
+
+Most software uses access control systems that are role-based:
+some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges.
+
+### Identity Model
+
+AccessControl uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it via the `wit_AccessControlSK` witness. The module computes an account identifier
+as `H(secretKey)` (a `persistentHash` of the key), which is a commitment that hides the key while providing a stable,
+pseudonymous on-chain identity.
+
+What is public and what is private:
+
+* **On the ledger**: `_operatorRoles`, a public map from role identifier to account identifier to a Boolean,
+ and `_adminRoles`. Every grant, revoke, and role check discloses the role identifier and the account identifier it touches,
+ so an observer can see which identifiers hold which roles.
+* **Held privately by each account**: the 32-byte `secretKey`, supplied through `wit_AccessControlSK` at proving time.
+ The key never reaches the ledger; only `H(secretKey)` does.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+The same secret key produces the same identity across all contracts.
+Users who desire cross-contract unlinkability can use different secret keys per contract at the wallet layer.
+
+To derive an account identifier off-chain (e.g. before passing it to a grant operation),
+use [computeAccountId] from the [Utils] module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+### Using `AccessControl`
+
+The Compact contracts library provides `AccessControl` for implementing role-based access control.
+Its usage is straightforward: for each role that you want to define,
+you will create a new role identifier that is used to grant, revoke, and check if an account has that role.
+
+Here's a simple example of using `AccessControl` with [FungibleToken] to define a 'minter' role,
+which allows accounts that have this role to create new tokens:
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+
+/**
+ * Initialize FungibleToken and MINTER_ROLE
+ */
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ minter: Either, ContractAddress>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ AccessControl__grantRole(MINTER_ROLE, minter);
+}
+
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(MINTER_ROLE);
+ FungibleToken__mint(recipient, value);
+}
+
+```
+
+Make sure you fully understand how [AccessControl] works before using it on your system,
+or copy-pasting the examples from this guide.
+
+
+While clear and explicit, this isn't anything we wouldn't have been able to achieve with [Ownable].
+Indeed, where `AccessControl` shines is in scenarios where granular permissions are required,
+which can be implemented by defining _multiple_ roles.
+
+
+Let's augment our FungibleToken example by also defining a 'burner' role, which lets accounts destroy tokens.
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+export sealed ledger BURNER_ROLE: Bytes<32>;
+
+/**
+ * Initialize FungibleToken and MINTER_ROLE
+ */
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ minter: Either, ContractAddress>,
+ burner: Either, ContractAddress>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE"));
+ AccessControl__grantRole(MINTER_ROLE, minter);
+ AccessControl__grantRole(BURNER_ROLE, burner);
+}
+
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(MINTER_ROLE);
+ FungibleToken__mint(recipient, value);
+}
+
+export circuit burn(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(BURNER_ROLE);
+ FungibleToken__burn(recipient, value);
+}
+
+```
+
+So clean! By splitting concerns this way,
+more granular levels of permission may be implemented than were possible with the simpler _ownership_ approach to access control.
+Limiting what each component of a system is able to do is known as the [principle of least privilege],
+and is a good security practice.
+Note that each account may still have more than one role, if so desired.
+
+### Granting and Revoking Roles
+
+The FungibleToken example above uses [_grantRole],
+an internal circuit that is useful when programmatically assigning roles (such as during construction).
+But what if we later want to grant the 'minter' role to additional accounts?
+
+By default, **accounts with a role cannot grant it or revoke it from other accounts**:
+all having a role does is making the [hasRole] check pass.
+To grant and revoke roles dynamically, you will need help from the _role's admin_.
+
+Every role has an associated admin role,
+which grants permission to call the [grantRole] and [revokeRole] circuits.
+A role can be granted or revoked by using these if the calling account has the corresponding admin role.
+Multiple roles may have the same admin role to make management easier.
+A role's admin can even be the same role itself,
+which would cause accounts with that role to be able to also grant and revoke it.
+
+This mechanism can be used to create complex permissioning structures resembling organizational charts,
+but it also provides an easy way to manage simpler applications.
+`AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`,
+which acts as the **default admin role for all roles**.
+An account with this role will be able to manage any other role,
+unless [_setRoleAdmin] is used to select a new admin role.
+
+Since it is the admin for all roles by default,
+and in fact it is also its own admin, this role carries significant risk.
+
+Let's take a look at the FungibleToken example, this time taking advantage of the default admin role:
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+export sealed ledger BURNER_ROLE: Bytes<32>;
+
+/**
+ * Initialize FungibleToken and MINTER_ROLE
+ */
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ defaultAdmin: Either, ContractAddress>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE"));
+ // Grant the default admin role to the provided account identifier.
+ // This account will be able to grant and revoke any roles.
+ // The account identifier should be derived off-chain using Utils_computeAccountId(secretKey).
+ AccessControl__grantRole(AccessControl_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+}
+
+export circuit mint(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(MINTER_ROLE);
+ FungibleToken__mint(recipient, value);
+}
+
+export circuit burn(recipient: Either, ContractAddress>, value: Uint<128>): [] {
+ AccessControl_assertOnlyRole(BURNER_ROLE);
+ FungibleToken__burn(recipient, value);
+}
+
+```
+
+Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles.
+However, because those roles' admin role is the default admin role, and _that_ role was granted to `defaultAdmin`,
+that same account can call [grantRole] to give minting or burning permission, and [revokeRole] to remove it.
+
+The `defaultAdmin` account identifier should be derived off-chain using [computeAccountId] from the [Utils] module
+(`Utils_computeAccountId(secretKey)`),
+where `secretKey` is a 32-byte cryptographically secure random value held by the deployer.
+Pass it as `left, ContractAddress>(accountId)`.
+
+Dynamic role allocation is often a desirable property,
+for example in systems where trust in a participant may vary over time.
+It can also be used to support use cases such as KYC,
+where the list of role-bearers may not be known up-front,
+or may be prohibitively expensive to include in a single transaction.
+
+### Unsafe circuits
+
+This module offers a circuit that allows access control permissions to be granted to contract addresses, [_unsafeGrantRole].
+
+Note that the circuit name is very explicit ("unsafe").
+Until contract-to-contract calls are supported,
+there is no direct way for a contract to call permissioned circuits of other contracts or grant/revoke role permissions.
+
+
+The unsafe circuits are planned to become deprecated once contract-to-contract calls become available.
+
diff --git a/content/contracts-compact/0.4.x/api/access-control.mdx b/content/contracts-compact/0.4.x/api/access-control.mdx
new file mode 100644
index 00000000..c7fc04d6
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/access-control.mdx
@@ -0,0 +1,286 @@
+---
+title: AccessControl API
+---
+
+This page provides the full AccessControl module API.
+
+Roles are referred to by their `Bytes<32>` identifier.
+These should be exposed in the top-level contract and be unique.
+The best way to achieve this is by using `export sealed ledger` hash digests that are initialized in the top-level contract:
+
+```typescript
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl"
+ prefix AccessControl_;
+
+export sealed ledger MY_ROLE: Bytes<32>;
+
+constructor() {
+ MY_ROLE = persistentHash>(pad(32, "MY_ROLE"));
+}
+```
+
+To restrict access to a circuit, use [assertOnlyRole](#AccessControl-assertOnlyRole):
+
+
+```typescript
+circuit foo(): [] {
+ AccessControl_assertOnlyRole(MY_ROLE);
+}
+```
+
+Roles can be granted and revoked dynamically via the [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole) circuits.
+Each role has an associated admin role,
+and only accounts that have a role's admin role can call [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole).
+
+By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE` (zero bytes),
+which means that only accounts with this role will be able to grant or revoke other roles.
+More complex role relationships can be created by using [_setRoleAdmin](#AccessControl-_setRoleAdmin).
+
+
+ The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role.
+ Extra precautions should be taken to secure accounts that have been granted it.
+
+
+
+ For an overview of the module, read the [AccessControl guide](../access-control).
+
+
+## AccessControl [toc] [#AccessControl]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/AccessControl";
+```
+
+---
+
+### Ledger [toc] [#AccessControl-Ledger]
+### Ledger [!toc] [#AccessControl-Ledger]
+
+#### _operatorRoles [toc] [#AccessControl-_operatorRoles]
+#### [!toc] [#AccessControl-_operatorRoles]
+
+ Mapping from a role identifier -> account -> its permissions.
+
+
+#### _adminRoles [toc] [#AccessControl-_adminRoles]
+#### [!toc] [#AccessControl-_adminRoles]
+
+ Mapping from a role identifier to an admin role identifier.
+
+
+### Witnesses [toc] [#AccessControl-Witnesses]
+### Witnesses [!toc] [#AccessControl-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_AccessControlSK [toc] [#AccessControl-wit_AccessControlSK]
+#### [!toc] [#AccessControl-wit_AccessControlSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#AccessControl-Circuits]
+### Circuits [!toc] [#AccessControl-Circuits]
+
+#### DEFAULT_ADMIN_ROLE [toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
+#### [!toc] [#AccessControl-DEFAULT_ADMIN_ROLE]
+
+ The default admin role for all roles. Only accounts with this role will be able
+ to grant or revoke other roles unless custom admin roles are created via [_setRoleAdmin](#AccessControl-_setRoleAdmin).
+
+
+#### hasRole [toc] [#AccessControl-hasRole]
+#### [!toc] [#AccessControl-hasRole]
+
+ Returns `true` if `account` has been granted `roleId`.
+
+
+#### assertOnlyRole [toc] [#AccessControl-assertOnlyRole]
+#### [!toc] [#AccessControl-assertOnlyRole]
+
+ Reverts if the caller cannot prove ownership of `roleId`.
+ In the case of an external (non-contract) caller, the caller’s identity is
+ derived from the [wit_AccessControlSK](#AccessControl-wit_AccessControlSK) witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * The caller must have `roleId`.
+
+
+#### _checkRole [toc] [#AccessControl-_checkRole]
+#### [!toc] [#AccessControl-_checkRole]
+
+ Reverts if `account` is missing `roleId`.
+
+ Requirements:
+
+ * `account` must have `roleId`.
+
+
+#### getRoleAdmin [toc] [#AccessControl-getRoleAdmin]
+#### [!toc] [#AccessControl-getRoleAdmin]
+
+ Returns the admin role that controls `roleId` or
+ a byte array with all zero bytes if `roleId` doesn't exist. See [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole).
+
+ To change a role's admin use [_setRoleAdmin](#AccessControl-_setRoleAdmin).
+
+
+#### grantRole [toc] [#AccessControl-grantRole]
+#### [!toc] [#AccessControl-grantRole]
+
+ Grants `roleId` to `account`.
+
+ Requirements:
+
+ * `account` must not be a ContractAddress.
+ * The caller must have `roleId`'s admin role.
+
+
+#### revokeRole [toc] [#AccessControl-revokeRole]
+#### [!toc] [#AccessControl-revokeRole]
+
+ Revokes `roleId` from `account`.
+
+ Requirements:
+
+ * The caller must have `roleId`'s admin role.
+
+
+#### renounceRole [toc] [#AccessControl-renounceRole]
+#### [!toc] [#AccessControl-renounceRole]
+
+ Revokes `roleId` from the calling account.
+
+
+ Roles are often managed via [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole): this circuit's
+ purpose is to provide a mechanism for accounts to lose their privileges
+ if they are compromised (such as when a trusted device is misplaced).
+
+ The caller's identity is derived from the [wit_AccessControlSK](#AccessControl-wit_AccessControlSK) witness as
+ `persistentHash(secretKey)`. The `callerConfirmation` parameter must match
+ the caller's computed account identifier to prevent accidental renunciation.
+
+
+ Requirements:
+
+ * The caller's computed account identifier must match `callerConfirmation`.
+ * The caller must not be a `ContractAddress`.
+
+
+#### _setRoleAdmin [toc] [#AccessControl-_setRoleAdmin]
+#### [!toc] [#AccessControl-_setRoleAdmin]
+
+ Sets `adminRole` as `roleId`'s admin role.
+
+
+#### _grantRole [toc] [#AccessControl-_grantRole]
+#### [!toc] [#AccessControl-_grantRole]
+
+ Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
+ Internal circuit without access restriction.
+
+ Requirements:
+
+ * `account` must not be a ContractAddress.
+
+
+#### _unsafeGrantRole [toc] [#AccessControl-_unsafeGrantRole]
+#### [!toc] [#AccessControl-_unsafeGrantRole]
+
+ Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted.
+ Internal circuit without access restriction. It does NOT check if the role is granted to a ContractAddress.
+
+
+ External smart contracts cannot call the access control contract at this time, so granting a role to a ContractAddress may
+ render a circuit permanently inaccessible.
+
+
+
+#### _revokeRole [toc] [#AccessControl-_revokeRole]
+#### [!toc] [#AccessControl-_revokeRole]
+
+ Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked.
+ Internal circuit without access restriction.
+
diff --git a/content/contracts-compact/0.4.x/api/confidential-fungible-token.mdx b/content/contracts-compact/0.4.x/api/confidential-fungible-token.mdx
new file mode 100644
index 00000000..10ecc5d2
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/confidential-fungible-token.mdx
@@ -0,0 +1,778 @@
+---
+title: ConfidentialFungibleToken API
+---
+
+This module provides the full ConfidentialFungibleToken module API.
+
+
+ For an overview of the module, read the [ConfidentialFungibleToken guide](../confidential-fungible-token).
+ The `ElGamal_Ciphertext` and `EcdhMask_Ciphertext` types used below are documented in the [Crypto API](./crypto).
+
+
+The page covers the following modules:
+
+- [ConfidentialFungibleToken](#ConfidentialFungibleToken): the token with ElGamal-encrypted balances.
+- [ConfidentialFungibleTokenPublicSupply](#ConfidentialFungibleTokenPublicSupply): an extension that tracks a public total supply.
+
+## ConfidentialFungibleToken [toc] [#ConfidentialFungibleToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/ConfidentialFungibleToken";
+```
+
+---
+
+### Types [toc] [#ConfidentialFungibleToken-Types]
+### Types [!toc] [#ConfidentialFungibleToken-Types]
+
+#### EscrowEntry [toc] [#ConfidentialFungibleToken-EscrowEntry]
+#### [!toc] [#ConfidentialFungibleToken-EscrowEntry]
+
+ Per-(owner, spender) encrypted escrow. Stored as two
+ ciphertexts of the same value: one to the spender, one to the owner.
+ No public cap.
+
+
+### Ledger [toc] [#ConfidentialFungibleToken-Ledger]
+### Ledger [!toc] [#ConfidentialFungibleToken-Ledger]
+
+#### _balances [toc] [#ConfidentialFungibleToken-_balances]
+#### [!toc] [#ConfidentialFungibleToken-_balances]
+
+ Spendable (confirmed) balances, encrypted under each account's
+ ElGamal key. Debits draw from here. Paired with `_pending`: only the owner
+ moves pending into spendable, via [sweep](#ConfidentialFungibleToken-sweep), so a third party spamming dust
+ credits cannot churn a victim's spendable ciphertext and invalidate their
+ in-flight spend proofs. A wallet's total is [balanceOf](#ConfidentialFungibleToken-balanceOf) + [pendingOf](#ConfidentialFungibleToken-pendingOf).
+
+
+#### _pending [toc] [#ConfidentialFungibleToken-_pending]
+#### [!toc] [#ConfidentialFungibleToken-_pending]
+
+ Incoming (unconfirmed) balances, encrypted under each account's
+ ElGamal key. Credits land here rather than in `_balances`; the owner moves
+ them across with [sweep](#ConfidentialFungibleToken-sweep).
+
+
+#### _encryptionKeys [toc] [#ConfidentialFungibleToken-_encryptionKeys]
+#### [!toc] [#ConfidentialFungibleToken-_encryptionKeys]
+
+ Mapping from account identifiers to their ElGamal public key, set by [register](#ConfidentialFungibleToken-register).
+ Membership in this map is what "registered" means throughout the module.
+ Registration is one-shot: a second registration for the same account is
+ rejected.
+
+
+#### _memos [toc] [#ConfidentialFungibleToken-_memos]
+#### [!toc] [#ConfidentialFungibleToken-_memos]
+
+ Mapping from account identifiers to their memo list. Every credit pushes one memo carrying the credited amount,
+ encrypted to the recipient's key, to the front of the list, so the newest memo comes first.
+ Memos record credits only; debits leave none.
+
+
+#### _escrow [toc] [#ConfidentialFungibleToken-_escrow]
+#### [!toc] [#ConfidentialFungibleToken-_escrow]
+
+ Mapping from owner accounts to spender accounts to their [EscrowEntry](#ConfidentialFungibleToken-EscrowEntry).
+ Membership is checked independently of the entry, so an allowance that was
+ never escrowed is unspendable. A spend also requires both owner and spender
+ to be registered, so an entry written here for an unregistered account (for
+ example by a composing contract) is unspendable as well.
+
+
+#### _escrowSpendEpochs [toc] [#ConfidentialFungibleToken-_escrowSpendEpochs]
+#### [!toc] [#ConfidentialFungibleToken-_escrowSpendEpochs]
+
+ Per-(owner, spender) escrow-spend counter, folded into the randomness
+ `_spendEscrow` derives so repeat spends by one spender against one owner
+ never share a one-time pad. It restarts for each pair, which is why both
+ counterparties are bound in too; see `SpendRandomnessPreimage`.
+
+ Kept out of `EscrowEntry`: [approve](#ConfidentialFungibleToken-approve) replaces that entry wholesale, so a
+ counter living there would restart on every re-approve.
+
+ Note that this exposes a per-pair spend count which is already derivable
+ from tx history.
+
+
+#### _creditEpochs [toc] [#ConfidentialFungibleToken-_creditEpochs]
+#### [!toc] [#ConfidentialFungibleToken-_creditEpochs]
+
+ Per-account credit counter. `_credit` uses it as the nonce that keeps each
+ credit's randomness distinct, so it MUST only ever increase. No circuit
+ resets it. It is kept out of `_memos` for that reason: the memo list is
+ prunable by its owner, and a nonce that could be walked back would let a
+ reused seed repeat an earlier credit's randomness. The cost is that an
+ account's lifetime credit count is permanently readable from state.
+
+ `Counter` is chosen for no-reset monotonicity, not commutativity. Its
+ `increment` emits no pinned read, but `_credit` reads the value back to
+ derive the nonce, and that read pins the recipient's key. Credits to one
+ recipient still serialize; see the Concurrency note above.
+
+
+#### _isInitialized [toc] [#ConfidentialFungibleToken-_isInitialized]
+#### [!toc] [#ConfidentialFungibleToken-_isInitialized]
+
+ Whether [initialize](#ConfidentialFungibleToken-initialize) has run. Guards against re-initialization
+ and against use before initialization.
+
+
+#### _name [toc] [#ConfidentialFungibleToken-_name]
+#### [!toc] [#ConfidentialFungibleToken-_name]
+
+ The token's human-readable name, set once at construction.
+
+
+#### _symbol [toc] [#ConfidentialFungibleToken-_symbol]
+#### [!toc] [#ConfidentialFungibleToken-_symbol]
+
+ The token's ticker symbol, set once at construction.
+
+
+#### _decimals [toc] [#ConfidentialFungibleToken-_decimals]
+#### [!toc] [#ConfidentialFungibleToken-_decimals]
+
+ The number of decimals used to display balances, set once at
+ construction. Purely presentational; no circuit scales by it.
+
+
+### Witnesses [toc] [#ConfidentialFungibleToken-Witnesses]
+### Witnesses [!toc] [#ConfidentialFungibleToken-Witnesses]
+
+The TypeScript witnesses in the repository are test-only and are not published; a consumer authors its own.
+
+#### wit_ConfidentialTokenSK [toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenSK]
+#### [!toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenSK]
+
+ Returns the caller's account secret. Verified in-circuit by
+ matching `persistentHash(SK)` against the public accountId.
+
+
+#### wit_ConfidentialTokenEK [toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenEK]
+#### [!toc] [#ConfidentialFungibleToken-wit_ConfidentialTokenEK]
+
+ Returns the caller's ElGamal secret. Verified in-circuit by
+ `ElGamal_assertDecryptsTo`, which re-derives the pk and compares
+ against the stored pk.
+
+
+#### wit_PlaintextBalance [toc] [#ConfidentialFungibleToken-wit_PlaintextBalance]
+#### [!toc] [#ConfidentialFungibleToken-wit_PlaintextBalance]
+
+ Returns the wallet's cached plaintext for the given
+ ciphertext. Called whenever the circuit needs to verify a ciphertext's
+ plaintext value (sender's main balance, escrow ciphertext during spend,
+ prior escrow during refund, etc.).
+
+ The ciphertext is passed explicitly so the wallet can route to the right
+ cached plaintext by ciphertext identity. This avoids call-order
+ conventions (which would be brittle to circuit refactors) and lets the
+ wallet maintain a `Map` lookup.
+
+ Verified in-circuit by `ElGamal_assertDecryptsTo`: a wrong plaintext
+ fails the assertion `Dec(ct, EK) == claimedValue`.
+
+
+#### wit_RandomnessSeed [toc] [#ConfidentialFungibleToken-wit_RandomnessSeed]
+#### [!toc] [#ConfidentialFungibleToken-wit_RandomnessSeed]
+
+ Returns a single 32-byte seed from which all per-operation
+ randomness (and each memo's ephemeral) is derived in-circuit.
+
+ Seed freshness is load-bearing for confidentiality on EVERY path (see the
+ module header). The derivation is deterministic and public, so an observer
+ who knows the seed can strip a memo's mask and read the delivered amount, and
+ recover balance/escrow amounts from the public ciphertexts. Seed REUSE leaks
+ plaintext differences and the circuit does NOT prevent it on most paths.
+ `_spendEscrow` is an exception (its randomness folds the spend epoch and both
+ counterparties); the credit path folds the recipient's `_creditEpochs`
+ counter as defense in depth, and that covers credits alone. The seed MUST be
+ fresh, high-entropy, and secret for every invocation.
+
+
+### Circuits [toc] [#ConfidentialFungibleToken-Circuits]
+### Circuits [!toc] [#ConfidentialFungibleToken-Circuits]
+
+#### MAX_TRANSFER_VALUE [toc] [#ConfidentialFungibleToken-MAX_TRANSFER_VALUE]
+#### [!toc] [#ConfidentialFungibleToken-MAX_TRANSFER_VALUE]
+
+ Maximum allowed value per single transfer / mint / burn /
+ approve: the `Uint<128>` maximum. The prior `2^48` cap existed only to keep
+ wallet-side discrete-log recovery tractable; with the ECDH memo delivering
+ values directly (see `crypto/EcdhMask`), that constraint is gone. Values stay
+ well under the Jubjub scalar-field order, so homomorphic sums do not wrap.
+
+
+ This equals the argument type's own maximum, so the per-op check is
+ redundant today. KEEP IT: it compares against a hard constant rather than
+ the argument type, so it still catches a later widening of the transfer
+ type — which is what the balance-claim width is sized against.
+
+
+
+#### initialize [toc] [#ConfidentialFungibleToken-initialize]
+#### [!toc] [#ConfidentialFungibleToken-initialize]
+
+ Initializes the token's name, symbol, and decimals. Intended to
+ be called once, from the deploying contract's constructor.
+
+ Requirements:
+
+ * Contract is not already initialized.
+
+
+#### balanceOf [toc] [#ConfidentialFungibleToken-balanceOf]
+#### [!toc] [#ConfidentialFungibleToken-balanceOf]
+
+ Returns `account`'s balance ciphertext, or the default (zero)
+ ciphertext if the account is unregistered. The amount is encrypted; only the
+ account (or an authorized viewer) can decrypt it.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### pendingOf [toc] [#ConfidentialFungibleToken-pendingOf]
+#### [!toc] [#ConfidentialFungibleToken-pendingOf]
+
+ Returns `account`'s PENDING balance ciphertext (incoming credits
+ not yet swept into spendable), or Enc(0) if unregistered. The spendable
+ balance is [balanceOf](#ConfidentialFungibleToken-balanceOf); a wallet's total is the sum of the two. See the
+ dual-balance note on the ledger declarations.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### allowance [toc] [#ConfidentialFungibleToken-allowance]
+#### [!toc] [#ConfidentialFungibleToken-allowance]
+
+ Returns the escrow entry (the encrypted allowance) that `owner`
+ granted `spender`. When no allowance exists, returns a placeholder entry
+ rather than `default`: the returned Jubjub points are on-curve,
+ so the view is safe to prove (e.g. through a contract-to-contract call),
+ whereas a zeroed default holds off-curve points. The cap is encrypted, not
+ public.
+
+
+ The placeholder's `spenderCt` and `ownerCt` are genuine encryptions
+ of 0. Its `ownerMemo` is not: it is a sentinel for an absent allowance,
+ recognised by `ephemeralPk == identity`, which `EcdhMask_encrypt` never
+ produces and `EcdhMask_decrypt` rejects. A wallet must test for the
+ sentinel rather than decrypt and narrow it.
+
+
+
+ "No allowance" and "an allowance of 0" both read as encrypted-zero in
+ the amount fields; the memo sentinel tells them apart, since [approve](#ConfidentialFungibleToken-approve) stores
+ a real memo even for 0. This getter grants nothing, the spend path
+ ([transferFrom](#ConfidentialFungibleToken-transferFrom)/`burnFrom`) gates on `_escrow` membership independently, so a
+ non-existent allowance is unspendable.
+
+
+
+#### isRegistered [toc] [#ConfidentialFungibleToken-isRegistered]
+#### [!toc] [#ConfidentialFungibleToken-isRegistered]
+
+ Returns whether `account` has registered an encryption key.
+
+
+#### register [toc] [#ConfidentialFungibleToken-register]
+#### [!toc] [#ConfidentialFungibleToken-register]
+
+ Registers the caller: derives their encryption public key from
+ [wit_ConfidentialTokenEK](#ConfidentialFungibleToken-wit_ConfidentialTokenEK) and initializes their balance to an encryption of
+ zero. Registration is a prerequisite for sending or receiving.
+
+
+ Returns the caller's `accountId`. A composing contract can gate on
+ the returned value (e.g. assert it is KYC-approved) to restrict who may
+ register. See the module header note on caller-identity gating.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is not already registered.
+
+
+#### _mint [toc] [#ConfidentialFungibleToken-_mint]
+#### [!toc] [#ConfidentialFungibleToken-_mint]
+
+ Mints `value` to `account`: `_credit` under its intent name.
+ The building block for a composing contract's gated `mint`; it performs no
+ supply accounting. Pair it with a supply tracker (see
+ `extensions/ConfidentialFungibleTokenPublicSupply`) if the deployment tracks
+ `totalSupply`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * `account` is registered.
+
+
+#### _burn [toc] [#ConfidentialFungibleToken-_burn]
+#### [!toc] [#ConfidentialFungibleToken-_burn]
+
+ Burns `value` from the caller's own balance: `_debit` under
+ its intent name. The building block for a composing contract's `burn`; it
+ performs no supply accounting. Pair it with a supply tracker (see
+ `extensions/ConfidentialFungibleTokenPublicSupply`) if the deployment tracks
+ `totalSupply`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * The caller is registered and has a balance of at least `value`.
+
+
+#### transfer [toc] [#ConfidentialFungibleToken-transfer]
+#### [!toc] [#ConfidentialFungibleToken-transfer]
+
+ Transfers `value` from the caller to `to`: debits the caller,
+ credits `to`, and pushes an encrypted memo to `to`. The amount is hidden;
+ the (sender, recipient) pair is public.
+
+
+ Returns the caller's (sender's) `accountId` for caller-side gating.
+ The sender is derived from the same authentication `_debit` performs, so the
+ returned id is exactly the account whose funds moved.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * The caller is registered with a balance of at least `value`.
+ * `to` is registered and is not the caller (no self-transfer).
+
+
+#### _move [toc] [#ConfidentialFungibleToken-_move]
+#### [!toc] [#ConfidentialFungibleToken-_move]
+
+ The conserving value-movement primitive: debits the caller and
+ credits `to` by the same `value`, net zero. It NEVER changes supply, so it is
+ the safe surface for composition and the primitive this supply-free base
+ exposes. [transfer](#ConfidentialFungibleToken-transfer) is [_move](#ConfidentialFungibleToken-_move) plus a self-transfer guard; supply-changing
+ operations (`mint`/`burn`) live in a separate layer outside this conserving
+ surface, and only they may break conservation.
+
+
+ Like [transfer](#ConfidentialFungibleToken-transfer), the credit lands in the recipient's pending pool
+ (see the dual-balance note); the caller sweeps their own incoming value.
+
+
+ Self-move (`to` == caller) is PERMITTED here, unlike [transfer](#ConfidentialFungibleToken-transfer), which
+ rejects it. A self-move is conserving and safe: it debits the caller's
+ spendable and credits the caller's own pending (net zero, distinct ledger
+ cells; the value is recovered with [sweep](#ConfidentialFungibleToken-sweep)). [transfer](#ConfidentialFungibleToken-transfer)'s self-transfer guard
+ is therefore a UX choice, not a safety requirement: it spares users a
+ self-transfer with no foreseeable benefit (a shuffle of spendable into
+ pending plus a self-memo). This primitive stays permissive so a composer can
+ set its own policy; add a `to != caller` check in the wrapper to reject a
+ self-move.
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is registered with a spendable balance of at least `value`.
+ * `to` is registered.
+
+
+#### sweep [toc] [#ConfidentialFungibleToken-sweep]
+#### [!toc] [#ConfidentialFungibleToken-sweep]
+
+ Sweeps the caller's incoming `pending` pool into their
+ `spendable` balance. Credits land in `pending` (see the dual-balance note);
+ this [sweep](#ConfidentialFungibleToken-sweep) is the ONLY path from pending into spendable, and only the owner
+ can invoke it (the caller proves BOTH witness secrets: the account secret
+ identifies the account, the encryption secret proves the caller can read it).
+ So a third party spamming credits cannot force a victim's spendable ciphertext
+ to change, which is what would otherwise invalidate the victim's in-flight
+ spend proofs (a liveness grief).
+
+
+ Purely homomorphic: adds the two ciphertexts (same key) and resets
+ pending to Enc(0). No plaintext is needed; the wallet already learned the
+ pending amounts from its memos.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is registered.
+ * The caller holds the encryption secret registered for their account.
+
+
+#### approve [toc] [#ConfidentialFungibleToken-approve]
+#### [!toc] [#ConfidentialFungibleToken-approve]
+
+ Sets `spender`'s allowance over the caller's balance to `value`
+ via an escrow: the caller's main balance is debited by `value`, and matching
+ encryptions are stored for the spender and the owner. Re-approving first
+ refunds any prior escrow, so allowances replace rather than stack. The cap
+ is not public.
+
+ The allowance is funded at approve time, and funding is required: the
+ spender holds no owner encryption key, so at spend time it can only prove
+ against its own escrow copy, never that the owner's balance covers the
+ allowance. An approve conserves the owner's holdings (spendable plus every
+ outstanding escrow), so allowances across spenders can sum to the whole
+ holding but never exceed it.
+
+
+ Returns the caller's (approver's) `accountId` for caller-side gating.
+
+
+ Also stores an owner-readable `ownerMemo` (ECDH one-time pad; see
+ `crypto/EcdhMask`) in the escrow entry, carrying the current remaining
+ allowance (= `value` here, refreshed to the reduced remaining on every
+ spend). This is what lets the owner prove the post-refund balance on a later
+ re-approve or revoke after a partial spend; without it a partial spend would
+ strand the escrow. See the `EscrowEntry` field doc.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * `spender` is registered, is not the zero account, and is not the caller.
+ * The caller is registered with a balance of at least `value`.
+
+
+#### transferFrom [toc] [#ConfidentialFungibleToken-transferFrom]
+#### [!toc] [#ConfidentialFungibleToken-transferFrom]
+
+ Spends `value` from `fromAddress`'s escrow (granted to the
+ caller via [approve](#ConfidentialFungibleToken-approve)) and credits it to `to`, reducing both escrow copies by
+ `value`. The amount is hidden.
+
+
+ Returns the caller's (spender's) `accountId` for caller-side gating.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * `to` is registered and is not the zero account.
+ * An escrow from `fromAddress` to the caller exists with at least `value`.
+ * `fromAddress` and the caller are registered.
+
+
+#### clearMemos [toc] [#ConfidentialFungibleToken-clearMemos]
+#### [!toc] [#ConfidentialFungibleToken-clearMemos]
+
+ Clears the caller's memo list, letting a wallet that has folded
+ its memos into its local balance cache prune on-chain memo storage.
+
+ `expectedEpoch` is the caller's view of its `_creditEpochs` value at the
+ moment it read the list. The call is rejected unless that still matches, so
+ a credit landing between the read and this transaction is never silently
+ discarded. The wallet retries against the memos it has now seen. A wallet
+ therefore reads its memo list and its epoch together, folds the memos into
+ its cache, and passes that epoch back here.
+
+
+ Clearing memos is destructive and can permanently lock funds. The
+ on-chain balance ciphertext is not directly decryptable (discrete-log
+ bound), so a wallet reconstructs its balance by replaying memos. Clearing
+ them without a durable off-chain record of the balance loses the ability to
+ recover it; and because spending requires proving the balance plaintext, the
+ funds become unspendable, not merely undisplayable. Clear only memos already
+ folded into a durable cache/backup. The epoch check only rules out a credit
+ the caller has not seen; it does not make clearing safe for memos the caller
+ never folded in.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is registered.
+ * The caller holds the encryption secret registered for their account.
+ * The caller has a memo list.
+ * `expectedEpoch` equals the caller's current credit epoch.
+
+
+#### computeAccountId [toc] [#ConfidentialFungibleToken-computeAccountId]
+#### [!toc] [#ConfidentialFungibleToken-computeAccountId]
+
+ Derives the public `accountId` from a secret key, as
+ `persistentHash(sk)`. Pure, so a wallet can compute its own account
+ identifier off-chain (no proof) before transacting.
+
+ Delegates to `Utils.computeAccountId` rather than reimplementing the
+ hash. The identifier is deliberately GLOBAL. The same key material yields
+ the same id in every module that derives it.
+
+
+#### _burnFrom [toc] [#ConfidentialFungibleToken-_burnFrom]
+#### [!toc] [#ConfidentialFungibleToken-_burnFrom]
+
+ Burns `value` from `fromAddress` by consuming an escrow that
+ owner granted to the caller: `_spendEscrow` under its intent name, with no
+ recipient credit. The building block for a composing contract's
+ `burnFrom`; it performs no supply accounting (see [_burn](#ConfidentialFungibleToken-_burn)).
+
+ Requirements:
+
+ * Contract is initialized.
+ * `value` is within [MAX_TRANSFER_VALUE](#ConfidentialFungibleToken-MAX_TRANSFER_VALUE).
+ * An escrow from `fromAddress` to the caller exists with at least `value`.
+ * `fromAddress` and the caller are registered.
+
+
+---
+
+## ConfidentialFungibleTokenPublicSupply [toc] [#ConfidentialFungibleTokenPublicSupply]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/ConfidentialFungibleTokenPublicSupply";
+```
+
+Optional standalone extension that adds public supply accounting to a ConfidentialFungibleToken. It imports no token
+module and needs no initialization. "Public" supply means the total, and therefore each mint and burn amount, is
+disclosed on chain through the delta; balances stay confidential. The composing contract pairs each accounting block
+with the matching token operation.
+
+---
+
+### Ledger [toc] [#ConfidentialFungibleTokenPublicSupply-Ledger]
+### Ledger [!toc] [#ConfidentialFungibleTokenPublicSupply-Ledger]
+
+#### _totalSupply [toc] [#ConfidentialFungibleTokenPublicSupply-_totalSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-_totalSupply]
+
+ Total tokens in circulation, tracked in the clear even though
+ balances are encrypted. Raised by [_addSupply](#ConfidentialFungibleTokenPublicSupply-_addSupply) and lowered by
+ [_subSupply](#ConfidentialFungibleTokenPublicSupply-_subSupply), each rejecting over/underflow.
+
+
+ Mint and burn disclose their amounts through the delta on this value.
+
+
+
+### Witnesses [toc] [#ConfidentialFungibleTokenPublicSupply-Witnesses]
+### Witnesses [!toc] [#ConfidentialFungibleTokenPublicSupply-Witnesses]
+
+None.
+
+### Circuits [toc] [#ConfidentialFungibleTokenPublicSupply-Circuits]
+### Circuits [!toc] [#ConfidentialFungibleTokenPublicSupply-Circuits]
+
+#### _addSupply [toc] [#ConfidentialFungibleTokenPublicSupply-_addSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-_addSupply]
+
+ Increases [totalSupply](#ConfidentialFungibleTokenPublicSupply-totalSupply) by `value`. Call alongside the token
+ module's `_mint`. The added amount is disclosed via the supply delta.
+
+ Requirements:
+
+ * `totalSupply + value` does not overflow `Uint<128>`.
+
+
+#### _subSupply [toc] [#ConfidentialFungibleTokenPublicSupply-_subSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-_subSupply]
+
+ Decreases [totalSupply](#ConfidentialFungibleTokenPublicSupply-totalSupply) by `value`. Call alongside the token
+ module's `_burn` or `_burnFrom`. The removed amount is disclosed via the
+ supply delta.
+
+ Requirements:
+
+ * [totalSupply](#ConfidentialFungibleTokenPublicSupply-totalSupply) is at least `value`.
+
+
+#### totalSupply [toc] [#ConfidentialFungibleTokenPublicSupply-totalSupply]
+#### [!toc] [#ConfidentialFungibleTokenPublicSupply-totalSupply]
+
+ Returns the public total token supply.
+
diff --git a/content/contracts-compact/0.4.x/api/crypto.mdx b/content/contracts-compact/0.4.x/api/crypto.mdx
new file mode 100644
index 00000000..cf68c9b1
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/crypto.mdx
@@ -0,0 +1,682 @@
+---
+title: Crypto API
+---
+
+This page provides the full API for the Crypto modules.
+
+
+ For an overview of the modules, read the [Crypto guide](../crypto).
+
+
+The page covers the following modules:
+
+- [ElGamal](#ElGamal): exponential ElGamal encryption over Jubjub with homomorphic addition and re-randomization.
+- [EcdhMask](#EcdhMask): an ECDH-derived mask for encrypting a value to a recipient's public key.
+- [Ecdsa](#Ecdsa): the low-s rule for secp256k1 ECDSA signatures.
+- [Eip712](#Eip712): the EIP-712 typed-data envelope, for verifying signatures produced by Ethereum signing infrastructure.
+
+## ElGamal [toc] [#ElGamal]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal";
+```
+
+Scalars are typed `JubjubScalar`, not `Field`. A `Field as JubjubScalar` cast reduces modulo the subgroup order,
+so casting at a call site would silently accept an out-of-range scalar the typed parameter rejects.
+
+---
+
+### Types [toc] [#ElGamal-Types]
+### Types [!toc] [#ElGamal-Types]
+
+#### Ciphertext [toc] [#ElGamal-Ciphertext]
+#### [!toc] [#ElGamal-Ciphertext]
+
+ Single-receiver ElGamal ciphertext over Jubjub.
+ c1 = g^r
+ c2 = pk^r * m (lifted: m = g^v)
+
+
+### Ledger [toc] [#ElGamal-Ledger]
+### Ledger [!toc] [#ElGamal-Ledger]
+
+None.
+
+### Witnesses [toc] [#ElGamal-Witnesses]
+### Witnesses [!toc] [#ElGamal-Witnesses]
+
+None.
+
+### Circuits [toc] [#ElGamal-Circuits]
+### Circuits [!toc] [#ElGamal-Circuits]
+
+#### secretToScalar [toc] [#ElGamal-secretToScalar]
+#### [!toc] [#ElGamal-secretToScalar]
+
+ Maps a 32-byte secret to a valid Jubjub scalar. See the
+ module-level hash-to-scalar note for why `degradeToTransient` is required.
+
+ Domain separation here is load-bearing, not cosmetic. This output is
+ the private key protecting every ciphertext held under an account. Public
+ account identifiers derived from `Utils.computeAccountId`, for example,
+ are computed as `persistentHash([secretKey])`, and that identifier
+ is stored in the clear as a ledger map key. Untagged, this circuit
+ would compute that same hash, so a secret used in both roles would
+ have its encryption key recoverable from a published identifier by applying
+ `degradeToTransient`. The tag makes the two derivations unrelated, so a
+ wallet MAY safely derive its account secret and its encryption secret from
+ common key material.
+
+ The tag is FIRST, deliberately. [expandRandomness](#ElGamal-expandRandomness) hashes
+ `[seed, tag]` with both slots caller-supplied, so tagging this circuit as
+ `[secret, tag]` would make `secretToScalar(x)` identical to
+ `expandRandomness(x, )` reachable by passing this domain string
+ to the parameter that exists to receive domain strings. Leading with the
+ tag means reproducing this output through [expandRandomness](#ElGamal-expandRandomness) would require
+ passing the domain constant as the *seed*. Do not normalise the orderings.
+
+
+#### derivePk [toc] [#ElGamal-derivePk]
+#### [!toc] [#ElGamal-derivePk]
+
+ Derives the ElGamal public key `pk = g^secretToScalar(ek)`
+ from a secret `ek`.
+
+
+#### expandRandomness [toc] [#ElGamal-expandRandomness]
+#### [!toc] [#ElGamal-expandRandomness]
+
+ Deterministically expands a 32-byte seed into a Jubjub scalar
+ tagged by a distinguishing string, so a single witness seed can yield many
+ distinct randomness values within one circuit invocation. Distinct tags
+ always produce distinct outputs (collision resistance of the hash); the
+ caller is responsible for using distinct tags.
+
+
+#### encryptZero [toc] [#ElGamal-encryptZero]
+#### [!toc] [#ElGamal-encryptZero]
+
+ The identity ciphertext `Enc(0)` (both components are the
+ curve identity). Useful for initializing a fresh balance.
+
+
+ This is a fixed, deterministic value `(O, O)` — NOT a hiding
+ encryption. It is publicly recognizable, so a slot left at [encryptZero](#ElGamal-encryptZero) is
+ distinguishable as never-written. Rerandomize (or credit with fresh `r`)
+ before treating it as private.
+
+
+
+#### encryptPoint [toc] [#ElGamal-encryptPoint]
+#### [!toc] [#ElGamal-encryptPoint]
+
+ Encrypts an arbitrary message point `m` under `pk` with
+ randomness `r`, producing the ciphertext `(g^r, pk^r * m)`. This is the
+ general (non-lifted) ElGamal encryption; [encrypt](#ElGamal-encrypt) is the lifted special
+ case `encryptPoint(pk, g^value, r)`.
+
+
+ `m` (like `pk`) is constrained into the prime-order subgroup by the
+ runtime (see the module-level subgroup note), so no in-circuit membership
+ check is performed. `pk` is asserted non-identity and `r` non-zero — the
+ two inputs that would zero the mask and expose `m` (see the weak-keys and
+ randomness notes). Decrypting `m` is only feasible when it is a known or
+ small-exponent point; the caller owns that concern (the lifted [encrypt](#ElGamal-encrypt)
+ bounds it via the discrete-log range).
+
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### encrypt [toc] [#ElGamal-encrypt]
+#### [!toc] [#ElGamal-encrypt]
+
+ Produces a fresh ciphertext of `value` under `pk` with
+ randomness `r`, lifting `value` to the point `g^value`. Randomness is taken
+ as a scalar directly to avoid a redundant conversion at every call site;
+ [expandRandomness](#ElGamal-expandRandomness) produces one for the same reason.
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### negate [toc] [#ElGamal-negate]
+#### [!toc] [#ElGamal-negate]
+
+ Negates a ciphertext componentwise: if `ct` encrypts `v` then
+ the result encrypts `-v` under the same key (with negated randomness).
+ This is the building block [sub](#ElGamal-sub) rests on.
+
+
+#### add [toc] [#ElGamal-add]
+#### [!toc] [#ElGamal-add]
+
+ Homomorphically adds two ciphertexts: `Enc(a) + Enc(b)`
+ decrypts to `a + b`. This is the defining additive-homomorphism primitive
+ and combines two independently produced ciphertexts componentwise.
+
+
+ Both inputs MUST be encrypted under the same public key for the
+ result to decrypt under that key's secret; combining ciphertexts under
+ different keys yields a ciphertext no single key can open. The randomness
+ of the result is the sum of the inputs' randomness, so the result is a
+ valid fresh ciphertext under that key.
+
+
+
+#### sub [toc] [#ElGamal-sub]
+#### [!toc] [#ElGamal-sub]
+
+ Homomorphically subtracts two ciphertexts: `Enc(a) - Enc(b)`
+ decrypts to `a - b`. Implemented as `add(a, negate(b))`.
+
+
+ Same-key requirement as [add](#ElGamal-add). The caller MUST ensure the
+ underlying plaintext subtraction does not underflow; this circuit cannot
+ check it (see [subEncrypted](#ElGamal-subEncrypted)).
+
+
+
+#### scalarMul [toc] [#ElGamal-scalarMul]
+#### [!toc] [#ElGamal-scalarMul]
+
+ Homomorphically multiplies the plaintext of `ct` by a public
+ scalar `k`: if `ct` encrypts `v` then the result encrypts `k * v` under the
+ same key. Both components are scaled by `k`, so the randomness scales to
+ `k * r` and the result is a valid fresh ciphertext under that key.
+
+ This is the scalar-multiplication leg of the homomorphism, and the only way
+ to express linear combinations: a weighted sum `Enc(3a + 5b)` is
+ `add(scalarMul(ca, 3), scalarMul(cb, 5))`, which repeated [add](#ElGamal-add) cannot do
+ for large weights.
+
+
+ `k` MUST be a valid Jubjub scalar (less than the subgroup order);
+ larger values fault `ecMul` (see the module-level hash-to-scalar note). The
+ caller also remains responsible for the discrete-log bound — `k * v` must
+ stay within the range a recovery search can clear.
+
+
+
+#### addEncrypted [toc] [#ElGamal-addEncrypted]
+#### [!toc] [#ElGamal-addEncrypted]
+
+ Homomorphically adds `value` to the plaintext encrypted by
+ `old`, producing a fresh ciphertext under `pk` with new randomness `r`.
+ Convenience over [add](#ElGamal-add) for folding a known plaintext into a ciphertext:
+ `add(old, encrypt(pk, value, r))`.
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### subEncrypted [toc] [#ElGamal-subEncrypted]
+#### [!toc] [#ElGamal-subEncrypted]
+
+ Homomorphically subtracts `value` from the plaintext encrypted
+ by `old`. Convenience over [sub](#ElGamal-sub): `sub(old, encrypt(pk, value, r))`.
+
+
+ The caller MUST ensure the underlying plaintext subtraction does
+ not underflow; this circuit cannot check it. Callers verify sufficiency by
+ reading the plaintext via their own witness and asserting it with
+ [assertDecryptsTo](#ElGamal-assertDecryptsTo) before calling.
+
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### rerandomize [toc] [#ElGamal-rerandomize]
+#### [!toc] [#ElGamal-rerandomize]
+
+ Rerandomizes `ct`: returns a fresh ciphertext of the same
+ plaintext under the same key `pk`, using new randomness `r`. The output is
+ unlinkable to the input (it is a uniformly fresh encryption of the same
+ value) yet decrypts identically. Equivalent to homomorphically adding an
+ encryption of zero.
+
+
+ `r = 0` is rejected (it would be a no-op via the [encryptPoint](#ElGamal-encryptPoint)
+ zero-randomness assert). One silent failure mode remains that the circuit
+ cannot catch: a `pk` that is not the key `ct` was encrypted under corrupts
+ the plaintext (the result no longer decrypts to the original value). Pass
+ fresh randomness and the matching `pk`.
+
+
+ Requirements:
+
+ * `pk` is not the identity point.
+ * `r` is non-zero.
+
+
+#### assertKeyPair [toc] [#ElGamal-assertKeyPair]
+#### [!toc] [#ElGamal-assertKeyPair]
+
+ Asserts that `ek` is the secret for `pk`, i.e.
+ `derivePk(ek) == pk`. Lets a caller prove ownership of a public key
+ without touching any ciphertext or plaintext.
+
+ Requirements:
+
+ * `ek` is the secret for `pk` (`derivePk(ek) == pk`).
+
+
+#### assertDecryptsToPoint [toc] [#ElGamal-assertDecryptsToPoint]
+#### [!toc] [#ElGamal-assertDecryptsToPoint]
+
+ Asserts that `ct` decrypts under `(pk, ek)` to the message
+ point `m`, and that `ek` is the secret for `pk`. This is the general
+ (non-lifted) verification; [assertDecryptsTo](#ElGamal-assertDecryptsTo) is the lifted special case
+ with `m = g^claimedValue`.
+
+ Requirements:
+
+ * `ek` is the secret for `pk`.
+ * `ct` decrypts under `(pk, ek)` to `m`.
+
+
+#### assertDecryptsTo [toc] [#ElGamal-assertDecryptsTo]
+#### [!toc] [#ElGamal-assertDecryptsTo]
+
+ Asserts that `ct` decrypts under `(pk, ek)` to `claimedValue`,
+ and that `ek` is the secret for `pk`. This is the in-circuit verification
+ path an importing module uses to bind its witness-supplied `ek` and cached
+ plaintext to publicly stored state: a wrong `ek` fails the key check, a
+ wrong plaintext fails the decryption check. Lifted special case of
+ [assertDecryptsToPoint](#ElGamal-assertDecryptsToPoint) with the message point `g^claimedValue`.
+
+
+ `claimedValue` is the widest `Uint` Compact offers, so this parameter
+ never bounds a caller. A narrower value costs proportionally fewer rows: the
+ range constraint belongs to where the value is declared, not to this
+ signature.
+
+
+
+ The claim domain must stay below the Jubjub subgroup order `l`:
+ verification compares `g^claimedValue`, so two claims congruent mod `l` are
+ indistinguishable. `Uint<248>` clears `l` (~2^251.86) by under 4 bits, and
+ that width is set by Compact's cap rather than by this bound. 251 is the
+ widest sound `Uint` if the cap ever rises.
+
+
+ Requirements:
+
+ * `ek` is the secret for `pk`.
+ * `ct` decrypts under `(pk, ek)` to `claimedValue`.
+
+
+---
+
+## EcdhMask [toc] [#EcdhMask]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/crypto/EcdhMask";
+```
+
+---
+
+### Types [toc] [#EcdhMask-Types]
+### Types [!toc] [#EcdhMask-Types]
+
+#### Ciphertext [toc] [#EcdhMask-Ciphertext]
+#### [!toc] [#EcdhMask-Ciphertext]
+
+ An ECDH one-time-pad ciphertext: the ephemeral public key and
+ the field-masked value.
+
+
+### Ledger [toc] [#EcdhMask-Ledger]
+### Ledger [!toc] [#EcdhMask-Ledger]
+
+None.
+
+### Witnesses [toc] [#EcdhMask-Witnesses]
+### Witnesses [!toc] [#EcdhMask-Witnesses]
+
+None.
+
+### Circuits [toc] [#EcdhMask-Circuits]
+### Circuits [!toc] [#EcdhMask-Circuits]
+
+#### kdf [toc] [#EcdhMask-kdf]
+#### [!toc] [#EcdhMask-kdf]
+
+ Derives the field mask from the ECDH shared secret point,
+ domain-separated by a caller-supplied `domain` tag. Hashes the point to
+ `Bytes<32>`, then re-hashes it with the domain tag and truncates into the
+ field via `degradeToTransient`. The consumer chooses `domain` (so this
+ module is not tied to any one protocol) and MUST use the same value on
+ encrypt and decrypt.
+
+ Uses `persistentHash` deliberately: the recipient reproduces this mask
+ to decrypt, possibly across a platform upgrade, so it must be upgrade-stable.
+
+
+#### encrypt [toc] [#EcdhMask-encrypt]
+#### [!toc] [#EcdhMask-encrypt]
+
+ Encrypts `value` to `recipientPk` under ephemeral scalar `e`.
+
+
+ `e` MUST be fresh per call (see the module freshness precondition).
+
+
+ Requirements:
+
+ * `recipientPk` is not the identity point.
+ * `e` is non-zero, a valid Jubjub scalar (`< ℓ`), secret, and fresh per call.
+
+
+#### decrypt [toc] [#EcdhMask-decrypt]
+#### [!toc] [#EcdhMask-decrypt]
+
+ Recovers the value from a ciphertext using the recipient's
+ secret scalar. Pure and off-chain (no proof needed to read a received value).
+
+ Requirements:
+
+ * `ciphertext.ephemeralPk` is not the identity point.
+
+
+---
+
+## Ecdsa [toc] [#Ecdsa]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/crypto/Ecdsa";
+```
+
+`secp256k1EcdsaVerify` accepts both `(r, s)` and `(r, n - s)`, where `n` is the secp256k1 group order.
+This module rejects `s > n / 2`, so each signature has one encoding.
+Requires ZKIR v3: the `Secp256k1` types are unavailable on the v2 backend.
+
+---
+
+### Ledger [toc] [#Ecdsa-Ledger]
+### Ledger [!toc] [#Ecdsa-Ledger]
+
+None.
+
+### Witnesses [toc] [#Ecdsa-Witnesses]
+### Witnesses [!toc] [#Ecdsa-Witnesses]
+
+None.
+
+### Circuits [toc] [#Ecdsa-Circuits]
+### Circuits [!toc] [#Ecdsa-Circuits]
+
+#### isLowS [toc] [#Ecdsa-isLowS]
+#### [!toc] [#Ecdsa-isLowS]
+
+ Returns whether `s` is canonical, i.e. `s <= n / 2` where `n`
+ is the secp256k1 group order.
+
+
+#### assertLowS [toc] [#Ecdsa-assertLowS]
+#### [!toc] [#Ecdsa-assertLowS]
+
+ Rejects a signature in the high half.
+
+ Requirements:
+
+ * `sig.s` must satisfy `s <= n / 2`.
+
+
+#### secp256k1EcdsaVerifyLowS [toc] [#Ecdsa-secp256k1EcdsaVerifyLowS]
+#### [!toc] [#Ecdsa-secp256k1EcdsaVerifyLowS]
+
+ Verifies `sig` over `msgHash` under `pk`, accepting only the
+ low-s encoding. Reports the result instead of asserting, so a caller can
+ fold it into a count or a threshold.
+
+
+---
+
+## Eip712 [toc] [#Eip712]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/crypto/Eip712";
+```
+
+The domain is `(name, version, salt)`; `chainId` and `verifyingContract` are omitted.
+Requires ZKIR v3: `keccak256` is unavailable on the v2 backend.
+
+---
+
+### Ledger [toc] [#Eip712-Ledger]
+### Ledger [!toc] [#Eip712-Ledger]
+
+None.
+
+### Witnesses [toc] [#Eip712-Witnesses]
+### Witnesses [!toc] [#Eip712-Witnesses]
+
+None.
+
+### Circuits [toc] [#Eip712-Circuits]
+### Circuits [!toc] [#Eip712-Circuits]
+
+#### domainSeparator [toc] [#Eip712-domainSeparator]
+#### [!toc] [#Eip712-domainSeparator]
+
+ Computes the domain separator for `(name, version, salt)`.
+
+ EIP-712 encodes a `string` field as the keccak hash of its UTF-8 bytes,
+ so the name and version arrive already hashed: `hashedName` is `keccak256(bytes(name))`
+ and `hashedVersion` is `keccak256(bytes(version))`. Compact has no dynamic types,
+ so a consumer hashes its own fixed-length literals. `salt` is a per-deployment,
+ per-network random value.
+
+
+#### hashTypedData [toc] [#Eip712-hashTypedData]
+#### [!toc] [#Eip712-hashTypedData]
+
+ Wraps a struct hash in the typed-data envelope `keccak256(0x1901 || separator || structHash)`,
+ yielding the digest an EVM signer actually signs. `structHash` is
+ `keccak256(typeHash || the operation's ABI words)`.
+
+
+ A zero separator is what an uninitialized ledger cell reads as, and a digest
+ built from one still verifies signatures, so this circuit fails closed on it.
+
+
+ Requirements:
+
+ * `separator` must be non-zero, which an initialized domain always is.
+
diff --git a/content/contracts-compact/0.4.x/api/fungible-token.mdx b/content/contracts-compact/0.4.x/api/fungible-token.mdx
new file mode 100644
index 00000000..f3fcc3cd
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/fungible-token.mdx
@@ -0,0 +1,530 @@
+---
+title: FungibleToken API
+---
+
+This module provides the full FungibleToken module API.
+
+
+ For an overview of the module, read the [FungibleToken guide](../fungible-token).
+
+
+## FungibleToken [toc] [#FungibleToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken";
+```
+
+---
+
+### Ledger [toc] [#FungibleToken-Ledger]
+### Ledger [!toc] [#FungibleToken-Ledger]
+
+#### _isInitialized [toc] [#FungibleToken-_isInitialized]
+#### [!toc] [#FungibleToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _balances [toc] [#FungibleToken-_balances]
+#### [!toc] [#FungibleToken-_balances]
+
+ Mapping from account addresses to their token balances.
+
+
+#### _allowances [toc] [#FungibleToken-_allowances]
+#### [!toc] [#FungibleToken-_allowances]
+
+ Mapping from owner accounts to spender accounts and their allowances.
+
+
+#### _totalSupply [toc] [#FungibleToken-_totalSupply]
+#### [!toc] [#FungibleToken-_totalSupply]
+
+ Total tokens in circulation. `_update` raises it on a mint and
+ lowers it on a burn, rejecting an increase that would overflow `Uint<128>`.
+
+
+#### _name [toc] [#FungibleToken-_name]
+#### [!toc] [#FungibleToken-_name]
+
+ The token's human-readable name, set once at construction.
+
+
+#### _symbol [toc] [#FungibleToken-_symbol]
+#### [!toc] [#FungibleToken-_symbol]
+
+ The token's ticker symbol, set once at construction.
+
+
+#### _decimals [toc] [#FungibleToken-_decimals]
+#### [!toc] [#FungibleToken-_decimals]
+
+ The number of decimals used to display balances, set once at
+ construction. Purely presentational; no circuit scales by it.
+
+
+### Witnesses [toc] [#FungibleToken-Witnesses]
+### Witnesses [!toc] [#FungibleToken-Witnesses]
+
+#### wit_FungibleTokenSK [toc] [#FungibleToken-wit_FungibleTokenSK]
+#### [!toc] [#FungibleToken-wit_FungibleTokenSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#FungibleToken-Circuits]
+### Circuits [!toc] [#FungibleToken-Circuits]
+
+#### initialize [toc] [#FungibleToken-initialize]
+#### [!toc] [#FungibleToken-initialize]
+
+ Initializes the contract by setting the name, symbol, and decimals.
+ This MUST be called in the implementing contract's constructor. Failure to do so
+ can lead to an irreparable contract.
+
+ Requirements:
+
+ * Contract is not initialized.
+
+
+#### name [toc] [#FungibleToken-name]
+#### [!toc] [#FungibleToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#FungibleToken-symbol]
+#### [!toc] [#FungibleToken-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#FungibleToken-decimals]
+#### [!toc] [#FungibleToken-decimals]
+
+ Returns the number of decimals used to get its user representation.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### totalSupply [toc] [#FungibleToken-totalSupply]
+#### [!toc] [#FungibleToken-totalSupply]
+
+ Returns the value of tokens in existence.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### balanceOf [toc] [#FungibleToken-balanceOf]
+#### [!toc] [#FungibleToken-balanceOf]
+
+ Returns the value of tokens owned by `account`.
+
+ Manually checks if `account` is a key in the map and returns 0 if it is not.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transfer [toc] [#FungibleToken-transfer]
+#### [!toc] [#FungibleToken-transfer]
+
+ Moves a `value` amount of tokens from the caller's account to `to`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not a ContractAddress.
+ * `to` is not the zero address.
+ * The caller has a balance of at least `value`.
+
+
+#### _unsafeTransfer [toc] [#FungibleToken-_unsafeTransfer]
+#### [!toc] [#FungibleToken-_unsafeTransfer]
+
+ Unsafe variant of [transfer](#FungibleToken-transfer) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not the zero address.
+ * The caller has a balance of at least `value`.
+
+
+#### allowance [toc] [#FungibleToken-allowance]
+#### [!toc] [#FungibleToken-allowance]
+
+ Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner`
+ through [transferFrom](#FungibleToken-transferFrom). This value changes when [approve](#FungibleToken-approve) or [transferFrom](#FungibleToken-transferFrom) are called.
+
+ Manually checks if `owner` and `spender` are keys in the map and returns 0 if they are not.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### approve [toc] [#FungibleToken-approve]
+#### [!toc] [#FungibleToken-approve]
+
+ Sets a `value` amount of tokens as allowance of `spender` over the caller's tokens.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `spender` is not the zero address.
+
+
+#### transferFrom [toc] [#FungibleToken-transferFrom]
+#### [!toc] [#FungibleToken-transferFrom]
+
+ Moves `value` tokens from `fromAddress` to `to` using the allowance mechanism.
+ `value` is the deducted from the caller's allowance.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have a balance of at least `value`.
+ * `to` is not the zero address.
+ * `to` is not a ContractAddress.
+ * The caller has an allowance of `fromAddress`'s tokens of at least `value`.
+
+
+#### _unsafeTransferFrom [toc] [#FungibleToken-_unsafeTransferFrom]
+#### [!toc] [#FungibleToken-_unsafeTransferFrom]
+
+ Unsafe variant of [transferFrom](#FungibleToken-transferFrom) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have a balance of at least `value`.
+ * `to` is not the zero address.
+ * The caller has an allowance of `fromAddress`'s tokens of at least `value`.
+
+
+#### _transfer [toc] [#FungibleToken-_transfer]
+#### [!toc] [#FungibleToken-_transfer]
+
+ Moves a `value` amount of tokens from `fromAddress` to `to`.
+ This circuit is equivalent to [transfer](#FungibleToken-transfer), and can be used to
+ e.g. implement automatic token fees, slashing mechanisms, etc.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `fromAddress` must have at least a balance of `value`.
+ * `to` must not be the zero address.
+ * `to` must not be a ContractAddress.
+
+
+#### _unsafeUncheckedTransfer [toc] [#FungibleToken-_unsafeUncheckedTransfer]
+#### [!toc] [#FungibleToken-_unsafeUncheckedTransfer]
+
+ Unsafe variant of [transferFrom](#FungibleToken-transferFrom) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not the zero address.
+ * `to` is not the zero address.
+
+
+#### _mint [toc] [#FungibleToken-_mint]
+#### [!toc] [#FungibleToken-_mint]
+
+ Creates a `value` amount of tokens and assigns them to `account`,
+ by transferring it from the zero address. Relies on the `update` mechanism.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `account` is not a ContractAddress.
+ * `account` is not the zero address.
+
+
+#### _unsafeMint [toc] [#FungibleToken-_unsafeMint]
+#### [!toc] [#FungibleToken-_unsafeMint]
+
+ Unsafe variant of [_mint](#FungibleToken-_mint) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `account` is not the zero address.
+
+
+#### _burn [toc] [#FungibleToken-_burn]
+#### [!toc] [#FungibleToken-_burn]
+
+ Destroys a `value` amount of tokens from `account`, lowering the total supply.
+ Relies on the `_update` mechanism.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `account` is not the zero address.
+ * `account` must have at least a balance of `value`.
+
+
+#### _approve [toc] [#FungibleToken-_approve]
+#### [!toc] [#FungibleToken-_approve]
+
+ Sets `value` as the allowance of `spender` over the `owner`'s tokens.
+ This circuit is equivalent to [approve](#FungibleToken-approve), and can be used to
+ e.g. set automatic allowances for certain subsystems, etc.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `owner` is not the zero address.
+ * `spender` is not the zero address.
+
+
+#### _spendAllowance [toc] [#FungibleToken-_spendAllowance]
+#### [!toc] [#FungibleToken-_spendAllowance]
+
+ Updates `owner`'s allowance for `spender` based on spent `value`.
+ Does not update the allowance value in case of infinite allowance.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `spender` must have at least an allowance of `value` from `owner`.
+
+
diff --git a/content/contracts-compact/0.4.x/api/multi-token.mdx b/content/contracts-compact/0.4.x/api/multi-token.mdx
new file mode 100644
index 00000000..e7e54cb8
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/multi-token.mdx
@@ -0,0 +1,445 @@
+---
+title: MultiToken API
+---
+
+This module provides the full MultiToken module API.
+
+
+ For an overview of the module, read the [MultiToken guide](../multi-token).
+
+
+## MultiToken [toc] [#MultiToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken";
+```
+
+---
+
+### Ledger [toc] [#MultiToken-Ledger]
+### Ledger [!toc] [#MultiToken-Ledger]
+
+#### _isInitialized [toc] [#MultiToken-_isInitialized]
+#### [!toc] [#MultiToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _balances [toc] [#MultiToken-_balances]
+#### [!toc] [#MultiToken-_balances]
+
+ Mapping from token ID to account balances.
+
+
+#### _operatorApprovals [toc] [#MultiToken-_operatorApprovals]
+#### [!toc] [#MultiToken-_operatorApprovals]
+
+ Mapping from account to operator approvals.
+
+
+#### _uri [toc] [#MultiToken-_uri]
+#### [!toc] [#MultiToken-_uri]
+
+ Base URI for computing token URIs.
+ Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/`id`.json
+
+
+### Witnesses [toc] [#MultiToken-Witnesses]
+### Witnesses [!toc] [#MultiToken-Witnesses]
+
+#### wit_MultiTokenSK [toc] [#MultiToken-wit_MultiTokenSK]
+#### [!toc] [#MultiToken-wit_MultiTokenSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#MultiToken-Circuits]
+### Circuits [!toc] [#MultiToken-Circuits]
+
+#### initialize [toc] [#MultiToken-initialize]
+#### [!toc] [#MultiToken-initialize]
+
+ Initializes the contract by setting the base URI for all tokens.
+
+ Requirements:
+
+ * Must be called in the contract's constructor.
+ * Contract is not initialized.
+
+
+#### uri [toc] [#MultiToken-uri]
+#### [!toc] [#MultiToken-uri]
+
+ This implementation returns the same URI for *all* token types. It relies
+ on the token type ID substitution mechanism defined in the EIP:
+ https://eips.ethereum.org/EIPS/eip-1155#metadata.
+ Clients calling this function must replace the `\{id\}` substring with the
+ actual token type ID.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### balanceOf [toc] [#MultiToken-balanceOf]
+#### [!toc] [#MultiToken-balanceOf]
+
+ Returns the amount of `id` tokens owned by `account`.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### setApprovalForAll [toc] [#MultiToken-setApprovalForAll]
+#### [!toc] [#MultiToken-setApprovalForAll]
+
+ Enables or disables approval for `operator` to manage all of the caller's assets.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_MultiTokenSK](#MultiToken-wit_MultiTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `operator` is not zero.
+
+
+#### isApprovedForAll [toc] [#MultiToken-isApprovedForAll]
+#### [!toc] [#MultiToken-isApprovedForAll]
+
+ Queries if `operator` is an authorized operator for `account`.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transferFrom [toc] [#MultiToken-transferFrom]
+#### [!toc] [#MultiToken-transferFrom]
+
+ Transfers ownership of `value` amount of `id` tokens from `fromAddress` to `to`.
+ The caller must be `fromAddress` or approved to transfer on their behalf.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_MultiTokenSK](#MultiToken-wit_MultiTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not a ContractAddress.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * Caller must be `fromAddress` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _transfer [toc] [#MultiToken-_transfer]
+#### [!toc] [#MultiToken-_transfer]
+
+ Transfers ownership of `value` amount of `id` tokens from `fromAddress` to `to`.
+ Does not impose restrictions on the caller, making it suitable for composition
+ in higher-level contract logic.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not a ContractAddress.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _unsafeTransferFrom [toc] [#MultiToken-_unsafeTransferFrom]
+#### [!toc] [#MultiToken-_unsafeTransferFrom]
+
+ Unsafe variant of [transferFrom](#MultiToken-transferFrom) which allows transfers to contract addresses.
+ The caller must be `fromAddress` or approved to transfer on their behalf.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_MultiTokenSK](#MultiToken-wit_MultiTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not zero.
+ * `fromAddress` is not zero.
+ * Caller must be `fromAddress` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll).
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _unsafeTransfer [toc] [#MultiToken-_unsafeTransfer]
+#### [!toc] [#MultiToken-_unsafeTransfer]
+
+ Unsafe variant of [_transfer](#MultiToken-_transfer) which allows transfers to contract addresses.
+ Does not impose restrictions on the caller, making it suitable as a low-level
+ building block for advanced contract logic.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _setURI [toc] [#MultiToken-_setURI]
+#### [!toc] [#MultiToken-_setURI]
+
+ Sets a new URI for all token types, by relying on the token type ID
+ substitution mechanism defined in the MultiToken standard.
+ See https://eips.ethereum.org/EIPS/eip-1155#metadata.
+
+
+ By this mechanism, any occurrence of the `\{id\}` substring in either the
+ URI or any of the values in the JSON file at said URI will be replaced by
+ clients with the token type ID.
+
+ For example, the `https://token-cdn-domain/\{id\}.json` URI would be
+ interpreted by clients as
+ `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
+ for token type ID 0x4cce0.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#MultiToken-_mint]
+#### [!toc] [#MultiToken-_mint]
+
+ Creates a `value` amount of tokens of type `id`, and assigns them to `to`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from
+ being inadvertently locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+
+
+#### _unsafeMint [toc] [#MultiToken-_unsafeMint]
+#### [!toc] [#MultiToken-_unsafeMint]
+
+ Unsafe variant of [_mint](#MultiToken-_mint) which allows transfers to contract addresses.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `to` is not zero.
+
+
+#### _burn [toc] [#MultiToken-_burn]
+#### [!toc] [#MultiToken-_burn]
+
+ Destroys a `value` amount of tokens of type `id` from `fromAddress`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `fromAddress` is not zero.
+ * `fromAddress` must have an `id` balance of at least `value`.
+
+
+#### _setApprovalForAll [toc] [#MultiToken-_setApprovalForAll]
+#### [!toc] [#MultiToken-_setApprovalForAll]
+
+ Enables or disables approval for `operator` to manage all of `owner`'s assets.
+
+
+ This circuit does not check for access permissions but can be useful as a building block
+ for more complex contract logic.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `owner` is not zero.
+ * `operator` is not zero.
+
+
diff --git a/content/contracts-compact/0.4.x/api/multisig.mdx b/content/contracts-compact/0.4.x/api/multisig.mdx
new file mode 100644
index 00000000..54be9394
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/multisig.mdx
@@ -0,0 +1,2174 @@
+---
+title: Multisig API
+---
+
+This page provides the full API for the Multisig modules.
+
+
+ For an overview of the modules, read the [Multisig guide](../multisig).
+
+
+The page covers the following modules:
+
+- [Signer](#Signer): the registry of who may approve and how many approvals are required, generic over the signer identity type.
+- [Secp256k1EcdsaMultisig](#Secp256k1EcdsaMultisig): a `t`-of-`n` signer set of secp256k1 key commitments that verifies `t` ECDSA approvals over a digest.
+- [ProposalManager](#ProposalManager): the lifecycle of transfer proposals, independent of the token kind.
+- [ShieldedTreasury](#ShieldedTreasury): holds one shielded coin per token color on the ledger and sends from it.
+- [ShieldedTreasuryStateless](#ShieldedTreasuryStateless): receives and sends shielded coins without keeping any ledger state.
+- [UnshieldedTreasury](#UnshieldedTreasury): holds unshielded tokens with a tracked per-color balance.
+- [ForwarderPrivate](#ForwarderPrivate): forwards shielded deposits to a parent stored as a commitment.
+- [ForwarderShielded](#ForwarderShielded): forwards shielded deposits to a public parent in the same transaction.
+- [ForwarderUnshielded](#ForwarderUnshielded): forwards unshielded deposits to a public parent in the same transaction.
+- [ShieldedMultiSigV2](#ShieldedMultiSigV2): preset; a stateless shielded wallet whose spends are authorized by `t` EIP-712 ECDSA signatures.
+- [NativeShieldedTokenIssuer](#NativeShieldedTokenIssuer): preset; a native shielded token minted and burned only with `t` EIP-712 ECDSA signatures.
+
+## Signer [toc] [#Signer]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Signer">;
+```
+
+`Signer` is generic over the signer identity type `T`, chosen at import time (for example `Bytes<32>`, `JubjubPoint`, or `Either`).
+The module never resolves the caller; the consuming contract passes an identity in.
+It has no `initialize`: the consuming contract registers signers with [_addSigner](#Signer-_addSigner) and sets the threshold with [_changeThreshold](#Signer-_changeThreshold) or [_setThreshold](#Signer-_setThreshold).
+
+---
+
+### Ledger [toc] [#Signer-Ledger]
+### Ledger [!toc] [#Signer-Ledger]
+
+#### _signers [toc] [#Signer-_signers]
+#### [!toc] [#Signer-_signers]
+
+ The set of registered signer identities.
+
+
+#### _signerCount [toc] [#Signer-_signerCount]
+#### [!toc] [#Signer-_signerCount]
+
+ The number of registered signers (at most 255).
+
+
+#### _threshold [toc] [#Signer-_threshold]
+#### [!toc] [#Signer-_threshold]
+
+ The minimum number of approvals required. Zero until it is set.
+
+
+### Witnesses [toc] [#Signer-Witnesses]
+### Witnesses [!toc] [#Signer-Witnesses]
+
+None.
+
+### Circuits [toc] [#Signer-Circuits]
+### Circuits [!toc] [#Signer-Circuits]
+
+#### assertSigner [toc] [#Signer-assertSigner]
+#### [!toc] [#Signer-assertSigner]
+
+ Asserts that the given caller is an active signer.
+
+ Requirements:
+
+ * `caller` must be a member of the signers registry.
+
+
+#### assertThresholdMet [toc] [#Signer-assertThresholdMet]
+#### [!toc] [#Signer-assertThresholdMet]
+
+ Asserts that the given approval count meets the threshold.
+
+
+ `approvalCount` is supplied by the consuming contract and is
+ compared against the threshold only. This circuit never consults the signer
+ registry. Ensuring the count reflects distinct, currently-registered signers
+ is the consuming contract's responsibility, and a contract whose approval
+ accounting is not itself gated by [assertSigner](#Signer-assertSigner) gets no protection here.
+
+ The count is deliberately left unbounded by `_signerCount`: approvals
+ collected before a signer was removed must still count toward a threshold
+ the remaining signers satisfy, and bounding the count would reject those.
+ See the stale-signer-set guidance in the module header.
+
+
+ Requirements:
+
+ * Ledger threshold must be set (not be zero). An unconfigured module has a
+ zero threshold, so this rejects before any approval count is compared.
+ * `approvalCount` must be >= threshold.
+
+
+#### getSignerCount [toc] [#Signer-getSignerCount]
+#### [!toc] [#Signer-getSignerCount]
+
+ Returns the current signer count. Returns zero when no
+ signers have been registered.
+
+
+#### getThreshold [toc] [#Signer-getThreshold]
+#### [!toc] [#Signer-getThreshold]
+
+ Returns the approval threshold. Returns zero when the
+ threshold has not been set.
+
+
+#### isSigner [toc] [#Signer-isSigner]
+#### [!toc] [#Signer-isSigner]
+
+ Returns whether the given account is an active signer.
+
+
+#### _addSigner [toc] [#Signer-_addSigner]
+#### [!toc] [#Signer-_addSigner]
+
+ Adds a new signer to the registry.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `signer` must not already be an active signer.
+
+
+#### _removeSigner [toc] [#Signer-_removeSigner]
+#### [!toc] [#Signer-_removeSigner]
+
+ Removes a signer from the registry.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `signer` must be an active signer.
+ * Removal must not drop signer count below threshold.
+
+
+#### _changeThreshold [toc] [#Signer-_changeThreshold]
+#### [!toc] [#Signer-_changeThreshold]
+
+ Updates the approval threshold.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `newThreshold` must not be zero.
+ * `newThreshold` must not exceed the current signer count.
+
+
+#### _setThreshold [toc] [#Signer-_setThreshold]
+#### [!toc] [#Signer-_setThreshold]
+
+ Sets the approval threshold without checking
+ against the current signer count.
+
+
+ This is intended for use during contract construction
+ or custom setup flows where signers may not yet be registered.
+
+ Leaving `newThreshold` above the signer count is permitted and expected
+ mid-setup, and is not self-enforcing: [assertThresholdMet](#Signer-assertThresholdMet) compares
+ `approvalCount` against the threshold alone, so a caller supplying a matching
+ count satisfies it even against an empty registry. Contracts must not gate
+ operations on [assertThresholdMet](#Signer-assertThresholdMet) while setup is incomplete. Lower the
+ threshold with [_setThreshold](#Signer-_setThreshold); [_changeThreshold](#Signer-_changeThreshold) cannot lower one while the
+ signer count is zero, because that would require a zero threshold.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy. Use [_changeThreshold](#Signer-_changeThreshold) for
+ operational threshold changes with signer count validation.
+
+
+ Requirements:
+
+ * `newThreshold` must not be zero.
+
+
+---
+
+## Secp256k1EcdsaMultisig [toc] [#Secp256k1EcdsaMultisig]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Secp256k1EcdsaMultisig"<2>;
+```
+
+`Secp256k1EcdsaMultisig<#t>` takes the threshold `t` as its import generic; `initialize` fixes the signer count `n`.
+It wraps a `Signer>` registry, requires ZKIR v3, and must be imported at one width per contract and never alongside `Signer`.
+
+---
+
+### Ledger [toc] [#Secp256k1EcdsaMultisig-Ledger]
+### Ledger [!toc] [#Secp256k1EcdsaMultisig-Ledger]
+
+#### _signers [toc] [#Secp256k1EcdsaMultisig-_signers]
+#### [!toc] [#Secp256k1EcdsaMultisig-_signers]
+
+ The registered signer commitments. Re-exported from `Signer`.
+
+
+#### _signerCount [toc] [#Secp256k1EcdsaMultisig-_signerCount]
+#### [!toc] [#Secp256k1EcdsaMultisig-_signerCount]
+
+ The number of registered signers, `n`. Re-exported from `Signer`.
+
+
+#### _threshold [toc] [#Secp256k1EcdsaMultisig-_threshold]
+#### [!toc] [#Secp256k1EcdsaMultisig-_threshold]
+
+ The approval threshold, `t` once initialized. Re-exported from `Signer`.
+
+
+#### _instanceSalt [toc] [#Secp256k1EcdsaMultisig-_instanceSalt]
+#### [!toc] [#Secp256k1EcdsaMultisig-_instanceSalt]
+
+ Deployment-supplied salt for signer commitments. Set at
+ initialization and never changed. The registered commitments must have been
+ derived under this same salt.
+
+
+### Witnesses [toc] [#Secp256k1EcdsaMultisig-Witnesses]
+### Witnesses [!toc] [#Secp256k1EcdsaMultisig-Witnesses]
+
+None.
+
+### Circuits [toc] [#Secp256k1EcdsaMultisig-Circuits]
+### Circuits [!toc] [#Secp256k1EcdsaMultisig-Circuits]
+
+#### initialize [toc] [#Secp256k1EcdsaMultisig-initialize]
+#### [!toc] [#Secp256k1EcdsaMultisig-initialize]
+
+ Stores the instance salt and registers the signer set at threshold `t`.
+ It writes the `sealed` salt, so it MUST only be called from the consuming contract's constructor.
+ The set is final: signers must be known at deploy, and no circuit adds, removes, or replaces one afterwards.
+
+ Requirements:
+
+ * Can only be called once.
+ * `instanceSalt` should be cryptographically random.
+ * `signerCommitments` must not contain duplicates.
+ * `n` must be at least `t`.
+
+
+#### assertApprovals [toc] [#Secp256k1EcdsaMultisig-assertApprovals]
+#### [!toc] [#Secp256k1EcdsaMultisig-assertApprovals]
+
+ Verifies `t` approvals over `msgHash`: distinct registered signers,
+ each with a valid low-s signature.
+ `pubkeys` and `signatures` are parallel: entry `i` of each belongs to the same signer.
+
+ The threshold check runs first, so an uninitialized registry fails before any signature work.
+ Then all `t(t-1)/2` pairs of commitments are compared, and each entry's commitment and signature are checked.
+ Verification goes through [secp256k1EcdsaVerifyLowS](./crypto#Ecdsa), so a high-s encoding is an invalid signature.
+ The commitments of the `t` approvers are disclosed, so which signers approved is public; keys and signatures are not.
+
+ Cost (ZKIR v3): `t = 1` is k=16, rows=40538; `t = 2` is k=17, rows=64908; `t = 3` is k=17, rows=94995.
+
+
+ This circuit verifies signatures over whatever `msgHash` it is handed.
+ Replay, domain, and instance binding belong in the consumer's digest.
+
+
+ Requirements:
+
+ * The module must be initialized.
+ * No two entries may be the same signer.
+ * Every public key must hash to a registered signer commitment.
+ * Every signature must be valid over `msgHash` and use the low-s encoding.
+
+
+#### calculateSignerId [toc] [#Secp256k1EcdsaMultisig-calculateSignerId]
+#### [!toc] [#Secp256k1EcdsaMultisig-calculateSignerId]
+
+ Computes a signer commitment from a secp256k1 public key,
+ as `persistentHash` of the affine coordinates `pkX` and `pkY`, the `salt`, and the domain `pad(32, "Secp256k1EcdsaMultisig:signer")`.
+ Pure, so the deployer can derive constructor commitments off-chain
+ the same way the circuits derive them in-circuit.
+
+
+ The hash is over the Compact encoding of an internal struct, not a byte concatenation.
+ Do not reimplement it: call this circuit.
+ A wrong derivation registers commitments no key can satisfy.
+
+
+ Requirements:
+
+ * `pk` must not be the secp256k1 identity point, which has no coordinates.
+
+
+#### getSignerCount [toc] [#Secp256k1EcdsaMultisig-getSignerCount]
+#### [!toc] [#Secp256k1EcdsaMultisig-getSignerCount]
+
+ Returns the number of registered signers.
+
+
+#### getThreshold [toc] [#Secp256k1EcdsaMultisig-getThreshold]
+#### [!toc] [#Secp256k1EcdsaMultisig-getThreshold]
+
+ Returns the approval threshold.
+
+
+#### isSigner [toc] [#Secp256k1EcdsaMultisig-isSigner]
+#### [!toc] [#Secp256k1EcdsaMultisig-isSigner]
+
+ Returns whether the given commitment is a registered signer.
+
+
+---
+
+## ProposalManager [toc] [#ProposalManager]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ProposalManager";
+```
+
+---
+
+### Types [toc] [#ProposalManager-Types]
+### Types [!toc] [#ProposalManager-Types]
+
+#### ProposalStatus [toc] [#ProposalManager-ProposalStatus]
+#### [!toc] [#ProposalManager-ProposalStatus]
+
+ Proposal lifecycle states. `Proposal.status` stores `Active`, `Executed`, or `Cancelled`;
+ [getProposalStatus](#ProposalManager-getProposalStatus) derives the other two.
+
+ * `Inactive`: no proposal with that id exists. Never stored.
+ * `Active`: created and not retired. Executable only while the block time is below `Proposal.expiry`.
+ * `Executed`: [_markExecuted](#ProposalManager-_markExecuted) ran. Terminal.
+ * `Cancelled`: [_cancelProposal](#ProposalManager-_cancelProposal) ran. Terminal.
+ * `Expired`: stored `Active` with an `expiry` at or before the current block time. Never stored.
+
+
+#### RecipientKind [toc] [#ProposalManager-RecipientKind]
+#### [!toc] [#ProposalManager-RecipientKind]
+
+ Says how a recipient's 32 address bytes are to be read: as a Zswap coin public key, a user address, or a contract address.
+ `Contract` is accepted by both [toShieldedRecipient](#ProposalManager-toShieldedRecipient) and [toUnshieldedRecipient](#ProposalManager-toUnshieldedRecipient),
+ so the tag alone does not determine the rail.
+
+
+#### Recipient [toc] [#ProposalManager-Recipient]
+#### [!toc] [#ProposalManager-Recipient]
+
+ A typed recipient. Construct it with the recipient helper circuits rather than directly.
+
+
+#### Proposal [toc] [#ProposalManager-Proposal]
+#### [!toc] [#ProposalManager-Proposal]
+
+ A transfer proposal: recipient, token color, amount, deadline, and stored lifecycle stage.
+ `expiry` is in seconds since the Unix epoch. Reaching a terminal stage changes `status` only,
+ so an executed or cancelled proposal keeps the deadline it was created with.
+
+
+ `expiry` marks when a proposal STOPS being executable, not when it starts:
+ an `Active` proposal is executable while `blockTime < expiry` and inert once `blockTime >= expiry`.
+ The module implements no execution delay; a consumer that needs one must compose it.
+
+
+
+### Ledger [toc] [#ProposalManager-Ledger]
+### Ledger [!toc] [#ProposalManager-Ledger]
+
+#### _nextProposalId [toc] [#ProposalManager-_nextProposalId]
+#### [!toc] [#ProposalManager-_nextProposalId]
+
+ Counter of proposals created. Its value is the id of the most recent proposal.
+
+
+#### _proposals [toc] [#ProposalManager-_proposals]
+#### [!toc] [#ProposalManager-_proposals]
+
+ Mapping from proposal id to proposal. Proposals are public: every recipient address, color, amount, expiry, and status is readable off-chain.
+ An off-chain reader takes `status` as stored and, when it is `Active`, compares `expiry` with the current block time.
+ Entries are never deleted.
+
+
+### Witnesses [toc] [#ProposalManager-Witnesses]
+### Witnesses [!toc] [#ProposalManager-Witnesses]
+
+None.
+
+### Circuits [toc] [#ProposalManager-Circuits]
+### Circuits [!toc] [#ProposalManager-Circuits]
+
+#### shieldedUserRecipient [toc] [#ProposalManager-shieldedUserRecipient]
+#### [!toc] [#ProposalManager-shieldedUserRecipient]
+
+ Constructs a shielded user recipient.
+
+
+#### unshieldedUserRecipient [toc] [#ProposalManager-unshieldedUserRecipient]
+#### [!toc] [#ProposalManager-unshieldedUserRecipient]
+
+ Constructs an unshielded user recipient.
+
+
+#### contractRecipient [toc] [#ProposalManager-contractRecipient]
+#### [!toc] [#ProposalManager-contractRecipient]
+
+ Constructs a contract recipient.
+
+
+#### toShieldedRecipient [toc] [#ProposalManager-toShieldedRecipient]
+#### [!toc] [#ProposalManager-toShieldedRecipient]
+
+ Converts a Recipient to a shielded send recipient.
+ Handles both ShieldedUser and Contract kinds.
+
+ Requirements:
+
+ * Recipient kind must be ShieldedUser or Contract.
+
+
+#### toUnshieldedRecipient [toc] [#ProposalManager-toUnshieldedRecipient]
+#### [!toc] [#ProposalManager-toUnshieldedRecipient]
+
+ Converts a Recipient to an unshielded send recipient.
+ Handles both UnshieldedUser and Contract kinds.
+
+ Requirements:
+
+ * Recipient kind must be UnshieldedUser or Contract.
+
+
+#### assertProposalExists [toc] [#ProposalManager-assertProposalExists]
+#### [!toc] [#ProposalManager-assertProposalExists]
+
+ Asserts that a proposal exists.
+
+ Requirements:
+
+ * Proposal with `id` must have been created.
+
+
+#### assertProposalActive [toc] [#ProposalManager-assertProposalActive]
+#### [!toc] [#ProposalManager-assertProposalActive]
+
+ Asserts that a proposal exists and is still executable.
+ A `status` other than `Active` and a deadline that has passed fail with different errors.
+
+
+ Expiry is terminal. A transaction rejected for a stale read succeeds once rebuilt against fresh state,
+ but block time only moves forward, so an expired proposal stays expired.
+ Check the proposal's status before re-proving.
+
+
+ Requirements:
+
+ * Proposal must exist.
+ * Proposal `status` must be `Active`.
+ * Proposal must not have expired.
+
+
+#### _createProposal [toc] [#ProposalManager-_createProposal]
+#### [!toc] [#ProposalManager-_createProposal]
+
+ Creates a new proposal that is executable until `expiry`,
+ in seconds since the Unix epoch.
+
+
+ The recipient is NOT validated beyond being well-typed. Two
+ consequences the consumer must handle:
+
+ * A `kind` the execution rail cannot accept (an `UnshieldedUser` recipient
+ on a shielded treasury, say) creates a proposal that passes here and then
+ reverts in [toShieldedRecipient](#ProposalManager-toShieldedRecipient) at execution. It stays `Active` and
+ unexecutable on that rail until [_cancelProposal](#ProposalManager-_cancelProposal) retires it or its
+ `expiry` lapses.
+ * A zero or otherwise unspendable `address` is accepted, and funds sent to
+ it on execution are unrecoverable.
+
+
+
+ `_nextProposalId` is incremented and then read back for the map key,
+ making this a pinned read-modify-write.
+
+
+
+ `expiry` is only checked against the present: a proposal cannot be born expired.
+ There is deliberately no maximum horizon, so a caller may pass an effectively unreachable expiry.
+ Consuming contracts that need a bounded lifetime must impose their own cap.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `amount` must be greater than 0.
+ * `expiry` must be in the future.
+
+
+#### _cancelProposal [toc] [#ProposalManager-_cancelProposal]
+#### [!toc] [#ProposalManager-_cancelProposal]
+
+ Cancels a proposal, retiring it before its expiry.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Proposal must be active and not expired.
+
+
+#### _markExecuted [toc] [#ProposalManager-_markExecuted]
+#### [!toc] [#ProposalManager-_markExecuted]
+
+ Marks a proposal as executed.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Proposal must be active and not expired.
+
+
+#### getProposal [toc] [#ProposalManager-getProposal]
+#### [!toc] [#ProposalManager-getProposal]
+
+ Returns the full proposal data. The returned `status` is the stored stage,
+ so an `Active` entry may already be past its `expiry`.
+ Prefer [getProposalStatus](#ProposalManager-getProposalStatus) when the effective status is what is wanted.
+
+
+#### getProposalRecipient [toc] [#ProposalManager-getProposalRecipient]
+#### [!toc] [#ProposalManager-getProposalRecipient]
+
+ Returns the recipient of a proposal.
+
+
+#### getProposalAmount [toc] [#ProposalManager-getProposalAmount]
+#### [!toc] [#ProposalManager-getProposalAmount]
+
+ Returns the amount of a proposal.
+
+
+#### getProposalColor [toc] [#ProposalManager-getProposalColor]
+#### [!toc] [#ProposalManager-getProposalColor]
+
+ Returns the token color of a proposal.
+
+
+#### getProposalStatus [toc] [#ProposalManager-getProposalStatus]
+#### [!toc] [#ProposalManager-getProposalStatus]
+
+ Returns the effective status of a proposal: the stored `status` unless it is `Active`,
+ in which case `expiry` and the current block time decide between `Active` and `Expired`.
+ Returns `Inactive` for an id that was never created rather than failing.
+
+
+---
+
+## ShieldedTreasury [toc] [#ShieldedTreasury]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ShieldedTreasury";
+```
+
+---
+
+### Ledger [toc] [#ShieldedTreasury-Ledger]
+### Ledger [!toc] [#ShieldedTreasury-Ledger]
+
+#### _coins [toc] [#ShieldedTreasury-_coins]
+#### [!toc] [#ShieldedTreasury-_coins]
+
+ One stored coin per token color. A deposit of an existing color merges into the stored coin rather than adding a second entry.
+ Every coin held (nonce, color, value, and Merkle index) is readable off-chain in plaintext.
+
+
+#### _received [toc] [#ShieldedTreasury-_received]
+#### [!toc] [#ShieldedTreasury-_received]
+
+ Cumulative value received per token color. Only ever increases, so it is a running total rather than a balance;
+ [getTokenBalance](#ShieldedTreasury-getTokenBalance) reads `_coins` and is the canonical balance.
+
+
+#### _sent [toc] [#ShieldedTreasury-_sent]
+#### [!toc] [#ShieldedTreasury-_sent]
+
+ Cumulative value sent per token color. The counterpart to `_received`; also only ever increases.
+
+
+### Witnesses [toc] [#ShieldedTreasury-Witnesses]
+### Witnesses [!toc] [#ShieldedTreasury-Witnesses]
+
+None.
+
+### Circuits [toc] [#ShieldedTreasury-Circuits]
+### Circuits [!toc] [#ShieldedTreasury-Circuits]
+
+#### _deposit [toc] [#ShieldedTreasury-_deposit]
+#### [!toc] [#ShieldedTreasury-_deposit]
+
+ Receives a shielded coin into the treasury.
+
+ The coin is first claimed at the protocol level via `receiveShielded`,
+ which allocates the Merkle tree index required by `insertCoin`.
+ The coin is then merged with any existing coin of the same color,
+ or inserted as a new entry if no coin of that color exists.
+
+ Zero-value deposits are permitted and are not always a no-op. On a color the
+ treasury does not yet hold, a zero-value coin is inserted and occupies that
+ color's single slot until a later deposit merges into it; if a coin of that
+ color already exists, the zero merges into it via `mergeCoinImmediate` and
+ the stored value is unchanged. They may serve as signaling mechanisms when
+ events are supported. They are not useful as privacy decoys, this
+ treasury's holdings are public (see the module header).
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Deposit must not cause received total overflow.
+
+
+#### _send [toc] [#ShieldedTreasury-_send]
+#### [!toc] [#ShieldedTreasury-_send]
+
+ Sends shielded tokens from the treasury.
+
+ Looks up the stored coin by color, verifies sufficient value,
+ and executes the shielded send. `sendShielded` already routes any
+ change back to this contract as a self-owned output. Unlike the
+ stateless variant, this treasury RETAINS the change: it is recorded in
+ the UTXO map for the next spend. The returned `result.change` therefore
+ belongs to the treasury and must not be re-spent by the caller (doing
+ so would double-spend the recorded coin).
+
+
+ The returned `ShieldedSendResult` carries the sent and change coins
+ in plaintext, so sinking it (returning it from an entry point, writing it
+ to ledger, passing it cross-contract) discloses them. `_coins` publishes only
+ the retained change coin, and nothing at all when the send leaves no change,
+ so the sent coin's plaintext is disclosed beyond anything in public state.
+
+
+ A self-addressed coin is auto-received by `sendShielded` but never recorded in `_coins`,
+ so it would be stranded. This circuit therefore rejects sends to self.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `recipient` must not be this contract's own address.
+ * A coin of the given `color` must exist in the treasury.
+ * The coin's value must be >= `amount`.
+ * Send must not cause sent total overflow.
+
+
+#### getTokenBalance [toc] [#ShieldedTreasury-getTokenBalance]
+#### [!toc] [#ShieldedTreasury-getTokenBalance]
+
+ Returns the current token balance for a color.
+ Reads the actual coin value from the UTXO map.
+
+
+#### getReceivedTotal [toc] [#ShieldedTreasury-getReceivedTotal]
+#### [!toc] [#ShieldedTreasury-getReceivedTotal]
+
+ Returns the cumulative received total for a color.
+
+
+#### getSentTotal [toc] [#ShieldedTreasury-getSentTotal]
+#### [!toc] [#ShieldedTreasury-getSentTotal]
+
+ Returns the cumulative sent total for a color.
+
+
+#### getReceivedMinusSent [toc] [#ShieldedTreasury-getReceivedMinusSent]
+#### [!toc] [#ShieldedTreasury-getReceivedMinusSent]
+
+ Returns the difference between cumulative received
+ and cumulative sent totals for a color. Should equal
+ [getTokenBalance](#ShieldedTreasury-getTokenBalance) if accounting is consistent.
+
+
+ The subtraction is unguarded. [_send](#ShieldedTreasury-_send) cannot drive the sent total
+ past the received total. Change re-insertion never increments `_received`
+ so the invariant holds for any consumer that goes through [_deposit](#ShieldedTreasury-_deposit) and
+ [_send](#ShieldedTreasury-_send). A consumer that writes the exported `_coins` directly can break
+ it, and this circuit then fails as an unprovable range violation rather
+ than a named assert.
+
+
+
+---
+
+## ShieldedTreasuryStateless [toc] [#ShieldedTreasuryStateless]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ShieldedTreasuryStateless";
+```
+
+Keeping coin data off the ledger hides it from the public, not from a depositor.
+Whoever knows a deposited coin can find the spend that consumes it, recover the amount sent, and follow every later spend descending from its change.
+
+---
+
+### Ledger [toc] [#ShieldedTreasuryStateless-Ledger]
+### Ledger [!toc] [#ShieldedTreasuryStateless-Ledger]
+
+None. The module keeps no coin map, balances, or totals; the operator tracks coins off-chain.
+
+### Witnesses [toc] [#ShieldedTreasuryStateless-Witnesses]
+### Witnesses [!toc] [#ShieldedTreasuryStateless-Witnesses]
+
+None.
+
+### Circuits [toc] [#ShieldedTreasuryStateless-Circuits]
+### Circuits [!toc] [#ShieldedTreasuryStateless-Circuits]
+
+#### _deposit [toc] [#ShieldedTreasuryStateless-_deposit]
+#### [!toc] [#ShieldedTreasuryStateless-_deposit]
+
+ Receives a shielded coin into the treasury.
+
+
+#### _send [toc] [#ShieldedTreasuryStateless-_send]
+#### [!toc] [#ShieldedTreasuryStateless-_send]
+
+ Sends shielded tokens using a caller-provided coin.
+
+ The coin is supplied by the caller rather than looked up from
+ on-chain state; the token color is derived from `coin.color`.
+ Executes the shielded send and returns the result untouched.
+ `sendShielded` already routes any change back to this contract as a
+ self-owned output, so `result.change` is a live, spendable coin. The
+ implementing contract chooses what to do with it: retain it off-chain and
+ pass it back as the `coin` for a later spend, or spend it onward to a
+ different recipient in the same transaction via `sendImmediateShielded`.
+ (Re-spending change is only meaningful when it goes somewhere other than
+ self — `sendShielded` already routes it to self.) No balance or
+ received/sent accounting is tracked on the ledger.
+
+
+ The returned `ShieldedSendResult` holds the sent and change coins
+ in plaintext (`nonce`, `color`, `value`). Consuming it in-circuit is safe
+ (passing `result.change` to `sendImmediateShielded`, for instance). Returning
+ it from your entry point, writing it to ledger, or passing it to another
+ contract publishes those coin details, including the amount left over from
+ the coin that was spent. Note the compiler will NOT stop you: the
+ `disclose` calls below clear the witness taint, so such a return compiles
+ with no `disclose` of your own and no marker at the disclosure site. If you
+ do not need in-circuit change handling, ignore the result. The change is
+ recoverable off-chain from the transaction's private proving state.
+
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `coin.value` must be >= `amount`.
+
+
+---
+
+## UnshieldedTreasury [toc] [#UnshieldedTreasury]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/UnshieldedTreasury";
+```
+
+---
+
+### Ledger [toc] [#UnshieldedTreasury-Ledger]
+### Ledger [!toc] [#UnshieldedTreasury-Ledger]
+
+#### _balances [toc] [#UnshieldedTreasury-_balances]
+#### [!toc] [#UnshieldedTreasury-_balances]
+
+ Tracked balance per token color. Deposits add to it and sends subtract. It is the authority for the module's sufficiency and overflow guards and stays in step with the protocol balance only while every credit and debit goes through `_deposit` and `_send`.
+
+
+### Witnesses [toc] [#UnshieldedTreasury-Witnesses]
+### Witnesses [!toc] [#UnshieldedTreasury-Witnesses]
+
+None.
+
+### Circuits [toc] [#UnshieldedTreasury-Circuits]
+### Circuits [!toc] [#UnshieldedTreasury-Circuits]
+
+#### _deposit [toc] [#UnshieldedTreasury-_deposit]
+#### [!toc] [#UnshieldedTreasury-_deposit]
+
+ Receives unshielded tokens into the treasury.
+
+ The token receive is executed at the protocol level first via
+ `receiveUnshielded`. The balance map is then updated.
+
+ Zero-value deposits are permitted. While currently a no-op
+ economically, they may serve as signaling mechanisms when events
+ are supported. On a color the treasury has not held, a zero-value
+ operation creates a zero-valued `_balances` entry, which
+ [getTokenBalance](#UnshieldedTreasury-getTokenBalance) reports the same as absence.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * Deposit must not cause balance overflow.
+
+
+#### _send [toc] [#UnshieldedTreasury-_send]
+#### [!toc] [#UnshieldedTreasury-_send]
+
+ Sends unshielded tokens from the treasury.
+
+
+ Recipients other than this contract are not validated. An unspendable recipient (such as a
+ zero address) is accepted and permanently removes the funds from the
+ contract. This is not a sanctioned burn (no unshielded equivalent of
+ `shieldedBurnAddress` is exposed to contracts), so treat recipient
+ validation as the consuming contract's responsibility.
+
+
+ A self-addressed send is auto-received by `sendUnshielded` but still debited from `_balances`,
+ so the funds would be stranded. This circuit therefore rejects sends to self.
+
+
+ Access control is NOT enforced here.
+ The consuming contract must gate this behind its own
+ authorization policy.
+
+
+ Requirements:
+
+ * `recipient` must not be this contract's own address.
+ * `amount` must be non-zero. The ledger rejects zero-value outputs.
+ * The treasury must hold sufficient balance for the given token color.
+
+
+#### getTokenBalance [toc] [#UnshieldedTreasury-getTokenBalance]
+#### [!toc] [#UnshieldedTreasury-getTokenBalance]
+
+ Returns the balance for a given token color.
+
+
+---
+
+## ForwarderPrivate [toc] [#ForwarderPrivate]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ForwarderPrivate";
+```
+
+The commitment hides the parent from the public, not from a depositor.
+Whoever knows a deposited coin can find the drain that spends it, recover the drained amount, and test candidate parent keys against the sent coin's commitment.
+
+---
+
+### Ledger [toc] [#ForwarderPrivate-Ledger]
+### Ledger [!toc] [#ForwarderPrivate-Ledger]
+
+#### _parentCommitment [toc] [#ForwarderPrivate-_parentCommitment]
+#### [!toc] [#ForwarderPrivate-_parentCommitment]
+
+ Commitment to the `(parent, opSecret)` pair, `H(pad(32, "ForwarderPrivate:commitment") || parent || opSecret)`, published in place of the parent itself.
+ The parent key alone is not secret, so `opSecret` supplies the hiding: without it a candidate parent could be hashed and matched against this value.
+ Immutable after initialization.
+
+
+#### _isInitialized [toc] [#ForwarderPrivate-_isInitialized]
+#### [!toc] [#ForwarderPrivate-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#ForwarderPrivate-Witnesses]
+### Witnesses [!toc] [#ForwarderPrivate-Witnesses]
+
+None.
+
+### Circuits [toc] [#ForwarderPrivate-Circuits]
+### Circuits [!toc] [#ForwarderPrivate-Circuits]
+
+#### initialize [toc] [#ForwarderPrivate-initialize]
+#### [!toc] [#ForwarderPrivate-initialize]
+
+ Initializes the forwarder with a parent commitment.
+ Called once from the consuming contract's constructor. The commitment is
+ immutable after init.
+
+ Requirements:
+
+ * Contract must not be initialized.
+ * `parentCommitment` must not be the zero bytes. A zero commitment
+ is the sole drain gate and would leave every deposited coin
+ permanently unrecoverable (no preimage exists for `default>`
+ under the domain-tagged hash).
+
+
+#### _deposit [toc] [#ForwarderPrivate-_deposit]
+#### [!toc] [#ForwarderPrivate-_deposit]
+
+ Receives a shielded coin into the forwarder's custody.
+ No ledger write — coins dwell at the contract address until drained.
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+#### _drain [toc] [#ForwarderPrivate-_drain]
+#### [!toc] [#ForwarderPrivate-_drain]
+
+ Spends a previously-deposited shielded coin to the parent,
+ a coin public key. The caller proves knowledge of `(parent, opSecret)`
+ matching the stored commitment; the coin is sent as a plain shielded note,
+ so the parent stays hidden from anyone who holds no deposited coin.
+ If `coin.value` exceeds `value`,
+ `sendShielded` routes the change back to this contract as a self-owned
+ output; [_drain](#ForwarderPrivate-_drain) returns it untouched via `result.change`. The
+ implementing contract chooses what to do with it: leave it dwelling at
+ the contract for a future drain, or spend it onward to a different
+ recipient in the same transaction via `sendImmediateShielded`.
+
+ The parent is a `ZswapCoinPublicKey`, never a `ContractAddress`: a shielded
+ send to a contract publishes that contract's address in cleartext on the
+ transaction (the protocol must route the coin to a named contract), which
+ would defeat the private-parent guarantee. A coin public key is encrypted
+ inside the Zswap output and never appears on the public transcript.
+
+ Requirements:
+
+ * Contract must be initialized.
+ * `parent` must not be the zero key.
+ * `_calculateParentCommitment(parent.bytes, opSecret) == _parentCommitment`.
+ * `coin.value` must be >= `value` (enforced by `sendShielded`).
+
+
+ **Losing the operational secret is permanent fund loss.** It
+ is the sole drain authorization, with no rotation, revocation, or
+ recovery path. If the operator misplaces it, every shielded coin
+ accumulated at this contract is forever inaccessible, equivalent to
+ losing a hot-wallet private key. Back it up offline before the first
+ deposit and treat it with the same hygiene as a signing key.
+
+
+
+#### _calculateParentCommitment [toc] [#ForwarderPrivate-_calculateParentCommitment]
+#### [!toc] [#ForwarderPrivate-_calculateParentCommitment]
+
+ Computes the parent commitment from `(parentAddr, opSecret)`.
+ Pure circuit — used off-chain by the deployer to compute the
+ constructor argument, and inside [_drain](#ForwarderPrivate-_drain) for the preimage check.
+ Callable without initialization.
+
+ The first hash input is a fixed domain tag
+ (`pad(32, "ForwarderPrivate:commitment")`). The tag prevents
+ preimage collisions with other `persistentHash` users in the
+ system that hash two `Bytes<32>` values — a colliding preimage
+ crafted under a different domain cannot satisfy this commitment.
+
+
+---
+
+## ForwarderShielded [toc] [#ForwarderShielded]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ForwarderShielded";
+```
+
+---
+
+### Ledger [toc] [#ForwarderShielded-Ledger]
+### Ledger [!toc] [#ForwarderShielded-Ledger]
+
+#### _parent [toc] [#ForwarderShielded-_parent]
+#### [!toc] [#ForwarderShielded-_parent]
+
+ The parent recipient, stored generically as
+ `Either` for forward compatibility.
+ [initialize](#ForwarderShielded-initialize) only ever writes the `left` (coin public key) arm today. See
+ the module header.
+
+
+#### _isInitialized [toc] [#ForwarderShielded-_isInitialized]
+#### [!toc] [#ForwarderShielded-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#ForwarderShielded-Witnesses]
+### Witnesses [!toc] [#ForwarderShielded-Witnesses]
+
+None.
+
+### Circuits [toc] [#ForwarderShielded-Circuits]
+### Circuits [!toc] [#ForwarderShielded-Circuits]
+
+#### initialize [toc] [#ForwarderShielded-initialize]
+#### [!toc] [#ForwarderShielded-initialize]
+
+ Initializes the forwarder with a parent recipient.
+ Called once from the consuming contract's constructor. The coin public key is
+ stored in the `left` arm of the generic `_parent` ledger field.
+
+ Requirements:
+
+ * Contract must not be initialized.
+ * `parent` must not be the zero key. A zero parent would forward
+ every deposit to an unspendable key with no recovery path.
+
+
+ Beyond the zero-key check, the caller must ensure `parent` can
+ spend forwarded coins. An unspendable parent strands every deposit with
+ no recovery path.
+
+
+
+#### _deposit [toc] [#ForwarderShielded-_deposit]
+#### [!toc] [#ForwarderShielded-_deposit]
+
+ Receives a shielded coin and atomically forwards it to
+ `_parent`. The coin is claimed at the protocol level via
+ `receiveShielded`, then immediately re-sent via
+ `sendImmediateShielded` to the stored parent recipient (always the
+ `left` / coin-public-key arm today).
+ Returns the coin sent to the parent, with no change: the whole value is forwarded.
+
+
+ The depositor must encrypt the forwarded output to the parent's
+ encryption public key, obtained off-chain. The ledger cannot check that
+ key against `_parent`, so a wrong or missing key leaves the coin
+ spendable by the parent but invisible to its wallet.
+
+
+ Requirements:
+
+ * Contract must be initialized.
+ * The forwarded output must be encrypted to the parent's encryption public key.
+ If unsure, hand the returned `ShieldedSendResult` to the parent so it can recover the coin.
+
+
+---
+
+## ForwarderUnshielded [toc] [#ForwarderUnshielded]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ForwarderUnshielded";
+```
+
+---
+
+### Ledger [toc] [#ForwarderUnshielded-Ledger]
+### Ledger [!toc] [#ForwarderUnshielded-Ledger]
+
+#### _parent [toc] [#ForwarderUnshielded-_parent]
+#### [!toc] [#ForwarderUnshielded-_parent]
+
+ The parent recipient, stored generically as
+ `Either` for forward compatibility.
+ [initialize](#ForwarderUnshielded-initialize) only ever writes the `right` (user address) arm today. See
+ the module header.
+
+
+#### _isInitialized [toc] [#ForwarderUnshielded-_isInitialized]
+#### [!toc] [#ForwarderUnshielded-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#ForwarderUnshielded-Witnesses]
+### Witnesses [!toc] [#ForwarderUnshielded-Witnesses]
+
+None.
+
+### Circuits [toc] [#ForwarderUnshielded-Circuits]
+### Circuits [!toc] [#ForwarderUnshielded-Circuits]
+
+#### initialize [toc] [#ForwarderUnshielded-initialize]
+#### [!toc] [#ForwarderUnshielded-initialize]
+
+ Initializes the forwarder with a parent recipient.
+ Called once from the consuming contract's constructor. The user address is stored
+ in the `right` arm of the generic `_parent` ledger field.
+
+ Requirements:
+
+ * Contract must not be initialized.
+ * `parent` must not be the zero address. A zero parent would forward
+ every deposit to an unspendable address with no recovery path.
+
+
+ Beyond the zero-address check, the caller must ensure `parent`
+ can spend forwarded amounts. An unspendable parent strands every
+ deposit with no recovery path.
+
+
+
+#### _deposit [toc] [#ForwarderUnshielded-_deposit]
+#### [!toc] [#ForwarderUnshielded-_deposit]
+
+ Receives an unshielded amount of `color` and atomically
+ forwards it to `_parent` via `sendUnshielded` (always the `right` /
+ user-address arm today).
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+---
+
+## ShieldedMultiSigV2 [toc] [#ShieldedMultiSigV2]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/presets/ShieldedMultiSigV2"<2>;
+```
+
+`ShieldedMultiSigV2<#t>` composes [Secp256k1EcdsaMultisig](#Secp256k1EcdsaMultisig) and [ShieldedTreasuryStateless](#ShieldedTreasuryStateless); `t` is both the threshold and the approval width.
+It requires ZKIR v3, and implementing contracts MUST NOT import `Signer` or `Secp256k1EcdsaMultisig` directly. The `execute` complexity is measured at `t = 2`.
+
+---
+
+### Types [toc] [#ShieldedMultiSigV2-Types]
+### Types [!toc] [#ShieldedMultiSigV2-Types]
+
+#### Proposal_Recipient [toc] [#ShieldedMultiSigV2-Proposal_Recipient]
+#### [!toc] [#ShieldedMultiSigV2-Proposal_Recipient]
+
+ Re-export of the ProposalManager [Recipient](#ProposalManager-Recipient), needed for the [execute](#ShieldedMultiSigV2-execute) signature.
+ Importing `ProposalManager` again instead would give the implementing contract a second, unwritten copy of its state.
+
+
+### Ledger [toc] [#ShieldedMultiSigV2-Ledger]
+### Ledger [!toc] [#ShieldedMultiSigV2-Ledger]
+
+#### _domainSeparator [toc] [#ShieldedMultiSigV2-_domainSeparator]
+#### [!toc] [#ShieldedMultiSigV2-_domainSeparator]
+
+ The EIP-712 domain separator every [execute](#ShieldedMultiSigV2-execute) digest is built
+ under, derived from `(name, version, instanceSalt)`. Set once at construction.
+
+
+### Witnesses [toc] [#ShieldedMultiSigV2-Witnesses]
+### Witnesses [!toc] [#ShieldedMultiSigV2-Witnesses]
+
+None.
+
+### Circuits [toc] [#ShieldedMultiSigV2-Circuits]
+### Circuits [!toc] [#ShieldedMultiSigV2-Circuits]
+
+#### initialize [toc] [#ShieldedMultiSigV2-initialize]
+#### [!toc] [#ShieldedMultiSigV2-initialize]
+
+ Registers the signer set at threshold `t` and fixes the EIP-712 domain separator.
+ It writes the `sealed` `_domainSeparator`, so it MUST only be called from the implementing contract's constructor
+ and MUST NOT be exposed as a post-deploy circuit.
+
+ The EIP-712 domain fixed here is `("ShieldedMultiSigV2", "1", instanceSalt)`. `chainId` and
+ `verifyingContract` are absent: no network identifier is available
+ in-circuit, and a 32-byte Midnight address does not fit
+ `verifyingContract`'s `address` type.
+ Replay protection comes from [execute](#ShieldedMultiSigV2-execute) binding `kernel.self()` in its struct instead.
+
+ `instanceSalt` also keys the signer commitments, so a fresh value per
+ deployment keeps one public key from producing the same commitment in two
+ deployments, which would link the signer set across them.
+
+ Requirements:
+
+ * Can only be called once.
+ * `instanceSalt` should be cryptographically random.
+ * `signerCommitments` must not contain duplicates.
+ * `n` must be at least `t`.
+
+
+#### deposit [toc] [#ShieldedMultiSigV2-deposit]
+#### [!toc] [#ShieldedMultiSigV2-deposit]
+
+ Receives a shielded coin into the multisig treasury. Anyone may deposit.
+ The coin is claimed through `receiveShielded`. No coin data is stored on the
+ public ledger, so no balance is readable from it; a depositor can still
+ follow the coin they deposited (see [ShieldedTreasuryStateless](#ShieldedTreasuryStateless)).
+
+ The operator discovers the coin's Merkle tree index by subscribing
+ to `ZswapOutput` events via the indexer, filtering by contract address,
+ and extracting `mt_index`. Combined with the known `ShieldedCoinInfo`,
+ this produces the `QualifiedShieldedCoinInfo` needed for spending.
+
+
+#### execute [toc] [#ShieldedMultiSigV2-execute]
+#### [!toc] [#ShieldedMultiSigV2-execute]
+
+ Executes a shielded send authorized by threshold signatures.
+
+ The circuit reads the current nonce, increments it, then rebuilds the EIP-712 digest that signers must have signed
+ off-chain: the `Execute` struct hashed under [_domainSeparator](#ShieldedMultiSigV2-_domainSeparator), with type string
+ `Execute(bytes32 contractAddress,uint256 nonce,uint8 recipientKind,bytes32 recipient,bytes32 coinColor,uint256 amount)`.
+ `contractAddress` is `kernel.self()`, so a signature authorizes exactly one operation on one instance.
+ Approvals are checked by [assertApprovals](#Secp256k1EcdsaMultisig-assertApprovals).
+ The spend goes through [ShieldedTreasuryStateless _send](#ShieldedTreasuryStateless-_send);
+ read its warning on the returned `ShieldedSendResult` before returning it from an entry point.
+
+
+ `to` must be a `ShieldedUser`. A contract-addressed coin would be
+ unclaimable, making the transaction malformed.
+
+
+ Requirements:
+
+ * No two entries may be the same signer.
+ * Every public key must hash to a registered signer commitment.
+ * Every signature must be valid over the message hash and use the low-s encoding.
+ * `to.kind` must be `ShieldedUser`.
+ * `coin.value` must be >= `amount`.
+
+
+#### _calculateSignerId [toc] [#ShieldedMultiSigV2-_calculateSignerId]
+#### [!toc] [#ShieldedMultiSigV2-_calculateSignerId]
+
+ Computes a signer commitment from a secp256k1 public key with
+ [calculateSignerId](#Secp256k1EcdsaMultisig-calculateSignerId).
+ Pure, so the deployer can derive constructor commitments off-chain.
+
+ Requirements:
+
+ * `pk` must not be the secp256k1 identity point, which has no coordinates.
+
+
+#### getNonce [toc] [#ShieldedMultiSigV2-getNonce]
+#### [!toc] [#ShieldedMultiSigV2-getNonce]
+
+ Returns the nonce the next [execute](#ShieldedMultiSigV2-execute) digest will bind to.
+
+
+#### getSignerCount [toc] [#ShieldedMultiSigV2-getSignerCount]
+#### [!toc] [#ShieldedMultiSigV2-getSignerCount]
+
+ Returns the number of registered signers.
+
+
+#### getThreshold [toc] [#ShieldedMultiSigV2-getThreshold]
+#### [!toc] [#ShieldedMultiSigV2-getThreshold]
+
+ Returns the approval threshold.
+
+
+#### isSigner [toc] [#ShieldedMultiSigV2-isSigner]
+#### [!toc] [#ShieldedMultiSigV2-isSigner]
+
+ Returns whether the given commitment is a registered signer.
+
+
+---
+
+## NativeShieldedTokenIssuer [toc] [#NativeShieldedTokenIssuer]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/multisig/presets/NativeShieldedTokenIssuer"<2>;
+```
+
+`NativeShieldedTokenIssuer<#t>` composes [Secp256k1EcdsaMultisig](#Secp256k1EcdsaMultisig), [NativeShieldedToken](./native-shielded-token#NativeShieldedToken), and [NativeShieldedTokenDerivedNonce](./native-shielded-token#NativeShieldedTokenDerivedNonce); every mint and burn carries `t` approvals.
+It requires ZKIR v3, and implementing contracts MUST NOT import `Signer` or `Secp256k1EcdsaMultisig` directly. The gated circuits' complexity is measured at `t = 2`.
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenIssuer-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenIssuer-Ledger]
+
+#### _counter [toc] [#NativeShieldedTokenIssuer-_counter]
+#### [!toc] [#NativeShieldedTokenIssuer-_counter]
+
+ Signature replay counter. Every operation ([mint](#NativeShieldedTokenIssuer-mint),
+ [mintToSelf](#NativeShieldedTokenIssuer-mintToSelf), [burn](#NativeShieldedTokenIssuer-burn), [burnFromSelf](#NativeShieldedTokenIssuer-burnFromSelf))
+ reads it into its digest and increments it. The read is pinned, so at most one operation lands per block;
+ the losing signers re-sign at the next nonce.
+
+
+#### _domainSeparator [toc] [#NativeShieldedTokenIssuer-_domainSeparator]
+#### [!toc] [#NativeShieldedTokenIssuer-_domainSeparator]
+
+ The EIP-712 domain separator every operation digest is
+ built under, derived from `(name, version, instanceSalt)`. Set once at construction.
+
+
+#### _derivedNonceCounter [toc] [#NativeShieldedTokenIssuer-_derivedNonceCounter]
+#### [!toc] [#NativeShieldedTokenIssuer-_derivedNonceCounter]
+
+ The counter that feeds coin nonces, separate from `_counter`.
+ Re-exported from `NativeShieldedTokenDerivedNonce` as `_counter`, renamed because this module's replay counter owns that name.
+
+
+#### _instanceSalt [toc] [#NativeShieldedTokenIssuer-_instanceSalt]
+#### [!toc] [#NativeShieldedTokenIssuer-_instanceSalt]
+
+ Salt for the signer commitments and the EIP-712 domain. Re-exported from `Secp256k1EcdsaMultisig`.
+
+
+#### _signers [toc] [#NativeShieldedTokenIssuer-_signers]
+#### [!toc] [#NativeShieldedTokenIssuer-_signers]
+
+ The registered signer commitments. Re-exported from `Secp256k1EcdsaMultisig`.
+
+
+#### _signerCount [toc] [#NativeShieldedTokenIssuer-_signerCount]
+#### [!toc] [#NativeShieldedTokenIssuer-_signerCount]
+
+ The number of registered signers. Re-exported from `Secp256k1EcdsaMultisig`.
+
+
+#### _threshold [toc] [#NativeShieldedTokenIssuer-_threshold]
+#### [!toc] [#NativeShieldedTokenIssuer-_threshold]
+
+ The approval threshold. Re-exported from `Secp256k1EcdsaMultisig`.
+
+
+#### _domain [toc] [#NativeShieldedTokenIssuer-_domain]
+#### [!toc] [#NativeShieldedTokenIssuer-_domain]
+
+ Domain separator fixed at construction; with the contract
+ address it determines this token's color. Re-exported from `NativeShieldedToken`.
+
+
+#### _name [toc] [#NativeShieldedTokenIssuer-_name]
+#### [!toc] [#NativeShieldedTokenIssuer-_name]
+
+ The immutable token name. Re-exported from `NativeShieldedToken`.
+
+
+#### _symbol [toc] [#NativeShieldedTokenIssuer-_symbol]
+#### [!toc] [#NativeShieldedTokenIssuer-_symbol]
+
+ The immutable token symbol. Re-exported from `NativeShieldedToken`.
+
+
+#### _decimals [toc] [#NativeShieldedTokenIssuer-_decimals]
+#### [!toc] [#NativeShieldedTokenIssuer-_decimals]
+
+ The immutable token decimals. Re-exported from `NativeShieldedToken`.
+
+
+#### _isInitialized [toc] [#NativeShieldedTokenIssuer-_isInitialized]
+#### [!toc] [#NativeShieldedTokenIssuer-_isInitialized]
+
+ The token module's initialization flag. Re-exported from `NativeShieldedToken`.
+
+
+### Witnesses [toc] [#NativeShieldedTokenIssuer-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenIssuer-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenIssuer-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenIssuer-Circuits]
+
+#### initialize [toc] [#NativeShieldedTokenIssuer-initialize]
+#### [!toc] [#NativeShieldedTokenIssuer-initialize]
+
+ Registers the signer set at threshold `t`, initializes the
+ token metadata and domain, and fixes the EIP-712 domain separator
+ `("NativeShieldedTokenIssuer", "1", instanceSalt)`.
+ It writes the token module's `sealed` domain and `_domainSeparator`, so it MUST only be called from the
+ implementing contract's constructor and MUST NOT be exposed as a post-deploy circuit.
+
+ `instanceSalt` is both the EIP-712 domain salt and the signer-commitment
+ salt, so a fresh value per deployment keeps one public key from
+ producing the same commitment in two deployments.
+ The token color is `tokenType(tokenDomain, kernel.self())`. Only coins
+ of this color can be burned through this contract.
+
+ Requirements:
+
+ * Can only be called once.
+ * `instanceSalt` should be cryptographically random.
+ * `signerCommitments` must not contain duplicates.
+ * `n` must be at least `t`.
+
+
+#### _calculateSignerId [toc] [#NativeShieldedTokenIssuer-_calculateSignerId]
+#### [!toc] [#NativeShieldedTokenIssuer-_calculateSignerId]
+
+ Computes a signer commitment from a secp256k1 public key with
+ [calculateSignerId](#Secp256k1EcdsaMultisig-calculateSignerId).
+ Pure, so the deployer can derive constructor commitments off-chain.
+
+ Requirements:
+
+ * `pk` must not be the secp256k1 identity point, which has no coordinates.
+
+
+#### mint [toc] [#NativeShieldedTokenIssuer-mint]
+#### [!toc] [#NativeShieldedTokenIssuer-mint]
+
+ Mints a new shielded coin of this contract's token to
+ `recipient`, authorized by threshold signatures over
+ `Mint(bytes32 contractAddress,bytes32 recipient,uint256 nonce,uint256 amount)`.
+
+ The amount is public. The coin nonce comes from `NativeShieldedTokenDerivedNonce`, so anyone
+ can test a candidate recipient against the coin commitment.
+
+
+ The returned coin info is the only copy available to the
+ recipient; deliver it out of band.
+
+
+ Requirements:
+
+ * No two entries may be the same signer.
+ * Every public key must hash to a registered signer commitment.
+ * Every signature must be valid over the mint message hash and use the low-s encoding.
+ * `recipient` is not zero.
+
+
+#### mintToSelf [toc] [#NativeShieldedTokenIssuer-mintToSelf]
+#### [!toc] [#NativeShieldedTokenIssuer-mintToSelf]
+
+ Mints a new shielded coin of this contract's token to the
+ contract itself, claimed in the same call, authorized by threshold
+ signatures over `MintToSelf(bytes32 contractAddress,uint256 nonce,uint256 amount)`.
+
+
+ The returned coin info is the contract's only record of the
+ holding; persist it for [burnFromSelf](#NativeShieldedTokenIssuer-burnFromSelf).
+
+
+ Requirements:
+
+ * No two entries may be the same signer.
+ * Every public key must hash to a registered signer commitment.
+ * Every signature must be valid over the mint-to-self message hash and use the low-s encoding.
+
+
+#### burn [toc] [#NativeShieldedTokenIssuer-burn]
+#### [!toc] [#NativeShieldedTokenIssuer-burn]
+
+ Burns `amount` from a holder's coin, authorized by threshold signatures over
+ `Burn(bytes32 contractAddress,bytes32 refundTo,uint256 nonce,uint256 amount)`.
+ The holder's transaction pays `coin` in; the contract
+ receives it and burns it in the same call. Any change goes to `refundTo`.
+ Returns the refund coin, or `none` if the coin was burned in full.
+
+
+ The returned refund coin info is the only copy available to
+ `refundTo`; deliver it out of band.
+
+
+
+ `amount`, `coin.value`, and `refundTo` stay hidden only while `coin` carries a nonce the observer does not know.
+ A coin passed straight from [mint](#NativeShieldedTokenIssuer-mint) has a derived nonce,
+ so the burn links to its mint and all three are recoverable by trial.
+ For an unlinkable burn the holder re-nonces the coin first with a wallet self-transfer; the contract cannot enforce this.
+
+
+ Requirements:
+
+ * No two entries may be the same signer.
+ * Every public key must hash to a registered signer commitment.
+ * Every signature must be valid over the burn message hash and use the low-s encoding.
+ * `coin.color` is this contract's token color.
+ * `amount` is less than or equal to `coin.value`.
+ * `refundTo` is not zero.
+
+
+#### burnFromSelf [toc] [#NativeShieldedTokenIssuer-burnFromSelf]
+#### [!toc] [#NativeShieldedTokenIssuer-burnFromSelf]
+
+ Burns `amount` from a coin this contract holds, authorized
+ by threshold signatures over
+ `BurnFromSelf(bytes32 contractAddress,uint256 nonce,uint256 amount,bytes32 coinNonce,uint256 coinValue)`.
+
+ The operator supplies the `QualifiedShieldedCoinInfo` from the off-chain
+ UTXO pool. On a partial burn the change is auto-received by the contract
+ and returned; it replaces `coin` as the contract's holding, so the
+ operator must persist it.
+ The digest binds `coin.nonce` and `coin.value`, so approvals authorize one
+ specific holding rather than an amount. Pick the coin before collecting signatures.
+
+ Requirements:
+
+ * No two entries may be the same signer.
+ * Every public key must hash to a registered signer commitment.
+ * Every signature must be valid over the burn-from-self message hash and use the low-s encoding.
+ * `coin.color` is this contract's token color.
+ * `amount` is less than or equal to `coin.value`.
+ * `coin.nonce` and `coin.value` match the approved coin.
+
+
+#### getNonce [toc] [#NativeShieldedTokenIssuer-getNonce]
+#### [!toc] [#NativeShieldedTokenIssuer-getNonce]
+
+ Returns the counter the next operation's digest will bind to.
+ Submitted as a transaction, it pins `_counter` and conflicts with any same-block operation.
+
+
+#### name [toc] [#NativeShieldedTokenIssuer-name]
+#### [!toc] [#NativeShieldedTokenIssuer-name]
+
+ Returns the token name.
+
+
+#### symbol [toc] [#NativeShieldedTokenIssuer-symbol]
+#### [!toc] [#NativeShieldedTokenIssuer-symbol]
+
+ Returns the token symbol.
+
+
+#### decimals [toc] [#NativeShieldedTokenIssuer-decimals]
+#### [!toc] [#NativeShieldedTokenIssuer-decimals]
+
+ Returns the token decimals.
+
+
+#### tokenColor [toc] [#NativeShieldedTokenIssuer-tokenColor]
+#### [!toc] [#NativeShieldedTokenIssuer-tokenColor]
+
+ Returns this contract's token color, `tokenType(tokenDomain, kernel.self())`.
+
+
+#### getSignerCount [toc] [#NativeShieldedTokenIssuer-getSignerCount]
+#### [!toc] [#NativeShieldedTokenIssuer-getSignerCount]
+
+ Returns the number of registered signers.
+
+
+#### getThreshold [toc] [#NativeShieldedTokenIssuer-getThreshold]
+#### [!toc] [#NativeShieldedTokenIssuer-getThreshold]
+
+ Returns the approval threshold.
+
+
+#### isSigner [toc] [#NativeShieldedTokenIssuer-isSigner]
+#### [!toc] [#NativeShieldedTokenIssuer-isSigner]
+
+ Returns whether the given commitment is a registered signer.
+
diff --git a/content/contracts-compact/0.4.x/api/native-shielded-token.mdx b/content/contracts-compact/0.4.x/api/native-shielded-token.mdx
new file mode 100644
index 00000000..259a8725
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/native-shielded-token.mdx
@@ -0,0 +1,1703 @@
+---
+title: NativeShieldedToken API
+---
+
+This module provides the full NativeShieldedToken module API: the two token flavors, their shared core,
+the supply and derived-nonce extensions, and the owner-gated preset.
+
+
+ For an overview of the modules, read the [NativeShieldedToken guide](../native-shielded-token).
+
+
+The page covers the following modules:
+
+- **Token modules**
+ - [NativeShieldedToken](#NativeShieldedToken): a single token type issued and burned by the contract.
+ - [NativeShieldedTokenFamily](#NativeShieldedTokenFamily): many token types, keyed by domain, under one contract.
+ - [NativeShieldedTokenCore](#NativeShieldedTokenCore): the shared core behind the two flavors; not composed directly.
+- **Extensions**
+ - [NativeShieldedTokenPublicSupply](#NativeShieldedTokenPublicSupply): on-chain minted and burned totals for a `NativeShieldedToken`.
+ - [NativeShieldedTokenFamilyPublicSupply](#NativeShieldedTokenFamilyPublicSupply): per-domain on-chain totals for a `NativeShieldedTokenFamily`.
+ - [NativeShieldedTokenPublicSupplyCore](#NativeShieldedTokenPublicSupplyCore): the shared core behind the two supply extensions; not composed directly.
+ - [NativeShieldedTokenDerivedNonce](#NativeShieldedTokenDerivedNonce): mint coin nonces derived from a counter.
+- **Preset**
+ - [ZOwnableNativeShieldedToken](#ZOwnableNativeShieldedToken): a single token whose `ZOwnablePK` owner mints and burns, with a public minted total.
+
+These modules export no structs or enums of their own. Coins are the standard library's `ShieldedCoinInfo`
+(a `nonce`, `color`, and `value`) and `QualifiedShieldedCoinInfo` (the same plus the Merkle-tree index `mt_index`
+of a coin the contract holds).
+
+## NativeShieldedToken [toc] [#NativeShieldedToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedToken";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedToken-Ledger]
+### Ledger [!toc] [#NativeShieldedToken-Ledger]
+
+#### _domain [toc] [#NativeShieldedToken-_domain]
+#### [!toc] [#NativeShieldedToken-_domain]
+
+ Domain separator fixed at construction; with the contract
+ address it determines this token's color.
+
+
+#### _name [toc] [#NativeShieldedToken-_name]
+#### [!toc] [#NativeShieldedToken-_name]
+
+ The immutable token name. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _symbol [toc] [#NativeShieldedToken-_symbol]
+#### [!toc] [#NativeShieldedToken-_symbol]
+
+ The immutable token symbol. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _decimals [toc] [#NativeShieldedToken-_decimals]
+#### [!toc] [#NativeShieldedToken-_decimals]
+
+ The immutable token decimals. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _isInitialized [toc] [#NativeShieldedToken-_isInitialized]
+#### [!toc] [#NativeShieldedToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#NativeShieldedToken-Witnesses]
+### Witnesses [!toc] [#NativeShieldedToken-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedToken-Circuits]
+### Circuits [!toc] [#NativeShieldedToken-Circuits]
+
+#### initialize [toc] [#NativeShieldedToken-initialize]
+#### [!toc] [#NativeShieldedToken-initialize]
+
+ Initializes the module's domain and metadata.
+ This MUST be called in the implementing contract's constructor, and it
+ MUST be the only token operation performed there. Failure to call it can
+ lead to an irreparable contract; perform mint, burn, and [tokenColor](#NativeShieldedToken-tokenColor) only
+ in post-deployment circuits, never in the constructor.
+
+
+#### isInitialized [toc] [#NativeShieldedToken-isInitialized]
+#### [!toc] [#NativeShieldedToken-isInitialized]
+
+ Returns whether the contract has been initialized.
+
+
+#### name [toc] [#NativeShieldedToken-name]
+#### [!toc] [#NativeShieldedToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#NativeShieldedToken-symbol]
+#### [!toc] [#NativeShieldedToken-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#NativeShieldedToken-decimals]
+#### [!toc] [#NativeShieldedToken-decimals]
+
+ Returns the number of decimals used to get its user representation.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#NativeShieldedToken-tokenColor]
+#### [!toc] [#NativeShieldedToken-tokenColor]
+
+ Returns this token's coin color:
+ `tokenType(_domain, kernel.self())`, computed at call time.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#NativeShieldedToken-_mint]
+#### [!toc] [#NativeShieldedToken-_mint]
+
+ Mints `amount` of the token to `recipient`, using a
+ caller-supplied nonce.
+
+ The caller is fully responsible for nonce uniqueness. Reusing a
+ nonce for the same (value, recipient) produces a duplicate commitment,
+ which the protocol rejects. With a secret random nonce this is the
+ recipient-private mint. For mints that require no caller-managed nonce,
+ compose the `NativeShieldedTokenDerivedNonce` extension and pass its
+ `_deriveNonce()` output.
+
+
+ The returned coin info is the only copy available to the
+ recipient; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-minted coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `recipient` is not zero.
+
+
+#### _mintToSelf [toc] [#NativeShieldedToken-_mintToSelf]
+#### [!toc] [#NativeShieldedToken-_mintToSelf]
+
+ Mints `amount` to this contract itself, claiming the coin in
+ the same call. Forwards to `NativeShieldedTokenCore._mint` with
+ `right(kernel.self())`.
+
+ Nonce uniqueness is the caller's responsibility, as in [_mint](#NativeShieldedToken-_mint). Spend
+ the coin later with [_burnFromSelf](#NativeShieldedToken-_burnFromSelf), once it carries an `mt_index`.
+
+
+ The consumer SHOULD persist the returned coin info in its own
+ ledger state: it is the contract's holding and is not otherwise
+ recoverable. The coin is already claimed as a receive by the mint, so a
+ custody module must record it without calling `receiveShielded` on it
+ again. [ShieldedTreasury._deposit](./multisig#ShieldedTreasury-_deposit) claims on the way in and is not that path;
+ a record-only circuit is tracked in compact-contracts#1112. Until a
+ custody module is composed, the only spend of a contract-held coin is
+ [_burnFromSelf](#NativeShieldedToken-_burnFromSelf).
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _burn [toc] [#NativeShieldedToken-_burn]
+#### [!toc] [#NativeShieldedToken-_burn]
+
+ Burns `amount` from `coin`, a coin provided within the
+ current transaction (e.g. paid in by the caller's wallet), and routes the
+ remaining change to `refundTo`.
+
+ To destroy `coin` in full, pass `coin.value` as `amount`; the change
+ branch will not fire and `none` is returned. The `refundTo` value is
+ inert in that case but must still be supplied and non-zero.
+
+ The coin is received by the contract and spent in the same
+ transaction, so the spend goes through `sendImmediateShielded` (the
+ transient path). For coins the contract already holds, use
+ [_burnFromSelf](#NativeShieldedToken-_burnFromSelf) instead.
+
+
+ The returned refund coin info is the only copy available to
+ `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-sent coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token color.
+ * `amount` is less than or equal to `coin.value`.
+ * `refundTo` is not zero (the zero key is the burn address; a zero
+ `refundTo` would silently burn the change as well).
+
+
+#### _burnFromSelf [toc] [#NativeShieldedToken-_burnFromSelf]
+#### [!toc] [#NativeShieldedToken-_burnFromSelf]
+
+ Burns `amount` from `coin`, a coin this contract already
+ holds (a Merkle-tree entry with a valid `mt_index`). Any change is
+ auto-received by the contract and returned.
+
+ The consumer SHOULD persist the returned change coin info in its own
+ ledger state: the change replaces `coin` as the contract's holding, and
+ its info is not otherwise recoverable. Like a [_mintToSelf](#NativeShieldedToken-_mintToSelf) coin, the
+ change is already claimed as a receive, so a custody module must record
+ it without a second `receiveShielded`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token color.
+ * `amount` is less than or equal to `coin.value`.
+
+
+---
+
+## NativeShieldedTokenFamily [toc] [#NativeShieldedTokenFamily]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedTokenFamily";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenFamily-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenFamily-Ledger]
+
+#### _name [toc] [#NativeShieldedTokenFamily-_name]
+#### [!toc] [#NativeShieldedTokenFamily-_name]
+
+ The immutable family name shared by all token types. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _symbol [toc] [#NativeShieldedTokenFamily-_symbol]
+#### [!toc] [#NativeShieldedTokenFamily-_symbol]
+
+ The immutable family symbol shared by all token types. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _decimals [toc] [#NativeShieldedTokenFamily-_decimals]
+#### [!toc] [#NativeShieldedTokenFamily-_decimals]
+
+ The immutable family-wide decimals. Re-exported from `NativeShieldedTokenCore`.
+
+
+#### _isInitialized [toc] [#NativeShieldedTokenFamily-_isInitialized]
+#### [!toc] [#NativeShieldedTokenFamily-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+### Witnesses [toc] [#NativeShieldedTokenFamily-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenFamily-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenFamily-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenFamily-Circuits]
+
+#### initialize [toc] [#NativeShieldedTokenFamily-initialize]
+#### [!toc] [#NativeShieldedTokenFamily-initialize]
+
+ Initializes the module's family metadata.
+ This MUST be called in the implementing contract's constructor, and it
+ MUST be the only token operation performed there. Failure to call it can
+ lead to an irreparable contract; perform mint, burn, and [tokenColor](#NativeShieldedTokenFamily-tokenColor) only
+ in post-deployment circuits, never in the constructor.
+
+
+#### isInitialized [toc] [#NativeShieldedTokenFamily-isInitialized]
+#### [!toc] [#NativeShieldedTokenFamily-isInitialized]
+
+ Returns whether the contract has been initialized.
+
+
+#### name [toc] [#NativeShieldedTokenFamily-name]
+#### [!toc] [#NativeShieldedTokenFamily-name]
+
+ Returns the family name shared by all token types of this
+ contract. Per-type identity belongs in the consumer's own state; see the
+ module notes.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#NativeShieldedTokenFamily-symbol]
+#### [!toc] [#NativeShieldedTokenFamily-symbol]
+
+ Returns the family symbol shared by all token types of this
+ contract.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#NativeShieldedTokenFamily-decimals]
+#### [!toc] [#NativeShieldedTokenFamily-decimals]
+
+ Returns the family-wide number of decimals used to get the
+ user representation.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#NativeShieldedTokenFamily-tokenColor]
+#### [!toc] [#NativeShieldedTokenFamily-tokenColor]
+
+ Returns the coin color (token type) for `domain` under this
+ contract's address, so integrators do not have to re-derive it by hand.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#NativeShieldedTokenFamily-_mint]
+#### [!toc] [#NativeShieldedTokenFamily-_mint]
+
+ Mints `amount` of the token identified by `domain` to
+ `recipient`, using a caller-supplied nonce.
+
+ The caller is fully responsible for nonce uniqueness. Reusing a
+ nonce for the same (domain, value, recipient) produces a duplicate
+ commitment, which the protocol rejects. With a secret random nonce this
+ is the recipient-private mint. For mints that require no caller-managed
+ nonce, compose the `NativeShieldedTokenDerivedNonce` extension and pass
+ its `_deriveNonce()` output.
+
+
+ The returned coin info is the only copy available to the
+ recipient; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-minted coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `recipient` is not zero.
+
+
+#### _mintToSelf [toc] [#NativeShieldedTokenFamily-_mintToSelf]
+#### [!toc] [#NativeShieldedTokenFamily-_mintToSelf]
+
+ Mints `amount` of the token identified by `domain` to this
+ contract itself, claiming the coin in the same call. Forwards to
+ `NativeShieldedTokenCore._mint` with `right(kernel.self())`.
+
+ Nonce uniqueness is the caller's responsibility, as in [_mint](#NativeShieldedTokenFamily-_mint). Spend
+ the coin later with [_burnFromSelf](#NativeShieldedTokenFamily-_burnFromSelf), once it carries an `mt_index`.
+
+
+ The consumer SHOULD persist the returned coin info in its own
+ ledger state: it is the contract's holding and is not otherwise
+ recoverable.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _burn [toc] [#NativeShieldedTokenFamily-_burn]
+#### [!toc] [#NativeShieldedTokenFamily-_burn]
+
+ Burns `amount` from `coin`, a coin provided within the
+ current transaction (e.g. paid in by the caller's wallet), and routes the
+ remaining change to `refundTo`.
+
+ To destroy `coin` in full, pass `coin.value` as `amount`; the change
+ branch will not fire and `none` is returned. The `refundTo` value is
+ inert in that case but must still be supplied and non-zero.
+
+ The coin is received by the contract and spent in the same
+ transaction, so the spend goes through `sendImmediateShielded` (the
+ transient path). For coins the contract already holds, use
+ [_burnFromSelf](#NativeShieldedTokenFamily-_burnFromSelf) instead.
+
+
+ The returned refund coin info is the only copy available to
+ `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-sent coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+ * `refundTo` is not zero (the zero key is the burn address; a zero
+ `refundTo` would silently burn the change as well).
+
+
+#### _burnFromSelf [toc] [#NativeShieldedTokenFamily-_burnFromSelf]
+#### [!toc] [#NativeShieldedTokenFamily-_burnFromSelf]
+
+ Burns `amount` from `coin`, a coin this contract already
+ holds (a Merkle-tree entry with a valid `mt_index`). Any change is
+ auto-received by the contract and returned.
+
+ The consumer SHOULD persist the returned change coin info in its own
+ ledger state: the change replaces `coin` as the contract's holding, and
+ its info is not otherwise recoverable.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+
+
+---
+
+## NativeShieldedTokenCore [toc] [#NativeShieldedTokenCore]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedTokenCore";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenCore-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenCore-Ledger]
+
+#### _isInitialized [toc] [#NativeShieldedTokenCore-_isInitialized]
+#### [!toc] [#NativeShieldedTokenCore-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _name [toc] [#NativeShieldedTokenCore-_name]
+#### [!toc] [#NativeShieldedTokenCore-_name]
+
+ The token's human-readable name, set once at construction.
+
+
+#### _symbol [toc] [#NativeShieldedTokenCore-_symbol]
+#### [!toc] [#NativeShieldedTokenCore-_symbol]
+
+ The token's ticker symbol, set once at construction.
+
+
+#### _decimals [toc] [#NativeShieldedTokenCore-_decimals]
+#### [!toc] [#NativeShieldedTokenCore-_decimals]
+
+ The number of decimals used to display balances, set once at
+ construction. Purely presentational; no circuit scales by it.
+
+
+### Witnesses [toc] [#NativeShieldedTokenCore-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenCore-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenCore-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenCore-Circuits]
+
+#### initialize [toc] [#NativeShieldedTokenCore-initialize]
+#### [!toc] [#NativeShieldedTokenCore-initialize]
+
+ Initializes the contract-wide metadata.
+ This MUST be called in the implementing contract's constructor (via
+ the wrapping flavor's [initialize](#NativeShieldedTokenCore-initialize)). Failure to do so can lead to an
+ irreparable contract.
+
+
+#### assertInitialized [toc] [#NativeShieldedTokenCore-assertInitialized]
+#### [!toc] [#NativeShieldedTokenCore-assertInitialized]
+
+ Asserts that the contract has been initialized, throwing an
+ error if not.
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+#### assertNotInitialized [toc] [#NativeShieldedTokenCore-assertNotInitialized]
+#### [!toc] [#NativeShieldedTokenCore-assertNotInitialized]
+
+ Asserts that the contract has not been initialized, throwing
+ an error if it has.
+
+ Requirements:
+
+ * Contract must not be initialized.
+
+
+#### isInitialized [toc] [#NativeShieldedTokenCore-isInitialized]
+#### [!toc] [#NativeShieldedTokenCore-isInitialized]
+
+ Returns whether the contract has been initialized.
+ Does not gate on init (it reports it), so it is safe to call before
+ [initialize](#NativeShieldedTokenCore-initialize).
+
+
+#### name [toc] [#NativeShieldedTokenCore-name]
+#### [!toc] [#NativeShieldedTokenCore-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#NativeShieldedTokenCore-symbol]
+#### [!toc] [#NativeShieldedTokenCore-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#NativeShieldedTokenCore-decimals]
+#### [!toc] [#NativeShieldedTokenCore-decimals]
+
+ Returns the number of decimals used to get its user representation.
+ This is a display convention only. The protocol stores and operates
+ on integer values without reference to decimals.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#NativeShieldedTokenCore-tokenColor]
+#### [!toc] [#NativeShieldedTokenCore-tokenColor]
+
+ Returns the coin color for `domain` under this contract's
+ address: `tokenType(domain, kernel.self())`, computed at call time.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _mint [toc] [#NativeShieldedTokenCore-_mint]
+#### [!toc] [#NativeShieldedTokenCore-_mint]
+
+ Mints `amount` of the token identified by `domain` to
+ `recipient`, using a caller-supplied nonce.
+
+ The caller is fully responsible for nonce uniqueness. Reusing a
+ nonce for the same (domain, value, recipient) produces a duplicate
+ commitment, which the protocol rejects. A `right(kernel.self())` recipient
+ is claimed by `mintShieldedToken` in this same call, so the consumer SHOULD
+ persist the returned coin info: it is the contract's holding and is not
+ otherwise recoverable.
+
+
+ The returned coin info is the only copy available to the
+ recipient; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-minted coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `recipient` is not zero.
+ * `recipient`, if a contract address, is this contract's own address.
+
+
+#### _burn [toc] [#NativeShieldedTokenCore-_burn]
+#### [!toc] [#NativeShieldedTokenCore-_burn]
+
+ Burns `amount` from `coin`, a coin provided within the
+ current transaction (e.g. paid in by the caller's wallet), and routes the
+ remaining change to `refundTo`.
+
+ To destroy `coin` in full, pass `coin.value` as `amount`; the change
+ branch will not fire and `none` is returned. The `refundTo` value is
+ inert in that case but must still be supplied and non-zero.
+
+ The coin is received by the contract and spent in the same
+ transaction, so the spend goes through `sendImmediateShielded` (the
+ transient path). For coins the contract already holds, use
+ [_burnFromSelf](#NativeShieldedTokenCore-_burnFromSelf) instead. A `right(kernel.self())` refund is claimed by
+ `sendImmediateShielded` in this same call.
+
+
+ The returned refund coin info is the only copy available to
+ `refundTo`; callers SHOULD deliver it out of band. Wallets cannot detect
+ contract-sent coins by scanning the chain.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+ * `refundTo` is not zero (the zero key is the burn address; a zero
+ `refundTo` would silently burn the change as well).
+ * `refundTo`, if a contract address, is this contract's own address.
+
+
+#### _burnFromSelf [toc] [#NativeShieldedTokenCore-_burnFromSelf]
+#### [!toc] [#NativeShieldedTokenCore-_burnFromSelf]
+
+ Burns `amount` from `coin`, a coin this contract already
+ holds (a Merkle-tree entry with a valid `mt_index`). Any change is
+ auto-received by the contract and returned.
+
+ The consumer SHOULD persist the returned change coin info in its own
+ ledger state: the change replaces `coin` as the contract's holding, and
+ its info is not otherwise recoverable.
+
+ Requirements:
+
+ * Contract is initialized.
+ * `coin.color` is this contract's token type for `domain`.
+ * `amount` is less than or equal to `coin.value`.
+
+
+---
+
+## NativeShieldedTokenPublicSupply [toc] [#NativeShieldedTokenPublicSupply]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenPublicSupply";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenPublicSupply-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenPublicSupply-Ledger]
+
+#### _totalMinted [toc] [#NativeShieldedTokenPublicSupply-_totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#NativeShieldedTokenPublicSupply-_totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#NativeShieldedTokenPublicSupply-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenPublicSupply-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenPublicSupply-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenPublicSupply-Circuits]
+
+#### _addMinted [toc] [#NativeShieldedTokenPublicSupply-_addMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_addMinted]
+
+ Adds `amount` to the exact minted total. Call once per
+ successful `NativeShieldedToken._mint`.
+
+
+#### _addBurned [toc] [#NativeShieldedTokenPublicSupply-_addBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupply-_addBurned]
+
+ Adds `amount` to the contract-mediated burned total. Call once
+ per successful `NativeShieldedToken._burn` / `_burnFromSelf`.
+ Enforces `burned <= minted`; an unpaired or duplicate burn reverts.
+
+
+#### totalMinted [toc] [#NativeShieldedTokenPublicSupply-totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupply-totalMinted]
+
+ Returns the exact amount ever minted.
+
+
+#### totalBurned [toc] [#NativeShieldedTokenPublicSupply-totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupply-totalBurned]
+
+ Returns the contract-mediated amount burned.
+
+ This is a lower bound: coins sent directly to the burn address
+ without going through the contract are not counted.
+
+
+
+#### totalSupply [toc] [#NativeShieldedTokenPublicSupply-totalSupply]
+#### [!toc] [#NativeShieldedTokenPublicSupply-totalSupply]
+
+ Returns `totalMinted() - totalBurned()`.
+
+ This is an UPPER BOUND on circulating supply, not an exact value:
+ burns that bypass the contract are invisible. See the module notes.
+
+
+
+---
+
+## NativeShieldedTokenFamilyPublicSupply [toc] [#NativeShieldedTokenFamilyPublicSupply]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenFamilyPublicSupply";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenFamilyPublicSupply-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenFamilyPublicSupply-Ledger]
+
+#### _totalMinted [toc] [#NativeShieldedTokenFamilyPublicSupply-_totalMinted]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#NativeShieldedTokenFamilyPublicSupply-_totalBurned]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#NativeShieldedTokenFamilyPublicSupply-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenFamilyPublicSupply-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenFamilyPublicSupply-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenFamilyPublicSupply-Circuits]
+
+#### _addMinted [toc] [#NativeShieldedTokenFamilyPublicSupply-_addMinted]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_addMinted]
+
+ Adds `amount` to the exact minted total for `domain`. Call
+ once per successful `NativeShieldedTokenFamily._mint`.
+
+
+#### _addBurned [toc] [#NativeShieldedTokenFamilyPublicSupply-_addBurned]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-_addBurned]
+
+ Adds `amount` to the contract-mediated burned total for
+ `domain`. Call once per successful `NativeShieldedTokenFamily._burn` /
+ `_burnFromSelf`.
+ Enforces per-domain `burned <= minted`; an unpaired, duplicate, or
+ wrong-domain burn reverts.
+
+
+#### totalMinted [toc] [#NativeShieldedTokenFamilyPublicSupply-totalMinted]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-totalMinted]
+
+ Returns the exact amount ever minted for `domain`.
+
+
+#### totalBurned [toc] [#NativeShieldedTokenFamilyPublicSupply-totalBurned]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-totalBurned]
+
+ Returns the contract-mediated amount burned for `domain`.
+
+ This is a lower bound: coins sent directly to the burn address
+ without going through the contract are not counted.
+
+
+
+#### totalSupply [toc] [#NativeShieldedTokenFamilyPublicSupply-totalSupply]
+#### [!toc] [#NativeShieldedTokenFamilyPublicSupply-totalSupply]
+
+ Returns `totalMinted(domain) - totalBurned(domain)`.
+
+ This is an UPPER BOUND on circulating supply, not an exact value:
+ burns that bypass the contract are invisible. See the module notes.
+
+
+
+---
+
+## NativeShieldedTokenPublicSupplyCore [toc] [#NativeShieldedTokenPublicSupplyCore]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenPublicSupplyCore";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenPublicSupplyCore-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenPublicSupplyCore-Ledger]
+
+#### _totalMinted [toc] [#NativeShieldedTokenPublicSupplyCore-_totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#NativeShieldedTokenPublicSupplyCore-_totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#NativeShieldedTokenPublicSupplyCore-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenPublicSupplyCore-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenPublicSupplyCore-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenPublicSupplyCore-Circuits]
+
+#### _addMinted [toc] [#NativeShieldedTokenPublicSupplyCore-_addMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_addMinted]
+
+ Adds `amount` to the exact minted total for `domain`. Call
+ once per successful token `_mint`.
+ Checks for overflow in order to output a readable error message.
+
+
+#### _addBurned [toc] [#NativeShieldedTokenPublicSupplyCore-_addBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-_addBurned]
+
+ Adds `amount` to the contract-mediated burned total for
+ `domain`. Call once per successful token `_burn` / `_burnFromSelf`.
+ Enforces the per-domain `burned <= minted` invariant: a wrong-domain,
+ duplicate, or unpaired burn reverts rather than corrupting [totalSupply](#NativeShieldedTokenPublicSupplyCore-totalSupply).
+ The guard also keeps the add and the [totalSupply](#NativeShieldedTokenPublicSupplyCore-totalSupply) subtraction within
+ bounds, so no separate overflow/underflow check is needed.
+
+
+#### totalMinted [toc] [#NativeShieldedTokenPublicSupplyCore-totalMinted]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-totalMinted]
+
+ Returns the exact amount ever minted for `domain`.
+
+
+#### totalBurned [toc] [#NativeShieldedTokenPublicSupplyCore-totalBurned]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-totalBurned]
+
+ Returns the contract-mediated amount burned for `domain`.
+
+ This is a lower bound: coins sent directly to the burn address
+ without going through the contract are not counted.
+
+
+
+#### totalSupply [toc] [#NativeShieldedTokenPublicSupplyCore-totalSupply]
+#### [!toc] [#NativeShieldedTokenPublicSupplyCore-totalSupply]
+
+ Returns `totalMinted(domain) - totalBurned(domain)`, or 0 if
+ [totalBurned](#NativeShieldedTokenPublicSupplyCore-totalBurned) somehow exceeds [totalMinted](#NativeShieldedTokenPublicSupplyCore-totalMinted).
+
+ This is an UPPER BOUND on circulating supply, not an exact value:
+ burns that bypass the contract are invisible. See the module notes.
+
+ The `burned <= minted` invariant enforced by [_addBurned](#NativeShieldedTokenPublicSupplyCore-_addBurned) already makes
+ `burned > minted` unreachable through the accounting API, so the clamp never
+ fires in normal operation. It is kept so the getter is a total function that
+ reads cleanly (returning 0) rather than reverting on an out-of-bound state.
+
+
+---
+
+## NativeShieldedTokenDerivedNonce [toc] [#NativeShieldedTokenDerivedNonce]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenDerivedNonce";
+```
+
+---
+
+### Ledger [toc] [#NativeShieldedTokenDerivedNonce-Ledger]
+### Ledger [!toc] [#NativeShieldedTokenDerivedNonce-Ledger]
+
+#### _counter [toc] [#NativeShieldedTokenDerivedNonce-_counter]
+#### [!toc] [#NativeShieldedTokenDerivedNonce-_counter]
+
+ Monotonic index feeding the derived-nonce derivation.
+
+
+### Witnesses [toc] [#NativeShieldedTokenDerivedNonce-Witnesses]
+### Witnesses [!toc] [#NativeShieldedTokenDerivedNonce-Witnesses]
+
+None.
+
+### Circuits [toc] [#NativeShieldedTokenDerivedNonce-Circuits]
+### Circuits [!toc] [#NativeShieldedTokenDerivedNonce-Circuits]
+
+#### _deriveNonce [toc] [#NativeShieldedTokenDerivedNonce-_deriveNonce]
+#### [!toc] [#NativeShieldedTokenDerivedNonce-_deriveNonce]
+
+ Advances the counter and returns the next derived coin nonce,
+ for use as the `nonce` argument of a native token mint.
+
+
+ The derivation input is public ledger state, so mints using this
+ nonce are recipient-public; see the module notes.
+
+
+
+---
+
+## ZOwnableNativeShieldedToken [toc] [#ZOwnableNativeShieldedToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/presets/ZOwnableNativeShieldedToken";
+```
+
+---
+
+### Ledger [toc] [#ZOwnableNativeShieldedToken-Ledger]
+### Ledger [!toc] [#ZOwnableNativeShieldedToken-Ledger]
+
+#### _ownerCommitment [toc] [#ZOwnableNativeShieldedToken-_ownerCommitment]
+#### [!toc] [#ZOwnableNativeShieldedToken-_ownerCommitment]
+
+ Stores the current hashed commitment representing the owner.
+ This commitment is derived from the public identifier (e.g., `SHA256(pk, nonce)`),
+ the `instanceSalt`, the transfer `counter`, and a domain separator.
+
+ A commitment of `default>` (i.e., zero) indicates the contract is unowned.
+
+
+#### _counter [toc] [#ZOwnableNativeShieldedToken-_counter]
+#### [!toc] [#ZOwnableNativeShieldedToken-_counter]
+
+ Internal transfer counter used to prevent commitment reuse.
+
+ Increments by 1 on every successful ownership transfer. Combined with `id` and
+ `instanceSalt` to compute unique owner commitments over time.
+
+
+#### _instanceSalt [toc] [#ZOwnableNativeShieldedToken-_instanceSalt]
+#### [!toc] [#ZOwnableNativeShieldedToken-_instanceSalt]
+
+ A per-instance value provided at initialization used to namespace
+ commitments for this contract instance.
+
+ This salt prevents commitment collisions across contracts that might otherwise use
+ the same owner identifiers or domain parameters. It is immutable after initialization.
+
+
+#### _ownableIsInitialized [toc] [#ZOwnableNativeShieldedToken-_ownableIsInitialized]
+#### [!toc] [#ZOwnableNativeShieldedToken-_ownableIsInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _domain [toc] [#ZOwnableNativeShieldedToken-_domain]
+#### [!toc] [#ZOwnableNativeShieldedToken-_domain]
+
+ Domain separator fixed at construction; with the contract
+ address it determines this token's color.
+
+
+#### _name [toc] [#ZOwnableNativeShieldedToken-_name]
+#### [!toc] [#ZOwnableNativeShieldedToken-_name]
+
+ The immutable token name. Re-exported from `NativeShieldedToken`.
+
+
+#### _symbol [toc] [#ZOwnableNativeShieldedToken-_symbol]
+#### [!toc] [#ZOwnableNativeShieldedToken-_symbol]
+
+ The immutable token symbol. Re-exported from `NativeShieldedToken`.
+
+
+#### _decimals [toc] [#ZOwnableNativeShieldedToken-_decimals]
+#### [!toc] [#ZOwnableNativeShieldedToken-_decimals]
+
+ The immutable token decimals. Re-exported from `NativeShieldedToken`.
+
+
+#### _isInitialized [toc] [#ZOwnableNativeShieldedToken-_isInitialized]
+#### [!toc] [#ZOwnableNativeShieldedToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _totalMinted [toc] [#ZOwnableNativeShieldedToken-_totalMinted]
+#### [!toc] [#ZOwnableNativeShieldedToken-_totalMinted]
+
+ Exact amount minted per domain. See "Accounting guarantees".
+
+
+#### _totalBurned [toc] [#ZOwnableNativeShieldedToken-_totalBurned]
+#### [!toc] [#ZOwnableNativeShieldedToken-_totalBurned]
+
+ Contract-mediated amount burned per domain (lower bound).
+
+
+### Witnesses [toc] [#ZOwnableNativeShieldedToken-Witnesses]
+### Witnesses [!toc] [#ZOwnableNativeShieldedToken-Witnesses]
+
+None declared by this module. The composed `ZOwnablePK` requires
+[wit_secretNonce](./ownable#ZOwnablePK-wit_secretNonce), which every owner-gated circuit calls through
+`assertOnlyOwner`. The implementing DApp supplies it; the TypeScript witnesses in the library repository are
+test-only and are not published.
+
+### Circuits [toc] [#ZOwnableNativeShieldedToken-Circuits]
+### Circuits [!toc] [#ZOwnableNativeShieldedToken-Circuits]
+
+#### initialize [toc] [#ZOwnableNativeShieldedToken-initialize]
+#### [!toc] [#ZOwnableNativeShieldedToken-initialize]
+
+ Sets the owner and the token's domain and metadata.
+
+
+ Call from the implementing contract's constructor only. It writes
+ sealed fields, so implementing contracts MUST NOT expose it as a
+ post-deploy circuit.
+
+
+
+ `ownerId` must be `_computeOwnerId(pk, secretNonce)` for the coin
+ public key the owner's wallet will supply as `ownPublicKey()`. The circuit
+ cannot check this at deploy; any other key leaves the contract with no
+ usable owner and no recovery.
+
+
+ Requirements:
+
+ * Contract is not initialized.
+ * `ownerId` is not zero.
+
+
+#### mint [toc] [#ZOwnableNativeShieldedToken-mint]
+#### [!toc] [#ZOwnableNativeShieldedToken-mint]
+
+ Mints `amount` to `recipient` and adds it to the minted total.
+ Owner only.
+
+ Requirements:
+
+ * Caller is the owner.
+ * `recipient` is not zero.
+
+
+#### burn [toc] [#ZOwnableNativeShieldedToken-burn]
+#### [!toc] [#ZOwnableNativeShieldedToken-burn]
+
+ Burns `amount` of `coin`, paid into the tx by the caller's
+ wallet, and refunds the rest to `refundTo`. Owner only.
+
+ Requirements:
+
+ * Caller is the owner.
+ * `coin.color` is this token's color.
+ * `amount` \<= `coin.value`.
+ * `refundTo` is not zero.
+
+
+#### transferOwnership [toc] [#ZOwnableNativeShieldedToken-transferOwnership]
+#### [!toc] [#ZOwnableNativeShieldedToken-transferOwnership]
+
+ Transfers ownership to `newOwnerId`. Owner only.
+
+
+ `newOwnerId` must be `_computeOwnerId(pk, secretNonce)` for the
+ coin public key the new owner's wallet will supply as `ownPublicKey()`.
+ The circuit cannot check this; any other key leaves the contract with no
+ usable owner and no recovery.
+
+
+ Requirements:
+
+ * Caller is the owner.
+ * `newOwnerId` is not zero.
+
+
+#### renounceOwnership [toc] [#ZOwnableNativeShieldedToken-renounceOwnership]
+#### [!toc] [#ZOwnableNativeShieldedToken-renounceOwnership]
+
+ Clears the owner. Owner only.
+
+
+ Irreversible: mint and burn stop forever.
+
+
+ Requirements:
+
+ * Caller is the owner.
+
+
+#### owner [toc] [#ZOwnableNativeShieldedToken-owner]
+#### [!toc] [#ZOwnableNativeShieldedToken-owner]
+
+ Returns the current owner commitment.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _computeOwnerId [toc] [#ZOwnableNativeShieldedToken-_computeOwnerId]
+#### [!toc] [#ZOwnableNativeShieldedToken-_computeOwnerId]
+
+ Returns the owner id `H(pk, nonce)`.
+
+ Requirements:
+
+ * `pk` is a coin public key.
+
+
+#### totalMinted [toc] [#ZOwnableNativeShieldedToken-totalMinted]
+#### [!toc] [#ZOwnableNativeShieldedToken-totalMinted]
+
+ Returns the total amount ever minted.
+
+
+#### name [toc] [#ZOwnableNativeShieldedToken-name]
+#### [!toc] [#ZOwnableNativeShieldedToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### symbol [toc] [#ZOwnableNativeShieldedToken-symbol]
+#### [!toc] [#ZOwnableNativeShieldedToken-symbol]
+
+ Returns the token symbol.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### decimals [toc] [#ZOwnableNativeShieldedToken-decimals]
+#### [!toc] [#ZOwnableNativeShieldedToken-decimals]
+
+ Returns the token decimals.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### tokenColor [toc] [#ZOwnableNativeShieldedToken-tokenColor]
+#### [!toc] [#ZOwnableNativeShieldedToken-tokenColor]
+
+ Returns this contract's token color,
+ `tokenType(tokenDomain, kernel.self())`.
+
+ Requirements:
+
+ * Contract is initialized.
+
diff --git a/content/contracts-compact/0.4.x/api/non-fungible-token.mdx b/content/contracts-compact/0.4.x/api/non-fungible-token.mdx
new file mode 100644
index 00000000..a04d52d9
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/non-fungible-token.mdx
@@ -0,0 +1,661 @@
+---
+title: NonFungibleToken API
+---
+
+This module provides the full NonFungibleToken module API.
+
+
+ For an overview of the module, read the [NonFungibleToken guide](../non-fungible-token).
+
+
+## NonFungibleToken [toc] [#NonFungibleToken]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken";
+```
+
+---
+
+### Ledger [toc] [#NonFungibleToken-Ledger]
+### Ledger [!toc] [#NonFungibleToken-Ledger]
+
+#### _name [toc] [#NonFungibleToken-_name]
+#### [!toc] [#NonFungibleToken-_name]
+
+ The token's human-readable name, set once at construction.
+
+
+#### _symbol [toc] [#NonFungibleToken-_symbol]
+#### [!toc] [#NonFungibleToken-_symbol]
+
+ The token's ticker symbol, set once at construction.
+
+
+#### _isInitialized [toc] [#NonFungibleToken-_isInitialized]
+#### [!toc] [#NonFungibleToken-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _owners [toc] [#NonFungibleToken-_owners]
+#### [!toc] [#NonFungibleToken-_owners]
+
+ Mapping from token IDs to their owner addresses.
+
+
+#### _balances [toc] [#NonFungibleToken-_balances]
+#### [!toc] [#NonFungibleToken-_balances]
+
+ Mapping from account addresses to their token balances.
+
+
+#### _tokenApprovals [toc] [#NonFungibleToken-_tokenApprovals]
+#### [!toc] [#NonFungibleToken-_tokenApprovals]
+
+ Mapping from token IDs to approved addresses.
+
+
+#### _operatorApprovals [toc] [#NonFungibleToken-_operatorApprovals]
+#### [!toc] [#NonFungibleToken-_operatorApprovals]
+
+ Mapping from owner addresses to operator approvals.
+
+
+#### _tokenURIs [toc] [#NonFungibleToken-_tokenURIs]
+#### [!toc] [#NonFungibleToken-_tokenURIs]
+
+ Mapping from token IDs to their metadata URIs.
+
+
+### Witnesses [toc] [#NonFungibleToken-Witnesses]
+### Witnesses [!toc] [#NonFungibleToken-Witnesses]
+
+#### wit_NonFungibleTokenSK [toc] [#NonFungibleToken-wit_NonFungibleTokenSK]
+#### [!toc] [#NonFungibleToken-wit_NonFungibleTokenSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#NonFungibleToken-Circuits]
+### Circuits [!toc] [#NonFungibleToken-Circuits]
+
+#### initialize [toc] [#NonFungibleToken-initialize]
+#### [!toc] [#NonFungibleToken-initialize]
+
+ Initializes the contract by setting the name and symbol.
+
+ This MUST be called in the implementing contract's constructor.
+ Failure to do so can lead to an irreparable contract.
+
+ Requirements:
+
+ * Contract is not initialized.
+
+
+#### balanceOf [toc] [#NonFungibleToken-balanceOf]
+#### [!toc] [#NonFungibleToken-balanceOf]
+
+ Returns the number of tokens in `owner`'s account.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### ownerOf [toc] [#NonFungibleToken-ownerOf]
+#### [!toc] [#NonFungibleToken-ownerOf]
+
+ Returns the owner of the `tokenId` token.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `tokenId` must exist.
+
+
+#### name [toc] [#NonFungibleToken-name]
+#### [!toc] [#NonFungibleToken-name]
+
+ Returns the token name.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### symbol [toc] [#NonFungibleToken-symbol]
+#### [!toc] [#NonFungibleToken-symbol]
+
+ Returns the symbol of the token.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### tokenURI [toc] [#NonFungibleToken-tokenURI]
+#### [!toc] [#NonFungibleToken-tokenURI]
+
+ Returns the token URI for the given `tokenId`. Returns the empty
+ string if a tokenURI does not exist.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `tokenId` must exist.
+
+
+ Native strings and string operations aren't supported within the Compact language,
+ e.g. concatenating a base URI + token ID is not possible like in other NFT implementations.
+ Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs.
+ It's up to the implementation to decide on how to handle this.
+
+
+
+#### _setTokenURI [toc] [#NonFungibleToken-_setTokenURI]
+#### [!toc] [#NonFungibleToken-_setTokenURI]
+
+ Sets the URI as [tokenURI](#NonFungibleToken-tokenURI) for the given `tokenId`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `tokenId` must exist.
+
+
+ The URI for a given NFT is usually set when the NFT is minted.
+
+
+
+#### approve [toc] [#NonFungibleToken-approve]
+#### [!toc] [#NonFungibleToken-approve]
+
+ Gives permission to `to` to transfer `tokenId` token to another account.
+ The approval is cleared when the token is transferred.
+
+ Only a single account can be approved at a time, so approving the zero value clears previous approvals.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The caller must either own the token or be an approved operator.
+ * `tokenId` must exist.
+
+
+#### getApproved [toc] [#NonFungibleToken-getApproved]
+#### [!toc] [#NonFungibleToken-getApproved]
+
+ Returns the account approved for `tokenId` token.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+
+
+#### setApprovalForAll [toc] [#NonFungibleToken-setApprovalForAll]
+#### [!toc] [#NonFungibleToken-setApprovalForAll]
+
+ Approve or remove `operator` as an operator for the caller.
+ Operators can call [transferFrom](#NonFungibleToken-transferFrom) for any token owned by the caller.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * The `operator` cannot be zero.
+
+
+#### isApprovedForAll [toc] [#NonFungibleToken-isApprovedForAll]
+#### [!toc] [#NonFungibleToken-isApprovedForAll]
+
+ Returns if the `operator` is allowed to manage all of the assets of `owner`.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### transferFrom [toc] [#NonFungibleToken-transferFrom]
+#### [!toc] [#NonFungibleToken-transferFrom]
+
+ Transfers `tokenId` token from `fromAddress` to `to`.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract interactions
+ are supported in Compact. This restriction prevents assets from being inadvertently locked in contracts that cannot
+ currently handle token receipt.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Requirements:
+
+ * The contract is initialized.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+ * `tokenId` token must be owned by `fromAddress`.
+ * If the caller is not `fromAddress`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
+
+
+#### _unsafeTransferFrom [toc] [#NonFungibleToken-_unsafeTransferFrom]
+#### [!toc] [#NonFungibleToken-_unsafeTransferFrom]
+
+ Transfers `tokenId` token from `fromAddress` to `to`. It does NOT check if the recipient is a ContractAddress.
+
+ In the case of an external (non-contract) caller, the caller's identity is derived from the [wit_NonFungibleTokenSK](#NonFungibleToken-wit_NonFungibleTokenSK)
+ witness as `persistentHash(secretKey)`.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract calls
+ are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * The contract is initialized.
+ * `fromAddress` is not zero.
+ * `to` is not zero.
+ * `tokenId` token must be owned by `fromAddress`.
+ * If the caller is not `fromAddress`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll).
+
+
+#### _ownerOf [toc] [#NonFungibleToken-_ownerOf]
+#### [!toc] [#NonFungibleToken-_ownerOf]
+
+ Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### _getApproved [toc] [#NonFungibleToken-_getApproved]
+#### [!toc] [#NonFungibleToken-_getApproved]
+
+ Returns the approved address for `tokenId`. Returns the zero value if `tokenId` is not minted.
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+#### _isAuthorized [toc] [#NonFungibleToken-_isAuthorized]
+#### [!toc] [#NonFungibleToken-_isAuthorized]
+
+ Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
+ particular (ignoring whether it is owned by `owner`).
+
+ Requirements:
+
+ * The contract is initialized.
+
+
+ This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
+ assumption.
+
+
+
+#### _checkAuthorized [toc] [#NonFungibleToken-_checkAuthorized]
+#### [!toc] [#NonFungibleToken-_checkAuthorized]
+
+ Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `spender` has approval from `owner` for `tokenId` OR `spender` has approval to manage all of `owner`'s assets.
+
+
+ This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
+ assumption.
+
+
+
+#### _mint [toc] [#NonFungibleToken-_mint]
+#### [!toc] [#NonFungibleToken-_mint]
+
+ Mints `tokenId` and transfers it to `to`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must not exist.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+
+
+#### _unsafeMint [toc] [#NonFungibleToken-_unsafeMint]
+#### [!toc] [#NonFungibleToken-_unsafeMint]
+
+ Mints `tokenId` and transfers it to `to`. It does NOT check if the recipient is a ContractAddress.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must not exist.
+ * `to` is not zero.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+
+#### _burn [toc] [#NonFungibleToken-_burn]
+#### [!toc] [#NonFungibleToken-_burn]
+
+ Destroys `tokenId`.
+ The approval is cleared when the token is burned.
+ This circuit does not check if the sender is authorized to operate on the token.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+
+
+#### _transfer [toc] [#NonFungibleToken-_transfer]
+#### [!toc] [#NonFungibleToken-_transfer]
+
+ Transfers `tokenId` from `fromAddress` to `to`.
+ As opposed to [transferFrom](#NonFungibleToken-transferFrom), this imposes no restrictions on the caller's identity.
+
+
+ Transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact. This restriction prevents assets from being inadvertently
+ locked in contracts that cannot currently handle token receipt.
+
+
+ Requirements:
+
+ * The contract is initialized.
+ * `to` is not zero.
+ * `to` is not a ContractAddress.
+ * `tokenId` token must be owned by `fromAddress`.
+
+
+#### _unsafeTransfer [toc] [#NonFungibleToken-_unsafeTransfer]
+#### [!toc] [#NonFungibleToken-_unsafeTransfer]
+
+ Transfers `tokenId` from `fromAddress` to `to`.
+ As opposed to [_unsafeTransferFrom](#NonFungibleToken-_unsafeTransferFrom), this imposes no restrictions on the caller's identity.
+ It does NOT check if the recipient is a ContractAddress.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `to` is not zero.
+ * `tokenId` token must be owned by `fromAddress`.
+
+
+ Transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported. Tokens sent to a contract address may become irretrievable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+
+#### _approve [toc] [#NonFungibleToken-_approve]
+#### [!toc] [#NonFungibleToken-_approve]
+
+ Approve `to` to operate on `tokenId`.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+ * If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
+ or approved to operate on the token (by the owner).
+
+
+#### _unsafeApprove [toc] [#NonFungibleToken-_unsafeApprove]
+#### [!toc] [#NonFungibleToken-_unsafeApprove]
+
+ Approve `to` to operate on `tokenId`, requiring the token to exist
+ only when `isExistenceRequired` is true.
+
+
+ Passing false skips the check that `tokenId` was minted. Both
+ [_unsafeTransferFrom](#NonFungibleToken-_unsafeTransferFrom) and `_update` read that invariant back, so a planted approval
+ can mint a token nobody authorized or outlive a later mint. Pass false only where
+ the owner has already been read, as `_update` does.
+
+
+ Counterpart to Solidity's `_approve(to, tokenId, auth, bool emitEvent)`, where
+ the flag also suppresses the `Approval` event. Compact has no events, so it carries
+ the existence check alone.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist, unless `isExistenceRequired` is false and `auth` is zero.
+ * If `auth` is non 0, then this function will check that `auth` is either the owner of the token,
+ or approved to operate on the token (by the owner).
+
+
+#### _setApprovalForAll [toc] [#NonFungibleToken-_setApprovalForAll]
+#### [!toc] [#NonFungibleToken-_setApprovalForAll]
+
+ Approve `operator` to operate on all of `owner` tokens.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `owner` is not zero.
+ * `operator` is not zero.
+
+
+#### _requireOwned [toc] [#NonFungibleToken-_requireOwned]
+#### [!toc] [#NonFungibleToken-_requireOwned]
+
+ Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
+ Returns the owner.
+
+ Requirements:
+
+ * The contract is initialized.
+ * `tokenId` must exist.
+
+
diff --git a/content/contracts-compact/0.4.x/api/ownable.mdx b/content/contracts-compact/0.4.x/api/ownable.mdx
new file mode 100644
index 00000000..7e918970
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/ownable.mdx
@@ -0,0 +1,511 @@
+---
+title: Ownable API
+---
+
+This module provides the full Ownable module API.
+
+
+For an overview of the module, read the [Ownable guide](../ownable).
+
+
+The page covers the following modules:
+
+- [Ownable](#Ownable): a single owner identified by a public account identifier.
+- [ZOwnablePK](#ZOwnablePK): a single owner identified by a shielded commitment.
+
+## Ownable [toc] [#Ownable]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable";
+```
+
+---
+
+### Ledger [toc] [#Ownable-Ledger]
+### Ledger [!toc] [#Ownable-Ledger]
+
+#### _isInitialized [toc] [#Ownable-_isInitialized]
+#### [!toc] [#Ownable-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _owner [toc] [#Ownable-_owner]
+#### [!toc] [#Ownable-_owner]
+
+ The current owner, canonicalized on write. A `left` holds an
+ account identifier, stored as supplied and never checked against the secret
+ key it should derive from; a `right` holds a contract address. The zero
+ value means ownership has been renounced.
+
+
+### Witnesses [toc] [#Ownable-Witnesses]
+### Witnesses [!toc] [#Ownable-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_OwnableSK [toc] [#Ownable-wit_OwnableSK]
+#### [!toc] [#Ownable-wit_OwnableSK]
+
+ Returns the caller's secret key used in deriving the account identifier.
+
+ The same key produces the same account identifier across all contracts. Users who
+ desire cross-contract unlinkability should use different keys per contract.
+
+
+### Circuits [toc] [#Ownable-Circuits]
+### Circuits [!toc] [#Ownable-Circuits]
+
+#### initialize [toc] [#Ownable-initialize]
+#### [!toc] [#Ownable-initialize]
+
+ Initializes the contract by setting the `initialOwner`.
+ This must be called in the contract's constructor.
+
+ Requirements:
+
+ * Contract is not already initialized.
+ * `initialOwner` is not a ContractAddress.
+ * `initialOwner` is not the zero address.
+
+
+#### owner [toc] [#Ownable-owner]
+#### [!toc] [#Ownable-owner]
+
+ Returns the current contract owner.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transferOwnership [toc] [#Ownable-transferOwnership]
+#### [!toc] [#Ownable-transferOwnership]
+
+ Transfers ownership of the contract to `newOwner`.
+
+
+ Ownership transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact.
+ This restriction prevents permanently disabling access to a circuit.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+ * `newOwner` is not a ContractAddress.
+ * `newOwner` is not the zero address.
+
+
+#### _unsafeTransferOwnership [toc] [#Ownable-_unsafeTransferOwnership]
+#### [!toc] [#Ownable-_unsafeTransferOwnership]
+
+ Unsafe variant of [transferOwnership](#Ownable-transferOwnership).
+
+
+ Ownership transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported.
+ Ownership privileges sent to a contract address may become uncallable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+ * `newOwner` is not the zero address.
+
+
+#### renounceOwnership [toc] [#Ownable-renounceOwnership]
+#### [!toc] [#Ownable-renounceOwnership]
+
+ Leaves the contract without an owner.
+ It will not be possible to call [assertOnlyOwner](#Ownable-assertOnlyOwner) circuits anymore.
+ Can only be called by the current owner.
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+
+
+#### assertOnlyOwner [toc] [#Ownable-assertOnlyOwner]
+#### [!toc] [#Ownable-assertOnlyOwner]
+
+ Throws if called by any account other than the owner.
+ Use this to restrict access of specific circuits to the owner.
+
+ In the case of an external (non-contract) caller, the caller’s identity is
+ derived from the [wit_OwnableSK](#Ownable-wit_OwnableSK) witness as `persistentHash(secretKey)`.
+
+ Requirements:
+
+ * Contract is initialized.
+ * The caller is the current contract owner.
+
+
+#### _transferOwnership [toc] [#Ownable-_transferOwnership]
+#### [!toc] [#Ownable-_transferOwnership]
+
+ Transfers ownership of the contract to `newOwner` without
+ enforcing permission checks on the caller.
+
+
+ Ownership transfers to contract addresses are currently disallowed until contract-to-contract
+ interactions are supported in Compact.
+ This restriction prevents circuits from being inadvertently locked in contracts.
+
+
+ Requirements:
+
+ * Contract is initialized.
+ * `newOwner` is not a ContractAddress.
+
+
+#### _unsafeUncheckedTransferOwnership [toc] [#Ownable-_unsafeUncheckedTransferOwnership]
+#### [!toc] [#Ownable-_unsafeUncheckedTransferOwnership]
+
+ Unsafe variant of [_transferOwnership](#Ownable-_transferOwnership).
+
+
+ Ownership transfers to contract addresses are considered unsafe because contract-to-contract
+ calls are not currently supported.
+ Ownership privileges sent to a contract address may become uncallable.
+ Once contract-to-contract calls are supported, this circuit may be deprecated.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+---
+
+## ZOwnablePK [toc] [#ZOwnablePK]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK";
+```
+
+---
+
+### Ledger [toc] [#ZOwnablePK-Ledger]
+### Ledger [!toc] [#ZOwnablePK-Ledger]
+
+#### _isInitialized [toc] [#ZOwnablePK-_isInitialized]
+#### [!toc] [#ZOwnablePK-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _ownerCommitment [toc] [#ZOwnablePK-_ownerCommitment]
+#### [!toc] [#ZOwnablePK-_ownerCommitment]
+
+ Stores the current hashed commitment representing the owner.
+ This commitment is derived from the public identifier (e.g., `SHA256(pk, nonce)`),
+ the `instanceSalt`, the transfer `counter`, and a domain separator.
+
+ A commitment of `default>` (i.e., zero) indicates the contract is unowned.
+
+
+#### _counter [toc] [#ZOwnablePK-_counter]
+#### [!toc] [#ZOwnablePK-_counter]
+
+ Internal transfer counter used to prevent commitment reuse.
+
+ Increments by 1 on every successful ownership transfer. Combined with `id` and
+ `instanceSalt` to compute unique owner commitments over time.
+
+
+#### _instanceSalt [toc] [#ZOwnablePK-_instanceSalt]
+#### [!toc] [#ZOwnablePK-_instanceSalt]
+
+ A per-instance value provided at initialization used to namespace
+ commitments for this contract instance.
+
+ This salt prevents commitment collisions across contracts that might otherwise use
+ the same owner identifiers or domain parameters. It is immutable after initialization.
+
+
+### Witnesses [toc] [#ZOwnablePK-Witnesses]
+### Witnesses [!toc] [#ZOwnablePK-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_secretNonce [toc] [#ZOwnablePK-wit_secretNonce]
+#### [!toc] [#ZOwnablePK-wit_secretNonce]
+
+ A private per-user nonce used in deriving the shielded owner identifier.
+
+ Combined with the user's public key as `SHA256(pk, nonce)` to produce an obfuscated,
+ unlinkable identity commitment. Users are encouraged to rotate this value on ownership changes.
+
+
+### Circuits [toc] [#ZOwnablePK-Circuits]
+### Circuits [!toc] [#ZOwnablePK-Circuits]
+
+#### initialize [toc] [#ZOwnablePK-initialize]
+#### [!toc] [#ZOwnablePK-initialize]
+
+ Initializes the contract by setting the initial owner via `ownerId`
+ and storing the `instanceSalt` that acts as a privacy additive for preventing
+ duplicate commitments among other contracts implementing ZOwnablePK.
+
+
+ The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm.
+ Using any other algorithm will result in a permanent loss of contract access.
+
+
+ Requirements:
+
+ * Contract is not initialized.
+ * `ownerId` is not zero.
+ * `instanceSalt` is not zero.
+
+
+#### owner [toc] [#ZOwnablePK-owner]
+#### [!toc] [#ZOwnablePK-owner]
+
+ Returns the current commitment representing the contract owner.
+ The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### transferOwnership [toc] [#ZOwnablePK-transferOwnership]
+#### [!toc] [#ZOwnablePK-transferOwnership]
+
+ Transfers ownership to `newOwnerId`.
+ `newOwnerId` must be precalculated and given to the current owner off chain.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller is the current owner.
+ * `newOwnerId` is not an empty array.
+
+
+#### renounceOwnership [toc] [#ZOwnablePK-renounceOwnership]
+#### [!toc] [#ZOwnablePK-renounceOwnership]
+
+ Leaves the contract without an owner.
+ It will not be possible to call [assertOnlyOwner](#ZOwnablePK-assertOnlyOwner) circuits anymore.
+ Can only be called by the current owner.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller is the current owner.
+
+
+#### assertOnlyOwner [toc] [#ZOwnablePK-assertOnlyOwner]
+#### [!toc] [#ZOwnablePK-assertOnlyOwner]
+
+ Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match
+ the stored owner commitment.
+ Use this to only allow the owner to call specific circuits.
+
+ Requirements:
+
+ * Contract is initialized.
+ * Caller's id (`SHA256(pk, nonce)`) when used in [_computeOwnerCommitment](#ZOwnablePK-_computeOwnerCommitment) equals
+ the stored `_ownerCommitment`, thus verifying themselves as the owner.
+
+
+#### _computeOwnerCommitment [toc] [#ZOwnablePK-_computeOwnerCommitment]
+#### [!toc] [#ZOwnablePK-_computeOwnerCommitment]
+
+ Computes the owner commitment from the given `id` and `counter`.
+
+ ## Owner ID (`id`)
+ The `id` is expected to be computed off-chain as:
+ `id = SHA256(pk, nonce)`
+
+ * `pk`: The owner's public key.
+ * `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer.
+
+ ## Commitment Derivation
+ `commitment = SHA256(id, instanceSalt, counter, domain)`
+
+ * `id`: See above.
+ * `instanceSalt`: A unique per-deployment salt, stored during initialization.
+ This prevents commitment collisions across deployments.
+ * `counter`: Incremented with each ownership transfer, ensuring uniqueness
+ even with repeated `id` values. Cast to `Field` then `Bytes<32>` for hashing.
+ * `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent
+ hash collisions when extending the module or using similar commitment schemes.
+
+ Requirements:
+
+ * Contract is initialized.
+
+
+#### _computeOwnerId [toc] [#ZOwnablePK-_computeOwnerId]
+#### [!toc] [#ZOwnablePK-_computeOwnerId]
+
+ Computes the unique identifier (`id`) of the owner from their
+ public key and a secret nonce.
+
+ ## ID Derivation
+ `id = SHA256(pk, nonce)`
+
+ * `pk`: The public key of the caller. This is passed explicitly to allow
+ for off-chain derivation, testing, or scenarios where the caller is
+ different from the subject of the computation.
+ We recommend using an Air-Gapped Public Key.
+ * `nonce`: A secret nonce tied to the identity. The generation strategy is
+ left to the user, offering different security/convenience trade-offs.
+
+ The result is a 32-byte commitment that uniquely identifies the owner.
+ This value is later used in owner commitment hashing,
+ and acts as a privacy-preserving alternative to a raw public key.
+
+
+ This module allows ownership to be tied to an identity commitment derived
+ from a public key and secret nonce.
+ While typically used with user public keys, this mechanism may also
+ support contract addresses as identifiers in future contract-to-contract
+ interactions. Both are treated as 32-byte values (`Bytes<32>`).
+
+
+ Requirements:
+
+ * `pk` is not a ContractAddress.
+
+
+#### _transferOwnership [toc] [#ZOwnablePK-_transferOwnership]
+#### [!toc] [#ZOwnablePK-_transferOwnership]
+
+ Transfers ownership to owner id `newOwnerId` without
+ enforcing permission checks on the caller.
+
+ Requirements:
+
+ * Contract is initialized.
+
diff --git a/content/contracts-compact/0.4.x/api/security.mdx b/content/contracts-compact/0.4.x/api/security.mdx
new file mode 100644
index 00000000..d829ab29
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/security.mdx
@@ -0,0 +1,387 @@
+---
+title: Security
+---
+
+This package provides the API for all Security modules.
+
+
+ For an overview of the module, read the [Security guide](../security).
+
+
+The page covers the following modules:
+
+- [Initializable](#Initializable): a one-time initialization guard.
+- [Pausable](#Pausable): an emergency stop mechanism.
+- [Allowlist](#Allowlist): a permit-list keyed by account identifier.
+- [Blocklist](#Blocklist): a deny-list keyed by account identifier.
+
+## Initializable [toc] [#Initializable]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Initializable";
+```
+
+
+ Use this module only at the top contract layer, and import it into at most one module per contract.
+ When two modules in the same directory both import Initializable, the compiler collapses their
+ `_isInitialized` flags into a single shared ledger slot
+ ([compact#270](https://github.com/LFDT-Minokawa/compact/issues/270)): initializing one marks the other
+ as initialized. The OpenZeppelin modules do not import this module; each tracks its own `_isInitialized` flag inline.
+
+
+---
+
+### Ledger [toc] [#Initializable-Ledger]
+### Ledger [!toc] [#Initializable-Ledger]
+
+#### _isInitialized [toc] [#Initializable-_isInitialized]
+#### [!toc] [#Initializable-_isInitialized]
+
+ Whether `initialize` has run. Guards against re-initialization
+ and against use before initialization.
+
+
+### Witnesses [toc] [#Initializable-Witnesses]
+### Witnesses [!toc] [#Initializable-Witnesses]
+
+None.
+
+### Circuits [toc] [#Initializable-Circuits]
+### Circuits [!toc] [#Initializable-Circuits]
+
+#### initialize [toc] [#Initializable-initialize]
+#### [!toc] [#Initializable-initialize]
+
+ Initializes the state thus ensuring the calling circuit can only be called once.
+
+ Requirements:
+
+ * Contract must not be initialized.
+
+
+#### assertInitialized [toc] [#Initializable-assertInitialized]
+#### [!toc] [#Initializable-assertInitialized]
+
+ Asserts that the contract has been initialized, throwing an error if not.
+
+ Requirements:
+
+ * Contract must be initialized.
+
+
+#### assertNotInitialized [toc] [#Initializable-assertNotInitialized]
+#### [!toc] [#Initializable-assertNotInitialized]
+
+ Asserts that the contract has not been initialized, throwing an error if it has.
+
+ Requirements:
+
+ * Contract must not be initialized.
+
+
+---
+
+## Pausable [toc] [#Pausable]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable";
+```
+
+---
+
+### Ledger [toc] [#Pausable-Ledger]
+### Ledger [!toc] [#Pausable-Ledger]
+
+#### _isPaused [toc] [#Pausable-_isPaused]
+#### [!toc] [#Pausable-_isPaused]
+
+ Whether the contract is paused. Consuming contracts gate
+ state-changing circuits on it via `assertPaused` / `assertNotPaused`.
+
+
+### Witnesses [toc] [#Pausable-Witnesses]
+### Witnesses [!toc] [#Pausable-Witnesses]
+
+None.
+
+### Circuits [toc] [#Pausable-Circuits]
+### Circuits [!toc] [#Pausable-Circuits]
+
+#### isPaused [toc] [#Pausable-isPaused]
+#### [!toc] [#Pausable-isPaused]
+
+ Returns true if the contract is paused, and false otherwise.
+
+
+#### assertPaused [toc] [#Pausable-assertPaused]
+#### [!toc] [#Pausable-assertPaused]
+
+ Makes a circuit only callable when the contract is paused.
+
+ Requirements:
+
+ * Contract must be paused.
+
+
+#### assertNotPaused [toc] [#Pausable-assertNotPaused]
+#### [!toc] [#Pausable-assertNotPaused]
+
+ Makes a circuit only callable when the contract is not paused.
+
+ Requirements:
+
+ * Contract must not be paused.
+
+
+#### _pause [toc] [#Pausable-_pause]
+#### [!toc] [#Pausable-_pause]
+
+ Triggers a stopped state.
+
+ Requirements:
+
+ * Contract must not be paused.
+
+
+#### _unpause [toc] [#Pausable-_unpause]
+#### [!toc] [#Pausable-_unpause]
+
+ Lifts the pause on the contract.
+
+ Requirements:
+
+ * Contract must be paused.
+
+
+---
+
+## Allowlist [toc] [#Allowlist]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Allowlist";
+```
+
+A minimal permit-list keyed by a `Bytes<32>` account identifier.
+The mutators `_allow` and `_disallow` are ungated: the implementing contract must protect them with its own access control
+and decides where `assertAllowed` applies.
+Membership is public and the approved set is publicly enumerable.
+
+---
+
+### Ledger [toc] [#Allowlist-Ledger]
+### Ledger [!toc] [#Allowlist-Ledger]
+
+#### _allowed [toc] [#Allowlist-_allowed]
+#### [!toc] [#Allowlist-_allowed]
+
+ Approved accounts. Absent means not allowed.
+
+
+### Witnesses [toc] [#Allowlist-Witnesses]
+### Witnesses [!toc] [#Allowlist-Witnesses]
+
+None.
+
+### Circuits [toc] [#Allowlist-Circuits]
+### Circuits [!toc] [#Allowlist-Circuits]
+
+#### isAllowed [toc] [#Allowlist-isAllowed]
+#### [!toc] [#Allowlist-isAllowed]
+
+ Returns whether `account` is currently allowed.
+
+
+#### assertAllowed [toc] [#Allowlist-assertAllowed]
+#### [!toc] [#Allowlist-assertAllowed]
+
+ Asserts that `account` is allowed. The composing contract
+ decides when this gate applies.
+
+ Requirements:
+
+ * `account` is a member of the allowlist.
+
+
+#### _allow [toc] [#Allowlist-_allow]
+#### [!toc] [#Allowlist-_allow]
+
+ Adds `account` to the allowlist.
+
+
+#### _disallow [toc] [#Allowlist-_disallow]
+#### [!toc] [#Allowlist-_disallow]
+
+ Removes `account` from the allowlist.
+
+
+---
+
+## Blocklist [toc] [#Blocklist]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/security/Blocklist";
+```
+
+A minimal deny-list keyed by a `Bytes<32>` account identifier.
+The mutators `_block` and `_unblock` are ungated: the implementing contract must protect them with its own access control
+and decides where `assertNotBlocked` applies.
+Membership is public and the blocked set is publicly enumerable.
+
+---
+
+### Ledger [toc] [#Blocklist-Ledger]
+### Ledger [!toc] [#Blocklist-Ledger]
+
+#### _blocked [toc] [#Blocklist-_blocked]
+#### [!toc] [#Blocklist-_blocked]
+
+ Blocked accounts. Absent means not blocked.
+
+
+### Witnesses [toc] [#Blocklist-Witnesses]
+### Witnesses [!toc] [#Blocklist-Witnesses]
+
+None.
+
+### Circuits [toc] [#Blocklist-Circuits]
+### Circuits [!toc] [#Blocklist-Circuits]
+
+#### isBlocked [toc] [#Blocklist-isBlocked]
+#### [!toc] [#Blocklist-isBlocked]
+
+ Returns whether `account` is currently blocked.
+
+
+#### assertNotBlocked [toc] [#Blocklist-assertNotBlocked]
+#### [!toc] [#Blocklist-assertNotBlocked]
+
+ Asserts that `account` is not blocked. The composing contract
+ decides when this gate applies.
+
+ Requirements:
+
+ * `account` is not a member of the blocklist.
+
+
+#### _block [toc] [#Blocklist-_block]
+#### [!toc] [#Blocklist-_block]
+
+ Adds `account` to the blocklist.
+
+
+#### _unblock [toc] [#Blocklist-_unblock]
+#### [!toc] [#Blocklist-_unblock]
+
+ Removes `account` from the blocklist.
+
diff --git a/content/contracts-compact/0.4.x/api/shielded-access-control.mdx b/content/contracts-compact/0.4.x/api/shielded-access-control.mdx
new file mode 100644
index 00000000..dd7263ba
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/shielded-access-control.mdx
@@ -0,0 +1,629 @@
+---
+title: ShieldedAccessControl API
+---
+
+This page provides the full ShieldedAccessControl module API.
+
+This module provides a shielded role-based access control (RBAC) mechanism,
+where roles can be used to represent a set of permissions.
+Roles are stored as Merkle tree commitments to avoid disclosing information about role holders.
+Role commitments are created with the following hashing scheme,
+where `||` denotes concatenation and all values are `Bytes<32>`:
+
+```
+roleCommitment := SHA256( role || accountId || instanceSalt || commitmentDomain )
+
+accountId := SHA256( secretKey || instanceSalt || accountIdDomain )
+
+roleNullifier := SHA256( roleCommitment || nullifierDomain )
+
+commitmentDomain := pad(32, "ShieldedAccessControl:commitment")
+accountIdDomain := pad(32, "ShieldedAccessControl:accountId")
+nullifierDomain := pad(32, "ShieldedAccessControl:nullifier")
+```
+
+In this RBAC model, role commitments behave like private bearer tokens.
+Possession of a valid, non-revoked role commitment grants authorization.
+Revocation permanently burns the role instance, requiring explicit new issuance under a new account identifier.
+Users must rotate their secret key to generate a new `accountId` to be re-authorized.
+
+A single `secretKey` may be used for all roles within a contract instance.
+This is safe because the `role` identifier is mixed into the commitment (not the `accountId`),
+so different roles produce different commitments and different nullifiers even when the underlying `accountId` is the same.
+
+Roles are referred to by their `Bytes<32>` identifier.
+These should be exposed in the top-level contract and be unique.
+The best way to achieve this is by using `export sealed ledger` hash digests that are initialized in the top-level contract:
+
+```typescript
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl"
+ prefix ShieldedAccessControl_;
+
+export sealed ledger MY_ROLE: Bytes<32>;
+
+constructor(instanceSalt: Bytes<32>, defaultAdmin: ShieldedAccessControl_AccountIdentifier) {
+ MY_ROLE = persistentHash>(pad(32, "MY_ROLE"));
+ ShieldedAccessControl_initialize(instanceSalt);
+ ShieldedAccessControl__grantRole(ShieldedAccessControl_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+}
+```
+
+To restrict access to a circuit, use [assertOnlyRole](#ShieldedAccessControl-assertOnlyRole):
+
+```typescript
+circuit foo(): [] {
+ ShieldedAccessControl_assertOnlyRole(MY_ROLE as ShieldedAccessControl_RoleIdentifier);
+}
+```
+
+Roles can be granted and revoked dynamically via the [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole) circuits.
+Each role has an associated admin role,
+and only accounts that have a role's admin role can call [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole).
+
+By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`,
+which means that only accounts with this role will be able to grant or revoke other roles.
+More complex role relationships can be created by using [_setRoleAdmin](#ShieldedAccessControl-_setRoleAdmin).
+
+
+ The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role.
+ Extra precautions should be taken to secure accounts that have been granted it.
+
+
+
+ For an overview of the module, read the [ShieldedAccessControl guide](../shielded-access-control).
+
+
+## ShieldedAccessControl [toc] [#ShieldedAccessControl]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl";
+```
+
+---
+
+### Types [toc] [#ShieldedAccessControl-Types]
+### Types [!toc] [#ShieldedAccessControl-Types]
+
+#### UpdateType [toc] [#ShieldedAccessControl-UpdateType]
+#### [!toc] [#ShieldedAccessControl-UpdateType]
+
+ Enum indicating whether a role update is a grant or a revocation.
+
+
+#### RoleCommitment [toc] [#ShieldedAccessControl-RoleCommitment]
+#### [!toc] [#ShieldedAccessControl-RoleCommitment]
+
+ A Merkle tree leaf committing a `(role, accountId)` pairing. Computed as `SHA256(role || accountId || instanceSalt || commitmentDomain)`.
+
+
+#### RoleIdentifier [toc] [#ShieldedAccessControl-RoleIdentifier]
+#### [!toc] [#ShieldedAccessControl-RoleIdentifier]
+
+ A unique identifier for a role.
+
+
+#### AccountIdentifier [toc] [#ShieldedAccessControl-AccountIdentifier]
+#### [!toc] [#ShieldedAccessControl-AccountIdentifier]
+
+ A privacy-preserving identity commitment. Computed as `SHA256(secretKey || instanceSalt || accountIdDomain)`.
+
+
+#### RoleNullifier [toc] [#ShieldedAccessControl-RoleNullifier]
+#### [!toc] [#ShieldedAccessControl-RoleNullifier]
+
+ A one-time burn token that permanently invalidates a role commitment on revocation. Computed as `SHA256(roleCommitment || nullifierDomain)`.
+
+
+### Ledger [toc] [#ShieldedAccessControl-Ledger]
+### Ledger [!toc] [#ShieldedAccessControl-Ledger]
+
+#### _isInitialized [toc] [#ShieldedAccessControl-_isInitialized]
+#### [!toc] [#ShieldedAccessControl-_isInitialized]
+
+ Initialization flag, tracked per-module to avoid the compiler's
+ shared transitive-dependency state bug (LFDT-Minokawa/compact#270). See the
+ Initializable module for rationale.
+
+
+#### _operatorRoles [toc] [#ShieldedAccessControl-_operatorRoles]
+#### [!toc] [#ShieldedAccessControl-_operatorRoles]
+
+ A Merkle tree of role commitments stored as SHA256(role | accountId | instanceSalt | commitmentDomain)
+ Role commitments are derived from a public role identifier (e.g., `persistentHash>(pad(32, "MY_ROLE")`),
+ an account identifier (e.g., `SHA256(secretKey, instanceSalt, accountIdDomain)`), the `instanceSalt`, and a domain separator.
+
+
+#### _adminRoles [toc] [#ShieldedAccessControl-_adminRoles]
+#### [!toc] [#ShieldedAccessControl-_adminRoles]
+
+ Mapping from a role identifier to an admin role identifier.
+ The key is the role being administered and the value is the role permitted to grant and revoke it.
+
+
+#### _roleCommitmentNullifiers [toc] [#ShieldedAccessControl-_roleCommitmentNullifiers]
+#### [!toc] [#ShieldedAccessControl-_roleCommitmentNullifiers]
+
+ A set of nullifiers used to prove a role has been revoked
+
+
+#### _grantedRoleNullifiers [toc] [#ShieldedAccessControl-_grantedRoleNullifiers]
+#### [!toc] [#ShieldedAccessControl-_grantedRoleNullifiers]
+
+ Nullifiers of every pairing ever granted. Makes a repeated grant a no-op so
+ one pairing can never consume more than one `_operatorRoles` slot, and gates revocation
+ so a nullifier can only exist for a granted pairing.
+
+
+#### _instanceSalt [toc] [#ShieldedAccessControl-_instanceSalt]
+#### [!toc] [#ShieldedAccessControl-_instanceSalt]
+
+ A per-instance value provided at initialization used to namespace
+ commitments for this contract instance.
+
+ This salt prevents commitment collisions across contracts that might otherwise use
+ the same identifiers or domain parameters. It should be a cryptographically strong random value.
+ If two deployments share the same instanceSalt and a user reuses their secretKey across both,
+ the resulting accountId and all role commitments will be identical in both contracts.
+ An observer who sees the same nullifier appear in both contracts' nullifier sets can conclude
+ that it's the same user.
+ It is immutable after initialization.
+
+
+### Witnesses [toc] [#ShieldedAccessControl-Witnesses]
+### Witnesses [!toc] [#ShieldedAccessControl-Witnesses]
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+#### wit_getRoleCommitmentPath [toc] [#ShieldedAccessControl-wit_getRoleCommitmentPath]
+#### [!toc] [#ShieldedAccessControl-wit_getRoleCommitmentPath]
+
+ Returns a path to a role commitment in the `_operatorRoles` Merkle tree if one exists. Otherwise, returns an invalid path.
+
+
+#### wit_secretKey [toc] [#ShieldedAccessControl-wit_secretKey]
+#### [!toc] [#ShieldedAccessControl-wit_secretKey]
+
+ Returns the user's secret key used in deriving the shielded account identifier.
+
+ The same key can be used across multiple roles within a contract instance. If a role is
+ revoked and re-granted, a new secret key must be generated to produce a new `accountId`.
+
+
+### Circuits [toc] [#ShieldedAccessControl-Circuits]
+### Circuits [!toc] [#ShieldedAccessControl-Circuits]
+
+#### initialize [toc] [#ShieldedAccessControl-initialize]
+#### [!toc] [#ShieldedAccessControl-initialize]
+
+ Initializes the contract by storing the `instanceSalt` that acts as a privacy additive
+ for preventing duplicate commitments among other contracts implementing ShieldedAccessControl.
+
+
+ The `instanceSalt` must be calculated prior to contract deployment using a cryptographically
+ secure random number generator e.g. crypto.getRandomValues() to maintain strong privacy guarantees
+
+
+ Requirements:
+
+ * Contract is not initialized.
+ * `instanceSalt` is not zero.
+
+
+#### DEFAULT_ADMIN_ROLE [toc] [#ShieldedAccessControl-DEFAULT_ADMIN_ROLE]
+#### [!toc] [#ShieldedAccessControl-DEFAULT_ADMIN_ROLE]
+
+ The default admin role for all roles. Only accounts with this role will be able to grant or revoke other roles
+ unless custom admin roles are created.
+
+ The Compact language does not support constant declarations,
+ so DEFAULT_ADMIN_ROLE is implemented as a circuit that returns a constant value by necessity.
+
+
+#### assertOnlyRole [toc] [#ShieldedAccessControl-assertOnlyRole]
+#### [!toc] [#ShieldedAccessControl-assertOnlyRole]
+
+ Reverts if caller cannot provide a valid proof of ownership for `role`.
+
+ Requirements:
+
+ * caller must prove ownership of `role`.
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+
+
+#### canProveRole [toc] [#ShieldedAccessControl-canProveRole]
+#### [!toc] [#ShieldedAccessControl-canProveRole]
+
+ Returns `true` if a caller proves ownership of `role` and is not revoked. MAY return false for a legitimately credentialed
+ caller if the proving environment supplies an invalid Merkle path. This circuit will never return true for an
+ unauthorized caller.
+
+ Requirements:
+
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+
+
+#### grantRole [toc] [#ShieldedAccessControl-grantRole]
+#### [!toc] [#ShieldedAccessControl-grantRole]
+
+ Grants `role` to `accountId` by inserting a role commitment unique to the
+ `(role, accountId)` pairing into the `_operatorRoles` Merkle tree. Granting an already active
+ pairing is a no-op. Once revoked, a role cannot be re-granted to the same `accountId`. A new
+ `accountId` must be generated to be re-authorized for a revoked `role`.
+
+ Requirements:
+
+ * caller must prove they're an admin for `role`.
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+ * A role identifier.
+
+
+#### revokeRole [toc] [#ShieldedAccessControl-revokeRole]
+#### [!toc] [#ShieldedAccessControl-revokeRole]
+
+ Permanently revokes `role` from `accountId` by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
+ `role`.
+
+ Requirements:
+
+ * caller must prove they're an admin for `role`.
+ * caller must not provide valid Merkle tree path for a different `(role, accountId)` pairing.
+ * The `(role, accountId)` pairing must have been granted.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A Merkle tree path to a role commitment.
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+ * A role identifier.
+
+
+#### renounceRole [toc] [#ShieldedAccessControl-renounceRole]
+#### [!toc] [#ShieldedAccessControl-renounceRole]
+
+ Revokes `role` from the calling account by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be
+ re-authorized for `role`.
+
+
+ Roles are often managed via [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole): this circuit's
+ purpose is to provide a mechanism for accounts to lose their privileges
+ if they are compromised (such as when a trusted device is misplaced).
+ Consumers with no need for self-revocation should not export this circuit.
+
+
+
+ Outside observers may be able to use timing and pattern analysis to weaken pseudonymity
+ guarantees if renounceRole is used in tandem with other on-chain actions.
+
+
+ Requirements:
+
+ * The caller must provide a valid `accountId` for the role.
+ * The `(role, accountId)` pairing must have been granted.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A nullifier for the respective role commitment.
+
+
+#### getRoleAdmin [toc] [#ShieldedAccessControl-getRoleAdmin]
+#### [!toc] [#ShieldedAccessControl-getRoleAdmin]
+
+ Returns the admin role that controls `role`. Returns [DEFAULT_ADMIN_ROLE](#ShieldedAccessControl-DEFAULT_ADMIN_ROLE) for
+ roles with no explicitly set admin. Since [DEFAULT_ADMIN_ROLE](#ShieldedAccessControl-DEFAULT_ADMIN_ROLE) is the zero byte array,
+ there is no distinction between a nonexistent `role` and one whose admin is [DEFAULT_ADMIN_ROLE](#ShieldedAccessControl-DEFAULT_ADMIN_ROLE).
+ See [grantRole](#ShieldedAccessControl-grantRole) and [revokeRole](#ShieldedAccessControl-revokeRole).
+
+ To change a role's admin use [_setRoleAdmin](#ShieldedAccessControl-_setRoleAdmin).
+
+ Disclosures:
+
+ * A role identifier.
+
+
+#### _grantRole [toc] [#ShieldedAccessControl-_grantRole]
+#### [!toc] [#ShieldedAccessControl-_grantRole]
+
+ Grants `role` to `accountId` by inserting a role commitment unique to the
+ `(role, accountId)` pairing into the `_operatorRoles` Merkle tree. Granting an already active
+ pairing is a no-op. Once revoked, a role cannot be re-granted to the same `accountId`. A new
+ `accountId` must be generated to be re-authorized for a revoked `role`.
+
+ Internal circuit without access restriction.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to grant
+ roles without authorization. It must be wrapped with appropriate access control.
+
+
+
+ The `_operatorRoles` Merkle tree has a fixed capacity of 2^20 leaf slots, one per
+ distinct pairing. Deployers should monitor slot consumption off-chain.
+
+
+ Requirements:
+
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A role commitment corresponding to a `(role, accountId)` pairing.
+ * A nullifier for the respective role commitment.
+
+
+#### _revokeRole [toc] [#ShieldedAccessControl-_revokeRole]
+#### [!toc] [#ShieldedAccessControl-_revokeRole]
+
+ Permanently revokes `role` from `accountId` by inserting a role nullifier into the
+ `_roleCommitmentNullifiers` set. Once revoked, a new `accountId` must be generated to be re-authorized for
+ `role`.
+
+ Internal circuit without access restriction.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to revoke
+ roles without authorization. It must be wrapped with appropriate access control.
+
+
+ Requirements:
+
+ * The `(role, accountId)` pairing must have been granted.
+ * The `(role, accountId)` pairing must not be already revoked.
+ * Contract is initialized.
+
+ Disclosures:
+
+ * A nullifier for the respective role commitment.
+
+
+#### _setRoleAdmin [toc] [#ShieldedAccessControl-_setRoleAdmin]
+#### [!toc] [#ShieldedAccessControl-_setRoleAdmin]
+
+ Sets `adminId` as `role`'s admin identifier. Users with valid admin identifiers
+ may grant and revoke access to the specified `role`. Internal circuit without access restriction.
+
+
+ Exposing this circuit directly in an implementing contract would allow anyone to assign
+ arbitrary admin roles without authorization. It must be wrapped with appropriate access control.
+
+
+ Requirements:
+
+ * Contract is initialized.
+
+ Disclosures:
+
+ * The role identifier
+ * The admin identifier
+
+
+#### computeRoleCommitment [toc] [#ShieldedAccessControl-computeRoleCommitment]
+#### [!toc] [#ShieldedAccessControl-computeRoleCommitment]
+
+ Computes the role commitment from the given `accountId` and `role`.
+
+
+ This circuit does not perform an initialization check. It's only meant to be used as
+ an internal helper in the Shielded Access Control module. Using this circuit outside of the
+ module may cause undefined behavior and break security guarantees.
+
+ ## Account ID (`accountId`)
+ The `accountId` is expected to be computed as:
+ `accountId = SHA256(secretKey, instanceSalt, accountIdDomain)`
+
+ * `secretKey`: A 32-byte cryptographically secure random value.
+
+ ## Role Commitment Derivation
+ `roleCommitment = SHA256(role, accountId, instanceSalt, commitmentDomain)`
+
+ * `accountId`: See above.
+ * `role`: A unique role identifier.
+ * `instanceSalt`: A unique per-deployment salt, stored during initialization.
+ This prevents commitment collisions across deployments.
+ * `commitmentDomain`: Domain separator `"ShieldedAccessControl:commitment"` (padded to 32 bytes) to prevent
+ hash collisions when extending the module or using similar commitment schemes.
+
+
+
+#### computeNullifier [toc] [#ShieldedAccessControl-computeNullifier]
+#### [!toc] [#ShieldedAccessControl-computeNullifier]
+
+ Computes the role nullifier for a given `roleCommitment`.
+
+ ## Role Nullifier Derivation
+ `roleNullifier = SHA256(roleCommitment, nullifierDomain)`
+
+ * `roleCommitment`: See [computeRoleCommitment](#ShieldedAccessControl-computeRoleCommitment).
+ * `nullifierDomain`: Domain separator `"ShieldedAccessControl:nullifier"` (padded to 32 bytes) to prevent
+ hash collisions when extending the module or using similar commitment schemes.
+
+
+#### computeAccountId [toc] [#ShieldedAccessControl-computeAccountId]
+#### [!toc] [#ShieldedAccessControl-computeAccountId]
+
+ Computes an `accountId` without on-chain state, allowing a user to derive
+ their shielded identity commitment before submitting it in a grant or revoke operation.
+ This is the off-chain counterpart to `_computeAccountId` and produces an identical result
+ given the same inputs.
+
+
+ OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
+ permanently compromise the privacy guarantees of this system:
+
+ * **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
+ application logs, analytics pipelines, or any observable side-channel.
+ * **Store offline or in secure enclaves** — hardware security modules (HSMs),
+ air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
+ * **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
+ or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
+ * **Treat key loss as identity loss** — a lost key cannot be recovered. Back up
+ keys securely before using them in role commitments.
+ * **Avoid calling this circuit in untrusted environments** — executing this in an
+ unverified browser extension, compromised runtime, or shared machine may expose
+ the key to a malicious observer.
+
+ ## ID Derivation
+ `accountId = SHA256(secretKey, instanceSalt, accountIdDomain)`
+
+ See `_computeAccountId` for further details.
+
+
diff --git a/content/contracts-compact/0.4.x/api/utils.mdx b/content/contracts-compact/0.4.x/api/utils.mdx
new file mode 100644
index 00000000..7b185ac7
--- /dev/null
+++ b/content/contracts-compact/0.4.x/api/utils.mdx
@@ -0,0 +1,288 @@
+---
+title: Utils
+---
+
+This package provides the API for all Utils modules.
+
+
+ For an overview of the modules, read the [Utils guide](../utils).
+
+
+The page covers the following modules:
+
+- [Utils](#Utils): miscellaneous circuits and common utilities for Compact contract development.
+- [EvmAbi](#EvmAbi): Ethereum ABI word encoding, for messages that must hash identically in Compact and on the EVM.
+
+## Utils [toc] [#Utils]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils";
+```
+
+---
+
+### Ledger [toc] [#Utils-Ledger]
+### Ledger [!toc] [#Utils-Ledger]
+
+None.
+
+### Witnesses [toc] [#Utils-Witnesses]
+### Witnesses [!toc] [#Utils-Witnesses]
+
+None.
+
+### Circuits [toc] [#Utils-Circuits]
+### Circuits [!toc] [#Utils-Circuits]
+
+#### isKeyOrAddressZero [toc] [#Utils-isKeyOrAddressZero]
+#### [!toc] [#Utils-isKeyOrAddressZero]
+
+ Returns whether `keyOrAddress` is the zero address.
+
+
+ Midnight's burn address is represented as left\(default\)
+ in Compact, so we've chosen to represent the zero address as this structure as well.
+
+
+
+#### isKeyZero [toc] [#Utils-isKeyZero]
+#### [!toc] [#Utils-isKeyZero]
+
+ Returns whether `key` is the zero address.
+
+
+#### isKeyOrAddressEqual [toc] [#Utils-isKeyOrAddressEqual]
+#### [!toc] [#Utils-isKeyOrAddressEqual]
+
+ Returns whether `keyOrAddress` is equal to `other`. Assumes that a ZswapCoinPublicKey
+ and a ContractAddress can never be equal
+
+
+#### isContractAddress [toc] [#Utils-isContractAddress]
+#### [!toc] [#Utils-isContractAddress]
+
+ Returns whether `keyOrAddress` is a ContractAddress type.
+
+
+#### emptyString [toc] [#Utils-emptyString]
+#### [!toc] [#Utils-emptyString]
+
+ A helper function that returns the empty string: "".
+
+
+#### canonicalize [toc] [#Utils-canonicalize]
+#### [!toc] [#Utils-canonicalize]
+
+ Zeroes out the unused side of an `Either` value. Prevents crafted
+ inputs where both `left` and `right` fields carry data from bypassing checks that
+ only inspect the active side.
+
+
+#### zeroAccount [toc] [#Utils-zeroAccount]
+#### [!toc] [#Utils-zeroAccount]
+
+ Returns the canonical zero `Either, ContractAddress>` value: the
+ left variant (`Bytes<32>`) set to zero. Used as the sentinel for mint/burn operations
+ (a zero `from` signals a mint, a zero `to` signals a burn) and for cleared approvals.
+
+ The left variant is chosen so that [isTargetZero](#Utils-isTargetZero) checks against `default>`,
+ and to avoid triggering contract-address guards (e.g. circuits that check `!to.is_left`
+ before delegating to an `_unsafe` variant). A right variant with `default`
+ would also pass [isTargetZero](#Utils-isTargetZero), but the left variant keeps guards and error messages consistent.
+
+
+#### isTargetZero [toc] [#Utils-isTargetZero]
+#### [!toc] [#Utils-isTargetZero]
+
+ Returns `true` if `target`'s active branch (as indicated by `is_left`)
+ holds the zero value.
+
+
+#### computeAccountId [toc] [#Utils-computeAccountId]
+#### [!toc] [#Utils-computeAccountId]
+
+ Computes an account identifier without on-chain state, allowing a user to derive
+ their identity commitment before submitting an operation.
+
+
+ OpSec: The `secretKey` parameter is a sensitive secret. Mishandling it can
+ permanently compromise the security of this system:
+
+ * **Never log or persist** the `secretKey` in plaintext — avoid browser devtools,
+ application logs, analytics pipelines, or any observable side-channel.
+ * **Store offline or in secure enclaves** — hardware security modules (HSMs),
+ air-gapped devices, or encrypted vaults are strongly preferred over hot storage.
+ * **Use cryptographically secure randomness** — generate keys with `crypto.getRandomValues()`
+ or equivalent; weak or predictable keys can be brute-forced to reveal your identity.
+ * **Treat key loss as identity loss** — a lost key cannot be recovered.
+ * **Avoid calling this circuit in untrusted environments** — executing this in an
+ unverified browser extension, compromised runtime, or shared machine may expose
+ the key to a malicious observer.
+
+ ## ID Derivation
+ `accountId = persistentHash(secretKey)`
+
+
+ The absence of a domain-separation tag is DELIBERATE. This identifier
+ is global by design: the same key material yields the same identity in
+ every module deriving one, so a user who wishes to carry one identity
+ across modules can. Modules wanting a per-deployment, unlinkable identity
+ should not use this circuit.
+
+ NOT FOR PRIVATE DERIVATION. This returns an identifier, not a secret,
+ and the construction is untagged. A circuit deriving a PRIVATE value from a
+ secret MUST use its own domain-separation tag.
+
+
+#### selfAsRecipient [toc] [#Utils-selfAsRecipient]
+#### [!toc] [#Utils-selfAsRecipient]
+
+ Returns the current contract's address as an
+ `Either` for use as a
+ recipient in shielded send operations (deposits and receiving change).
+
+
+#### UINT128_MAX [toc] [#Utils-UINT128_MAX]
+#### [!toc] [#Utils-UINT128_MAX]
+
+ The maximum value representable by a `Uint<128>`.
+
+
+ TODO: Remove once a math module providing this constant is available.
+
+
+
+---
+
+## EvmAbi [toc] [#EvmAbi]
+
+
+```ts
+import "./node_modules/@openzeppelin/compact-contracts/utils/EvmAbi";
+```
+
+The module needs no `keccak256` of its own and compiles on either ZKIR backend.
+Every circuit size below is a ZKIR v3 figure.
+
+---
+
+### Ledger [toc] [#EvmAbi-Ledger]
+### Ledger [!toc] [#EvmAbi-Ledger]
+
+None.
+
+### Witnesses [toc] [#EvmAbi-Witnesses]
+### Witnesses [!toc] [#EvmAbi-Witnesses]
+
+None.
+
+### Circuits [toc] [#EvmAbi-Circuits]
+### Circuits [!toc] [#EvmAbi-Circuits]
+
+#### uint8Word [toc] [#EvmAbi-uint8Word]
+#### [!toc] [#EvmAbi-uint8Word]
+
+ Encodes a `Uint<8>` as an `abi.encode(uint8)` word: the
+ value in the least significant byte, zero elsewhere.
+
+ This is also how a Compact `enum` should reach a digest, matching Solidity,
+ where an enum ABI-encodes as its `uint8` ordinal: `uint8Word(kind as Uint<8>)`.
+
+
+#### uint64Word [toc] [#EvmAbi-uint64Word]
+#### [!toc] [#EvmAbi-uint64Word]
+
+ Encodes a `Uint<64>` as an `abi.encode(uint256)` word: the
+ value big-endian in the low 8 bytes, zero-padded on the left.
+
+
+#### uint128Word [toc] [#EvmAbi-uint128Word]
+#### [!toc] [#EvmAbi-uint128Word]
+
+ Encodes a `Uint<128>` as an `abi.encode(uint256)` word: the
+ value big-endian in the low 16 bytes, zero-padded on the left.
+
+ Costs roughly twice [uint64Word](#EvmAbi-uint64Word), so prefer the narrower
+ encoder when the value fits in 64 bits.
+
+
+#### boolWord [toc] [#EvmAbi-boolWord]
+#### [!toc] [#EvmAbi-boolWord]
+
+ Encodes a `Boolean` as an `abi.encode(bool)` word: zero
+ everywhere except the least significant byte, which is 1 for true.
+
diff --git a/content/contracts-compact/0.4.x/confidential-fungible-token.mdx b/content/contracts-compact/0.4.x/confidential-fungible-token.mdx
new file mode 100644
index 00000000..36217730
--- /dev/null
+++ b/content/contracts-compact/0.4.x/confidential-fungible-token.mdx
@@ -0,0 +1,427 @@
+---
+title: ConfidentialFungibleToken
+---
+
+{/* links */}
+[FungibleToken]: ./fungible-token.mdx
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[Ownable]: ./ownable.mdx
+[crypto guide]: ./crypto
+[ElGamal]: api/crypto#ElGamal
+[EcdhMask]: api/crypto#EcdhMask
+[ConfidentialFungibleToken API]: api/confidential-fungible-token
+[EscrowEntry]: api/confidential-fungible-token#ConfidentialFungibleToken-EscrowEntry
+[wit_ConfidentialTokenSK]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_ConfidentialTokenSK
+[wit_ConfidentialTokenEK]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_ConfidentialTokenEK
+[wit_PlaintextBalance]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_PlaintextBalance
+[wit_RandomnessSeed]: api/confidential-fungible-token#ConfidentialFungibleToken-wit_RandomnessSeed
+[MAX_TRANSFER_VALUE]: api/confidential-fungible-token#ConfidentialFungibleToken-MAX_TRANSFER_VALUE
+[initialize]: api/confidential-fungible-token#ConfidentialFungibleToken-initialize
+[balanceOf]: api/confidential-fungible-token#ConfidentialFungibleToken-balanceOf
+[pendingOf]: api/confidential-fungible-token#ConfidentialFungibleToken-pendingOf
+[allowance]: api/confidential-fungible-token#ConfidentialFungibleToken-allowance
+[isRegistered]: api/confidential-fungible-token#ConfidentialFungibleToken-isRegistered
+[register]: api/confidential-fungible-token#ConfidentialFungibleToken-register
+[_mint]: api/confidential-fungible-token#ConfidentialFungibleToken-_mint
+[_burn]: api/confidential-fungible-token#ConfidentialFungibleToken-_burn
+[_burnFrom]: api/confidential-fungible-token#ConfidentialFungibleToken-_burnFrom
+[transfer]: api/confidential-fungible-token#ConfidentialFungibleToken-transfer
+[_move]: api/confidential-fungible-token#ConfidentialFungibleToken-_move
+[sweep]: api/confidential-fungible-token#ConfidentialFungibleToken-sweep
+[approve]: api/confidential-fungible-token#ConfidentialFungibleToken-approve
+[transferFrom]: api/confidential-fungible-token#ConfidentialFungibleToken-transferFrom
+[clearMemos]: api/confidential-fungible-token#ConfidentialFungibleToken-clearMemos
+[computeAccountId]: api/confidential-fungible-token#ConfidentialFungibleToken-computeAccountId
+[ConfidentialFungibleTokenPublicSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply
+[_addSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply-_addSupply
+[_subSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply-_subSupply
+[totalSupply]: api/confidential-fungible-token#ConfidentialFungibleTokenPublicSupply-totalSupply
+[compact#757]: https://github.com/LFDT-Minokawa/compact/issues/757
+
+ConfidentialFungibleToken is an account-based fungible token whose balances live on the public ledger
+as [ElGamal] ciphertexts over the Jubjub curve, so no amount is ever written in the clear.
+Accounts are identified the same way as in [FungibleToken], by a 32-byte `accountId` derived from a secret key,
+but every value a circuit moves is encrypted under the account holder's own encryption key,
+and the caller proves in zero knowledge that it knows the plaintext of the balance it spends from.
+Transfers hide the amount; the (sender, recipient) pair is public.
+Total-supply tracking is optional and lives in the separate [ConfidentialFungibleTokenPublicSupply] extension.
+
+The encryption primitives (`ElGamal` and `EcdhMask`) are documented in the [crypto guide].
+This page covers how the token composes them.
+
+## The account model
+
+### What is on the public ledger
+
+Every ledger field of the module is exported, so an observer can read all of the following directly from state:
+
+* **Encryption keys** (`_encryptionKeys`): `accountId` to the account's ElGamal public key, published by [register].
+* **Spendable balances** (`_balances`): one ElGamal ciphertext per registered account.
+ Debits (transfers out, approvals, burns) draw from this cell.
+* **Pending balances** (`_pending`): a second ciphertext per account where incoming credits land.
+ Only the owner moves pending into spendable, with [sweep].
+* **Memos** (`_memos`): per account, a list of [EcdhMask] ciphertexts, one per credit received.
+ Each one carries the credited amount encrypted to the recipient's key; this is how a recipient learns what it was sent.
+* **Escrow entries** (`_escrow`): per (owner, spender), an [EscrowEntry] holding the encrypted allowance.
+* **Credit counters** (`_creditEpochs`): per account, the lifetime number of credits received. It never resets.
+* **Escrow-spend counters** (`_escrowSpendEpochs`): per (owner, spender), the number of times the escrow was drawn on. It never resets.
+* **Metadata** (`_name`, `_symbol`, `_decimals`) and the `_isInitialized` flag.
+ `_decimals` is presentational only; no circuit scales by it.
+
+The module keeps no nullifier sets; the two counters above are the only monotonic state it writes,
+and both exist to keep per-operation randomness distinct (see below), not to prevent replay.
+
+What an observer learns from this is the set of registered `accountId`s, which accounts transact with which,
+how many credits each account has received, and how often each escrow has been spent from.
+What an observer does not learn is any balance, any transferred amount, or any allowance cap,
+unless the deployment also tracks public supply (see [Minting, burning and supply](#minting-burning-and-supply)).
+
+### What the user holds privately
+
+Four witnesses feed the circuits. Each is listed with how (or whether) the circuit verifies it.
+
+* [wit_ConfidentialTokenSK] returns the **account secret**. The circuit computes `accountId = H(SK)`
+ and uses that id for every ledger lookup, so a wrong secret resolves to a different account and the call fails.
+* [wit_ConfidentialTokenEK] returns the **encryption secret**. The circuit re-derives the ElGamal public key from it
+ and compares against the key stored at registration, either through the balance proof or directly on
+ [sweep] and [clearMemos], which have no plaintext to prove.
+* [wit_PlaintextBalance] returns the wallet's **cached plaintext** for a given ciphertext.
+ The ciphertext is passed in as the argument so the wallet can route by ciphertext identity rather than by call order.
+ The circuit verifies the claim with `ElGamal_assertDecryptsTo`; a wrong value fails the proof.
+* [wit_RandomnessSeed] returns a 32-byte **randomness seed** from which every per-operation random scalar
+ and every memo ephemeral is expanded in-circuit. Unlike the other three, this witness is **not** verified.
+
+The same key material may back both the account secret and the encryption secret, or both may be derived from one master secret:
+the account id is an untagged hash of the SK, while the encryption scalar is a domain-separated hash of the EK,
+so publishing the id does not reveal the encryption key.
+
+
+ Seed freshness is load-bearing for confidentiality on every path. The seed-to-randomness expansion is deterministic
+ and public, so anyone who knows a seed can strip the memo mask and read the delivered amount, and can recover a balance
+ or escrow amount from its public ciphertext. Reusing a seed across two operations leaks the difference between their amounts,
+ and the circuit does not prevent reuse. A wallet must supply a fresh, high-entropy, secret seed on every invocation.
+ On `transferFrom` and `burnFrom` the seed is the spender's but the ciphertexts it randomizes belong to the owner,
+ so a careless spender leaks a counterparty's balance, not their own.
+
+
+The TypeScript witnesses in the repository are test-only and are not published with the package;
+in particular the test seed witness returns a fixed value. A consumer authors its own witnesses,
+backed by the user's wallet, and the seed witness must draw from a cryptographically secure random source.
+
+### Balance claims and the per-operation bound
+
+Every value-bearing circuit takes a `Uint<128>` amount and checks it against [MAX_TRANSFER_VALUE].
+A balance, however, is the homomorphic sum of every credit it has ever received and has no running total to bound it,
+so the plaintext a wallet claims through [wit_PlaintextBalance] is a `Uint<248>`, wider than any single amount.
+A balance accumulated past the per-operation bound therefore stays spendable.
+Escrow claims are narrowed back to `Uint<128>`, since an allowance is set by one `approve` and only ever reduced.
+
+## Registration
+
+[register] derives the caller's ElGamal public key from the encryption secret, stores it under the caller's `accountId`,
+and initializes both the spendable and the pending balance to an encryption of zero.
+It discloses the `accountId` and the public key. Registration is required before an account can send **or** receive:
+a credit needs the recipient's public key to encrypt under.
+Registration is one-shot: a second [register] for the same `accountId` is rejected, so the stored key cannot be replaced.
+
+Before any on-chain call, a wallet derives its own id with the pure [computeAccountId] circuit (no proof needed):
+
+```ts
+const accountId = Token_computeAccountId(secretKey);
+```
+
+The id is global: the same secret produces the same `accountId` in every module that derives it this way
+(`Utils_computeAccountId` is the shared implementation).
+
+## Transfers, pending balances and memos
+
+[transfer] does three things in one proof:
+
+1. **Debit.** The caller proves the plaintext of its spendable balance, the circuit checks it is at least `value`,
+ and the balance ciphertext is replaced by a homomorphic subtraction of `value` under fresh randomness.
+2. **Credit.** The recipient's *pending* ciphertext is replaced by a homomorphic addition of `value`.
+ The recipient's credit counter is incremented and folded into the randomness, so two credits never share randomness
+ even if a sender reuses a seed.
+3. **Memo.** An [EcdhMask] ciphertext carrying `value`, encrypted to the recipient's public key, is pushed to the front of
+ the recipient's memo list.
+
+What reaches the ledger: the sender's `accountId`, the recipient's `accountId`, two new ciphertexts and one memo.
+The amount is not disclosed. Self-transfer is rejected.
+
+### How a recipient learns of incoming funds
+
+The on-chain balance ciphertext is exponential ElGamal (it encrypts `g^value`), so a wallet cannot cheaply decrypt it.
+Instead the wallet scans its own `_memos` list off-chain, decrypts each new entry directly with its encryption secret
+(no discrete-log search and no bounded-value table), and adds the amounts to a local plaintext cache.
+
+Memos record credits only. Debits (sends, approvals, burns) move value with no memo, so summing memos overcounts an account
+that has ever spent. The wallet's running cache, not memo replay, is the authoritative balance,
+and it is what the wallet returns from [wit_PlaintextBalance].
+
+### Sweeping pending into spendable
+
+Credits land in `_pending`, not in `_balances`, so a third party cannot change the ciphertext an owner is about to prove against.
+Without this split an attacker could send dust to invalidate a victim's in-flight spend proofs.
+The owner calls [sweep] when ready: it homomorphically adds the two ciphertexts, resets pending to an encryption of zero,
+and needs no plaintext (the wallet already learned the pending amounts from its memos).
+Only the owner can sweep, since the circuit checks both the account secret and the encryption secret.
+A wallet's total holding is `balanceOf + pendingOf`.
+
+### The conserving primitive
+
+[_move] is [transfer] without the self-transfer guard: it debits the caller and credits `to` by the same amount, net zero,
+and never changes supply. It is the building block for custom value flows (fees, splits, settlement),
+and it stays permissive so a composing contract can set its own policy.
+
+### Pruning memos
+
+A wallet that has folded its memos into a durable cache can call [clearMemos] to empty its list on chain.
+The call takes the caller's view of its credit counter and is rejected if a credit landed in between,
+so a memo the wallet has not yet seen is never discarded.
+
+
+ Clearing memos is destructive. Spending requires proving the balance plaintext, and the plaintext can only be
+ reconstructed from the memos or from the wallet's cache. Clear only memos already folded into a durable, backed-up cache;
+ otherwise the balance becomes unspendable, not merely undisplayable.
+
+
+## Approvals and escrow
+
+An allowance is not a public cap. [approve] moves `value` out of the caller's spendable balance into an escrow entry
+that holds two encryptions of the same amount, one under the spender's key and one under the owner's,
+plus an owner-readable memo of the current remaining allowance:
+
+```
+EscrowEntry { spenderCt, ownerCt, ownerMemo }
+```
+
+Re-approving first refunds any prior escrow for that spender back to the owner's balance (a homomorphic add of `ownerCt`,
+no plaintext needed) and then escrows the new amount, so allowances replace rather than stack.
+Revoking is `approve(spender, 0)`. What reaches the ledger: the owner's and spender's ids, the owner's new balance ciphertext,
+and the entry; the cap is not disclosed.
+
+Funding the allowance up front is required. The spender holds no owner encryption key, so at spend time it can only prove
+against its own escrow copy, never that the owner's balance covers the allowance.
+An approve conserves the owner's holdings (spendable balance plus every outstanding escrow),
+so allowances across spenders can sum to the whole holding but never exceed it.
+
+[transferFrom] is called by the spender. It proves the plaintext of `spenderCt`, checks it is at least `value`,
+reduces **both** escrow copies by `value`, refreshes `ownerMemo` with the new remaining amount
+(computed in-circuit from the verified plaintext, so the spender cannot forge it), increments the (owner, spender) spend counter,
+and credits `to` exactly as [transfer] does. [_burnFrom] is the same escrow spend with no credit.
+Both require the owner and the spender to be registered, so an `_escrow` entry written for an unregistered account
+(by a composing contract, for instance) is unspendable.
+
+### What a partial spend leaves behind
+
+After a spender draws part of an allowance, the ledger holds a reduced `spenderCt` and `ownerCt` that still encrypt the same
+remaining amount, a fresh `ownerMemo` stating that remainder, and an incremented spend counter.
+The owner receives no balance memo (its spendable balance did not change) and instead decrypts `ownerMemo`
+to learn how much is left in escrow. That is what lets the owner prove its post-refund balance on the next `approve`,
+when the remainder flows back into `_balances`. Without the memo a partial spend would strand the escrow.
+
+[allowance] returns the entry, or an on-curve placeholder when none exists. The placeholder's `spenderCt` and `ownerCt`
+are genuine encryptions of zero, but its `ownerMemo` is a sentinel whose `ephemeralPk` is the identity point,
+which `EcdhMask_decrypt` rejects. A wallet tests for the sentinel instead of decrypting it.
+The sentinel is also what tells "no allowance" from "an allowance of zero", since `approve` stores a real memo even for zero.
+Granting nothing is safe because the spend path gates on escrow membership independently.
+
+## Minting, burning and supply
+
+The base module ships three supply-changing building blocks and no supply accounting:
+
+* [_mint] credits `value` to `account` (with a memo), exactly like the credit half of a transfer.
+* [_burn] debits `value` from the caller's spendable balance.
+* [_burnFrom] consumes `value` from an escrow the owner granted the caller.
+
+None of them touches a total. Pair them with [ConfidentialFungibleTokenPublicSupply] when the deployment tracks supply:
+its `_totalSupply` ledger field is a public `Uint<128>`, and [_addSupply] / [_subSupply] disclose each mint and burn amount
+through the delta on that value. Balances stay confidential; only the aggregate is visible. This is the right default
+for issuers who must attest to a circulating supply. The extension imports no token module, needs no initialization,
+and the pairing of each token operation with its accounting block is the composing contract's responsibility.
+
+
+ Never re-export `_mint`, `_burn`, or `_burnFrom` from a deployed contract. Compact cannot restrict a re-export,
+ so a raw `_mint` is a permissionless mint, a raw `_burn` a permissionless burn, and a raw `_burnFrom` destroys escrowed
+ value with no supply decrement. Expose only the conserving surface (`transfer`, `_move`, `approve`, `transferFrom`, `sweep`)
+ plus your own gated `mint` / `burn` / `burnFrom` wrappers.
+
+
+## Gating on the caller
+
+A composing contract that enforces policy on the caller (freeze lists, KYC, pause) cannot read the caller's identity itself:
+the caller is derived from a witness inside the module, and a second witness call in the wrapper could return a different secret.
+For that reason every caller-authenticating circuit ([register], [_burn], [transfer], [_move], [approve], [transferFrom],
+[sweep], [_burnFrom]) returns the `accountId` it authenticated. Gate on the returned value after the call;
+an assertion that fails anywhere in a circuit invalidates the whole transaction, so a post-call check still blocks the operation.
+The returned id is always the caller (sender, approver, spender, or registrant), never `to` or `fromAddress`,
+which are explicit arguments a wrapper gates directly.
+
+Accounts are externally owned. A contract has no private state in which to hold an encryption secret,
+so it could be credited but could never prove a balance or scan memos; the module therefore keys everything by a `Bytes<32>`
+account id and offers no `ContractAddress` variant. A protocol that needs to hold these tokens uses an account it controls
+through `approve` / `transferFrom`.
+
+## Usage
+
+The following contract composes the token with the public-supply extension and [Ownable],
+pairing every supply change with its accounting block and restricting `mint` to the contract owner.
+It re-exports the module's ledger so wallets can read ciphertexts and memos off chain,
+and it exposes the conserving surface plus gated supply wrappers.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/ConfidentialFungibleToken"
+ prefix Token_;
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/ConfidentialFungibleTokenPublicSupply"
+ prefix Supply_;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal"
+ prefix ElGamal_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+// Types wallets need to read balances and escrow entries.
+export { Token_EscrowEntry, ElGamal_Ciphertext };
+
+// Surface the module's ledger so wallets can scan memos and read ciphertexts
+// without a proof.
+export {
+ Token__balances,
+ Token__pending,
+ Token__encryptionKeys,
+ Token__memos,
+ Token__escrow,
+ Token__creditEpochs,
+ Token__name,
+ Token__symbol,
+ Token__decimals,
+ Supply__totalSupply
+};
+
+constructor(
+ name_: Opaque<"string">,
+ symbol_: Opaque<"string">,
+ decimals_: Uint<8>,
+ issuer: Either, ContractAddress>
+) {
+ Token_initialize(name_, symbol_, decimals_);
+ Ownable_initialize(issuer);
+}
+
+// Views
+
+export circuit totalSupply(): Uint<128> {
+ return Supply_totalSupply();
+}
+
+export circuit balanceOf(account: Bytes<32>): ElGamal_Ciphertext {
+ return Token_balanceOf(account);
+}
+
+export circuit pendingOf(account: Bytes<32>): ElGamal_Ciphertext {
+ return Token_pendingOf(account);
+}
+
+export circuit allowance(owner: Bytes<32>, spender: Bytes<32>): Token_EscrowEntry {
+ return Token_allowance(owner, spender);
+}
+
+export circuit isRegistered(account: Bytes<32>): Boolean {
+ return Token_isRegistered(account);
+}
+
+export pure circuit computeAccountId(sk: Bytes<32>): Bytes<32> {
+ return Token_computeAccountId(sk);
+}
+
+// Registration and wallet maintenance
+
+export circuit register(): Bytes<32> {
+ return Token_register();
+}
+
+export circuit sweep(): Bytes<32> {
+ return Token_sweep();
+}
+
+export circuit clearMemos(expectedEpoch: Uint<64>): [] {
+ Token_clearMemos(expectedEpoch);
+}
+
+// Conserving value operations
+
+export circuit transfer(to: Bytes<32>, value: Uint<128>): Bytes<32> {
+ return Token_transfer(to, value);
+}
+
+export circuit approve(spender: Bytes<32>, value: Uint<128>): Bytes<32> {
+ return Token_approve(spender, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Bytes<32>,
+ to: Bytes<32>,
+ value: Uint<128>
+): Bytes<32> {
+ return Token_transferFrom(fromAddress, to, value);
+}
+
+// Supply operations: each token building block is paired with its
+// accounting block, so sum(balances) == totalSupply holds by construction.
+
+export circuit mint(account: Bytes<32>, value: Uint<128>): [] {
+ Ownable_assertOnlyOwner();
+ Supply__addSupply(value);
+ Token__mint(account, value);
+}
+
+export circuit burn(value: Uint<128>): Bytes<32> {
+ const accountId = Token__burn(value);
+ Supply__subSupply(value);
+ return accountId;
+}
+
+export circuit burnFrom(fromAddress: Bytes<32>, value: Uint<128>): Bytes<32> {
+ const spenderId = Token__burnFrom(fromAddress, value);
+ Supply__subSupply(value);
+ return spenderId;
+}
+```
+
+A few points about this layout:
+
+* `mint` is gated on the contract owner. `burn` and `burnFrom` need no extra gate: the module already authenticates
+ the caller as the account (or the approved spender) whose value is destroyed, and both return that `accountId`
+ for any further policy check.
+* `name`, `symbol`, and `decimals` are read from the exported ledger rather than through getter circuits.
+* The raw `_mint`, `_burn`, and `_burnFrom` building blocks are not re-exported; see the callout above.
+
+### Wallet-side flow
+
+A wallet integrating this contract typically:
+
+1. Derives `accountId` with `computeAccountId(sk)` and calls `register()` once.
+2. Scans `_memos[accountId]` after each block, decrypts new entries with the encryption secret, and updates its plaintext cache.
+3. Calls `sweep()` to make pending credits spendable.
+4. Supplies, through its witnesses, the account secret, the encryption secret, the cached plaintext for whichever ciphertext
+ the circuit asks about, and a fresh seed on every call.
+5. After a partial `transferFrom` against an allowance it granted, decrypts the escrow entry's `ownerMemo` to learn the remainder.
+
+## Known limitations
+
+* Two credits to the same recipient in the same block conflict, because each credit reads and advances the recipient's
+ credit counter. Credits to one account serialize.
+* Anyone can append memos to any registered account by sending it dust, and only the account owner can prune the list.
+* The value circuits are large. A deployment that exposes the full surface may exceed a network's per-transaction byte budget;
+ confirm deployability against the target network and expose only the operations the contract needs.
+* Contract-to-contract holdings are out of scope, as described under [Gating on the caller](#gating-on-the-caller).
+* An upgrade may add operations but must not change how existing state is derived or laid out.
+ Key derivation is the quiet case: [register] stores the key `ElGamal.derivePk` returns and refuses a second one,
+ and the account id does not move, so an upgrade that derives keys differently leaves every balance, pending balance,
+ escrow and memo permanently unreachable. A changed derivation applies to fresh deployments only,
+ and off-chain code that mirrors `derivePk` must keep the instance's derivation for its lifetime.
+* The library targets ZKIR v2, which compiler 0.34.0 emits by default.
+ Under `--feature-zkir-v3`, key generation fails with `Unsupported test_eq: JubjubScalar == JubjubScalar`
+ for any impure circuit that reaches `ElGamal.encryptPoint`, which every value circuit of this module does ([compact#757]).
diff --git a/content/contracts-compact/0.4.x/crypto.mdx b/content/contracts-compact/0.4.x/crypto.mdx
new file mode 100644
index 00000000..d6100668
--- /dev/null
+++ b/content/contracts-compact/0.4.x/crypto.mdx
@@ -0,0 +1,413 @@
+---
+title: Crypto
+---
+
+{/* links */}
+[ConfidentialFungibleToken guide]: ./confidential-fungible-token
+[Multisig guide]: ./multisig
+[ElGamal API]: api/crypto#ElGamal
+[EcdhMask API]: api/crypto#EcdhMask
+[Ecdsa API]: api/crypto#Ecdsa
+[Eip712 API]: api/crypto#Eip712
+[EvmAbi API]: api/utils#EvmAbi
+[secretToScalar]: api/crypto#ElGamal-secretToScalar
+[derivePk]: api/crypto#ElGamal-derivePk
+[expandRandomness]: api/crypto#ElGamal-expandRandomness
+[encrypt]: api/crypto#ElGamal-encrypt
+[encryptPoint]: api/crypto#ElGamal-encryptPoint
+[encryptZero]: api/crypto#ElGamal-encryptZero
+[add]: api/crypto#ElGamal-add
+[sub]: api/crypto#ElGamal-sub
+[scalarMul]: api/crypto#ElGamal-scalarMul
+[addEncrypted]: api/crypto#ElGamal-addEncrypted
+[subEncrypted]: api/crypto#ElGamal-subEncrypted
+[rerandomize]: api/crypto#ElGamal-rerandomize
+[assertKeyPair]: api/crypto#ElGamal-assertKeyPair
+[assertDecryptsTo]: api/crypto#ElGamal-assertDecryptsTo
+[assertDecryptsToPoint]: api/crypto#ElGamal-assertDecryptsToPoint
+[kdf]: api/crypto#EcdhMask-kdf
+[EcdhMask.encrypt]: api/crypto#EcdhMask-encrypt
+[EcdhMask.decrypt]: api/crypto#EcdhMask-decrypt
+[isLowS]: api/crypto#Ecdsa-isLowS
+[assertLowS]: api/crypto#Ecdsa-assertLowS
+[secp256k1EcdsaVerifyLowS]: api/crypto#Ecdsa-secp256k1EcdsaVerifyLowS
+[domainSeparator]: api/crypto#Eip712-domainSeparator
+[hashTypedData]: api/crypto#Eip712-hashTypedData
+[LFDT-Minokawa/compact#757]: https://github.com/LFDT-Minokawa/compact/issues/757
+[LFDT-Minokawa/compact#616]: https://github.com/LFDT-Minokawa/compact/issues/616
+
+The Crypto directory provides two stateless encryption primitives over Midnight's embedded Jubjub curve:
+ElGamal, an additively homomorphic encryption of small integers, and EcdhMask,
+a one-time pad derived from an ECDH key exchange that delivers a value to a recipient directly.
+It also provides two modules for verifying secp256k1 signatures produced by Ethereum signing infrastructure:
+Ecdsa, which enforces the low-s encoding, and Eip712, which builds the typed-data digest an EVM signer signs.
+All four are pure modules: they hold no ledger state, declare no witnesses,
+and take their keys, messages, and randomness as explicit arguments.
+The importing contract decides what is stored, what is disclosed, and where its secrets come from.
+
+The [ConfidentialFungibleToken guide] shows the two primitives working together end to end:
+ElGamal keeps every balance as a ciphertext that the contract can update without decrypting it,
+and EcdhMask hands each recipient the plaintext amount of an incoming transfer.
+
+
+Ecdsa and Eip712 require ZKIR v3: the `Secp256k1` types and `keccak256` are unavailable on the v2 backend,
+which compiler 0.34.0 emits by default. Compile a contract that imports either with `--feature-zkir-v3`;
+the library's `compile:crypto` script passes it.
+ElGamal and EcdhMask target v2. Under v3, an impure circuit that reaches [encryptPoint] fails at key generation
+([LFDT-Minokawa/compact#757]), and exporting [derivePk] as an impure circuit panics ([LFDT-Minokawa/compact#616]).
+
+
+## ElGamal
+
+The [ElGamal API] implements single-receiver ElGamal encryption over Jubjub.
+A ciphertext is a pair of curve points `(c1, c2)` with `c1 = g^r` and `c2 = pk^r * m`,
+where `g` is the Jubjub generator, `pk` is the recipient's public key, `r` is the encryption randomness,
+and `m` is the message point.
+
+The module offers the scheme in two forms.
+
+* ***Exponential (lifted) form*** ([encrypt], [assertDecryptsTo]): a `Uint<128>` value `v` is encrypted as the point `g^v`.
+Because the message sits in the exponent, ciphertexts combine arithmetically:
+[add] turns ciphertexts of `a` and `b` into a ciphertext of `a + b`, [sub] yields `a - b`,
+and [scalarMul] multiplies the plaintext by a public scalar.
+[addEncrypted] and [subEncrypted] fold a known plaintext into an existing ciphertext in one call.
+Recovering `v` from `g^v` is a discrete-log search, so the lifted form only works for values small enough to search;
+the caller is responsible for keeping every encrypted value inside that bound.
+* ***General point form*** ([encryptPoint], [assertDecryptsToPoint]): an arbitrary Jubjub point is encrypted as is.
+There is no bound to respect, at the cost of decryption recovering only a point, never a scalar.
+
+[rerandomize] produces a fresh ciphertext of the same plaintext under the same key
+(it homomorphically adds an encryption of zero), so a stored ciphertext can be made unlinkable to its previous form
+without changing what it encrypts.
+[encryptZero] returns the fixed pair of identity points `(O, O)`, a convenient initial value for a fresh slot.
+
+
+`encryptZero()` is a constant, not a hiding encryption.
+A slot still holding it is publicly recognizable as never written.
+Credit it with fresh randomness, or rerandomize it, before treating it as private.
+
+
+### Decryption by assertion
+
+No circuit returns a plaintext: a circuit cannot run the discrete-log search that recovers `v` from `g^v`.
+Instead, the owner's wallet keeps the plaintext of every ciphertext it holds
+and supplies it to the circuit through a witness, and the circuit calls [assertDecryptsTo]
+to prove that the claimed plaintext is correct: it re-derives `pk` from the secret `ek`,
+computes `c2 - ek * c1`, and asserts that the result equals `g^claimedValue`.
+A wrong secret fails the key check; a wrong plaintext fails the decryption check.
+[assertKeyPair] performs the key check alone, which lets a caller prove ownership of a public key
+without touching any ciphertext.
+
+`claimedValue` is a `Uint<248>` so that a balance accumulated through many homomorphic additions
+never outgrows the claim. The comparison happens on the curve, so two claims that are congruent modulo
+the Jubjub subgroup order `ℓ` are indistinguishable; `Uint<248>` stays below `ℓ`, so the claim domain is sound.
+
+### Keys and randomness
+
+The secret in this scheme is a 32-byte value `ek`. Everything else is derived from it.
+
+* [secretToScalar] maps the secret to a Jubjub scalar: `degradeToTransient(H("ElGamal:secretToScalar" || ek))`,
+where the tag is the 32-byte padded string and `H` is `persistentHash`.
+`degradeToTransient` truncates the hash to 31 bytes, which guarantees a valid Jubjub scalar.
+The tag is placed first so this derivation can never coincide with a call to [expandRandomness].
+It also keeps the encryption scalar unrelated to an account identifier computed as `H(secret)`,
+so a wallet may derive both roles from common key material.
+* [derivePk] computes the public key `pk = g^secretToScalar(ek)`. The public key is safe to publish
+and is normally stored on the ledger next to the ciphertexts encrypted under it.
+* [expandRandomness] derives encryption randomness from a seed: `degradeToTransient(H(seed || tag))`.
+One secret seed per circuit invocation can therefore yield as many distinct scalars as the circuit needs,
+one per `tag`.
+
+Every encryption takes its randomness `r` as a `JubjubScalar` argument, and the module holds no record of which values have been used.
+The caller owns three obligations that no circuit can check:
+
+* `r` must be a valid Jubjub scalar (strictly less than `ℓ`), derived rather than cast from a `Field` (see below).
+[expandRandomness] and [secretToScalar] always satisfy this.
+* `r` must be secret. Anyone who learns `r` for a ciphertext computes `pk^r` and strips the mask.
+* `r` must be fresh for every ciphertext. Reusing `r` under the same `pk` repeats `c1`,
+and `c2 - c2'` then reveals the difference of the two plaintexts.
+In practice this means the witness seed must be fresh per invocation and the tag distinct per encryption within an invocation.
+
+The module does reject the two inputs that would zero the mask altogether: `pk` equal to the identity point and `r = 0`.
+Both are asserted in [encryptPoint], which every encryption path goes through.
+
+## EcdhMask
+
+The [EcdhMask API] is the direct-decrypt counterpart to ElGamal. Where exponential ElGamal lets a contract add ciphertexts
+but makes recovery a search, EcdhMask delivers a `Uint<128>` value to whoever holds the secret behind a Jubjub public key,
+with no bound on the value and no search to recover it. The construction, for a recipient key `pk = g^ek` and a fresh ephemeral scalar `e`:
+
+```
+E = g^e (ephemeral public key, published with the ciphertext)
+S = pk^e (ECDH shared secret point)
+mask = kdf(S, domain)
+ct = value + mask (one-time pad in the field)
+```
+
+[kdf] hashes the shared point and the consumer's domain tag: `degradeToTransient(H(H(S) || domain))`,
+with `H` again `persistentHash`. The consumer chooses `domain` and must use the same value on both sides.
+[EcdhMask.encrypt] returns the `Ciphertext { ephemeralPk: E, ct }`; the recipient recovers `S = E^ek` and subtracts the mask with [EcdhMask.decrypt],
+passing its secret as the scalar produced by ElGamal's [secretToScalar].
+`decrypt` is a pure circuit meant for wallets and off-chain code: reading a received value needs no proof,
+and the on-chain path only ever calls `encrypt`.
+
+The same Jubjub key may serve both primitives, as the confidential token does.
+The schemes share only the public key, the KDF is domain-separated and keyed by the secret point,
+and neither exposes a decryption oracle.
+
+
+The ephemeral scalar `e` is a one-time pad key. It must be unique per ciphertext AND secret.
+Reusing `e` to the same recipient repeats the mask, and subtracting the two ciphertexts recovers both values.
+A unique but predictable `e` (a counter, a public nonce) is just as fatal, because `pk` is public and anyone can recompute `pk^e`.
+Derive `e` the way you derive ElGamal randomness: from a secret, per-invocation seed, with a distinct tag per memo.
+
+
+The pad is malleable and the module adds no authentication. That is safe when the ciphertext is computed inside the same circuit
+that commits to the value elsewhere and is immutable once written, which is the intended on-chain use.
+A consumer with mutable storage or an in-transit attacker in its threat model must add its own authentication.
+As with ElGamal, the weak inputs that would zero the shared secret are rejected: `encrypt` asserts that `recipientPk` is not the identity
+and that the ephemeral point `g^e` is not the identity (which covers `e = 0` and any `e` congruent to zero modulo `ℓ`).
+`decrypt` guards the same point: it refuses an identity ephemeral rather than unmasking against that public constant.
+
+## Ecdsa
+
+The [Ecdsa API] supplies the low-s rule for secp256k1 ECDSA signatures.
+The standard library's `secp256k1EcdsaVerify` accepts both `(r, s)` and `(r, n - s)`, where `n` is the secp256k1 group order,
+so every valid signature has a second valid encoding that anyone can derive without the signing key.
+`secp256k1EcdsaVerify` leaves the low-s constraint to its caller, and this module is that constraint:
+rejecting `s > n / 2` gives each signature one encoding, which is what replay sets, nullifiers,
+and signature-derived identifiers rely on.
+
+* [isLowS] returns whether `s <= n / 2`.
+* [assertLowS] reverts with `Ecdsa: high-s signature` on a signature in the high half.
+* [secp256k1EcdsaVerifyLowS] verifies a signature and accepts only the low-s encoding.
+It reports the result instead of asserting, so a caller can fold it into a count or a threshold.
+
+## Eip712
+
+The [Eip712 API] implements the EIP-712 typed-data envelope, for verifying signatures produced by Ethereum signing infrastructure.
+A typed-data signature covers:
+
+```
+digest = keccak256(0x1901 || domainSeparator || structHash)
+```
+
+The domain separator binds the signature to one application, so a signature obtained for any other domain cannot be replayed here.
+`personal_sign` does not provide that binding, since it signs an opaque hash.
+
+* [domainSeparator] computes the separator for `(name, version, salt)`.
+EIP-712 encodes a `string` field as the keccak hash of its UTF-8 bytes and Compact has no dynamic types,
+so the consumer hashes its own fixed-length name and version literals and passes the hashes in.
+* [hashTypedData] wraps a struct hash in the envelope and returns the digest the signer actually signed.
+It reverts with `Eip712: domain separator not set` on a zero separator, which is what an uninitialized ledger cell reads as.
+
+The struct hash is `keccak256` over EVM ABI words with the operation's type hash as the first word;
+the [EvmAbi API] supplies the word encoders. The multisig presets build their approval digests this way (see the [Multisig guide]).
+
+### The domain
+
+The domain is `(name, version, salt)`, with the type string `EIP712Domain(string name,string version,bytes32 salt)`.
+The signer's domain must have exactly these fields: a type string that differs by one character yields a different separator, and every signature fails.
+`chainId` and `verifyingContract` are omitted, which EIP-712 permits since every domain field is optional.
+Neither is expressible: a Midnight contract address is 32 bytes and does not fit `verifyingContract`'s `address` type,
+and no network identifier is available in-circuit.
+
+On an EVM chain, `verifyingContract` is what makes a domain identify one deployment rather than one application.
+Without it, the consumer has to get that binding elsewhere, and the two places to put it are not equivalent:
+
+* ***As a field of the operation struct***, holding the contract's own address (`kernel.self()`).
+This is the stronger option and the one to reach for by default: a Midnight address is randomized at deployment and cannot be predicted,
+so no two instances share one, and a signature for one deployment cannot authorize another.
+* ***As `salt`***, a per-deployment value in the domain. This is weaker, because it separates domains rather than messages.
+It is worth adding when the signer gates on domains but cannot read struct fields, since that is the only level at which it is visible.
+
+
+A consumer that binds the deployment in neither place has one domain shared by every deployment of the contract,
+and nothing distinguishes them: a signature for one deployment verifies on all of them.
+
+
+## Jubjub facts a consumer must respect
+
+ElGamal and EcdhMask rely on two properties of Midnight's embedded-curve arithmetic.
+
+* ***Every point is constrained into the prime-order subgroup.*** The runtime assigns each `JubjubPoint` reaching a curve operation
+by cofactor clearing at the constraint level, so an off-curve, low-order, or mixed-order point makes the circuit unsatisfiable.
+This holds for points a caller passes as arguments or witnesses (a recipient `pk`, the message point of `encryptPoint`),
+which is why the modules perform no in-circuit membership check and why the homomorphic algebra is sound.
+The identity point is a valid subgroup member, so it is NOT caught by this guarantee; that is why the modules assert against it explicitly.
+This is a substantiated-but-unaudited assumption:
+it was verified in the source of the pinned Midnight runtime and is regression-tested by the library's runtime canary test,
+but compactc and midnight-ledger are unaudited, so re-verify it before using the modules on a newer runtime.
+* ***Scalars are typed `JubjubScalar`, not `Field`.*** Every scalar parameter of both modules (`r`, `k`, `e`, `ekScalar`) is a `JubjubScalar`.
+A `Field as JubjubScalar` cast reduces modulo `ℓ`, so casting at a call site silently accepts an out-of-range value that the typed parameter rejects.
+Derive scalars with [secretToScalar] and [expandRandomness] instead of casting; their 31-byte output is always below `ℓ`.
+Point negation uses the standard library's `ecNeg`, so no circuit needs a negative scalar.
+
+## Usage
+
+The modules are imported like any other module; prefix each with its name (`ElGamal_`, `EcdhMask_`, `Ecdsa_`, `Eip712_`)
+to avoid circuit signature clashes.
+ElGamal and EcdhMask each export a `Ciphertext` struct, which the prefix turns into `ElGamal_Ciphertext` and `EcdhMask_Ciphertext`.
+
+The witnesses below are declared by the importing contract; this library ships no TypeScript witness implementations
+(the ones in the repository are test-only), so consumers author their own.
+
+### An encrypted single-owner balance
+
+The contract stores the owner's public key and one ElGamal ciphertext on the ledger.
+The owner's wallet holds the encryption secret, the current plaintext, and a fresh randomness seed per call,
+and supplies them through witnesses.
+`credit` takes a public `value` and adds it to the ciphertext without knowing the balance;
+`debit` proves the claimed balance is correct before subtracting.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal"
+ prefix ElGamal_;
+
+export ledger _ownerPk: JubjubPoint;
+export ledger _balance: ElGamal_Ciphertext;
+
+// The owner's 32-byte encryption secret.
+witness wit_EncryptionKey(): Bytes<32>;
+// The wallet's cached plaintext for `ct`.
+witness wit_Plaintext(ct: ElGamal_Ciphertext): Uint<248>;
+// A fresh, secret 32-byte seed for every call.
+witness wit_RandomnessSeed(): Bytes<32>;
+
+export circuit register(): [] {
+ const pk = ElGamal_derivePk(wit_EncryptionKey());
+ _ownerPk = disclose(pk);
+ _balance = ElGamal_encryptZero();
+}
+
+export circuit credit(value: Uint<128>): [] {
+ const r = ElGamal_expandRandomness(wit_RandomnessSeed(), pad(32, "credit"));
+ _balance = disclose(ElGamal_addEncrypted(_balance, _ownerPk, value, r));
+}
+
+export circuit debit(value: Uint<128>): [] {
+ const ek = wit_EncryptionKey();
+ const ct = _balance;
+ const plaintext = wit_Plaintext(ct);
+ // Binds the private `ek` and `plaintext` to the public `_ownerPk` and `ct`.
+ ElGamal_assertDecryptsTo(ct, _ownerPk, ek, plaintext);
+ assert(plaintext >= value, "Balance: insufficient balance");
+
+ const r = ElGamal_expandRandomness(wit_RandomnessSeed(), pad(32, "debit"));
+ _balance = disclose(ElGamal_subEncrypted(ct, _ownerPk, value, r));
+}
+```
+
+What reaches the ledger: the public key and each new ciphertext. The `value` argument of an exported circuit is public,
+so these circuits hide the running balance, not the individual amounts;
+the confidential token layers escrow and memos on top of this pattern to hide amounts as well.
+The secret, the plaintext, and the seed never leave the witness, and `assertDecryptsTo` is what makes a dishonest plaintext unprovable.
+
+### Delivering a value with a memo
+
+A sender who credits a recipient's ciphertext has no way to tell the recipient how much arrived, since the recipient would
+otherwise face a discrete-log search over its own balance. An EcdhMask memo solves that: the circuit encrypts the amount
+to the recipient's public key and appends it to a per-recipient list the recipient's wallet reads and decrypts off chain.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/ElGamal"
+ prefix ElGamal_;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/EcdhMask"
+ prefix EcdhMask_;
+
+export ledger _recipientPks: Map, JubjubPoint>;
+export ledger _memos: Map, List>;
+
+witness wit_RandomnessSeed(): Bytes<32>;
+
+export circuit notify(recipient: Bytes<32>, value: Uint<128>): [] {
+ const pk = _recipientPks.lookup(disclose(recipient));
+ // Secret, fresh per call, distinct tag per memo within a call.
+ const e = ElGamal_expandRandomness(wit_RandomnessSeed(), pad(32, "memo"));
+ const memo = EcdhMask_encrypt(pk, value, e, pad(32, "MyApp:memo:v1"));
+
+ if (!_memos.member(disclose(recipient))) {
+ _memos.insert(disclose(recipient), default>);
+ }
+ _memos.lookup(disclose(recipient)).pushFront(disclose(memo));
+}
+```
+
+The recipient's wallet recovers the amount with the pure circuit
+`EcdhMask_decrypt(memo, ElGamal_secretToScalar(ek), pad(32, "MyApp:memo:v1"))`, using the same domain tag.
+Only the memo (the ephemeral point and the masked field element) reaches the ledger.
+
+### Verifying an EVM-signed approval
+
+A contract that accepts approvals from an Ethereum key rebuilds the digest the signer signed and checks the signature against it.
+The struct hash covers the type hash, the contract's own address, a nonce, and the operation's fields, each as an EVM ABI word.
+The domain separator is computed once in the constructor and sealed.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/Ecdsa"
+ prefix Ecdsa_;
+import "./node_modules/@openzeppelin/compact-contracts/crypto/Eip712"
+ prefix Eip712_;
+import "./node_modules/@openzeppelin/compact-contracts/utils/EvmAbi"
+ prefix EvmAbi_;
+
+export sealed ledger _domainSeparator: Bytes<32>;
+export sealed ledger _approver: Secp256k1Point;
+export ledger _nonce: Counter;
+export ledger _limit: Uint<128>;
+export ledger _enabled: Boolean;
+
+constructor(approver: Secp256k1Point, salt: Bytes<32>) {
+ _approver = disclose(approver);
+ _domainSeparator = Eip712_domainSeparator(
+ keccak256>(pad(5, "MyApp")),
+ keccak256>(pad(1, "1")),
+ disclose(salt)
+ );
+}
+
+export circuit setLimit(
+ limit: Uint<128>,
+ enabled: Boolean,
+ sig: Secp256k1EcdsaSignature
+): [] {
+ const nonce = _nonce;
+ _nonce.increment(1);
+
+ // The type string must match the signer's byte for byte.
+ // A precomputed literal word saves one keccak permutation per call.
+ const typeHash = keccak256>(
+ pad(74, "SetLimit(bytes32 contractAddress,uint256 nonce,uint256 limit,bool enabled)")
+ );
+ const structHash = keccak256>>([
+ typeHash,
+ kernel.self().bytes,
+ EvmAbi_uint64Word(nonce),
+ EvmAbi_uint128Word(limit),
+ EvmAbi_boolWord(enabled)
+ ]);
+ const digest = Eip712_hashTypedData(_domainSeparator, structHash);
+
+ assert(
+ Ecdsa_secp256k1EcdsaVerifyLowS(digest, sig, _approver),
+ "MyApp: invalid approval"
+ );
+ _limit = disclose(limit);
+ _enabled = disclose(enabled);
+}
+```
+
+The signer signs the `SetLimit` struct as typed data (`eth_signTypedData_v4`) under the domain `{ name: "MyApp", version: "1", salt }`.
+`kernel.self()` in the struct binds each signature to this deployment, and the nonce makes it single-use.
+The type string declares `uint256` fields while the encoders read the values at their own width; the resulting word is the same.
+`pad(n, s)` zero-pads a string shorter than `n`, so `n` must equal the string's byte length or the hash no longer matches the signer's.
+Compile the contract with `--feature-zkir-v3`.
diff --git a/content/contracts-compact/0.4.x/extensibility.mdx b/content/contracts-compact/0.4.x/extensibility.mdx
new file mode 100644
index 00000000..f030f54e
--- /dev/null
+++ b/content/contracts-compact/0.4.x/extensibility.mdx
@@ -0,0 +1,175 @@
+---
+title: Extensibility
+---
+
+## The Module/Contract Pattern
+
+We use the term **modular composition by delegation** to describe the practice of having contracts call into module-defined circuits to implement behavior.
+Rather than inheriting or overriding functionality,
+a contract delegates responsibility to the module by explicitly invoking its exported circuits.
+
+The idea is that there are two types of compact files: modules and contracts.
+To minimize risk, boilerplate, and avoid naming clashes, we follow these rules:
+
+### Modules
+
+Modules expose functionality through three circuit types:
+
+1. `internal`: private helpers → used to break up logic within the module.
+2. `public`: composable building blocks → intended for contracts to use in complex flows (`_mint`, `_burn`).
+3. `external`: standalone circuits → safe to expose as-is (`transfer`, `approve`).
+
+Modules must:
+
+* Export only `public` and `external` circuits.
+* Prefix `public` circuits with `_` (e.g., `FungibleToken._mint`).
+* Avoid `_` prefix for `external` circuits (e.g., `FungibleToken.transfer`).
+* Avoid defining or calling constructors or `initialize()` directly.
+* Optionally define an `initialize()` circuit for internal setup—but execution must be delegated to the contract.
+
+
+Compact files must contain only one top-level module and all logic must be defined **inside** the module declaration.
+
+
+### Contracts
+
+Contracts compose behavior by explicitly invoking the relevant circuits from imported modules.
+Therefore, contracts:
+
+* Can import from modules.
+* Should add prefix to imports (`import "FungibleToken" prefix FungibleToken_;`).
+* Should re-expose external module circuits through wrapper circuits to control naming and layering.
+Avoid raw re-exports to prevent name clashes.
+* Should implement constructor that calls `initialize` from imported modules.
+* Must not call initializers outside of the constructor.
+
+This pattern balances modularity with local control, avoids tight coupling, and works within Compact’s language constraints.
+As Compact matures, this pattern will likely evolve as well.
+
+### Example contract implementing modules
+
+```ts
+// FungibleTokenMintablePausableOwnableContract
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ _name: Opaque<"string">,
+ _symbol: Opaque<"string">,
+ _decimals: Uint<8>,
+ _owner: Either, ContractAddress>
+) {
+ FungibleToken_initialize(_name, _symbol, _decimals);
+ Ownable_initialize(_owner);
+}
+
+/** IFungibleTokenMetadata */
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+/** IFungibleToken */
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(
+ account: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
+export circuit transfer(
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transfer(to, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(
+ spender: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_approve(spender, value);
+}
+
+/** IMintable */
+
+export circuit mint(
+ account: Either, ContractAddress>,
+ value: Uint<128>
+): [] {
+ Pausable_assertNotPaused();
+ Ownable_assertOnlyOwner();
+ FungibleToken__mint(account, value);
+}
+
+/** IPausable */
+
+export circuit isPaused(): Boolean {
+ return Pausable_isPaused();
+}
+
+export circuit pause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__pause();
+}
+
+export circuit unpause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__unpause();
+}
+
+/** IOwnable */
+
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+export circuit transferOwnership(
+ newOwner: Either, ContractAddress>
+): [] {
+ Ownable_transferOwnership(newOwner);
+}
+
+export circuit renounceOwnership(): [] {
+ Ownable_renounceOwnership();
+}
+
+```
diff --git a/content/contracts-compact/0.4.x/fungible-token.mdx b/content/contracts-compact/0.4.x/fungible-token.mdx
new file mode 100644
index 00000000..93324a76
--- /dev/null
+++ b/content/contracts-compact/0.4.x/fungible-token.mdx
@@ -0,0 +1,205 @@
+---
+title: FungibleToken
+---
+
+{/* links */}
+[fungible tokens]: /contracts/5.x/tokens#different-kinds-of-tokens
+[EIP-20]: https://eips.ethereum.org/EIPS/eip-20
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[_mint]: api/fungible-token#FungibleToken-_mint
+[transfer]: api/fungible-token#FungibleToken-transfer
+[approve]: api/fungible-token#FungibleToken-approve
+[transferFrom]: api/fungible-token#FungibleToken-transferFrom
+[wit_FungibleTokenSK]: api/fungible-token#FungibleToken-wit_FungibleTokenSK
+[computeAccountId]: api/utils#Utils-computeAccountId
+
+FungibleToken is a specification for [fungible tokens],
+a type of token where all the units are exactly equal to each other.
+This module is an approximation of [EIP-20] written in the Compact programming language for the Midnight network.
+
+## ERC20 Compatibility
+
+Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard.
+Some features and behaviors are either not possible, not possible yet,
+or changed because of the vastly different tech stack and Compact language constraints.
+
+***Notable changes***
+
+* ***`Uint<128>` as value type*** - Since 256-bit unsigned integers are not supported,
+the library uses the Compact type `Uint<128>`.
+
+***Features and specifications NOT supported***
+
+* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future.
+* ***Uint256 type*** - There's ongoing research on ways to support uint256 in the future.
+* ***Interface*** - Compact currently does not have a way to define a contract interface.
+This library offers modules of contracts with free floating circuits;
+nevertheless, there are no means of enforcing that all circuits are provided.
+
+## Contract-to-contract calls
+
+Contract-to-contract calls are currently not supported in the Compact language.
+Due to this limitation, the current iteration of FungibleToken disallows transfers and mints to the `ContractAddress` type.
+Transferring tokens to a contract may result in those tokens being locked forever.
+The FungibleToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts.
+
+
+The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning
+`transfer`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type.
+
+
+## Identity Model
+
+FungibleToken uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it through the [wit_FungibleTokenSK] witness. The module computes the caller's account identifier
+as `persistentHash(secretKey)`, a commitment that hides the key while providing a stable, pseudonymous on-chain identity.
+That identifier is the `Bytes<32>` (left) branch of the `Either, ContractAddress>` account type
+used by [transfer], [approve], and [transferFrom] to locate the caller's balance and allowances.
+
+What is public on the ledger:
+
+* `_balances`, keyed by account identifier (or contract address), and `_totalSupply`.
+* `_allowances`, keyed by owner and then spender.
+* `_name`, `_symbol`, and `_decimals`.
+
+What the caller holds privately:
+
+* The 32-byte `secretKey`. It never reaches the ledger; only its hash does.
+
+A transfer discloses the caller's account identifier, the recipient, and the amount, because the balance map is updated with those values.
+An approval discloses the owner's identifier, the spender, and the allowance.
+
+Because the identifier is `H(secretKey)` with no per-deployment salt, the same key produces the same identity in every contract.
+Users who want cross-contract unlinkability can use a different secret key per contract at the wallet layer.
+
+To derive an account identifier off-chain (for example, to pass as the `recipient` of a mint), use [computeAccountId] from the Utils module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+The TypeScript witness implementation lives with the DApp, not with the library.
+The witnesses in the `compact-contracts` repository are test-only and are not published;
+consumers author their own `wit_FungibleTokenSK` that returns the caller's 32-byte secret key.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of the tokens held under that identity,
+ and key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+## Usage
+
+Import the FungibleToken module into the implementing contract.
+It's recommended to prefix the module with `FungibleToken_` to avoid circuit signature clashes.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+}
+
+```
+
+Next, expose the circuits that users may call in the contract.
+
+This library enables extensibility by following the rules of the [Module/Contract Pattern].
+Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts.
+Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens.
+
+```typescript
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+(...)
+```
+
+The following example is a simple token contract with a fixed supply that's minted to the passed recipient upon construction.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+ recipient: Either, ContractAddress>,
+ fixedSupply: Uint<128>,
+) {
+ FungibleToken_initialize(name, symbol, decimals);
+ FungibleToken__mint(recipient, fixedSupply);
+}
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
+export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean {
+ return FungibleToken_transfer(to, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>,
+): Boolean {
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
+ return FungibleToken_approve(spender, value);
+}
+
+```
diff --git a/content/contracts-compact/0.4.x/index.mdx b/content/contracts-compact/0.4.x/index.mdx
new file mode 100644
index 00000000..056962ea
--- /dev/null
+++ b/content/contracts-compact/0.4.x/index.mdx
@@ -0,0 +1,192 @@
+---
+title: Contracts for Compact
+---
+
+{/* links */}
+[Midnight]: https://midnight.network/
+[nvm]: https://github.com/nvm-sh/nvm
+[yarn]: https://yarnpkg.com/getting-started/install
+[compact-dev-tools]: https://docs.midnight.network/blog/compact-developer-tools
+[Known issues]: https://github.com/OpenZeppelin/compact-contracts/blob/v0.4.0/CHANGELOG.md#known-issues
+
+**A library for secure smart contract development** written in Compact for [Midnight].
+This library consists of modules to build custom smart contracts.
+Version 0.4.0 targets Midnight Ledger v9 and Compact compiler 0.34.0.
+
+
+Pre-release versions (`-alpha`, `-rc`) contain experimental, unaudited code.
+Expect rapid iteration. **Use at your own risk.**
+For production, use a stable release (no `-alpha` or `-rc` suffix) on the Midnight ledger it targets.
+See the [release and audit table](https://github.com/OpenZeppelin/compact-contracts/blob/v0.4.0/README.md#security) in the repository.
+
+
+## Usage
+
+Make sure you have [nvm] and [yarn] installed on your machine.
+
+Follow Midnight's [Compact Developer Tools][compact-dev-tools] installation guide and confirm that `compact` is in the `PATH` env variable.
+
+```bash
+$ compact compile --version
+
+Compactc version: {{compact_compiler_version}}
+{{compact_compiler_version}}
+```
+
+### Installation
+
+Create a directory for your project.
+
+```bash
+mkdir my-project
+cd my-project
+```
+
+Install the library.
+
+```bash
+yarn add @openzeppelin/compact-contracts
+```
+
+### Write a custom contract using library modules
+
+In the root of `my-project`, create a custom contract using OpenZeppelin Compact modules.
+Import the modules through `./node_modules/@openzeppelin/compact-contracts/...`.
+
+```typescript
+// MyContract.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ _name: Opaque<"string">,
+ _symbol: Opaque<"string">,
+ _decimals: Uint<8>,
+ _recipient: Either, ContractAddress>,
+ _amount: Uint<128>,
+ _initOwner: Either, ContractAddress>,
+) {
+ Ownable_initialize(_initOwner);
+ FungibleToken_initialize(_name, _symbol, _decimals);
+ FungibleToken__mint(_recipient, _amount);
+}
+
+/** IFungibleToken */
+
+export circuit name(): Opaque<"string"> {
+ return FungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return FungibleToken_symbol();
+}
+
+export circuit decimals(): Uint<8> {
+ return FungibleToken_decimals();
+}
+
+export circuit totalSupply(): Uint<128> {
+ return FungibleToken_totalSupply();
+}
+
+export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
+ return FungibleToken_balanceOf(account);
+}
+
+export circuit allowance(
+ owner: Either, ContractAddress>,
+ spender: Either, ContractAddress>
+): Uint<128> {
+ return FungibleToken_allowance(owner, spender);
+}
+
+export circuit transfer(
+ to: Either, ContractAddress>,
+ value: Uint<128>,
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transfer(to, value);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ value: Uint<128>
+): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_transferFrom(fromAddress, to, value);
+}
+
+export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
+ Pausable_assertNotPaused();
+ return FungibleToken_approve(spender, value);
+}
+
+/** IOwnable */
+
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ return Ownable_transferOwnership(newOwner);
+}
+
+export circuit renounceOwnership(): [] {
+ return Ownable_renounceOwnership();
+}
+
+/** IPausable */
+
+export circuit pause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__pause();
+}
+
+export circuit unpause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__unpause();
+}
+```
+
+### Compile the contract
+
+In the project root, compile the contract.
+
+```bash
+% compact compile MyContract.compact artifacts/MyContract
+Compiling 14 circuits:
+ circuit "allowance" (k=11, rows=1352)
+ circuit "approve" (k=13, rows=3080)
+ circuit "balanceOf" (k=10, rows=673)
+ circuit "decimals" (k=6, rows=28)
+ circuit "name" (k=6, rows=28)
+ circuit "owner" (k=7, rows=76)
+ circuit "pause" (k=13, rows=2365)
+ circuit "renounceOwnership" (k=13, rows=2364)
+ circuit "symbol" (k=6, rows=28)
+ circuit "totalSupply" (k=6, rows=28)
+ circuit "transfer" (k=13, rows=3990)
+ circuit "transferFrom" (k=13, rows=4977)
+ circuit "transferOwnership" (k=13, rows=2959)
+ circuit "unpause" (k=13, rows=2362)
+Overall progress [====================] 14/14
+```
+
+**ZKIR v3.** The library targets ZKIR v2, the compiler 0.34.0 default.
+Pass `--feature-zkir-v3` only when the contract reaches a module that needs it:
+the secp256k1-based `crypto/Ecdsa` and `multisig/Secp256k1EcdsaMultisig`,
+or the `keccak256`-based `crypto/Eip712`.
+`multisig/presets/ShieldedMultiSigV2`, `multisig/presets/NativeShieldedTokenIssuer`
+and the `multisig/examples/` contracts reach both.
+`utils/EvmAbi` needs no `keccak256` of its own and compiles on either backend.
+Compiling the other modules under v3 currently hits upstream blockers,
+listed under [Known issues] in the changelog.
diff --git a/content/contracts-compact/0.4.x/multi-token.mdx b/content/contracts-compact/0.4.x/multi-token.mdx
new file mode 100644
index 00000000..4a567e74
--- /dev/null
+++ b/content/contracts-compact/0.4.x/multi-token.mdx
@@ -0,0 +1,213 @@
+---
+title: MultiToken
+---
+
+{/* links */}
+[EIP-1155]: https://eips.ethereum.org/EIPS/eip-1155
+[ERC165]: https://eips.ethereum.org/EIPS/eip-165
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[_mint]: ./api/multi-token#MultiToken-_mint
+[setApprovalForAll]: ./api/multi-token#MultiToken-setApprovalForAll
+[transferFrom]: ./api/multi-token#MultiToken-transferFrom
+[wit_MultiTokenSK]: ./api/multi-token#MultiToken-wit_MultiTokenSK
+[computeAccountId]: ./api/utils#Utils-computeAccountId
+
+MultiToken is a specification for contracts that manage multiple token types.
+This module is an approximation of [EIP-1155] written in the Compact programming language for the Midnight network.
+
+## ERC1155 Compatibility
+
+Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard.
+Some features and behaviors are either not possible, not possible yet,
+or changed because of the vastly different tech stack and Compact language constraints.
+
+**Notable changes**
+
+* **`Uint<128>` as value and id type** - Since 256-bit unsigned integers are not supported,
+the library uses the Compact type `Uint<128>`.
+
+**Features and specifications NOT supported**
+
+* **Events** - Midnight does not currently support events, but this is planned on being supported in the future.
+* **Uint256 type** - There's ongoing research on ways to support uint256 in the future.
+* **Interface** - Compact currently does not have a way to define a contract interface.
+This library offers modules of contracts with free floating circuits;
+nevertheless, there's no means of enforcing that all circuits are provided.
+* **Batch mint, burn, transfer** - Without support for dynamic arrays,
+batching transfers is difficult to do without a hacky solution.
+For instance, we could change the `to` and `fromAddress` parameters to be vectors.
+This would change the signature and would be both difficult to use and easy to misuse.
+* **Querying batched balances** - This can be somewhat supported.
+The issue, without dynamic arrays, is that the module circuit must use `Vector` for accounts and ids;
+therefore, the implementing contract must explicitly define the number of balances to query in the circuit i.e.
+
+```ts
+balanceOfBatch_10(
+ accounts: Vector<10, Either, ContractAddress>>,
+ ids: Vector<10, Uint<128>>
+): Vector<10, Uint<128>>
+```
+
+Since this module does not offer mint or transfer batching, balance batching is also not included at this time.
+
+* **Introspection** - Compact currently cannot support contract-to-contract queries for introspection.
+[ERC165] (or an equivalent thereof) is NOT included in the contract.
+* **Safe transfers** - The lack of an introspection mechanism means safe transfers of any kind can not be supported.
+
+## Contract-to-contract calls
+
+Contract-to-contract calls are currently not supported in the Compact language.
+Due to this limitation, the current iteration of MultiToken disallows transfers and mints to the `ContractAddress` type.
+Transferring tokens to a contract may result in those tokens being locked forever.
+The MultiToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts.
+
+
+The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning
+`transferFrom`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type.
+
+
+## Identity Model
+
+MultiToken uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it through the [wit_MultiTokenSK] witness. The module computes the caller's account identifier
+as `persistentHash(secretKey)`, a commitment that hides the key while providing a stable, pseudonymous on-chain identity.
+That identifier is the `Bytes<32>` (left) branch of the `Either, ContractAddress>` account type
+and is what [setApprovalForAll] and [transferFrom] check ownership and operator rights against.
+
+What is public on the ledger:
+
+* `_balances`, keyed by token id and then by account identifier (or contract address).
+* `_operatorApprovals`, keyed by owner and then operator.
+* `_uri`.
+
+What the caller holds privately:
+
+* The 32-byte `secretKey`. It never reaches the ledger; only its hash does.
+
+A transfer discloses the sender, the recipient, the token id, and the amount, because the balance map is updated with those values.
+An operator approval discloses the caller's identifier, the operator, and the flag.
+
+Because the identifier is `H(secretKey)` with no per-deployment salt, the same key produces the same identity in every contract.
+Users who want cross-contract unlinkability can use a different secret key per contract at the wallet layer.
+
+To derive an account identifier off-chain (for example, to pass as the `recipient` of a mint), use [computeAccountId] from the Utils module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+The TypeScript witness implementation lives with the DApp, not with the library.
+The witnesses in the `compact-contracts` repository are test-only and are not published;
+consumers author their own `wit_MultiTokenSK` that returns the caller's 32-byte secret key.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of the tokens held under that identity,
+ and key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+## Usage
+
+Import the MultiToken module into the implementing contract.
+It's recommended to prefix the module with `MultiToken_` to avoid circuit signature clashes.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken"
+ prefix MultiToken_;
+
+constructor(
+ uri: Opaque<"string">,
+) {
+ MultiToken_initialize(uri);
+}
+
+```
+
+Next, expose the circuits that users may call in the contract.
+
+This library enables extensibility by following the rules of the [Module/Contract Pattern].
+Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts.
+Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens.
+
+```typescript
+export circuit uri(id: Uint<128>): Opaque<"string"> {
+ return MultiToken_uri(id);
+}
+
+export circuit balanceOf(account: Either, ContractAddress>, id: Uint<128>): Uint<128> {
+ return MultiToken_balanceOf(account, id);
+}
+
+(...)
+```
+
+The following example is a simple multi-token contract that creates both a fixed-supply fungible token and an NFT using the same contract.
+
+```typescript
+// MultiTokenTwoTokenTypes.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/MultiToken"
+ prefix MultiToken_;
+
+constructor(
+ _uri: Opaque<"string">,
+ recipient: Either, ContractAddress>,
+ fungibleFixedSupply: Uint<128>,
+) {
+ // `initialize` sets the URI (base) for all tokens minted from this contract
+ MultiToken_initialize(_uri);
+
+ // Token id `123` is a fungible token (with a fixed supply of `fungibleFixedSupply`)
+ const fungibleTokenId = 123;
+ MultiToken__mint(recipient, fungibleTokenId, fungibleFixedSupply);
+
+ // Token id `987` is a non-fungible token (so the supply is only 1)
+ const nonFungibleTokenId = 987;
+ MultiToken__mint(recipient, nonFungibleTokenId, 1);
+}
+
+export circuit uri(id: Uint<128>): Opaque<"string"> {
+ return MultiToken_uri(id);
+}
+
+export circuit balanceOf(account: Either, ContractAddress>, id: Uint<128>): Uint<128> {
+ return MultiToken_balanceOf(account, id);
+}
+
+export circuit setApprovalForAll(
+ operator: Either, ContractAddress>,
+ approved: Boolean
+): [] {
+ return MultiToken_setApprovalForAll(operator, approved);
+}
+
+export circuit isApprovedForAll(
+ account: Either, ContractAddress>,
+ operator: Either, ContractAddress>
+): Boolean {
+ return MultiToken_isApprovedForAll(account, operator);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ id: Uint<128>,
+ value: Uint<128>,
+): [] {
+ return MultiToken_transferFrom(fromAddress, to, id, value);
+}
+
+```
diff --git a/content/contracts-compact/0.4.x/multisig.mdx b/content/contracts-compact/0.4.x/multisig.mdx
new file mode 100644
index 00000000..b14d8d84
--- /dev/null
+++ b/content/contracts-compact/0.4.x/multisig.mdx
@@ -0,0 +1,651 @@
+---
+title: Multisig
+---
+
+{/* links */}
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[Ownable]: ./ownable
+[NativeShieldedToken]: ./native-shielded-token
+[Signer API]: api/multisig#Signer
+[assertSigner]: api/multisig#Signer-assertSigner
+[assertThresholdMet]: api/multisig#Signer-assertThresholdMet
+[isSigner]: api/multisig#Signer-isSigner
+[_addSigner]: api/multisig#Signer-_addSigner
+[_removeSigner]: api/multisig#Signer-_removeSigner
+[_changeThreshold]: api/multisig#Signer-_changeThreshold
+[_setThreshold]: api/multisig#Signer-_setThreshold
+[Secp256k1EcdsaMultisig]: api/multisig#Secp256k1EcdsaMultisig
+[Secp256k1EcdsaMultisig API]: api/multisig#Secp256k1EcdsaMultisig
+[Secp256k1EcdsaMultisig initialize]: api/multisig#Secp256k1EcdsaMultisig-initialize
+[assertApprovals]: api/multisig#Secp256k1EcdsaMultisig-assertApprovals
+[calculateSignerId]: api/multisig#Secp256k1EcdsaMultisig-calculateSignerId
+[ProposalManager API]: api/multisig#ProposalManager
+[_createProposal]: api/multisig#ProposalManager-_createProposal
+[_cancelProposal]: api/multisig#ProposalManager-_cancelProposal
+[_markExecuted]: api/multisig#ProposalManager-_markExecuted
+[assertProposalActive]: api/multisig#ProposalManager-assertProposalActive
+[getProposalStatus]: api/multisig#ProposalManager-getProposalStatus
+[toShieldedRecipient]: api/multisig#ProposalManager-toShieldedRecipient
+[toUnshieldedRecipient]: api/multisig#ProposalManager-toUnshieldedRecipient
+[ShieldedTreasury API]: api/multisig#ShieldedTreasury
+[ShieldedTreasury _deposit]: api/multisig#ShieldedTreasury-_deposit
+[ShieldedTreasury _send]: api/multisig#ShieldedTreasury-_send
+[ShieldedTreasuryStateless API]: api/multisig#ShieldedTreasuryStateless
+[ShieldedTreasuryStateless _send]: api/multisig#ShieldedTreasuryStateless-_send
+[UnshieldedTreasury API]: api/multisig#UnshieldedTreasury
+[UnshieldedTreasury _deposit]: api/multisig#UnshieldedTreasury-_deposit
+[UnshieldedTreasury _send]: api/multisig#UnshieldedTreasury-_send
+[ForwarderShielded API]: api/multisig#ForwarderShielded
+[ForwarderShielded _deposit]: api/multisig#ForwarderShielded-_deposit
+[ForwarderUnshielded API]: api/multisig#ForwarderUnshielded
+[ForwarderPrivate API]: api/multisig#ForwarderPrivate
+[_drain]: api/multisig#ForwarderPrivate-_drain
+[_calculateParentCommitment]: api/multisig#ForwarderPrivate-_calculateParentCommitment
+[ShieldedMultiSigV2 API]: api/multisig#ShieldedMultiSigV2
+[execute]: api/multisig#ShieldedMultiSigV2-execute
+[NativeShieldedTokenIssuer API]: api/multisig#NativeShieldedTokenIssuer
+[mint]: api/multisig#NativeShieldedTokenIssuer-mint
+[mintToSelf]: api/multisig#NativeShieldedTokenIssuer-mintToSelf
+[burn]: api/multisig#NativeShieldedTokenIssuer-burn
+[burnFromSelf]: api/multisig#NativeShieldedTokenIssuer-burnFromSelf
+[Eip712]: api/crypto#Eip712
+[Ecdsa]: api/crypto#Ecdsa
+[EvmAbi]: api/utils#EvmAbi
+[presets directory]: https://github.com/OpenZeppelin/compact-contracts/tree/v0.4.0/contracts/src/multisig/presets
+[examples directory]: https://github.com/OpenZeppelin/compact-contracts/tree/v0.4.0/contracts/src/multisig/examples
+
+The multisig directory provides building blocks for M-of-N governance over funds held by a contract:
+a `Signer` registry with an approval threshold, a `Secp256k1EcdsaMultisig` signer set that verifies ECDSA approvals,
+a `ProposalManager` that tracks transfer proposals,
+three treasuries that hold and move shielded or unshielded tokens,
+and three forwarders that pass every deposit on to a parent.
+Apart from `Secp256k1EcdsaMultisig`, which wraps `Signer`, the modules are independent of one another.
+A contract composes the ones it needs, following the [Module/Contract Pattern],
+and supplies its own authorization policy on top.
+The package also ships two presets, modules that compose these into a ready-made ECDSA policy; see [Presets](#presets).
+
+None of the multisig modules declares a witness.
+Every input is a circuit parameter, so what reaches the public ledger is decided by the `disclose` calls inside each circuit.
+The sections below name what each module publishes.
+
+
+ `Secp256k1EcdsaMultisig`, the presets, and the contracts in the [examples directory] require ZKIR v3 (`--feature-zkir-v3`):
+ the `Secp256k1` types and `keccak256` are unavailable on the v2 backend.
+ The library's `compile:multisig` script passes the flag for the whole directory, but the other multisig modules do not need it.
+
+
+## Signer
+
+The [Signer API] keeps the registry of who may approve and how many approvals are required.
+It is generic over the signer identity type `T`, which the consuming contract chooses at import time:
+`Bytes<32>` for a commitment such as the hash of a public key,
+`JubjubPoint` for an aggregated key,
+or `Either` for a caller identity.
+The module never resolves who the caller is.
+The contract passes an identity in, and Signer checks it against the registry.
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Signer">
+ prefix Signer_;
+```
+
+### Public ledger state
+
+Everything the module keeps is public:
+`_signers` (the full set of registered identities), `_signerCount`, and `_threshold`.
+[isSigner], [_addSigner], and [_removeSigner] disclose the identity they are given, since it is the key of the set lookup or insertion,
+and [_setThreshold] discloses the new threshold.
+The signer count and threshold are `Uint<8>`, so a registry holds at most 255 signers.
+
+### Who may call what
+
+* The module has no `initialize`.
+ The consuming contract registers signers with [_addSigner] and sets the threshold with [_changeThreshold] or [_setThreshold],
+ either from the constructor or behind its own authorization policy.
+* The guards [assertSigner] and [assertThresholdMet] and the views `getSignerCount`, `getThreshold`, and [isSigner] are open to anyone
+ and carry no "configured" flag.
+ Safety comes from the state rather than from a flag:
+ an empty registry makes [isSigner] false for every input, so [assertSigner] rejects every caller,
+ and a zero threshold makes [assertThresholdMet] reject before it compares any approval count.
+ A contract that needs a "configured" signal beyond that tracks one itself.
+* [_addSigner], [_removeSigner], [_changeThreshold], and [_setThreshold] have no access control.
+ The consuming contract must gate them behind its own authorization policy.
+ Left ungated, the first caller registers their own signer set and takes control of the contract.
+
+[assertThresholdMet] compares the supplied count against the threshold and nothing else; it never consults the registry.
+The contract's own approval accounting must be gated by [assertSigner] and must count distinct signers.
+The count is intentionally not bounded by `_signerCount`,
+so approvals collected before a signer was removed still count toward a threshold the remaining signers satisfy.
+
+[_changeThreshold] refuses a threshold above the current signer count.
+[_setThreshold] skips that check so a custom setup flow can set the threshold before any signer is registered.
+A contract must not gate operations on [assertThresholdMet] while such a setup is incomplete.
+
+Reconfigurations that take several transactions (removing a signer, then lowering the threshold) pass through intermediate states
+where the module's checks hold but the contract's intended configuration is incomplete.
+Perform them atomically in a single circuit, or keep a configuration nonce that invalidates proposals created under a stale signer set.
+
+### Usage
+
+The example registers three `Bytes<32>` signer identities at deployment and lets an owner manage the registry afterwards.
+The [Ownable] module authenticates the owner through its `wit_OwnableSK` witness,
+which the consuming contract supplies in its own TypeScript
+(the witness implementations in the library repository are test-only and are not published).
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Signer">
+ prefix Signer_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(
+ initialOwner: Either, ContractAddress>,
+ signers: Vector<3, Bytes<32>>,
+ thresh: Uint<8>
+) {
+ Ownable_initialize(initialOwner);
+ for (const signer of signers) {
+ Signer__addSigner(signer);
+ }
+ Signer__changeThreshold(thresh);
+}
+
+export circuit addSigner(signer: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Signer__addSigner(signer);
+}
+
+export circuit removeSigner(signer: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Signer__removeSigner(signer);
+}
+
+export circuit changeThreshold(newThreshold: Uint<8>): [] {
+ Ownable_assertOnlyOwner();
+ Signer__changeThreshold(newThreshold);
+}
+
+export circuit isSigner(account: Bytes<32>): Boolean {
+ return Signer_isSigner(account);
+}
+
+export circuit getThreshold(): Uint<8> {
+ return Signer_getThreshold();
+}
+```
+
+## Secp256k1EcdsaMultisig
+
+The [Secp256k1EcdsaMultisig API] is a `t`-of-`n` signer set over secp256k1 ECDSA, built on a `Signer>` registry.
+`n` signer commitments are registered at deployment, and a call passes when `t` distinct registered signers have signed its digest.
+The threshold `t` is the module generic, fixed at import, and [Secp256k1EcdsaMultisig initialize] fixes `n`.
+No circuit takes a threshold argument, and `t = 0` does not compile.
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/multisig/Secp256k1EcdsaMultisig"<2>
+ prefix Secp256k1EcdsaMultisig_;
+```
+
+A signer commitment is [calculateSignerId] over the public key's affine coordinates, a per-deployment salt, and the domain `Secp256k1EcdsaMultisig:signer`.
+It hashes the Compact encoding of a struct, not a byte concatenation,
+so derive commitments with the pure circuit rather than reimplementing it: a wrong derivation registers commitments no key can satisfy.
+
+[assertApprovals] takes the digest, `t` public keys, and `t` signatures in matching positions.
+It checks the threshold first, so an uninitialized registry fails before any signature work,
+then rejects a repeated signer, then checks each commitment against the registry and each signature through the low-s verifier in [Ecdsa].
+The high-s encoding of an otherwise valid signature is rejected.
+The module verifies whatever digest it is handed: replay, domain, and instance binding belong in the consumer's digest (see [Signing approvals](#signing-approvals)).
+
+### Public ledger state
+
+The registry fields `_signers`, `_signerCount`, and `_threshold` are re-exported unprefixed, and `_instanceSalt` is sealed.
+[assertApprovals] discloses the `t` approving commitments, so which signers approved each call is public.
+Public keys and signatures are verified in-circuit and never disclosed.
+The salt is public, so an observer holding a candidate key can test membership; one without cannot correlate a signer across deployments.
+
+### Who may call what
+
+* [Secp256k1EcdsaMultisig initialize] writes the sealed salt, so it MUST only be called from the constructor.
+ It rejects a second call while signers are registered.
+* [assertApprovals], [calculateSignerId], and the views are open.
+* No signer or threshold change is exported, so the signer set is final once deployed.
+
+Import the module at exactly one width per contract.
+Two widths instantiate two modules over one shared `Signer>` registry, so the second `initialize` trips the once-only guard.
+Do not import `Signer` alongside it either: a direct import instantiates a separate registry that [assertApprovals] never reads.
+
+
+ Losing more than `n - t` keys freezes every circuit gated by [assertApprovals].
+ There is no rotation or recovery path.
+
+
+## ProposalManager
+
+The [ProposalManager API] tracks the lifecycle of transfer proposals independently of the token kind.
+A `Proposal` records a `Recipient`, a token `color`, an `amount`, an `expiry`, and a stored `ProposalStatus` (`Active`, `Executed`, or `Cancelled`).
+[getProposalStatus] derives the other two values and never stores them:
+`Inactive` for an id that was never created, and `Expired` for an `Active` proposal past its deadline.
+A `Recipient` pairs 32 address bytes with a `RecipientKind` tag (`ShieldedUser`, `UnshieldedUser`, or `Contract`)
+that says how those bytes are to be read.
+Build recipients with `shieldedUserRecipient`, `unshieldedUserRecipient`, and `contractRecipient`,
+and convert them at execution time with [toShieldedRecipient] (accepts `ShieldedUser` and `Contract`)
+or [toUnshieldedRecipient] (accepts `UnshieldedUser` and `Contract`).
+Because `Contract` is accepted by both, the tag alone does not decide the rail;
+a contract composing both treasuries chooses which one a proposal executes on.
+
+`expiry` is a deadline in seconds since the Unix epoch, and [_createProposal] rejects one that is not in the future.
+An `Active` proposal is executable while the block time is below it and inert from then on:
+[assertProposalActive] fails once it passes, and so do [_cancelProposal] and [_markExecuted].
+Expiry is terminal, since block time only moves forward and rebuilding the transaction cannot help.
+The module sets no maximum horizon and no execution delay; a contract that needs a bounded lifetime or a minimum wait imposes its own.
+
+### Public ledger state
+
+Proposals are public.
+`_proposals` is exported ledger state, so every proposal's recipient address, color, amount, expiry, and status is readable off-chain,
+and `_nextProposalId` reveals how many have been created.
+An off-chain reader takes `status` as stored and, when it is `Active`, compares `expiry` with the current block time.
+This holds whichever treasury the contract composes: pairing the module with `ShieldedTreasuryStateless` still publishes
+the full intent of every transfer.
+The transfer is private; the plan is not.
+
+### Who may call what
+
+[_createProposal], [_cancelProposal], and [_markExecuted] have no access control and must be gated by the contract.
+The guards `assertProposalExists` and `assertProposalActive`, the recipient helpers, and the getters are open.
+[_createProposal] increments the counter and reads it back as the map key,
+so two transactions that create proposals against the same ledger state conflict and only one of them lands.
+
+[_createProposal] validates nothing about the recipient beyond its type.
+A kind the execution rail cannot accept (an `UnshieldedUser` recipient on a shielded treasury)
+creates a proposal that stays `Active` and unexecutable until [_cancelProposal] retires it or its `expiry` lapses,
+and a zero or otherwise unspendable address is accepted, so funds sent to it on execution are unrecoverable.
+
+## Treasuries
+
+Each treasury exposes an ungated `_deposit` and `_send` pair that the contract gates with its own policy.
+They differ in what they keep on the ledger and in who handles the change of a shielded spend.
+
+### ShieldedTreasury
+
+The [ShieldedTreasury API] stores one coin per token color in `_coins`, a map from color to `QualifiedShieldedCoinInfo`,
+together with cumulative `_received` and `_sent` totals per color.
+[ShieldedTreasury _deposit] claims the incoming coin with `receiveShielded`
+and merges it into the stored coin of the same color (`mergeCoinImmediate`), or inserts it when the color is new,
+so the contract never has to choose between several UTXOs of one color at spend time.
+[ShieldedTreasury _send] rejects the contract's own address as recipient,
+since a self-addressed coin would be auto-received but never recorded in `_coins`.
+It looks the coin up by color, checks it covers the amount, and calls `sendShielded`,
+which routes any change back to the contract as a self-owned output.
+The treasury retains that change: it is written back to `_coins` for the next spend,
+and the color's entry is removed when the send leaves none.
+
+The holdings of this treasury are public.
+`_coins` is exported, so every coin it holds (nonce, color, value, and Merkle index) is readable in plaintext,
+as are the received and sent totals.
+"Shielded" refers to the token rail: individual transfers are shielded, but what the contract holds is not.
+Deposits disclose the incoming coin; sends disclose the color, recipient, and amount.
+
+
+ `_send` returns a `ShieldedSendResult` whose `sent` and `change` coins are in plaintext.
+ Consume it inside the circuit or drop it; returning it from an entry point, writing it to the ledger,
+ or passing it to another contract publishes the coin details.
+ The change coin already belongs to the treasury and is recorded in `_coins`:
+ spending it again in the same transaction double-spends the recorded coin.
+
+
+### ShieldedTreasuryStateless
+
+The [ShieldedTreasuryStateless API] keeps no ledger state at all: no coin map, no balances, no totals.
+`_deposit` calls `receiveShielded` and nothing else.
+The operator discovers the coin's Merkle index from `ZswapOutput` events through the indexer,
+builds the `QualifiedShieldedCoinInfo` off-chain, and passes it to [ShieldedTreasuryStateless _send] at spend time,
+so coin data never reaches the public ledger and there is no on-chain balance to query.
+Sufficiency is enforced by `sendShielded` rather than by the module,
+and a double spend or a coin the contract does not own is rejected by the protocol when the transaction is applied.
+
+`sendShielded` routes change back to the contract, and [ShieldedTreasuryStateless _send] returns it untouched in `result.change`
+as a live, spendable coin.
+The contract decides what to do with it: keep it off-chain and pass it back as the `coin` of a later spend,
+or route it onward to a different recipient in the same transaction with `sendImmediateShielded`.
+The only rule is that the change must never be re-spent and still treated as held.
+Writing it to the ledger would publish its nonce, color, and value.
+
+Deposits disclose the incoming coin; sends disclose the spent coin, recipient, and amount.
+"No ledger state" is not the same as "unobservable".
+A coin's nullifier is a hash of the coin and the contract's address,
+and `sendShielded` derives the nonces of the sent coin and of the change from a public hash of the spent coin's nonce.
+Whoever knows a deposited coin can therefore find the spend that consumes it, recover the amount sent,
+test candidate recipients against the sent coin's commitment, and repeat from the change for every later spend descending from it.
+
+### UnshieldedTreasury
+
+The [UnshieldedTreasury API] tracks a per-color balance in `_balances`.
+[UnshieldedTreasury _deposit] calls `receiveUnshielded` and adds to the balance;
+[UnshieldedTreasury _send] subtracts and calls `sendUnshielded`.
+The sufficiency and overflow guards read `_balances`, not the protocol balance,
+which is fixed at the start of execution and cannot see a deposit made earlier in the same transaction.
+Route every unshielded credit and debit through the module:
+a direct `receiveUnshielded` or `sendUnshielded` leaves `_balances` out of step and `getTokenBalance` misreports.
+
+Unshielded transfers are fully public: depositor, recipient, color, and amount all appear on the transcript.
+[UnshieldedTreasury _send] rejects the contract's own address, since a self-addressed send would be auto-received
+but still debited from `_balances`, and a zero amount, which the ledger refuses as a zero-value output.
+Other recipients are not validated, so an unspendable recipient permanently removes the funds.
+
+### Usage
+
+The example composes `ShieldedTreasury` with [Ownable]: anyone may fund the treasury, only the owner may spend from it.
+The send result is dropped because the change is already recorded in the treasury's coin map.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/multisig/ShieldedTreasury"
+ prefix Treasury_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(initialOwner: Either, ContractAddress>) {
+ Ownable_initialize(initialOwner);
+}
+
+export circuit deposit(coin: ShieldedCoinInfo): [] {
+ Treasury__deposit(coin);
+}
+
+export circuit withdraw(
+ recipient: Either,
+ color: Bytes<32>,
+ amount: Uint<128>
+): [] {
+ Ownable_assertOnlyOwner();
+ Treasury__send(recipient, color, amount);
+}
+
+export circuit getTokenBalance(color: Bytes<32>): Uint<128> {
+ return Treasury_getTokenBalance(color);
+}
+
+export circuit getReceivedTotal(color: Bytes<32>): Uint<128> {
+ return Treasury_getReceivedTotal(color);
+}
+
+export circuit getSentTotal(color: Bytes<32>): Uint<128> {
+ return Treasury_getSentTotal(color);
+}
+```
+
+## Forwarders
+
+A forwarder is bound to a single parent at deployment and passes every deposit on to it.
+The modules are permissionless: the recipient is fixed, so any caller may deposit without compromising the parent.
+None of them exposes a setter; a contract that needs to change the parent adds its own circuit, or deploys a new forwarder.
+The [examples directory] has one deployable contract per forwarder.
+
+### ForwarderShielded and ForwarderUnshielded
+
+The [ForwarderShielded API] and [ForwarderUnshielded API] forward atomically:
+`_deposit` claims the incoming value (`receiveShielded` or `receiveUnshielded`) and re-sends it to the parent in the same transaction
+(`sendImmediateShielded` or `sendUnshielded`).
+Each keeps two ledger fields, `_parent` and `_isInitialized`, and every circuit except `initialize` asserts initialization.
+The parent is stored as a generic `Either` so the ledger layout can later accept a contract address without a state migration,
+but `initialize` accepts only a `ZswapCoinPublicKey` (shielded, stored in the `left` arm)
+or a `UserAddress` (unshielded, stored in the `right` arm) today:
+an atomic forward can only reach a recipient that needs no in-transaction claim,
+and a third-party contract would never claim the output.
+
+`initialize` rejects only the all-zero key or address.
+The deployer must set a parent that can actually spend the forwarded coins; forwarding to a key no one controls strands every deposit.
+The shielded forwarder discloses the incoming coin; the unshielded forwarder discloses depositor, recipient, color, and amount.
+
+[ForwarderShielded _deposit] returns the forwarded coin as a `ShieldedSendResult`.
+The depositor must encrypt the forwarded output to the parent's encryption public key, obtained off-chain.
+The ledger cannot check that key against `_parent`, so a wrong or missing key leaves the coin spendable by the parent but invisible to its wallet.
+If unsure, hand the returned result to the parent so it can recover the coin.
+
+### ForwarderPrivate
+
+The [ForwarderPrivate API] hides the parent from the public.
+The ledger holds `_parentCommitment`, computed off-chain by the deployer as
+`H(pad(32, "ForwarderPrivate:commitment") || parent || opSecret)` with the pure [_calculateParentCommitment],
+plus `_isInitialized`.
+`_deposit` claims the coin and writes nothing; coins dwell at the contract address until drained.
+[_drain] takes the coin, the parent coin public key, the operational secret, and the value to send:
+it checks that the pair hashes to the stored commitment, then sends the value to the parent with `sendShielded`.
+The parent is a `ZswapCoinPublicKey` and never a `ContractAddress`:
+a coin-public-key recipient is encrypted inside the Zswap output and never appears on the public transcript,
+whereas a shielded send to a contract publishes that contract's address.
+Any change is routed back to the contract and returned untouched in `result.change`,
+to leave dwelling for a future drain or to route onward with `sendImmediateShielded`.
+
+Knowledge of the preimage is the sole authorization gate.
+Two forwarders bound to the same parent with different operational secrets produce different commitments, so the ledger does not link them.
+Deposits disclose the incoming coin; drains disclose the spent coin and the value.
+
+The commitment hides the parent from the public, not from a depositor.
+A contract-owned coin's nullifier is a hash of the coin and the contract address,
+and `sendShielded` derives the nonces of the sent coin and of the change from a public hash of the spent coin's nonce.
+Whoever knows a deposited coin can therefore find the drain that spends it, recover the drained amount,
+and test candidate parent keys against the sent coin's commitment: a depositor who can guess the parent can confirm it.
+
+
+ Losing the operational secret is permanent fund loss.
+ It is the only drain authorization, with no rotation, revocation, or recovery path:
+ every coin accumulated at the contract becomes inaccessible.
+ Back it up offline before the first deposit and treat it with the hygiene of a signing key.
+
+
+## Presets
+
+The package ships two presets under its [presets directory]: `ShieldedMultiSigV2` and `NativeShieldedTokenIssuer`.
+Each is a module that composes [Secp256k1EcdsaMultisig] with a treasury or a token and gates every state-changing circuit on `t` ECDSA approvals.
+The threshold `t` is the import generic, and the preset's `initialize` fixes the signer count `n`.
+A deployable contract imports the preset at one width, calls `initialize` from its constructor, and re-exports the circuits it needs;
+the [examples directory] holds a 2-of-3 contract for each preset.
+
+* `initialize` writes sealed fields, so it MUST only be called from the constructor and never exposed as a post-deploy circuit.
+* Implementing contracts MUST NOT import `Signer` or `Secp256k1EcdsaMultisig` directly, and import one width per contract.
+* No signer or threshold change is exported. Losing more than `n - t` keys freezes the contract.
+
+### Signing approvals
+
+Signers approve an operation off-chain by signing an EIP-712 typed-data digest.
+The circuit rebuilds the same digest with [Eip712] and [EvmAbi], then passes it to [assertApprovals].
+
+* Before deployment, pick a random `instanceSalt`, derive each signer commitment with the preset's `_calculateSignerId(pk, instanceSalt)`,
+ and pass the commitments to `initialize`.
+ A fresh salt per deployment keeps one public key from producing the same commitment in two deployments.
+* The EIP-712 domain is `(name, version, salt)`: the preset name, `"1"`, and `instanceSalt`.
+ `chainId` and `verifyingContract` are absent, because no network identifier is available in-circuit
+ and a 32-byte Midnight address does not fit the `address` type.
+* Every operation struct starts with `bytes32 contractAddress`, filled in-circuit with `kernel.self()`.
+ The ledger randomizes a contract's address at deployment, so a signature for one deployment cannot authorize another.
+* Each preset keeps a replay counter that every gated circuit reads into its digest as `nonce` and then increments.
+ `getNonce` returns the value the next digest binds to.
+ The read pins the counter, so two operations in one block conflict and the losing signers re-sign at the next nonce.
+* Integers are big-endian ABI words, so an EVM signer's typed-data encoder reproduces the digest.
+* Each call carries exactly `t` public keys and `t` low-s signatures from distinct signers, in matching `Vector` positions.
+ A high-s signature is rejected.
+
+`contracts/src/multisig/test/EcdsaTestUtils.ts` in the library repository rebuilds every preset digest through ethers' `TypedDataEncoder`,
+which shows what the off-chain signer must produce
+(like the witness implementations, it is test-only and is not published).
+
+### ShieldedMultiSigV2
+
+The [ShieldedMultiSigV2 API] is a shielded wallet with no proposal lifecycle: one [execute] call verifies `t` approvals and spends.
+It composes [Secp256k1EcdsaMultisig] with `ShieldedTreasuryStateless`, so coin data never reaches the public ledger.
+Anyone may `deposit`.
+The operator tracks the wallet's coins off-chain from `ZswapOutput` events and passes the coin to spend as an argument.
+
+The digest is the struct
+`Execute(bytes32 contractAddress,uint256 nonce,uint8 recipientKind,bytes32 recipient,bytes32 coinColor,uint256 amount)`
+under the domain `("ShieldedMultiSigV2", "1", instanceSalt)`,
+so a signature binds the full recipient (kind and address), the coin color, and the amount.
+[execute] accepts only a `ShieldedUser` recipient: a coin addressed to a contract would be unclaimable.
+It returns the `ShieldedSendResult` of [ShieldedTreasuryStateless _send], whose warning on returning that result applies here.
+
+Each execution publishes which signers approved and what `ShieldedTreasuryStateless` discloses for the spend.
+Keeping coin data off the ledger hides the balance from the public, not from a depositor:
+whoever knows a deposited coin can follow every spend descending from it (see [ShieldedTreasuryStateless](#shieldedtreasurystateless)).
+
+### NativeShieldedTokenIssuer
+
+The [NativeShieldedTokenIssuer API] mints and burns one [NativeShieldedToken], for tokenized deposits, with every operation carrying `t` approvals.
+Value enters only through [mint] (to a holder's coin public key) or [mintToSelf] (a coin the contract holds),
+and leaves only through [burn] (a holder's coin paid into the transaction, with any change refunded to `refundTo`)
+or [burnFromSelf] (a coin the contract holds).
+Coins minted to holders are ordinary Zswap coins that holders transfer without the issuer.
+No send circuit exists, so contract-held coins leave only through [burnFromSelf], and no supply is tracked.
+
+The four digests share the domain `("NativeShieldedTokenIssuer", "1", instanceSalt)`:
+
+* `Mint(bytes32 contractAddress,bytes32 recipient,uint256 nonce,uint256 amount)`
+* `MintToSelf(bytes32 contractAddress,uint256 nonce,uint256 amount)`
+* `Burn(bytes32 contractAddress,bytes32 refundTo,uint256 nonce,uint256 amount)`
+* `BurnFromSelf(bytes32 contractAddress,uint256 nonce,uint256 amount,bytes32 coinNonce,uint256 coinValue)`
+
+`BurnFromSelf` binds the coin's nonce and value, so approvals authorize one holding: pick the coin before collecting signatures.
+[mint] and [burn] return the only copy of the new coin or refund; hand it to its owner out of band,
+since a contract-initiated mint creates no ciphertext a wallet can find by scanning.
+[mintToSelf] and a partial [burnFromSelf] return the contract's new holding, which the operator must persist
+together with its `mt_index` from the indexer.
+
+Every mint amount is public.
+Coin nonces derive from the public `_derivedNonceCounter`, so anyone can test a candidate recipient against a minted coin's commitment,
+and [burnFromSelf] amounts are recoverable by trial.
+
+
+ A coin burned exactly as it was minted is linkable to its mint,
+ which exposes the burn `amount`, the coin value, and `refundTo` by trial.
+ For an unlinkable [burn], the holder re-nonces the coin first with a wallet self-transfer.
+ The contract cannot enforce this.
+
+
+### A 2-of-3 shielded multisig
+
+The following contract trims `ShieldedMultiSigV2Example` from the [examples directory].
+It fixes `t = 2` at import and `n = 3` in the constructor, and surfaces the preset's domain separator unprefixed for `ledger()` readers.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/multisig/presets/ShieldedMultiSigV2"<2>
+ prefix Multisig_;
+
+export { ShieldedCoinInfo, QualifiedShieldedCoinInfo, ShieldedSendResult };
+
+import { _domainSeparator } from "./node_modules/@openzeppelin/compact-contracts/multisig/presets/ShieldedMultiSigV2"<2>;
+export { _domainSeparator };
+
+constructor(
+ instanceSalt: Bytes<32>,
+ signerCommitments: Vector<3, Bytes<32>>
+) {
+ Multisig_initialize<3>(instanceSalt, signerCommitments);
+}
+
+export circuit deposit(coin: ShieldedCoinInfo): [] {
+ return Multisig_deposit(coin);
+}
+
+export circuit execute(
+ to: Multisig_Proposal_Recipient,
+ amount: Uint<128>,
+ coin: QualifiedShieldedCoinInfo,
+ pubkeys: Vector<2, Secp256k1Point>,
+ signatures: Vector<2, Secp256k1EcdsaSignature>
+): ShieldedSendResult {
+ return Multisig_execute(to, amount, coin, pubkeys, signatures);
+}
+
+export pure circuit _calculateSignerId(pk: Secp256k1Point, salt: Bytes<32>): Bytes<32> {
+ return Multisig__calculateSignerId(pk, salt);
+}
+
+export circuit getNonce(): Uint<64> {
+ return Multisig_getNonce();
+}
+```
+
+### A 2-of-3 token issuer
+
+The following contract trims `NativeShieldedTokenIssuerExample` from the [examples directory].
+The constructor also fixes the token domain and metadata.
+The published example additionally re-exports the preset's ledger fields unprefixed and the remaining views.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/multisig/presets/NativeShieldedTokenIssuer"<2>
+ prefix Issuer_;
+
+export { ZswapCoinPublicKey, ContractAddress, Either, Maybe };
+export { ShieldedCoinInfo, QualifiedShieldedCoinInfo };
+
+constructor(
+ instanceSalt: Bytes<32>,
+ tokenDomain: Bytes<32>,
+ name_: Opaque<"string">,
+ symbol_: Opaque<"string">,
+ decimals_: Uint<8>,
+ signerCommitments: Vector<3, Bytes<32>>
+) {
+ Issuer_initialize<3>(instanceSalt, tokenDomain, name_, symbol_, decimals_, signerCommitments);
+}
+
+export circuit mint(
+ amount: Uint<64>,
+ recipient: ZswapCoinPublicKey,
+ pubkeys: Vector<2, Secp256k1Point>,
+ signatures: Vector<2, Secp256k1EcdsaSignature>
+): ShieldedCoinInfo {
+ return Issuer_mint(amount, recipient, pubkeys, signatures);
+}
+
+export circuit mintToSelf(
+ amount: Uint<64>,
+ pubkeys: Vector<2, Secp256k1Point>,
+ signatures: Vector<2, Secp256k1EcdsaSignature>
+): ShieldedCoinInfo {
+ return Issuer_mintToSelf(amount, pubkeys, signatures);
+}
+
+export circuit burn(
+ coin: ShieldedCoinInfo,
+ amount: Uint<128>,
+ refundTo: ZswapCoinPublicKey,
+ pubkeys: Vector<2, Secp256k1Point>,
+ signatures: Vector<2, Secp256k1EcdsaSignature>
+): Maybe {
+ return Issuer_burn(coin, amount, refundTo, pubkeys, signatures);
+}
+
+export circuit burnFromSelf(
+ coin: QualifiedShieldedCoinInfo,
+ amount: Uint<128>,
+ pubkeys: Vector<2, Secp256k1Point>,
+ signatures: Vector<2, Secp256k1EcdsaSignature>
+): Maybe {
+ return Issuer_burnFromSelf(coin, amount, pubkeys, signatures);
+}
+
+export pure circuit _calculateSignerId(pk: Secp256k1Point, salt: Bytes<32>): Bytes<32> {
+ return Issuer__calculateSignerId(pk, salt);
+}
+
+export circuit getNonce(): Uint<64> {
+ return Issuer_getNonce();
+}
+
+export circuit tokenColor(): Bytes<32> {
+ return Issuer_tokenColor();
+}
+```
diff --git a/content/contracts-compact/0.4.x/native-shielded-token.mdx b/content/contracts-compact/0.4.x/native-shielded-token.mdx
new file mode 100644
index 00000000..1a1fef55
--- /dev/null
+++ b/content/contracts-compact/0.4.x/native-shielded-token.mdx
@@ -0,0 +1,384 @@
+---
+title: NativeShieldedToken
+---
+
+{/* links */}
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[ZOwnablePK]: ./ownable
+[wit_secretNonce]: api/ownable#ZOwnablePK-wit_secretNonce
+[ZOwnablePK__computeOwnerId]: api/ownable#ZOwnablePK-_computeOwnerId
+[NativeShieldedToken]: api/native-shielded-token#NativeShieldedToken
+[NativeShieldedTokenFamily]: api/native-shielded-token#NativeShieldedTokenFamily
+[NativeShieldedTokenCore]: api/native-shielded-token#NativeShieldedTokenCore
+[tokenColor]: api/native-shielded-token#NativeShieldedToken-tokenColor
+[_mint]: api/native-shielded-token#NativeShieldedToken-_mint
+[_mintToSelf]: api/native-shielded-token#NativeShieldedToken-_mintToSelf
+[_burn]: api/native-shielded-token#NativeShieldedToken-_burn
+[_burnFromSelf]: api/native-shielded-token#NativeShieldedToken-_burnFromSelf
+[Core__mint]: api/native-shielded-token#NativeShieldedTokenCore-_mint
+[NativeShieldedTokenPublicSupply]: api/native-shielded-token#NativeShieldedTokenPublicSupply
+[NativeShieldedTokenFamilyPublicSupply]: api/native-shielded-token#NativeShieldedTokenFamilyPublicSupply
+[_addMinted]: api/native-shielded-token#NativeShieldedTokenPublicSupply-_addMinted
+[_addBurned]: api/native-shielded-token#NativeShieldedTokenPublicSupply-_addBurned
+[totalMinted]: api/native-shielded-token#NativeShieldedTokenPublicSupply-totalMinted
+[totalBurned]: api/native-shielded-token#NativeShieldedTokenPublicSupply-totalBurned
+[totalSupply]: api/native-shielded-token#NativeShieldedTokenPublicSupply-totalSupply
+[NativeShieldedTokenDerivedNonce]: api/native-shielded-token#NativeShieldedTokenDerivedNonce
+[_deriveNonce]: api/native-shielded-token#NativeShieldedTokenDerivedNonce-_deriveNonce
+[ZOwnableNativeShieldedToken]: api/native-shielded-token#ZOwnableNativeShieldedToken
+[mint]: api/native-shielded-token#ZOwnableNativeShieldedToken-mint
+[burn]: api/native-shielded-token#ZOwnableNativeShieldedToken-burn
+[ShieldedTreasury _deposit]: api/multisig#ShieldedTreasury-_deposit
+[compact-contracts#1112]: https://github.com/OpenZeppelin/compact-contracts/issues/1112
+
+A native shielded token is a token whose units are Midnight's own shielded coins (Zswap UTXOs)
+rather than entries in a contract-owned map.
+The issuing contract mints coins of its own color and burns them;
+holding, spending, and transferring happen in the holder's wallet through Zswap, outside the contract.
+This family of modules implements the MIP-0011 native shielded token standard for the Midnight network:
+a single-token flavor ([NativeShieldedToken]), a multi-token flavor ([NativeShieldedTokenFamily]),
+the shared core both are built on ([NativeShieldedTokenCore]), opt-in supply accounting,
+an opt-in derived-nonce helper, and an owner-gated preset ([ZOwnableNativeShieldedToken]).
+
+## How a native shielded token works
+
+**Coins, not balances.** A unit of the token is a shielded coin with a `color`, a `value`, and a `nonce`.
+The ledger stores a commitment to each coin and a nullifier once it is spent; it never stores who holds what.
+There is no `balanceOf`, `allowance`, or contract-mediated `transfer`:
+once a user holds a coin, the contract cannot observe or restrict its movement,
+and holders move coins between wallets with ordinary Zswap transactions.
+
+**Color.** Every token type is identified by its coin color, `tokenType(domain, contractAddress)`,
+where `domain` is a 32-byte discriminator and `contractAddress` is the issuing contract.
+Because the contract address is part of the derivation, only the issuing contract can mint coins of its colors.
+The modules compute the color at call time with [tokenColor] and never store it.
+
+**Minting.** The contract calls `mintShieldedToken`, which creates a new coin for a recipient.
+The protocol caps a single mint at `Uint<64>`.
+
+**Burning.** The contract burns by sending value to `shieldedBurnAddress()`.
+Burn amounts are `Uint<128>`, because `sendShielded` accepts that width; the asymmetry with mints is a protocol property.
+
+**Metadata.** `name`, `symbol`, and `decimals` are contract-wide, fixed at construction, and public.
+`decimals` is a display convention only; the protocol operates on integer values.
+
+## What is public and what is private
+
+The token modules declare no witnesses; the only secrets are the ones that travel inside coins.
+
+Public on the ledger as state:
+
+* `_name`, `_symbol`, `_decimals`, and `_isInitialized` (and `_domain` for the single-token flavor).
+* The supply counters, if a supply extension is composed.
+
+Public in every transaction:
+
+* Each mint's color and amount, through the protocol's shielded-mint effect.
+* The coin commitments created and the nullifiers spent.
+* Which circuit ran.
+
+Private:
+
+* The recipient and the nonce of a mint, provided the nonce is secret and random.
+ Anyone who knows the nonce can recompute the commitment for candidate recipient keys.
+* The amount, the coin, and the refund recipient of a burn, as long as the observer does not know the coin's nonce.
+ A change output does reveal that a burn was partial.
+
+
+A contract-initiated mint or refund carries a coin ciphertext only when the DApp can resolve the recipient's
+encryption key at proving time (its own, or one supplied through `additionalCoinEncPublicKeyMappings`).
+Without that, the recipient's wallet cannot discover the coin by scanning the chain, and the `ShieldedCoinInfo`
+a circuit returns is the only copy of the coin: the DApp must capture it and deliver it to the recipient
+(or persist it, when the contract is the recipient). Dropping it strands the value irrecoverably.
+
+
+## Single token or a family
+
+Both user-facing modules are thin wrappers over [NativeShieldedTokenCore], which owns the initialization flag,
+the metadata, and the mint and burn logic. They differ only in where the domain comes from.
+
+* [NativeShieldedToken] issues one token type. The domain is passed to `initialize`, stored as `sealed ledger _domain`,
+ and forwarded to every core operation, so no circuit takes a domain parameter and a caller cannot misuse one.
+* [NativeShieldedTokenFamily] issues many token types from one contract. Every circuit takes a `domain` parameter,
+ and `tokenType(domain, kernel.self())` discriminates the types.
+ Midnight has no contract-to-contract calls and no way for a contract to deploy another,
+ so a protocol that issues many related tokens (one liquidity-share token per pair, for instance)
+ must do so from a single contract. Metadata is family-wide; per-type identity belongs in the consumer's own state.
+
+
+Pick one flavor per contract. Both wrap the same core, so importing both into one contract shares a single
+initialization flag and one set of `name`, `symbol`, and `decimals`.
+
+
+The core itself is not meant to be composed directly. Its `_mint` and `_burn` take an
+`Either` recipient; the flavors narrow that to a `ZswapCoinPublicKey`
+and expose [_mintToSelf] for the contract's own coins.
+
+## Minting and who can receive a coin
+
+[_mint] takes a `ZswapCoinPublicKey` recipient, a `Uint<64>` amount, and a caller-supplied `Bytes<32>` nonce,
+and returns the new coin's `ShieldedCoinInfo`. The recipient must not be the zero key.
+
+**Recipients.** A coin can be minted to any user's coin public key, or to the issuing contract itself through [_mintToSelf],
+which mints to `kernel.self()`.
+`mintShieldedToken` itself claims a self-addressed coin as the contract's receive in the same call.
+No other contract can receive a mint: the ledger only accepts a contract-addressed commitment when that contract claims it
+in the same transaction, and no primitive can claim a receive on another contract's behalf.
+The core enforces this directly ([Core__mint] rejects any `ContractAddress` other than the contract's own),
+and the flavors do not expose the address arm at all.
+A coin minted to the contract is the contract's holding: persist the returned coin info in your own ledger state.
+Because the mint already claimed it, a custody module must record the coin without calling `receiveShielded` again.
+A second receive emits a duplicate Zswap output for the same commitment.
+[ShieldedTreasury _deposit] claims coins on the way in, so it is not that path.
+A record-only path is tracked in [compact-contracts#1112].
+Until a custody module is composed, the only way to spend a contract-held coin is [_burnFromSelf],
+once it carries a Merkle-tree index.
+
+**Nonces.** The caller is responsible for nonce uniqueness. Reusing a nonce for the same (domain, value, recipient)
+produces a duplicate commitment, which the ledger rejects.
+With a secret, cryptographically random nonce the mint is recipient-private.
+If you do not want to manage nonces, compose [NativeShieldedTokenDerivedNonce] and pass its [_deriveNonce] output,
+at the cost of making those mints recipient-public (see below).
+
+The modules include no access control. Gate `_mint` and `_mintToSelf` with an access-control module
+following the [Module/Contract Pattern]; exposing them as-is lets anyone mint.
+
+## Burning and change
+
+There are two burn paths, chosen by where the coin comes from.
+
+[_burn] destroys value from a coin that the caller's wallet pays into the current transaction.
+The contract receives the coin and spends `amount` of it to the burn address through `sendImmediateShielded`,
+the required path for a coin that was created in the same transaction.
+If `amount` is less than `coin.value`, the remainder is sent to `refundTo` as a new coin and returned as `some(refund)`;
+pass `coin.value` as `amount` to burn the coin in full, in which case `none` is returned.
+`refundTo` must always be supplied and non-zero, even on a full burn: the zero key is the burn address,
+so a zero `refundTo` would silently burn the change as well.
+
+[_burnFromSelf] destroys value from a coin the contract already holds, identified by a `QualifiedShieldedCoinInfo`
+(a coin with a valid Merkle-tree index). It spends through `sendShielded`; any change is received by the contract
+automatically and returned so the consumer can persist it. The change coin replaces the original as the contract's holding,
+and its info is not otherwise recoverable.
+Like a coin minted to self, the change is already claimed, so a custody module records it without a second `receiveShielded`.
+
+Both paths put the spent coin's nullifier and the new output commitments on the ledger.
+Neither puts the burned amount on the ledger: the `disclose()` calls in the module are compiler permission markers
+for the coin primitives, not disclosure sinks.
+
+## Supply accounting (optional)
+
+The token modules track no supply totals. [NativeShieldedTokenPublicSupply] (for the single-token flavor) and
+[NativeShieldedTokenFamilyPublicSupply] (for the family) add on-chain counters.
+They import no token module: the consuming contract pairs every mint with [_addMinted] and every burn with [_addBurned],
+under the same domain for the family. The three getters carry different guarantees:
+
+* [totalMinted] is exact. Every mint of this contract's colors goes through the contract.
+* [totalBurned] is a lower bound. A holder can send coins to the burn address without calling the contract, and those burns are invisible.
+* [totalSupply] is `totalMinted - totalBurned`, an upper bound on circulating supply.
+
+`_addBurned` enforces `burned <= minted`, so an unpaired, duplicate, or wrong-domain burn reverts instead of pushing
+the supply negative. Nothing catches drift on the mint side: a missing `_addMinted` undercounts, after which
+`_addBurned` rejects legitimate burns, and a duplicate `_addMinted` lets burns exceed what was minted.
+The pairing is an invariant of the consuming contract.
+
+
+Composing a supply extension makes contract-mediated burn amounts public: the `_addBurned` write is what puts the
+burned amount into the public transcript. Mint amounts are already public through the shielded-mint effect, so
+`_addMinted` adds accounting, not disclosure. Compose supply tracking only when on-chain auditability is worth more
+than burn-amount privacy.
+
+
+## Derived nonces (optional)
+
+[NativeShieldedTokenDerivedNonce] owns a monotonic `_counter` and exposes [_deriveNonce], which increments the counter
+and returns `evolveNonce(_counter, pad(32, "NativeShieldedToken:nonce"))`.
+Uniqueness comes from the counter; the fixed tag separates these nonces from any other `evolveNonce` use in the contract.
+One counter serves the whole contract, so derived nonces never repeat across token types.
+It needs no initialization and pairs with either flavor.
+
+Derived nonces are predictable from public state, so mints that use them are recipient-public:
+anyone can recompute the commitment for candidate recipient keys.
+They are also open to collision griefing: an actor with access to a caller-nonce mint can pre-mint a colliding commitment
+and make the matching derived-nonce mint fail. Gate both mint paths at the same trust level, and do not rely on retrying:
+the nonce is a pure function of the counter, and a reverted mint does not advance it, so an identical retry collides again.
+A mint with different inputs advances the counter and clears the way.
+
+## Usage
+
+### An owner-gated single token
+
+[ZOwnableNativeShieldedToken] composes [ZOwnablePK] (a shielded owner), [NativeShieldedToken] (one color), and
+[NativeShieldedTokenPublicSupply] (a public minted total). Only the owner can [mint] and [burn];
+the preset deliberately never calls `_addBurned`, so burn amounts stay private and `_totalBurned` stays empty.
+The following contract is the published `ZOwnableNativeShieldedTokenExample`.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/presets/ZOwnableNativeShieldedToken"
+ prefix ZOwnableNativeShieldedToken_;
+
+export { ZswapCoinPublicKey, ContractAddress, Either, Maybe, ShieldedCoinInfo };
+
+// Surface the preset's state unprefixed so `ledger()` readers use the keys the
+// modules declare.
+import {
+ _ownerCommitment,
+ _counter,
+ _instanceSalt,
+ _ownableIsInitialized,
+ _domain,
+ _name,
+ _symbol,
+ _decimals,
+ _isInitialized,
+ _totalMinted,
+ _totalBurned,
+} from "./node_modules/@openzeppelin/compact-contracts/token/presets/ZOwnableNativeShieldedToken";
+export {
+ _ownerCommitment,
+ _counter,
+ _instanceSalt,
+ _ownableIsInitialized,
+ _domain,
+ _name,
+ _symbol,
+ _decimals,
+ _isInitialized,
+ _totalMinted,
+ _totalBurned,
+};
+
+constructor(
+ ownerId: Bytes<32>,
+ instanceSalt: Bytes<32>,
+ tokenDomain: Bytes<32>,
+ name_: Opaque<"string">,
+ symbol_: Opaque<"string">,
+ decimals_: Uint<8>,
+) {
+ ZOwnableNativeShieldedToken_initialize(
+ ownerId,
+ instanceSalt,
+ tokenDomain,
+ name_,
+ symbol_,
+ decimals_,
+ );
+}
+
+export circuit mint(recipient: ZswapCoinPublicKey, amount: Uint<64>, nonce: Bytes<32>): ShieldedCoinInfo {
+ return ZOwnableNativeShieldedToken_mint(recipient, amount, nonce);
+}
+
+export circuit burn(coin: ShieldedCoinInfo, amount: Uint<64>, refundTo: ZswapCoinPublicKey): Maybe {
+ return ZOwnableNativeShieldedToken_burn(coin, amount, refundTo);
+}
+
+export circuit transferOwnership(newOwnerId: Bytes<32>): [] {
+ ZOwnableNativeShieldedToken_transferOwnership(newOwnerId);
+}
+
+export circuit renounceOwnership(): [] {
+ ZOwnableNativeShieldedToken_renounceOwnership();
+}
+
+export circuit owner(): Bytes<32> {
+ return ZOwnableNativeShieldedToken_owner();
+}
+
+export pure circuit _computeOwnerId(pk: Either, nonce: Bytes<32>): Bytes<32> {
+ return ZOwnableNativeShieldedToken__computeOwnerId(pk, nonce);
+}
+
+export circuit totalMinted(): Uint<128> {
+ return ZOwnableNativeShieldedToken_totalMinted();
+}
+
+export circuit tokenColor(): Bytes<32> {
+ return ZOwnableNativeShieldedToken_tokenColor();
+}
+```
+
+The owner is identified by a commitment, not a key. Before deploying, compute
+`ownerId = _computeOwnerId(pk, secretNonce)` off-chain, where `pk` is the coin public key the owner's wallet
+will supply as `ownPublicKey()` and `secretNonce` is fresh random bytes the owner keeps private.
+The contract cannot check this value at deploy time; any other value leaves the contract with no usable owner and no recovery.
+Every owner transaction then proves knowledge of the pair: the wallet supplies the coin public key, and the
+[wit_secretNonce] witness (inherited from [ZOwnablePK]) supplies the nonce.
+The TypeScript witness implementations in the library repository are test-only and are not published;
+you author the witness for your own DApp.
+The proof server sees `wit_secretNonce`, so run it where the owner controls it.
+
+On the client side, pass fresh random bytes as `nonce` on every `mint`, and persist the `ShieldedCoinInfo` values that
+`mint` and `burn` return: midnight-js encrypts an output to its recipient only when it can resolve the recipient's
+encryption key (its own, or one supplied in `additionalCoinEncPublicKeyMappings`).
+
+`initialize` writes sealed fields, so it belongs in the constructor and must not be exposed as a post-deploy circuit.
+`transferOwnership` is one step and `renounceOwnership` stops minting and burning forever.
+
+### A token family with supply tracking and derived nonces
+
+This contract issues one token type per `domain`, counts supply per domain, and lets the owner mint without
+managing nonces. Burns are open to any holder, since a holder can always burn by sending to the burn address directly;
+routing burns through the contract keeps `totalBurned` closer to the truth.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK"
+ prefix ZOwnablePK_;
+import "./node_modules/@openzeppelin/compact-contracts/token/NativeShieldedTokenFamily"
+ prefix Family_;
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenFamilyPublicSupply"
+ prefix Supply_;
+import "./node_modules/@openzeppelin/compact-contracts/token/extensions/NativeShieldedTokenDerivedNonce"
+ prefix Derived_;
+
+export { ZswapCoinPublicKey, Maybe, ShieldedCoinInfo };
+
+constructor(
+ ownerId: Bytes<32>,
+ instanceSalt: Bytes<32>,
+ name_: Opaque<"string">,
+ symbol_: Opaque<"string">,
+ decimals_: Uint<8>,
+) {
+ ZOwnablePK_initialize(ownerId, instanceSalt);
+ Family_initialize(name_, symbol_, decimals_);
+}
+
+// Owner only. Derived nonces make these mints recipient-public.
+export circuit mint(domain: Bytes<32>, recipient: ZswapCoinPublicKey, amount: Uint<64>): ShieldedCoinInfo {
+ ZOwnablePK_assertOnlyOwner();
+ const coin = Family__mint(domain, recipient, amount, Derived__deriveNonce());
+ Supply__addMinted(domain, amount);
+ return coin;
+}
+
+// Any holder. `coin` is paid into the transaction by the caller's wallet;
+// the change, if any, goes to `refundTo`. Publishes `amount`.
+export circuit burn(
+ domain: Bytes<32>,
+ coin: ShieldedCoinInfo,
+ amount: Uint<128>,
+ refundTo: ZswapCoinPublicKey
+): Maybe {
+ const refund = Family__burn(domain, coin, amount, refundTo);
+ Supply__addBurned(domain, amount);
+ return refund;
+}
+
+export circuit tokenColor(domain: Bytes<32>): Bytes<32> {
+ return Family_tokenColor(domain);
+}
+
+export circuit totalSupply(domain: Bytes<32>): Uint<128> {
+ return Supply_totalSupply(domain);
+}
+```
+
+A `mint` and a `burn` under the same `domain` both touch that domain's supply entry, so only one of them lands per block;
+operations on different domains do not conflict.
diff --git a/content/contracts-compact/0.4.x/non-fungible-token.mdx b/content/contracts-compact/0.4.x/non-fungible-token.mdx
new file mode 100644
index 00000000..5cb1346d
--- /dev/null
+++ b/content/contracts-compact/0.4.x/non-fungible-token.mdx
@@ -0,0 +1,216 @@
+---
+title: NonFungibleToken
+---
+
+{/* links */}
+[non-fungible tokens]: /contracts/5.x/tokens#different-kinds-of-tokens
+[EIP-721]: https://eips.ethereum.org/EIPS/eip-721
+[ERC-165]: https://eips.ethereum.org/EIPS/eip-165
+[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern
+[_mint]: ./api/non-fungible-token#NonFungibleToken-_mint
+[approve]: ./api/non-fungible-token#NonFungibleToken-approve
+[setApprovalForAll]: ./api/non-fungible-token#NonFungibleToken-setApprovalForAll
+[transferFrom]: ./api/non-fungible-token#NonFungibleToken-transferFrom
+[wit_NonFungibleTokenSK]: ./api/non-fungible-token#NonFungibleToken-wit_NonFungibleTokenSK
+[computeAccountId]: ./api/utils#Utils-computeAccountId
+
+NonFungibleToken is a specification for [non-fungible tokens],
+a type of token where all the units are unique and distinct from each other.
+This module is an approximation of [EIP-721] written in the Compact programming language for the Midnight network.
+
+## ERC721 Compatibility
+
+Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard.
+Some features and behaviors are either not possible, not possible yet,
+or changed because of the vastly different tech stack and Compact language constraints.
+
+***Notable changes***
+
+* ***`Uint<128>` tokenIds*** - Since 256-bit unsigned integers are not supported,
+the library uses the Compact type `Uint<128>`.
+* ***No _baseURI() support*** - Native strings and string operations are not supported within the Compact language,
+so concatenating a base URI + token ID is not possible like in other NFT implementations.
+Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs.
+It's up to the implementation to decide on how to handle this.
+
+***Features and specifications NOT supported***
+
+* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future.
+* ***Uint256 type*** - There's ongoing research on ways to support uint256 in the future.
+* ***Interface*** - Compact currently does not have a way to define a contract interface.
+This library offers modules of contracts with free floating circuits;
+nevertheless, there are no means of enforcing that all circuits are provided.
+* ***ERC-165 Standard*** - Since Compact doesn't provide a way to define a contract interface,
+it's not possible to implement an [ERC-165] like interface standard at this time.
+* ***Safe Transfers*** - It's not possible to implement safe transfers without an [ERC-165]-like
+interface standard at this time.
+
+## Contract-to-contract calls
+
+Contract-to-contract calls are currently not supported in the Compact language.
+Due to this limitation, the current iteration of NonFungibleToken disallows transfers and mints to the `ContractAddress` type.
+Transferring tokens to a contract may result in those tokens being locked forever.
+The NonFungibleToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts.
+
+
+The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning
+`_transfer`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type.
+
+
+## Identity Model
+
+NonFungibleToken uses a witness-derived identity scheme. Each caller proves knowledge of a secret key
+by injecting it through the [wit_NonFungibleTokenSK] witness. The module computes the caller's account identifier
+as `persistentHash(secretKey)`, a commitment that hides the key while providing a stable, pseudonymous on-chain identity.
+That identifier is the `Bytes<32>` (left) branch of the `Either, ContractAddress>` account type
+and is what [approve], [setApprovalForAll], and [transferFrom] check ownership and operator rights against.
+
+What is public on the ledger:
+
+* `_owners`, keyed by token id, and `_balances`, keyed by account identifier (or contract address).
+* `_tokenApprovals`, keyed by token id, and `_operatorApprovals`, keyed by owner and then operator.
+* `_tokenURIs`, `_name`, and `_symbol`.
+
+What the caller holds privately:
+
+* The 32-byte `secretKey`. It never reaches the ledger; only its hash does.
+
+A transfer discloses the token id, the previous owner, and the recipient, because the owner and balance maps are updated with those values.
+A single-token approval discloses the token id and the approved account; an operator approval discloses the caller's identifier, the operator, and the flag.
+
+Because the identifier is `H(secretKey)` with no per-deployment salt, the same key produces the same identity in every contract.
+Users who want cross-contract unlinkability can use a different secret key per contract at the wallet layer.
+
+To derive an account identifier off-chain (for example, to pass as the `recipient` of a mint), use [computeAccountId] from the Utils module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The [Utils guide](./utils#deriving-an-identifier-off-chain) shows the equivalent computation with the Compact runtime.
+
+The TypeScript witness implementation lives with the DApp, not with the library.
+The witnesses in the `compact-contracts` repository are test-only and are not published;
+consumers author their own `wit_NonFungibleTokenSK` that returns the caller's 32-byte secret key.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of the tokens held under that identity,
+ and key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+## Usage
+
+Import the NonFungibleToken module into the implementing contract.
+It's recommended to prefix the module with `NonFungibleToken_` to avoid circuit signature clashes.
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken"
+ prefix NonFungibleToken_;
+
+constructor(name: Opaque<"string">, symbol: Opaque<"string">) {
+ NonFungibleToken_initialize(name, symbol);
+}
+
+```
+
+Next, expose the circuits that users may call in the contract.
+
+This library enables extensibility by following the rules of the [Module/Contract Pattern].
+Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts.
+Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens.
+
+```typescript
+export circuit name(): Opaque<"string"> {
+ return NonFungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return NonFungibleToken_symbol();
+}
+
+(...)
+```
+
+The following example is a simple non-fungible token contract that mints an NFT to the passed recipient upon construction.
+
+```typescript
+// SimpleNonFungibleToken.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/token/NonFungibleToken"
+ prefix NonFungibleToken_;
+
+constructor(
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ recipient: Either, ContractAddress>,
+ tokenURI: Opaque<"string">,
+) {
+ const tokenId = 1 as Uint<128>;
+ NonFungibleToken_initialize(name, symbol);
+ NonFungibleToken__mint(recipient, tokenId);
+ NonFungibleToken__setTokenURI(tokenId, tokenURI);
+}
+
+export circuit balanceOf(owner: Either, ContractAddress>): Uint<128> {
+ return NonFungibleToken_balanceOf(owner);
+}
+
+export circuit ownerOf(tokenId: Uint<128>): Either, ContractAddress> {
+ return NonFungibleToken_ownerOf(tokenId);
+}
+
+export circuit name(): Opaque<"string"> {
+ return NonFungibleToken_name();
+}
+
+export circuit symbol(): Opaque<"string"> {
+ return NonFungibleToken_symbol();
+}
+
+export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> {
+ return NonFungibleToken_tokenURI(tokenId);
+}
+
+export circuit approve(to: Either, ContractAddress>, tokenId: Uint<128>): [] {
+ NonFungibleToken_approve(to, tokenId);
+}
+
+export circuit getApproved(tokenId: Uint<128>): Either, ContractAddress> {
+ return NonFungibleToken_getApproved(tokenId);
+}
+
+export circuit setApprovalForAll(
+ operator: Either, ContractAddress>,
+ approved: Boolean
+): [] {
+ NonFungibleToken_setApprovalForAll(operator, approved);
+}
+
+export circuit isApprovedForAll(
+ owner: Either, ContractAddress>,
+ operator: Either, ContractAddress>
+): Boolean {
+ return NonFungibleToken_isApprovedForAll(owner, operator);
+}
+
+export circuit transferFrom(
+ fromAddress: Either, ContractAddress>,
+ to: Either, ContractAddress>,
+ tokenId: Uint<128>
+): [] {
+ NonFungibleToken_transferFrom(fromAddress, to, tokenId);
+}
+
+```
diff --git a/content/contracts-compact/0.4.x/ownable.mdx b/content/contracts-compact/0.4.x/ownable.mdx
new file mode 100644
index 00000000..2c25eb5b
--- /dev/null
+++ b/content/contracts-compact/0.4.x/ownable.mdx
@@ -0,0 +1,439 @@
+---
+title: Ownable
+---
+
+{/* links */}
+[initialize]: api/ownable#Ownable-initialize
+[transferOwnership]: api/ownable#Ownable-transferOwnership
+[_transferOwnership]: api/ownable#Ownable-_transferOwnership
+[_unsafeTransferOwnership]: api/ownable#Ownable-_unsafeTransferOwnership
+[_unsafeUncheckedTransferOwnership]: api/ownable#Ownable-_unsafeUncheckedTransferOwnership
+[computeAccountId]: api/utils#Utils-computeAccountId
+[assertOnlyOwner]: api/ownable#Ownable-assertOnlyOwner
+[Ownable API]: api/ownable#Ownable
+
+[ZOwnablePK_initialize]: api/ownable#ZOwnablePK-initialize
+[ZOwnablePK_transferOwnership]: api/ownable#ZOwnablePK-transferOwnership
+[ZOwnablePK_assertOnlyOwner]: api/ownable#ZOwnablePK-assertOnlyOwner
+[_computeOwnerId]: api/ownable#ZOwnablePK-_computeOwnerId
+[wit_secretNonce]: api/ownable#ZOwnablePK-wit_secretNonce
+
+[ed25519]: https://ed25519.cr.yp.to/
+[rfc6979]: https://datatracker.ietf.org/doc/html/rfc6979
+[AccessControl]: ./access-control.mdx
+[Utils]: ./utils.mdx
+
+## Access Control Defined
+
+Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts.
+The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things.
+It is therefore critical to understand how you implement it, lest someone else steals your whole system.
+This library provides a variety of access control modules to suit your application and privacy needs.
+
+## Ownership and `Ownable`
+
+The most common and basic form of access control is the concept of ownership:
+there's an account that is the owner of a contract and can do administrative tasks on it.
+This approach is perfectly reasonable for contracts that have a single administrative user.
+
+This library provides an Ownable module for implementing ownership in your contracts.
+The initial owner must be set by using the [initialize] circuit during construction.
+This can later be changed with [transferOwnership].
+
+
+ For more granular, role-based access control, see [AccessControl].
+
+
+### Identity Model
+
+Ownable uses a witness-derived identity scheme. The owner proves knowledge of a secret key
+by injecting it via the `wit_OwnableSK` witness. The module computes an account identifier
+as `H(secretKey)` (a `persistentHash` of the key), which is a commitment that hides the key while providing a stable,
+pseudonymous on-chain identity.
+
+What is public and what is private:
+
+* **On the ledger**: `_owner` holds the account identifier `H(secretKey)` (or a contract address), and `_isInitialized`.
+* **Held privately by the owner**: the 32-byte `secretKey`, supplied through `wit_OwnableSK` at proving time.
+ The key never reaches the ledger; [assertOnlyOwner] recomputes the identifier inside the proof and compares it against `_owner`.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+To derive an account identifier off-chain (e.g. for the `initialOwner` parameter during construction),
+use [computeAccountId] from the [Utils] module:
+
+```typescript
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+A contract that re-exports it this way exposes `pureCircuits.computeAccountId(secretKey)` to its TypeScript side.
+The same value can be computed directly in TypeScript with the Compact runtime:
+
+```typescript
+import {
+ CompactTypeBytes,
+ CompactTypeVector,
+ persistentHash,
+} from '@midnight-ntwrk/compact-runtime';
+
+const computeAccountId = (secretKey: Uint8Array): Uint8Array => {
+ const rt_type = new CompactTypeVector(1, new CompactTypeBytes(32));
+ return persistentHash(rt_type, [secretKey]);
+};
+```
+
+The `secretKey` is a 32-byte cryptographically secure random value that the owner keeps private.
+Because the identifier has no per-deployment salt, the same key produces the same identity across all contracts.
+Owners who want cross-contract unlinkability should use a different key per contract.
+
+
+ The `secretKey` must never be logged or persisted in plaintext. Loss of the key means loss of ownership.
+ Key exposure allows impersonation. Use `crypto.getRandomValues()` or equivalent to generate keys.
+
+
+### Ownership Transfers
+
+Ownership can only be transferred to `Bytes<32>` account identifiers through the main transfer circuits
+([transferOwnership] and [_transferOwnership]).
+
+In other words, ownership transfers to contract addresses are disallowed through these circuits.
+This is because Compact currently does not support contract-to-contract calls, which means if a contract is granted ownership,
+the owner contract cannot directly call the protected circuit.
+
+### Unsafe Circuits
+
+This module offers circuits that allow ownership to be granted to contract addresses
+([_unsafeTransferOwnership] and [_unsafeUncheckedTransferOwnership]).
+
+Note that the circuit names are very explicit ("unsafe").
+Until contract-to-contract calls are supported,
+there is no direct way for a contract to call circuits of other contracts or transfer ownership back to a user.
+
+
+ The unsafe circuits are planned to become deprecated once contract-to-contract calls become available.
+
+
+### Usage
+
+Import the Ownable module into the implementing contract.
+It's recommended to prefix the module with `Ownable_` to avoid circuit signature clashes.
+
+The `initialOwner` should be an account identifier derived off-chain using `Utils_computeAccountId(secretKey)`:
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(
+ initialOwner: Either, ContractAddress>
+) {
+ Ownable_initialize(initialOwner);
+}
+```
+
+To protect a circuit so that only the contract owner may call it,
+insert the `assertOnlyOwner` circuit in the beginning of the circuit body like this:
+
+```ts
+export circuit mySensitiveCircuit(): [] {
+ Ownable_assertOnlyOwner();
+
+ // Do something
+}
+```
+
+Contracts may expose [transferOwnership] to allow the owner to transfer ownership.
+The `newOwner` should be an account identifier derived by the new owner using `Utils_computeAccountId(secretKey)`:
+
+```ts
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ Ownable_transferOwnership(newOwner);
+}
+```
+
+Here's a complete contract showcasing how to integrate the Ownable module and protect sensitive circuits.
+
+```ts
+// SimpleOwnable.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+/**
+ * Set `initialOwner` as the owner of the contract.
+ * `initialOwner` should be derived off-chain using Utils_computeAccountId(secretKey).
+ */
+constructor(initialOwner: Either, ContractAddress>) {
+ Ownable_initialize(initialOwner);
+}
+
+/**
+ * The current owner of the contract.
+ */
+export circuit owner(): Either, ContractAddress> {
+ return Ownable_owner();
+}
+
+/**
+ * Transfers ownership of the contract.
+ * Can only be called by the current owner.
+ * `newOwner` should be derived off-chain by the new owner using Utils_computeAccountId(secretKey).
+ */
+export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
+ Ownable_transferOwnership(newOwner);
+}
+
+/**
+ * Leaves the contract without an owner.
+ * Can only be called by the current owner.
+ * Renouncing ownership means `mySensitiveCircuit` can never be called again.
+ */
+export circuit renounceOwnership(): [] {
+ Ownable_renounceOwnership();
+}
+
+/**
+ * This is the protected circuit that only the current owner can call.
+ */
+export circuit mySensitiveCircuit(): [] {
+ // Protects the circuit
+ Ownable_assertOnlyOwner();
+
+ // Do something
+}
+```
+
+
+ For more complex logic,
+ contracts may transfer ownership to another user irrespective of the caller by leveraging [_transferOwnership].
+ This is generally more useful when contract addresses are the owner or when a contract has a unique deployment process.
+
+
+## Shielded Ownership and `ZOwnablePK`
+
+Privacy-preserving access control is a fundamental building block for confidential smart contracts on Midnight.
+While the `Ownable` module stores the owner's account identifier on-chain,
+many applications require administrative control without revealing who holds that authority.
+
+### Privacy-First Ownership
+
+The `ZOwnablePK` module implements shielded ownership meaning administrative control without identity disclosure.
+The owner's public key is never revealed on-chain;
+instead, the contract stores only a cryptographic commitment that proves ownership without exposing the underlying identity.
+
+This is useful in scenarios such as private treasuries, anonymous governance,
+or any application where revealing the administrator's identity may compromise the system's confidentiality.
+
+What is public and what is private:
+
+* **On the ledger**: `_ownerCommitment`, the transfer `_counter`, the sealed `_instanceSalt`, and `_isInitialized`.
+ Ownership changes disclose only the new commitment, never the owner identifier that produced it.
+* **Held privately by the owner**: the secret nonce, supplied through the [wit_secretNonce] witness,
+ and the public key, supplied through `ownPublicKey()`. [ZOwnablePK_assertOnlyOwner] recomputes the commitment
+ from both inside the proof and compares it against `_ownerCommitment`.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+### Commitment Scheme
+
+The `ZOwnablePK` module employs a two-layer cryptographic commitment scheme designed to provide privacy,
+unlinkability, and collision resistance across deployments and ownership transfers.
+
+#### Owner ID Computation
+
+The foundation of the system is the owner identifier, computed as:
+
+```typescript
+id = SHA256(pk || nonce)
+```
+
+Where `pk` is the owner's public key and `nonce` is a secret value that may be either randomly generated for maximum privacy
+or deterministically derived for recoverability.
+This identifier serves as a privacy-preserving alternative to exposing the raw public key,
+ensuring the owner's identity remains confidential.
+
+#### Owner Commitment Computation
+
+The final ownership commitment stored on-chain is computed as:
+
+```typescript
+commitment = SHA256(id || instanceSalt || counter || pad(32, "ZOwnablePK:shield:"))
+```
+
+This multi-element hash provides several security properties:
+
+* `id`: The privacy-preserving owner identifier described above.
+* `instanceSalt`: A unique per-deployment salt that prevents commitment collisions across different contract instances,
+even when the same owner and nonce are used. It must be nonzero, and it should be a fresh random value per deployment:
+a reused salt links the same owner across deployments.
+* `counter`: Incremented with each ownership transfer to ensure unlinkability.
+Each transfer produces a completely different commitment even with the same underlying owner.
+* `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions
+with other commitment schemes and enables safe protocol extensions.
+
+#### Security Properties
+
+This commitment scheme ensures that:
+
+* Public keys are never revealed on-chain.
+* Observers cannot correlate past and future ownership.
+* Cross-contract collisions are prevented through instance-specific salting.
+
+
+ `ownPublicKey()` is a witness: it returns the caller's Zswap coin public key, and nothing in the circuit proves
+ that the caller holds the matching private key. The secret nonce is therefore the owner credential:
+ anyone holding the nonce and the owner's public key can pass [ZOwnablePK_assertOnlyOwner].
+ Generate the nonce as 32 bytes from a CSPRNG (or a derivation with equivalent entropy) and keep it private.
+
+
+### Nonce Generation Strategies
+
+The choice of nonce generation strategy represents a fundamental trade-off between simplicity/security and recoverability.
+Both approaches are valid, and the best choice depends on your specific threat model and operational requirements.
+
+#### Random Nonce
+
+Generating a cryptographically strong random nonce provides the strongest privacy guarantees:
+
+```typescript
+const randomNonce = crypto.getRandomValues(new Uint8Array(32));
+const ownerId = ZOwnablePK._computeOwnerId(publicKey, randomNonce);
+```
+
+This approach is easy to generate and ensures maximum unlinkability.
+However, it requires secure backup of both the public key and the nonce.
+**Loss of either component results in permanent, irrecoverable loss of ownership.**
+
+#### Deterministic Nonce
+
+Deriving the nonce deterministically enables recovery through derivation schemes. Some examples:
+
+* `H(passphrase + context)` — recoverable from passphrase only, but passphrase becomes critical single point of failure.
+* `H(publicKey + userPassphrase + context)` — requires both public key and passphrase.
+* `H(signature + context) where signature = sign(context)` — leverages wallet without exposing private key.
+
+
+ When using signature-based nonce derivation,
+ ensure the wallet/library uses deterministic signatures ([ed25519] or [rfc6979] for ECDSA).
+ Non-deterministic signatures will generate different nonces on each signing, making recovery impossible.
+ Test the implementation by signing the same message twice then verify that the signatures match.
+
+
+**Context-Dependent Derivations:**
+
+* Include contract address, deployment timestamp, user ID, etc.
+* Trade-off: more context is more unique but harder to recreate.
+
+
+ Approaches that avoid private key exposure (public key + passphrase, signature-based)
+ are generally recommended for operational security.
+
+
+**Security Considerations**
+
+The `ZOwnablePK` module remains agnostic to nonce generation methods,
+placing the security/convenience decision entirely with the user.
+Key considerations include:
+
+* **Backup requirements**: Random nonces require additional secure storage.
+* **Recovery scenarios**: Deterministic nonces enable recovery.
+* **Cross-contract correlation**: Reusing nonce strategies may reduce privacy across deployments.
+* **Rotation costs**: Changing nonces requires ownership transfer transactions with associated costs.
+
+Users should carefully evaluate their threat model, operational requirements, and privacy needs when selecting a nonce generation strategy,
+as this choice cannot be easily changed without transferring ownership.
+
+### Usage
+
+Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits.
+It's recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes.
+
+```typescript
+// MyZOwnablePKContract.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ZOwnablePK"
+ prefix ZOwnablePK_;
+
+constructor(
+ initOwnerId: Bytes<32>,
+ instanceSalt: Bytes<32>,
+) {
+ ZOwnablePK_initialize(initOwnerId, instanceSalt);
+}
+
+export circuit owner(): Bytes<32> {
+ return ZOwnablePK_owner();
+}
+
+export circuit transferOwnership(newOwnerId: Bytes<32>): [] {
+ return ZOwnablePK_transferOwnership(disclose(newOwnerId));
+}
+
+export circuit renounceOwnership(): [] {
+ return ZOwnablePK_renounceOwnership();
+}
+```
+
+[ZOwnablePK_initialize] rejects a zero `ownerId` and a zero `instanceSalt`.
+
+Similar to the [Ownable](#usage) module,
+circuits can be protected so that only the contract owner may call them by adding `assertOnlyOwner` as the first line in the circuit body:
+
+```typescript
+export circuit mySensitiveCircuit(): [] {
+ ZOwnablePK_assertOnlyOwner();
+
+ // Do something
+}
+```
+
+This covers the basics for creating a contract, but before deploying the contract,
+the owner's id must be derived for the commitment scheme because it's required to deploy the contract.
+
+First, the owner needs to generate a secret nonce that's stored in the owner's private state.
+See [Nonce Generation Strategies](#nonce-generation-strategies).
+
+Once the owner has the secret nonce generated, they can derive their owner ID:
+
+```typescript
+import {
+ CompactTypeBytes,
+ CompactTypeVector,
+ persistentHash,
+} from '@midnight-ntwrk/compact-runtime';
+import { getRandomValues } from 'node:crypto';
+
+// Owner ID
+const generateId = (
+ pk: Uint8Array,
+ nonce: Uint8Array,
+): Uint8Array => {
+ const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32));
+ return persistentHash(rt_type, [pk, nonce]);
+};
+
+// Instance salt for the constructor (must be nonzero)
+const generateInstanceSalt = (): Uint8Array => {
+ return getRandomValues(new Uint8Array(32));
+}
+```
+
+
+ Another way to get the user ID is to expose [_computeOwnerId] in the contract and call this circuit off-chain through a contract simulator.
+
diff --git a/content/contracts-compact/0.4.x/security.mdx b/content/contracts-compact/0.4.x/security.mdx
new file mode 100644
index 00000000..b2824704
--- /dev/null
+++ b/content/contracts-compact/0.4.x/security.mdx
@@ -0,0 +1,282 @@
+---
+title: Security
+---
+
+{/* links */}
+[Ownable]: ./ownable
+[Utils guide]: ./utils#account-identifiers
+[assertPaused]: ./api/security#Pausable-assertPaused
+[assertNotPaused]: ./api/security#Pausable-assertNotPaused
+[Initializable API]: ./api/security#Initializable
+[Allowlist API]: ./api/security#Allowlist
+[Blocklist API]: ./api/security#Blocklist
+[isAllowed]: ./api/security#Allowlist-isAllowed
+[assertAllowed]: ./api/security#Allowlist-assertAllowed
+[_allow]: ./api/security#Allowlist-_allow
+[_disallow]: ./api/security#Allowlist-_disallow
+[isBlocked]: ./api/security#Blocklist-isBlocked
+[assertNotBlocked]: ./api/security#Blocklist-assertNotBlocked
+[_block]: ./api/security#Blocklist-_block
+[_unblock]: ./api/security#Blocklist-_unblock
+[compact#270]: https://github.com/LFDT-Minokawa/compact/issues/270
+
+The following documentation provides context, reasoning, and examples of modules found in the Security directory.
+
+## Initializable
+
+The Initializable module provides a simple mechanism that mimics the functionality of a constructor.
+More specifically, it enables logic to be performed once and only once to set up a contract's initial state.
+This is useful when a constructor cannot be used,
+for example when there are circular dependencies at construction time.
+
+The initializable pattern ensures that implementing contracts:
+
+- Don't allow circuit calls until the contract is initialized.
+- Can only initialize the contract once.
+
+
+ Use this module only at the top contract layer, and import it into at most one module per contract.
+ The Compact compiler collapses a shared import's ledger state inconsistently depending on directory layout
+ ([compact#270]): when two modules in the same directory both import Initializable,
+ their `_isInitialized` flags share a single ledger slot, so initializing one marks the other as initialized
+ and the other can never be initialized on its own.
+ For this reason the OpenZeppelin modules do not import Initializable; each one tracks its own
+ `_isInitialized` ledger flag inline.
+
+
+### Usage
+
+```ts
+// CustomContractStateSetup.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Initializable"
+ prefix Initializable_;
+
+export ledger _fieldAfterDeployment: Field;
+
+export circuit doSomethingBeforeInitialized(): [] {
+ // Ensure action can only be done prior to custom initialization
+ Initializable_assertNotInitialized();
+ // Do the thing
+}
+
+export circuit setFieldAfterDeployment(f: Field): [] {
+ // Initialize so the circuit cannot be called again
+ Initializable_initialize();
+ _fieldAfterDeployment = disclose(f);
+}
+
+export circuit checkFieldAfterDeployment(): Field {
+ // Can only be called after the contract is initialized
+ Initializable_assertInitialized();
+ return _fieldAfterDeployment;
+}
+```
+
+See the [Initializable API] for the full list of circuits.
+
+## Pausable
+
+The Pausable module allows contracts to implement an emergency stop mechanism.
+This can be useful for scenarios such as preventing trades until the end of an evaluation period
+or having an emergency switch to freeze all transactions in the event of a large bug.
+
+To become pausable, the contract should include `pause` and `unpause` circuits (which should be protected).
+For circuits that should be available only when paused or not,
+insert calls to [assertPaused] and [assertNotPaused] respectively.
+
+### Usage
+
+For example (using the [Ownable] module for access control):
+
+```ts
+// OwnablePausable.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
+ prefix Pausable_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+
+constructor(initOwner: Either, ContractAddress>) {
+ Ownable_initialize(initOwner);
+}
+
+export circuit pause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__pause();
+}
+
+export circuit unpause(): [] {
+ Ownable_assertOnlyOwner();
+ Pausable__unpause();
+}
+
+export circuit whenNotPaused(): [] {
+ Pausable_assertNotPaused();
+ // Do something
+}
+
+export circuit whenPaused(): [] {
+ Pausable_assertPaused();
+ // Do something
+}
+```
+
+## Allowlist
+
+The Allowlist module is a minimal, reusable permit-list keyed by a `Bytes<32>` account identifier.
+It answers a single question: is this account a member?
+It backs use cases such as KYC allowlists, permissioned mints, and membership gates.
+
+The module is a membership mechanism, not a policy.
+It holds no access control and no notion of when the list is enforced:
+
+- The mutators [_allow] and [_disallow] are ungated building blocks.
+ The implementing contract must protect them with its own access control module,
+ in the same way a token protects `_mint` and `_burn`.
+- The implementing contract decides where [assertAllowed] applies,
+ for example only on transfers, only on mints, or only while an "eligibility required" flag is set.
+- [isAllowed] reads membership without asserting it.
+
+### What is public
+
+The approved set lives in the public ledger field `_allowed: Set>`.
+Membership is publicly readable and the approved set is publicly enumerable.
+Every circuit discloses the `account` it is called with, so the identifier reaches the ledger as a public input.
+This is the right posture for transparent or public-graph assets.
+An asset whose transaction graph is meant to stay private should not use a public allowlist,
+since the published candidate set would bound its anonymity set.
+
+The `Bytes<32>` keys are account identifiers as derived by `Utils_computeAccountId`;
+see the [Utils guide] for how to derive one off-chain.
+
+### Usage
+
+The following example is a token whose recipients must be approved by the owner before they can receive tokens.
+Membership changes are gated behind [Ownable], and the gate is enforced on `transfer`:
+
+```ts
+// PermissionedToken.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Allowlist"
+ prefix Allowlist_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ initialOwner: Either, ContractAddress>,
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+) {
+ Ownable_initialize(initialOwner);
+ FungibleToken_initialize(name, symbol, decimals);
+}
+
+// Membership changes are protected by the owner.
+export circuit allow(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Allowlist__allow(account);
+}
+
+export circuit disallow(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Allowlist__disallow(account);
+}
+
+export circuit isAllowed(account: Bytes<32>): Boolean {
+ return Allowlist_isAllowed(account);
+}
+
+// The implementing contract decides where the gate applies.
+export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean {
+ assert(to.is_left, "PermissionedToken: recipient must be an account identifier");
+ Allowlist_assertAllowed(to.left);
+ return FungibleToken_transfer(to, value);
+}
+```
+
+See the [Allowlist API] for the full list of circuits.
+
+## Blocklist
+
+The Blocklist module is a minimal, reusable deny-list keyed by a `Bytes<32>` account identifier.
+It answers a single question: is this account blocked?
+It backs use cases such as sanctions lists, account freezes, and abuse denials.
+
+As with Allowlist, the module is a membership mechanism, not a policy:
+
+- The mutators [_block] and [_unblock] are ungated building blocks that the implementing contract
+ must protect with its own access control module.
+- The implementing contract decides where [assertNotBlocked] applies.
+- [isBlocked] reads membership without asserting it.
+
+### What is public
+
+The blocked set lives in the public ledger field `_blocked: Set>`.
+Membership is publicly readable and the blocked set is publicly enumerable,
+and every circuit discloses the `account` it is called with.
+As with Allowlist, this is the right posture for transparent or public-graph assets only.
+
+### Usage
+
+The following example lets the owner freeze accounts, and refuses transfers to a frozen recipient:
+
+```ts
+// FreezableToken.compact
+
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/security/Blocklist"
+ prefix Blocklist_;
+import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
+ prefix Ownable_;
+import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
+ prefix FungibleToken_;
+
+constructor(
+ initialOwner: Either, ContractAddress>,
+ name: Opaque<"string">,
+ symbol: Opaque<"string">,
+ decimals: Uint<8>,
+) {
+ Ownable_initialize(initialOwner);
+ FungibleToken_initialize(name, symbol, decimals);
+}
+
+// Freezing and unfreezing are protected by the owner.
+export circuit block(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Blocklist__block(account);
+}
+
+export circuit unblock(account: Bytes<32>): [] {
+ Ownable_assertOnlyOwner();
+ Blocklist__unblock(account);
+}
+
+export circuit isBlocked(account: Bytes<32>): Boolean {
+ return Blocklist_isBlocked(account);
+}
+
+// The implementing contract decides where the gate applies.
+export circuit transfer(to: Either, ContractAddress>, value: Uint<128>): Boolean {
+ assert(to.is_left, "FreezableToken: recipient must be an account identifier");
+ Blocklist_assertNotBlocked(to.left);
+ return FungibleToken_transfer(to, value);
+}
+```
+
+See the [Blocklist API] for the full list of circuits.
diff --git a/content/contracts-compact/0.4.x/shielded-access-control.mdx b/content/contracts-compact/0.4.x/shielded-access-control.mdx
new file mode 100644
index 00000000..578933c0
--- /dev/null
+++ b/content/contracts-compact/0.4.x/shielded-access-control.mdx
@@ -0,0 +1,226 @@
+---
+title: ShieldedAccessControl
+---
+
+{/* links */}
+[Role-Based Access Control (RBAC)]: https://en.wikipedia.org/wiki/Role-based_access_control
+[AccessControl]: ./access-control.mdx
+[ShieldedAccessControl API]: api/shielded-access-control
+
+[assertOnlyRole]: api/shielded-access-control#ShieldedAccessControl-assertOnlyRole
+[canProveRole]: api/shielded-access-control#ShieldedAccessControl-canProveRole
+[grantRole]: api/shielded-access-control#ShieldedAccessControl-grantRole
+[_grantRole]: api/shielded-access-control#ShieldedAccessControl-_grantRole
+[revokeRole]: api/shielded-access-control#ShieldedAccessControl-revokeRole
+[_revokeRole]: api/shielded-access-control#ShieldedAccessControl-_revokeRole
+[renounceRole]: api/shielded-access-control#ShieldedAccessControl-renounceRole
+[_setRoleAdmin]: api/shielded-access-control#ShieldedAccessControl-_setRoleAdmin
+[getRoleAdmin]: api/shielded-access-control#ShieldedAccessControl-getRoleAdmin
+[computeAccountId]: api/shielded-access-control#ShieldedAccessControl-computeAccountId
+[DEFAULT_ADMIN_ROLE]: api/shielded-access-control#ShieldedAccessControl-DEFAULT_ADMIN_ROLE
+[_operatorRoles]: api/shielded-access-control#ShieldedAccessControl-_operatorRoles
+[_grantedRoleNullifiers]: api/shielded-access-control#ShieldedAccessControl-_grantedRoleNullifiers
+[_roleCommitmentNullifiers]: api/shielded-access-control#ShieldedAccessControl-_roleCommitmentNullifiers
+[wit_secretKey]: api/shielded-access-control#ShieldedAccessControl-wit_secretKey
+[wit_getRoleCommitmentPath]: api/shielded-access-control#ShieldedAccessControl-wit_getRoleCommitmentPath
+
+This module provides a **privacy-preserving** role-based access control mechanism.
+Unlike [AccessControl], which stores role assignments in a public map,
+ShieldedAccessControl stores roles as Merkle tree commitments which hides _who_ holds a role
+while still allowing on-chain verification of authorization.
+
+If your contract does not require privacy for role holders, the simpler [AccessControl] module is recommended.
+
+## Why Shielded Access Control?
+
+In many applications, revealing which accounts hold administrative or privileged roles
+creates a security and privacy risk. ShieldedAccessControl addresses this by ensuring
+that role holders are never identified on-chain. Instead, callers prove ownership of a role
+using zero-knowledge proofs against Merkle tree commitments.
+
+This is useful in scenarios such as:
+
+- **Private governance**: voters or committee members prove eligibility without revealing identity.
+- **Confidential operations**: admins can manage a system without their addresses being publicly linked to the contract.
+- **Pseudonymous authorization**: role holders maintain a stable pseudonymous identity for the lifetime of their role, but that identity cannot be linked to a real-world identity without key compromise.
+
+## How It Works
+
+ShieldedAccessControl is built on three cryptographic primitives:
+
+**Role commitments** are Merkle tree leaves that bind a role to an account without revealing either.
+When a role is granted, a commitment is inserted into a Merkle tree:
+
+```
+roleCommitment = SHA256(role || accountId || instanceSalt || commitmentDomain)
+```
+
+**Account identifiers** are privacy-preserving identity commitments derived from a secret key
+and a per-deployment instance salt. A single secret key can be used across multiple roles within the same contract:
+
+```
+accountId = SHA256(secretKey || instanceSalt || accountIdDomain)
+```
+
+**Role nullifiers** are one-time burn tokens. When a role is revoked, its nullifier is inserted into a set,
+permanently invalidating the corresponding commitment:
+
+```
+roleNullifier = SHA256(roleCommitment || nullifierDomain)
+```
+
+The `instanceSalt` is a per-deployment random value that prevents commitment collisions across contracts
+and ensures that the same secret key produces different identities in different deployments.
+
+### Public ledger and private witnesses
+
+What is on the ledger:
+
+- [_operatorRoles]: the Merkle tree of role commitments. Inserts expose only the internal leaf hash, not the commitment value.
+- [_grantedRoleNullifiers]: the nullifier of every `(role, accountId)` pairing ever granted.
+- [_roleCommitmentNullifiers]: the nullifier of every pairing that has been revoked.
+- `_adminRoles`, the sealed `_instanceSalt`, and `_isInitialized`.
+
+What each role holder keeps private, and supplies through witnesses at proving time:
+
+- The `secretKey`, via [wit_secretKey]. It is hashed with `_instanceSalt` inside the proof and never reaches the ledger.
+- The Merkle path to their role commitment, via [wit_getRoleCommitmentPath].
+ The consumer's witness implementation builds this path from the exported `_operatorRoles` state.
+
+Witness implementations are authored by the consuming DApp.
+The TypeScript witnesses in the repository are test-only and are not published with the package.
+
+## Using `ShieldedAccessControl`
+
+Usage follows the same pattern as [AccessControl] which is to define role identifiers,
+grant them during construction, and enforce them with [assertOnlyRole].
+The key difference is that role assignments are stored as Merkle tree commitments rather than in a public map,
+and revocation uses a nullifier scheme rather than simply flipping a boolean.
+Account identifiers also incorporate a per-instance salt, preventing cross-contract correlation of the same secret key.
+
+```ts
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/access/ShieldedAccessControl"
+ prefix SAC_;
+
+export sealed ledger MINTER_ROLE: Bytes<32>;
+
+constructor(
+ instanceSalt: Bytes<32>,
+ defaultAdmin: SAC_AccountIdentifier,
+ minter: SAC_AccountIdentifier
+) {
+ SAC_initialize(instanceSalt);
+ MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE"));
+ // Grant the default admin role and the minter role
+ SAC__grantRole(SAC_DEFAULT_ADMIN_ROLE(), defaultAdmin);
+ SAC__grantRole(MINTER_ROLE as SAC_RoleIdentifier, minter);
+}
+
+export circuit mint(/* ... */): [] {
+ SAC_assertOnlyRole(MINTER_ROLE as SAC_RoleIdentifier);
+ // ... minting logic
+}
+```
+
+
+ The `instanceSalt` must be nonzero and generated using a cryptographically secure random number generator
+ (e.g. `crypto.getRandomValues()`) prior to deployment. Weak or predictable salts weaken privacy guarantees.
+
+
+### Deriving Account Identifiers
+
+Before an account can be granted a role, the grantor needs the grantee's `AccountIdentifier`.
+This is derived off-chain using [computeAccountId]:
+
+```ts
+const accountId = SAC_computeAccountId(secretKey, instanceSalt);
+```
+
+The `secretKey` is a 32-byte cryptographically secure random value that the user keeps private.
+The `instanceSalt` is the same value used during contract initialization.
+
+
+ The `secretKey` must never be logged, persisted in plaintext, or exposed in browser devtools.
+ Loss of the key means loss of all roles held under that identity. Key exposure allows impersonation
+ and retroactive deanonymization.
+
+
+### Granting and Revoking Roles
+
+Role management works similarly to [AccessControl]. Every role has an associated admin role.
+Only accounts that can prove they hold the admin role may call [grantRole] and [revokeRole].
+By default, all roles are administered by [DEFAULT_ADMIN_ROLE].
+Custom admin relationships can be set using [_setRoleAdmin].
+
+Each `(role, accountId)` pairing has exactly one lifecycle:
+
+- **Grant**: the first grant inserts the role commitment into the Merkle tree and records the pairing's nullifier
+ in [_grantedRoleNullifiers]. Granting a pairing that is already active is a no-op, so a pairing never consumes
+ more than one tree slot.
+- **Revoke**: revoking (or renouncing) inserts the nullifier into [_roleCommitmentNullifiers].
+ A pairing that was never granted cannot be revoked; the call reverts with `role was never granted`.
+
+There is one important difference from unshielded AccessControl:
+**revocation is permanent for a given identity**. When a role is revoked,
+its nullifier is burned, and the same `(role, accountId)` pairing can never be granted again.
+To be re-authorized, the user must generate a new `secretKey` to produce a new `accountId`.
+
+Other roles held under the old secret key remain valid. The user will need to retain both keys
+until all roles under the old key are no longer needed.
+
+### Renouncing Roles
+
+[renounceRole] allows a role holder to voluntarily give up their own role.
+This is useful if a trusted device is compromised and the holder wants to immediately invalidate their credentials.
+The caller must confirm their `accountId` to prevent accidental renunciation,
+and the pairing must have been granted. Consumers with no need for self-revocation should not export this circuit.
+
+### Proving Role Ownership
+
+Two circuits are available for verifying role ownership:
+
+- [assertOnlyRole]: reverts if the caller cannot prove they hold the role. Use this to gate access to circuits.
+- [canProveRole]: returns a boolean without reverting. Useful for conditional logic where lacking a role is not an error.
+
+Both circuits disclose a nullifier, which means every call made under the same role instance
+can be linked by an outside observer. This provides **pseudonymity, not anonymity**.
+
+## Privacy Considerations
+
+ShieldedAccessControl provides strong privacy guarantees, but it's important to understand what is and isn't hidden:
+
+**What observers CAN see:**
+- When roles are granted and revoked
+- All role identifiers and which are admin identifiers (disclosed via [getRoleAdmin], [_setRoleAdmin], [grantRole], and [revokeRole])
+- How many admins exist
+- All role nullifiers (disclosed via `Set` operations in every grant, revoke, and role-proof transaction)
+- The same nullifier across calls, allowing them to link all actions performed under the same role instance over time (pseudonymity, not anonymity)
+- Correlation between a grant and its revocation (the same nullifier appears in both transactions)
+- Which role is being granted or revoked when the admin-checked circuits ([grantRole], [revokeRole]) are used, since those disclose the role identifier in the same transaction
+- The number of distinct `(role, accountId)` pairings ever granted across all roles; per-role counts are also inferable when the admin-checked circuits are used
+
+**What observers CANNOT see:**
+- The identity of any role holder, so long as secret keys are kept private and generated using cryptographically secure random values
+- The value of role commitments stored in the Merkle tree (MerkleTree inserts hash the value internally; only the internal leaf hash appears as a public input, unlike Set operations where values are directly visible)
+
+Users can be retroactively deanonymized if their secret key is exposed.
+
+
+ The internal circuits ([_grantRole], [_revokeRole]) do not disclose the role identifier, offering stronger privacy
+ when wrapped with custom authorization logic. The trade-off is that the implementing contract takes full
+ responsibility for access control.
+
+
+## Security Considerations
+
+- The `_operatorRoles` Merkle tree has a **fixed capacity of 2^20 leaf slots**. Each distinct `(role, accountId)` pairing consumes one slot, re-granting an active pairing is a no-op, and slots cannot be reclaimed after revocation. Once exhausted, the contract can never grant a new role. Monitor slot consumption off-chain (`firstFree()` on the exported tree in TypeScript, `isFull()` in a circuit) and plan capacity accordingly.
+- A single secret key compromise exposes **all roles** held by that user within the contract instance. Users requiring compartmentalization between roles should use separate keys per role. On compromise, coordinate with admins to revoke every role under the compromised `accountId` and re-grant under a new identity.
+- A pairing can only be revoked after it was granted, so the nullifier set holds at most one entry per granted pairing. Admin trust is still a fundamental assumption: an admin can revoke any granted pairing, and revocation is irreversible.
+- The SHA256 hashing function is used throughout.
+
+
+ For the full API reference, see the [ShieldedAccessControl API].
+
diff --git a/content/contracts-compact/0.4.x/utils.mdx b/content/contracts-compact/0.4.x/utils.mdx
new file mode 100644
index 00000000..4c6f5120
--- /dev/null
+++ b/content/contracts-compact/0.4.x/utils.mdx
@@ -0,0 +1,147 @@
+---
+title: Utils
+---
+
+{/* links */}
+[Utils API]: ./api/utils#Utils
+[computeAccountId]: ./api/utils#Utils-computeAccountId
+[zeroAccount]: ./api/utils#Utils-zeroAccount
+[isTargetZero]: ./api/utils#Utils-isTargetZero
+[selfAsRecipient]: ./api/utils#Utils-selfAsRecipient
+[EvmAbi API]: ./api/utils#EvmAbi
+[uint8Word]: ./api/utils#EvmAbi-uint8Word
+[uint64Word]: ./api/utils#EvmAbi-uint64Word
+[uint128Word]: ./api/utils#EvmAbi-uint128Word
+[boolWord]: ./api/utils#EvmAbi-boolWord
+[Ownable]: ./ownable
+[AccessControl]: ./access-control
+[Crypto guide]: ./crypto
+
+The Utils directory contains two modules.
+The Utils module provides miscellaneous circuits and common utilities for Compact contract development.
+Most of them are pure circuits with no ledger state, so they can be called from a circuit,
+from a constructor, or off-chain through the compiled contract.
+The EvmAbi module encodes values as Ethereum ABI words, for messages that must hash identically in Compact and on the EVM.
+
+## Account identifiers
+
+The [Ownable] and [AccessControl] modules, and the token modules, identify a caller by a `Bytes<32>` account identifier
+rather than by a public key.
+The caller proves knowledge of a 32-byte secret key through a witness, and the module derives the identifier as:
+
+```
+accountId = H(secretKey)
+```
+
+where `H` is `persistentHash`.
+[computeAccountId] is the shared derivation: it is a pure circuit, so the same function that the modules
+evaluate inside a proof can be evaluated ahead of time to produce the identifier a contract expects as input,
+for example the `initialOwner` of an Ownable contract, the `defaultAdmin` of an AccessControl contract,
+or the recipient of a token mint.
+
+The witnesses that supply the secret key are authored by the implementing contract's TypeScript side.
+The witnesses in the library repository are test-only and are not published with the package.
+
+### Deriving an identifier off-chain
+
+Any of the following produce the same identifier for the same key:
+
+* Call the pure circuit through the compiled contract: `pureCircuits.computeAccountId(secretKey)`
+ (exposed by any contract that re-exports it as a `pure circuit`).
+* Compute it directly with `@midnight-ntwrk/compact-runtime`:
+
+```typescript
+import {
+ CompactTypeBytes,
+ CompactTypeVector,
+ persistentHash,
+} from '@midnight-ntwrk/compact-runtime';
+
+const accountIdOf = (secretKey: Uint8Array): Uint8Array =>
+ persistentHash(new CompactTypeVector(1, new CompactTypeBytes(32)), [secretKey]);
+```
+
+The secret key must be a 32-byte cryptographically secure random value (`crypto.getRandomValues()` or equivalent).
+
+
+ The secret key is the identity. Never log or persist it in plaintext, keep it in offline or enclave storage
+ where possible, and avoid evaluating the derivation in an untrusted runtime. A lost key cannot be recovered,
+ and an exposed key allows impersonation.
+
+
+### Scope of the identifier
+
+The derivation is deliberately untagged and global: the same secret key yields the same identifier in every
+module and every contract that derives one, so a user who wants to carry a single identity across contracts can.
+Users who want unlinkable identities across contracts should use a different secret key per contract.
+A module that needs a per-deployment, unlinkable identity should not use this circuit.
+
+The output is an identifier, not a secret. Because the construction carries no domain-separation tag,
+it must not be reused to derive private values: a circuit that derives a private value from a secret
+must use its own domain-separation tag.
+
+## Zero accounts
+
+[zeroAccount] returns the canonical zero `Either, ContractAddress>` (the left variant set to zero),
+which the token modules use as the mint and burn sentinel and for cleared approvals.
+[isTargetZero] checks whether the active side of such a value is zero.
+
+## Contract as recipient
+
+[selfAsRecipient] wraps the executing contract's own address as an `Either`,
+the recipient shape expected by shielded send operations, for deposits into the contract and for receiving change.
+
+## EvmAbi
+
+The [EvmAbi API] supplies the encoders a contract needs to hash the exact bytes an EVM signer hashed.
+`keccak256(value)` hashes Compact's binary representation of `T`, which coincides with `abi.encode` for some types
+and diverges for others:
+
+* `Bytes` is emitted as exactly `n` bytes. A `Bytes<32>` already is an ABI word,
+and `keccak256>>(words)` is byte-identical to Solidity's `keccak256(abi.encode(word0, ..., wordN))`.
+* Integers cast with `as Bytes` are emitted little-endian at their own width (a `Uint<64>` occupies 8 bytes),
+while `abi.encode(uint256)` is a big-endian 32-byte word. [uint8Word], [uint64Word], and [uint128Word] produce that word.
+* `Boolean` is emitted as a single byte, while `abi.encode(bool)` is a full word holding 0 or 1 in its least significant byte.
+[boolWord] produces that word.
+
+A message is assembled as a `Vector>` of ABI words and hashed with `keccak256` to reproduce an EVM digest exactly.
+A Compact `enum` reaches a digest as `uint8Word(kind as Uint<8>)`, matching Solidity, where an enum ABI-encodes as its `uint8` ordinal.
+Pick the narrowest encoder that fits the value: [uint128Word] costs roughly twice [uint64Word] for the same 32-byte word.
+
+These are encoding primitives only. The signing envelope (a raw digest, EIP-191, or EIP-712 typed data)
+belongs to the contract that owns the message; the [Crypto guide] composes EvmAbi with the Eip712 and Ecdsa modules.
+
+
+EvmAbi needs no `keccak256` of its own and compiles on either ZKIR backend.
+Hashing its words with `keccak256` requires ZKIR v3 (`--feature-zkir-v3`).
+
+
+## Usage
+
+```typescript
+pragma language_version >= {{compact_language_version}};
+
+import CompactStandardLibrary;
+import "./node_modules/@openzeppelin/compact-contracts/utils/Utils"
+ prefix Utils_;
+
+export circuit performActionWhenEqual(
+ a: Either,
+ b: Either,
+): [] {
+ const isEqual = Utils_isKeyOrAddressEqual(a, b);
+ if (isEqual) {
+ // Do something
+ } else {
+ // Do something else
+ }
+}
+
+// Re-export the derivation so the identifier can be computed off-chain
+// through the compiled contract.
+export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
+ return Utils_computeAccountId(secretKey);
+}
+```
+
+See the [Utils API] for the full list of circuits.
diff --git a/content/contracts-compact/0.4.x/utils/constants.js b/content/contracts-compact/0.4.x/utils/constants.js
new file mode 100644
index 00000000..614f46a8
--- /dev/null
+++ b/content/contracts-compact/0.4.x/utils/constants.js
@@ -0,0 +1,2 @@
+export const COMPACT_COMPILER_VERSION = "0.34.0";
+export const COMPACT_LANGUAGE_VERSION = "0.26.0";
diff --git a/content/contracts-compact/0.4.x/utils/replacements.ts b/content/contracts-compact/0.4.x/utils/replacements.ts
new file mode 100644
index 00000000..ae8b306d
--- /dev/null
+++ b/content/contracts-compact/0.4.x/utils/replacements.ts
@@ -0,0 +1,9 @@
+import { COMPACT_COMPILER_VERSION, COMPACT_LANGUAGE_VERSION } from "./constants";
+
+export const REPLACEMENTS = {
+ include: ['**/content/contracts-compact/0.4.x/**/*.mdx'],
+ replacements: {
+ compact_compiler_version: COMPACT_COMPILER_VERSION,
+ compact_language_version: COMPACT_LANGUAGE_VERSION
+ }
+}
diff --git a/content/contracts-compact/0.4.x/zk-circuits-101.mdx b/content/contracts-compact/0.4.x/zk-circuits-101.mdx
new file mode 100644
index 00000000..5e954d15
--- /dev/null
+++ b/content/contracts-compact/0.4.x/zk-circuits-101.mdx
@@ -0,0 +1,36 @@
+---
+title: ZK Circuits 101
+---
+
+{/* links */}
+[the math behind the scenes needs it that way]: https://zkhack.dev/whiteboard/module-three/
+
+## What are ZK Circuits?
+
+Compact code is compiled into arithmetic circuits, which are mathematical representations of the contract's logic.
+These circuits are made up of arithmetic gates.
+The gates enforce the rules and constraints defined in the Compact program.
+At a high level, ZK circuits can be thought of as a magic puzzle board that proves a series of steps was followed correctly - like a recipe.
+The puzzle board is laid out in a grid like a giant sheet of graph paper with a certain number of rows.
+Each row is a space where a step or rule that needs to be followed is written.
+These steps/rules correspond to the gates that make up the arithmetic circuit.
+
+## Circuit Size and Domain
+
+The size of the board is called the **domain size** which is referred to as `k` in the documentation.
+It’s always a power of 2 (like 256, 512, 1024, etc.),
+because [the math behind the scenes needs it that way].
+Now, just because the board has 1024 rows doesn’t mean it uses all of them.
+Maybe the recipe takes only 563 steps, so only 563 rows are filled and the rest are left blank.
+These filled-in rows are called **used rows**.
+So "k = 10, rows = 563" in the API Reference documentation that means "this circuit has a size of 2^10 = 1024 rows and only uses 563 rows".
+
+## Why Circuit Size Matters
+
+Why is this important? Well, when writing ZK circuits the size and number of rules to follow should be as small as possible.
+The number of rules in a zero-knowledge circuit directly impacts both the prover time
+(how long it takes to generate a proof) and, to a lesser extent, the proof size and verifier time.
+Larger circuits with more rules require more computation to generate a proof,
+which can make proof generation slower and more resource-intensive.
+This is especially relevant for privacy-preserving blockchains like Midnight,
+where proof generation is often the most computationally expensive part of a transaction.
diff --git a/content/contracts-compact/compact-replacements.ts b/content/contracts-compact/compact-replacements.ts
new file mode 100644
index 00000000..494ab81a
--- /dev/null
+++ b/content/contracts-compact/compact-replacements.ts
@@ -0,0 +1,9 @@
+import { REPLACEMENTS as zeroOneXReplacements } from "./0.1.x/utils/replacements";
+import { REPLACEMENTS as zeroThreeXReplacements } from "./0.3.x/utils/replacements";
+import { REPLACEMENTS as zeroFourXReplacements } from "./0.4.x/utils/replacements";
+
+export const COMPACT_REPLACEMENTS = [
+ zeroOneXReplacements,
+ zeroThreeXReplacements,
+ zeroFourXReplacements,
+];
diff --git a/content/contracts-compact/index.mdx b/content/contracts-compact/index.mdx
index 1963a214..07fcecc9 100644
--- a/content/contracts-compact/index.mdx
+++ b/content/contracts-compact/index.mdx
@@ -2,174 +2,102 @@
title: Contracts for Compact
---
-{/* links */}
-[Midnight]: https://midnight.network/
-[nvm]: https://github.com/nvm-sh/nvm
-[yarn]: https://yarnpkg.com/getting-started/install
-[compact-dev-tools]: https://docs.midnight.network/blog/compact-developer-tools
-
-**A library for secure smart contract development** written in Compact for [Midnight].
-This library consists of modules to build custom smart contracts.
-
-
-This repo contains highly experimental code.
-Expect rapid iteration. **Use at your own risk.**
-
-
-## Usage
-
-Make sure you have [nvm] and [yarn] installed on your machine.
-
-Follow Midnight's [Compact Developer Tools][compact-dev-tools] installation guide and confirm that `compact` is in the `PATH` env variable.
-
-```bash
-$ compact compile --version
-
-Compactc version: {{compact_compiler_version}}
-{{compact_compiler_version}}
-```
-
-### Installation
-
-Create a directory for your project.
-
-```bash
-mkdir my-project
-cd my-project
-```
-
-Install the library.
-
-```bash
-yarn add @openzeppelin/compact-contracts
-```
-
-### Write a custom contract using library modules
-
-In the root of `my-project`, create a custom contract using OpenZeppelin Compact modules.
-Import the modules through `./node_modules/@openzeppelin/compact-contracts/...`.
-
-```typescript
-// MyContract.compact
-
-pragma language_version >= {{compact_language_version}};
-
-import CompactStandardLibrary;
-import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
- prefix Ownable_;
-import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
- prefix Pausable_;
-import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
- prefix FungibleToken_;
-
-constructor(
- _name: Opaque<"string">,
- _symbol: Opaque<"string">,
- _decimals: Uint<8>,
- _recipient: Either, ContractAddress>,
- _amount: Uint<128>,
- _initOwner: Either, ContractAddress>,
-) {
- Ownable_initialize(_initOwner);
- FungibleToken_initialize(_name, _symbol, _decimals);
- FungibleToken__mint(_recipient, _amount);
-}
-
-/** IFungibleToken */
-
-export circuit name(): Opaque<"string"> {
- return FungibleToken_name();
-}
-
-export circuit symbol(): Opaque<"string"> {
- return FungibleToken_symbol();
-}
-
-export circuit decimals(): Uint<8> {
- return FungibleToken_decimals();
-}
-
-export circuit totalSupply(): Uint<128> {
- return FungibleToken_totalSupply();
-}
-
-export circuit balanceOf(account: Either, ContractAddress>): Uint<128> {
- return FungibleToken_balanceOf(account);
-}
-
-export circuit allowance(
- owner: Either, ContractAddress>,
- spender: Either, ContractAddress>
-): Uint<128> {
- return FungibleToken_allowance(owner, spender);
-}
-
-export circuit transfer(
- to: Either, ContractAddress>,
- value: Uint<128>,
-): Boolean {
- Pausable_assertNotPaused();
- return FungibleToken_transfer(to, value);
-}
-
-export circuit transferFrom(
- fromAddress: Either, ContractAddress>,
- to: Either, ContractAddress>,
- value: Uint<128>
-): Boolean {
- Pausable_assertNotPaused();
- return FungibleToken_transferFrom(fromAddress, to, value);
-}
-
-export circuit approve(spender: Either, ContractAddress>, value: Uint<128>): Boolean {
- Pausable_assertNotPaused();
- return FungibleToken_approve(spender, value);
-}
-
-/** IOwnable */
-
-export circuit owner(): Either, ContractAddress> {
- return Ownable_owner();
-}
-
-export circuit transferOwnership(newOwner: Either, ContractAddress>): [] {
- return Ownable_transferOwnership(newOwner);
-}
-
-export circuit renounceOwnership(): [] {
- return Ownable_renounceOwnership();
-}
-
-/** IPausable */
-
-export circuit pause(): [] {
- Ownable_assertOnlyOwner();
- Pausable__pause();
-}
-
-export circuit unpause(): [] {
- Ownable_assertOnlyOwner();
- Pausable__unpause();
-}
-```
-
-### Compile the contract
-
-In the project root, compile the contract.
-
-```bash
-% compact compile MyContract.compact artifacts/MyContract
-Compiling 11 circuits:
- circuit "allowance" (k=11, rows=1352)
- circuit "approve" (k=13, rows=3075)
- circuit "balanceOf" (k=10, rows=673)
- circuit "decimals" (k=6, rows=28)
- circuit "name" (k=6, rows=28)
- circuit "pause" (k=13, rows=2365)
- circuit "symbol" (k=6, rows=28)
- circuit "totalSupply" (k=6, rows=28)
- circuit "transfer" (k=13, rows=3990)
- circuit "transferFrom" (k=13, rows=4972)
- circuit "unpause" (k=13, rows=2362)
-Overall progress [====================] 11/11
-```
+import { latestStable } from "./latest-versions.js";
+
+**OpenZeppelin Contracts for Compact** is a library of modules for secure smart contract development on Midnight.
+
+## Getting Started
+
+
+
+ Install the library, write a contract from its modules, and compile it.
+
+
+ Compose contracts from modules with the Module/Contract pattern.
+
+
+ Learn how Compact code compiles to circuits and what drives their cost.
+
+
+
+## Modules
+
+
+
+ Restrict circuits to a single owner, with a shielded-owner variant.
+
+
+ Grant and enforce permissions with role-based access control.
+
+
+ Role-based access control that hides who holds each role.
+
+
+ Initializable, Pausable, Allowlist, and Blocklist modules.
+
+
+ Fungible tokens, an approximation of EIP-20.
+
+
+ Non-fungible tokens, an approximation of EIP-721.
+
+
+ Multiple token types in one contract, an approximation of EIP-1155.
+
+
+ Mint and burn Midnight shielded coins of the contract's own color.
+
+
+ Fungible tokens with ElGamal-encrypted balances on the public ledger.
+
+
+ ElGamal and EcdhMask encryption over Jubjub, the ECDSA low-s rule, and the EIP-712 envelope.
+
+
+ Signer registry, proposals, treasuries, and threshold-ECDSA presets for contract-held funds.
+
+
+ Account identifiers, helper circuits, and EVM ABI word encoding.
+
+
+
+## API Reference
+
+
+
+ Circuits, ledger state, and errors of the AccessControl module.
+
+
+ Circuits, ledger state, and errors of the ConfidentialFungibleToken module.
+
+
+ Circuits and types of the ElGamal, EcdhMask, Ecdsa, and Eip712 modules.
+
+
+ Circuits, ledger state, and errors of the FungibleToken module.
+
+
+ Circuits, ledger state, and errors of the MultiToken module.
+
+
+ Circuits, ledger state, and errors of the multisig modules.
+
+
+ Circuits, ledger state, and errors of the NativeShieldedToken module.
+
+
+ Circuits, ledger state, and errors of the NonFungibleToken module.
+
+
+ Circuits, ledger state, and errors of the Ownable module.
+
+
+ Circuits and errors of the Initializable, Pausable, Allowlist, and Blocklist modules.
+
+
+ Circuits, ledger state, and errors of the ShieldedAccessControl module.
+
+
+ Circuits of the Utils and EvmAbi modules.
+
+
diff --git a/content/contracts-compact/latest-version.js b/content/contracts-compact/latest-version.js
deleted file mode 100644
index 52662a23..00000000
--- a/content/contracts-compact/latest-version.js
+++ /dev/null
@@ -1 +0,0 @@
-export const latestStable = "0.1.x";
diff --git a/content/contracts-compact/latest-versions.js b/content/contracts-compact/latest-versions.js
new file mode 100644
index 00000000..d6f8f170
--- /dev/null
+++ b/content/contracts-compact/latest-versions.js
@@ -0,0 +1 @@
+export const latestStable = "0.4.x";
diff --git a/netlify.toml b/netlify.toml
index 451e4f8e..86c5765d 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -136,9 +136,29 @@ status = 301
# Midnight Compact
[[redirects]]
-from = "/contracts-compact/0.0.1/*"
-to = "/contracts-compact/:splat"
-status = 301
+ from = "/contracts-compact/0.1.x/*"
+ to = "/contracts-compact/0.1.x/:splat"
+ status = 200
+
+[[redirects]]
+ from = "/contracts-compact/0.3.x/*"
+ to = "/contracts-compact/0.3.x/:splat"
+ status = 200
+
+[[redirects]]
+ from = "/contracts-compact/0.4.x/*"
+ to = "/contracts-compact/0.4.x/:splat"
+ status = 200
+
+[[redirects]]
+ from = "/contracts-compact/0.0.1/*"
+ to = "/contracts-compact/0.4.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/contracts-compact/*"
+ to = "/contracts-compact/0.4.x/:splat"
+ status = 301
# Stylus
diff --git a/public/llms.txt b/public/llms.txt
index 4e4f94f4..856786a4 100644
--- a/public/llms.txt
+++ b/public/llms.txt
@@ -162,6 +162,20 @@ Each ecosystem section lists the smart-contract libraries and language-specific
- [Tokens — Non-Fungible](https://docs.openzeppelin.com/stellar-contracts/tokens/non-fungible/non-fungible)
- [Tokens — RWA](https://docs.openzeppelin.com/stellar-contracts/tokens/rwa/rwa)
- [Tokens — Vault](https://docs.openzeppelin.com/stellar-contracts/tokens/vault/vault)
+- [Tokens — Confidential — Confidential Token](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/confidential)
+- [Tokens — Confidential — Privacy Model](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/privacy-model)
+- [Tokens — Confidential — Proof System](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/proof-system)
+- [Tokens — Confidential — Operations](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/operations)
+- [Tokens — Confidential — Operations — Spenders](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/spenders)
+- [Tokens — Confidential — Auditing](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/auditing)
+- [Tokens — Confidential — Selective Disclosure](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/selective-disclosure)
+- [Tokens — Confidential — Compliance](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/compliance)
+- [Tokens — Confidential — Compliance — Clawback](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/clawback)
+- [Tokens — Confidential — Wallet Integration](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/wallet-integration)
+- [Tokens — Confidential — Wallet Integration — Indexing and Recovery](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/indexing)
+- [Tokens — Confidential — Best Practices — Account Holders](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/best-practices/account-holders)
+- [Tokens — Confidential — Best Practices — Administrators](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/best-practices/administrators)
+- [Tokens — Confidential — Best Practices — Auditors](https://docs.openzeppelin.com/stellar-contracts/tokens/confidential/best-practices/auditors)
- [Access — Access Control](https://docs.openzeppelin.com/stellar-contracts/access/access-control)
- [Access — Ownable](https://docs.openzeppelin.com/stellar-contracts/access/ownable)
- [Governance — Governor](https://docs.openzeppelin.com/stellar-contracts/governance/governor)
@@ -170,6 +184,7 @@ Each ecosystem section lists the smart-contract libraries and language-specific
- [Utilities — Pausable](https://docs.openzeppelin.com/stellar-contracts/utils/pausable)
- [Utilities — Upgradeable](https://docs.openzeppelin.com/stellar-contracts/utils/upgradeable)
- [Utilities — Math — WAD](https://docs.openzeppelin.com/stellar-contracts/utils/math/wad): High-precision decimal arithmetic
+- [Utilities — Math — Fixed-Point](https://docs.openzeppelin.com/stellar-contracts/utils/math/fixed-point): Full-precision x * y / denominator for i128 and I256 with explicit rounding
- [Utilities — Cryptography](https://docs.openzeppelin.com/stellar-contracts/utils/crypto/crypto)
- [Utilities — Cryptography — Merkle Distributor](https://docs.openzeppelin.com/stellar-contracts/utils/crypto/merkle-distributor)
- [Fee Abstraction](https://docs.openzeppelin.com/stellar-contracts/fee-abstraction)
@@ -395,27 +410,83 @@ Each ecosystem section lists the smart-contract libraries and language-specific
## Midnight
+- [Getting Started](https://docs.openzeppelin.com/contracts-compact)
+
### Midnight Contracts
-- [Overview](https://docs.openzeppelin.com/contracts-compact)
-- [Learn — Extensibility](https://docs.openzeppelin.com/contracts-compact/extensibility)
-- [Learn — ZK Circuits 101](https://docs.openzeppelin.com/contracts-compact/zk-circuits-101)
-- [Modules — Access — Ownable](https://docs.openzeppelin.com/contracts-compact/ownable)
-- [Modules — Access — AccessControl](https://docs.openzeppelin.com/contracts-compact/access-control)
-- [Modules — Access — ShieldedAccessControl](https://docs.openzeppelin.com/contracts-compact/shielded-access-control)
-- [Modules — Security](https://docs.openzeppelin.com/contracts-compact/security)
-- [Modules — Tokens — FungibleToken](https://docs.openzeppelin.com/contracts-compact/fungible-token)
-- [Modules — Tokens — NonFungibleToken](https://docs.openzeppelin.com/contracts-compact/non-fungible-token)
-- [Modules — Tokens — MultiToken](https://docs.openzeppelin.com/contracts-compact/multi-token)
-- [Modules — Utils](https://docs.openzeppelin.com/contracts-compact/utils)
-- [API Reference — AccessControl API](https://docs.openzeppelin.com/contracts-compact/api/access-control)
-- [API Reference — FungibleToken API](https://docs.openzeppelin.com/contracts-compact/api/fungible-token)
-- [API Reference — MultiToken API](https://docs.openzeppelin.com/contracts-compact/api/multi-token)
-- [API Reference — NonFungibleToken API](https://docs.openzeppelin.com/contracts-compact/api/non-fungible-token)
-- [API Reference — Ownable API](https://docs.openzeppelin.com/contracts-compact/api/ownable)
-- [API Reference — Security API](https://docs.openzeppelin.com/contracts-compact/api/security)
-- [API Reference — ShieldedAccessControl API](https://docs.openzeppelin.com/contracts-compact/api/shielded-access-control)
-- [API Reference — Utils API](https://docs.openzeppelin.com/contracts-compact/api/utils)
+- [Overview](https://docs.openzeppelin.com/contracts-compact/0.4.x)
+- [Learn — Extensibility](https://docs.openzeppelin.com/contracts-compact/0.4.x/extensibility)
+- [Learn — ZK Circuits 101](https://docs.openzeppelin.com/contracts-compact/0.4.x/zk-circuits-101)
+- [Modules — Access — Ownable](https://docs.openzeppelin.com/contracts-compact/0.4.x/ownable)
+- [Modules — Access — AccessControl](https://docs.openzeppelin.com/contracts-compact/0.4.x/access-control)
+- [Modules — Access — ShieldedAccessControl](https://docs.openzeppelin.com/contracts-compact/0.4.x/shielded-access-control)
+- [Modules — Security](https://docs.openzeppelin.com/contracts-compact/0.4.x/security)
+- [Modules — Tokens — FungibleToken](https://docs.openzeppelin.com/contracts-compact/0.4.x/fungible-token)
+- [Modules — Tokens — NonFungibleToken](https://docs.openzeppelin.com/contracts-compact/0.4.x/non-fungible-token)
+- [Modules — Tokens — MultiToken](https://docs.openzeppelin.com/contracts-compact/0.4.x/multi-token)
+- [Modules — Tokens — NativeShieldedToken](https://docs.openzeppelin.com/contracts-compact/0.4.x/native-shielded-token)
+- [Modules — Tokens — ConfidentialFungibleToken](https://docs.openzeppelin.com/contracts-compact/0.4.x/confidential-fungible-token)
+- [Modules — Crypto](https://docs.openzeppelin.com/contracts-compact/0.4.x/crypto)
+- [Modules — Multisig](https://docs.openzeppelin.com/contracts-compact/0.4.x/multisig)
+- [Modules — Utils](https://docs.openzeppelin.com/contracts-compact/0.4.x/utils)
+- [API Reference — AccessControl API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/access-control)
+- [API Reference — ConfidentialFungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/confidential-fungible-token)
+- [API Reference — Crypto API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/crypto)
+- [API Reference — FungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/fungible-token)
+- [API Reference — MultiToken API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/multi-token)
+- [API Reference — Multisig API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/multisig)
+- [API Reference — NativeShieldedToken API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/native-shielded-token)
+- [API Reference — NonFungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/non-fungible-token)
+- [API Reference — Ownable API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/ownable)
+- [API Reference — Security API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/security)
+- [API Reference — ShieldedAccessControl API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/shielded-access-control)
+- [API Reference — Utils API](https://docs.openzeppelin.com/contracts-compact/0.4.x/api/utils)
+- [Previous Versions — 0.3.x — Overview](https://docs.openzeppelin.com/contracts-compact/0.3.x)
+- [Previous Versions — 0.3.x — Learn — Extensibility](https://docs.openzeppelin.com/contracts-compact/0.3.x/extensibility)
+- [Previous Versions — 0.3.x — Learn — ZK Circuits 101](https://docs.openzeppelin.com/contracts-compact/0.3.x/zk-circuits-101)
+- [Previous Versions — 0.3.x — Modules — Access — Ownable](https://docs.openzeppelin.com/contracts-compact/0.3.x/ownable)
+- [Previous Versions — 0.3.x — Modules — Access — AccessControl](https://docs.openzeppelin.com/contracts-compact/0.3.x/access-control)
+- [Previous Versions — 0.3.x — Modules — Access — ShieldedAccessControl](https://docs.openzeppelin.com/contracts-compact/0.3.x/shielded-access-control)
+- [Previous Versions — 0.3.x — Modules — Security](https://docs.openzeppelin.com/contracts-compact/0.3.x/security)
+- [Previous Versions — 0.3.x — Modules — Tokens — FungibleToken](https://docs.openzeppelin.com/contracts-compact/0.3.x/fungible-token)
+- [Previous Versions — 0.3.x — Modules — Tokens — NonFungibleToken](https://docs.openzeppelin.com/contracts-compact/0.3.x/non-fungible-token)
+- [Previous Versions — 0.3.x — Modules — Tokens — MultiToken](https://docs.openzeppelin.com/contracts-compact/0.3.x/multi-token)
+- [Previous Versions — 0.3.x — Modules — Tokens — NativeShieldedToken](https://docs.openzeppelin.com/contracts-compact/0.3.x/native-shielded-token)
+- [Previous Versions — 0.3.x — Modules — Tokens — ConfidentialFungibleToken](https://docs.openzeppelin.com/contracts-compact/0.3.x/confidential-fungible-token)
+- [Previous Versions — 0.3.x — Modules — Crypto](https://docs.openzeppelin.com/contracts-compact/0.3.x/crypto)
+- [Previous Versions — 0.3.x — Modules — Multisig](https://docs.openzeppelin.com/contracts-compact/0.3.x/multisig)
+- [Previous Versions — 0.3.x — Modules — Utils](https://docs.openzeppelin.com/contracts-compact/0.3.x/utils)
+- [Previous Versions — 0.3.x — API Reference — AccessControl API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/access-control)
+- [Previous Versions — 0.3.x — API Reference — ConfidentialFungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/confidential-fungible-token)
+- [Previous Versions — 0.3.x — API Reference — Crypto API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/crypto)
+- [Previous Versions — 0.3.x — API Reference — FungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/fungible-token)
+- [Previous Versions — 0.3.x — API Reference — MultiToken API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/multi-token)
+- [Previous Versions — 0.3.x — API Reference — Multisig API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/multisig)
+- [Previous Versions — 0.3.x — API Reference — NativeShieldedToken API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/native-shielded-token)
+- [Previous Versions — 0.3.x — API Reference — NonFungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/non-fungible-token)
+- [Previous Versions — 0.3.x — API Reference — Ownable API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/ownable)
+- [Previous Versions — 0.3.x — API Reference — Security API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/security)
+- [Previous Versions — 0.3.x — API Reference — ShieldedAccessControl API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/shielded-access-control)
+- [Previous Versions — 0.3.x — API Reference — Utils API](https://docs.openzeppelin.com/contracts-compact/0.3.x/api/utils)
+- [Previous Versions — 0.1.x — Overview](https://docs.openzeppelin.com/contracts-compact/0.1.x)
+- [Previous Versions — 0.1.x — Learn — Extensibility](https://docs.openzeppelin.com/contracts-compact/0.1.x/extensibility)
+- [Previous Versions — 0.1.x — Learn — ZK Circuits 101](https://docs.openzeppelin.com/contracts-compact/0.1.x/zk-circuits-101)
+- [Previous Versions — 0.1.x — Modules — Access — Ownable](https://docs.openzeppelin.com/contracts-compact/0.1.x/ownable)
+- [Previous Versions — 0.1.x — Modules — Access — AccessControl](https://docs.openzeppelin.com/contracts-compact/0.1.x/access-control)
+- [Previous Versions — 0.1.x — Modules — Access — ShieldedAccessControl](https://docs.openzeppelin.com/contracts-compact/0.1.x/shielded-access-control)
+- [Previous Versions — 0.1.x — Modules — Security](https://docs.openzeppelin.com/contracts-compact/0.1.x/security)
+- [Previous Versions — 0.1.x — Modules — Tokens — FungibleToken](https://docs.openzeppelin.com/contracts-compact/0.1.x/fungible-token)
+- [Previous Versions — 0.1.x — Modules — Tokens — NonFungibleToken](https://docs.openzeppelin.com/contracts-compact/0.1.x/non-fungible-token)
+- [Previous Versions — 0.1.x — Modules — Tokens — MultiToken](https://docs.openzeppelin.com/contracts-compact/0.1.x/multi-token)
+- [Previous Versions — 0.1.x — Modules — Utils](https://docs.openzeppelin.com/contracts-compact/0.1.x/utils)
+- [Previous Versions — 0.1.x — API Reference — AccessControl API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/access-control)
+- [Previous Versions — 0.1.x — API Reference — FungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/fungible-token)
+- [Previous Versions — 0.1.x — API Reference — MultiToken API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/multi-token)
+- [Previous Versions — 0.1.x — API Reference — NonFungibleToken API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/non-fungible-token)
+- [Previous Versions — 0.1.x — API Reference — Ownable API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/ownable)
+- [Previous Versions — 0.1.x — API Reference — Security API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/security)
+- [Previous Versions — 0.1.x — API Reference — ShieldedAccessControl API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/shielded-access-control)
+- [Previous Versions — 0.1.x — API Reference — Utils API](https://docs.openzeppelin.com/contracts-compact/0.1.x/api/utils)
## Zama FHEVM
diff --git a/source.config.ts b/source.config.ts
index c1c81411..99374168 100644
--- a/source.config.ts
+++ b/source.config.ts
@@ -4,7 +4,7 @@ import rehypeKatex from "rehype-katex";
import { remarkMdxMermaid } from "fumadocs-core/mdx-plugins";
import remarkReplace from "@/lib/remark-replace";
import { CAIRO_REPLACEMENTS as cairoContractReplacements } from "content/contracts-cairo/cairo-replacements";
-import { REPLACEMENTS as compactContractReplacements } from "content/contracts-compact/utils/replacements";
+import { COMPACT_REPLACEMENTS as compactContractReplacements } from "content/contracts-compact/compact-replacements";
// You can customise Zod schemas for frontmatter and `meta.json` here
// see https://fumadocs.vercel.app/docs/mdx/collections#define-docs
@@ -35,7 +35,7 @@ export default defineConfig({
remarkMdxMermaid,
[
remarkReplace,
- [...cairoContractReplacements, compactContractReplacements],
+ [...cairoContractReplacements, ...compactContractReplacements],
],
],
rehypePlugins: (v) => [rehypeKatex, ...v],
diff --git a/src/components/banners/version-banner.tsx b/src/components/banners/version-banner.tsx
index fece6529..9ea9e557 100644
--- a/src/components/banners/version-banner.tsx
+++ b/src/components/banners/version-banner.tsx
@@ -1,6 +1,7 @@
"use client";
import { latestStable } from "content/contracts-cairo/latest-versions";
+import { latestStable as compactLatestStable } from "content/contracts-compact/latest-versions";
import { latestStable as suiLatestStable } from "content/contracts-sui/latest-versions";
import { Callout } from "fumadocs-ui/components/callout";
import Link from "next/link";
@@ -25,6 +26,10 @@ export function VersionBanner() {
version: latestStable,
path: `/contracts-cairo/${latestStable}`,
},
+ "contracts-compact": {
+ version: compactLatestStable,
+ path: `/contracts-compact/${compactLatestStable}`,
+ },
"contracts-sui": {
version: suiLatestStable,
path: `/contracts-sui/${suiLatestStable}`,
diff --git a/src/lib/export-search-indexes.ts b/src/lib/export-search-indexes.ts
index 5a37b5af..b7c29ea2 100644
--- a/src/lib/export-search-indexes.ts
+++ b/src/lib/export-search-indexes.ts
@@ -1,8 +1,17 @@
import { latestStable as cairoLatestStable } from "content/contracts-cairo/latest-versions";
+import { latestStable as compactLatestStable } from "content/contracts-compact/latest-versions";
import type { DocumentRecord } from "fumadocs-core/search/algolia";
import type { Page } from "fumadocs-core/source";
import { source } from "@/lib/source";
+const isCompactPage = (url: string) =>
+ url === "/contracts-compact" || url.startsWith("/contracts-compact/");
+
+const isCurrentCompactPage = (url: string) =>
+ url === "/contracts-compact" ||
+ url === `/contracts-compact/${compactLatestStable}` ||
+ url.startsWith(`/contracts-compact/${compactLatestStable}/`);
+
export async function exportSearchIndexes() {
const results: DocumentRecord[] = [];
@@ -18,12 +27,13 @@ export async function exportSearchIndexes() {
const filteredPages = source
.getPages()
- .filter(
- (page: Page) =>
- page.url.startsWith("/contracts-sui/") ||
- page.url === `/contracts-cairo/${cairoLatestStable}` ||
- page.url.startsWith(`/contracts-cairo/${cairoLatestStable}/`) ||
- !excludedVersions.some((excluded) => page.url.includes(excluded)),
+ .filter((page: Page) =>
+ isCompactPage(page.url)
+ ? isCurrentCompactPage(page.url)
+ : page.url.startsWith("/contracts-sui/") ||
+ page.url === `/contracts-cairo/${cairoLatestStable}` ||
+ page.url.startsWith(`/contracts-cairo/${cairoLatestStable}/`) ||
+ !excludedVersions.some((excluded) => page.url.includes(excluded)),
);
for (const page of filteredPages) {
diff --git a/src/navigation/index.ts b/src/navigation/index.ts
index 59ccf948..2aa55225 100644
--- a/src/navigation/index.ts
+++ b/src/navigation/index.ts
@@ -3,7 +3,7 @@ import cantonData from "./canton.json";
import ethereumEvmData from "./ethereum-evm.json";
import impactData from "./impact.json";
import { mergeDeveloperLibraries } from "./merge-developer-libraries";
-import midnightData from "./midnight.json";
+import midnightData from "./midnight";
import polkadotData from "./polkadot.json";
import starknetData from "./starknet";
import stellarData from "./stellar.json";
diff --git a/src/navigation/midnight.json b/src/navigation/midnight.json
deleted file mode 100644
index 793f569e..00000000
--- a/src/navigation/midnight.json
+++ /dev/null
@@ -1,132 +0,0 @@
-[
- {
- "type": "separator",
- "name": "Midnight Contracts"
- },
- {
- "type": "page",
- "name": "Overview",
- "url": "/contracts-compact"
- },
- {
- "type": "folder",
- "name": "Learn",
- "defaultOpen": true,
- "children": [
- {
- "type": "page",
- "name": "Extensibility",
- "url": "/contracts-compact/extensibility"
- },
- {
- "type": "page",
- "name": "ZK Circuits 101",
- "url": "/contracts-compact/zk-circuits-101"
- }
- ]
- },
- {
- "type": "folder",
- "name": "Modules",
- "children": [
- {
- "type": "folder",
- "name": "Access",
- "children": [
- {
- "type": "page",
- "name": "Ownable",
- "url": "/contracts-compact/ownable"
- },
- {
- "type": "page",
- "name": "AccessControl",
- "url": "/contracts-compact/access-control"
- },
- {
- "type": "page",
- "name": "ShieldedAccessControl",
- "url": "/contracts-compact/shielded-access-control"
- }
- ]
- },
- {
- "type": "page",
- "name": "Security",
- "url": "/contracts-compact/security"
- },
- {
- "type": "folder",
- "name": "Tokens",
- "children": [
- {
- "type": "page",
- "name": "FungibleToken",
- "url": "/contracts-compact/fungible-token"
- },
- {
- "type": "page",
- "name": "NonFungibleToken",
- "url": "/contracts-compact/non-fungible-token"
- },
- {
- "type": "page",
- "name": "MultiToken",
- "url": "/contracts-compact/multi-token"
- }
- ]
- },
- {
- "type": "page",
- "name": "Utils",
- "url": "/contracts-compact/utils"
- }
- ]
- },
- {
- "type": "folder",
- "name": "API Reference",
- "children": [
- {
- "type": "page",
- "name": "AccessControl API",
- "url": "/contracts-compact/api/access-control"
- },
- {
- "type": "page",
- "name": "FungibleToken API",
- "url": "/contracts-compact/api/fungible-token"
- },
- {
- "type": "page",
- "name": "MultiToken API",
- "url": "/contracts-compact/api/multi-token"
- },
- {
- "type": "page",
- "name": "NonFungibleToken API",
- "url": "/contracts-compact/api/non-fungible-token"
- },
- {
- "type": "page",
- "name": "Ownable API",
- "url": "/contracts-compact/api/ownable"
- },
- {
- "type": "page",
- "name": "Security API",
- "url": "/contracts-compact/api/security"
- },
- {
- "type": "page",
- "name": "ShieldedAccessControl API",
- "url": "/contracts-compact/api/shielded-access-control"
- },
- {
- "type": "page",
- "name": "Utils API",
- "url": "/contracts-compact/api/utils"
- }
- ]
- }
-]
diff --git a/src/navigation/midnight/current.json b/src/navigation/midnight/current.json
new file mode 100644
index 00000000..fdd9c751
--- /dev/null
+++ b/src/navigation/midnight/current.json
@@ -0,0 +1,177 @@
+[
+ {
+ "type": "page",
+ "name": "Getting Started",
+ "url": "/contracts-compact"
+ },
+ {
+ "type": "separator",
+ "name": "Midnight Contracts"
+ },
+ {
+ "type": "page",
+ "name": "Overview",
+ "url": "/contracts-compact/0.4.x"
+ },
+ {
+ "type": "folder",
+ "name": "Learn",
+ "defaultOpen": true,
+ "children": [
+ {
+ "type": "page",
+ "name": "Extensibility",
+ "url": "/contracts-compact/0.4.x/extensibility"
+ },
+ {
+ "type": "page",
+ "name": "ZK Circuits 101",
+ "url": "/contracts-compact/0.4.x/zk-circuits-101"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "name": "Modules",
+ "children": [
+ {
+ "type": "folder",
+ "name": "Access",
+ "children": [
+ {
+ "type": "page",
+ "name": "Ownable",
+ "url": "/contracts-compact/0.4.x/ownable"
+ },
+ {
+ "type": "page",
+ "name": "AccessControl",
+ "url": "/contracts-compact/0.4.x/access-control"
+ },
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl",
+ "url": "/contracts-compact/0.4.x/shielded-access-control"
+ }
+ ]
+ },
+ {
+ "type": "page",
+ "name": "Security",
+ "url": "/contracts-compact/0.4.x/security"
+ },
+ {
+ "type": "folder",
+ "name": "Tokens",
+ "children": [
+ {
+ "type": "page",
+ "name": "FungibleToken",
+ "url": "/contracts-compact/0.4.x/fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "NonFungibleToken",
+ "url": "/contracts-compact/0.4.x/non-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "MultiToken",
+ "url": "/contracts-compact/0.4.x/multi-token"
+ },
+ {
+ "type": "page",
+ "name": "NativeShieldedToken",
+ "url": "/contracts-compact/0.4.x/native-shielded-token"
+ },
+ {
+ "type": "page",
+ "name": "ConfidentialFungibleToken",
+ "url": "/contracts-compact/0.4.x/confidential-fungible-token"
+ }
+ ]
+ },
+ {
+ "type": "page",
+ "name": "Crypto",
+ "url": "/contracts-compact/0.4.x/crypto"
+ },
+ {
+ "type": "page",
+ "name": "Multisig",
+ "url": "/contracts-compact/0.4.x/multisig"
+ },
+ {
+ "type": "page",
+ "name": "Utils",
+ "url": "/contracts-compact/0.4.x/utils"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "name": "API Reference",
+ "children": [
+ {
+ "type": "page",
+ "name": "AccessControl API",
+ "url": "/contracts-compact/0.4.x/api/access-control"
+ },
+ {
+ "type": "page",
+ "name": "ConfidentialFungibleToken API",
+ "url": "/contracts-compact/0.4.x/api/confidential-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "Crypto API",
+ "url": "/contracts-compact/0.4.x/api/crypto"
+ },
+ {
+ "type": "page",
+ "name": "FungibleToken API",
+ "url": "/contracts-compact/0.4.x/api/fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "MultiToken API",
+ "url": "/contracts-compact/0.4.x/api/multi-token"
+ },
+ {
+ "type": "page",
+ "name": "Multisig API",
+ "url": "/contracts-compact/0.4.x/api/multisig"
+ },
+ {
+ "type": "page",
+ "name": "NativeShieldedToken API",
+ "url": "/contracts-compact/0.4.x/api/native-shielded-token"
+ },
+ {
+ "type": "page",
+ "name": "NonFungibleToken API",
+ "url": "/contracts-compact/0.4.x/api/non-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "Ownable API",
+ "url": "/contracts-compact/0.4.x/api/ownable"
+ },
+ {
+ "type": "page",
+ "name": "Security API",
+ "url": "/contracts-compact/0.4.x/api/security"
+ },
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl API",
+ "url": "/contracts-compact/0.4.x/api/shielded-access-control"
+ },
+ {
+ "type": "page",
+ "name": "Utils API",
+ "url": "/contracts-compact/0.4.x/api/utils"
+ }
+ ]
+ }
+]
diff --git a/src/navigation/midnight/index.ts b/src/navigation/midnight/index.ts
new file mode 100644
index 00000000..780bb298
--- /dev/null
+++ b/src/navigation/midnight/index.ts
@@ -0,0 +1,22 @@
+import type { NavigationNode } from "../types";
+import currentData from "./current.json";
+import zeroOneXData from "./previous-versions/0.1.x.json";
+import zeroThreeXData from "./previous-versions/0.3.x.json";
+
+const current = currentData as NavigationNode[];
+const previousVersions: NavigationNode[] = [
+ zeroThreeXData as NavigationNode,
+ zeroOneXData as NavigationNode,
+];
+
+const midnightNavigation: NavigationNode[] = [
+ ...current,
+ {
+ type: "folder",
+ name: "Previous Versions",
+ children: previousVersions,
+ defaultOpen: true,
+ },
+];
+
+export default midnightNavigation;
diff --git a/src/navigation/midnight/previous-versions/0.1.x.json b/src/navigation/midnight/previous-versions/0.1.x.json
new file mode 100644
index 00000000..fc2b3409
--- /dev/null
+++ b/src/navigation/midnight/previous-versions/0.1.x.json
@@ -0,0 +1,131 @@
+{
+ "type": "folder",
+ "name": "0.1.x",
+ "children": [
+ {
+ "type": "page",
+ "name": "Overview",
+ "url": "/contracts-compact/0.1.x"
+ },
+ {
+ "type": "folder",
+ "name": "Learn",
+ "children": [
+ {
+ "type": "page",
+ "name": "Extensibility",
+ "url": "/contracts-compact/0.1.x/extensibility"
+ },
+ {
+ "type": "page",
+ "name": "ZK Circuits 101",
+ "url": "/contracts-compact/0.1.x/zk-circuits-101"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "name": "Modules",
+ "children": [
+ {
+ "type": "folder",
+ "name": "Access",
+ "children": [
+ {
+ "type": "page",
+ "name": "Ownable",
+ "url": "/contracts-compact/0.1.x/ownable"
+ },
+ {
+ "type": "page",
+ "name": "AccessControl",
+ "url": "/contracts-compact/0.1.x/access-control"
+ },
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl",
+ "url": "/contracts-compact/0.1.x/shielded-access-control"
+ }
+ ]
+ },
+ {
+ "type": "page",
+ "name": "Security",
+ "url": "/contracts-compact/0.1.x/security"
+ },
+ {
+ "type": "folder",
+ "name": "Tokens",
+ "children": [
+ {
+ "type": "page",
+ "name": "FungibleToken",
+ "url": "/contracts-compact/0.1.x/fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "NonFungibleToken",
+ "url": "/contracts-compact/0.1.x/non-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "MultiToken",
+ "url": "/contracts-compact/0.1.x/multi-token"
+ }
+ ]
+ },
+ {
+ "type": "page",
+ "name": "Utils",
+ "url": "/contracts-compact/0.1.x/utils"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "name": "API Reference",
+ "children": [
+ {
+ "type": "page",
+ "name": "AccessControl API",
+ "url": "/contracts-compact/0.1.x/api/access-control"
+ },
+ {
+ "type": "page",
+ "name": "FungibleToken API",
+ "url": "/contracts-compact/0.1.x/api/fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "MultiToken API",
+ "url": "/contracts-compact/0.1.x/api/multi-token"
+ },
+ {
+ "type": "page",
+ "name": "NonFungibleToken API",
+ "url": "/contracts-compact/0.1.x/api/non-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "Ownable API",
+ "url": "/contracts-compact/0.1.x/api/ownable"
+ },
+ {
+ "type": "page",
+ "name": "Security API",
+ "url": "/contracts-compact/0.1.x/api/security"
+ },
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl API",
+ "url": "/contracts-compact/0.1.x/api/shielded-access-control"
+ },
+ {
+ "type": "page",
+ "name": "Utils API",
+ "url": "/contracts-compact/0.1.x/api/utils"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/navigation/midnight/previous-versions/0.3.x.json b/src/navigation/midnight/previous-versions/0.3.x.json
new file mode 100644
index 00000000..cc070b66
--- /dev/null
+++ b/src/navigation/midnight/previous-versions/0.3.x.json
@@ -0,0 +1,171 @@
+{
+ "type": "folder",
+ "name": "0.3.x",
+ "children": [
+ {
+ "type": "page",
+ "name": "Overview",
+ "url": "/contracts-compact/0.3.x"
+ },
+ {
+ "type": "folder",
+ "name": "Learn",
+ "children": [
+ {
+ "type": "page",
+ "name": "Extensibility",
+ "url": "/contracts-compact/0.3.x/extensibility"
+ },
+ {
+ "type": "page",
+ "name": "ZK Circuits 101",
+ "url": "/contracts-compact/0.3.x/zk-circuits-101"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "name": "Modules",
+ "children": [
+ {
+ "type": "folder",
+ "name": "Access",
+ "children": [
+ {
+ "type": "page",
+ "name": "Ownable",
+ "url": "/contracts-compact/0.3.x/ownable"
+ },
+ {
+ "type": "page",
+ "name": "AccessControl",
+ "url": "/contracts-compact/0.3.x/access-control"
+ },
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl",
+ "url": "/contracts-compact/0.3.x/shielded-access-control"
+ }
+ ]
+ },
+ {
+ "type": "page",
+ "name": "Security",
+ "url": "/contracts-compact/0.3.x/security"
+ },
+ {
+ "type": "folder",
+ "name": "Tokens",
+ "children": [
+ {
+ "type": "page",
+ "name": "FungibleToken",
+ "url": "/contracts-compact/0.3.x/fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "NonFungibleToken",
+ "url": "/contracts-compact/0.3.x/non-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "MultiToken",
+ "url": "/contracts-compact/0.3.x/multi-token"
+ },
+ {
+ "type": "page",
+ "name": "NativeShieldedToken",
+ "url": "/contracts-compact/0.3.x/native-shielded-token"
+ },
+ {
+ "type": "page",
+ "name": "ConfidentialFungibleToken",
+ "url": "/contracts-compact/0.3.x/confidential-fungible-token"
+ }
+ ]
+ },
+ {
+ "type": "page",
+ "name": "Crypto",
+ "url": "/contracts-compact/0.3.x/crypto"
+ },
+ {
+ "type": "page",
+ "name": "Multisig",
+ "url": "/contracts-compact/0.3.x/multisig"
+ },
+ {
+ "type": "page",
+ "name": "Utils",
+ "url": "/contracts-compact/0.3.x/utils"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "name": "API Reference",
+ "children": [
+ {
+ "type": "page",
+ "name": "AccessControl API",
+ "url": "/contracts-compact/0.3.x/api/access-control"
+ },
+ {
+ "type": "page",
+ "name": "ConfidentialFungibleToken API",
+ "url": "/contracts-compact/0.3.x/api/confidential-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "Crypto API",
+ "url": "/contracts-compact/0.3.x/api/crypto"
+ },
+ {
+ "type": "page",
+ "name": "FungibleToken API",
+ "url": "/contracts-compact/0.3.x/api/fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "MultiToken API",
+ "url": "/contracts-compact/0.3.x/api/multi-token"
+ },
+ {
+ "type": "page",
+ "name": "Multisig API",
+ "url": "/contracts-compact/0.3.x/api/multisig"
+ },
+ {
+ "type": "page",
+ "name": "NativeShieldedToken API",
+ "url": "/contracts-compact/0.3.x/api/native-shielded-token"
+ },
+ {
+ "type": "page",
+ "name": "NonFungibleToken API",
+ "url": "/contracts-compact/0.3.x/api/non-fungible-token"
+ },
+ {
+ "type": "page",
+ "name": "Ownable API",
+ "url": "/contracts-compact/0.3.x/api/ownable"
+ },
+ {
+ "type": "page",
+ "name": "Security API",
+ "url": "/contracts-compact/0.3.x/api/security"
+ },
+ {
+ "type": "page",
+ "name": "ShieldedAccessControl API",
+ "url": "/contracts-compact/0.3.x/api/shielded-access-control"
+ },
+ {
+ "type": "page",
+ "name": "Utils API",
+ "url": "/contracts-compact/0.3.x/api/utils"
+ }
+ ]
+ }
+ ]
+}