Accounts
Account model, identifiers, keys, addresses, and human-readable handles in NOXY.
Accounts
An account is a 32-byte identifier that owns balances, keys, and nonces. Identifiers are hash-derived and stable across key rotation: the same account keeps its AccountId when its signing key changes. Transactions, balances, validator records, evidence, and account names all reference accounts by their raw AccountId. Human-facing wallets and explorers render the same identifier in three additional forms — bech32m address, account-name handle, and verifier words — but the canonical identity is always the raw 32-byte hash.
Identifier
AccountId is a BLAKE3 hash with explicit domain separation:
AccountId = BLAKE3("NOXY-L0/v0.1/account-id/v1\0" || preimage)
preimage = BLAKE3(public_key) || account_type_u16_le || salt
public_key is the bytes of the account's initial ML-DSA-44 public key. account_type is the canonical discriminant (see below). salt is operator-supplied bytes, 0..=64 bytes long, included so independent registrations of the same public key can produce distinct accounts when needed.
The derivation is implemented in the TypeScript SDK (@noxy/core) and reproduced by the wallet.
Account types
The account type is a u16 discriminant. It determines which payloads a key on the account is allowed to authorize and which RPC views the account appears in.
| Discriminant | Name | Use |
|---:|---|---|
| 1 | User | Regular user accounts. Wallets default to this type. |
| 2 | ValidatorOperator | Owns a validator record. Holds operator-only permissions for lifecycle actions. |
| 3 | ChainCreator | Registers service chains. |
| 4 | ServiceDefinitionOwner | Owns a service definition record. |
| 5 | Governance | Reserved for governance-related operations. |
Encoders and decoders reject unknown account-type values.
Account record
The on-chain account record is small. State proofs target this record directly.
Account {
account_id: AccountId // 32 bytes
account_type: AccountType // u16
status: AccountStatus // Active | Inactive
sequence_nonce: u64 // strictly monotonic per envelope
primary_key_id: KeyId // 32 bytes
}
sequence_nonce advances by exactly one per accepted transaction envelope, regardless of how many actions a Batch envelope contains. The primary key is the key that authorized account creation; additional keys are registered through RegisterKey and live in the key registry rather than on the account record.
Keys
A key is identified by a 32-byte KeyId:
KeyId = BLAKE3("NOXY-L0/v0.1/key-id/v1\0" || account_id || BLAKE3(public_key) || key_role_u16_le)
Each key registry entry binds account_id, algorithm_id, the raw public key, the key role, validity window, and lifecycle counters. The full field order is in specs/transaction-envelope.md under RegisterKey Payload.
Key roles separate transaction authorization from validator-specific responsibilities:
| Role | Purpose |
|---|---|
| AccountAuth | Signs ordinary user transactions (Transfer, RegisterKey, account-name operations, etc.). |
| ValidatorConsensusPq | ML-DSA-44 key used by a validator to sign consensus votes. |
| ValidatorConsensusClassical | Ed25519 key paired with the PQ consensus key for the hybrid vote scheme. |
| CheckpointSigner | Authorizes SubmitCheckpoint payloads. |
Key rotation does not change the AccountId. Adding a new AccountAuth key via RegisterKey and revoking the old one preserves the account's identity, its balances, and any account names that target it.
Addresses
The raw AccountId is the canonical wire form. Human-facing surfaces render the same 32 bytes in two additional forms.
Hex
Lowercase 64-character hex is the lossless representation. RPC paths and JSON payloads use hex. URL parameters reject uppercase hex with a typed error.
account_id_hex = "f63052cfff6e7141bf90b7f9170a589f0674771a2d44173549fe60ba6aebf251"
Bech32m
For wallet UI, CLI, and external display, the account is rendered as a checksummed bech32m string with a network-specific human-readable prefix:
tnxy17cc99nlldec5r0usklu3wzjcnur8gac694zpwd2flest56ht7fgscpxscj
The HRP is noxy on mainnet and tnxy on a testnet. The bech32m checksum (BIP-350) catches single-character typos and rejects any address that mixes upper- and lower-case letters. The same 32 bytes encoded under a different HRP fails to decode — a tnxy1… address presented to a mainnet wallet is rejected before any state lookup.
The conversion is mechanical and does not touch the chain: encoding and decoding live in noxy-types::address and the matching TypeScript helper.
Verifier words
To make address verification practical without reading 64 hex characters, every account also has a deterministic three-word verifier and a two-digit check number, derived from the AccountId:
Verify: general · leisure · vote · 97
The verifier digest is BLAKE3("NOXY-L0/v0.1/account-verifier/v1\0" || account_id). The three words are indexed into the BIP-39 English wordlist; the two-digit number is u16_le(digest[6..8]) mod 100. Wallets display the verifier alongside the address so users can confirm the recipient by reading four short tokens aloud, instead of comparing two long hex strings.
The verifier is informational. Transactions sign and bind the raw AccountId, not the verifier display.
Account names
An account name is a human-readable alias that resolves to an AccountId. The display form combines a handle with the chain identifier:
alice@noxy-localnet-0
The same identity is also accessible as a quanta:// URI:
quanta://alice.noxy-localnet-0/
Name records are stored on-chain in StateNamespace::AccountNameRecord and govern ownership, target, lease, and version separately from the underlying account. Each chain hosts its own namespace: a name registered on one chain does not resolve on another. Wallets refuse to resolve a handle against a chain other than the active profile.
The name record carries:
AccountNameRecord {
labels: [String] // 1..=protocol_params.account_name_max_labels
owner_account_id: AccountId // pays renewals, can update/transfer/release
target_account_id: AccountId // resolution target
target_account_type: AccountType
version: u32 // compare-and-set protection
registered_at_epoch: Epoch
expires_at_epoch: Epoch
grace_until_epoch: Epoch
status: Active | Grace | Released
}
Owner and target may differ. A treasury or DAO account can own a name that points at an operational account, and the operational account can rotate without losing the name.
Registration, renewal, target update, transfer, and release are five separate transactions. Fees scale with label length and are credited to the chain treasury.
Resolution works from state: the SDK normalizes the labels, derives the name hash, and reads the record from StateNamespace::AccountNameRecord. The wallet resolves handles the same way and refuses to resolve against a chain other than the active profile. A dedicated public HTTP resolve endpoint is planned but not exposed yet.
RPC
The read endpoints related to accounts return canonical state:
| Endpoint | Returns |
|---|---|
| GET /v0/account/{id_hex} | The account record. |
| GET /v0/account/{id_hex}/nonce | The current sequence_nonce. |
| GET /v0/account/{id_hex}/balance/{asset_id_hex} | The asset balance, as a decimal string. |
| GET /v0/account/{id_hex}/activity | Recent activity for the account. |
References
- Protocol architecture — transaction envelope, checkpoints, consensus.
specs/identifiers.md— identifier grammar and domain strings.specs/transaction-envelope.md—CreateAccount,RegisterKey, and other account-related payloads.specs/wallet.md— BIP-39 mnemonic and ML-DSA-44 derivation path.specs/account-names.md— name registry semantics, lease curve, reservations.