Skip to content
Closed
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
12 changes: 12 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "target-feature=+crt-static",
"-C", "link-arg=--import-memory",
"-C", "link-arg=--initial-memory=2097152",
"-C", "link-arg=--max-memory=2097152",
"-C", "link-arg=--stack-first",
"-C", "link-arg=--export-table",
]

[build]
target = "wasm32-unknown-unknown"
21 changes: 16 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,32 @@ edition = "2021"

[lib]
name = "sp_client"
crate-type = ["lib", "staticlib", "cdylib"]

crate-type = ["lib", "staticlib", "cdylib", "rlib"]

[dependencies]
silentpayments = "0.4"
anyhow = "1.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
bitcoin = { version = "0.31.1", features = ["serde", "rand", "base64"] }
rayon = "1.10.0"
futures = "0.3"
log = "0.4"
async-trait = "0.1"
reqwest = { version = "0.12.4", features = ["rustls-tls", "gzip", "json"], default-features = false, optional = true }
hex = { version = "0.4.3", features = ["serde"], optional = true }
hex = { version = "0.4.3", features = ["serde"] }
bdk_coin_select = "0.4.0"

rayon = { version = "1.10.0", optional = true }

wasm-bindgen = { version = "0.2", optional = true }

reqwest = { version = "0.12.4", features = ["rustls-tls", "gzip", "json"], default-features = false, optional = true }

js-sys = { version = "0.3", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
serde-wasm-bindgen = { version = "0.6.5", optional = true }

[features]
blindbit-backend = ["reqwest", "hex"]
blindbit-native = ["reqwest", "rayon"]
blindbit-wasm = ["wasm-bindgen", "js-sys", "wasm-bindgen-futures", "serde-wasm-bindgen"]
default = ["blindbit-native"]
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,77 @@ Whereas rust-silentpayments concerns itself with cryptography (it is essentially
sp-client is concerned with high-level wallet stuff, such as parsing incoming transactions, managing owned outputs, and signing transactions.

This library is used as a backend for the silent payment wallet [Dana wallet](https://github.com/cygnet3/danawallet).

## WASM Support

This library supports WebAssembly (WASM) targets for use in web applications. To build for WASM:

### Prerequisites

1. Install the WASM target:
```bash
rustup target add wasm32-unknown-unknown
```

2. Install wasm-pack (optional, for easier WASM builds):
```bash
cargo install wasm-pack
```

### Building for WASM

#### Using Cargo directly:
```bash
cargo build --target wasm32-unknown-unknown
```

#### Using wasm-pack:
```bash
wasm-pack build --target web
```

### Features

When building for WASM:
- The `rayon` dependency is automatically disabled and parallel processing falls back to sequential processing
- The `blindbit-backend` feature is available with **TypeScript HTTP client injection** for WASM
- All core functionality remains available

### HTTP Client in WASM

The library uses a **TypeScript HTTP client injection** approach for WASM builds:

- **Native builds**: Use `reqwest` for HTTP requests
- **WASM builds**: Accept a TypeScript HTTP client that implements the required interface

This approach provides several benefits:
- **No bundle bloat**: TypeScript code doesn't increase WASM bundle size
- **Familiar APIs**: Use standard `fetch()` API
- **Better error handling**: TypeScript gives proper error types
- **Flexibility**: Easy to add features like retry logic, caching, etc.

### Usage in Web Applications

The library can be used in web applications through standard WASM interop. For HTTP functionality in WASM:

```typescript
import { WasmHttpClient } from './http-client';
import init, { BlindbitClient } from './pkg/sp_client';

async function main() {
await init();

const httpClient = new WasmHttpClient();
const blindbitClient = BlindbitClient.new_wasm(
"https://api.example.com/",
httpClient
);

const height = await blindbitClient.block_height_wasm();
console.log('Block height:', height);
}

main();
```

See the `examples/` directory for complete working examples and the `http-client.ts` file for the TypeScript HTTP client implementation.
15 changes: 15 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "wasm_example"
version = "0.1.0"
edition = "2021"

[dependencies]
sp_client = { path = ".." }
bitcoin = { version = "0.31.1", features = ["serde", "rand", "base64"] }
hex = "0.4.3"

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"

[lib]
crate-type = ["cdylib"]
169 changes: 169 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Using SP Client with TypeScript HTTP Client in WASM

This example demonstrates how to use the SP Client library in a WASM environment with a TypeScript HTTP client instead of the native `reqwest` client.

## Overview

The library now supports both native and WASM environments:
- **Native**: Uses `reqwest` for HTTP requests
- **WASM**: Uses a TypeScript HTTP client passed from JavaScript

## Setup

### 1. Build for WASM

```bash
# Install WASM target
rustup target add wasm32-unknown-unknown

# Build the library
cargo build --target wasm32-unknown-unknown

# Or use wasm-pack for easier builds
wasm-pack build --target web
```

### 2. TypeScript HTTP Client

The `http-client.ts` file provides a TypeScript implementation of the HTTP client interface expected by the WASM code.

## Usage

### Basic Example

```typescript
import { WasmHttpClient } from './http-client';
import init, { BlindbitClient } from './pkg/sp_client';

async function main() {
// Initialize WASM
await init();

// Create HTTP client
const httpClient = new WasmHttpClient();

// Create Blindbit client with HTTP client
const blindbitClient = BlindbitClient.new_wasm(
"https://api.example.com/",
httpClient
);

// Use the client
try {
const height = await blindbitClient.block_height_wasm();
console.log('Block height:', height);

const info = await blindbitClient.info_wasm();
console.log('Info:', info);
} catch (error) {
console.error('Error:', error);
}
}

main();
```

### Advanced Example with Error Handling

```typescript
import { WasmHttpClient } from './http-client';
import init, { BlindbitClient } from './pkg/sp_client';

class BlindbitService {
private client: BlindbitClient;

constructor(apiUrl: string) {
this.client = BlindbitClient.new_wasm(apiUrl, new WasmHttpClient());
}

async getBlockHeight(): Promise<number> {
try {
return await this.client.block_height_wasm();
} catch (error) {
console.error('Failed to get block height:', error);
throw new Error(`Block height request failed: ${error}`);
}
}

async getInfo(): Promise<any> {
try {
const infoJson = await this.client.info_wasm();
return JSON.parse(infoJson);
} catch (error) {
console.error('Failed to get info:', error);
throw new Error(`Info request failed: ${error}`);
}
}
}

// Usage
async function main() {
await init();

const service = new BlindbitService("https://api.example.com/");

try {
const [height, info] = await Promise.all([
service.getBlockHeight(),
service.getInfo()
]);

console.log(`Current block height: ${height}`);
console.log('API info:', info);
} catch (error) {
console.error('Service error:', error);
}
}

main();
```

## Architecture

### How It Works

1. **TypeScript HTTP Client**: Implements the interface expected by Rust WASM code
2. **WASM Bindings**: Rust code exposes methods that can be called from JavaScript
3. **HTTP Client Injection**: The TypeScript client is passed to the Rust code during construction
4. **Request Delegation**: Rust code delegates HTTP requests to the injected TypeScript client

### Benefits

- **No Bundle Bloat**: TypeScript code doesn't increase WASM bundle size
- **Familiar APIs**: Use standard `fetch()` API
- **Better Error Handling**: TypeScript gives proper error types
- **Flexibility**: Easy to add features like retry logic, caching, etc.
- **Performance**: No overhead from Rust-to-JS conversions for HTTP operations

### Trade-offs

- **Complexity**: Need to handle JS interop and type conversions
- **Memory Management**: Careful about JS object lifetimes
- **Error Propagation**: Errors cross Rust/JS boundary
- **Type Safety**: Less compile-time safety for HTTP operations

## Troubleshooting

### Common Issues

1. **WASM not initialized**: Make sure to call `await init()` before using the library
2. **HTTP client not passed**: Use `new_wasm()` constructor for WASM builds
3. **CORS issues**: Ensure your API server allows requests from your domain
4. **Type errors**: Make sure TypeScript types match the expected interface

### Debug Tips

- Check browser console for JavaScript errors
- Use browser dev tools to inspect network requests
- Verify WASM module is loaded correctly
- Test HTTP client independently before integrating with WASM

## Next Steps

- Add retry logic to the HTTP client
- Implement request caching
- Add request/response interceptors
- Support for different authentication methods
- Add request timeout handling


Loading
Loading