Guide

Working with Addresses

Turn a public key into an address, pick a format, and check one you were given.

From public key to address

import { useBlockchain } from "@agntn/keys";
import Bitcoin from "@agntn/keys/blockchains/bitcoin";

const chain = useBlockchain(new Bitcoin());
const keys = chain.generateKeys();
const address = chain.getAddress(keys.keys.public);

console.log(address); // '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'

Every chain does the same thing here: hash the public key, then encode the hash the way the chain likes. What differs is the hash, the encoding, and whether there is a checksum.

Address types

The second argument picks a format on chains that have more than one.

chain.getAddress(publicKey); // legacy, starts with 1
chain.getAddress(publicKey, "segwit"); // bc1q...
chain.getAddress(publicKey, "taproot"); // bc1p...

Bitcoin has five formats, Cardano three, Sui uses the argument to name the curve. Ethereum, Base, TRON, Solana, and Aptos have one format and ignore the argument.

Validating

chain.validateAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); // true
chain.validateAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNb"); // false, checksum

Validation checks the format and the checksum when the format has one. It does not touch the network. A valid address with no history is still valid.

validateAddress is optional on the BlockchainImplementation type so that structural implementations can skip it, but every class built on AbstractBlockchain has one. Every built in chain does.

How each chain builds its address

ChainRecipe
BitcoinRIPEMD160(SHA256(pubkey)), then base58check for legacy and p2sh, bech32 for segwit, bech32m with a BIP341 tweak for taproot
Ethereum, Baselast 20 bytes of Keccak-256(uncompressed pubkey without 04), 0x prefix, EIP-55 mixed case checksum
TRONsame 20 bytes as Ethereum, version byte 0x41, base58check, so it starts with T
Solanathe ed25519 public key itself, base58
AptosSHA3-256(pubkey + 0x00), 0x prefix
SuiBlake2b-256(flag + pubkey), flag 0x00 for ed25519 and 0x01 for secp256k1, 0x prefix
CardanoBlake2b-224(pubkey) behind a header byte, bech32 with addr or stake

The chain pages have the code for each, plus the testnet variants where they exist.

A quick tour

import Ethereum from "@agntn/keys/blockchains/ethereum";
import Solana from "@agntn/keys/blockchains/solana";
import Sui from "@agntn/keys/blockchains/sui";

const ethereumChain = useBlockchain(new Ethereum());
ethereumChain.getAddress(ethereumChain.getKeyPublic(privateKey)); // '0x...' with checksum

const solanaChain = useBlockchain(new Solana());
solanaChain.getAddress(solanaChain.getKeyPublic(privateKey)); // base58, no hashing at all

const suiChain = useBlockchain(new Sui());
const secpKey = suiChain.getKeyPublic(privateKey, { scheme: "secp256k1" });
suiChain.getAddress(secpKey, "secp256k1"); // '0x...' from the secp256k1 flag

The Sui line is the one people get wrong: a secp256k1 public key hashed with the ed25519 flag produces a perfectly valid looking address that no key can spend from. Keep the scheme on both calls.

@agntn/keys·MIT license· Keys never leave the browser.