Skip to content
Draft
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
68 changes: 64 additions & 4 deletions .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
- name: Install wasm-pack
run: curl -sSfL https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz | tar xz -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
run: |
wasm_pack_url="https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz"
archive="/tmp/wasm-pack.tar.gz"

for attempt in 1 2 3 4 5; do
if curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 --retry-connrefused --output "$archive" "$wasm_pack_url"; then
tar xzf "$archive" -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
exit 0
fi

echo "wasm-pack download failed on attempt $attempt/5"
sleep 5
done

echo "Failed to download wasm-pack after multiple attempts"
exit 1
- name: Rust Cache
uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 # v2.8.2
- name: Build
Expand All @@ -45,7 +60,22 @@ jobs:
- name: Enable Corepack
run: corepack enable
- name: Install wasm-pack
run: curl -sSfL https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz | tar xz -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
run: |
wasm_pack_url="https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz"
archive="/tmp/wasm-pack.tar.gz"

for attempt in 1 2 3 4 5; do
if curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 --retry-connrefused --output "$archive" "$wasm_pack_url"; then
tar xzf "$archive" -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
exit 0
fi

echo "wasm-pack download failed on attempt $attempt/5"
sleep 5
done

echo "Failed to download wasm-pack after multiple attempts"
exit 1
- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
Expand All @@ -70,7 +100,22 @@ jobs:
- name: Enable Corepack
run: corepack enable
- name: Install wasm-pack
run: curl -sSfL https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz | tar xz -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
run: |
wasm_pack_url="https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz"
archive="/tmp/wasm-pack.tar.gz"

for attempt in 1 2 3 4 5; do
if curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 --retry-connrefused --output "$archive" "$wasm_pack_url"; then
tar xzf "$archive" -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
exit 0
fi

echo "wasm-pack download failed on attempt $attempt/5"
sleep 5
done

echo "Failed to download wasm-pack after multiple attempts"
exit 1
- name: Rust Cache
uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 # v2.8.2
- name: Setup Node
Expand Down Expand Up @@ -120,7 +165,22 @@ jobs:
- name: Enable Corepack
run: corepack enable
- name: Install wasm-pack
run: curl -sSfL https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz | tar xz -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
run: |
wasm_pack_url="https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz"
archive="/tmp/wasm-pack.tar.gz"

for attempt in 1 2 3 4 5; do
if curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 --retry-connrefused --output "$archive" "$wasm_pack_url"; then
tar xzf "$archive" -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
exit 0
fi

echo "wasm-pack download failed on attempt $attempt/5"
sleep 5
done

echo "Failed to download wasm-pack after multiple attempts"
exit 1
- name: Rust Cache
uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 # v2.8.2
- name: Setup Node
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,22 @@ jobs:
with:
toolchain: stable
- name: Install wasm-pack
run: curl -sSfL https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz | tar xz -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
run: |
wasm_pack_url="https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz"
archive="/tmp/wasm-pack.tar.gz"

for attempt in 1 2 3 4 5; do
if curl --fail --location --retry 5 --retry-all-errors --retry-delay 2 --retry-connrefused --output "$archive" "$wasm_pack_url"; then
tar xzf "$archive" -C /usr/local/bin --strip-components=1 wasm-pack-v0.14.0-x86_64-unknown-linux-musl/wasm-pack
exit 0
fi

echo "wasm-pack download failed on attempt $attempt/5"
sleep 5
done

echo "Failed to download wasm-pack after multiple attempts"
exit 1
- name: Install jq
run: sudo apt-get update -y && sudo apt-get install -y jq
- name: Rust Cache
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add an `examples/` directory with browser (Vite), Node.js, and Next.js
tutorials, plus README links for quicker onboarding
([#23](https://github.com/bitcoindevkit/bdk-wasm/issues/23))
- Expand Wallet API surface ([#21](https://github.com/bitcoindevkit/bdk-wasm/issues/21)):
- `Wallet::finalize_psbt` for finalizing PSBTs (adding finalized script/witness to inputs)
- `Wallet::cancel_tx` for releasing reserved change addresses when a transaction won't be broadcast
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ yarn add @bitcoindevkit/bdk-wallet-web
yarn add @bitcoindevkit/bdk-wallet-node
```

## Examples and tutorials

The repository now includes ready-to-copy example projects under
[`examples/`](./examples/):

- [`examples/browser-vite`](./examples/browser-vite) — vanilla JavaScript +
Vite browser example using `@bitcoindevkit/bdk-wallet-web`
- [`examples/node-wallet`](./examples/node-wallet) — Node.js example that
creates a wallet, syncs with Esplora, signs a PSBT, and broadcasts a
self-send transaction
- [`examples/nextjs`](./examples/nextjs) — Next.js client-side integration
example for `@bitcoindevkit/bdk-wallet-web`
- [`examples/README.md`](./examples/README.md) — overview, safety notes, and
when to use each example

## Notes on WASM Specific Considerations

> [!WARNING]
Expand Down
35 changes: 35 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Examples

This directory contains small, focused examples for the published
`bdk-wasm` JavaScript packages.

## Included examples

- [`browser-vite`](./browser-vite) — vanilla JavaScript + Vite in the browser
- [`node-wallet`](./node-wallet) — Node.js + Esplora full scan, PSBT signing,
and transaction broadcast
- [`nextjs`](./nextjs) — client-side loading pattern for Next.js / React apps

## Safety note

The browser and Next.js examples embed throwaway demo descriptors so the code
works out of the box. Those descriptors are for documentation only.

Do **not** ship private descriptors, seeds, or xprvs inside browser bundles or
React apps in production. For production:

- use public descriptors client-side when possible
- keep signing in a secure backend or hardware signer flow
- persist wallet state outside the WASM module using the exported `ChangeSet`

## Picking the right example

- Start with [`browser-vite`](./browser-vite) if you want the smallest browser
setup and only need local wallet operations.
- Start with [`node-wallet`](./node-wallet) if you want a scriptable backend,
job worker, or service that talks to Esplora.
- Start with [`nextjs`](./nextjs) if you are integrating `bdk-wasm` into a
React application with server-side rendering in the stack.

Each example README lists the exact commands needed to bootstrap a fresh app
and copy the sample files over.
41 changes: 41 additions & 0 deletions examples/browser-vite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Browser example with Vite

This example shows the smallest browser setup for
`@bitcoindevkit/bdk-wallet-web` using vanilla JavaScript and Vite.

It creates a demo signet wallet in the browser and renders:

- the network
- the first derived address
- the next revealed address
- the public external descriptor

## 1. Create a fresh Vite app

```sh
npm create vite@latest browser-vite -- --template vanilla
cd browser-vite
npm install
npm install @bitcoindevkit/bdk-wallet-web
```

## 2. Replace the generated files

Copy the sample files from this directory into the fresh Vite app:

- `index.html`
- `src/main.js`

## 3. Start the dev server

```sh
npm run dev
```

## Notes

- The descriptors in `src/main.js` are throwaway demo descriptors copied from
the repository test fixtures.
- This example intentionally avoids syncing against Esplora so it stays focused
on local wallet initialization in a browser context.
- In production, never embed real xprvs or seed material in client-side code.
12 changes: 12 additions & 0 deletions examples/browser-vite/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bdk-wasm Vite example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
60 changes: 60 additions & 0 deletions examples/browser-vite/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import init, { Wallet } from "@bitcoindevkit/bdk-wallet-web";

const network = "signet";

const demoDescriptors = {
external:
"wpkh(tprv8ZgxMBicQKsPd5puBG1xsJ5V53vVPfCy2gnZfsqzmDSDjaQx8LEW4REFvrj6PQMuer7NqZeBiy9iP9ucqJZiveeEGqQ5CvcfV6SPcy8LQR7/84'/1'/0'/0/*)#jjcsy5wd",
internal:
"wpkh(tprv8ZgxMBicQKsPd5puBG1xsJ5V53vVPfCy2gnZfsqzmDSDjaQx8LEW4REFvrj6PQMuer7NqZeBiy9iP9ucqJZiveeEGqQ5CvcfV6SPcy8LQR7/84'/1'/0'/1/*)#rxa3ep74",
};

document.querySelector("#app").innerHTML = `
<main style="font-family: sans-serif; margin: 2rem auto; max-width: 52rem; line-height: 1.5;">
<h1>bdk-wasm browser example</h1>
<p>
This page loads <code>@bitcoindevkit/bdk-wallet-web</code>, creates a
demo signet wallet, and derives a couple of addresses in the browser.
</p>
<dl>
<dt><strong>Network</strong></dt>
<dd id="network">Loading...</dd>

<dt><strong>First external address</strong></dt>
<dd id="first-address">Loading...</dd>

<dt><strong>Next revealed external address</strong></dt>
<dd id="next-address">Loading...</dd>

<dt><strong>Public external descriptor</strong></dt>
<dd><code id="descriptor" style="word-break: break-all;">Loading...</code></dd>
</dl>

<p id="error" style="color: #b00020;"></p>
</main>
`;

async function main() {
await init();

const wallet = Wallet.create(
network,
demoDescriptors.external,
demoDescriptors.internal
);

const firstAddress = wallet.peek_address("external", 0).address.toString();
const nextAddress = wallet.reveal_next_address("external").address.toString();

document.querySelector("#network").textContent = wallet.network;
document.querySelector("#first-address").textContent = firstAddress;
document.querySelector("#next-address").textContent = nextAddress;
document.querySelector("#descriptor").textContent =
wallet.public_descriptor("external");
}

main().catch((error) => {
console.error(error);
document.querySelector("#error").textContent =
error instanceof Error ? error.message : String(error);
});
38 changes: 38 additions & 0 deletions examples/nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Next.js example

This example shows the recommended loading pattern for
`@bitcoindevkit/bdk-wallet-web` inside a Next.js app:

- keep the page itself server-rendered
- load the WASM package only inside a client component
- initialize the module inside `useEffect`

## 1. Create a new app

```sh
npx create-next-app@latest nextjs-bdk-demo --ts --app
cd nextjs-bdk-demo
npm install @bitcoindevkit/bdk-wallet-web
```

## 2. Copy the sample files

Copy these files into the generated project:

- `app/page.tsx`
- `app/components/wallet-demo.tsx`

## 3. Start the app

```sh
npm run dev
```

## Why this pattern matters

`@bitcoindevkit/bdk-wallet-web` is a browser-side WASM package. Importing it at
module scope in a server component can break SSR builds. Dynamic importing it
inside a `"use client"` component keeps the boundary explicit and reliable.

The sample uses demo signet descriptors for illustration only. Replace them
with your own safe integration strategy before shipping anything real.
Loading
Loading