NEAR
NEAR
ed25519
- implicit account (hex)
- Formats
- 1
- Coin type
- 397'
- Key 1
- 4cb5abf6ad…a5ba29
Access
- Load
blockchains.near()() - Import
@agntn/keys/blockchains/near - Walkkeyspace in the browser
Load it
import { useBlockchain, blockchains } from "@agntn/keys";
const nearChain = useBlockchain(await blockchains.near()());
Mainnet and testnet write the same accounts. The network option changes nothing here.
What's an implicit account?
The public key. In hex. That's the whole recipe.
const privateKey = "0000000000000000000000000000000000000000000000000000000000000001";
const publicKey = nearChain.getKeyPublic(privateKey);
nearChain.getAddress(publicKey);
// 4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29
No hash, no checksum, not even base58. Even Solana bothers with base58. NEAR looked at 32 bytes and said, good enough.
The hex is lowercase. NEAR refuses uppercase in account IDs, so validateAddress does too.
And the ed25519: thing?
That's how NEAR prints a key: ed25519: and the 32 bytes in base58. Wallets, near-cli and the RPC all show it this way. getAddress and verifyMessage take it as is.
nearChain.getAddress("ed25519:6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt");
// 4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29
Look at that base58 again. Seen it before? It's the Solana address of the same private key. Solana's address is base58 of the key, NEAR's key is base58 of the key. One chain calls it an address, the other a key with a label on it.
Agents get the same deal. keys_address_get and keys_message_verify take the ed25519: form on near, and every other chain asks for hex instead.
What about alice.near?
Named accounts are made by a transaction on chain. No key derives one, so nothing here writes them.
nearChain.validateAddress(nearChain.getAddress(publicKey)); // true
nearChain.validateAddress("alice.near"); // false
false doesn't mean alice.near is broken. It means no key leads there. The 0x ETH-implicit accounts get false for the same reason, they come from secp256k1 keys.
One more trap. The account ID names the key that created it, not the key that holds it today. Owners add keys and delete the first one, and the account keeps its name. It happens on mainnet, not just in theory. So a key that matches the ID tells you who opened the account. Who can sign now is on chain, in view_access_key_list.
Mnemonics
SLIP-10 on ed25519, every level hardened. near-seed-phrase stops at m/44'/397'/0', and that's what getDerivationPath writes. Account a is m/44'/397'/a', and there's no change branch or index below it.
const mnemonic =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
nearChain.getDerivationPath(); // "m/44'/397'/0'"
nearChain.deriveHDWallet(mnemonic, "m/44'/397'/0'").address;
// 5510e2b44cae6eb807e3e0e45d579dda058c274abcba15e5cb84636f5d1ee412
Ledger went its own way, of course. Its NEAR app defaults to m/44'/397'/0'/0'/1', five levels deep. Pass that path to deriveHDWallet as it is.
nearChain.deriveHDWallet(mnemonic, "m/44'/397'/0'/0'/1'").address;
// c571e33e2e36c2c728d617ea77a88e2320c8697eac8b463adfc0128b96825cbf
Not sure which wallet made the phrase? keys_hd_wallet_scan walks both, bip44 over the account level and ledger over the last one.
Signing
signMessage signs the raw message bytes with ed25519, like Solana and Aptos.
const signature = nearChain.signMessage("Hello, NEAR!", privateKey);
nearChain.verifyMessage("Hello, NEAR!", signature, publicKey); // true
Wallets that sign under NEP-413 won't match this. They wrap a nonce and a recipient around the message first and sign the hash of that. Different bytes, different signature.
Where it lives
src/blockchains/near.ts, and it's short. The curve comes from @noble/curves, base58 from @agntn/encodings.