diff --git a/packages/demo-wallet/src/App/components/AddAccount.tsx b/packages/demo-wallet/src/App/components/AddAccount.tsx index 8086829..989a0ae 100644 --- a/packages/demo-wallet/src/App/components/AddAccount.tsx +++ b/packages/demo-wallet/src/App/components/AddAccount.tsx @@ -46,7 +46,7 @@ export function AddAccount() { const handleMetamaskImport = async (e: FormEvent) => { e.preventDefault(); const viewKey = (await invokeSnap({ method: 'getViewingKey' })) as string; - console.log(viewKey); + await addNewAccountFromUfvk(state, dispatch, viewKey, birthdayHeight); toast.success('Account imported successfully', { position: 'top-center', diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index ef05ff8..ef28124 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": "5kk+7sR8jY1fw3MjTH9dRIPkwAHHo0pFgfCseq+3rko=", + "shasum": "K0ntTcddlgOu8VTqTscszF/wJHveOkwBujyr7uvVf7g=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -27,7 +27,8 @@ { "coinType": 133 } - ] + ], + "snap_manageState": {} }, "platformVersion": "6.17.1", "manifestVersion": "0.1" diff --git a/packages/snap/src/index.tsx b/packages/snap/src/index.tsx index 1ab36c1..23ae8d1 100644 --- a/packages/snap/src/index.tsx +++ b/packages/snap/src/index.tsx @@ -1,8 +1,15 @@ 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'; +import { + OnRpcRequestHandler, + OnUserInputHandler, + UserInputEventType, +} from '@metamask/snaps-sdk'; +import { setBirthdayBlock } from './rpc/setBirthdayBlock'; +import { getSnapState } from './rpc/getSnapState'; +import { SetBirthdayBlockParams, SnapState } from './types'; +import { setSnapState } from './rpc/setSnapState'; let wasm: InitOutput; @@ -24,24 +31,29 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { case 'getViewingKey': return await getViewingKey(); case 'setBirthdayBlock': - const params = request.params as SetBirthdayBlockParams; - return await setBirthdayBlock(params); + const setBirthdayBlockParams = request.params as SetBirthdayBlockParams; + return await setBirthdayBlock(setBirthdayBlockParams); + case 'getSnapStete': + return await getSnapState(); + case 'setSnapStete': + const setSnapStateParams = request.params as unknown as SnapState; + return await setSnapState(setSnapStateParams); default: throw new Error('Method not found.'); } }; -export const onUserInput: OnUserInputHandler = async ({ id, event, context }) => { +export const onUserInput: OnUserInputHandler = async ({ id, event }) => { if (event.type === UserInputEventType.FormSubmitEvent) { switch (event.name) { - case "birthday-block-form": + case 'birthday-block-form': await snap.request({ - method: "snap_resolveInterface", + method: 'snap_resolveInterface', params: { id, - value: event.value - } - }) + value: event.value, + }, + }); default: break; diff --git a/packages/snap/src/rpc/getSnapState.tsx b/packages/snap/src/rpc/getSnapState.tsx new file mode 100644 index 0000000..fb47be8 --- /dev/null +++ b/packages/snap/src/rpc/getSnapState.tsx @@ -0,0 +1,12 @@ +import { Json } from '@metamask/snaps-sdk'; + +export async function getSnapState(): Promise { + const state = (await snap.request({ + method: 'snap_manageState', + params: { + operation: 'get', + }, + })) as unknown as Json; + + return state; +} diff --git a/packages/snap/src/rpc/setBirthdayBlock.tsx b/packages/snap/src/rpc/setBirthdayBlock.tsx index 3164ee3..82ab4e3 100644 --- a/packages/snap/src/rpc/setBirthdayBlock.tsx +++ b/packages/snap/src/rpc/setBirthdayBlock.tsx @@ -1,3 +1,4 @@ +/** @jsxImportSource @metamask/snaps-sdk */ import { Form, Box, @@ -6,17 +7,17 @@ import { Button, Text, Bold, - Divider + Divider, } from '@metamask/snaps-sdk/jsx'; +import { setSyncBlockHeight } from '../utils/setSyncBlockHeight'; type BirthdayBlockForm = { customBirthdayBlock: string | null }; -export type SetBirthdayBlockParams = { latestBlock?: number }; +type SetBirthdayBlockParams = { latestBlock: number }; export async function setBirthdayBlock({ latestBlock, -}: SetBirthdayBlockParams): Promise { - +}: SetBirthdayBlockParams): Promise { const interfaceId = await snap.request({ method: 'snap_createInterface', params: { @@ -29,12 +30,13 @@ export async function setBirthdayBlock({ seed you can enter optional birthday block of that Wallet. - - Syncing - proccess will start from that block. - + Syncing proccess will start from that block. - {!!latestBlock && Latest block: {latestBlock.toString()}} + {!!latestBlock && ( + + Latest block: {latestBlock.toString()} + + )} { + const state = (await snap.request({ + method: 'snap_manageState', + params: { + operation: 'update', + newState: newSnapState, + }, + })) as unknown as Json; + + return state; +} diff --git a/packages/snap/src/types.ts b/packages/snap/src/types.ts new file mode 100644 index 0000000..cba264a --- /dev/null +++ b/packages/snap/src/types.ts @@ -0,0 +1,7 @@ +import { Json } from "@metamask/snaps-sdk"; + +export type SetBirthdayBlockParams = { latestBlock: number }; + +export interface SnapState extends Record { + webWalletSyncStartBlock: string; +} diff --git a/packages/snap/src/utils/setSyncBlockHeight.ts b/packages/snap/src/utils/setSyncBlockHeight.ts new file mode 100644 index 0000000..73df5b6 --- /dev/null +++ b/packages/snap/src/utils/setSyncBlockHeight.ts @@ -0,0 +1,15 @@ +//NU5 (Network Upgrade 5) (Block 1,687,104, May 31, 2022) +const NU5_ACTIVATION = 1687104; + +export function setSyncBlockHeight( + userInputCreationBlock: string | null, + latestBlock: number, +) { + if(userInputCreationBlock === null) return latestBlock; + + const customBirthdayBlock = Number(userInputCreationBlock); + + const latestAcceptableSyncBlock = NU5_ACTIVATION; + + return customBirthdayBlock>latestAcceptableSyncBlock ? customBirthdayBlock : latestAcceptableSyncBlock +} diff --git a/packages/web-wallet/src/App.tsx b/packages/web-wallet/src/App.tsx index 12dc427..089ee91 100644 --- a/packages/web-wallet/src/App.tsx +++ b/packages/web-wallet/src/App.tsx @@ -1,22 +1,11 @@ import { useInterval } from 'usehooks-ts'; -import { Outlet, useLocation } from 'react-router-dom'; +import { Outlet } from 'react-router-dom'; import { RESCAN_INTERVAL } from './config/constants'; -import { useEffect } from 'react'; import { useWebZjsActions } from './hooks'; import Layout from './components/Layout/Layout'; function App() { const { triggerRescan } = useWebZjsActions(); - const location = useLocation(); - - useEffect(() => { - // Add custom background to home page - if (location.pathname === '/') { - document.body.classList.add('home-page', 'home-page-bg'); - } else { - document.body.classList.remove('home-page', 'home-page-bg'); - } - }, [location.pathname]); useInterval(() => { triggerRescan(); diff --git a/packages/web-wallet/src/components/BlockHeightCard/BlockHeightCard.tsx b/packages/web-wallet/src/components/BlockHeightCard/BlockHeightCard.tsx new file mode 100644 index 0000000..6d94797 --- /dev/null +++ b/packages/web-wallet/src/components/BlockHeightCard/BlockHeightCard.tsx @@ -0,0 +1,69 @@ +import { FC } from 'react'; +import { WebZjsState } from 'src/context/WebzjsContext'; + +export const BlockHeightCard: FC<{ + state: WebZjsState; + syncedFrom?: string; +}> = ({ state, syncedFrom }) => { + return ( +
+ {state.syncInProgress ? ( +
+ + + + + + Sync in progress... + +
+ ) : null} +
+ Chain Height +
+
+
+ {state.chainHeight ? '' + state.chainHeight : '?'} +
+
+
+ Synced Height +
+
+
+ {state.summary?.fully_scanned_height + ? state.summary?.fully_scanned_height + : '?'} +
+
+ {syncedFrom && ( + <> +
+ Sync Start Block +
+
+
+ {syncedFrom} +
+
+ + )} +
+ ); +}; diff --git a/packages/web-wallet/src/components/Header/Header.tsx b/packages/web-wallet/src/components/Header/Header.tsx index 7f339e0..895d924 100644 --- a/packages/web-wallet/src/components/Header/Header.tsx +++ b/packages/web-wallet/src/components/Header/Header.tsx @@ -7,7 +7,7 @@ const Header = (): React.JSX.Element => { const isHomePage = location.pathname === '/'; return ( -
+
void; setInstalledSnap: (snap: Snap | null) => void; setError: (error: Error) => void; }; @@ -17,6 +20,8 @@ const MetaMaskContext = createContext({ provider: null, installedSnap: null, error: null, + snapState: null, + setSnapState: () => {}, setInstalledSnap: () => {}, setError: () => {}, }); @@ -29,9 +34,12 @@ const MetaMaskContext = createContext({ * @returns JSX. */ export const MetaMaskProvider = ({ children }: { children: ReactNode }) => { + // const { getSnapState } = useGetSnapState(); + const [provider, setProvider] = useState(null); const [installedSnap, setInstalledSnap] = useState(null); const [error, setError] = useState(null); + const [snapState, setSnapState] = useState(null); useEffect(() => { getSnapsProvider().then(setProvider).catch(console.error); @@ -56,6 +64,8 @@ export const MetaMaskProvider = ({ children }: { children: ReactNode }) => { value={{ provider, error, + snapState, + setSnapState, setError, installedSnap, setInstalledSnap, diff --git a/packages/web-wallet/src/context/WebzjsContext.tsx b/packages/web-wallet/src/context/WebzjsContext.tsx index 5ff1510..ca2bb61 100644 --- a/packages/web-wallet/src/context/WebzjsContext.tsx +++ b/packages/web-wallet/src/context/WebzjsContext.tsx @@ -17,7 +17,7 @@ import { MAINNET_LIGHTWALLETD_PROXY } from '../config/constants'; import { Snap } from '../types'; import toast, { Toaster } from 'react-hot-toast'; -interface State { +export interface WebZjsState { webWallet: WebWallet | null; installedSnap: Snap | null; error: Error | null | string; @@ -37,7 +37,7 @@ type Action = | { type: 'set-sync-in-progress'; payload: boolean } | { type: 'set-loading'; payload: boolean }; -const initialState: State = { +const initialState: WebZjsState = { webWallet: null, installedSnap: null, error: null, @@ -48,7 +48,7 @@ const initialState: State = { loading: true, }; -function reducer(state: State, action: Action): State { +function reducer(state: WebZjsState, action: Action): WebZjsState { switch (action.type) { case 'set-web-wallet': return { ...state, webWallet: action.payload }; @@ -70,7 +70,7 @@ function reducer(state: State, action: Action): State { } interface WebZjsContextType { - state: State; + state: WebZjsState; dispatch: React.Dispatch; } diff --git a/packages/web-wallet/src/hooks/snaps/useGetSnapState.ts b/packages/web-wallet/src/hooks/snaps/useGetSnapState.ts new file mode 100644 index 0000000..99a701c --- /dev/null +++ b/packages/web-wallet/src/hooks/snaps/useGetSnapState.ts @@ -0,0 +1,19 @@ +import { useCallback } from 'react'; +import { useInvokeSnap } from './useInvokeSnap'; + +export interface SnapState { + webWalletSyncStartBlock: string; +} + +export const useGetSnapState = () => { + const invokeSnap = useInvokeSnap(); + + const getSnapState = useCallback(async () => { + const snapStateHome = (await invokeSnap({ + method: 'getSnapStete', + })) as unknown as SnapState; + return snapStateHome; + }, [invokeSnap]); + + return { getSnapState }; +}; diff --git a/packages/web-wallet/src/hooks/useWebzjsActions.ts b/packages/web-wallet/src/hooks/useWebzjsActions.ts index ec71199..ce335b7 100644 --- a/packages/web-wallet/src/hooks/useWebzjsActions.ts +++ b/packages/web-wallet/src/hooks/useWebzjsActions.ts @@ -25,15 +25,20 @@ export function useWebZjsActions(): WebzjsActions { try { const accountIndex = state.activeAccount ?? 0; + if(!state.webWallet) return + const unifiedAddress = - await state.webWallet!.get_current_address(accountIndex); + await state.webWallet.get_current_address(accountIndex); + const transparentAddress = - await state.webWallet!.get_current_address_transparent(accountIndex); + await state.webWallet.get_current_address_transparent(accountIndex); return { unifiedAddress, transparentAddress, }; + + } catch (error) { dispatch({ type: 'set-error', @@ -88,33 +93,39 @@ export function useWebZjsActions(): WebzjsActions { try { await requestSnap(); - const latestBlockBigInt = await state.webWallet?.get_latest_block(); + if (state.webWallet === null) { + dispatch({ + type: 'set-error', + payload: new Error('Wallet not initialized'), + }); + return; + } + + const latestBlockBigInt = await state.webWallet.get_latest_block(); const latestBlock = Number(latestBlockBigInt); - const customBirthdayBlock = (await invokeSnap({ + let birthdayBlock = (await invokeSnap({ method: 'setBirthdayBlock', params: { latestBlock }, - })) as string | null; - - const viewingKey = (await invokeSnap({ - method: 'getViewingKey', - })) as string; + })) as number | null; - const creationBlockHeight = Number( - customBirthdayBlock !== null ? customBirthdayBlock : latestBlock, - ); - if (state.webWallet === null) { - dispatch({ - type: 'set-error', - payload: new Error('Wallet not initialized'), + // in case user pressed "Close" instead of "Continue to wallet" on prompt, still allow account creation with latest block + if(birthdayBlock === null) { + await invokeSnap({ + method: 'setSnapStete', + params: { webWalletSyncStartBlock: latestBlock }, }); - return; + birthdayBlock = latestBlock } + const viewingKey = (await invokeSnap({ + method: 'getViewingKey', + })) as string; + const account_id = await state.webWallet.create_account_ufvk( viewingKey, - creationBlockHeight, + birthdayBlock, ); dispatch({ type: 'set-active-account', payload: account_id }); @@ -164,10 +175,7 @@ export function useWebZjsActions(): WebzjsActions { return; } if (state.syncInProgress) { - dispatch({ - type: 'set-error', - payload: new Error('Sync already in progress'), - }); + return; } diff --git a/packages/web-wallet/src/pages/AccountSummary.tsx b/packages/web-wallet/src/pages/AccountSummary.tsx index 335b515..d233788 100644 --- a/packages/web-wallet/src/pages/AccountSummary.tsx +++ b/packages/web-wallet/src/pages/AccountSummary.tsx @@ -2,6 +2,9 @@ import React from 'react'; import { zatsToZec } from '../utils'; import { CoinsSvg, ShieldDividedSvg, ShieldSvg } from '../assets'; import useBalance from '../hooks/useBalance'; +import { useWebZjsContext } from 'src/context/WebzjsContext'; +import { BlockHeightCard } from 'src/components/BlockHeightCard/BlockHeightCard'; +import { useMetaMaskContext } from 'src/context/MetamaskContext'; interface BalanceCard { name: string; @@ -11,6 +14,8 @@ interface BalanceCard { function AccountSummary() { const { totalBalance, unshieldedBalance, shieldedBalance } = useBalance(); + const { state } = useWebZjsContext(); + const { snapState } = useMetaMaskContext(); const BalanceCards: BalanceCard[] = [ { @@ -59,10 +64,16 @@ function AccountSummary() {
{BalanceCards.map((card) => renderBalanceCard(card))}
+ ); } diff --git a/packages/web-wallet/src/pages/Home.tsx b/packages/web-wallet/src/pages/Home.tsx index 25ad964..316f409 100644 --- a/packages/web-wallet/src/pages/Home.tsx +++ b/packages/web-wallet/src/pages/Home.tsx @@ -2,16 +2,17 @@ import React, { useEffect } from 'react'; import { ZcashYellowPNG, FormTransferSvg, MetaMaskLogoPNG } from '../assets'; import { useNavigate } from 'react-router-dom'; import { useWebZjsContext } from '../context/WebzjsContext'; -import { - useMetaMask, - useWebZjsActions, -} from '../hooks'; +import { useMetaMask, useWebZjsActions } from '../hooks'; +import { useMetaMaskContext } from 'src/context/MetamaskContext'; +import { useGetSnapState } from 'src/hooks/snaps/useGetSnapState'; const Home: React.FC = () => { const navigate = useNavigate(); const { state } = useWebZjsContext(); const { getAccountData, connectWebZjsSnap } = useWebZjsActions(); const { installedSnap, isFlask } = useMetaMask(); + const { getSnapState } = useGetSnapState(); + const { setSnapState } = useMetaMaskContext(); const handleConnectButton: React.MouseEventHandler< HTMLButtonElement @@ -25,14 +26,17 @@ const Home: React.FC = () => { const homeReload = async () => { const accountData = await getAccountData(); - if (accountData?.unifiedAddress) navigate('/dashboard/account-summary') - } + const snapState = await getSnapState(); + setSnapState(snapState); + + if (accountData?.unifiedAddress) navigate('/dashboard/account-summary'); + }; homeReload(); }; - }, [installedSnap, navigate, getAccountData, state.activeAccount]); + }, [navigate, getAccountData, state.activeAccount]); return ( -
+
diff --git a/packages/web-wallet/src/styles/index.css b/packages/web-wallet/src/styles/index.css index 56814b7..3b7e9e6 100644 --- a/packages/web-wallet/src/styles/index.css +++ b/packages/web-wallet/src/styles/index.css @@ -44,11 +44,11 @@ } body { + background: linear-gradient(#fff 0%, #bcefef 187.66%); position: relative; - overflow: hidden; background: #fafafa url('../assets/diamond-bg.png') no-repeat center center fixed; - + background-size: 400%; &.home-page-bg { background: linear-gradient(180deg, #fff 0%, #bcefef 187.66%); diff --git a/yarn.lock b/yarn.lock index f1ef8b1..d1fccef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13546,15 +13546,15 @@ __metadata: linkType: hard "react-hot-toast@npm:^2.5.1": - version: 2.5.1 - resolution: "react-hot-toast@npm:2.5.1" + version: 2.5.2 + resolution: "react-hot-toast@npm:2.5.2" dependencies: csstype: "npm:^3.1.3" goober: "npm:^2.1.16" peerDependencies: react: ">=16" react-dom: ">=16" - checksum: 10c0/179efb089276fd20d246fa17e8282437f28e838561e83765d33ed8c03bc0ecff642243003aa08ccdf839616f9c00b3fad2e617de995ea2cd6bd6219325791fc5 + checksum: 10c0/2db00b491bd1d2ec3ed04754a32b640272cdb32985f95e32ef033fcc996588a93e67d9764b005f9f391ee2bad5d0f9f343ba290dca82fcd439e08e708a161d76 languageName: node linkType: hard