Concepts
Native Usernames
@names built into the chain · Live since block 1,200,000 · Estimated read time: 8 minutes
Updated 2026-11-07: names are live. Building with names (transfers, records, a marketplace) is in Names for Developers.
What a name is
A native username such as @alice is a short, readable pointer to an Asentum address. Instead of pasting ase15xs6rgdp5x... you send to @alice. Names live in a system contract that is part of the chain itself, so every wallet, app, explorer and bot can read them from any node without a third-party service.
- The address stays the account. Your balance, stake and history belong to your address. The name only points at it. Selling or transferring a name never moves funds.
- Resolved at signing time. Wallets look the name up when you build the transaction, not earlier, so the transaction goes to whoever owns the name at that moment.
- Both are shown. Confirm screens show the name and the address it resolves to (for example
@alicewithase15xs6...yggla5), so you can always check where the ASE goes. - Display is
@name. There is no suffix such as .ase.
Activation
Names went live with release 0.9.0 at block 1,200,000 on the testnet (chain 1423), on 7 November 2026. At the start of that block the registry was installed at the system address ase1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqty736ey together with the reserved list, and registrations opened in the same block.
- A node older than 0.9.0 has no name lookups (it answers 404). Apps should treat that as names being off and keep showing plain addresses.
- Names have no expiry on the testnet.
- Testnet names do not carry over to mainnet. Mainnet starts with an empty registry.
Naming rules
- 3 to 24 characters.
- Only lowercase letters
a-z, digits0-9and the hyphen-. ASCII only: lookalike letters from other alphabets are refused. - No hyphen at the start or the end, and no double hyphen (
my--name). - A name cannot start with
0xorase1, so it can never be mistaken for an address. - Case does not matter:
@Alice,ALICEandaliceare the same name. A single leading@is accepted and ignored. - Each address can register at most 5 names per 43,200 blocks (one day at the 2 second target block time).
Fees
Shorter names cost more. The fee is paid once, at registration.
| Length | Fee | Example |
|---|---|---|
| 3 characters | 500 ASE | @ase |
| 4 characters | 100 ASE | @pepe |
| 5 characters | 50 ASE | @alice |
| 6 to 24 characters | 10 ASE | @satoshi |
- Send exactly the fee as the value of the
registercall. Any other amount reverts and nothing is charged beyond gas. - Half of every fee is burned (sent to
ase1qqqqqqqqqqqqqqqqqqqqqqqqqqqqph4djanawk, which nobody holds a key for). The other half goes to the treasury,ase13qvpuh46hzcchnq0pjy037f8rv4utlq62l6zpj. - Transfers, primary changes and records cost only gas.
Primary names
An address can own several names but has one primary name. The primary name is what apps show next to your address (the explorer, the airdrop leaderboard, Social and Communities).
- Your first name becomes your primary name automatically.
- Call
setPrimary(name)to switch to another name you own, orclearPrimary()to show no name. - Transferring a name away clears it as your primary name. The new owner does not get it as their primary name until they set it.
Records
The owner can attach a few text records to a name. Records are printable ASCII. Setting a record to an empty value deletes it. When a name is transferred its records are cleared, so the new owner starts clean.
| Key | Max length | What it holds |
|---|---|---|
| avatar | 256 | An image URL starting with https://, http:// or ipfs:// |
| x | 15 | An X handle (letters, digits, underscore). A leading @ is dropped. |
| url | 256 | A link starting with https://, http:// or ipfs:// |
| description | 160 | A short bio |
Records are text written by the name owner. Apps should show them as plain text, never as HTML, and treat links as untrusted.
Reserved names
Some names are reserved from the start to protect users from impersonation: network names, roles such as admin and support, and partner projects. A reserved name cannot be registered. Only the registrar address can release one (to its rightful owner, free of charge) or change the list. This is the registry's only admin power: it cannot take, move or edit a name somebody owns.
The registrar can add more reserved names later. Use isReserved(name) or isAvailable(name) to check the current state.
How to register
With the explorer
- Open explorer.asentum.com/names.
- Search for a name. The page shows whether it is free, why not if it is taken, reserved or invalid, and the exact fee.
- Connect the Asentum Chrome extension or the Telegram wallet and confirm the registration. Check that the amount matches the fee.
- Once the transaction is in a block the name is yours. Your first name is your primary name; with more than one you can choose which one is primary on the same page.
From code
Registering is an ordinary contract call to ase1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqty736ey: method register, the name as the only argument, the fee as the value, and a gas limit of about 300,000.
import { NAME_REGISTRY_CONTRACT, nameFeeWei, normalizeName } from '@asentum/sdk';
const name = normalizeName('@MyName'); // "myname", or null if invalid
const tx = await wallet.sendCall({
to: NAME_REGISTRY_CONTRACT, // ase1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqty736ey
method: 'register',
args: [name],
valueWei: nameFeeWei(name), // exactly the fee, or it reverts
gasLimit: 300_000n,
});
await tx.wait();
// Optional: pick which of your names is the primary one.
await wallet.sendCall({ to: NAME_REGISTRY_CONTRACT, method: 'setPrimary', args: [name], gasLimit: 300_000n });With @asentum/sdk 0.3.1 the same is one line: await (await wallet.registerName(name)).wait(). The same with @asentum/connect in a dapp:
import { useContract } from '@asentum/connect';
const REGISTRY = 'ase1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqty736ey';
const registry = useContract(REGISTRY);
// Check first (free view call), then register with value = the fee in wei.
const info = await registry.view('isAvailable', ['myname']);
if (info.available) {
const txHash = await registry.call('register', ['myname'], info.fee);
}Node API
Every node from release 0.9.0 answers two lookups. Each runs one view call and counts toward the caller's view-call rate limit.
| Endpoint | Returns |
|---|---|
| GET /name/:name | The owner of a name: { active, name, address, ase1, blockNumber }. address and ase1 are null when the name is free. |
| GET /name-of/:address | The primary name of an address (ase1 or 0x): { active, address, ase1, name, blockNumber }. name is null when it has none. |
GET https://testnet.asentum.com/name/alice
{
"active": true,
"name": "alice",
"address": "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ase1": "ase15xs6rgdp5xs6rgdp5xs6rgdp5xs6rgdpyggla5",
"blockNumber": "1200345"
}
GET https://testnet.asentum.com/name-of/ase15xs6rgdp5xs6rgdp5xs6rgdp5xs6rgdpyggla5
{
"active": true,
"address": "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ase1": "ase15xs6rgdp5xs6rgdp5xs6rgdp5xs6rgdpyggla5",
"name": "alice",
"blockNumber": "1200345"
}- Add
?blockTag=finalizedto read finalized state instead of the tip. - Before activation the answer is
{ "active": false, "error": "names not active", "activatesAt": "1200000" }. - Input that breaks the naming rules returns
error: "invalid name"(or"invalid address"). - Nodes older than 0.9.0 answer 404. Treat that the same as not active and fall back to showing the address.
JSON-RPC
The same lookups are available as JSON-RPC methods on the node's POST / endpoint. The first parameter is the name or address; an optional second parameter "finalized" reads finalized state.
| Method | Params | Result |
|---|---|---|
| ase_resolveName | [name, blockTag?] | Same body as GET /name/:name |
| ase_lookupAddress | [address, blockTag?] | Same body as GET /name-of/:address |
curl -s https://testnet.asentum.com -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": 1,
"method": "ase_resolveName",
"params": ["@alice"]
}'
// result: the same body as GET /name/:name
{ "jsonrpc": "2.0", "id": 1, "method": "ase_lookupAddress",
"params": ["ase15xs6rgdp5xs6rgdp5xs6rgdp5xs6rgdpyggla5", "finalized"] }On a node without names the methods fail with JSON-RPC error -32601 (method not found).
SDK
@asentum/sdk 0.3.0 added the name lookups (they go through the node API above), and 0.3.1 added a wallet method for every write. The developer guide has worked examples.
| Function | What it does |
|---|---|
| client.resolveName(name) | The ase1 owner of a name, or null when it is free. |
| client.lookupAddress(address) | The primary name of an address (without the @), or null. |
| client.getNameLookup(name) | The full GET /name/:name body. |
| client.resolveRecipient(input) | A "to" field to { address, name }: "@name" is resolved, an address is passed through. Throws NameNotFoundError for a free name. |
| normalizeName(input) | The canonical form ("@Alice" to "alice"), or null when it breaks a rule. |
| formatName(name) | "alice" to "@alice" for display. |
| isNameInput(input) | true for valid "@name" input. |
| nameFeeWei(name) | The registration fee in wei. |
| client.namesActive() | true once the node serves names. |
| wallet.registerName(name) | 0.3.1. Registers the name and sends exactly the fee. |
| wallet.transferName(name, to) | 0.3.1. Gives the name to another address or a contract. Owner only, no fee. |
| wallet.setPrimaryName(name) / wallet.clearPrimaryName() | 0.3.1. Picks or removes the primary name. |
| wallet.setNameRecord(name, key, value) | 0.3.1. Sets avatar, url, x or description; an empty value clears it. |
| NAME_REGISTRY_CONTRACT | The registry address (ase1). |
| NamesNotActiveError, NameNotFoundError, InvalidNameError | Thrown before activation, for a free name, and for invalid input. |
import { AsentumClient, normalizeName, formatName, nameFeeWei,
NamesNotActiveError, NameNotFoundError } from '@asentum/sdk';
const client = new AsentumClient('https://testnet.asentum.com');
// Name to address (ase1), or null when nobody owns it.
const owner = await client.resolveName('@alice');
// Address to its primary name (without the @), or null.
const name = await client.lookupAddress('ase15xs6rgdp5xs6rgdp5xs6rgdp5xs6rgdpyggla5');
if (name) console.log(formatName(name)); // "@alice"
// A "to" field: "@name" is resolved, an address is passed through.
const { address, name: shown } = await client.resolveRecipient('@alice');
// On a node older than 0.9.0 every lookup throws NamesNotActiveError.Contract methods
The registry is a JavaScript system contract at ase1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqty736ey. Addresses inside the contract are lowercase hex (what msg.sender is). Every method except register refuses a non-zero value.
There is no approve or operator method: only the owner can move a name. A contract can own names, so a marketplace holds listed names in escrow. See Building a names marketplace.
Writes
| Method | Who | What it does |
|---|---|---|
| register(name) | Anyone | Registers a free name to the caller. Send exactly the fee as the call value. Your first name becomes your primary name. Returns the name. |
| setPrimary(name) | Owner | Makes one of your names the primary name of your address. |
| clearPrimary() | Anyone | Removes your primary name. The name stays yours. |
| transfer(name, to) | Owner | Gives the name to another address. Clears your primary name if it was this one, and clears its records. |
| setRecord(name, key, value) | Owner | Sets avatar, x, url or description. An empty value deletes the record. |
| releaseReserved(name, to) | Registrar | Hands a reserved name to an address, free of charge. |
| addReserved(name) / removeReserved(name) | Registrar | Adds or removes a name on the reserved list. |
Views (free, POST /view)
| Method | Returns |
|---|---|
| resolve(name) | The owner (lowercase hex) or null. |
| primaryOf(address) | The primary name of an address (without the @) or null. |
| isAvailable(name) | { name, available, reason, fee }. reason is "taken", "reserved", a rule the input breaks, or null. fee is in wei. |
| get(name) | { name, owner, registeredAt, primary, records } or null. |
| namesOfCount(address) | How many names an address owns, as a string. |
| capOf(address) | { used, limit, windowEndsAt }: registrations used in the current cap window. |
| isReserved(name) | true while a name is reserved. |
| stats() | { total, burned, toTreasury }: names registered, wei burned, wei sent to the treasury. |
| params() | Fees, cap, registrar, treasury, burn address, record keys and limits. |
Events
Indexers can follow the registry from its events. In eth_getTransactionReceipt logs, topics[0] is the BLAKE3 hash of the event name and data is the JSON payload.
| Event | Data | When |
|---|---|---|
| Registered | { name, owner, fee, via } | via is "fee" for a paid registration or "registrar" for a released reserved name. |
| PrimarySet | { address, name } | On setPrimary, and automatically for a first name. |
| PrimaryCleared | { address, name } | On clearPrimary, or when a transfer moves the primary name away. |
| Transferred | { name, from, to } | A name changed owner. |
| RecordSet | { name, key, value } | A record was set or cleared (empty value). |
| ReservedReleased | { name, to } | The registrar released a reserved name. |
| ReservedAdded / ReservedRemoved | { name } | The reserved list changed. |
Using names in your app
- Resolve at action time. Look the name up when the user sends, not when the page loads, and show the resolved ase1 address on the confirm step.
- Show
@namewith the address available. Put the primary name next to the address, never instead of a way to see the address. - Stay quiet until active. Hide name features until a lookup returns
active: true, and treat a 404 from an older node the same way. - Treat names and records as text. Render them as plain text, never as HTML.
- Cache lightly. Primary names can change at any block. A short cache (a minute or so) for display is fine; never cache for sending.
Read next