AccountMapping Precompile
Address: 0x0000000000000000000000000000000000000800
The AccountMapping precompile exposes pallet-account-mapping to EVM clients. A single precompile covers five distinct feature areas: EVM↔Substrate account linking, on-chain aliases, chain links, Private Link, and account metadata.
Overview
| Property | Value |
|---|---|
| Address | 0x0000000000000000000000000000000000000800 |
| Index | 2048 (0x800) |
| Source | frame/evm/precompile/account-mapping/ |
| Crate | pallet-evm-precompile-account-mapping |
| Status | Implemented (MVP) |
Caller Identity
The precompile derives the caller's AccountId32 from the EVM H160 sender via AddressMapping:
AccountId32 = H160 ++ [0x00; 12]
All state-changing calls are signed implicitly by the EVM transaction. The derived AccountId32 is the identity on the Substrate side.
Function Reference
Account Mapping
mapAccount
Emits an AccountMapped event signaling the association between the caller's EVM H160 and their AccountId32. For secp256k1 accounts, this is a pure event — no storage is written, since the mapping is already implicit from the AccountId32 suffix pattern ([H160 | 0x00×12]). Calling it is optional and intended for indexers.
function mapAccount() external;
No arguments required. Reverts with NativeAccountCannotBeMapped if the caller's AccountId32 does not follow the EVM suffix pattern (i.e. bytes 20–31 are not all zero).
Selector: 0xdca49d0e
unmapAccount
Removes the H160 → AccountId32 mapping for the caller.
function unmapAccount() external;
Selector: 0x08f57367
Account Composability Helpers
These read-only functions were added as part of the Account Mapping Alignment Plan (Fase 3) to enable composable EVM interactions involving AccountId32 resolution. All three are pure view functions — no state is mutated and no transaction is required.
isEvmSuffixAccount (read-only)
Checks whether an AccountId32 follows the secp256k1 suffix pattern ([H160 | 0x00×12]). No storage access — O(1) byte inspection.
function isEvmSuffixAccount(bytes32 accountId) external view returns (bool);
| Parameter | Type | Description |
|---|---|---|
accountId | bytes32 | Raw AccountId32 to inspect |
Returns true if bytes 20–31 of accountId are all zero (secp256k1 implicit account). Returns false for Sr25519/Ed25519 accounts.
Selector: 0x96e69f8a
toEvmAddress (read-only)
Derives the H160 address from an AccountId32. For secp256k1 accounts (suffix pattern), the derivation is implicit and requires no storage lookup. Returns address(0) for Sr25519/Ed25519 accounts.
function toEvmAddress(bytes32 accountId) external view returns (address);
| Parameter | Type | Description |
|---|---|---|
accountId | bytes32 | Raw AccountId32 |
Selector: 0x0f5b3052
resolveAliasFull (read-only)
Extended alias resolution that returns the full identity tuple required to interact with BalancesPrecompile. The returned accountId32 is directly usable as the recipient argument in BalancesPrecompile.transfer(bytes32, uint256).
function resolveAliasFull(string calldata alias)
external view
returns (bytes32 accountId32, address evmAddress, bool isEvmSuffix);
| Parameter | Type | Description |
|---|---|---|
alias | string | Alias to resolve |
Returns (bytes32(0), address(0), false) if the alias is not registered.
For secp256k1 accounts, evmAddress is derived implicitly from accountId32 bytes 0–19 (no storage lookup); isEvmSuffix is true. For accounts where isEvmSuffix is false, evmAddress may be address(0) if no explicit mapping exists.
Selector: 0x2e40772b
Aliases
registerAlias
Reserves a human-readable alias for the caller's account.
function registerAlias(string calldata alias) external;
| Parameter | Type | Description |
|---|---|---|
alias | string | UTF-8 alias (max 64 bytes) |
Selector: 0x2f8839c3
releaseAlias
Releases the alias owned by the caller.
function releaseAlias() external;
Selector: 0x7fac359e
transferAlias
Transfers ownership of the caller's alias to another address.
function transferAlias(address newOwner) external;
| Parameter | Type | Description |
|---|---|---|
newOwner | address | New owner's EVM address |
Selector: 0x5ac998e7
putAliasOnSale
Lists the caller's alias for sale at the given price.
function putAliasOnSale(uint256 price, address[] calldata allowedBuyers) external;
| Parameter | Type | Description |
|---|---|---|
price | uint256 | Sale price in the native token (Planck) |
allowedBuyers | address[] | Whitelist of allowed buyers; empty array = public listing |
Selector: 0x32091192
cancelSale
Cancels an active alias sale listing.
function cancelSale() external;
Selector: 0x4d023ab9
buyAlias
Purchases an alias that is listed for sale.
function buyAlias(string calldata alias) external;
| Parameter | Type | Description |
|---|---|---|
alias | string | Alias to purchase |
Selector: 0x1625df3a
resolveAlias (read-only)
Returns the AccountId32 (as bytes32) associated with an alias.
function resolveAlias(string calldata alias) external view returns (bytes32);
| Parameter | Type | Description |
|---|---|---|
alias | string | Alias to look up |
Selector: 0xd03149ab
getAliasOf (read-only)
Returns the alias registered for a given EVM address as a UTF-8 string.
function getAliasOf(address account) external view returns (string memory);
| Parameter | Type | Description |
|---|---|---|
account | address | EVM address to look up |
Selector: 0x7a0ed62c
Chain Links
Chain links associate an Orbinum account with an address on another chain (e.g. Ethereum mainnet, Solana). The link is verified by a signature produced by the external key.
addChainLink
Adds a verified link to an external chain address.
function addChainLink(
uint32 chainId,
bytes calldata externalAddr,
bytes calldata signature
) external;
| Parameter | Type | Description |
|---|---|---|
chainId | uint32 | SLIP-0044 chain identifier |
externalAddr | bytes | Raw address bytes on the external chain |
signature | bytes | Signature produced by the external key over the Orbinum AccountId32 |
The signature scheme (EIP-191 for EVM chains, Ed25519 for others) is resolved from SupportedChains storage by chainId — it is not passed directly.
Selector: 0x5f3e837c
removeChainLink
Removes the chain link for the specified chain.
function removeChainLink(uint32 chainId) external;
| Parameter | Type | Description |
|---|---|---|
chainId | uint32 | SLIP-0044 chain identifier of the link to remove |
Selector: 0x6f579c0c
dispatchAsLinkedAccount
Dispatches a SCALE-encoded RuntimeCall as the owner account, authorized by the external wallet registered as a chain link. Any EVM address can act as relayer — the relayer pays gas but the call executes with origin = Signed(owner).
function dispatchAsLinkedAccount(
bytes32 owner,
uint32 chainId,
bytes calldata externalAddr,
bytes calldata signature,
bytes calldata call
) external;
| Parameter | Type | Description |
|---|---|---|
owner | bytes32 | Raw AccountId32 of the Orbinum account that owns the chain link |
chainId | uint32 | Chain ID of the registered link |
externalAddr | bytes | Raw address bytes of the external wallet (e.g. 20 bytes for EVM) |
signature | bytes | External wallet signature over keccak256(SCALE(call)) (EIP-191, 65 bytes for EVM) |
call | bytes | SCALE-encoded RuntimeCall to dispatch as owner |
Selector: 0x0630cef9
Execution flow:
- Decodes
ownerasAccountId32andcallasRuntimeCall(SCALE). - Looks up
(chainId, externalAddr)inReverseChainLinksand verifies it belongs toowner. - Verifies the external wallet's signature over
encode(call)using the scheme configured forchainId. - Dispatches
callwithorigin = Signed(owner).
On success, emits ProxyCallExecuted { owner, chain_id, address }.
Signature scheme for EIP-191 chains:
The pallet hashes the payload as follows before verifying the ECDSA signature:
msg_hash = keccak256(SCALE(call))
final_hash = keccak256("\x19Ethereum Signed Message:\n32" || msg_hash)
In ethers.js, this is equivalent to:
const scaleCallBytes = api.tx.system.remark(new Uint8Array(0)).method.toU8a();
const sig = await wallet.signMessage(ethers.getBytes(ethers.keccak256(scaleCallBytes)));
Calls using the Orbinum native EVM chain ID (2700) are rejected with UseNativeSignatureForEvmAccounts. Native secp256k1 (MetaMask) accounts sign Substrate extrinsics directly with OrbinumSignature::Ecdsa — no proxy dispatch needed.
The EVM transaction sender (msg.sender) pays the EVM gas cost. The owner account does not need to hold ORB or interact with the chain directly. This enables Ethereum or Solana wallets to control an Orbinum identity via any willing relayer.
Private Link
Private Link allows an account to prove ownership of an address on another chain without revealing that address publicly. The flow has two phases: register (commit) and reveal (open).
The commitment is computed off-chain as:
commitment = Poseidon(chain_id, owner_account_id, external_address, blinding)
This uses the same Poseidon implementation as the ZK circuits.
registerPrivateLink
Publishes a commitment to a private cross-chain link without revealing the external address.
function registerPrivateLink(
uint32 chainId,
bytes32 commitment
) external;
| Parameter | Type | Description |
|---|---|---|
chainId | uint32 | SLIP-0044 chain identifier |
commitment | bytes32 | Poseidon(chain_id, owner_account_id, external_address, blinding) |
Selector: 0xc04e98f4
removePrivateLink
Removes a previously registered commitment.
function removePrivateLink(bytes32 commitment) external;
| Parameter | Type | Description |
|---|---|---|
commitment | bytes32 | Commitment to remove |
Selector: 0xdfd8b57e
revealPrivateLink
Reveals the external address behind a commitment by providing the opening.
function revealPrivateLink(
bytes32 commitment,
bytes calldata externalAddr,
bytes32 blinding,
bytes calldata signature
) external;
| Parameter | Type | Description |
|---|---|---|
commitment | bytes32 | Previously registered commitment |
externalAddr | bytes | Plaintext external address |
blinding | bytes32 | Blinding factor used to compute the commitment |
signature | bytes | Signature from the external key over the Orbinum AccountId32 |
The runtime recomputes Poseidon(chain_id, owner_account_id, externalAddr, blinding) and verifies it matches the registered commitment. If it matches and the signature is valid, the link is promoted to a public chain link.
Selector: 0x4df1f33d
hasPrivateLink (read-only)
Checks whether a given alias has a private commitment registered for a specific commitment hash.
function hasPrivateLink(
string calldata alias,
bytes32 commitment
) external view returns (bool);
| Parameter | Type | Description |
|---|---|---|
alias | string | Alias of the account to query |
commitment | bytes32 | Commitment to look up |
Selector: 0x47e05c6c
Account Metadata
setAccountMetadata
Sets up to three metadata fields for the caller's account. Pass empty bytes to leave a field unchanged.
function setAccountMetadata(
bytes calldata displayName,
bytes calldata bio,
bytes calldata avatar
) external;
| Parameter | Type | Max size | Description |
|---|---|---|---|
displayName | bytes | 128 bytes | UTF-8 display name |
bio | bytes | 128 bytes | UTF-8 short biography |
avatar | bytes | 128 bytes | URL or IPFS CID |
Selector: 0x776cf9ff
Selector Reference
| Selector | Function | Type |
|---|---|---|
0xdca49d0e | mapAccount() | write |
0x08f57367 | unmapAccount() | write |
0x96e69f8a | isEvmSuffixAccount(bytes32) | read |
0x0f5b3052 | toEvmAddress(bytes32) | read |
0x2e40772b | resolveAliasFull(string) | read |
0x2f8839c3 | registerAlias(string) | write |
0x7fac359e | releaseAlias() | write |
0x5ac998e7 | transferAlias(address) | write |
0x32091192 | putAliasOnSale(uint256,address[]) | write |
0x4d023ab9 | cancelSale() | write |
0x1625df3a | buyAlias(string) | write |
0xd03149ab | resolveAlias(string) | read |
0x7a0ed62c | getAliasOf(address) | read |
0x5f3e837c | addChainLink(uint32,bytes,bytes) | write |
0x6f579c0c | removeChainLink(uint32) | write |
0x0630cef9 | dispatchAsLinkedAccount(bytes32,uint32,bytes,bytes,bytes) | write |
0xc04e98f4 | registerPrivateLink(uint32,bytes32) | write |
0xdfd8b57e | removePrivateLink(bytes32) | write |
0x4df1f33d | revealPrivateLink(bytes32,bytes,bytes32,bytes) | write |
0x47e05c6c | hasPrivateLink(string,bytes32) | read |
0x776cf9ff | setAccountMetadata(bytes,bytes,bytes) | write |
Usage Example
import { ethers } from "ethers";
import { buildPoseidon } from "circomlibjs";
const ADDRESS = "0x0000000000000000000000000000000000000800";
const abi = [
"function registerPrivateLink(uint32 chainId, bytes32 commitment) external",
"function revealPrivateLink(bytes32 commitment, bytes externalAddr, bytes32 blinding, bytes signature) external",
"function hasPrivateLink(string alias, bytes32 commitment) external view returns (bool)",
"function registerAlias(string alias) external",
];
const precompile = new ethers.Contract(ADDRESS, abi, signer);
// Step 1 — register alias
await precompile.registerAlias("alice");
// Step 2 — register a private link to Ethereum mainnet (SLIP-0044: 60)
const poseidon = await buildPoseidon();
const chainId = 60;
const externalAddr = ethers.getBytes("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
const blinding = ethers.randomBytes(32);
const ownerAccountId = ethers.getBytes(substrateAccountId32); // 32 bytes
const raw = poseidon([chainId, ownerAccountId, externalAddr, blinding]);
const commitment = ethers.hexlify(poseidon.F.toObject(raw));
await precompile.registerPrivateLink(chainId, commitment);
// Step 3 — reveal later by providing the opening + signature from the external key
await precompile.revealPrivateLink(commitment, externalAddr, blinding, externalSignature);
Composability: resolving aliases for BalancesPrecompile
resolveAliasFull combined with BalancesPrecompile allows any EVM contract to send ORB to an Orbinum alias without knowing whether the recipient is a secp256k1 or Sr25519 account:
interface IAccountMapping {
function resolveAliasFull(string calldata alias)
external view
returns (bytes32 accountId32, address evmAddress, bool isEvmSuffix);
}
interface IBalances {
function transfer(bytes32 accountId32, uint256 amount) external;
}
address constant ACCOUNT_MAPPING = 0x0000000000000000000000000000000000000800;
address constant BALANCES = 0x0000000000000000000000000000000000000802;
function sendToAlias(string calldata alias, uint256 amount) external {
(bytes32 accountId32, , ) =
IAccountMapping(ACCOUNT_MAPPING).resolveAliasFull(alias);
require(accountId32 != bytes32(0), "alias not found");
IBalances(BALANCES).transfer(accountId32, amount);
}
This pattern works for both secp256k1 and Sr25519 recipients. accountId32 is passed directly to transfer without additional conversion.
Notes
- Read-only functions (
resolveAlias,getAliasOf,hasPrivateLink,isEvmSuffixAccount,toEvmAddress,resolveAliasFull) do not require a signed transaction and can be called witheth_call. - All write functions consume gas proportional to the underlying Substrate extrinsic weight via
GasWeightMapping. - The Private Link commitment uses Poseidon (BN254 scalar field) — not
keccak256. Usecircomlibjsor@orbinum/proof-generatorto compute it off-chain.