Working with Addresses
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
| Chain | Recipe |
|---|---|
| Bitcoin | RIPEMD160(SHA256(pubkey)), then base58check for legacy and p2sh, bech32 for segwit, bech32m with a BIP341 tweak for taproot |
| Ethereum, Base | last 20 bytes of Keccak-256(uncompressed pubkey without 04), 0x prefix, EIP-55 mixed case checksum |
| TRON | same 20 bytes as Ethereum, version byte 0x41, base58check, so it starts with T |
| Solana | the ed25519 public key itself, base58 |
| Aptos | SHA3-256(pubkey + 0x00), 0x prefix |
| Sui | Blake2b-256(flag + pubkey), flag 0x00 for ed25519 and 0x01 for secp256k1, 0x prefix |
| Cardano | Blake2b-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.