diff --git a/packages/wasm-utxo/docs/adding-new-coin.md b/packages/wasm-utxo/docs/adding-new-coin.md
new file mode 100644
index 0000000..4c67166
--- /dev/null
+++ b/packages/wasm-utxo/docs/adding-new-coin.md
@@ -0,0 +1,337 @@
+# Adding a New Coin to wasm-utxo
+
+This guide covers adding support for a new UTXO coin to the wasm-utxo library.
+wasm-utxo handles low-level PSBT construction, transaction signing, and address
+encoding/decoding, compiled from Rust to WASM. It uses **foocoin**
+(`foo`/`tfoo`) as a worked example.
+
+## Overview of changes
+
+```mermaid
+graph TD
+ N[src/networks.rs
Network enum] --> A[src/address/mod.rs
Codec constants]
+ N --> C[js/coinName.ts
CoinName type + helpers]
+ A --> AN[src/address/networks.rs
Codec wiring + script support]
+ N --> P[src/fixed_script_wallet/bitgo_psbt/mod.rs
PSBT deserialization + sighash]
+ AN --> T[test/fixtures/
Address + PSBT fixtures]
+ C --> T
+ P --> T
+```
+
+## 1. Network enum
+
+**File:** `src/networks.rs`
+
+Add two variants to the `Network` enum (mainnet + testnet) and update every
+match arm. The Rust compiler will enforce exhaustive matching, so any missed arm
+will be a compile error.
+
+### Enum definition
+
+```rust
+pub enum Network {
+ // ...existing variants...
+ Foocoin,
+ FoocoinTestnet,
+}
+```
+
+### Match arms to update
+
+There are 5 match-based functions/arrays that need a new arm. Use the existing
+Dogecoin entries as a template for a simple coin.
+
+| Location | What to add |
+| ------------------- | ------------------------------------------------------------------------------------- |
+| `ALL` array | `Network::Foocoin, Network::FoocoinTestnet` |
+| `as_str()` | `"Foocoin"`, `"FoocoinTestnet"` |
+| `from_name_exact()` | `"Foocoin" => Some(Network::Foocoin)`, etc. |
+| `from_coin_name()` | `"foo" => Some(Network::Foocoin)`, `"tfoo" => ...` |
+| `to_coin_name()` | `Network::Foocoin => "foo"`, etc. |
+| `mainnet()` | `Network::Foocoin => Network::Foocoin`, `Network::FoocoinTestnet => Network::Foocoin` |
+
+> **Skip `from_utxolib_name()` / `to_utxolib_name()`** — these exist for
+> backwards compatibility with existing coins routed through the deprecated
+> utxo-lib. New coins must not be added to these functions.
+
+Also update the test `test_all_networks` assertion count.
+
+## 2. TypeScript coin name
+
+**File:** `js/coinName.ts`
+
+Register the new coin's short names so that the TypeScript layer can reference
+them. The `CoinName` type is derived automatically from the `coinNames` tuple.
+
+1. Add `"foo"` and `"tfoo"` to the `coinNames` array.
+2. Add a `case "tfoo": return "foo"` arm to `getMainnet()`.
+
+No changes are needed to `isMainnet()` / `isTestnet()` — they delegate to
+`getMainnet()`.
+
+## 3. Address codec constants
+
+**File:** `src/address/mod.rs`
+
+Define the Base58Check version bytes for the coin. Find these in the coin's
+`chainparams.cpp` under `base58Prefixes[PUBKEY_ADDRESS]` and
+`base58Prefixes[SCRIPT_ADDRESS]`.
+
+```rust
+// Foocoin
+// https://github.com/example/foocoin/blob/master/src/chainparams.cpp
+pub const FOOCOIN: Base58CheckCodec = Base58CheckCodec::new(0x3f, 0x41);
+pub const FOOCOIN_TEST: Base58CheckCodec = Base58CheckCodec::new(0x6f, 0xc4);
+```
+
+If the coin supports SegWit (bech32 addresses), also add:
+
+```rust
+pub const FOOCOIN_BECH32: Bech32Codec = Bech32Codec::new("foo");
+pub const FOOCOIN_TEST_BECH32: Bech32Codec = Bech32Codec::new("tfoo");
+```
+
+If the coin uses CashAddr (like Bitcoin Cash), use `CashAddrCodec` instead.
+
+### Where to find version bytes
+
+| Coin | Source |
+| -------- | ---------------------------------------------- |
+| Bitcoin | `base58Prefixes[PUBKEY_ADDRESS] = {0}` → 0x00 |
+| Dogecoin | `base58Prefixes[PUBKEY_ADDRESS] = {30}` → 0x1e |
+| Zcash | Uses 2-byte versions: `{0x1C,0xB8}` → 0x1cb8 |
+
+## 4. Address codec wiring
+
+**File:** `src/address/networks.rs`
+
+Update three functions and one method.
+
+### get_decode_codecs()
+
+Returns the codecs to try when decoding an address string.
+
+```rust
+fn get_decode_codecs(network: Network) -> Vec<&'static dyn AddressCodec> {
+ match network {
+ // ...existing cases...
+ Network::Foocoin => vec![&FOOCOIN, &FOOCOIN_BECH32],
+ Network::FoocoinTestnet => vec![&FOOCOIN_TEST, &FOOCOIN_TEST_BECH32],
+ }
+}
+```
+
+If the coin does not support SegWit, omit the bech32 codec:
+
+```rust
+Network::Foocoin => vec![&FOOCOIN],
+```
+
+### get_encode_codec()
+
+Returns the single codec to use when encoding an output script to an address.
+
+```rust
+fn get_encode_codec(network: Network, script: &Script, format: AddressFormat)
+ -> Result<&'static dyn AddressCodec>
+{
+ match network {
+ // ...existing cases...
+ Network::Foocoin => {
+ if is_witness { Ok(&FOOCOIN_BECH32) } else { Ok(&FOOCOIN) }
+ }
+ Network::FoocoinTestnet => {
+ if is_witness { Ok(&FOOCOIN_TEST_BECH32) } else { Ok(&FOOCOIN_TEST) }
+ }
+ }
+}
+```
+
+### output_script_support()
+
+Declares which script types the coin supports.
+
+```rust
+impl Network {
+ pub fn output_script_support(&self) -> OutputScriptSupport {
+ let segwit = matches!(
+ self.mainnet(),
+ Network::Bitcoin | Network::Litecoin | Network::BitcoinGold
+ | Network::Foocoin // <-- add if coin supports segwit
+ );
+
+ let taproot = segwit && matches!(
+ self.mainnet(),
+ Network::Bitcoin
+ // Foocoin intentionally omitted — no taproot
+ );
+
+ OutputScriptSupport { segwit, taproot }
+ }
+}
+```
+
+## 5. PSBT deserialization
+
+**File:** `src/fixed_script_wallet/bitgo_psbt/mod.rs`
+
+### BitGoPsbt::deserialize()
+
+The `BitGoPsbt` enum has three variants:
+
+| Variant | When to use |
+| -------------------------------- | ------------------------------------------------ |
+| `BitcoinLike(Psbt, Network)` | Standard Bitcoin transaction format (most coins) |
+| `Dash(DashBitGoPsbt, Network)` | Dash special transaction format |
+| `Zcash(ZcashBitGoPsbt, Network)` | Zcash overwintered transaction format |
+
+For most Bitcoin forks, use `BitcoinLike`:
+
+```rust
+pub fn deserialize(psbt_bytes: &[u8], network: Network) -> Result {
+ match network {
+ // ...existing cases...
+
+ // Add foocoin to the BitcoinLike arm:
+ Network::Bitcoin
+ | Network::BitcoinTestnet3
+ // ...
+ | Network::Foocoin // <-- add
+ | Network::FoocoinTestnet // <-- add
+ => Ok(BitGoPsbt::BitcoinLike(
+ Psbt::deserialize(psbt_bytes)?,
+ network,
+ )),
+ }
+}
+```
+
+If the coin has a non-standard transaction format (like Zcash's overwintered
+format or Dash's special transactions), you'll need to create a dedicated PSBT
+type. See `zcash_psbt.rs` or `dash_psbt.rs` as examples.
+
+### BitGoPsbt::new() / new_internal()
+
+Similarly, add foocoin to the arm that creates empty PSBTs. If the coin is
+BitcoinLike, it will be handled by the existing fallthrough.
+
+### get_default_sighash_type()
+
+**Location:** Same file, `get_default_sighash_type()` function.
+
+If foocoin uses `SIGHASH_ALL|FORKID` (like BCH, BTG, BSV), add it to the
+`uses_forkid` match:
+
+```rust
+let uses_forkid = matches!(
+ network.mainnet(),
+ Network::BitcoinCash | Network::BitcoinGold | Network::BitcoinSV | Network::Ecash
+ // | Network::Foocoin // <-- only if coin uses FORKID
+);
+```
+
+If foocoin uses standard `SIGHASH_ALL`, no change is needed — it falls through
+to the default.
+
+## 6. Test fixtures
+
+### Address fixtures
+
+**Directory:** `test/fixtures/address/`
+
+Create `foocoin.json` with test vectors: `[scriptType, scriptHex, expectedAddress]`.
+
+The easiest way to generate these is to use the coin's reference implementation
+or a known address from a block explorer. You need vectors for each supported
+script type (P2PKH, P2SH, and P2WPKH/P2WSH if segwit-capable).
+
+```json
+[
+ ["p2pkh", "76a914...88ac", "F..."],
+ ["p2sh", "a914...87", "3..."],
+ ["p2wpkh", "0014...", "foo1..."]
+]
+```
+
+Also update `get_codecs_for_fixture()` in `src/address/mod.rs` (test section):
+
+```rust
+"foocoin.json" => vec![&FOOCOIN, &FOOCOIN_BECH32],
+```
+
+### PSBT fixtures
+
+**Directory:** `test/fixtures/fixed-script/`
+
+PSBT fixtures are **auto-generated** when the JSON files don't exist on disk.
+The generator lives in `test/fixedScript/generateFixture.ts` and creates PSBTs
+with one input per supported script type plus a replay protection input, then
+signs progressively to produce all three signature states.
+
+Fixtures are generated for two transaction formats (`psbt` and `psbt-lite`),
+giving six files per coin:
+
+- `psbt.foo.unsigned.json` / `psbt-lite.foo.unsigned.json`
+- `psbt.foo.halfsigned.json` / `psbt-lite.foo.halfsigned.json`
+- `psbt.foo.fullsigned.json` / `psbt-lite.foo.fullsigned.json`
+
+The `psbt` format includes `non_witness_utxo` on every input; `psbt-lite`
+omits it. Zcash skips the `psbt` format because it does not support
+`non_witness_utxo`.
+
+**To generate fixtures for a new coin:**
+
+No manual registration step is needed — `mainnetCoinNames` in
+`test/fixedScript/networkSupport.util.ts` is derived automatically from
+`coinNames` in `js/coinName.ts` (step 2). On the first test run,
+`loadPsbtFixture()` detects missing fixture files, generates them, writes
+them to disk, and then throws an error prompting you to commit the new files.
+Re-run the tests after committing.
+
+The generator selects script types based on `output_script_support()`:
+
+| Network capability | Chains included |
+| ------------------ | --------------------------------------------------------- |
+| Legacy only | 0 (p2sh) |
+| Segwit | 0, 10 (p2shP2wsh), 20 (p2wsh) |
+| Taproot | + 30 (p2trLegacy), 40 (p2trMusig2 script path + key path) |
+
+If the generated fixtures need updating (e.g. after changing signing logic),
+delete the JSON files and re-run the tests to regenerate them.
+
+## 7. TypeScript bindings
+
+The TypeScript layer wraps the WASM module. The `NetworkName` type should
+automatically include new networks if it's derived from the Rust enum's string
+representation. Verify that:
+
+- `fixedScriptWallet.BitGoPsbt.fromBytes(buf, "foo")` works
+- `fixedScriptWallet.address(rootWalletKeys, chainCode, index, network)` works
+
+If `NetworkName` is a manually maintained union type, add `'foo' | 'tfoo'` to it.
+
+## 8. Run tests
+
+```bash
+# Rust tests (address encoding, PSBT parsing, signing)
+cargo test
+
+# TypeScript integration tests
+npm test
+```
+
+## 9. Checklist
+
+- [ ] `src/networks.rs`: `Foocoin` + `FoocoinTestnet` added to enum + all 7 match arms + `ALL`
+- [ ] `js/coinName.ts`: `"foo"` + `"tfoo"` added to `coinNames`, `getMainnet()` updated
+- [ ] `src/address/mod.rs`: Codec constants defined (Base58Check, optionally Bech32/CashAddr)
+- [ ] `src/address/networks.rs`: `get_decode_codecs()` updated
+- [ ] `src/address/networks.rs`: `get_encode_codec()` updated
+- [ ] `src/address/networks.rs`: `output_script_support()` updated (segwit/taproot flags)
+- [ ] `src/fixed_script_wallet/bitgo_psbt/mod.rs`: `deserialize()` case added
+- [ ] `src/fixed_script_wallet/bitgo_psbt/mod.rs`: `get_default_sighash_type()` updated (if FORKID)
+- [ ] `test/fixtures/address/foocoin.json` created
+- [ ] `test/fixtures/fixed-script/psbt.foo.*.json` + `psbt-lite.foo.*.json` auto-generated by `npm test`
+- [ ] TypeScript `NetworkName` includes new network
+- [ ] `cargo test` passes
+- [ ] `npm test` passes