Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,13 @@ jobs:
- name: Check Source Code Formatting
run: npm run check-fmt

- name: Wasm-Pack Test (Node)
run: npm run test:wasm-pack-node
working-directory: packages/wasm-utxo

- name: Wasm-Pack Test (Chrome)
run: npm run test:wasm-pack-chrome
working-directory: packages/wasm-utxo

- name: Unit Test
run: npm --workspaces test
101 changes: 101 additions & 0 deletions packages/wasm-utxo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/wasm-utxo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ base64 = "0.22.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
hex = "0.4"
wasm-bindgen-test = "0.3"

[profile.release]
# this is required to make webpack happy
Expand Down
89 changes: 89 additions & 0 deletions packages/wasm-utxo/js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Purpose

The primary purpose of this directory is to expose better TypeScript signatures than those
generated by the `wasm-pack` command (which uses `wasm-bindgen`).

While the `wasm-bindgen` crate allows some customization of the emitted type signatures, it
is a bit painful to use and has certain limitations that cannot be easily worked around.

## Architecture Pattern

This directory implements a **namespace wrapper pattern** that provides a cleaner, more
type-safe API over the raw WASM bindings.

### Pattern Overview

1. **WASM Generation** (`wasm/wasm_utxo.d.ts`)

- Generated by `wasm-bindgen` from Rust code
- Exports classes with static methods (e.g., `AddressNamespace`, `UtxolibCompatNamespace`)
- Uses loose types (`any`, `string | null`) due to WASM-bindgen limitations

2. **Namespace Wrapper Files** (e.g., `address.ts`, `utxolibCompat.ts`, `fixedScriptWallet.ts`)

- Import the generated WASM namespace classes
- Define precise TypeScript types to replace `any` types
- Export individual functions that wrap the static WASM methods
- Re-export related types for convenience

3. **Shared Type Files** (e.g., `coinName.ts`, `triple.ts`)

- Define common types used across multiple modules
- Single source of truth to avoid duplication
- Imported by wrapper files as needed

4. **Main Entry Point** (`index.ts`)
- Uses `export * as` to group related functionality into namespaces
- Re-exports shared types for top-level access
- Augments WASM types with additional TypeScript declarations

### Example

Given a WASM-generated class:

```typescript
// wasm/wasm_utxo.d.ts (generated)
export class AddressNamespace {
static to_output_script_with_coin(address: string, coin: string): Uint8Array;
static from_output_script_with_coin(
script: Uint8Array,
coin: string,
format?: string | null,
): string;
}
```

We create a wrapper module:

```typescript
// address.ts
import { AddressNamespace } from "./wasm/wasm_utxo";
import type { CoinName } from "./coinName";

export type AddressFormat = "default" | "cashaddr";

export function toOutputScriptWithCoin(address: string, coin: CoinName): Uint8Array {
return AddressNamespace.to_output_script_with_coin(address, coin);
}

export function fromOutputScriptWithCoin(
script: Uint8Array,
coin: CoinName,
format?: AddressFormat,
): string {
return AddressNamespace.from_output_script_with_coin(script, coin, format);
}
```

And expose it via the main entry point:

```typescript
// index.ts
export * as address from "./address";
```

### Benefits

- **Type Safety**: Replace loose `any` and `string` types with precise union types
- **Better DX**: IDE autocomplete works better with concrete types
- **Maintainability**: Centralized type definitions prevent duplication
16 changes: 16 additions & 0 deletions packages/wasm-utxo/js/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AddressNamespace } from "./wasm/wasm_utxo";
import type { CoinName } from "./coinName";

export type AddressFormat = "default" | "cashaddr";

export function toOutputScriptWithCoin(address: string, coin: CoinName): Uint8Array {
return AddressNamespace.to_output_script_with_coin(address, coin);
}

export function fromOutputScriptWithCoin(
script: Uint8Array,
coin: CoinName,
format?: AddressFormat,
): string {
return AddressNamespace.from_output_script_with_coin(script, coin, format);
}
23 changes: 23 additions & 0 deletions packages/wasm-utxo/js/coinName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// BitGo coin names (from Network::from_coin_name in src/networks.rs)
export type CoinName =
| "btc"
| "tbtc"
| "tbtc4"
| "tbtcsig"
| "tbtcbgsig"
| "bch"
| "tbch"
| "bcha"
| "tbcha"
| "btg"
| "tbtg"
| "bsv"
| "tbsv"
| "dash"
| "tdash"
| "doge"
| "tdoge"
| "ltc"
| "tltc"
| "zec"
| "tzec";
29 changes: 29 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FixedScriptWalletNamespace } from "./wasm/wasm_utxo";
import type { UtxolibNetwork, UtxolibRootWalletKeys } from "./utxolibCompat";
import { Triple } from "./triple";

export type WalletKeys =
/** Just an xpub triple, will assume default derivation prefixes */
| Triple<string>
/** Compatible with utxolib RootWalletKeys */
| UtxolibRootWalletKeys;

/**
* Create the output script for a given wallet keys and chain and index
*/
export function outputScript(keys: WalletKeys, chain: number, index: number): Uint8Array {
return FixedScriptWalletNamespace.output_script(keys, chain, index);
}

/**
* Create the address for a given wallet keys and chain and index and network.
* Wrapper for outputScript that also encodes the script to an address.
*/
export function address(
keys: WalletKeys,
chain: number,
index: number,
network: UtxolibNetwork,
): string {
return FixedScriptWalletNamespace.address(keys, chain, index, network);
}
Loading