Encoding and Decoding
By name
import { decode, encode } from "@agntn/encodings";
encode("hex", "gm"); // "676d"
encode("hex", "gm", { upper: true });
decode("hex", "0x67 6D").bytes; // Uint8Array [103, 109]
The name is forgiving. Base 64, base_64 and b64 all find base64. qp finds quoted-printable, btoa finds ascii85. A name nobody knows throws UnknownEncodingError. The message lists every name, so you don't guess twice.
Options are checked against what the encoding declares. Pass upper to base64 and you get InvalidOptionError, not a silent nothing. Only six encodings take options at all.
| Encoding | Options |
|---|---|
hex | upper |
binary | separate |
ascii85 | delimiters |
bech32, bech32m | prefix, limit |
uuencode | name, mode |
What comes back
decode returns { bytes, details }. The bytes are a Uint8Array, always. Want text? Run them through TextDecoder yourself. The library won't guess that your bytes are UTF-8.
And details? That's the part of the text that isn't data.
decode("bech32", "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4").details;
// { prefix: "bc", witnessVersion: 0 }
decode("uuencode", "begin 644 cat.txt\n#0V%T\n`\nend\n").details;
// { mode: "644", name: "cat.txt" }
Codec objects
Every encoding is also a plain object with encode and decode. No registry involved.
import { base58, base58check, hex } from "@agntn/encodings";
base58.encode(new TextEncoder().encode("hello world")); // "StV1DL6CwTryKyV"
base58check.decode("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
// 21 bytes: version 0x00, then the HASH160
These work on bytes. hex.decode returns a Uint8Array, base58.encode wants one. The registry just wraps them.
Subpaths
Each family has its own entry. Pulling base58 into a bundle doesn't drag the registry along. Or identify. Or sixteen other alphabets.
import { base58, base58check } from "@agntn/encodings/base58";
import { bech32, segwit } from "@agntn/encodings/bech32";
Eleven families, one subpath each. A test bundles every one of them and fails if another family's code sneaks in.
Decoding is strict, mostly
Where a spec says something is wrong, decoding says so. It names the offending character and its index.
base58: "0" (U+0030) at index 0 is not in the alphabet
base64: 1 padding characters where the length needs 2
Real text often comes wrapped, though. So decoding reads through the wrapping. Base64 and base32 skip line breaks and spaces, and a MIME body decodes as it is. Hex takes either case, spaces and a 0x. Crockford reads O as 0 and I or L as 1. That's the whole point of Crockford. Want the rules per encoding? Every encoding page lists them.
An invisible character never reaches the message raw. A U+2028 shows up as U+2028. Not as a line break that rewrites the error under your feet.