Skip to content

Commit f1c03fd

Browse files
Merge pull request #16 from BitGo/BTC-2652.improve-namespacing
feat(wasm-utxo): improve TypeScript APIs and add BIP32 support
2 parents bb22b6c + 8ecd880 commit f1c03fd

File tree

23 files changed

+1025
-216
lines changed

23 files changed

+1025
-216
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,13 @@ jobs:
8080
- name: Check Source Code Formatting
8181
run: npm run check-fmt
8282

83+
- name: Wasm-Pack Test (Node)
84+
run: npm run test:wasm-pack-node
85+
working-directory: packages/wasm-utxo
86+
87+
- name: Wasm-Pack Test (Chrome)
88+
run: npm run test:wasm-pack-chrome
89+
working-directory: packages/wasm-utxo
90+
8391
- name: Unit Test
8492
run: npm --workspaces test

packages/wasm-utxo/Cargo.lock

Lines changed: 101 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/wasm-utxo/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ base64 = "0.22.1"
2020
serde = { version = "1.0", features = ["derive"] }
2121
serde_json = "1.0"
2222
hex = "0.4"
23+
wasm-bindgen-test = "0.3"
2324

2425
[profile.release]
2526
# this is required to make webpack happy

packages/wasm-utxo/js/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Purpose
2+
3+
The primary purpose of this directory is to expose better TypeScript signatures than those
4+
generated by the `wasm-pack` command (which uses `wasm-bindgen`).
5+
6+
While the `wasm-bindgen` crate allows some customization of the emitted type signatures, it
7+
is a bit painful to use and has certain limitations that cannot be easily worked around.
8+
9+
## Architecture Pattern
10+
11+
This directory implements a **namespace wrapper pattern** that provides a cleaner, more
12+
type-safe API over the raw WASM bindings.
13+
14+
### Pattern Overview
15+
16+
1. **WASM Generation** (`wasm/wasm_utxo.d.ts`)
17+
18+
- Generated by `wasm-bindgen` from Rust code
19+
- Exports classes with static methods (e.g., `AddressNamespace`, `UtxolibCompatNamespace`)
20+
- Uses loose types (`any`, `string | null`) due to WASM-bindgen limitations
21+
22+
2. **Namespace Wrapper Files** (e.g., `address.ts`, `utxolibCompat.ts`, `fixedScriptWallet.ts`)
23+
24+
- Import the generated WASM namespace classes
25+
- Define precise TypeScript types to replace `any` types
26+
- Export individual functions that wrap the static WASM methods
27+
- Re-export related types for convenience
28+
29+
3. **Shared Type Files** (e.g., `coinName.ts`, `triple.ts`)
30+
31+
- Define common types used across multiple modules
32+
- Single source of truth to avoid duplication
33+
- Imported by wrapper files as needed
34+
35+
4. **Main Entry Point** (`index.ts`)
36+
- Uses `export * as` to group related functionality into namespaces
37+
- Re-exports shared types for top-level access
38+
- Augments WASM types with additional TypeScript declarations
39+
40+
### Example
41+
42+
Given a WASM-generated class:
43+
44+
```typescript
45+
// wasm/wasm_utxo.d.ts (generated)
46+
export class AddressNamespace {
47+
static to_output_script_with_coin(address: string, coin: string): Uint8Array;
48+
static from_output_script_with_coin(
49+
script: Uint8Array,
50+
coin: string,
51+
format?: string | null,
52+
): string;
53+
}
54+
```
55+
56+
We create a wrapper module:
57+
58+
```typescript
59+
// address.ts
60+
import { AddressNamespace } from "./wasm/wasm_utxo";
61+
import type { CoinName } from "./coinName";
62+
63+
export type AddressFormat = "default" | "cashaddr";
64+
65+
export function toOutputScriptWithCoin(address: string, coin: CoinName): Uint8Array {
66+
return AddressNamespace.to_output_script_with_coin(address, coin);
67+
}
68+
69+
export function fromOutputScriptWithCoin(
70+
script: Uint8Array,
71+
coin: CoinName,
72+
format?: AddressFormat,
73+
): string {
74+
return AddressNamespace.from_output_script_with_coin(script, coin, format);
75+
}
76+
```
77+
78+
And expose it via the main entry point:
79+
80+
```typescript
81+
// index.ts
82+
export * as address from "./address";
83+
```
84+
85+
### Benefits
86+
87+
- **Type Safety**: Replace loose `any` and `string` types with precise union types
88+
- **Better DX**: IDE autocomplete works better with concrete types
89+
- **Maintainability**: Centralized type definitions prevent duplication

packages/wasm-utxo/js/address.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { AddressNamespace } from "./wasm/wasm_utxo";
2+
import type { CoinName } from "./coinName";
3+
4+
export type AddressFormat = "default" | "cashaddr";
5+
6+
export function toOutputScriptWithCoin(address: string, coin: CoinName): Uint8Array {
7+
return AddressNamespace.to_output_script_with_coin(address, coin);
8+
}
9+
10+
export function fromOutputScriptWithCoin(
11+
script: Uint8Array,
12+
coin: CoinName,
13+
format?: AddressFormat,
14+
): string {
15+
return AddressNamespace.from_output_script_with_coin(script, coin, format);
16+
}

packages/wasm-utxo/js/coinName.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// BitGo coin names (from Network::from_coin_name in src/networks.rs)
2+
export type CoinName =
3+
| "btc"
4+
| "tbtc"
5+
| "tbtc4"
6+
| "tbtcsig"
7+
| "tbtcbgsig"
8+
| "bch"
9+
| "tbch"
10+
| "bcha"
11+
| "tbcha"
12+
| "btg"
13+
| "tbtg"
14+
| "bsv"
15+
| "tbsv"
16+
| "dash"
17+
| "tdash"
18+
| "doge"
19+
| "tdoge"
20+
| "ltc"
21+
| "tltc"
22+
| "zec"
23+
| "tzec";
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FixedScriptWalletNamespace } from "./wasm/wasm_utxo";
2+
import type { UtxolibNetwork, UtxolibRootWalletKeys } from "./utxolibCompat";
3+
import { Triple } from "./triple";
4+
5+
export type WalletKeys =
6+
/** Just an xpub triple, will assume default derivation prefixes */
7+
| Triple<string>
8+
/** Compatible with utxolib RootWalletKeys */
9+
| UtxolibRootWalletKeys;
10+
11+
/**
12+
* Create the output script for a given wallet keys and chain and index
13+
*/
14+
export function outputScript(keys: WalletKeys, chain: number, index: number): Uint8Array {
15+
return FixedScriptWalletNamespace.output_script(keys, chain, index);
16+
}
17+
18+
/**
19+
* Create the address for a given wallet keys and chain and index and network.
20+
* Wrapper for outputScript that also encodes the script to an address.
21+
*/
22+
export function address(
23+
keys: WalletKeys,
24+
chain: number,
25+
index: number,
26+
network: UtxolibNetwork,
27+
): string {
28+
return FixedScriptWalletNamespace.address(keys, chain, index, network);
29+
}

0 commit comments

Comments
 (0)