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();