diff --git a/packages/demo-wallet/.gitignore b/packages/demo-wallet/.gitignore
new file mode 100644
index 0000000..863381c
--- /dev/null
+++ b/packages/demo-wallet/.gitignore
@@ -0,0 +1,11 @@
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+dist
+.parcel-cache
+wasm-pkg
diff --git a/packages/demo-wallet/.proxyrc.js b/packages/demo-wallet/.proxyrc.js
new file mode 100644
index 0000000..fdb1831
--- /dev/null
+++ b/packages/demo-wallet/.proxyrc.js
@@ -0,0 +1,7 @@
+module.exports = function (app) {
+ app.use((req, res, next) => {
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
+ res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
+ next();
+ });
+ };
diff --git a/packages/demo-wallet/README.md b/packages/demo-wallet/README.md
new file mode 100644
index 0000000..9051997
--- /dev/null
+++ b/packages/demo-wallet/README.md
@@ -0,0 +1 @@
+# WebZjs Demo Web Wallet
diff --git a/packages/demo-wallet/package.json b/packages/demo-wallet/package.json
new file mode 100644
index 0000000..5346a80
--- /dev/null
+++ b/packages/demo-wallet/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@webzjs/demo-wallet",
+ "source": "src/index.html",
+ "scripts": {
+ "start:dev": "parcel --no-autoinstall --no-cache src/index.html"
+ },
+ "dependencies": {
+ "@webzjs/webz-core": "workspace:^",
+ "bootstrap": "^5.3.3",
+ "react": "^18.2.0",
+ "react-bootstrap": "^2.10.4",
+ "react-dom": "^18.2.0"
+ }
+}
diff --git a/packages/demo-wallet/src/App/App.css b/packages/demo-wallet/src/App/App.css
new file mode 100644
index 0000000..65f3382
--- /dev/null
+++ b/packages/demo-wallet/src/App/App.css
@@ -0,0 +1,10 @@
+/* Style inputs */
+input, select, button {
+ width: 100%;
+ padding: 12px 20px;
+ margin: 8px 0;
+ display: inline-block;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+}
diff --git a/packages/demo-wallet/src/App/App.js b/packages/demo-wallet/src/App/App.js
new file mode 100644
index 0000000..80bf2f9
--- /dev/null
+++ b/packages/demo-wallet/src/App/App.js
@@ -0,0 +1,60 @@
+import "./App.css";
+import "bootstrap/dist/css/bootstrap.min.css";
+
+import { useState, useEffect, createContext } from "react";
+
+import Tab from "react-bootstrap/Tab";
+import Tabs from "react-bootstrap/Tabs";
+
+import initWasm, { initThreadPool, start, WebWallet } from "@webzjs/webz-core";
+
+import { ImportAccount } from "./components/ImportAccount";
+import { SendFunds } from "./components/SendFunds";
+import { ReceiveFunds } from "./components/ReceiveFunds";
+import { Balance } from "./components/Balance";
+
+const SAPLING_ACTIVATION = 419200;
+const ORCHARD_ACTIVATION = 1687104;
+const TIP = 2442739;
+
+const MAINNET_LIGHTWALLETD_PROXY = "https://zcash-mainnet.chainsafe.dev";
+const TESTNET_LIGHTWALLETD_PROXY = "https://zcash-testnet.chainsafe.dev";
+
+export const WalletContext = createContext();
+
+export function App() {
+
+ useEffect(() => {
+ async function init() {
+ await initWasm();
+ await initThreadPool(1);
+ setWebWallet(new WebWallet("main", MAINNET_LIGHTWALLETD_PROXY, 1));
+ }
+ init();
+ }, []);
+
+ let [webWallet, setWebWallet] = useState();
+
+ return (
+
+
+ WebZjs Wallet Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/demo-wallet/src/App/components/Balance.js b/packages/demo-wallet/src/App/components/Balance.js
new file mode 100644
index 0000000..faffff2
--- /dev/null
+++ b/packages/demo-wallet/src/App/components/Balance.js
@@ -0,0 +1,14 @@
+import Form from "react-bootstrap/Form";
+
+export function Balance() {
+ return (
+
+ Shielded ZEC (spendable):
+
+ Change Pending:
+
+
+
+ );
+}
diff --git a/packages/demo-wallet/src/App/components/ImportAccount.js b/packages/demo-wallet/src/App/components/ImportAccount.js
new file mode 100644
index 0000000..a344e1d
--- /dev/null
+++ b/packages/demo-wallet/src/App/components/ImportAccount.js
@@ -0,0 +1,52 @@
+import { useContext, useState } from "react";
+
+import Button from "react-bootstrap/Button";
+import Form from "react-bootstrap/Form";
+
+import { WalletContext } from "../App";
+
+export function ImportAccount() {
+ let webWallet = useContext(WalletContext);
+
+ let [birthdayHeight, setBirthdayHeight] = useState(0);
+ let [seedPhrase, setSeedPhrase] = useState("");
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ await webWallet.create_account(seedPhrase, 0, birthdayHeight);
+ };
+
+ return (
+
+ Seed Phrase
+ setSeedPhrase(value)}
+ rows={3}
+ />
+
+ Do not import a seed phrase holding any significant funds into this
+ wallet demo
+
+
+
+
+ Birthday Block Height
+
+ setBirthdayHeight(parseInt(value))
+ }
+ />
+
+
+
+ );
+}
diff --git a/packages/demo-wallet/src/App/components/ReceiveFunds.js b/packages/demo-wallet/src/App/components/ReceiveFunds.js
new file mode 100644
index 0000000..434f1a4
--- /dev/null
+++ b/packages/demo-wallet/src/App/components/ReceiveFunds.js
@@ -0,0 +1,21 @@
+import Form from "react-bootstrap/Form";
+
+export function ReceiveFunds() {
+ return (
+ To Account:
+
+
+
+
+ Share one of these addresses to receive funds
+
+
+ Unified Address:
+
+ Transparent Address:
+
+
+
+ );
+}
diff --git a/packages/demo-wallet/src/App/components/SendFunds.js b/packages/demo-wallet/src/App/components/SendFunds.js
new file mode 100644
index 0000000..68d4eff
--- /dev/null
+++ b/packages/demo-wallet/src/App/components/SendFunds.js
@@ -0,0 +1,24 @@
+import Button from "react-bootstrap/Button";
+import Form from "react-bootstrap/Form";
+
+export function SendFunds() {
+ return (
+
+ From Account:
+
+
+
+ To:
+
+ Amount:
+
+ Memo (optional):
+
+
+
+
+ );
+}
diff --git a/packages/demo-wallet/src/enable-threads.js b/packages/demo-wallet/src/enable-threads.js
new file mode 100644
index 0000000..ea8cfc1
--- /dev/null
+++ b/packages/demo-wallet/src/enable-threads.js
@@ -0,0 +1,76 @@
+// NOTE: This file creates a service worker that cross-origin-isolates the page (read more here: https://web.dev/coop-coep/) which allows us to use wasm threads.
+// Normally you would set the COOP and COEP headers on the server to do this, but Github Pages doesn't allow this, so this is a hack to do that.
+
+/* Edited version of: coi-serviceworker v0.1.6 - Guido Zuidhof, licensed under MIT */
+// From here: https://github.com/gzuidhof/coi-serviceworker
+if(typeof window === 'undefined') {
+ self.addEventListener("install", () => self.skipWaiting());
+ self.addEventListener("activate", e => e.waitUntil(self.clients.claim()));
+
+ async function handleFetch(request) {
+ if(request.cache === "only-if-cached" && request.mode !== "same-origin") {
+ return;
+ }
+
+ if(request.mode === "no-cors") { // We need to set `credentials` to "omit" for no-cors requests, per this comment: https://bugs.chromium.org/p/chromium/issues/detail?id=1309901#c7
+ request = new Request(request.url, {
+ cache: request.cache,
+ credentials: "omit",
+ headers: request.headers,
+ integrity: request.integrity,
+ destination: request.destination,
+ keepalive: request.keepalive,
+ method: request.method,
+ mode: request.mode,
+ redirect: request.redirect,
+ referrer: request.referrer,
+ referrerPolicy: request.referrerPolicy,
+ signal: request.signal,
+ });
+ }
+
+ let r = await fetch(request).catch(e => console.error(e));
+
+ if(r.status === 0) {
+ return r;
+ }
+
+ const headers = new Headers(r.headers);
+ headers.set("Cross-Origin-Embedder-Policy", "require-corp");
+ headers.set("Cross-Origin-Opener-Policy", "same-origin");
+
+ return new Response(r.body, { status: r.status, statusText: r.statusText, headers });
+ }
+
+ self.addEventListener("fetch", function(e) {
+ e.respondWith(handleFetch(e.request)); // respondWith must be executed synchonously (but can be passed a Promise)
+ });
+
+ } else {
+ (async function() {
+ if(window.crossOriginIsolated !== false) return;
+
+ let registration = await navigator.serviceWorker.register(window.document.currentScript.src).catch(e => console.error("COOP/COEP Service Worker failed to register:", e));
+ if(registration) {
+ console.log("COOP/COEP Service Worker registered", registration.scope);
+
+ registration.addEventListener("updatefound", () => {
+ console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
+ window.location.reload();
+ });
+
+ // If the registration is active, but it's not controlling the page
+ if(registration.active && !navigator.serviceWorker.controller) {
+ console.log("Reloading page to make use of COOP/COEP Service Worker.");
+ window.location.reload();
+ }
+ }
+ })();
+ }
+
+ // Code to deregister:
+ // let registrations = await navigator.serviceWorker.getRegistrations();
+ // for(let registration of registrations) {
+ // await registration.unregister();
+ // }
+
\ No newline at end of file
diff --git a/packages/demo-wallet/src/index.html b/packages/demo-wallet/src/index.html
new file mode 100644
index 0000000..60c5356
--- /dev/null
+++ b/packages/demo-wallet/src/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ WebZjs Wallet Demo
+
+
+
+
+
+
+
diff --git a/packages/demo-wallet/src/index.js b/packages/demo-wallet/src/index.js
new file mode 100644
index 0000000..b224e35
--- /dev/null
+++ b/packages/demo-wallet/src/index.js
@@ -0,0 +1,6 @@
+import { createRoot } from "react-dom/client";
+import { App } from "./app/App";
+
+const container = document.getElementById("app");
+const root = createRoot(container)
+root.render();