Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Lykhoyda committed Dec 9, 2024
1 parent 214ee4d commit 3631610
Show file tree
Hide file tree
Showing 22 changed files with 809 additions and 292 deletions.
448 changes: 226 additions & 222 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ codegen-units = 1

[workspace.dependencies]
## Web dependencies
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2.99"
js-sys = "0.3.70"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen-futures = "0.4.49"
web-sys = { version = "0.3.70", features = [
"console",
] }
wasm-bindgen-rayon = { version = "1.2.1" }
wasm-bindgen-rayon = { version = "1.2.2" }

# WASM specific dependencies
tracing-web = { version = "0.1.3" }
Expand Down Expand Up @@ -79,7 +79,7 @@ byte-unit = { version = "5.1.4", features = ["byte"] }


[patch.crates-io]
zip32 = { git = "https://github.com/zcash/zip32.git", branch = "diversifier_index_ord" }
zip32 = { git = "https://github.com/zcash/zip32.git" }
# TODO: See: https://github.com/RReverser/wasm-bindgen-rayon/pull/12
wasm-bindgen-rayon = { git = "https://github.com/9SMTM6/wasm-bindgen-rayon", rev = "d1816e5bc2bf928ff5442355c04500a381d66a41" }
# TODO: Remove these once the PRs are merged
Expand Down
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/template-snap-monorepo.git"
},
"source": {
"shasum": "+SKENP/6gsN0rziHiYtTQI27Cu1ssKsp6FUdW/pjriw=",
"shasum": "5kG/5tKuPrrpApOlFvFR5gCuTloSUZfJ4QCjtVezHu8=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
1 change: 0 additions & 1 deletion packages/web-wallet/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebZjs Web Wallet</title>
</head>
Expand Down
10 changes: 8 additions & 2 deletions packages/web-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@
},
"dependencies": {
"@metamask/providers": "^18.2.0",
"@webzjs/webz-keys": "workspace:^",
"@webzjs/webz-wallet": "workspace:^",
"idb-keyval": "^6.2.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.27.0"
"react-router-dom": "^6.27.0",
"usehooks-ts": "^3.1.0",
"vite-plugin-top-level-await": "^1.4.4"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
Expand All @@ -33,6 +38,7 @@
"typescript": "~5.6.2",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10",
"vite-plugin-svgr": "^4.3.0"
"vite-plugin-svgr": "^4.3.0",
"vite-plugin-wasm": "^3.3.0"
}
}
22 changes: 22 additions & 0 deletions packages/web-wallet/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useInterval } from 'usehooks-ts';
import { RESCAN_INTERVAL } from '@webzjs/demo-wallet/src/App/Constants.tsx';
import { useWebZjsActions } from '@hooks/useWebzjsActions.ts';
import Layout from '@components/Layout/Layout.tsx';
import { Outlet } from 'react-router-dom';

function App() {
const { triggerRescan } = useWebZjsActions();

// rescan the wallet periodically
useInterval(() => {
triggerRescan();
}, RESCAN_INTERVAL);

return (
<Layout>
<Outlet />
</Layout>
);
}

export default App;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Navigate, Outlet } from 'react-router-dom';
import { useMetaMask } from '@hooks/useMetaMask.ts';
import { useMetaMask } from '@hooks/snaps/useMetaMask.ts';
import React from 'react';

const ProtectedRoute: React.FC<{ children?: React.ReactNode }> = ({
Expand Down
4 changes: 4 additions & 0 deletions packages/web-wallet/src/config/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const MAINNET_LIGHTWALLETD_PROXY = 'https://zcash-mainnet.chainsafe.dev';
export const ZATOSHI_PER_ZEC = 1e8;
export const RESCAN_INTERVAL = 20000;
export const NU5_ACTIVATION = 1687104;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type MetaMaskContextType = {
setError: (error: Error) => void;
};

export const MetaMaskContext = createContext<MetaMaskContextType>({
const MetaMaskContext = createContext<MetaMaskContextType>({
provider: null,
installedSnap: null,
error: null,
Expand Down Expand Up @@ -70,5 +70,13 @@ export const MetaMaskProvider = ({ children }: { children: ReactNode }) => {
* @returns The MetaMask context.
*/
export function useMetaMaskContext() {
return useContext(MetaMaskContext);
const context = useContext(MetaMaskContext);

if (context === undefined) {
throw new Error(
'useMetaMaskContext must be called within a MetaMaskProvider',
);
}

return context;
}
193 changes: 193 additions & 0 deletions packages/web-wallet/src/context/WebzjsContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React, { createContext, useReducer, useEffect } from 'react';
import type { ReactNode } from 'react';
import type { MetaMaskInpageProvider } from '@metamask/providers';
import { get, set } from 'idb-keyval';

import initWebzWallet, { initThreadPool, WebWallet } from '@webzjs/webz-wallet';
import initWebzKeys from '@webzjs/webz-keys';

import type { Snap } from '../types';
import { getSnapsProvider } from '../utils';
import { MAINNET_LIGHTWALLETD_PROXY } from '../config/constants.ts';

interface Summary {
chain_tip_height: number;
fully_scanned_height: number;
next_sapling_subtree_index: bigint;
next_orchard_subtree_index: bigint;
account_balances: [number, number][];
}

interface State {
webWallet: WebWallet | null;
provider: MetaMaskInpageProvider | null;
installedSnap: Snap | null;
error: Error | null;
summary: Summary | null;
chainHeight: bigint | null;
activeAccount: number | null;
syncInProgress: boolean;
loading: boolean;
}

type Action =
| { type: 'set-web-wallet'; payload: WebWallet }
| { type: 'set-provider'; payload: MetaMaskInpageProvider | null }
| { type: 'set-error'; payload: Error | null }
| { type: 'set-summary'; payload: Summary }
| { type: 'set-chain-height'; payload: bigint }
| { type: 'set-active-account'; payload: number }
| { type: 'set-sync-in-progress'; payload: boolean }
| { type: 'set-loading'; payload: boolean };

const initialState: State = {
webWallet: null,
provider: null,
installedSnap: null,
error: null,
summary: null,
chainHeight: null,
activeAccount: null,
syncInProgress: false,
loading: true,
};

function reducer(state: State, action: Action): State {
switch (action.type) {
case 'set-web-wallet':
return { ...state, webWallet: action.payload };
case 'set-provider':
return { ...state, provider: action.payload };
case 'set-error':
return { ...state, error: action.payload };
case 'set-summary':
return { ...state, summary: action.payload };
case 'set-chain-height':
return { ...state, chainHeight: action.payload };
case 'set-active-account':
return { ...state, activeAccount: action.payload };
case 'set-sync-in-progress':
return { ...state, syncInProgress: action.payload };
case 'set-loading':
return { ...state, loading: action.payload };

default:
return state;
}
}

interface WebZjsContextType {
state: State;
dispatch: React.Dispatch<Action>;
}

const WebZjsContext = createContext<WebZjsContextType>({
state: initialState,
dispatch: () => {},
});

export const WebZjsProvider = ({ children }: { children: ReactNode }) => {
const [state, dispatch] = useReducer(reducer, initialState);

// Initialize provider and web wallet
useEffect(() => {
initAll();
}, []);

async function initAll() {
try {
await initWebzWallet();
await initWebzKeys();
try {
await initThreadPool(10);
} catch (err) {
console.error(err);
throw Error('Unable to initialize Thread Pool');
}
const provider = await getSnapsProvider();
dispatch({ type: 'set-provider', payload: provider });

const bytes = await get('wallet');
let wallet;

if (bytes) {
console.info('Saved wallet detected. Restoring wallet from storage');
wallet = new WebWallet('main', MAINNET_LIGHTWALLETD_PROXY, 1, bytes);
} else {
console.info('No saved wallet detected. Creating new wallet');
wallet = new WebWallet('main', MAINNET_LIGHTWALLETD_PROXY, 1);
}

dispatch({ type: 'set-web-wallet', payload: wallet });

const summary = await wallet.get_wallet_summary();
if (summary) {
dispatch({ type: 'set-summary', payload: summary });
// Set an active account from summary if available
if (summary.account_balances.length > 0) {
dispatch({
type: 'set-active-account',
payload: summary.account_balances[0][0],
});
}
}

const chainHeight = await wallet.get_latest_block();
if (chainHeight) {
dispatch({ type: 'set-chain-height', payload: chainHeight });
}

dispatch({ type: 'set-loading', payload: false });
} catch (err: never) {
console.error('Initialization error:', err);
dispatch({ type: 'set-error', payload: err.toString() });
dispatch({ type: 'set-loading', payload: false });
}
}

// Clear error after 10 seconds if any
useEffect(() => {
if (state.error) {
const timeout = setTimeout(() => {
dispatch({ type: 'set-error', payload: null });
}, 10000);

return () => clearTimeout(timeout);
}
}, [state.error]);

// Persist changes to IndexedDB whenever relevant parts of state change
useEffect(() => {
if (!state.webWallet) return;

async function flushDb() {
console.info('Serializing wallet and dumping to IndexedDB store');

if (state.webWallet instanceof WebWallet) {
const bytes = await state.webWallet.db_to_bytes();
await set('wallet', bytes);
console.info('Wallet saved to storage');
}
}

// Flush changes on these triggers:
if (!state.loading && !state.syncInProgress && state.webWallet) {
flushDb().catch(console.error);
}
}, [state.webWallet, state.syncInProgress, state.loading]);

return (
<WebZjsContext.Provider value={{ state, dispatch }}>
{children}
</WebZjsContext.Provider>
);
};

export function useWebZjsContext(): WebZjsContextType {
const context = React.useContext(WebZjsContext);

if (context === undefined) {
throw new Error('useWebZjsContext must be used within a WebZjsProvider');
}
return context;
}
10 changes: 5 additions & 5 deletions packages/web-wallet/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './MetamaskContext';
export * from './useMetaMask';
export * from './useRequest';
export * from './useRequestSnap';
export * from './useInvokeSnap';
export * from './snaps/useMetaMask.ts';
export * from './snaps/useRequest.ts';
export * from './snaps/useRequestSnap.ts';
export * from './snaps/useInvokeSnap.ts';
export * from './useWebzjsActions.ts';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defaultSnapOrigin } from '../config';
import { useRequest } from './useRequest';
import { defaultSnapOrigin } from '../../config';
import { useRequest } from './useRequest.ts';

export type InvokeSnapParams = {
method: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useEffect, useState } from 'react';

import { useMetaMaskContext } from './MetamaskContext';
import { useRequest } from './useRequest';
import { GetSnapsResponse } from '../types';
import { defaultSnapOrigin } from '../config';
import { useMetaMaskContext } from '../../context/MetamaskContext.tsx';
import { useRequest } from './useRequest.ts';
import { GetSnapsResponse } from '../../types';
import { defaultSnapOrigin } from '../../config';

/**
* A Hook to retrieve useful data from MetaMask.
* @returns The informations.
* @returns The information.
*/
export const useMetaMask = () => {
const { provider, setInstalledSnap, installedSnap } = useMetaMaskContext();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RequestArguments } from '@metamask/providers';

import { useMetaMaskContext } from './MetamaskContext';
import { useMetaMaskContext } from '../../context/MetamaskContext.tsx';

export type Request = (params: RequestArguments) => Promise<unknown | null>;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defaultSnapOrigin } from '../config';
import type { Snap } from '../types';
import { useMetaMaskContext } from './MetamaskContext';
import { useRequest } from './useRequest';
import { defaultSnapOrigin } from '../../config';
import type { Snap } from '../../types';
import { useMetaMaskContext } from '../../context/MetamaskContext.tsx';
import { useRequest } from './useRequest.ts';

/**
* Utility hook to wrap the `wallet_requestSnaps` method.
Expand Down
Loading

0 comments on commit 3631610

Please sign in to comment.