diff --git a/packages/snap/package.json b/packages/snap/package.json index a458652..366fda0 100644 --- a/packages/snap/package.json +++ b/packages/snap/package.json @@ -27,7 +27,7 @@ "test": "jest" }, "dependencies": { - "@metamask/snaps-sdk": "^6.9.0", + "@metamask/snaps-sdk": "^6.17.1", "@webzjs/webz-keys": "workspace:^", "buffer": "^6.0.3", "fork-ts-checker-webpack-plugin": "^9.0.2" @@ -41,6 +41,8 @@ "@metamask/eslint-config-typescript": "^14.0.0", "@metamask/snaps-cli": "^6.5.2", "@metamask/snaps-jest": "^8.8.1", + "@types/react": "18.2.4", + "@types/react-dom ": "18.2.4", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^5.42.1", "eslint": "^9.14.0", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 7f67b59..ef05ff8 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/template-snap-monorepo.git" }, "source": { - "shasum": "SA1fH7ay1ietGUsIELeD6rgpMuXu/rusR65jSdjCBrk=", + "shasum": "5kk+7sR8jY1fw3MjTH9dRIPkwAHHo0pFgfCseq+3rko=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -29,5 +29,6 @@ } ] }, + "platformVersion": "6.17.1", "manifestVersion": "0.1" } diff --git a/packages/snap/src/index.tsx b/packages/snap/src/index.tsx index 7c9aab8..1ab36c1 100644 --- a/packages/snap/src/index.tsx +++ b/packages/snap/src/index.tsx @@ -1,6 +1,8 @@ import { getViewingKey } from './rpc/getViewingKey'; import { InitOutput } from '@webzjs/webz-keys'; import { initialiseWasm } from './utils/initialiseWasm'; +import { OnRpcRequestHandler, OnUserInputHandler, UserInputEventType } from '@metamask/snaps-sdk'; +import { setBirthdayBlock, SetBirthdayBlockParams } from './rpc/setBirthdayBlock'; let wasm: InitOutput; @@ -13,11 +15,7 @@ let wasm: InitOutput; * @returns The ViewingKey * @throws If the request method is not valid for this snap. */ -export const onRpcRequest: ({ - request, -}: { - request: any; -}) => Promise = async ({ request }) => { +export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { if (!wasm) { wasm = initialiseWasm(); } @@ -25,7 +23,28 @@ export const onRpcRequest: ({ switch (request.method) { case 'getViewingKey': return await getViewingKey(); + case 'setBirthdayBlock': + const params = request.params as SetBirthdayBlockParams; + return await setBirthdayBlock(params); default: throw new Error('Method not found.'); } }; + +export const onUserInput: OnUserInputHandler = async ({ id, event, context }) => { + if (event.type === UserInputEventType.FormSubmitEvent) { + switch (event.name) { + case "birthday-block-form": + await snap.request({ + method: "snap_resolveInterface", + params: { + id, + value: event.value + } + }) + + default: + break; + } + } +}; diff --git a/packages/snap/src/rpc/getViewingKey.ts b/packages/snap/src/rpc/getViewingKey.tsx similarity index 74% rename from packages/snap/src/rpc/getViewingKey.ts rename to packages/snap/src/rpc/getViewingKey.tsx index 1e89b0e..a31e646 100644 --- a/packages/snap/src/rpc/getViewingKey.ts +++ b/packages/snap/src/rpc/getViewingKey.tsx @@ -12,10 +12,10 @@ export async function getViewingKey( const seed = await getSeed(); // Generate the UnifiedSpendingKey and obtain the Viewing Key - let spendingKey = new UnifiedSpendingKey(network, seed, accountIndex); - let viewingKey = spendingKey.to_unified_full_viewing_key(); + const spendingKey = new UnifiedSpendingKey(network, seed, accountIndex); + const viewingKey = spendingKey.to_unified_full_viewing_key().encode(network); - return viewingKey.encode(network); + return viewingKey } catch (error) { console.error('Error generating Viewing Key:', error); throw new Error('Failed to generate Viewing Key'); diff --git a/packages/snap/src/rpc/setBirthdayBlock.tsx b/packages/snap/src/rpc/setBirthdayBlock.tsx new file mode 100644 index 0000000..3164ee3 --- /dev/null +++ b/packages/snap/src/rpc/setBirthdayBlock.tsx @@ -0,0 +1,63 @@ +import { + Form, + Box, + Heading, + Input, + Button, + Text, + Bold, + Divider +} from '@metamask/snaps-sdk/jsx'; + +type BirthdayBlockForm = { customBirthdayBlock: string | null }; + +export type SetBirthdayBlockParams = { latestBlock?: number }; + +export async function setBirthdayBlock({ + latestBlock, +}: SetBirthdayBlockParams): Promise { + + const interfaceId = await snap.request({ + method: 'snap_createInterface', + params: { + ui: ( +
+ + Optional syncing block height + + If you alerady created Zcash Web Wallet account with this Metamask + seed you can enter optional birthday block of that Wallet. + + + + Syncing + proccess will start from that block. + + + {!!latestBlock && Latest block: {latestBlock.toString()}} + + + +
+ ), + }, + }); + + const { customBirthdayBlock } = (await snap.request({ + method: 'snap_dialog', + params: { + id: interfaceId, + }, + })) as BirthdayBlockForm; + + + return customBirthdayBlock; +} diff --git a/packages/web-wallet/package.json b/packages/web-wallet/package.json index 4f5f3c2..5d9de18 100644 --- a/packages/web-wallet/package.json +++ b/packages/web-wallet/package.json @@ -45,6 +45,7 @@ "globals": "^15.11.0", "parcel": "^2.13.3", "path": "^0.12.7", + "svgo": "^3", "tailwindcss": "^4.0.0", "typescript": "^5.7.3", "typescript-eslint": "^8.11.0" diff --git a/packages/web-wallet/src/context/WebzjsContext.tsx b/packages/web-wallet/src/context/WebzjsContext.tsx index e3f47a5..7763f12 100644 --- a/packages/web-wallet/src/context/WebzjsContext.tsx +++ b/packages/web-wallet/src/context/WebzjsContext.tsx @@ -22,7 +22,7 @@ interface State { error: Error | null | string; summary?: WalletSummary; chainHeight?: bigint; - activeAccount?: number; + activeAccount?: number | null; syncInProgress: boolean; loading: boolean; } @@ -42,7 +42,7 @@ const initialState: State = { error: null, summary: undefined, chainHeight: undefined, - activeAccount: 0, + activeAccount: null, syncInProgress: false, loading: true, }; diff --git a/packages/web-wallet/src/hooks/useWebzjsActions.ts b/packages/web-wallet/src/hooks/useWebzjsActions.ts index 21e1ca8..ec71199 100644 --- a/packages/web-wallet/src/hooks/useWebzjsActions.ts +++ b/packages/web-wallet/src/hooks/useWebzjsActions.ts @@ -1,22 +1,25 @@ import { set } from 'idb-keyval'; import { useCallback } from 'react'; import { useWebZjsContext } from '../context/WebzjsContext'; +import { useMetaMask } from './snaps/useMetaMask'; +import { useInvokeSnap } from './snaps/useInvokeSnap'; +import { useRequestSnap } from './snaps/useRequestSnap'; interface WebzjsActions { - addNewAccountFromUfvk: ( - ufvk: string, - birthdayHeight: number, - ) => Promise; getAccountData: () => Promise< { unifiedAddress: string; transparentAddress: string } | undefined >; triggerRescan: () => Promise; flushDbToStore: () => Promise; syncStateWithWallet: () => Promise; + connectWebZjsSnap: () => Promise; } export function useWebZjsActions(): WebzjsActions { const { state, dispatch } = useWebZjsContext(); + const { installedSnap } = useMetaMask(); + const invokeSnap = useInvokeSnap(); + const requestSnap = useRequestSnap(); const getAccountData = useCallback(async () => { try { @@ -81,23 +84,70 @@ export function useWebZjsActions(): WebzjsActions { } }, [state.webWallet, dispatch]); - const addNewAccountFromUfvk = useCallback( - async (ufvk: string, birthdayHeight: number) => { - const account_id = - (await state.webWallet?.create_account_ufvk(ufvk, birthdayHeight)) || 0; - dispatch({ type: 'set-active-account', payload: account_id }); + const connectWebZjsSnap = useCallback(async () => { + try { + await requestSnap(); + + const latestBlockBigInt = await state.webWallet?.get_latest_block(); + const latestBlock = Number(latestBlockBigInt); + + const customBirthdayBlock = (await invokeSnap({ + method: 'setBirthdayBlock', + params: { latestBlock }, + })) as string | null; + const viewingKey = (await invokeSnap({ + method: 'getViewingKey', + })) as string; + + const creationBlockHeight = Number( + customBirthdayBlock !== null ? customBirthdayBlock : latestBlock, + ); + + if (state.webWallet === null) { + dispatch({ + type: 'set-error', + payload: new Error('Wallet not initialized'), + }); + return; + } + + const account_id = await state.webWallet.create_account_ufvk( + viewingKey, + creationBlockHeight, + ); + + dispatch({ type: 'set-active-account', payload: account_id }); if (state.webWallet) { const summary = await state.webWallet.get_wallet_summary(); console.log('account_balances', summary?.account_balances.length); } await syncStateWithWallet(); + await flushDbToStore(); - }, - [dispatch, flushDbToStore, state.webWallet, syncStateWithWallet], - ); + } catch (error) { + console.error(error); + dispatch({ + type: 'set-error', + payload: new Error( + 'Failed to connect to MetaMask Snap and create account', + ), + }); + throw error; + } + }, [ + state.webWallet, + invokeSnap, + dispatch, + syncStateWithWallet, + flushDbToStore, + ]); const triggerRescan = useCallback(async () => { + if (!installedSnap) { + return; + } + if (state.loading) { dispatch({ type: 'set-error', payload: new Error('App not yet loaded') }); return; @@ -145,9 +195,9 @@ export function useWebZjsActions(): WebzjsActions { return { getAccountData, - addNewAccountFromUfvk, triggerRescan, flushDbToStore, syncStateWithWallet, + connectWebZjsSnap, }; } diff --git a/packages/web-wallet/src/pages/Home.tsx b/packages/web-wallet/src/pages/Home.tsx index c319e32..bed5d54 100644 --- a/packages/web-wallet/src/pages/Home.tsx +++ b/packages/web-wallet/src/pages/Home.tsx @@ -1,49 +1,35 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { ZcashYellowPNG, FormTransferSvg, MetaMaskLogoPNG } from '../assets'; import { useNavigate } from 'react-router-dom'; import { useWebZjsContext } from '../context/WebzjsContext'; import { - useInvokeSnap, - useRequestSnap, useMetaMask, useWebZjsActions, } from '../hooks'; const Home: React.FC = () => { - const [birthdayHeight, setBirthdayHeight] = useState(0); const navigate = useNavigate(); const { state } = useWebZjsContext(); - const { flushDbToStore, addNewAccountFromUfvk } = useWebZjsActions(); - const invokeSnap = useInvokeSnap(); + const { getAccountData, connectWebZjsSnap } = useWebZjsActions(); const { installedSnap, isFlask } = useMetaMask(); - const requestSnap = useRequestSnap(); - useEffect(() => { - const fetchBirthday = async () => { - const birthday = await state.webWallet?.get_latest_block(); - setBirthdayHeight(Number(birthday) || 0); - }; - fetchBirthday(); - }, [state]); - - const handleRequestSnapAndGetViewingKey: React.MouseEventHandler< + const handleConnectButton: React.MouseEventHandler< HTMLButtonElement > = async (e) => { e.preventDefault(); - await requestSnap(); - - const viewKey = (await invokeSnap({ method: 'getViewingKey' })) as string; - console.log(viewKey); - - await addNewAccountFromUfvk(viewKey, birthdayHeight); - setBirthdayHeight(0); - - await flushDbToStore(); + await connectWebZjsSnap(); }; useEffect(() => { - if (installedSnap) navigate('/dashboard/account-summary'); - }, [installedSnap, navigate]); + if (installedSnap) { + const homeReload = async () => { + const accountData = await getAccountData(); + + if (accountData?.unifiedAddress) navigate('/dashboard/account-summary') + } + homeReload(); + }; + }, [installedSnap, navigate, getAccountData, state.activeAccount]); return (
@@ -63,7 +49,7 @@ const Home: React.FC = () => {