# Asset & Chain Registry

Every asset your tenant uses, on every chain and network, under one key — your own tokens and the catalogue's —, the chains Aurea knows, and custom EVM tokens added by their contract alone, read on chain before anything is stored.

## Overview

- **An asset** is a chain's native coin, or a token contract on that chain, in one network. Your tenant's assets are its tokens, the ones its token lists and its wallets' balances read. The registry shows them with what they lacked: one key per asset, your own metadata, and the last reading of each contract.
- **Custom tokens by their contract.** Send a chain id and a contract address: Aurea reads the contract on its chain and stores the token with the contract's own decimals — or refuses, and stores nothing.
- **The routes** are under **Asset Registry** in the API Reference — [List Assets](https://docs.aureahub.com/docs/assets-list.md), [Get an Asset](https://docs.aureahub.com/docs/asset-get.md), [List Chains](https://docs.aureahub.com/docs/assets-chains.md), [Register an Asset](https://docs.aureahub.com/docs/asset-register.md), [Change an Asset's Metadata](https://docs.aureahub.com/docs/asset-metadata-update.md) and [Check an Asset](https://docs.aureahub.com/docs/asset-check.md) —, on the caller's own tenant, with an access token (Who Calls the Routes, below). Every tenant has them: no switch.
- **One side effect to know before your first asset on a chain**: where your tenant listed no token of its own on that chain, the chain's token lists become your own (Your Token Lists, below).

```json
{
  "assetId": "7e5df737-88f9-4c90-8fc8-0f70429c2d17",
  "assetKey": "evm:8453:0xdbfbe194c514a645bb0fcb5230adaf85efea3fe7",
  "policyAsset": "base:0xdbfbe194c514a645bb0fcb5230adaf85efea3fe7",
  "family": "evm",
  "chain": "base",
  "chainId": 8453,
  "isTestnet": false,
  "type": "token",
  "address": "0xdBfbe194c514a645Bb0FCb5230aDaF85EFea3fe7",
  "symbol": "GBND31",
  "name": "Green Bond 2031",
  "decimals": 6,
  "logoUri": "https://cdn.example.com/tokens/gbnd31.png",
  "coingeckoId": null,
  "isActive": true,
  "source": "tenant",
  "registeredVia": "registry",
  "metadata": {
    "desk": "treasury",
    "isin": "XS2964021187"
  },
  "contractCheck": {
    "result": "matched",
    "checkedAt": "2026-10-14T09:12:40.517Z"
  },
  "createdAt": "2026-10-14T09:12:40.517Z",
  "updatedAt": "2026-10-14T09:12:40.517Z"
}
```

A custom token registered on Base: its decimals, symbol and name read from the contract, your ISIN and desk as metadata, its contract matched when it was registered.

## The Asset Key

`assetKey` names one asset on one chain and network: `<family>:<chainId>:<asset>`.

| Part | What it is |
| --- | --- |
| family | `evm` for every EVM chain the Hub supports — one key signs on all of them —, else the chain itself: `solana`, or another chain the catalogue lists. |
| chainId | The chain id, which names the chain *and* its network: an EVM testnet has its own (`84532` is Base Sepolia); Solana's are `1399811149` (mainnet) and `1399811150` (testnet). |
| asset | `native` for the chain's coin; an EVM token's contract address in lower case; a Solana mint exactly as it is (base58 is case-sensitive). |

```text
evm:8453:native                                       ETH on Base
evm:84532:native                                      ETH on Base Sepolia
evm:8453:0xdbfbe194c514a645bb0fcb5230adaf85efea3fe7   a token on Base
solana:1399811149:native                              SOL on Solana
solana:1399811149:<mint>                              a Solana token, its mint as it is
```

- **Native aliases are `native`**: no address, the EVM zero address `0x0000000000000000000000000000000000000000`, Solana's System Program `11111111111111111111111111111111` and wrapped SOL `So11111111111111111111111111111111111111112`.
- **One string per asset**, whatever route or row it came from and whatever letter case its address was written in: a safe key for your own records. The routes answer it; none takes it as input.
- **`policyAsset`** is the same asset as a policy rule's `assets` names it — `<chain>:<address|native>`, e.g. `base:0xdbfbe194c514a645bb0fcb5230adaf85efea3fe7` or `gnosis:native` —, the network left to the rule's own `chains` ([Policy Sets](https://docs.aureahub.com/docs/guide-policy-sets.md)). Copy it into a rule as it is.
- Both are `null` only for a row whose address is not a valid one of its chain.

The chain ids the Hub supports:

| chain | chainId | chainId, isTestnet true |
| --- | --- | --- |
| ethereum | 1 | 11155111 |
| polygon | 137 | 80002 |
| gnosis | 100 | 10200 |
| bsc | 56 | 97 |
| arbitrum | 42161 | 421614 |
| optimism | 10 | 11155420 |
| base | 8453 | 84532 |
| avalanche | 43114 | 43113 |
| celo | 42220 | 11142220 |
| flowevm | 747 | 545 |
| solana | 1399811149 | 1399811150 |

## Your Assets and the Catalogue

- **`source` `tenant`**: one of your tenant's own assets, active or not — registered here (`registeredVia` `registry`) or added any other way, for example with the token routes (`registeredVia` `null`).
- **`source` `catalogue`**: an active asset of Aurea's catalogue, on a chain where your tenant lists none of its own: the assets your tenant uses there. You read them; you do not change them — Change an Asset's Metadata and Check an Asset answer `404` `ASSET_NOT_FOUND` for one.
- [List Assets](https://docs.aureahub.com/docs/assets-list.md) answers your own first, then the catalogue's on the other chains. [Get an Asset](https://docs.aureahub.com/docs/asset-get.md) answers either by its id — a catalogue asset even on a chain where your lists are now your own. Another tenant's asset: `404` `ASSET_NOT_FOUND`.
- `type`: `native` (its `address` is `null`) or `token` (the contract, or the Solana mint, as stored). `isActive`: whether your tenant's lists use it.

## The Chain Registry

[List Chains](https://docs.aureahub.com/docs/assets-chains.md) answers every chain of Aurea's chain registry — mainnets first, then by name —, with what your tenant has on each. `isTestnet` answers one network only.

| Field | Meaning |
| --- | --- |
| chain | The chain as wallets, tokens and balances name it — an EVM testnet by its chain's slug: Base Sepolia is `base` with `isTestnet` `true`. |
| registryId | The registry's own id of the row: `base-sepolia`, `polygon-amoy`, `celo-sepolia`… |
| name | Its name, e.g. `Base Sepolia`. |
| family | `evm`, or the chain itself (The Asset Key). |
| chainId, isTestnet | Its chain id — `null` when the registry holds none for it — and its network. |
| nativeToken | The chain's coin: `symbol`, `name`, `decimals`; `null` when the registry holds none. |
| platformNode | Whether Aurea has its own node for the chain: what reads a contract when you register or check an asset. Where it is `false`, both answer `503` `ASSET_CONTRACT_UNREAD`. The node's address is never answered. |
| enabled | Whether your tenant enabled the chain — its blockchain configuration for that chain id. Registering an asset does not need it. |
| assets | `total` and `active`: your tenant's own assets on the chain and network. The catalogue's are not counted. |

## Registering an Asset by Its Contract

[Register an Asset](https://docs.aureahub.com/docs/asset-register.md) adds an EVM token to your tenant's assets from its `chainId` and contract `address` alone. In this order:

1. **The chain.** `chainId` is one of the EVM chain ids above, mainnet or testnet. Solana's: `400` `ASSET_CHAIN_UNSUPPORTED` (Solana Mints, below). Any other: `400` `ASSET_CHAIN_UNKNOWN`.
2. **The reading**, through Aurea's own node of that chain — never your tenant's own blockchain configuration: the code at the address, then the contract's `decimals()`, `symbol()` and `name()` (a `string`, or the `bytes32` some older tokens answer), one after the other, each within 5 seconds. A node that fails or does not answer in time: `503` `ASSET_CONTRACT_UNREAD`, with `details.retryable` `true` — try again. No node for the chain (`platformNode` `false`): the same code. The address may be all lower or all upper case, or EIP-55: a mixed case with a wrong checksum is `400` `TOKEN_ADDRESS_CHECKSUM_INVALID`.
3. **The contract.** No code at the address: `422` `TOKEN_ADDRESS_NOT_A_CONTRACT`. No `decimals()` as an ERC-20 answers it — it reverts, answers nothing, or answers a value above 36: `422` `ASSET_NOT_ERC20`. `decimals` sent, and the contract answers others: `422` `ASSET_DECIMALS_MISMATCH`, with `details.onChain` and `details.given`.
4. **Its symbol and name**: yours when you send them, else the contract's; a name the contract does not answer is the symbol. No usable symbol (none, or longer than 20 characters) or name (longer than 255): `400` `ASSET_METADATA_UNREADABLE` — send `symbol` and `name`.
5. **One per address, one per symbol**, on each chain and network. An asset of yours at that address, in any letter case: `409` `ASSET_EXISTS`, with its `details.assetId`. Another of yours with that symbol, in any letter case — the chain's native coin included: `409` `ASSET_SYMBOL_TAKEN`, with its `details.assetId`: send another `symbol`.

- **A refusal writes nothing.**
- **Stored with the contract's decimals** — a token stored with the wrong decimals moves amounts wrong by orders of magnitude —, its address in its EIP-55 form, `registeredVia` `registry`, `contractCheck` `matched` at that moment, and what you sent: `logoUri` (an `https://` URL, up to 2048 characters), `coingeckoId` (the token's price-feed id: 1 to 100 of `a–z 0–9 -`, starting with a letter or a digit), `metadata`, and `active` (default `true`). `201` with the asset.
- **Then it is your tenant's like any token it lists.** Its logo, decimals or status change with the token routes ([Update Token](https://docs.aureahub.com/docs/admin-tokens-update.md), [Toggle Token Active](https://docs.aureahub.com/docs/admin-tokens-toggle.md)); here, only its metadata changes.
- Recorded in your audit trail as `asset.registered`.

## Your Token Lists

On a chain where your tenant lists no token of its own — no row on the chain's mainnet or testnet, active or not —, your tenant's token lists of that chain are Aurea's catalogue's: List Assets answers them with `source` `catalogue`. **The first asset your tenant adds on that chain — registered here, or added with the token routes — makes the chain's lists your own, on both of its networks, from then on:**

- **Per chain** — [Tokens by Chain](https://docs.aureahub.com/docs/tokens-by-chain.md), the tokens a wallet's balances read beside the native coin, the tokens whose received transfers are recorded (coming), the balances [report](https://docs.aureahub.com/docs/guide-reports.md): your tenant's own active tokens of that chain only. The catalogue's tokens of the chain — its native coin among them — are no longer in them unless your tenant lists them too; on the chain's other network, where it lists none, they hold no token. A wallet's native coin balance, and the balances report's, are read as before.
- **[List Tokens](https://docs.aureahub.com/docs/tokens-list.md)**, across chains, answers the catalogue's list only while your tenant has no active token at all on that network: from its first one, it answers your tenant's own tokens only, on every chain of that network.
- **[Supported Chains](https://docs.aureahub.com/docs/tokens-chains.md)** lists the chains where your tenant has an active token of its own and an enabled configuration: with the asset active and the chain enabled, the chain appears there.
- **An inactive asset counts too** (`"active": false`): the chain's lists are your own, and with nothing active they hold no token.

**Before your first asset on a chain**, read what the catalogue gives you there — [List Assets](https://docs.aureahub.com/docs/assets-list.md) with `source=catalogue` and the chain's `chainId` — and add with the token routes ([Create Token](https://docs.aureahub.com/docs/admin-tokens-create.md)) each one your tenant should keep. Token lists are cached for up to five minutes: a change can take that long to show everywhere.

## Metadata

- **Your own keys and values** on an asset — an ISIN, a desk, an internal code. Aurea stores them and answers them on these routes only; nothing else reads them.
- **The rules**: at most 20 keys; a key is 1 to 40 of `a–z 0–9 _`, starting with a letter; a value is a text — never a number, a boolean or an object — of 1 to 200 characters, with no space at either end. Else `400` `ASSET_REQUEST_INVALID`, each problem in `details.errors`, e.g. `metadata.ISIN: a key is 1 to 40 of a–z 0–9 _`.
- **At most 8 KB as stored.** Keys and values in plain ASCII within the rules always fit. Characters that take two to four bytes can pass it — such a request answers `500` and writes nothing: keep such values shorter.
- **Set it** when you register (`metadata`), or on any asset of your own — a native coin, a Solana mint, one added with the token routes — with [Change an Asset's Metadata](https://docs.aureahub.com/docs/asset-metadata-update.md), which **replaces the whole object**: send every key you keep. `null` clears it (`metadata` reads `null`); `{}` leaves an empty object. A catalogue asset: `404` `ASSET_NOT_FOUND`.
- Your audit trail records the names of the keys, never a value.

## Checking an Asset Against Its Contract

[Check an Asset](https://docs.aureahub.com/docs/asset-check.md) reads the contract of one of your EVM token assets again — the same reading as a registration, through Aurea's own node, each read within 5 seconds — and keeps what it found on the asset as `contractCheck`: its `result` and `checkedAt`. Any EVM token of yours, registered here or not, active or not. The results, decided in this order:

| result | Meaning |
| --- | --- |
| not_a_contract | No code at the address, on the asset's chain and network. |
| not_erc20 | Code, but no `decimals()` as an ERC-20 answers it: it reverts, answers nothing, or answers a value above 36. |
| decimals_differ | The contract's decimals are not the asset's: its amounts are read and sent wrong by a power of ten. |
| symbol_differs | The decimals agree; the contract's symbol is not the asset's (letter case aside). |
| matched | Code, the asset's decimals, and its symbol — or a contract that answers no readable symbol. |

- **The asset is not corrected.** Only `contractCheck` changes — not `updatedAt`. Correct an asset's decimals with the token routes ([Update Token](https://docs.aureahub.com/docs/admin-tokens-update.md)), then check it again.
- **A symbol of your own**, sent when you registered the asset, answers `symbol_differs` on a check whenever it is not the contract's — the registration itself records `matched`: its decimals are the contract's.
- The answer also carries the reading: `contract` — `address` (EIP-55), `hasCode`, `decimals`, `symbol`, `name`, each `null` where the contract answered nothing readable.
- A native coin: `400` `ASSET_NATIVE`. A Solana asset: `400` `ASSET_CHAIN_UNSUPPORTED`. A catalogue asset: `404` `ASSET_NOT_FOUND`. The node: `503` `ASSET_CONTRACT_UNREAD`, and nothing is kept.
- Recorded in your audit trail as `asset.checked`.

## Solana Mints

- **A Solana token is not registered here**: Register an Asset takes an EVM chain id only, and answers Solana's with `400` `ASSET_CHAIN_UNSUPPORTED`. No mint account is read.
- **Add it with the token routes** ([Create Token](https://docs.aureahub.com/docs/admin-tokens-create.md)), which take its mint as written. It is then one of your assets here — key `solana:1399811149:<mint>` on mainnet —, may carry metadata, and is not checked: Check an Asset answers `400` `ASSET_CHAIN_UNSUPPORTED`.

## Who Calls the Routes

- **A signed-in user's access token** (`Authorization: Bearer`), for the user's own tenant. Your tenant's signature (the HMAC headers) and your [server credentials](https://docs.aureahub.com/docs/guide-server-credentials.md)' tokens do not open these routes: a server token in `Authorization` answers `401` `Authentication required`, and `X-Aurea-Server-Token` is not read here.
- **Read** — List Assets, Get an Asset, List Chains: `owner`, `tenantadmin`, `non_signing_admin`, `signer`, `approver`, `editor`, `viewer`, `auditor`. **Write** — Register an Asset, Change an Asset's Metadata, and Check an Asset, which keeps its result on the asset: `owner`, `tenantadmin`, `non_signing_admin`, `editor`. The roles that read and change your tenant's tokens ([Roles](https://docs.aureahub.com/docs/authentication.md)).
- Any other role: `403` `Tenant admin access required. Your role does not have permission to perform this action.` A frozen user or tenant: `423` ([Freezes & Quarantines](https://docs.aureahub.com/docs/guide-freezes.md)).

## Audit Trail

Your [audit trail](https://docs.aureahub.com/docs/audit-events.md) [Coming soon, upon request](https://docs.aureahub.com/docs/feature-availability.md), records each write in the same transaction as the asset — category `settings`, subject `asset` and its id:

| action | details |
| --- | --- |
| asset.registered | `chainId`, `address` (the contract's, public), `symbol`, `decimals`, `active`, and `metadataKeys`: the names of the metadata's keys. |
| asset.metadata_changed | `before` and `after`: the names of the keys; `cleared`: `true` when `null` cleared it. |
| asset.checked | `chainId`, `address`, `result`, `decimals` (the asset's) and `onChainDecimals` (the contract's; `null` when it answered none). |

Each also carries `scope`: `tenant` for your users, `platform` when Aurea acted on your tenant's assets. Never a metadata value.

## Refusals

The usual envelope, the code in `details.code`. Nothing is written.

| Status | details.code | message |
| --- | --- | --- |
| 400 | ASSET_REQUEST_INVALID | `Invalid request`, each problem in `details.errors` — e.g. `address: an EVM address`, `logoUri: Invalid input: must start with "https://"`, `metadata.ISIN: a key is 1 to 40 of a–z 0–9 _`, `metadata: Required`, `assetId: Invalid uuid`, or `(body): Unrecognized key(s) in object: 'isNative'` for a field or query parameter the route does not take. Any of the six routes. |
| 400 | ASSET_CHAIN_UNKNOWN | `Chain 12345 is no chain the Hub supports`, with `details.chainId` — List Assets' `chainId`, Register an Asset |
| 400 | ASSET_CHAIN_UNSUPPORTED | `Only an EVM token contract is registered here; a Solana mint is added with the tenant's token routes` (Register an Asset) or `Only an EVM token contract is read here` (Check an Asset), with `details.chainId` |
| 400 | ASSET_METADATA_UNREADABLE | `` The contract names no usable symbol or name: send `symbol` and `name` ``, with `details.field` `symbol` |
| 400 | ASSET_NATIVE | `A native coin has no contract to read` — Check an Asset |
| 400 | TOKEN_ADDRESS_CHECKSUM_INVALID | `The token address has a wrong EIP-55 checksum: check it for a typo, or send it in lower case`, with `details.field` `address` |
| 404 | ASSET_NOT_FOUND | `No such asset here.` — another tenant's asset; on Change an Asset's Metadata and Check an Asset, a catalogue one too |
| 409 | ASSET_EXISTS | `The tenant lists this asset already`, with `details.assetId` (absent when two registrations of the same asset cross) |
| 409 | ASSET_SYMBOL_TAKEN | `` Another asset of the tenant on this chain has this symbol: send another `symbol` ``, with `details.assetId` |
| 422 | TOKEN_ADDRESS_NOT_A_CONTRACT | `No contract exists at this address on chain 8453`, with `details.field` `address` and `details.chainId` |
| 422 | ASSET_NOT_ERC20 | `The contract does not answer decimals() as an ERC-20 token does`, with `details.field` `address` and `details.chainId` |
| 422 | ASSET_DECIMALS_MISMATCH | `The contract's decimals are 6, not 18`, with `details.field` `decimals`, `details.chainId`, `details.onChain` and `details.given` |
| 503 | ASSET_CONTRACT_UNREAD | `The token contract cannot be read: the node for chain 8453 did not answer`, or `The token contract cannot be read: no node is configured for chain 8453`, with `details.chainId` and `details.retryable` `true` — Register an Asset, Check an Asset |

- No access token, or one that is not valid: `401` `Authentication required`. A role that may not call the route: `403`. A frozen user or tenant: `423`.
- Metadata over 8 KB as stored answers `500`, and nothing is written (Metadata).

---

Web version: https://docs.aureahub.com/#guide-asset-registry
