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
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: "Deploy to GitHub Pages"
on:
push:
branches:
- gh-pages
- master
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
Expand Down
26 changes: 24 additions & 2 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/webui/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist/
wasm/
6 changes: 4 additions & 2 deletions packages/webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
"main": "./dist/src/index.js",
"private": true,
"scripts": {
"build": "webpack --mode production --progress --config ./webpack.config.js",
"extract-samples": "node scripts/extract-samples.js",
"build:wasm": "bash scripts/build-wasm.sh",
"build": "npm run build:wasm && npm run extract-samples && webpack --mode production --progress --config ./webpack.config.js",
"typecheck": "tsc --noEmit",
"test": "echo \"Error: no test specified\"",
"dev": "webpack serve --mode development --progress --hot --config ./webpack.config.js",
"dev": "npm run build:wasm && npm run extract-samples && webpack serve --mode development --progress --hot --config ./webpack.config.js",
"fmt": "prettier --write .",
"check-fmt": "prettier --check '{src,webpack}/**/*.{tsx,ts,js}'",
"clean": "rm -r ./dist",
Expand Down
48 changes: 48 additions & 0 deletions packages/webui/scripts/build-wasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
WEBUI_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
WASM_UTXO_DIR="$(cd "$WEBUI_DIR/../wasm-utxo" && pwd)"
OUT_DIR="$WEBUI_DIR/wasm"

# Auto-detect Mac and use Homebrew LLVM for WASM compilation
# Apple's Clang doesn't support wasm32-unknown-unknown target
if [[ "$(uname -s)" == "Darwin" ]]; then
HOMEBREW_LLVM="$(brew --prefix llvm 2>/dev/null || true)"
if [[ -n "$HOMEBREW_LLVM" ]]; then
export CC="$HOMEBREW_LLVM/bin/clang"
export AR="$HOMEBREW_LLVM/bin/llvm-ar"
echo "Using Homebrew LLVM: $HOMEBREW_LLVM"
fi
fi

echo "Building wasm-utxo with inspect feature..."
rm -rf "$OUT_DIR"
wasm-pack build \
--no-opt \
--no-pack \
--weak-refs \
"$WASM_UTXO_DIR" \
--out-dir "$OUT_DIR" \
--target bundler \
--features inspect

echo "Optimizing wasm..."
wasm-opt \
--enable-bulk-memory \
--enable-nontrapping-float-to-int \
--enable-sign-ext \
-Oz \
"$OUT_DIR"/wasm_utxo_bg.wasm \
-o "$OUT_DIR"/wasm_utxo_bg.wasm

# Remove .gitignore files that wasm-pack generates
find "$OUT_DIR" -name .gitignore -delete

# Copy .d.ts to wasm-utxo/js/wasm/ so ts-loader (project references) sees inspect types.
# This is safe: js/wasm/ is a build artifact, and the npm publish uses dist/ which is already built.
cp "$OUT_DIR"/wasm_utxo.d.ts "$WASM_UTXO_DIR/js/wasm/"

echo "wasm build complete: $OUT_DIR"
ls -lh "$OUT_DIR"/*.wasm
130 changes: 130 additions & 0 deletions packages/webui/scripts/extract-samples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env node
/**
* Extract sample PSBT/TX data from fixtures for the parser UI.
*
* This script reads fixture files and extracts:
* - psbtBase64: unsigned or partially signed PSBT
* - psbtBase64Finalized: fully signed PSBT
* - extractedTransaction: raw transaction hex
*
* Usage: node scripts/extract-samples.js
*/

const fs = require("fs");
const path = require("path");

const FIXTURES_DIR = path.resolve(__dirname, "../src/fixtures/fixed-script");
const OUTPUT_FILE = path.resolve(__dirname, "../src/wasm-utxo/parser/samples.ts");

function extractSamples() {
const samples = [];

// Read fixture files
const files = fs.readdirSync(FIXTURES_DIR).filter((f) => f.endsWith(".json") && f.startsWith("psbt"));

for (const file of files) {
const filePath = path.join(FIXTURES_DIR, file);
const content = fs.readFileSync(filePath, "utf-8");

let fixture;
try {
fixture = JSON.parse(content);
} catch {
console.warn(`Skipping invalid JSON: ${file}`);
continue;
}

// Extract name from filename: psbt-lite.bitcoin.unsigned.json -> Bitcoin Lite (unsigned)
const match = file.match(/^psbt(-lite)?\.(\w+)\.(\w+)\.json$/);
if (!match) continue;

const [, lite, network, state] = match;
const networkName = network.charAt(0).toUpperCase() + network.slice(1);
const stateName = state.charAt(0).toUpperCase() + state.slice(1);
const liteLabel = lite ? " Lite" : "";

// Add psbtBase64 (unsigned/halfsigned/fullsigned)
if (fixture.psbtBase64) {
samples.push({
name: `${networkName}${liteLabel} PSBT (${stateName})`,
type: "psbt",
data: fixture.psbtBase64,
});
}

// Add psbtBase64Finalized (from fullsigned files only)
if (fixture.psbtBase64Finalized && state === "fullsigned") {
samples.push({
name: `${networkName}${liteLabel} PSBT (Finalized)`,
type: "psbt",
data: fixture.psbtBase64Finalized,
});
}

// Add extractedTransaction
if (fixture.extractedTransaction) {
samples.push({
name: `${networkName}${liteLabel} TX (Extracted)`,
type: "tx",
data: fixture.extractedTransaction,
});
}
}

// Sort by name
samples.sort((a, b) => a.name.localeCompare(b.name));

return samples;
}

function generateTypeScript(samples) {
const lines = [
"/**",
" * Sample PSBT/TX data extracted from test fixtures.",
" * Auto-generated by scripts/extract-samples.js",
" * DO NOT EDIT MANUALLY",
" */",
"",
"export interface Sample {",
" name: string;",
' type: "psbt" | "tx";',
" data: string;",
"}",
"",
"export const samples: Sample[] = [",
];

for (const sample of samples) {
lines.push(" {");
lines.push(` name: ${JSON.stringify(sample.name)},`);
lines.push(` type: ${JSON.stringify(sample.type)},`);
lines.push(` data: ${JSON.stringify(sample.data)},`);
lines.push(" },");
}

lines.push("];");
lines.push("");

return lines.join("\n");
}

function main() {
console.log("Extracting samples from fixtures...");

const samples = extractSamples();
console.log(`Found ${samples.length} samples`);

const typescript = generateTypeScript(samples);

// Ensure output directory exists
const outputDir = path.dirname(OUTPUT_FILE);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

fs.writeFileSync(OUTPUT_FILE, typescript);
console.log(`Written to ${OUTPUT_FILE}`);
}

main();

1 change: 1 addition & 0 deletions packages/webui/src/fixtures
Loading
Loading