Skip to content

ASA Metadata Registry

This ARC defines the interface and the implementation of a singleton Application that provides Algorand Standard Assets metadata through the Algod API or the AVM.

Algorand Standard Assets (ASA) lack a dedicated metadata field on the Algorand ledger for storing additional asset information.

Although it’s generally not advisable to use Algorand as a distributed storage system for data that could easily reside elsewhere, the absence of a native metadata store on the ledger has led the ecosystem to adopt less-than-ideal solutions for discovering and fetching off-chain asset data, involving the usage of an Indexer or external infrastructure (such as IPFS), or hacking on the ASA RBAC roles to get asset metadata mutability.

While storing huge data, such as images, off-chain is a practical (and recommended) approach, smaller, more pertinent data should not incur the expenses, availability challenges, and latency typically associated with external infrastructure.

This ARC establishes a standardized URI within the ASA URL field to solve this simple use case: directly retrieving ASA metadata using the Algod API or the AVM.

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

The data types (like uint64, byte[], etc.) in this document are to be interpreted as specified in ARC-4.

Notes like this are non-normative.

The ASA Metadata Registry is an immutable singleton Application that stores mutable or immutable Asset Metadata.

The trusted deployments of ASA Metadata Registry are:

NETWORKGENESIS HASH (base64)APP IDCREATOR ADDRESS
Main NetwGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=TBDXODGWLOMKUPTGL3ZV53H3GZZWMCTJVQ5B2BZICFD3STSLA2LPSH6V6RW3I
Test NetSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=753324084QYK5DXJ27Y7WIWUJMP3FFOTEU56L4KTRP4CY2GAKRXZHHKLNWV6M7JLYJM

Refer to the AppSpec section for the detailed ARC-56 Application Specification of the singleton reference implementation.

The initial Minimum Balance Requirement (MBR) for the ASA Metadata Registry Application Account SHOULD be provided before enabling the creation of any Asset Metadata.

Once deployed, the ASA Metadata Registry MUST NOT be updated.

The ASA Metadata, along with some ancillary information, are stored in a dedicated Box of the ASA Metadata Registry, called Asset Metadata Box.

There MUST be at most one Asset Metadata Box per ASA.

The Asset Metadata Box Name MUST be equal to the raw 8-byte big-endian encoding of the Asset ID (uint64) (ASSET_METADATA_BOX_KEY_SIZE = 8 bytes).

The Asset Metadata Box Value MUST be defined as follows:

FIELDSCOPEIN METADATA HASHTYPEBYTE OFFSETBYTE SIZE
Metadata IdentifiersHeaderYesbyte01
Reversible FlagsHeaderYesbyte11
Irreversible FlagsHeaderYesbyte21
Metadata HashHeaderNo (Recursive)byte[32]332
Last Modified RoundHeaderNouint64358
Deprecated ByHeaderNouint64438
MetadataBodyYesbyte[]51up to MAX_METADATA_SIZE

See the Metadata section for more details about the Metadata encoding and size limits.

The Metadata Header is a byte-array of fixed length (HEADER_SIZE), encoding ancillary attributes of the Asset Metadata.

The HEADER_SIZE (uint16) is a parameter of the ASA Metadata Registry that is equal to the sum of the Header fields byte sizes (51 bytes).

The maximum HEADER_SIZE depends on:

  • The AVM size of the log opcode MAX_LOG_SIZE (1024 bytes);

  • The ARC-4 return prefix (151f7c75) size ARC4_RETURN_PREFIX_SIZE (4 bytes);

Therefore, HEADER_SIZE ≤ MAX_LOG_SIZE - ARC4_RETURN_PREFIX_SIZE = 1020 bytes.

The Metadata Identifiers (byte) are a set of boolean switches set by the ASA Metadata Registry.

The Metadata Identifiers are defined as follows:

BITDESCRIPTIONDEFAULTSTATE TRANSITION
LSBNot used--
1Not used--
2Not used--
3Not used--
4Not used--
5Not used--
6Not used--
MSBShort MetadataFalseTwo-ways

The MSB is the leftmost bit in the byte stored in Asset Metadata Box.

The Metadata Identifiers SHALL NOT be updated if the Metadata is immutable.

The Metadata MAY be identified as short on creation or after, by setting the MSB in the Metadata Identifier to True.

The short Metadata identifier is derived by the metadata_size. It is set to True if and only if metadata_size ≤ SHORT_METADATA_SIZE, and False otherwise. Its value MAY change on update.

Clients MUST NOT assume shortness identifier persists across updates, since the Metadata size is not guaranteed to be constant (if not immutable).

If the Metadata is identified as short, clients are aware that all AVM opcodes can operate directly on the whole Metadata, example: decoding (json_ref, base64_decode), cryptography (sha256, keccak256, sha512_256, sha3_256), byte manipulations, etc.

For further details on identification rules, refer to the Metadata section.

The Metadata Flags (reversible and irreversible) are reserved for bitwise operations with a bitmask.

The Metadata Flags MAY set by the ASA Manager Address on creation or later.

The Metadata Flags SHALL NOT be updated if the Metadata is immutable.

The Reversible Flags (byte) are defined as follows:

BITDESCRIPTIONDEFAULTSET TIME
LSBARC-20 Smart ASAFalseAny
1ARC-62 Circulating SupplyFalseAny
2Custom, should be reserved for future ARCsFalseAny
3Custom, should be reserved for future ARCsFalseAny
4Custom, should be reserved for future ARCsFalseAny
5Custom, should be reserved for future ARCsFalseAny
6Custom, should be reserved for future ARCsFalseAny
MSBCustom, should be reserved for future ARCsFalseAny

The MSB is the leftmost bit in the byte stored in Asset Metadata Box.

The bits 2 ... MSB are reserved for future ARCs (default False if not used).

An ASA MAY be declared to be an ARC-20 Smart ASA on creation or after, setting the LSB in the Reversible Flags to True.

If the ASA is declared to be an ARC-20 Smart ASA:

  • The ASA MUST conform with the ARC-3 specification, and

  • The Metadata MUST be used for the ASA Controlling Application discovery, conforming to the ARC-20 specification.

The ARC-62 ASA Circulating Supply MAY be enabled on creation or after, setting the bit 1 in the Reversible Flags to True.

If the ARC-62 support is enabled:

  • The ASA MUST conform with the ARC-3 specification, and

  • The Metadata MUST be used for the ASA Circulating Supply Application discovery, conforming to the ARC-62 specification.

The Irreversible Flags (byte) are defined as follows:

BITDESCRIPTIONDEFAULTSET TIME
LSBARC-3 CompliantFalseAt metadata creation
1ARC-89 Native ASAFalseAt metadata creation
2Custom, should be reserved for future ARCsFalseAny
3Custom, should be reserved for future ARCsFalseAny
4Custom, should be reserved for future ARCsFalseAny
5Custom, should be reserved for future ARCsFalseAny
6Custom, should be reserved for future ARCsFalseAny
MSBMetadata ImmutabilityFalseAny

The MSB is the leftmost bit in the byte stored in Asset Metadata Box.

The bits 2 ... 6 are reserved for future ARCs (default False if not used).

The Metadata MAY be declared as ARC-3 compliant on creation setting the bit LSB in the Irreversible Flags to True.

The ASA MAY be declared as a native ARC-89 ASA on creation setting the bit 1 in the Irreversible Flags to True.

The Metadata MAY be declared as immutable on creation or after, setting the MSB in the Irreversible Flags to True.

⚠️ WARNING: If the ASA Manager Address is set to the Zero Address, this implies that the ASA is effectively immutable, regardless of the Metadata Immutability flag (MSB) setting.

The Metadata Hash (byte[32]) is a 256-bit hash computed as defined in the Metadata Hash Computation section.

The Metadata Hash MUST be set on Asset Metadata creation.

If the Asset Metadata is not immutable, the Metadata Hash MUST be updated on any modification of either:

  • Metadata Identifiers, or
  • Metadata Flags, or
  • Metadata (body).

The Last Modified Round (uint64) records the block in which the Metadata Header or the Metadata was last modified (or created).

If the Asset Metadata is not immutable, the Last Modified Round MUST be updated on any modification of either:

  • Metadata Identifiers, or
  • Metadata Flags, or
  • Metadata (body).

The Last Modified Round is guaranteed to be monotonically increasing.

The Deprecated By (uint64) is the Application ID of the new ASA Metadata Registry version.

The Deprecated By field MUST be set to 0 if the ASA Metadata Registry is not deprecated.

The ASA Manager Address MAY migrate mutable metadata to a new ASA Metadata Registry version by setting the Deprecated By field to the Application ID of the new ASA Metadata Registry version.

Immutable metadata MUST NOT be migrated.

The Metadata (byte[]) is a byte-array of variable length (metadata_size).

The metadata_size MAY be 0, representing empty Metadata. In this case, the Metadata Body is the empty byte string (and total_pages = 0, see Pagination).

The Metadata Header still exists and can be retrieved by clients.

The MAX_METADATA_SIZE (uint16) is a parameter of the ASA Metadata Registry that depends on:

  • The maximum byte size of an AVM Box MAX_BOX_SIZE (32768 bytes);

  • The maximum Application Call arguments size MAX_ARG_SIZE (2048 bytes);

  • The maximum number of transaction per Group MAX_GROUP_SIZE (16);

  • The HEADER_SIZE;

  • The ARC-4 method selector size ARC4_METHOD_SELECTOR_SIZE (4 bytes);

  • The available payload for the method arc89_create_metadata(uint64,byte,byte,uint16,byte[],pay) (FIRST_PAYLOAD_MAX_SIZE = MAX_ARG_SIZE - (ARC4_METHOD_SELECTOR_SIZE + 8 + 1 + 1 + 2 + 2 + 0) = 2030 bytes), which consumes an extra pay transaction in the Group (the pay transaction is not encoded as argument bytes, hence the + 0 in the formula);

  • The available payload for the method arc89_extra_payload(uint64,byte[]) (EXTRA_PAYLOAD_MAX_SIZE = MAX_ARG_SIZE - (ARC4_METHOD_SELECTOR_SIZE + 8 + 2 = 2034) bytes);

The MAX_METADATA_SIZE is not constrained by the first head payload for the methods arc89_replace_metadata(...) and arc89_replace_metadata_larger(...) since they are larger than the one of arc89_create_metadata(...).

Refer to the ARC-4 Interface section for details about the method signatures.

Therefore, MAX_METADATA_SIZE = FIRST_PAYLOAD_MAX_SIZE + 14 * EXTRA_PAYLOAD_MAX_SIZE = 30506 bytes.

The condition MAX_METADATA_SIZE ≤ MAX_BOX_SIZE - HEADER_SIZE MUST hold.

The metadata_size MUST hold the condition: metadata_size ≤ MAX_METADATA_SIZE.

The SHORT_METADATA_SIZE (uint16) is a parameter of the ASA Metadata Registry that is equal to the AVM Stack length (4096 bytes).

If the metadata_size ≤ SHORT_METADATA_SIZE, it MUST be declared as short.

The Metadata MUST NOT be updated if immutable.

The available payload for the method arc89_replace_metadata_slice(uint64,uint16,byte[]) is REPLACE_PAYLOAD_MAX_SIZE = MAX_ARG_SIZE - (ARC4_METHOD_SELECTOR_SIZE + 8 + 2 + 2 = 2032) bytes.

The Metadata MUST be a sequence of bytes representing a valid UTF-8 encoded JSON object, as defined in RFC 8259, without Byte Order Mark (BOM).

If Metadata is empty (metadata_size == 0), clients MUST treat it as an empty JSON object for parsing purposes.

If the Metadata is a valid JSON object, it SHOULD conform to the ARC-3 JSON Metadata File Schema. This is the RECOMMENDED schema for maximum interoperability with the ecosystem (e.g., explorers, wallets, etc.).

A Metadata Page is a byte-array of variable length (page_size) that contains a portion of (or the entire) Metadata.

The PAGE_SIZE (uint16) is a parameter of the ASA Metadata Registry that depends on:

  • The AVM size of the log opcode MAX_LOG_SIZE (1024 bytes);

  • The ARC-4 return prefix (151f7c75) size ARC4_RETURN_PREFIX_SIZE (4 bytes);

  • The maximum ARC-4 return type encoding overhead bytes depends on the Get Metadata interface) return type (bool,uint64,byte[]). ABI tuple are encoded as head(...) || tail(...).

Therefore, PAGE_SIZE = MAX_LOG_SIZE - ARC4_RETURN_PREFIX_SIZE - (1 + 8 + 2 + 2) = 1007 bytes.

The page_size MUST hold the condition page_size ≤ PAGE_SIZE.

A page MUST be identified by a 0-based index (uint8) from the head of the Metadata.

Page p covers the byte range [p*PAGE_SIZE, min((p+1)*PAGE_SIZE, metadata_size)). The final page MAY be shorter; all intermediate pages SHOULD have page_size = PAGE_SIZE.

A uint8 is enough as a page index since ceil(MAX_METADATA_SIZE/PAGE_SIZE) = 31; 0 pages are allowed (i.e., empty Metadata).

Empty Metadata: when total_pages == 0, there are no Metadata Pages for hashing purposes; however, the Get Metadata method accepts page = 0 and return an empty page (and has_next_page = False) as a convenience (any page != 0 fails).

The MBR Delta is the variation of the ASA Metadata Registry Application Account MBR due to the creation, update, or deletion of the Asset Metadata Box.

It is a tuple of two elements, encoding:

  • The sign (uint8) enum:
ENUMVALUEDESCRIPTION
NULL0Null
POS1Positive
NEG255Negative
  • The amount (uint64) of MBR, expressed in microALGO.

The MBR Delta is calculated based on the following contextual information:

  • The existence of the Asset Metadata Box for the ASA and,

  • The relative byte sizes (delta_size) between a new Metadata (new_metadata_size) and the existing Metadata (metadata_size, if any).

If the Asset Metadata Hash (am) field of the ASA is set (i.e., not zero), then:

  • It takes precedence over the hash computation, and it is copied verbatim as Metadata Hash, and
  • The Asset Metadata MUST be flagged as immutable at creation, and
  • The ASA Metadata Registry SHALL validate it (according to the following specification) if the Asset Metadata is flagged as ARC-89 Native ASA and not as ARC-3 compliant.

Refer to the Asset Metadata Hash section for details about the Asset Metadata Hash (am) field.

Otherwise, the Metadata Hash is computed as follows:

  1. Compute the Metadata Header Hash (hh):
hh = SHA-512/256("arc0089/header" || Asset ID || Metadata Identifiers || Reversible Flags || Irreversible Flags || Metadata Size)
  1. If total_pages > 0, compute the Page Hashes (ph[i]) for each Metadata Page (i = 0 ... total_pages - 1):
ph[i] = SHA-512/256("arc0089/page" || Asset ID || Page Index || Page Size || Page Content)
  1. If total_pages > 0, compute the Asset Metadata Hash (am) as:
am = SHA-512/256("arc0089/am" || hh || ph[0] || ph[1] || ... || ph[total_pages - 1])

otherwise, if total_pages == 0, compute the Asset Metadata Hash (am) as:

am = SHA-512/256("arc0089/am" || hh)

Where:

  • || denotes concatenation,
  • Asset ID is the 8-byte encoding of the Asset ID (uint64), serialized in network byte order (big-endian);
  • Metadata Identifiers is the 1-byte encoding of the Metadata Identifiers (byte);
  • Reversible Flags is the 1-byte encoding of the Reversible Flags (byte);
  • Irreversible Flags is the 1-byte encoding of the Irreversible Flags (byte);
  • Metadata Size is the 2-byte encoding of the Metadata Size (uint16), serialized in network byte order (big-endian);
  • Page Index is the 1-byte encoding of the 0-based Metadata Page Index (uint8);
  • Page Size is 2-byte encoding of the i-th Page byte size (uint16), serialized in network byte order (big-endian);
  • Page Content are the exact raw bytes content of the i-th Metadata Page, unpadded if len(page) < PAGE_SIZE;
  • SHA-512/256 is defined in NIST FIPS 180-4.

Hash components MUST NOT be reinterpreted as a signed integer, bitset string, or multibyte integer prior to hashing.

⚠️ The Last Modified Round and the Deprecated By fields are NOT included in the Metadata Hash computation.

Care has to be taken when creating an ARC-89 Native ASA, specifically:

  • The Asset URL (au) field is defined at ASA creation time, and it is immutable,

  • The Asset Metadata Hash (am) field is defined at ASA creation time, and it is immutable,

  • The ASA Manager Address MUST NOT be set to the Zero Address on creation.

The Asset URL (au) field is used as a partial URI pointing to the Asset Metadata on the Algorand ledger.

The Asset URL (au) MUST begin with the partial ARC-90 URI:

algorand://<netauth>/app/<singleton_arc89_app_id>?box=

and MAY declare the ARC-90 compliance fragment at the end of the partial URI:

algorand://<netauth>/app/<singleton_arc89_app_id>?box=#arc<A>+<B>+<C>...

where <A>, <B>, <C>, etc. are the ARC numbers of the compliance fragments.

The Native ARC-89 ASA Flag MUST be set to True.

Clients MUST resolve the partial Asset URL (au) to a complete Asset Metadata URI before using it.

Refer to the Asset Metadata URI section for details about the complete ARC-90 Asset Metadata URI.

The Asset Metadata Hash (am) field is used as hash-lock invariant on ASA creation.

The ASA Creator SHOULD compute the Asset Metadata Hash (am) field as specified by:

Since the Metadata Identifiers are set by the ASA Metadata Registry on creation, the ASA Creator needs to pre-identify the ASA based on the creation parameters, specifically:

  • If the Metadata size at creation time is less than or equal to SHORT_METADATA_SIZE.

The compliance with ARC-3 is OPTIONAL but RECOMMENDED to maximize interoperability with the ecosystem.

If the ASA conforms to ARC-3, then:

Refer to the Asset Metadata URI section for details about the complete ARC-90 Asset Metadata URI.

The ASA Metadata Registry does not enforce Asset Metadata Hash (am) validation for ARC-3 ASA.

Two RECOMMENDED creation processes are provided.

The RECOMMENDED creation process for an ARC-89 native ASA:

  1. The ASA Creator Address defines the Metadata Flags and the Metadata,

  2. The ASA Creator Address creates an ASA as follows:

    • The Asset URL (au) field is set to algorand://<netauth>/app/<singleton_arc89_app_id>?box=#arc89,
    • If the Asset Metadata is immutable, the Asset Metadata Hash (am) field is computed according to the Metadata Hash Computation using the Metadata Identifiers, the defined Metadata Flags and the Metadata (raw bytes),
    • The ASA Manager Address is not set to the Zero Address.
  3. The ASA Manager Address creates the Asset Metadata on the ASA Metadata Registry, using the defined Metadata Flags and Metadata.

ARC-89 Native ASA Creation with ARC-3 Compliant Metadata
Section titled “ARC-89 Native ASA Creation with ARC-3 Compliant Metadata”

The RECOMMENDED creation process for an ARC-89 native ASA with ARC-3 compliant Metadata is:

  1. The ASA Creator Address defines the Metadata Flags and the Metadata,

  2. The ASA Creator Address creates an ASA as follows:

    • The Asset URL (au) field is set to algorand://<netauth>/app/<singleton_arc89_app_id>?box=#arc3,
    • If the Asset Metadata is immutable, the Asset Metadata Hash (am) field is set according to the ARC-3 ASA Parameters Conventions,
    • The ASA Manager Address is not set to the Zero Address.
  3. The ASA Manager Address creates the Asset Metadata on the ASA Metadata Registry, using the defined Metadata Flags and Metadata.

If the ASA configuration (Role-Based Access Control and destroyability) needs to be locked (by disabling the ASA Manager Address), the Asset Metadata MUST be created first.

The compliance fragment for ARC-3 MUST NOT contain additional elements (i.e., #arc3+89 is not allowed), see ARC-90 compliance fragment section for details.

To get the ARC-90 Asset Metadata URI, clients SHALL complete the Asset URL with the boxparam filled with the Asset Metadata Box Name, equal to the Asset ID (big-endian uint64 encoded as base64url, URL-safe with padding):

algorand://<netauth>/app/<singleton_arc89_app_id>?box=<base64url_encoded_asset_id>#arc<A>+<B>+<C>...

The MainNet netauth is empty, therefore, the partial Asset URL on MainNet is: algorand://app/<singleton_arc89_app_id>?box=<base64url_encoded_asset_id>#arc<A>+<B>+<C>...

The TestNet deployments uses testnet as netlabel for the netauth selector, resulting in the following partial Asset URL: algorand://net:testnet/app/<singleton_arc89_app_id>?box=<base64url_encoded_asset_id>#arc<A>+<B>+<C>...

Clients MUST encode the Asset Metadata Box Name with URL-safe base64url (with padding) in ARC-90 URIs, and with Standard base64 when calling Algod API endpoints with /box?name= query parameter.

For further details on the base64 Standard and URL-safe encodings refer to the RFC 4648 sections 4 and 5.

The Asset ID (uint64) used as Asset Metadata Box Name (boxparam) in the Asset Metadata URI is encoded as base64url for two reasons: (1) the Box Name is assumed to be raw big-endian 8-bytes encoding a uint64 and (2) the Algod API requires /box?name= query parameter to be Standard base64 encoded, while the URI requires the URL-safe base64url encoding.

Asset ID (uint64)8-byte big-endian (hex)Algod /box?name= (Standard base64)ARC-90 box= (URL-safe base64url)
00000000000000000AAAAAAAAAAA=AAAAAAAAAAA=
10000000000000001AAAAAAAAAAE=AAAAAAAAAAE=
2^320000000100000000AAAAAQAAAAA=AAAAAQAAAAA=
2^63−17ffffffffffffffff/////////8=f_________8=

The Asset Metadata URI for the ASA 12345 would be:

algorand://<netauth>/app/<singleton_arc89_app_id>?box=AAAAAAAAMDk#arc89

The Asset Metadata URI for the ARC-3 ASA 12345 would be:

algorand://<netauth>/app/<singleton_arc89_app_id>?box=AAAAAAAAMDk#arc3

The ASA Metadata Registry singleton application is immutable.

Any eventual future version MUST be deployed as a new Application ID.

The decision to migrate existing ASA Metadata to a new version MUST be made by the ASA Manager Address, by declaring the new Application ID in the Deprecated By field of the Metadata Header.

If the Deprecated By field is not 0, clients SHALL point to the new ARC-90 Asset Metadata URI:

algorand://<netauth>/app/<deprecated_by_app_id>?box=<base64url_encoded_asset_id>#arc<A>+<B>+<C>...

and complete it as specified in the Asset Metadata URI section.

This ARC standardizes an on-chain, Algod/AVM-addressable metadata source for Algorand Standard Assets (ASAs).

The design goals are:

  1. Direct retrieval without Indexer or external storage for small but important metadata,

  2. Predictable costs and limits via a single-box layout and strict pagination caps,

  3. Interoperability with the existing ecosystem through conditional ARCs hooks,

  4. Forward compatibility with future ARCs standards, and

  5. Precise deprecation strategy for new ASA Metadata Registry versions.

ASA Metadata Registry Application + ARC-90 URI discovery

Section titled “ASA Metadata Registry Application + ARC-90 URI discovery”

By fixing a singleton application per Algorand network and using a partial ARC-90 URI in the Asset URL (au) field, any client can deterministically compute the query parameter pointing to the Asset Metadata (/box?name= as big-endian Asset ID) and retrieve the metadata through (a) Algod REST API (GetApplicationBoxByName) or (b) direct AVM calls to the ASA Metadata Registry. The standard supports two different entrypoints for the Metadata discovery and retrieval: the Asset ID (available on the Algorand ledger) or the Asset Metadata URI (which could be distributed on the Web or by other external channels).

Refer to the Usage section for details.

A compact header (Identifiers, Flags, Hash, Last-Modified Round, Deprecated By) precedes the body (JSON). The Last-Modified Round provides a monotonic version marker so readers can detect mid-stream changes. The Deprecated By field allows the Asset Managers to migrate existing ASA Metadata to a new future version of the ASA Metadata Registry.

The ASA Metadata Registry sets Identifiers (short-metadata hint) while the ASA Manager Address governs Flags (ARC-3, ARC-20, ARC-62, ARC-89, immutability). One-way transitions (e.g., immutability) are enforced on-chain. This mirrors ASA trust roles and prevents metadata rewrites after lock.

Metadata pagination is provided for the AVM clients (Algod clients can read entire Metadata in a single request). A fixed PAGE_SIZE keeps each response within AVM limits. The registry guarantees len(page) ≤ PAGE_SIZE and supplies a has_next boolean. AVM clients can read paginated Metadata either atomically (RECOMMENDED), using Group Transactions of Inner Transactions, or with sequential Application Calls. If the sequential read is used, the Last-Modified Round supports streaming and parallel fetch with drift detection. A separate pagination head exposes total metadata size, page size, and total pages for preallocation and progress UIs.

When the ASA is declared immutable at creation, the Asset Metadata Hash (am) field can commit to the on-chain bytes (domain-separated SHA-512/256 over Flags and Metadata). This binds the ledger state to a wallet-verifiable hash without requiring JSON normalization.

The registry intentionally caps data to a single box (~32 KiB minus header). Large artifacts (images, media) remain off-chain; their URIs (e.g., ipfs://..., https://...) live in the JSON. This strikes a balance between availability and ledger hygiene, discouraging chain-as-a-drive patterns.

Metadata deletion returns excess MBR; third-party cleanup of metadata for destroyed ASAs is permitted to prevent abandoned state. Network-specific singleton IDs are published by the ARC.

The registry turns ASA metadata into on-chain first class citizens, using the full potential of AVM opcodes (json_ref and base64_decode). ASA metadata on the registry can be read and written programatically on-chain, making them part of the AVM runtime (e.g., an Application can decide to pay a different amount based on some ASA metadata property).

Backwards compatibility for existing ASA is possible, as long as the size of their metadata does not exceed MAX_METADATA_SIZE. Existing ASAs SHOULD NOT be flagged as an ARC-89 native ASA.

The ASA Metadata Registry can be used by existing ASA as a fallback option in addition to the existing URIs requiring external infrastructures (e.g., Indexer, IPFS, etc.).

Since the Asset URL (au) field is immutable, the Asset Metadata cannot be discovered though an ASA look-up. Clients need to acquire the Asset Metadata URI from a distribution channel.

{
"name": "ASA Metadata Registry",
"desc": "Singleton Application providing ASA metadata via Algod API and AVM",
"methods": [
{
"name": "arc89_create_metadata",
"desc": "Create Asset Metadata for an existing ASA, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to create the Asset Metadata for" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags. WARNING: LSB and 1 can by set only at creation time. If the MSB is True the Asset Metadata is IMMUTABLE" },
{ "type": "uint16", "name": "metadata_size", "desc": "The Metadata byte size to be created" },
{ "type": "byte[]", "name": "payload", "desc": "The Metadata payload (without Header). WARNING: Payload larger than args capacity must be provided with arc89_extra_payload calls in the Group" },
{ "type": "pay", "name": "mbr_delta_payment", "desc": "Payment of the MBR Delta amount (microALGO) for the Asset Metadata Box creation" }
],
"events": [
{
"name": "Arc89MetadataUpdated",
"desc": "Event emitted when Asset Metadata is created or updated",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata creation or update" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata creation or update" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags" },
{ "type": "bool", "name": "is_short", "desc": "True if the Asset Metadata is identified as short" },
{ "type": "byte[32]", "name": "hash", "desc": "The Metadata Hash" }
]
}
],
"returns": { "type": "(uint8,uint64)", "desc": "MBR Delta: sign enum, and amount (microALGO)" }
},
{
"name": "arc89_replace_metadata",
"desc": "Replace mutable Metadata with smaller or equal size payload for an existing ASA, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to replace the Asset Metadata for" },
{ "type": "uint16", "name": "metadata_size", "desc": "The new Asset Metadata byte size" },
{ "type": "byte[]", "name": "payload", "desc": "The Metadata payload (without Header). WARNING: Payload larger than args capacity must be provided with arc89_extra_payload calls in the Group" }
],
"events": [
{
"name": "Arc89MetadataUpdated",
"desc": "Event emitted when Asset Metadata is created or updated",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Metadata creation or update" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata creation or update" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags" },
{ "type": "bool", "name": "is_short", "desc": "True if the Asset Metadata is identified as short" },
{ "type": "byte[32]", "name": "hash", "desc": "The Metadata Hash" }
]
}
],
"returns": { "type": "(uint8,uint64)", "desc": "MBR Delta: sign enum, and amount (microALGO)" }
},
{
"name": "arc89_replace_metadata_larger",
"desc": "Replace mutable Metadata with larger size payload for an existing ASA, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to replace the Asset Metadata for" },
{ "type": "uint16", "name": "metadata_size", "desc": "The new Metadata byte size" },
{ "type": "byte[]", "name": "payload", "desc": "The Metadata payload (without Header). WARNING: Payload larger than args capacity must be provided with arc89_extra_payload calls in the Group" },
{ "type": "pay", "name": "mbr_delta_payment", "desc": "Payment of the MBR Delta amount (microALGO) for the larger Asset Metadata Box replace" }
],
"events": [
{
"name": "Arc89MetadataUpdated",
"desc": "Event emitted when Asset Metadata is created or updated",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata creation or update" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata creation or update" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags" },
{ "type": "bool", "name": "is_short", "desc": "True if the Asset Metadata is identified as short" },
{ "type": "byte[32]", "name": "hash", "desc": "The Metadata Hash" }
]
}
],
"returns": { "type": "(uint8,uint64)", "desc": "MBR Delta: sign enum, and amount (microALGO)" }
},
{
"name": "arc89_replace_metadata_slice",
"desc": "Replace a slice of the Asset Metadata for an ASA with a payload of the same size, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to replace the Asset Metadata slice for" },
{ "type": "uint16", "name": "offset", "desc": "The 0-based byte offset within the Metadata (body) bytes" },
{ "type": "byte[]", "name": "payload", "desc": "The slice payload" }
],
"events": [
{
"name": "Arc89MetadataUpdated",
"desc": "Event emitted when Asset Metadata is created or updated",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata creation or update" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata creation or update" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags" },
{ "type": "bool", "name": "is_short", "desc": "True if the Asset Metadata is identified as short" },
{ "type": "byte[32]", "name": "hash", "desc": "The Metadata Hash" }
]
}
],
"returns": { "type": "void" }
},
{
"name": "arc89_migrate_metadata",
"desc": "Migrate the Asset Metadata for an ASA to a new ASA Metadata Registry version, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to migrate the Asset Metadata for" },
{ "type": "uint64", "name": "new_registry_id", "desc": "The Application ID of the new ASA Metadata Registry version" }
],
"events": [
{
"name": "Arc89MetadataMigrated",
"desc": "Event emitted when Asset Metadata has been migrated to a new ASA Metadata Registry version",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "new_registry_id", "desc": "The Application ID of the new ASA Metadata Registry version" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata migration" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata migration" }
]
}
],
"returns": { "type": "void" }
},
{
"name": "arc89_delete_metadata",
"desc": "Delete Asset Metadata for an ASA, restricted to the ASA Manager Address (if the ASA still exists)",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to delete the Asset Metadata for" }
],
"events": [
{
"name": "Arc89MetadataDeleted",
"desc": "Event emitted when Asset Metadata is deleted",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the deleted Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata delete" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata deletion" }
]
}
],
"returns": { "type": "(uint8,uint64)", "desc": "MBR Delta: sign enum, and amount (microALGO)" }
},
{
"name": "arc89_extra_payload",
"desc": "Concatenate extra payload to Asset Metadata head call methods (creation or replacement)",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to provide Metadata extra payload for" },
{ "type": "byte[]", "name": "payload", "desc": "The Metadata extra payload to concatenate" }
],
"returns": { "type": "void" }
},
{
"name": "arc89_set_reversible_flag",
"desc": "Set a reversible Asset Metadata Flag, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to set the Metadata Flag for" },
{ "type": "uint8", "name": "flag", "desc": "The reversible flag index to set" },
{ "type": "bool", "name": "value", "desc": "The flag value to set" }
],
"events": [
{
"name": "Arc89MetadataUpdated",
"desc": "Event emitted when Asset Metadata is created or updated",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata creation or update" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata creation or update" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags" },
{ "type": "bool", "name": "is_short", "desc": "True if the Asset Metadata is identified as short" },
{ "type": "byte[32]", "name": "hash", "desc": "The Metadata Hash" }
]
}
],
"returns": { "type": "void" }
},
{
"name": "arc89_set_irreversible_flag",
"desc": "Set an irreversible Asset Metadata Flag, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to set the Metadata Flag for" },
{ "type": "uint8", "name": "flag", "desc": "The irreversible flag index to set. WARNING: must be in 2 ... 6" }
],
"events": [
{
"name": "Arc89MetadataUpdated",
"desc": "Event emitted when Asset Metadata is created or updated",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata creation or update" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata creation or update" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags" },
{ "type": "bool", "name": "is_short", "desc": "True if the Asset Metadata is identified as short" },
{ "type": "byte[32]", "name": "hash", "desc": "The Metadata Hash" }
]
}
],
"returns": { "type": "void" }
},
{
"name": "arc89_set_immutable",
"desc": "Set Asset Metadata as immutable, restricted to the ASA Manager Address",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to set immutable Asset Metadata for" }
],
"events": [
{
"name": "Arc89MetadataUpdated",
"desc": "Event emitted when Asset Metadata is created or updated",
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID of the created or updated Asset Metadata" },
{ "type": "uint64", "name": "round", "desc": "Round of the Asset Metadata creation or update" },
{ "type": "uint64", "name": "timestamp", "desc": "Timestamp of the Asset Metadata creation or update" },
{ "type": "byte", "name": "reversible_flags", "desc": "The Reversible Flags" },
{ "type": "byte", "name": "irreversible_flags", "desc": "The Irreversible Flags" },
{ "type": "bool", "name": "is_short", "desc": "True if the Asset Metadata is identified as short" },
{ "type": "byte[32]", "name": "hash", "desc": "The Metadata Hash" }
]
}
],
"returns": { "type": "void" }
},
{
"name": "arc89_get_metadata_registry_parameters",
"desc": "Return the ASA Metadata Registry parameters",
"readonly": true,
"args": [],
"returns": { "type": "(uint8,uint16,uint16,uint16,uint16,uint16,uint16,uint16,uint64,uint64)", "desc": "Tuple of (ASSET_METADATA_BOX_KEY_SIZE, HEADER_SIZE, MAX_METADATA_SIZE, SHORT_METADATA_SIZE, PAGE_SIZE, FIRST_PAYLOAD_MAX_SIZE, EXTRA_PAYLOAD_MAX_SIZE, REPLACE_PAYLOAD_MAX_SIZE, FLAT_MBR, BYTE_MBR)" }
},
{
"name": "arc89_get_metadata_partial_uri",
"desc": "Return the Asset Metadata ARC-90 partial URI, without compliance fragment (optional)",
"readonly": true,
"args": [],
"returns": { "type": "string", "desc": "Asset Metadata ARC-90 partial URI, without compliance fragment" }
},
{
"name": "arc89_get_metadata_mbr_delta",
"desc": "Return the Asset Metadata Box MBR Delta for an ASA, given a new Asset Metadata byte size. If the Asset Metadata Box does not exist, the creation MBR Delta is returned.",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to calculate the Asset Metadata MBR Delta for" },
{ "type": "uint16", "name": "new_metadata_size", "desc": "The new Asset Metadata byte size" }
],
"returns": { "type": "(uint8,uint64)", "desc": "MBR Delta: sign enum, and amount (microALGO)" }
},
{
"name": "arc89_check_metadata_exists",
"desc": "Checks whether the specified ASA exists and whether its associated Asset Metadata is available",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to check the ASA and Asset Metadata existence for" }
],
"returns": { "type": "(bool,bool)", "desc": "Tuple of (ASA exists, Asset Metadata exists)" }
},
{
"name": "arc89_is_metadata_immutable",
"desc": "Return True if the Asset Metadata for an ASA is immutable, False otherwise",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to check the Asset Metadata immutability for" }
],
"returns": { "type": "bool", "desc": "Asset Metadata for the ASA is immutable" }
},
{
"name": "arc89_is_metadata_short",
"desc": "Return True if Asset Metadata for an ASA is short (up to 4096 bytes), False otherwise",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to check the Asset Metadata size classification for" }
],
"returns": { "type": "(bool,uint64)", "desc": "Tuple of (Is Short Metadata, Metadata Last Modified Round)" }
},
{
"name": "arc89_get_metadata_header",
"desc": "Return the Asset Metadata Header for an ASA",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the Asset Metadata Header for" }
],
"returns": { "type": "(byte,byte,byte,byte[32],uint64,uint64)", "desc": "Asset Metadata Header (Identifiers, Reversible Flags, Irreversible Flags, Hash, Last Modified Round, Deprecated By)" }
},
{
"name": "arc89_get_metadata_pagination",
"desc": "Return the Asset Metadata pagination for an ASA",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the Asset Metadata pagination for" }
],
"returns": { "type": "(uint16,uint16,uint8)", "desc": "Tuple of (total metadata byte size, PAGE_SIZE, total number of pages)" }
},
{
"name": "arc89_get_metadata",
"desc": "Return paginated Asset Metadata (without Header) for an ASA",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the Asset Metadata for" },
{ "type": "uint8", "name": "page", "desc": "The 0-based Metadata page number" }
],
"returns": { "type": "(bool,uint64,byte[])", "desc": "Tuple of (has next page, Metadata Last Modified Round, page content)" }
},
{
"name": "arc89_get_metadata_slice",
"desc": "Return a slice of the Asset Metadata for an ASA",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the Asset Metadata slice for" },
{ "type": "uint16", "name": "offset", "desc": "The 0-based byte offset within the Metadata (body) bytes" },
{ "type": "uint16", "name": "size", "desc": "The slice bytes size to return" }
],
"returns": { "type": "byte[]", "desc": "Asset Metadata slice (size limited to PAGE_SIZE)" }
},
{
"name": "arc89_get_metadata_header_hash",
"desc": "Return the Metadata Header Hash for an ASA",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the Metadata Header Hash for" }
],
"returns": { "type": "byte[32]", "desc": "Asset Metadata Header Hash" }
},
{
"name": "arc89_get_metadata_page_hash",
"desc": "Return the SHA512-256 of a Metadata page for an ASA",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the Asset Metadata page hash for" },
{ "type": "uint8", "name": "page", "desc": "The 0-based Metadata page number" }
],
"returns": { "type": "byte[32]", "desc": "The SHA512-256 of the Metadata page" }
},
{
"name": "arc89_get_metadata_hash",
"desc": "Return the Metadata Hash for an ASA",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the Metadata Hash for" }
],
"returns": { "type": "byte[32]", "desc": "Asset Metadata Hash" }
},
{
"name": "arc89_get_metadata_string_by_key",
"desc": "Return the UTF‑8 string value for a top‑level JSON key of type JSON String from short Metadata for an ASA; errors if the key does not exist or is not a JSON String",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the key value for" },
{ "type": "string", "name": "key", "desc": "The top‑level JSON key whose string value to fetch" }
],
"returns": { "type": "string", "desc": "The string value from valid UTF‑8 JSON Metadata (size limited to PAGE_SIZE)" }
},
{
"name": "arc89_get_metadata_uint64_by_key",
"desc": "Return the uint64 value for a top‑level JSON key of type JSON Uint64 from short Metadata for an ASA; errors if the key does not exist or is not a JSON Uint64",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the key value for" },
{ "type": "string", "name": "key", "desc": "The top‑level JSON key whose uint64 value to fetch" }
],
"returns": { "type": "uint64", "desc": "The uint64 value from valid UTF‑8 JSON Metadata" }
},
{
"name": "arc89_get_metadata_object_by_key",
"desc": "Return the UTF-8 object value for a top‑level JSON key of type JSON Object from short Metadata for an ASA; errors if the key does not exist or is not a JSON Object",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the key value for" },
{ "type": "string", "name": "key", "desc": "The top‑level JSON key whose object value to fetch" }
],
"returns": { "type": "string", "desc": "The object value from valid UTF‑8 JSON Metadata (size limited to PAGE_SIZE)" }
},
{
"name": "arc89_get_metadata_b64_bytes_by_key",
"desc": "Return the base64-decoded bytes for a top-level JSON key of type JSON String from short Metadata for an ASA; errors if the key does not exist, is not a JSON String, or is not valid base64 for the chosen encoding",
"readonly": true,
"args": [
{ "type": "uint64", "name": "asset_id", "desc": "The Asset ID to get the key value for" },
{ "type": "string", "name": "key", "desc": "The top-level JSON key whose base64 string value to fetch and decode" },
{ "type": "uint8", "name": "b64_encoding", "desc": "base64 encoding enum: 0 = URLEncoding, 1 = StdEncoding" }
],
"returns": { "type": "byte[]", "desc": "The base64-decoded bytes from valid UTF‑8 JSON Metadata (size limited to PAGE_SIZE)" }
}
]
}

The ASA Metadata Registry MUST validate ARC-4 method arguments size according to their types.

Refer to the AppSpec section for the detailed ARC-56 Application Specification of the singleton reference implementation.

To create the Asset Metadata:

  • The ASA MUST exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST NOT exist, and

If the provided metadata_size > MAX_METADATA_SIZE the creation MUST be rejected.

If the provided metadata_size ≤ SHORT_METADATA_SIZE, the Short Metadata Identifier MUST be set to True.

The Metadata MUST be initialized with the provided payload value (empty is allowed).

If the creation is part of a Group, the extra payload provided by later transactions for the same asset_id in the same Group MUST be concatenated in order.

The creation MUST be rejected as soon as the cumulative staged size for the same asset_id in the same Group exceeds metadata_size.

The cumulative staged payload MUST be equal to the provided metadata_size (no truncation), otherwise the creation is rejected.

The Reversible Flags MUST be initialized with the provided reversible_flags value (byte).

The Irreversible Flags MUST be initialized with the provided irreversible_flags value (byte).

The Metadata Hash MUST be initialized according to the Metadata Hash Computation.

The Last Modified Round MUST be initialized to the current round.

The Deprecated By field MUST be initialized to 0.

If the ASA is declared as ARC-3 compliant, the Asset Name (an) or the Asset URL (au) MUST comply with the ARC-3 ASA Parameters Conventions.

If the ASA is declared as ARC-89 Native ASA, the Asset URL (au) MUST comply with the specified Asset Metadata URI (no #arc fragment validation enforced).

The MBR Delta amount of the created Asset Metadata Box MUST be provided contextually to the ASA Metadata Registry Address.

An Arc89MetadataUpdated event MUST be emitted.

⚠️ WARNING: If the MSB of the Irreversible Flags is True the Asset Metadata is immutable, for further details refer to the Irreversible Flags section.

To replace the Asset Metadata for an ASA with smaller or equal size Metadata:

  • The ASA MUST still exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST NOT be immutable.

If the provided metadata_size > MAX_METADATA_SIZE the update MUST be rejected.

If the provided metadata_size > existing_metadata_size the update MUST be rejected.

If the provided metadata_size ≤ SHORT_METADATA_SIZE, the Short Metadata Identifier MUST be set to True.

The Metadata MUST be replaced with the provided payload value (empty is allowed).

If the replacement is part of a Group, the extra payload provided by later transactions for the same asset_id in the same Group MUST be concatenated in order.

The replacement MUST be rejected as soon as the cumulative staged payload for the same asset_id in the same Group exceeds metadata_size.

The cumulative staged payload MUST be equal to the provided metadata_size (no truncation), otherwise the replacement is rejected.

The Metadata Hash MUST be updated according to the Metadata Hash Computation.

The Last Modified Round MUST be updated to the current round.

The MBR Delta amount of the updated Asset Metadata Box MUST be managed contextually:

  • If sign is NULL, no MBR management is required;

  • If sign is NEG, the excess of MBR amount MUST be returned from the ASA Metadata Registry Address to the ASA Manager Address.

An Arc89MetadataUpdated event MUST be emitted.

MBR is returned with an Inner Transaction whose fee is externally provided.

To replace the Asset Metadata for an ASA with larger size Metadata:

  • The ASA MUST still exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST NOT be immutable.

If the provided metadata_size > MAX_METADATA_SIZE the update MUST be rejected.

If the provided metadata_size ≤ existing_metadata_size the update MUST be rejected.

If the provided metadata_size ≤ SHORT_METADATA_SIZE, the Short Metadata Identifier MUST be set to True.

The Metadata MUST be replaced with the provided payload value (empty is allowed).

If the creation is part of a Group, the extra payload provided by later transactions for the same asset_id in the same Group MUST be concatenated in order.

The replacement MUST be rejected as soon as the cumulative staged payload for the same asset_id in the same Group exceeds metadata_size.

The cumulative staged payload MUST be equal to the provided metadata_size (no truncation), otherwise the replacement is rejected.

The Metadata Hash MUST be updated according to the Metadata Hash Computation.

The Last Modified Round MUST be updated to the current round.

The MBR Delta amount of the updated Asset Metadata Box MUST be provided contextually to the ASA Metadata Registry Address.

An Arc89MetadataUpdated event MUST be emitted.

To replace the Metadata slice for an ASA:

  • The ASA MUST still exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST NOT be immutable, and

  • The byte range specified by offset (uint16) and payload length MUST NOT exceed the metadata_size.

The Metadata slice MUST be replaced with the provided payload value.

The Metadata slice replacement MUST preserve the metadata_size.

The Metadata Hash MUST be updated according to the Metadata Hash Computation.

The Last Modified Round MUST be updated to the current round.

An Arc89MetadataUpdated event MUST be emitted.

A group transaction can be used to replace a large Metadata slice atomically.

To migrate the Asset Metadata for an ASA to a new ASA Metadata Registry version:

  • The ASA MUST still exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST NOT be immutable, and

  • The new_registry_id (uint64) MUST be different from the current ASA Metadata Registry Application ID (uint64).

The Deprecated By field MUST be set to the new_registry_id (uint64) value.

An Arc89MetadataMigrated event MUST be emitted.

The migration can be performed more than once and reverted.

⚠️ The Deprecated By field is not included in the Metadata Hash computation, and does not affect the Last Modified Round.

To delete the Asset Metadata for an ASA:

  • The Asset Metadata Box MUST exist, and

  • If the ASA still exists:

    • The Asset Metadata MUST NOT be immutable, and

    • The authorization MUST be restricted to the ASA Manager Address.

⚠️ WARNING: Not even the ASA Manager Address can delete the immutable Asset Metadata of an existing ASA, while anyone can delete Asset Metadata if the ASA has been destroyed, regardless of being immutable or not.

The Asset Metadata Box MUST be deleted.

The MBR Delta amount of the deleted Asset Metadata Box MUST be managed contextually:

  • If the ASA exists, it MUST be returned to the ASA Manager Address, otherwise

  • It MUST be returned to the caller.

An Arc89MetadataDeleted event MUST be emitted.

MBR is returned with an Inner Transaction whose fee is externally provided.

⚠️ The ASA Metadata Registry is not aware of the ASA destruction events, therefore it cannot guarantee a grace period in favor of the ASA Manager Address. ASA Manager Address SHOULD group the ASA destruction and Asset Metadata deletion transactions in the same Group to avoid any race condition.

To provide an extra payload to append to Asset Metadata creation or replace for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist, and

  • The authorization MUST be restricted to the ASA Manager Address.

The extra payload calls MUST appear after the corresponding header call (create or replace) for that same asset_id in the same Group (top-level or inner). Concatenation order is transaction-index order.

All extra payload calls for a given asset_id MUST be top-level if the header call is top-level, or inner if the header is inner.

The Asset Metadata Box already exists since the extra payload call is always preceded by a header call (create or replace).

The header call (create or replace) checks that the extra payload call is keyed to the same Asset ID to manage interleaving and idempotence on the same Group. Interleaving on different Group levels (top-level / inner) are not supported.

Example: Creating and updating different Assets Metadata in the same Group

[Tx1: Create Payload A, Extra Payload A1, Update Payload B, Extra Payload A2, Extra Payload B1]

Would result in the following Asset Metadata Boxes:

  • Asset ID A: [Header A, Create Payload A || Extra Payload A1 || Extra Payload A2]
  • Asset ID B: [Header B, Update Payload B || Extra Payload B1]

To set a reversible Asset Metadata Flag for an ASA:

  • The ASA MUST still exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST NOT be immutable, and

  • The reversible flag (uint8) MUST be in 0 ... 7.

The reversible flag MUST be set to the provided value (bool).

The Metadata Hash MUST be updated according to the Metadata Hash Computation.

The Last Modified Round MUST be updated to the current round.

An Arc89MetadataUpdated event MUST be emitted if not idempotent.

To set an irreversible Asset Metadata Flag for an ASA:

  • The ASA MUST still exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST NOT be immutable, and

  • The irreversible flag (uint8) MUST be in 2 ... 6.

The irreversible flag MUST be set to True (idempotent).

The Metadata Hash MUST be updated according to the Metadata Hash Computation.

The Last Modified Round MUST be updated to the current round.

An Arc89MetadataUpdated event MUST be emitted if not idempotent.

⚠️ WARNING: flags 0, 1 are set only at creation time, for further details refer to the Irreversible Flags section.

To set the Asset Metadata as immutable:

  • The ASA MUST still exist, and

  • The authorization MUST be restricted to the ASA Manager Address, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST NOT be immutable.

The Asset Metadata immutability flag in the Irreversible Flags MUST be set to True.

The Metadata Hash MUST be updated according to the Metadata Hash Computation.

The Last Modified Round MUST be updated to the current round.

An Arc89MetadataUpdated event MUST be emitted.

⚠️ WARNING: Asset Metadata immutability cannot be revoked once set.

The method MUST return the ASA Metadata Registry parameters as a tuple:

  • The firs value is the ASSET_METADATA_BOX_KEY_SIZE (uint8),

  • The second value is the HEADER_SIZE (uint16),`

  • The third value is the MAX_METADATA_SIZE (uint16),`

  • The fourth value is the SHORT_METADATA_SIZE (uint16),`

  • The fifth value is the PAGE_SIZE (uint16),

  • The sixth value is the FIRST_PAYLOAD_MAX_SIZE (uint16),

  • The seventh value is the EXTRA_PAYLOAD_MAX_SIZE (uint16),

  • The eighth value is the REPLACE_PAYLOAD_MAX_SIZE (uint16),

  • The nineth value is the FLAT_MBR (uint64),

  • The tenth value is the BYTE_MBR (uint64).

Clients SHOULD use these parameter values and avoid locally computed constants.

The method MUST return the Asset Metadata Partial URI (string) without the optional #arc compliance fragment:

algorand://<netauth>/app/<singleton_arc89_app_id>?box=

Clients SHOULD use this value as Asset URL and avoid locally computed constants.

To get the MBR Delta for an ASA:

The new_metadata_size (uint16) MUST be less than or equal to MAX_METADATA_SIZE.

  • If the Asset Metadata Box exists, flat_mbr = 0 and then:

    • If the new_metadata_size == metadata_size, then:

      • The returned sign MUST be NULL, and
      • delta_size = 0.
    • If the new_metadata_size > metadata_size, then:

      • The returned sign MUST be POS, and
      • delta_size = new_metadata_size - metadata_size.
    • If the new_metadata_size < metadata_size, then:

      • The returned sign MUST be NEG, and
      • delta_size = metadata_size - new_metadata_size.
  • If the Asset Metadata Box does not exist, flat_mbr = FLAT_MBR and then:

    • The returned sign MUST be POS, and
    • delta_size = ASSET_METADATA_BOX_KEY_SIZE + HEADER_SIZE + new_metadata_size.

The returned amount MUST be flat_mbr + BYTE_MBR * delta_size.

The static MBR Delta calculation provided to the clients is based on:

  • FLAT_MBR (uint64), a parameter of the ASA Metadata Registry (microALGO) equal to AVM MBR for Box creation;

  • BYTE_MBR (uint64), a parameter of the ASA Metadata Registry (microALGO) equal to AVM MBR for byte used by the Box.

The dynamic (RECOMMENDED) MBR Delta calculation is provided to the clients by simulating the create, update, or delete methods.

The method MUST return a pair of booleans ((bool,bool)):

  • The first value is True if the ASA still exists, False otherwise;

  • The second value is True if the Asset Metadata for the ASA exists, False otherwise.

To check if the Asset Metadata is immutable:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist.

The method MUST return True if the Asset Metadata for an ASA is immutable or the ASA Manager Address is set to the Zero Address, False otherwise.

To check if the Asset Metadata is short:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist.

The method MUST return the value of the Short Metadata identifier and the Last Modified Round (uint64).

To get the Asset Metadata Header for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist.

The Metadata Header MUST be returned as a tuple (byte,byte,byte,byte[32],uint64,uint64), where:

To get the Asset Metadata pagination for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist.

The pagination MUST be returned as a tuple (uint16,uint16,uint8), where:

  • The first value (uint16) is the Metadata total length (metadata_size, in bytes),

  • The second value (uint16) is the PAGE_SIZE (in bytes, as defined in the Metadata Pagination section),

  • The third value (uint8) is the total number of Metadata pages (total_pages).

To get the Asset Metadata for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist,

Let total_pages be as returned by Get Metadata Pagination.

  • If total_pages > 0, the provided 0-indexed page (uint8) MUST satisfy page < total_pages.

  • If total_pages == 0, the provided page MUST be 0.

The paginated Asset Metadata MUST be returned as a tuple (bool,uint64,byte[]), where:

  • The first value (bool) is a flag indicating if the Metadata has next page,

  • The second value (uint64) is the Last Modified Round of the Metadata,

  • The third value (byte[]) is the content of Metadata page with length equal to content_size bytes. If total_pages == 0 (i.e., metadata_size == 0), the implementation MUST return an empty byte[]. The empty value does NOT imply the existence of a Metadata Page Hash (see Get Metadata Page Hash).

The has next page flag MUST be True if, at the time of serving the request, (page + 1) * PAGE_SIZE < metadata_size, and False otherwise.

The content byte size MUST NOT exceed the PAGE_SIZE.

The implementation MUST ensure that content_size ≤ PAGE_SIZE for every response.

For pages p where (p+1)*PAGE_SIZE ≤ metadata_size at serve time, the response the implementation SHOULD return content_size = PAGE_SIZE. The final page MUST return content_size = metadata_size − PAGE_SIZE*(total_pages−1).

This invariant guarantees the read operation remains within protocol return-size limits, enables deterministic computation of total pages and has next page, and allows client implementations to safely preallocate buffers and parallelize fetches without risk of oversized responses.

It is RECOMMENDED to group total_pages reading in a single atomic read using a Group Transaction or Inner Transactions.

If the total_pages reading is not atomic, clients MUST verify that Last Modified Round remains constant across pages; if it changes, clients SHOULD re-use the arc89_get_metadata_pagination method and restart reading from page 0. Clients MAY simulate the sequential calls to guarantee atomicity under their own round expectation.

For further details refer to the usage section.

To get a Metadata Slice for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist, and

  • The condition size ≤ PAGE_SIZE MUST hold, and

  • The byte range specified with offset (uint16) and size (uint16) MUST NOT exceed the metadata_size.

The slice extracted from the Metadata MUST be returned.

To get the Metadata Header Hash (hh) for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist.

The Metadata Header Hash (hh) MUST be returned according to the Metadata Hash Computation.

To get the Metadata Page Hash for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist, and

Let total_pages be as returned by Get Metadata Pagination.

  • If total_pages > 0, the provided 0-indexed page (uint8) MUST satisfy page < total_pages.

  • If total_pages == 0, the method MUST fail.

The Metadata Page Hash (ph[page]) MUST be returned according to the Metadata Hash Computation.

The Metadata Page Hash (ph[i]) MUST be returned according to the Metadata Hash Computation.

To get the Metadata Hash for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist.

The Metadata Hash (am) MUST be returned according to the Metadata Hash Computation.

To get a Metadata JSON String value by top-level key for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST be short, and

  • The key’s value length MUST NOT exceed PAGE_SIZE.

The top-level key’s value (JSON String) extracted from the JSON Metadata object MUST be returned (as string).

⚠️ WARNING: This getter does not provide pagination or truncation of the returned value.

⚠️ WARNING: The following conditions cause a runtime error:

  • The Metadata (body) is not a valid UTF-8 encoded JSON object,
  • The top-level key does not exist,
  • The top-level key’s value is not a JSON String.

To get a Metadata uint64 value by top-level JSON key for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST be short.

The top-level key’s value (JSON Uint64) extracted from the JSON Metadata object MUST be returned (as uint64).

⚠️ WARNING: The following conditions cause a runtime error:

  • The Metadata (body) is not a valid UTF-8 encoded JSON object,
  • The top-level key does not exist,
  • The top-level key’s value is not a JSON Uint64.

To get a Metadata object value by top-level JSON key for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST be short, and

  • The key’s value length MUST NOT exceed PAGE_SIZE.

The top-level key’s value (JSON Object) extracted from the JSON Metadata object MUST be returned (as string).

⚠️ WARNING: This getter does not provide pagination or truncation of the returned value.

⚠️ WARNING: The following conditions cause a runtime error:

  • The Metadata (body) is not a valid UTF-8 encoded JSON object,
  • The top-level key does not exist,
  • The top-level key’s value is not a JSON Object.

To get a Metadata base64-decoded value by top-level JSON key for an ASA:

  • The ASA MUST still exist, and

  • The Asset Metadata Box MUST exist, and

  • The Asset Metadata MUST be short, and

  • The b64_encoding enum (uint8) MUST be either 0 (URLEncoding) or 1 (StdEncoding), and

  • The key’s base64-decoded value length MUST NOT exceed PAGE_SIZE.

The top-level key’s value (JSON String) extracted from the JSON Metadata object MUST be base64-decoded using the selected b64_encoding and returned (as byte[]).

⚠️ WARNING: This getter does not provide pagination or truncation of the returned value.

⚠️ WARNING: The following conditions cause a runtime error:

  • The Metadata (body) is not a valid UTF-8 encoded JSON object,
  • The top-level key does not exist,
  • The top-level key’s value is not a JSON String,
  • The top-level key’s value is not a valid base64-encoding string for the chosen encoding.

For further details on the base64 encodings refer to the base64_decode AVM opcode specifications.

ARC-28 EVENT SIGNATURE4-BYTE SELECTOR (HEX)
Arc89MetadataUpdated(uint64,uint64,uint64,byte,byte,bool,byte[32])8b035084
Arc89MetadataMigrated(uint64,uint64,uint64,uint64)c87023bf
Arc89MetadataDeleted(uint64,uint64,uint64)bc3f20d1

The ASA Metadata Registry AppSpec is published in the reference implementation repository.

The ASA Metadata Registry has two modes of operation:

  • Algod API: the entire Asset Metadata is retrieved via a single request to the Algod REST API endpoints (or via SDK wrappers);

  • AVM: the paginated Asset Metadata is retrieved via grouped (RECOMMENDED) or sequential Application Calls (real or simulated) to the ASA Metadata Registry.

The Algod clients retrieve the Asset Metadata from two entrypoints:

  1. The Asset ID;

  2. The Asset Metadata URI.

A minimal [Python SDK. is provided with the reference implementation.

Given the Asset ID 12345, the client:

  1. Calls the Algod API GetAssetByID endpoint to get the Asset URL field (url) from the response and drops the #arc3 suffix (if present), obtaining:

    algorand://<netauth>/app/<singleton_arc89_app_id>?box=;

  2. Encodes the Asset ID as base64url to get the Asset Metadata Box Name (<base64url_encoded_asset_id>);

  3. Calls the Algod API GetApplicationBoxByName endpoint to get the content of the Asset Metadata Box from the response:

Terminal window
curl -X GET http://localhost/v2/applications/<singleton_arc89_app_id>/box?name=<base64_encoded_asset_id> \
-H 'Accept: application/json' \
-H 'X-Algo-API-Token: API_KEY'

The value field of the response contains the Asset Metadata Box content as concatenation of the following fields:

  • Metadata Header (byte[HEADER_SIZE]);
  • Metadata Body (byte[]): ARC-3 JSON Metadata.

Clients MUST strip the Metadata Header (byte[HEADER_SIZE]) from the Asset Metadata Box value before parsing the JSON Metadata.

Given the Asset Metadata URI algorand://<netauth>/app/<singleton_arc89_app_id>?box=<base64url_encoded_asset_id>#arc3, the client:

  1. Calls the Algod API GetApplicationBoxByName endpoint to get the content of the Asset Metadata Box from the response:
Terminal window
curl -X GET http://localhost/v2/applications/<singleton_arc89_app_id>/box?name=<base64_encoded_asset_id> \
-H 'Accept: application/json' \
-H 'X-Algo-API-Token: API_KEY'

The value field of the response contains the Asset Metadata Box content as concatenation of the following fields:

  • Metadata Header (byte[HEADER_SIZE]);
  • Metadata Body (byte[]): ARC-3 JSON Metadata.

Clients MUST strip the Metadata Header (byte[HEADER_SIZE]) from the Asset Metadata Box value before parsing the JSON Metadata.

The AVM clients issue Application calls (real or simulated) to the ASA Metadata Registry in two ways:

  1. (RECOMMENDED) Atomically, via grouped (Top-level or Inner) Application Calls;

  2. Sequentially, via standalone Application Calls;

Example 1: Atomic read with Top-level Group
Section titled “Example 1: Atomic read with Top-level Group”

Given the Asset ID 12345, the client:

  1. Call arc89_get_metadata_pagination with asset_id=12345, ASA Metadata Registry returns:

    • The total Metadata byte size (uint16);
    • The PAGE_SIZE (uint16), as defined in the Metadata Pagination section;
    • The total Metadata pages N (uint8).
  2. Check that N ≤ MAX_TXN_PER_GROUP.

  3. Group call N * arc89_get_metadata with asset_id=12345 and page=0...N-1 (0-based).

PROS:

  • Best UX, no delay, atomic fetch guarantees integrity and no data drift.

CONS:

  • The fetchable metadata_size is capped by MAX_TXN_PER_GROUP capacity for a Top-level Group, (while Inner Groups can fetch up to MAX_METADATA_SIZE).
Example 2: Sequential read, while “has next” page
Section titled “Example 2: Sequential read, while “has next” page”

Given the Asset ID 12345, the client:

  1. Call arc89_get_metadata with asset_id=12345 and page=0 (0-based), ASA Metadata Registry returns:

  2. While has next page, call arc89_get_metadata with asset_id=12345 and incremented page, verifying Last Modified Round is unchanged.

PROS:

  • No arithmetic on the caller; just loop while has next.

CONS:

  • Caller doesn’t know the total Metadata length or pages upfront.
  • Callers that want progress bars have to either read page 0 first or call the separate arc89_get_metadata_pagination method.

BEST FOR:

  • Wallets and explorers that stream progressively and don’t care about total Metadata length until finished.
Example 3: Sequential read, two-call pattern
Section titled “Example 3: Sequential read, two-call pattern”

Given the Asset ID 12345, the client:

  1. Call arc89_get_metadata_pagination with asset_id=12345, ASA Metadata Registry returns:

    • The total Metadata byte size (uint16);
    • The PAGE_SIZE (uint16), as defined in the Metadata Pagination section;
    • The total Metadata pages N (uint8).
  2. Loop calls arc89_get_metadata with asset_id=12345 and page=0...N-1 (0-based), verifying Last Modified Round is unchanged.

PROS:

  • Changes of PAGE_SIZE in the future won’t break readers;
  • Improves UX (progress, preallocation).

CONS:

  • Requires two round trips in the common case.

BEST FOR:

  • Latency-tolerant clients and SDKs that value clarity and future-proofing.

The authorization to create the Asset Metadata and update and delete mutable Asset Metadata is granted to the ASA Manager Address to preserve the ASA trust model. The authorization is not granted to the ASA Creator Address, since this role could be performed programmatically by Applications and is not supposed to be the long-lasting maintainer of the ASA.

Copyright and related rights waived via CC0.