You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: arc-0020/README.md
+10-10Lines changed: 10 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,15 +10,15 @@ created: 2026-03-18
10
10
11
11
## Abstract
12
12
13
-
ARC-20 defines a fungible token standard for Aleo, supporting both public and private balances through Aleo's dynamic dispatch (interface) system. Tokens implementing the ARC-20 interface can be called by any program at runtime without compile-time knowledge of the specific token implementation.
13
+
ARC-20 defines a fungible token standard for Aleo, supporting both public and private balances. Programs declare conformance to the ARC20 interface, and any other program can call them at runtime via dynamic dispatch -- without compile-time knowledge of the specific token implementation.
14
14
15
15
The standard defines **ARC20**, a minimal interface for public transfers, private transfers, shielding/unshielding, and approvals. An optional **MintableToken** extension adds mint and burn operations. Reference implementations: `wrapped_credits.aleo`, `wrapped_token_registry.aleo`.
16
16
17
-
For regulated tokens requiring freeze lists, compliance records, and pause functionality, see [ARC-22](../arc-0022/).
17
+
For regulated tokens requiring freeze listsand compliance records, see [ARC-22](../arc-0022/).
18
18
19
19
## Motivation
20
20
21
-
Without a standard interface, every DeFi program must import each token at compile time, making it impossible to build token-agnostic protocols. ARC-20 solves this through Leo's dynamic dispatch: a single AMM, lending protocol, or exchange can interact with any conforming token by program ID at runtime, with no recompilation or redeployment required.
21
+
Without a standard interface, every DeFi program must import each token at compile time, making it impossible to build token-agnostic protocols. ARC-20 solves this through Leo's dynamic dispatch: a single AMM, lending protocol, or exchange can interact with any conforming token by name at runtime, with no recompilation or redeployment required.
22
22
23
23
ARC-20 supersedes the previous ARC-20 draft (2023), which was written in Aleo instructions without interface support.
24
24
@@ -75,10 +75,10 @@ record Token {
75
75
}
76
76
```
77
77
78
-
**Metadata** -- Emitted by `transfer_private_to_public`. The sender's identity is private (consumed from a Token record), but the recipient and amount are public inputs. The Metadata record gives the sendera private receipt linking them to the transfer:
78
+
**Metadata** -- Emitted by `transfer_private_to_public`. The recipient and amount are already visible as public inputs, but the sender's identity is hidden. The Metadata record is owned by the sender, giving them a private receipt of the transfer that only they can decrypt:
79
79
```leo
80
80
record Metadata {
81
-
owner: address, // the sender (record owner = sender, so only they can decrypt)
81
+
owner: address, // set to the sender's address
82
82
sender: address,
83
83
}
84
84
```
@@ -94,7 +94,7 @@ record Metadata {
94
94
95
95
### Dynamic Dispatch
96
96
97
-
ARC-20 interfaces are consumed via dynamic dispatch, which resolves the target program at runtime. This allows a single program (e.g. an AMM or lending protocol) to interact with *any* ARC20-implementing token without importing it at compile time.
97
+
ARC-20 tokens are called via dynamic dispatch, which resolves the target program at runtime. This allows a single program (e.g. an AMM or lending protocol) to interact with *any* ARC20-implementing token without importing it at compile time.
The compiler resolves function names and validates argument types against the interface definition. No manual function selector encoding is needed.`Final` values returned by dynamic calls are run as `dynamic.future`s.
123
+
The compiler resolves function names and validates argument types against the interface definition. No manual function selector encoding is needed.
124
124
125
125
#### Example: Swap
126
126
@@ -152,7 +152,7 @@ program my_exchange.aleo {
152
152
153
153
#### Example: Private Transfer with `dyn record`
154
154
155
-
For private token operations, `dyn record` provides type-erased records that work with any ARC20 token:
155
+
For private token operations, `dyn record` provides dynamic records that work with any ARC20 token:
156
156
157
157
```leo
158
158
fn deposit_private(
@@ -214,7 +214,7 @@ Tests use Jest with a local devnode and Leo CLI execution.
214
214
215
215
## Rationale
216
216
217
-
**Additive approvals**: `approve_public` increases the existing allowance rather than replacing it. This matches Aleo's finalize model where concurrent transactions can both succeed -- two `approve_public` calls in the same block will both add to the allowance rather than one overwriting the other.
217
+
**Additive approvals**: `approve_public` increases the existing allowance rather than replacing it. This avoids race conditions -- two `approve_public` calls in the same block will both succeed and add to the allowance, rather than one silently overwriting the other.
218
218
219
219
**u128 amounts**: Future-proofs the standard for high-supply tokens and avoids truncation issues. u64 would limit maximum supply to ~18.4 quintillion base units, which is insufficient for some token designs.
220
220
@@ -323,7 +323,7 @@ For developers familiar with Ethereum's ERC-20 standard:
323
323
324
324
## Security Considerations
325
325
326
-
**Approval model**: ARC-20 uses additive approvals -- `approve_public` increases the allowance, and `unapprove_public` decreases it. This differs from ERC-20's replace semantics. Front-running `approve_public` can increase exposure; users should `unapprove_public` before setting a new allowance if needed.
326
+
**Approval model**: ARC-20 uses additive approvals -- `approve_public` increases the allowance, and `unapprove_public` decreases it. This differs from ERC-20's replace semantics. To change an allowance from 100 to 50, call `unapprove_public(50)` rather than setting a new value. Calling `approve_public` without first reducing the existing allowance will add to it.
327
327
328
328
**Arithmetic overflow/underflow**: Leo's `u128` arithmetic aborts the transaction on underflow/overflow (enforced by the Aleo VM). No explicit checks are needed in implementations.
Copy file name to clipboardExpand all lines: arc-0022/README.md
+10-36Lines changed: 10 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,17 +10,16 @@ created: 2026-03-18
10
10
11
11
## Abstract
12
12
13
-
ARC-22 defines a compliant fungible token interface for Aleo. It extends [ARC-20](../arc-0020/) with freeze-list enforcement, compliance records, role-based access control, and pause functionality for regulated token issuers (stablecoins, security tokens). ARC-22 preserves Aleo's privacy guarantees while enabling regulatory oversight through Merkle non-inclusion proofs and investigator-visible compliance records.
13
+
ARC-22 defines a compliant fungible token interface for Aleo. It extends [ARC-20](../arc-0020/) with freeze-list enforcementand compliance records for regulated token issuers (stablecoins, security tokens). ARC-22 preserves Aleo's privacy guarantees while enabling regulatory oversight through Merkle non-inclusion proofs and investigator-visible compliance records.
14
14
15
15
## Motivation
16
16
17
17
ARC-20 provides a minimal token standard but lacks regulatory compliance features required by many real-world token deployments. Regulated tokens need:
18
18
19
19
1.**Freeze lists** to block sanctioned or compromised addresses from transacting
20
-
2.**Audit trails** for private transfers, enabling authorized investigators to review token movements without breaking sender privacy for the public
21
-
3.**Role-based minting/burning/pausing** to support operational controls required by regulated issuers
20
+
2.**Audit trails** for private transfers, enabling authorized investigators to review token movements without exposing sender identity to the public
22
21
23
-
ARC-22 adds these capabilities while preserving Aleo's privacy guarantees through Merkle non-inclusion proofs. Private transfers remain private to the public, but produce compliance records visible only to a designated investigator address.
22
+
ARC-22 adds these capabilities while preserving Aleo's privacy guarantees through Merkle non-inclusion proofs. Private transfers remain hidden from the public, but produce compliance records visible only to a designated investigator address.
24
23
25
24
## Specification
26
25
@@ -103,7 +102,7 @@ The freeze list prevents sanctioned or compromised addresses from transacting. I
103
102
Private transfers require the sender to prove they are **not** on the freeze list without revealing their identity publicly. This is accomplished through non-inclusion proofs:
104
103
105
104
1. The freeze list is maintained as a sorted Merkle tree of frozen addresses
106
-
2. To prove non-inclusion, the sender provides two adjacent leaf proofs showing that their address falls in a**gap** between two consecutive frozen addresses (or before the first / after the last)
105
+
2. To prove non-inclusion, the sender provides Merkle proofs for two adjacent leaves in the tree, showing that their address falls in the**gap** between them (or before the first / after the last frozen address)
107
106
3. The proof verifies against the current (or previous) Merkle root stored on-chain
108
107
109
108
#### Windowed Root Updates
@@ -135,19 +134,6 @@ For `transfer_private_to_public`, a lighter `Metadata` record is emitted instead
135
134
136
135
The investigator address is hardcoded as the `INVESTIGATOR_ADDRESS` constant in `compliant_token_template.aleo`. It can only be changed by deploying a new edition of the program, which is gated by `multisig_core.aleo` signing operations. This ensures that changes to the investigator require multi-party approval.
137
136
138
-
### Role-Based Access Control
139
-
140
-
Roles are assigned using a bitmask system, allowing a single address to hold multiple roles:
141
-
142
-
| Role | Bitmask | Description |
143
-
|------|---------|-------------|
144
-
|`MINTER_ROLE`|`1`| Can mint new tokens (public and private) |
145
-
|`BURNER_ROLE`|`2`| Can burn tokens (public and private) |
146
-
|`PAUSE_ROLE`|`4`| Can pause/unpause all transfers |
147
-
|`MANAGER_ROLE`|`8`| Can assign roles to other addresses |
148
-
149
-
Role checks use bitwise AND: `role & REQUIRED_ROLE == REQUIRED_ROLE`. A MANAGER can assign any role combination to any address, but cannot remove MANAGER from themselves (the `update_role` function requires that if `caller == new_address`, the new role must still include MANAGER).
150
-
151
137
### Dynamic Dispatch
152
138
153
139
ARC20Compliant is declared as a Leo `interface`, so its public functions (`transfer_public`, `approve_public`, `transfer_from_public`) can be called dynamically using Leo's interface-enforced syntax:
See [ARC-20 Dynamic Dispatch](../arc-0020/#dynamic-dispatch) for the full syntax reference, `_dynamic_call` intrinsic details, and examples.
162
148
163
-
Private functions (`transfer_private`, `unshield`, etc.) require Merkle proof parameters specific to the freeze-list state, making them impractical for generic dynamic dispatch. These are typically called directly by the token owner or through a program that imports the specific compliant token.
164
-
165
-
### Pause Mechanism
166
-
167
-
`set_pause_status(bool)` requires `PAUSE_ROLE` and blocks all transfer functions (public, private, shield, unshield) while paused. Minting and burning are also blocked during pause. This provides an emergency stop mechanism for regulated tokens.
149
+
Private functions (`transfer_private`, `unshield`, etc.) can also be called dynamically -- the caller must pass the Merkle non-inclusion proofs as additional arguments.
168
150
169
151
## Test Cases
170
152
@@ -180,18 +162,14 @@ Tests use Jest with a local devnode and Leo CLI execution.
180
162
-`transfer_private`: Private transfer with freeze-list proof and ComplianceRecord
181
163
-`transfer_private_to_public`: Returns `Metadata` record (not `ComplianceRecord`) with investigator as owner; validates sender address
182
164
-`shield`: ComplianceRecord contains correct investigator owner and sender
-`set_pause_status (negative)`: Non-pauser is rejected
189
-
-`update_role (negative)`: Non-manager is rejected
190
-
-`pause/unpause`: `set_pause_status` blocks all transfers while paused
165
+
-`mint_public`: Minter increases recipient balance; non-minter is rejected
166
+
-`mint_private`: Minter creates private Token with ComplianceRecord
167
+
-`burn_public`: Burner decreases owner balance; non-burner is rejected
168
+
-`pause/unpause`: `set_pause_status` blocks and unblocks transfers
191
169
192
170
## Reference Implementations
193
171
194
-
-[`compliant_token_template/`](./compliant_token_template/) -- Full ARC20Compliant implementation with freeze list, role-based minting/burning/pausing, Merkle proof non-inclusion verification, and multisig-gated upgrades
172
+
-[`compliant_token_template/`](./compliant_token_template/) -- Full ARC20Compliant implementation with freeze list, Merkle proof non-inclusion verification, and multisig-gated upgrades
195
173
-[`freezelist/`](./freezelist/) -- On-chain freeze list using a Merkle tree with windowed root updates for proof validity across blocks
196
174
197
175
## Dependencies
@@ -214,10 +192,6 @@ ARC-22 is a new standard and has no backwards compatibility concerns. Programs i
214
192
215
193
**Metadata records**: `transfer_private_to_public` emits a lighter `Metadata` record instead of `ComplianceRecord`, since the amount and recipient are already visible as public inputs. The `Metadata` record owner is set to the investigator address so the investigator can identify the sender.
216
194
217
-
**Pause mechanism**: Token admins with the `PAUSE_ROLE` can halt all transfers via `set_pause_status`. This is an emergency mechanism -- prolonged pauses should be avoided as they freeze all user funds.
218
-
219
-
**Role-based access**: Minting, burning, pausing, and freeze-list management are gated by role bitmasks. A MANAGER can assign any role to any address but cannot remove MANAGER from themselves, preventing accidental self-lockout.
220
-
221
195
**Upgradability**: `compliant_token_template.aleo` and `freezelist.aleo` gate program upgrades behind `multisig_core.aleo` signing operations, ensuring that code changes require multi-party approval.
0 commit comments