Repository navigation
ARC-20 Reference Implementation v2.0.0
v2.0.0 is a revamp of the Smart ASA reference implementation, together with an
ARC-20 specification update. It replaces App Local State with Boxes, redefines the
freeze model, adopts ARC-65 errors, and adds extension points for custom transfer
rules and circulating supply definitions.
⚠️ This is a major release: the state schema, holders' setup and error reporting
change. Existing deployments are not upgradable (the App is immutable by design):
adopt v2.0.0 with a new deployment.
⚠️ The reference implementation has not been audited: review and test it before
using it in production.
Breaking changes
No Smart ASA Application opt-in
asset_opt_inandasset_close_outare removed, and the App has no Local
State (schema: 0 local uints, previously 2).- Holders only opt in to the Controlled ASA, as for any ASA: 0.1 ALGO of MBR instead
of 0.2285 ALGO. - Holders leave by closing out the Controlled ASA to the Smart ASA Application: any
balance left is surrendered (redeemed). To keep the units, transfer them first, e.g.
in the same group. - Transfers of
0units need no holding, as for native ASA (freeze checks still
apply).
Account freezes in Boxes, with deposits
Account freezes are stored in empty Boxes (freeze_exceptions box map, key prefix
f + account address): a box exists only for an account whose freeze state differs
from default_frozen.
- Deposit: a freeze that creates a box needs a payment of at least the box MBR
(0.0157 ALGO) to the Smart ASA Application, just beforeaccount_freezein the
group, from any sender:[pay 0.0157 ALGO to the App, account_freeze]. Otherwise
ERR:boxMbrUnfunded. - Refund: resetting an account to
default_frozendeletes its box and refunds
its MBR to Freeze with an inner payment (the reset fee must cover 2 transactions). - Box references: calls checking freezes (
asset_transfer,account_freeze,
get_account_is_frozen) need the boxes of the involved accounts. Clients can
populate them by simulation. - Freezes now stick in both profiles: holders can't reset them (no
ClearState
escape), they survive leaving, and Freeze can freeze any address before it holds
units (e.g., from a sanctions list). get_account_is_frozenworks for any address (no opt-in needed).
default_frozen set on creation only
asset_config ignores a new default_frozen, as native ASA reconfiguration does.
Choose the freeze profile (Blacklist False, Whitelist True) at creation.
Freeze model
- The Reserve obeys Freeze: issuing and redeeming fail if the Smart ASA or the
Reserve is frozen (and issuing if the receiver is frozen). A Whitelist Smart ASA
needs Freeze to approve the Reserve. - The Clawback ignores Freeze, both global and account freezes.
- Roles held by the same account compose: each action follows its own role's
rules (e.g., a Reserve that is also the Clawback issues as Reserve, claws back as
Clawback).
Destroy
asset_destroy now destroys the Controlled ASA, deletes the Smart ASA
Application, and closes its account, sending all its ALGO to the Manager.
It requires:
- No issued units (
ERR:unitsIssued); - No account freeze box left (
ERR:freezeBoxesLeft): Freeze resets all accounts to
default_frozenfirst, getting the deposits back. Disabling Freeze with boxes left
makes the Smart ASA permanent.
The caller fee must cover the inner asset destroy and account close (3 transactions).
ARC-65 errors
Failed calls log an ARC-65
error (ERR:<code>) before failing, and ARC-56 clients report the same code. Old
versions used plain assertion messages. Codes are defined in smart_contracts/errors.py:
| Area | Codes |
|---|---|
| Schema | wrongGlobalBytes, wrongGlobalUints, wrongLocalBytes, wrongLocalUints |
| Authorization | unauthorizedCreator, unauthorizedManager, unauthorizedReserve, unauthorizedFreeze, unauthorizedClawback |
| Controlled ASA | ctrlAsaNotExist, ctrlAsaExist, ctrlAsaInvalid |
| Roles | reserveDisabled, freezeDisabled, clawbackDisabled |
| Account freeze boxes | boxMbrUnfunded |
| Freeze | globalFrozen, senderFrozen, receiverFrozen, reserveFrozen |
| Configuration | totalInvalid, metadataHashInvalid |
| Transfers | selfIssue, overIssue, clawbackRedeem |
| Destroy | unitsIssued, freezeBoxesLeft |
| ASA Metadata Registry | registryNotSet, registryMbrPaymentInvalid, registryCallInvalid, ctrlAsaRbacInvalid |
Checks generated by the compiler (ARC-4 routing, ABI decoding, group transaction
types) fail without an ARC-65 error.
ABI: ARC-20 method selectors
v1.1.0 encoded ASA and account arguments as uint64 and address, so its method
selectors didn't match the ARC-20 interface. v2.0.0 uses the ARC-20 reference types
(asset, account):
| Method | v1.1.0 | v2.0.0 |
|---|---|---|
asset_config |
(uint64,uint64,…) |
(asset,uint64,…) |
get_asset_config |
(uint64) |
(asset) |
asset_transfer |
(uint64,uint64,address,address) |
(asset,uint64,account,account) |
asset_freeze |
(uint64,bool) |
(asset,bool) |
account_freeze |
(uint64,address,bool) |
(asset,account,bool) |
get_asset_is_frozen |
(uint64) |
(asset) |
get_account_is_frozen |
(uint64,address) |
(asset,account) |
asset_destroy |
(uint64) |
(asset) |
get_circulating_supply |
(uint64) |
(asset) |
Regenerate clients from the new ARC-56 App Specs.
New features
Issued and circulating supply
- The issued supply is the amount of units outside the Smart ASA Application
(totalof the Controlled ASA minus the App balance): what wallets already compute
with the ASA convention. The Smart ASAtotalcaps it (ERR:overIssue,
ERR:totalInvalid). - The circulating supply (
get_circulating_supply) is:- for the basic Smart ASA, the issued supply (units held by the Reserve circulate);
- for the Smart ASA with ASA Metadata Registry integration, the issued supply
minus the units held by the Reserve, so the Reserve can keep a treasury, issued
but not circulating. This variant implements [ARC-62]((https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0062.md)
(arc62_get_circulating_supply), and both getters always return the same value.
Nothing is excluded while the Reserve is disabled or set to the App. The getters
need the Reserve account reference (populated by simulation).
withdraw_excess_algo() (non-normative)
The Manager can withdraw the App ALGO above its MBR at any time. All the App ALGO
only ever leaves to the Manager: the excess with withdraw_excess_algo, everything
with asset_destroy. Callers cover all inner transaction fees.
Extension points
_regular_transfer_hook(asset_sender, asset_receiver, asset_amount): override it
in a subclass to add checks to regular transfers. It runs after the built-in
checks, never on issuances, redemptions or clawbacks. See the Capped Smart ASA
example extension (smart_contracts/capped_smart_asa/), with its tests._circulating_supply: override it to refine the circulating supply, as the ARC-89
variant does.
AVM clients
The artifacts now include an AVM client (on-chain) for each Smart ASA variant
(smart_asa_avm_client.py, arc89_smart_asa_avm_client.py), generated with
puyapy-clientgen, so Algorand Python contracts can call a Smart ASA with typed
arc4.abi_calls. The example extension has none.
Other changes
- Terminology: issue and redeem replace mint and burn (Smart ASA units
return to the App and can be issued again); holders surrender units by closing
out. - Metadata hash:
asset_confignow also requires a 32-bytemetadata_hash, as
asset_createalready did (ERR:metadataHashInvalid), stricter than ARC-20. - ARC-89 variant: creation fails without the ASA Metadata Registry App ID
(ARC89_APP_IDtemplate variable,ERR:registryNotSet). The ASA Metadata Registry
dependency is pinned tov1.0.0. - Deployment scripts: the deployer now holds all the Smart ASA roles, Reserve
included.
Migration checklist
- Deploy a new Smart ASA Application (v1.1.0 Apps can't be upgraded).
- Regenerate clients from the ARC-56 App Specs (new selectors, box map).
- Drop App opt-in and close-out flows: holders only opt in to the Controlled ASA.
- Choose
default_frozenat creation; in a Whitelist, approve the Reserve. - Send
[deposit, account_freeze]for freezes that create a box, and pay the
refund fee on resets. - Match ARC-65 error codes instead of assertion messages.
- Review fees: issue, redeem, clawback, regular transfers and resets 2× min fee;
asset_destroy3× min fee;withdraw_excess_algo2× min fee. - Use
get_circulating_supply(or ARC-62 for the ARC-89 variant) for supply data.
TestNet Deployment
This deployment demonstrates the full composability of 5 ARCs:
- with metadata (ARC-3)
- used to discover the Smart ASA App (ARC-20)
- and the ASA Circulating Supply end-point (ARC-62)
- from the ASA Metadata Registry (ARC-89)
- via Algorand standard URI (ARC-90).
What's Changed
- test: improve coverage, general refactiring by @cusma in #93
- fix: arc20 abi by @cusma in #94
- fix: clawback reach, metadata hash and frozen getter by @cusma in #95
- refactor: share the transfer check and simplify close-out by @cusma in #96
- fix: deployer as Reserve, document app ID discovery by @cusma in #97
- docs: roles and transfer paths by @cusma in #98
- fix: close the app account to manager on destroy by @cusma in #99
- feat: reserve obeys freeze, roles compose by @cusma in #100
- fix: explicit opt-in error, drop asset ID from local state by @cusma in #101
- feat: ARC-65 errors by @cusma in #102
- feat: account freeze boxes by @cusma in #103
- feat: transfer hook extension by @cusma in #104
- chore: bump dependencies by @cusma in #105
- feat: issued and circulating supply by @cusma in #106
- feat: AVM client by @cusma in #107
Full Changelog: v1.1.0...v2.0.0