From 76a58151091788255ef1eb6e934baa07ca1bfef0 Mon Sep 17 00:00:00 2001 From: lendihop Date: Tue, 14 Nov 2023 17:33:38 +0100 Subject: [PATCH 1/8] evm send/receive with confirmation through background script, tokens list --- package.json | 4 + src/app/PageRouter.tsx | 2 + .../PageLayout/ConfirmationOverlay.tsx | 23 +- .../Home/OtherComponents/Tokens/Tokens.tsx | 55 ++++- src/app/pages/TokenPage/ReceiveTab.tsx | 72 ++++++ src/app/pages/TokenPage/SendTab.tsx | 132 +++++++++++ src/app/pages/TokenPage/TokenPage.tsx | 150 ++++++++++++ src/app/templates/InternalEvmConfirmation.tsx | 103 ++++++++ src/lib/temple/back/actions.ts | 80 +++++++ src/lib/temple/back/main.ts | 17 ++ src/lib/temple/back/vault/index.ts | 104 +++++++- src/lib/temple/front/client.ts | 12 + src/lib/temple/types.ts | 45 +++- src/newChains/bitcoin.ts | 45 ++++ src/newChains/erc20abi.json | 222 ++++++++++++++++++ src/newChains/evm.ts | 12 + yarn.lock | 113 ++++++++- 17 files changed, 1173 insertions(+), 18 deletions(-) create mode 100644 src/app/pages/TokenPage/ReceiveTab.tsx create mode 100644 src/app/pages/TokenPage/SendTab.tsx create mode 100644 src/app/pages/TokenPage/TokenPage.tsx create mode 100644 src/app/templates/InternalEvmConfirmation.tsx create mode 100644 src/newChains/bitcoin.ts create mode 100644 src/newChains/erc20abi.json create mode 100644 src/newChains/evm.ts diff --git a/package.json b/package.json index 6b2f2d9cc..c7f384841 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@apollo/client": "^3.7.2", "@babel/core": "7.19.3", "@babel/runtime": "^7.19.0", + "@bitcoinerlab/secp256k1": "^1.0.5", "@dicebear/avatars": "4.2.5", "@dicebear/avatars-bottts-sprites": "4.2.5", "@dicebear/avatars-jdenticon-sprites": "4.2.5", @@ -99,7 +100,9 @@ "axios": "0.26.1", "babel-loader": "8.2.5", "bignumber.js": "9.1.0", + "bip32": "^4.0.0", "bip39": "3.0.4", + "bitcoinjs-lib": "^6.1.5", "bs58check": "2.1.2", "buffer": "5.6.0", "clean-webpack-plugin": "4.0.0", @@ -125,6 +128,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4", "eslint-webpack-plugin": "^3.2.0", + "ethers": "^6.8.1", "fast-glob": "^3.2.12", "file-loader": "6.2.0", "firebase": "^10.0.0", diff --git a/src/app/PageRouter.tsx b/src/app/PageRouter.tsx index 41aaa485f..431d31c2f 100644 --- a/src/app/PageRouter.tsx +++ b/src/app/PageRouter.tsx @@ -30,6 +30,7 @@ import { Notifications, NotificationsItem } from 'lib/notifications'; import { useTempleClient } from 'lib/temple/front'; import * as Woozie from 'lib/woozie'; +import { TokenPage } from './pages/TokenPage/TokenPage'; import { WithDataLoading } from './WithDataLoading'; interface RouteContext { @@ -75,6 +76,7 @@ const ROUTE_MAP = Woozie.createMap([ ['/loading', (_p, ctx) => (ctx.ready ? : )], ['/', (_p, ctx) => (ctx.ready ? : )], ['/explore/:assetSlug?', onlyReady(({ assetSlug }) => )], + ['/nonTezosTokenPage/:tokenAddress?', onlyReady(({ tokenAddress }) => )], ['/create-wallet', onlyNotReady(() => )], ['/create-account', onlyReady(() => )], ['/import-account/:tabSlug?', onlyReady(({ tabSlug }) => )], diff --git a/src/app/layouts/PageLayout/ConfirmationOverlay.tsx b/src/app/layouts/PageLayout/ConfirmationOverlay.tsx index 52ba8ee0c..6a29b468c 100644 --- a/src/app/layouts/PageLayout/ConfirmationOverlay.tsx +++ b/src/app/layouts/PageLayout/ConfirmationOverlay.tsx @@ -8,8 +8,10 @@ import InternalConfirmation from 'app/templates/InternalConfirmation'; import { useTempleClient } from 'lib/temple/front'; import Portal from 'lib/ui/Portal'; +import { InternalEvmConfirmation } from '../../templates/InternalEvmConfirmation'; + const ConfirmationOverlay: FC = () => { - const { confirmation, resetConfirmation, confirmInternal } = useTempleClient(); + const { confirmation, resetConfirmation, confirmInternal, confirmEvmInternal } = useTempleClient(); const displayed = Boolean(confirmation); useLayoutEffect(() => { @@ -36,6 +38,16 @@ const ConfirmationOverlay: FC = () => { [confirmation, confirmInternal, resetConfirmation] ); + const handleEvmConfirm = useCallback( + async (confirmed: boolean) => { + if (confirmation) { + await confirmEvmInternal(confirmation.id, confirmed); + } + resetConfirmation(); + }, + [confirmation, confirmEvmInternal, resetConfirmation] + ); + return ( <> {displayed && } @@ -52,13 +64,20 @@ const ConfirmationOverlay: FC = () => { unmountOnExit >
- {confirmation && ( + {confirmation && confirmation.payload.type !== 'evm_operations' && ( )} + {confirmation && confirmation.payload.type === 'evm_operations' && ( + + )}
diff --git a/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx b/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx index ad5d8d90a..5e7aa5abf 100644 --- a/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx +++ b/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx @@ -30,6 +30,9 @@ import { useLocalStorage } from 'lib/ui/local-storage'; import Popper, { PopperRenderProps } from 'lib/ui/Popper'; import { Link, navigate } from 'lib/woozie'; +import { Image } from '../../../../../lib/ui/Image'; +import Spinner from '../../../../atoms/Spinner/Spinner'; +import { getEvmTokensWithBalances, NonTezosToken } from '../../../TokenPage/TokenPage'; import { HomeSelectors } from '../../Home.selectors'; import { AssetsSelectors } from '../Assets.selectors'; import { AcceptAdsBanner } from './AcceptAdsBanner'; @@ -44,10 +47,27 @@ export const TokensTab: FC = () => { const chainId = useChainId(true)!; const balances = useBalancesWithDecimals(); - const { publicKeyHash } = useAccount(); + const { publicKeyHash, evmPublicKeyHash } = useAccount(); const { isSyncing } = useSyncTokens(); const { popup } = useAppEnv(); + const [nonTezosTokens, setNonTezosTokens] = useState([]); + + const getAllNonTezosTokens = async () => { + if (!evmPublicKeyHash) { + setNonTezosTokens([]); + return; + } + + const evmTokens = await getEvmTokensWithBalances(evmPublicKeyHash); + + setNonTezosTokens(evmTokens); + }; + + useEffect(() => { + getAllNonTezosTokens(); + }, [evmPublicKeyHash]); + const { data: tokens = [] } = useDisplayedFungibleTokens(chainId, publicKeyHash); const [isZeroBalancesHidden, setIsZeroBalancesHidden] = useLocalStorage(LOCAL_STORAGE_TOGGLE_KEY, false); @@ -183,6 +203,39 @@ export const TokensTab: FC = () => { {isEnabledAdsBanner && } + {nonTezosTokens.length !== 0 ? ( + nonTezosTokens.map((token: NonTezosToken) => ( + +
+ {token.name} +
+
+ {token.symbol} + {' (' + token.chainName + ')'} +
+
{token.name}
+
+
+
+ {(Number(token.balance) / 10 ** token.decimals).toFixed(6) + ' ' + token.symbol} +
+ + )) + ) : ( +
+
+ +
+
+ )} + {filteredAssets.length === 0 ? (

diff --git a/src/app/pages/TokenPage/ReceiveTab.tsx b/src/app/pages/TokenPage/ReceiveTab.tsx new file mode 100644 index 000000000..0b427e98f --- /dev/null +++ b/src/app/pages/TokenPage/ReceiveTab.tsx @@ -0,0 +1,72 @@ +import React, { FC } from 'react'; + +import classNames from 'clsx'; + +import { ReactComponent as CopyIcon } from 'app/icons/copy.svg'; + +import { setTestID } from '../../../lib/analytics'; +import { T, t } from '../../../lib/i18n'; +import useCopyToClipboard from '../../../lib/ui/useCopyToClipboard'; +import { FormField } from '../../atoms'; +import Spinner from '../../atoms/Spinner/Spinner'; +import { ReceiveSelectors } from '../Receive/Receive.selectors'; + +interface Props { + address?: string; +} + +export const ReceiveTab: FC = ({ address }) => { + const { fieldRef, copy, copied } = useCopyToClipboard(); + + if (!address) { + return ( +

+
+ +
+
+ ); + } + + return ( +
+ + + +
+ ); +}; diff --git a/src/app/pages/TokenPage/SendTab.tsx b/src/app/pages/TokenPage/SendTab.tsx new file mode 100644 index 000000000..a55fa9ab9 --- /dev/null +++ b/src/app/pages/TokenPage/SendTab.tsx @@ -0,0 +1,132 @@ +import React, { FC, useCallback, useMemo, useState } from 'react'; + +import { nanoid } from 'nanoid'; +import { Controller, useForm } from 'react-hook-form'; + +import { T, t } from '../../../lib/i18n'; +import { assertResponse, request, useTempleClient } from '../../../lib/temple/front'; +import { TempleMessageType } from '../../../lib/temple/types'; +import { FormSubmitButton, HashChip, NoSpaceField } from '../../atoms'; +import AssetField from '../../atoms/AssetField'; +import Spinner from '../../atoms/Spinner/Spinner'; +import { SendFormSelectors } from '../../templates/SendForm/selectors'; +import { NonTezosToken } from './TokenPage'; + +interface FormData { + to: string; + amount: string; +} + +interface Props { + isBitcoin: boolean; + accountPkh?: string; + token?: NonTezosToken; +} + +export const SendTab: FC = ({ token, isBitcoin, accountPkh }) => { + const [isProcessing, setProcessing] = useState(false); + const [hash, setHash] = useState(); + const { confirmationIdRef } = useTempleClient(); + + const { watch, handleSubmit, control, setValue, formState } = useForm({ + mode: 'onChange' + }); + + const toValue = watch('to'); + + const cleanToField = useCallback(() => { + setValue('to', ''); + }, [setValue]); + + const rpcUrl = useMemo(() => { + switch (token?.chainName) { + case 'Ethereum Sepolia': + return 'https://ethereum-sepolia.publicnode.com'; + case 'Polygon Mumbai': + return 'https://polygon-mumbai-bor.publicnode.com'; + case 'BSC Testnet': + return 'https://bsc-testnet.publicnode.com'; + default: + return ''; + } + }, [token?.chainName]); + + const onSubmit = async ({ to, amount }: FormData) => { + if (!accountPkh || !token) return; + + setProcessing(true); + + const id = nanoid(); + confirmationIdRef.current = id; + + const res = await request({ + type: TempleMessageType.EvmOperationsRequest, + id, + sourcePkh: accountPkh, + networkRpc: rpcUrl, + toAddress: to, + token, + amount + }); + assertResponse(res.type === TempleMessageType.EvmOperationsResponse); + + setHash(res.txHash); + setProcessing(false); + }; + + if (!token) { + return ( +
+
+ +
+
+ ); + } + + return ( +
+ } + control={control} + onChange={([v]) => v} + textarea + rows={2} + placeholder={`e.g. ${isBitcoin ? 'tb1qufwmq' : '0xe03CE86'}...`} + cleanable={Boolean(toValue)} + onClean={cleanToField} + id="send-to" + label={t('recipient')} + labelDescription={`${isBitcoin ? 'Bitcoin' : 'EVM'} address to send funds to`} + style={{ + resize: 'none' + }} + containerClassName="mb-4" + testID={SendFormSelectors.recipientInput} + /> + } + control={control} + onChange={([v]) => v} + id="send-amount" + assetSymbol={token.symbol} + assetDecimals={token.decimals} + label={t('amount')} + placeholder={t('amountPlaceholder')} + containerClassName="mb-4" + /> + + + + + + {hash && ( + + Tx hash: + + )} + + ); +}; diff --git a/src/app/pages/TokenPage/TokenPage.tsx b/src/app/pages/TokenPage/TokenPage.tsx new file mode 100644 index 000000000..2a8e31f00 --- /dev/null +++ b/src/app/pages/TokenPage/TokenPage.tsx @@ -0,0 +1,150 @@ +import React, { FC, useEffect, useMemo, useState } from 'react'; + +import { isDefined } from '@rnw-community/shared'; +import axios from 'axios'; +import clsx from 'clsx'; + +import { TID } from '../../../lib/i18n'; +import { useAccount } from '../../../lib/temple/front'; +import { Image } from '../../../lib/ui/Image'; +import { getAllBitcoinAddressesForCurrentMnemonic } from '../../../newChains/bitcoin'; +import Spinner from '../../atoms/Spinner/Spinner'; +import { useTabSlug } from '../../atoms/useTabSlug'; +import { useAppEnv } from '../../env'; +import PageLayout from '../../layouts/PageLayout'; +import { TabsBar } from '../../templates/TabBar'; +import EditableTitle from '../Home/OtherComponents/EditableTitle'; +import { ReceiveTab } from './ReceiveTab'; +import { SendTab } from './SendTab'; + +export interface NonTezosToken { + token_address: string; + symbol: string; + name: string; + logo: string; + thumbnail: string; + decimals: number; + balance: string; + chainName: string; + nativeToken: boolean; + possible_spam: boolean; +} + +interface TabData { + name: string; + titleI18nKey: TID; + Component: FC; +} + +interface Props { + tokenAddress: string | null; +} +export const TokenPage: FC = ({ tokenAddress }) => { + const { fullPage } = useAppEnv(); + const tabSlug = useTabSlug(); + const { evmPublicKeyHash } = useAccount(); + const isBitcoin = tokenAddress === 'btc'; + + const [nonTezosTokens, setNonTezosTokens] = useState([]); + + const getAllNonTezosTokens = async () => { + if (!evmPublicKeyHash) { + setNonTezosTokens([]); + return; + } + + const evmTokens = await getEvmTokensWithBalances(evmPublicKeyHash); + + setNonTezosTokens(evmTokens); + }; + + useEffect(() => { + getAllNonTezosTokens(); + }, []); + + const currentToken = useMemo(() => { + return nonTezosTokens.find(token => tokenAddress === token.token_address); + }, [nonTezosTokens, tokenAddress]); + + const tabs: TabData[] = useMemo( + () => [ + { + name: 'Receive', + titleI18nKey: 'receive', + Component: () => + }, + { + name: 'Send', + titleI18nKey: 'send', + Component: () => + } + ], + [currentToken, evmPublicKeyHash, isBitcoin] + ); + + const { name, Component } = useMemo(() => { + const tab = tabSlug ? tabs.find(currentTab => currentTab.name === tabSlug) : null; + return tab ?? tabs[0]; + }, [tabSlug, tabs]); + + return ( + + {isBitcoin ? 'Bitcoin' : isDefined(currentToken) ? currentToken.name : 'Token page'} + + } + > + {fullPage && ( +
+ +
+
+ )} +
+ {isDefined(currentToken) ? ( +
+
+ {currentToken.name} +
+
+ {currentToken.symbol} + {' (' + currentToken.chainName + ')'} +
+
{currentToken.name}
+
+
+
+ {(Number(currentToken.balance) / 10 ** currentToken.decimals).toFixed(6) + ' ' + currentToken.symbol} +
+
+ ) : ( +
+
+ +
+
+ )} +
+ +
+ + +
+
+ ); +}; + +export const getBitcoinWithBalance = async () => { + const addresses = getAllBitcoinAddressesForCurrentMnemonic().join(';'); + + const response = await axios.get(`http://localhost:3000/api/bitcoin?addresses=${addresses}`); + + return response.data; +}; + +export const getEvmTokensWithBalances = async (pkh: string) => { + const response = await axios.get(`http://localhost:3000/api/evm-tokens?address=${pkh}`); + + return response.data; +}; diff --git a/src/app/templates/InternalEvmConfirmation.tsx b/src/app/templates/InternalEvmConfirmation.tsx new file mode 100644 index 000000000..b08471e3d --- /dev/null +++ b/src/app/templates/InternalEvmConfirmation.tsx @@ -0,0 +1,103 @@ +import React, { FC, useCallback } from 'react'; + +import classNames from 'clsx'; + +import { FormSubmitButton, FormSecondaryButton } from 'app/atoms'; +import Logo from 'app/atoms/Logo'; +import SubTitle from 'app/atoms/SubTitle'; +import { useAppEnv } from 'app/env'; +import { T, t } from 'lib/i18n'; +import { TempleEvmOpsConfirmationPayload } from 'lib/temple/types'; + +import { useSafeState } from '../../lib/ui/hooks'; +import { delay } from '../../lib/utils'; +import NetworkBanner from './NetworkBanner'; + +interface Props { + payload: TempleEvmOpsConfirmationPayload; + onConfirm: (confirmed: boolean) => Promise; + error?: any; +} + +export const InternalEvmConfirmation: FC = ({ payload, onConfirm }) => { + const { popup } = useAppEnv(); + + const [confirming, setConfirming] = useSafeState(false); + const [declining, setDeclining] = useSafeState(false); + + const confirm = useCallback( + async (confirmed: boolean) => { + try { + await onConfirm(confirmed); + } catch (err: any) { + // Human delay. + await delay(); + } + }, + [onConfirm] + ); + + const handleDeclineClick = useCallback(async () => { + if (confirming || declining) return; + + setDeclining(true); + await confirm(false); + setDeclining(false); + }, [confirming, declining, setDeclining, confirm]); + + const handleConfirmClick = useCallback(async () => { + if (confirming || declining) return; + + setConfirming(true); + await confirm(true); + setConfirming(false); + }, [confirming, declining, setConfirming, confirm]); + + return ( +
+
+
+ +
+
+ +
+
+ + + + + +
+ +
+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ ); +}; diff --git a/src/lib/temple/back/actions.ts b/src/lib/temple/back/actions.ts index d363e1631..6d57ac931 100644 --- a/src/lib/temple/back/actions.ts +++ b/src/lib/temple/back/actions.ts @@ -22,6 +22,7 @@ import { } from 'lib/temple/types'; import { createQueue, delay } from 'lib/utils'; +import { NonTezosToken } from '../../../app/pages/TokenPage/TokenPage'; import { getCurrentPermission, requestPermission, @@ -260,6 +261,85 @@ export function sendOperations( }); } +export const sendEvmOperations = async ( + port: Runtime.Port, + id: string, + token: NonTezosToken, + sourceAddress: string, + toAddress: string, + amount: string, + rpcUrl: string +): Promise<{ txHash: string }> => { + return withUnlocked(async () => { + return new Promise((resolve, reject) => + promisableEvmUnlock(resolve, reject, port, id, sourceAddress, toAddress, rpcUrl, token, amount) + ); + }); +}; + +const promisableEvmUnlock = async ( + resolve: (arg: { txHash: string }) => void, + reject: (err: Error) => void, + port: Runtime.Port, + id: string, + sourcePkh: string, + toPkh: string, + networkRpc: string, + token: NonTezosToken, + amount: string +) => { + intercom.notify(port, { + type: TempleMessageType.ConfirmationRequested, + id, + payload: { + type: 'evm_operations', + sourcePkh, + networkRpc + } + }); + + let closing = false; + + const decline = () => { + reject(new Error('Declined')); + }; + const declineAndClose = () => { + decline(); + closing = close(closing, port, id, stopTimeout, stopRequestListening, stopDisconnectListening); + }; + + const stopRequestListening = intercom.onRequest(async (req: TempleRequest, reqPort) => { + if (reqPort === port && req?.type === TempleMessageType.EvmConfirmationRequest && req?.id === id) { + if (req.confirmed) { + try { + const transaction = await withUnlocked(({ vault }) => + vault.sendEvmOperations(sourcePkh, networkRpc, token, amount, toPkh) + ); + + resolve({ txHash: transaction.hash }); + } catch (err: any) { + reject(err); + } + } else { + decline(); + } + + closing = close(closing, port, id, stopTimeout, stopRequestListening, stopDisconnectListening); + + return { + type: TempleMessageType.EvmConfirmationResponse + }; + } + return undefined; + }); + + const stopDisconnectListening = intercom.onDisconnect(port, declineAndClose); + + // Decline after timeout + const t = setTimeout(declineAndClose, AUTODECLINE_AFTER); + const stopTimeout = () => clearTimeout(t); +}; + const promisableUnlock = async ( resolve: (arg: { opHash: string }) => void, reject: (err: Error) => void, diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index 34e52aab1..b0ff4f6d4 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -154,6 +154,23 @@ const processRequest = async (req: TempleRequest, port: Runtime.Port): Promise(accountsStrgKey, this.passKey); + async fetchAccounts() { + const accounts = await fetchAndDecryptOne(accountsStrgKey, this.passKey); + const mnemonic = await fetchAndDecryptOne(mnemonicStrgKey, this.passKey); + + for (let i = 0; i < accounts.length; i++) { + const currentAccount = accounts[i]; + + if (currentAccount.type === TempleAccountType.HD && !currentAccount.evmPublicKeyHash) { + const derivationPath = isDefined(currentAccount.derivationPath) + ? currentAccount.derivationPath + : `m/44'/1729'/${currentAccount.hdIndex}'/0'`; + + const evmWallet = getEvmWalletFromMnemonic(mnemonic, derivationPath); + + if (evmWallet) { + currentAccount.derivationPath = derivationPath; + currentAccount.evmPublicKeyHash = evmWallet.address; + + await encryptAndSaveMany([[accPrivKeyStrgKey(evmWallet.address), evmWallet.privateKey]], this.passKey); + } + } + } + + return accounts; } async fetchSettings() { @@ -539,6 +566,36 @@ export class Vault { }); } + async sendEvmOperations( + accPublicKeyHash: string, + rpc: string, + token: NonTezosToken, + amount: string, + toAddress: string + ): Promise { + return this.withEvmSigner(accPublicKeyHash, rpc, async signer => { + const parsedAmount = ethers.parseUnits(amount, token.decimals); + + try { + if (token.nativeToken) { + const tx = { + to: toAddress, + value: parsedAmount + }; + + return signer.sendTransaction(tx); + } else { + const contract = new ethers.Contract(token.token_address, ERC20ABI, signer); + + return contract.transfer(toAddress, parsedAmount); + } + } catch (e) { + console.error(e); + return; + } + }); + } + private async withSigner(accPublicKeyHash: string, factory: (signer: Signer) => Promise) { const { signer, cleanup } = await this.getSigner(accPublicKeyHash); try { @@ -548,6 +605,15 @@ export class Vault { } } + private async withEvmSigner( + accPublicKeyHash: string, + rpcUrl: string, + factory: (signer: ethers.Signer) => Promise + ) { + const { signer } = await this.getEvmSigner(accPublicKeyHash, rpcUrl); + return await factory(signer); + } + private async getSigner(accPublicKeyHash: string): Promise<{ signer: Signer; cleanup: () => void }> { const allAccounts = await this.fetchAccounts(); const acc = allAccounts.find(a => a.publicKeyHash === accPublicKeyHash); @@ -569,4 +635,18 @@ export class Vault { return { signer, cleanup: () => {} }; } } + + private async getEvmSigner(accPublicKeyHash: string, rpcUrl: string): Promise<{ signer: ethers.Signer }> { + const allAccounts = await this.fetchAccounts(); + const acc = allAccounts.find(a => a.evmPublicKeyHash === accPublicKeyHash); + if (!acc) { + throw new PublicError('Account not found'); + } + + const provider = new ethers.JsonRpcProvider(rpcUrl); + const privateKey = await fetchAndDecryptOne(accPrivKeyStrgKey(accPublicKeyHash), this.passKey); + const signer = new ethers.Wallet(privateKey, provider); + + return { signer }; + } } diff --git a/src/lib/temple/front/client.ts b/src/lib/temple/front/client.ts index e21c7f8e9..e5fac4e4b 100644 --- a/src/lib/temple/front/client.ts +++ b/src/lib/temple/front/client.ts @@ -72,6 +72,7 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { break; case TempleMessageType.ConfirmationRequested: + console.log('ConfirmationRequested'); if (msg.id === confirmationIdRef.current) { setConfirmation({ id: msg.id, payload: msg.payload, error: msg.error }); } @@ -266,6 +267,15 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { [] ); + const confirmEvmInternal = useCallback(async (id: string, confirmed: boolean) => { + const res = await request({ + type: TempleMessageType.EvmConfirmationRequest, + id, + confirmed + }); + assertResponse(res.type === TempleMessageType.EvmConfirmationResponse); + }, []); + const getDAppPayload = useCallback(async (id: string) => { const res = await request({ type: TempleMessageType.DAppGetPayloadRequest, @@ -380,6 +390,8 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { createLedgerAccount, updateSettings, confirmInternal, + confirmEvmInternal, + confirmationIdRef, getDAppPayload, confirmDAppPermission, confirmDAppOperation, diff --git a/src/lib/temple/types.ts b/src/lib/temple/types.ts index f0631e2a8..b1f7d04c9 100644 --- a/src/lib/temple/types.ts +++ b/src/lib/temple/types.ts @@ -4,6 +4,7 @@ import { TempleDAppMetadata, TempleDAppNetwork } from '@temple-wallet/dapp/dist/ import type { TID } from 'lib/i18n/types'; +import { NonTezosToken } from '../../app/pages/TokenPage/TokenPage'; import { TempleSendPageEventRequest, TempleSendPageEventResponse, @@ -94,6 +95,7 @@ interface TempleAccountBase { type: TempleAccountType; name: string; publicKeyHash: string; + evmPublicKeyHash?: string; hdIndex?: number; derivationPath?: string; derivationType?: DerivationType; @@ -176,7 +178,15 @@ interface TempleOpsConfirmationPayload extends TempleConfirmationPayloadBase { estimates?: Estimate[]; } -export type TempleConfirmationPayload = TempleSignConfirmationPayload | TempleOpsConfirmationPayload; +export interface TempleEvmOpsConfirmationPayload extends TempleConfirmationPayloadBase { + type: 'evm_operations'; + networkRpc: string; +} + +export type TempleConfirmationPayload = + | TempleSignConfirmationPayload + | TempleOpsConfirmationPayload + | TempleEvmOpsConfirmationPayload; /** * DApp confirmation payloads @@ -268,10 +278,14 @@ export enum TempleMessageType { UpdateSettingsResponse = 'TEMPLE_UPDATE_SETTINGS_RESPONSE', OperationsRequest = 'TEMPLE_OPERATIONS_REQUEST', OperationsResponse = 'TEMPLE_OPERATIONS_RESPONSE', + EvmOperationsRequest = 'TEMPLE_EVM_OPERATIONS_REQUEST', + EvmOperationsResponse = 'TEMPLE_EVM_OPERATIONS_RESPONSE', SignRequest = 'TEMPLE_SIGN_REQUEST', SignResponse = 'TEMPLE_SIGN_RESPONSE', ConfirmationRequest = 'TEMPLE_CONFIRMATION_REQUEST', + EvmConfirmationRequest = 'TEMPLE_EVM_CONFIRMATION_REQUEST', ConfirmationResponse = 'TEMPLE_CONFIRMATION_RESPONSE', + EvmConfirmationResponse = 'TEMPLE_EVM_CONFIRMATION_RESPONSE', PageRequest = 'TEMPLE_PAGE_REQUEST', PageResponse = 'TEMPLE_PAGE_RESPONSE', DAppGetPayloadRequest = 'TEMPLE_DAPP_GET_PAYLOAD_REQUEST', @@ -328,7 +342,9 @@ export type TempleRequest = | TempleUpdateSettingsRequest | TempleGetAllDAppSessionsRequest | TempleRemoveDAppSessionRequest + | TempleEvmOperationsRequest | TempleSendTrackEventRequest + | TempleEvmConfirmationRequest | TempleSendPageEventRequest; export type TempleResponse = @@ -361,7 +377,9 @@ export type TempleResponse = | TempleUpdateSettingsResponse | TempleGetAllDAppSessionsResponse | TempleRemoveDAppSessionResponse + | TempleEvmOperationsResponse | TempleSendTrackEventResponse + | TempleEvmConfirmationResponse | TempleSendPageEventResponse; export interface TempleMessageBase { @@ -583,11 +601,26 @@ interface TempleOperationsRequest extends TempleMessageBase { opParams: any[]; } +interface TempleEvmOperationsRequest extends TempleMessageBase { + type: TempleMessageType.EvmOperationsRequest; + id: string; + sourcePkh: string; + networkRpc: string; + token: NonTezosToken; + toAddress: string; + amount: string; +} + interface TempleOperationsResponse extends TempleMessageBase { type: TempleMessageType.OperationsResponse; opHash: string; } +interface TempleEvmOperationsResponse extends TempleMessageBase { + type: TempleMessageType.EvmOperationsResponse; + txHash: string; +} + interface TempleSignRequest extends TempleMessageBase { type: TempleMessageType.SignRequest; id: string; @@ -609,10 +642,20 @@ interface TempleConfirmationRequest extends TempleMessageBase { modifiedStorageLimit?: number; } +interface TempleEvmConfirmationRequest extends TempleMessageBase { + type: TempleMessageType.EvmConfirmationRequest; + id: string; + confirmed: boolean; +} + interface TempleConfirmationResponse extends TempleMessageBase { type: TempleMessageType.ConfirmationResponse; } +interface TempleEvmConfirmationResponse extends TempleMessageBase { + type: TempleMessageType.EvmConfirmationResponse; +} + interface TemplePageRequest extends TempleMessageBase { type: TempleMessageType.PageRequest; origin: string; diff --git a/src/newChains/bitcoin.ts b/src/newChains/bitcoin.ts new file mode 100644 index 000000000..76e50b2bd --- /dev/null +++ b/src/newChains/bitcoin.ts @@ -0,0 +1,45 @@ +import ecc from '@bitcoinerlab/secp256k1'; +import BIP32Factory from 'bip32'; +import * as Bip39 from 'bip39'; +import * as Bitcoin from 'bitcoinjs-lib'; + +const testnet = Bitcoin.networks.testnet; +const GAP_LIMIT = 10; + +export const generateBitcoinAddressesFromMnemonic = (mnemonic: string) => { + const seed = Bip39.mnemonicToSeedSync(mnemonic); + + const bip32 = BIP32Factory(ecc); + const root = bip32.fromSeed(seed, testnet); + + const addresses = []; + + for (let i = 0; i < GAP_LIMIT; i++) { + const currentChild = root.derivePath(`m/84'/0'/0'/0/${i}`); + + addresses.push(Bitcoin.payments.p2wpkh({ pubkey: currentChild.publicKey, network: testnet }).address); + } + + return addresses; +}; + +export const getBitcoinAddress = () => { + return generatedAddresses[0]; +}; + +export const getAllBitcoinAddressesForCurrentMnemonic = () => { + return generatedAddresses; +}; + +const generatedAddresses = [ + 'tb1qufwmqnja8v8knrkd0uej5v50m770z8nsfw2736', + 'tb1q32znzr8hr95eed0tagjn6jy4pgt6w7macc0zul', + 'tb1qdlcmn58ndpc8z57tnme2vfd0r7k8dk9kadptdg', + 'tb1qhgmj86ky09sx2g7v8fqczugmuq4regvfntvwv6', + 'tb1q3p44wkeslyschpxwl0pl7l3wrdw7k4jvnxjwup', + 'tb1qlyprq7l54a2uyxtpfjp85zxg9uvr8crnsggtvq', + 'tb1qjy2m5wskt4kaadmwmjpfnn70amx3zh9fmn2ags', + 'tb1q0sd9fvj60em285fkrd2ukr5h4qvrmx9qamtaq6', + 'tb1qgkzuznnwd6yg3klcsxrg4z7hn5p0shaqjxusxc', + 'tb1qae32d9fhf6m7qjsmf6dt2esj5m3tkt9uzs5z3v' +]; diff --git a/src/newChains/erc20abi.json b/src/newChains/erc20abi.json new file mode 100644 index 000000000..405d6b364 --- /dev/null +++ b/src/newChains/erc20abi.json @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] diff --git a/src/newChains/evm.ts b/src/newChains/evm.ts new file mode 100644 index 000000000..44b549cf1 --- /dev/null +++ b/src/newChains/evm.ts @@ -0,0 +1,12 @@ +import { ethers } from 'ethers'; + +export const getEvmWalletFromMnemonic = (mnemonic: string, derivationPath: string) => { + try { + const wallet = ethers.Wallet.fromPhrase(mnemonic); + + return wallet.derivePath(derivationPath); + } catch (e) { + console.error(e); + return; + } +}; diff --git a/yarn.lock b/yarn.lock index 91c22cef7..f4884fab8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + "@airgap/beacon-core@4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@airgap/beacon-core/-/beacon-core-4.0.2.tgz#3f94fbec9c86ff99659e789b70e1684db450234e" @@ -1161,6 +1166,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bitcoinerlab/secp256k1@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@bitcoinerlab/secp256k1/-/secp256k1-1.0.5.tgz#4643ba73619c24c7c455cc63c6338c69c2cf187c" + integrity sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w== + dependencies: + "@noble/hashes" "^1.1.5" + "@noble/secp256k1" "^1.7.1" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -2103,11 +2116,28 @@ dependencies: eslint-scope "5.1.1" +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/hashes@1.3.2", "@noble/hashes@^1.1.5": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@^1.2.0": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/secp256k1@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" + integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== + "@nodelib/fs.scandir@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" @@ -2310,6 +2340,11 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== +"@scure/base@^1.1.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" + integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== + "@serh11p/jest-webextension-mock@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@serh11p/jest-webextension-mock/-/jest-webextension-mock-4.0.0.tgz#c3b2a00e8c758e156a4a922718b35183e9023af9" @@ -3255,6 +3290,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "20.4.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" @@ -4122,6 +4162,11 @@ address@^1.1.2: resolved "https://registry.yarnpkg.com/address/-/address-1.2.1.tgz#25bb61095b7522d65b357baa11bc05492d4c8acd" integrity sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + ag-channel@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ag-channel/-/ag-channel-5.0.0.tgz#c2c00dfbe372ae43e0466ec89e29aca1bbb2fb3e" @@ -4697,6 +4742,11 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -4712,6 +4762,21 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== +bip174@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" + integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== + +bip32@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" + integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== + dependencies: + "@noble/hashes" "^1.2.0" + "@scure/base" "^1.1.1" + typeforce "^1.11.5" + wif "^2.0.6" + bip39@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.2.tgz#2baf42ff3071fc9ddd5103de92e8f80d9257ee32" @@ -4732,6 +4797,18 @@ bip39@3.0.4: pbkdf2 "^3.0.9" randombytes "^2.0.1" +bitcoinjs-lib@^6.1.5: + version "6.1.5" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz#3b03509ae7ddd80a440f10fc38c4a97f0a028d8c" + integrity sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ== + dependencies: + "@noble/hashes" "^1.2.0" + bech32 "^2.0.0" + bip174 "^2.1.1" + bs58check "^3.0.1" + typeforce "^1.11.3" + varuint-bitcoin "^1.1.2" + bl@^1.2.1: version "1.2.3" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" @@ -4905,7 +4982,7 @@ bs58@^5.0.0: dependencies: base-x "^4.0.0" -bs58check@2.1.2, bs58check@^2.1.2: +bs58check@2.1.2, bs58check@<3.0.0, bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== @@ -6526,6 +6603,19 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +ethers@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.8.1.tgz#ee2a1a39b5f62a13678f90ccd879175391d0a2b4" + integrity sha512-iEKm6zox5h1lDn6scuRWdIdFJUCGg3+/aQWu0F4K0GVyEZiktFkqrJbRjTn1FlYEPz7RKA707D6g5Kdk6j7Ljg== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.5.0" + eventemitter3@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" @@ -12142,6 +12232,11 @@ typedarray-to-buffer@^4.0.0: resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz#cdd2933c61dd3f5f02eda5d012d441f95bfeb50a" integrity sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ== +typeforce@^1.11.3, typeforce@^1.11.5: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + typescript@4.5.5: version "4.5.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" @@ -12363,6 +12458,13 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +varuint-bitcoin@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92" + integrity sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw== + dependencies: + safe-buffer "^5.1.1" + vinyl-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vinyl-buffer/-/vinyl-buffer-1.0.1.tgz#96c1a3479b8c5392542c612029013b5b27f88bbf" @@ -12645,6 +12747,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wif@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== + dependencies: + bs58check "<3.0.0" + wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -12693,7 +12802,7 @@ write@^1.0.3: dependencies: mkdirp "^0.5.1" -ws@7.4.6, ws@^7.4.6, ws@^7.5.1, ws@^7.5.2, ws@^8.4.2, ws@^8.6.0, ws@^8.9.0: +ws@7.4.6, ws@8.5.0, ws@^7.4.6, ws@^7.5.1, ws@^7.5.2, ws@^8.4.2, ws@^8.6.0, ws@^8.9.0: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== From 79895a5d571c807917ee7ebaea902bc4f6220daa Mon Sep 17 00:00:00 2001 From: lendihop Date: Wed, 15 Nov 2023 17:15:18 +0100 Subject: [PATCH 2/8] bitcoin address generation and send --- src/newChains/bitcoin.ts | 139 +++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 28 deletions(-) diff --git a/src/newChains/bitcoin.ts b/src/newChains/bitcoin.ts index 76e50b2bd..bf6d6942d 100644 --- a/src/newChains/bitcoin.ts +++ b/src/newChains/bitcoin.ts @@ -1,45 +1,128 @@ import ecc from '@bitcoinerlab/secp256k1'; -import BIP32Factory from 'bip32'; +import axios from 'axios'; +import BIP32Factory, { BIP32Interface } from 'bip32'; import * as Bip39 from 'bip39'; import * as Bitcoin from 'bitcoinjs-lib'; const testnet = Bitcoin.networks.testnet; -const GAP_LIMIT = 10; -export const generateBitcoinAddressesFromMnemonic = (mnemonic: string) => { - const seed = Bip39.mnemonicToSeedSync(mnemonic); +// SegWit +const getBitcoinAddress = (node: BIP32Interface, network: Bitcoin.networks.Network) => + Bitcoin.payments.p2wpkh({ pubkey: node.publicKey, network }).address; +export const getBitcoinXPubFromMnemonic = (mnemonic: string) => { + const seed = Bip39.mnemonicToSeedSync(mnemonic); const bip32 = BIP32Factory(ecc); - const root = bip32.fromSeed(seed, testnet); - const addresses = []; + return bip32.fromSeed(seed, testnet); +}; + +export const getNextBitcoinHDWallet = (hdMaster: BIP32Interface, index: number) => { + const keyPair = hdMaster.derivePath(`m/84'/0'/0'/0/${index}`); + + return { address: getBitcoinAddress(keyPair, testnet), keyPair }; +}; + +interface UnspentOutput { + txid: string; + vout: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + value: number; +} + +interface UtxoResponse { + address: string; + utxos: UnspentOutput[]; +} + +interface UtxoInput { + hash: string; + index: number; +} + +const OUTPUTS_COUNT = 2; + +export const sendBitcoin = async ( + addressKeyPairsRecord: Record, + receiverAddress: string, + changeAddress: string, + amount: string +): Promise => { + const userAddresses: string[] = []; + + const satoshiToSend = Number(amount) * 100000000; + + const { data: utxoResponse } = await axios.get( + `http://localhost:3000/api/bitcoin-utxos?addresses=${userAddresses}` + ); + + const allUtxos = utxoResponse.flatMap(({ utxos }) => utxos); + console.log(allUtxos, 'allUtxos'); + const inputs: UtxoInput[] = allUtxos.map(utxo => ({ hash: utxo.txid, index: utxo.vout })); + console.log(inputs, 'inputs'); - for (let i = 0; i < GAP_LIMIT; i++) { - const currentChild = root.derivePath(`m/84'/0'/0'/0/${i}`); + const totalAmountAvailable = allUtxos.reduce((acc, utxo) => (acc += utxo.value), 0); + console.log(totalAmountAvailable, 'totalAmount'); + const inputsCount = inputs.length; - addresses.push(Bitcoin.payments.p2wpkh({ pubkey: currentChild.publicKey, network: testnet }).address); + const transactionSize = inputsCount * 146 + OUTPUTS_COUNT * 34 + 10 - inputsCount; + console.log(transactionSize, 'transactrionSize'); + + const fee = transactionSize * 20; + const change = totalAmountAvailable - satoshiToSend - fee; + + console.log(fee, 'fee'); + console.log(change, 'change'); + + // Check if we have enough funds to cover the transaction and the fees assuming we want to pay 20 satoshis per byte + if (change < 0) { + throw new Error('Balance is too low for this transaction'); } - return addresses; -}; + const psbt = new Bitcoin.Psbt(); -export const getBitcoinAddress = () => { - return generatedAddresses[0]; -}; + //Set transaction input + psbt.addInputs(inputs); + + // set the receiving address and the amount to send + psbt.addOutput({ address: receiverAddress, value: satoshiToSend }); + + // set the change address and the amount to send + psbt.addOutput({ address: changeAddress, value: change }); -export const getAllBitcoinAddressesForCurrentMnemonic = () => { - return generatedAddresses; + // Transaction signing + utxoResponse.forEach(({ address, utxos }) => + utxos.forEach(utxo => psbt.signInputHD(utxo.vout, addressKeyPairsRecord[address])) + ); + + // serialized transaction + const txHex = psbt.extractTransaction().toHex(); + console.log(txHex, 'txHex'); + + const txId = await broadcastTransaction(txHex); + console.log(txId, 'txId'); + + return txId; }; -const generatedAddresses = [ - 'tb1qufwmqnja8v8knrkd0uej5v50m770z8nsfw2736', - 'tb1q32znzr8hr95eed0tagjn6jy4pgt6w7macc0zul', - 'tb1qdlcmn58ndpc8z57tnme2vfd0r7k8dk9kadptdg', - 'tb1qhgmj86ky09sx2g7v8fqczugmuq4regvfntvwv6', - 'tb1q3p44wkeslyschpxwl0pl7l3wrdw7k4jvnxjwup', - 'tb1qlyprq7l54a2uyxtpfjp85zxg9uvr8crnsggtvq', - 'tb1qjy2m5wskt4kaadmwmjpfnn70amx3zh9fmn2ags', - 'tb1q0sd9fvj60em285fkrd2ukr5h4qvrmx9qamtaq6', - 'tb1qgkzuznnwd6yg3klcsxrg4z7hn5p0shaqjxusxc', - 'tb1qae32d9fhf6m7qjsmf6dt2esj5m3tkt9uzs5z3v' -]; +const broadcastTransaction = async (txHex: string) => { + try { + const response = await axios.post('https://blockstream.info/api/tx', txHex); + + if (response.status === 200) { + console.log('Transaction successfully broadcasted!'); + console.log('Transaction ID:', response.data); + + return response.data; + } else { + console.error('Failed to broadcast transaction. Response:', response.status, response.data); + } + } catch (error: any) { + console.error('Error broadcasting transaction:', error.message); + } +}; From e2e60ccd8c52b44e7b11f818e0c13667d777bd4b Mon Sep 17 00:00:00 2001 From: lendihop Date: Wed, 15 Nov 2023 17:48:55 +0100 Subject: [PATCH 3/8] fix keypair record --- src/app/pages/TokenPage/TokenPage.tsx | 7 +++---- src/newChains/bitcoin.ts | 11 ++++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/pages/TokenPage/TokenPage.tsx b/src/app/pages/TokenPage/TokenPage.tsx index 2a8e31f00..66adbf973 100644 --- a/src/app/pages/TokenPage/TokenPage.tsx +++ b/src/app/pages/TokenPage/TokenPage.tsx @@ -7,7 +7,6 @@ import clsx from 'clsx'; import { TID } from '../../../lib/i18n'; import { useAccount } from '../../../lib/temple/front'; import { Image } from '../../../lib/ui/Image'; -import { getAllBitcoinAddressesForCurrentMnemonic } from '../../../newChains/bitcoin'; import Spinner from '../../atoms/Spinner/Spinner'; import { useTabSlug } from '../../atoms/useTabSlug'; import { useAppEnv } from '../../env'; @@ -135,10 +134,10 @@ export const TokenPage: FC = ({ tokenAddress }) => { ); }; -export const getBitcoinWithBalance = async () => { - const addresses = getAllBitcoinAddressesForCurrentMnemonic().join(';'); +export const getBitcoinWithBalance = async (addresses: string[]) => { + const addressesConcat = addresses.join(';'); - const response = await axios.get(`http://localhost:3000/api/bitcoin?addresses=${addresses}`); + const response = await axios.get(`http://localhost:3000/api/bitcoin?addresses=${addressesConcat}`); return response.data; }; diff --git a/src/newChains/bitcoin.ts b/src/newChains/bitcoin.ts index bf6d6942d..a1263ff48 100644 --- a/src/newChains/bitcoin.ts +++ b/src/newChains/bitcoin.ts @@ -20,7 +20,7 @@ export const getBitcoinXPubFromMnemonic = (mnemonic: string) => { export const getNextBitcoinHDWallet = (hdMaster: BIP32Interface, index: number) => { const keyPair = hdMaster.derivePath(`m/84'/0'/0'/0/${index}`); - return { address: getBitcoinAddress(keyPair, testnet), keyPair }; + return { address: getBitcoinAddress(keyPair, testnet), privateKey: keyPair.toWIF() }; }; interface UnspentOutput { @@ -48,7 +48,7 @@ interface UtxoInput { const OUTPUTS_COUNT = 2; export const sendBitcoin = async ( - addressKeyPairsRecord: Record, + addressKeyPairsRecord: Record, receiverAddress: string, changeAddress: string, amount: string @@ -85,6 +85,7 @@ export const sendBitcoin = async ( } const psbt = new Bitcoin.Psbt(); + const bip32 = BIP32Factory(ecc); //Set transaction input psbt.addInputs(inputs); @@ -97,7 +98,11 @@ export const sendBitcoin = async ( // Transaction signing utxoResponse.forEach(({ address, utxos }) => - utxos.forEach(utxo => psbt.signInputHD(utxo.vout, addressKeyPairsRecord[address])) + utxos.forEach(utxo => { + const keyPair = bip32.fromBase58(addressKeyPairsRecord[address], testnet); + + psbt.signInputHD(utxo.vout, keyPair); + }) ); // serialized transaction From 9d8f4c456ec0f1a9b7dca8e0067958f152e97968 Mon Sep 17 00:00:00 2001 From: lendihop Date: Fri, 17 Nov 2023 19:29:40 +0100 Subject: [PATCH 4/8] bitcoin send/receive --- .../PageLayout/ConfirmationOverlay.tsx | 29 ++++- .../Home/OtherComponents/Tokens/Tokens.tsx | 7 +- src/app/pages/TokenPage/ReceiveTab.tsx | 35 ++++- src/app/pages/TokenPage/SendTab.tsx | 23 +++- src/app/pages/TokenPage/TokenPage.tsx | 15 ++- src/app/templates/InternalBtcConfirmation.tsx | 106 +++++++++++++++ src/app/templates/InternalConfirmation.tsx | 9 +- src/app/templates/InternalEvmConfirmation.tsx | 8 +- src/lib/temple/back/actions.ts | 86 +++++++++++- src/lib/temple/back/main.ts | 13 +- src/lib/temple/back/store.test.ts | 2 +- src/lib/temple/back/store.ts | 14 +- src/lib/temple/back/vault/index.ts | 86 ++++++++++-- src/lib/temple/back/vault/storage-keys.ts | 2 + src/lib/temple/front/client.ts | 22 +++- src/lib/temple/front/ready.ts | 4 + src/lib/temple/types.ts | 54 +++++++- src/newChains/bitcoin.ts | 123 +++++++++++------- 18 files changed, 547 insertions(+), 91 deletions(-) create mode 100644 src/app/templates/InternalBtcConfirmation.tsx diff --git a/src/app/layouts/PageLayout/ConfirmationOverlay.tsx b/src/app/layouts/PageLayout/ConfirmationOverlay.tsx index 6a29b468c..cae6b8513 100644 --- a/src/app/layouts/PageLayout/ConfirmationOverlay.tsx +++ b/src/app/layouts/PageLayout/ConfirmationOverlay.tsx @@ -8,10 +8,12 @@ import InternalConfirmation from 'app/templates/InternalConfirmation'; import { useTempleClient } from 'lib/temple/front'; import Portal from 'lib/ui/Portal'; +import { InternalBtcConfirmation } from '../../templates/InternalBtcConfirmation'; import { InternalEvmConfirmation } from '../../templates/InternalEvmConfirmation'; const ConfirmationOverlay: FC = () => { - const { confirmation, resetConfirmation, confirmInternal, confirmEvmInternal } = useTempleClient(); + const { confirmation, resetConfirmation, confirmInternal, confirmBtcInternal, confirmEvmInternal } = + useTempleClient(); const displayed = Boolean(confirmation); useLayoutEffect(() => { @@ -38,6 +40,16 @@ const ConfirmationOverlay: FC = () => { [confirmation, confirmInternal, resetConfirmation] ); + const handleBtcConfirm = useCallback( + async (confirmed: boolean) => { + if (confirmation) { + await confirmBtcInternal(confirmation.id, confirmed); + } + resetConfirmation(); + }, + [confirmation, confirmBtcInternal, resetConfirmation] + ); + const handleEvmConfirm = useCallback( async (confirmed: boolean) => { if (confirmation) { @@ -64,11 +76,11 @@ const ConfirmationOverlay: FC = () => { unmountOnExit >
- {confirmation && confirmation.payload.type !== 'evm_operations' && ( - )} {confirmation && confirmation.payload.type === 'evm_operations' && ( @@ -78,6 +90,15 @@ const ConfirmationOverlay: FC = () => { onConfirm={handleEvmConfirm} /> )} + {confirmation && + confirmation.payload.type !== 'btc_operations' && + confirmation.payload.type !== 'evm_operations' && ( + + )}
diff --git a/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx b/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx index 5e7aa5abf..ac848c10b 100644 --- a/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx +++ b/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx @@ -30,9 +30,10 @@ import { useLocalStorage } from 'lib/ui/local-storage'; import Popper, { PopperRenderProps } from 'lib/ui/Popper'; import { Link, navigate } from 'lib/woozie'; +import { useBtcWalletAddresses } from '../../../../../lib/temple/front/ready'; import { Image } from '../../../../../lib/ui/Image'; import Spinner from '../../../../atoms/Spinner/Spinner'; -import { getEvmTokensWithBalances, NonTezosToken } from '../../../TokenPage/TokenPage'; +import { getBitcoinWithBalance, getEvmTokensWithBalances, NonTezosToken } from '../../../TokenPage/TokenPage'; import { HomeSelectors } from '../../Home.selectors'; import { AssetsSelectors } from '../Assets.selectors'; import { AcceptAdsBanner } from './AcceptAdsBanner'; @@ -50,6 +51,7 @@ export const TokensTab: FC = () => { const { publicKeyHash, evmPublicKeyHash } = useAccount(); const { isSyncing } = useSyncTokens(); const { popup } = useAppEnv(); + const btcWalletAddresses = useBtcWalletAddresses(); const [nonTezosTokens, setNonTezosTokens] = useState([]); @@ -59,9 +61,10 @@ export const TokensTab: FC = () => { return; } + const bitcoin = await getBitcoinWithBalance(btcWalletAddresses); const evmTokens = await getEvmTokensWithBalances(evmPublicKeyHash); - setNonTezosTokens(evmTokens); + setNonTezosTokens([bitcoin, ...evmTokens]); }; useEffect(() => { diff --git a/src/app/pages/TokenPage/ReceiveTab.tsx b/src/app/pages/TokenPage/ReceiveTab.tsx index 0b427e98f..7ca5589b5 100644 --- a/src/app/pages/TokenPage/ReceiveTab.tsx +++ b/src/app/pages/TokenPage/ReceiveTab.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import classNames from 'clsx'; @@ -6,17 +6,37 @@ import { ReactComponent as CopyIcon } from 'app/icons/copy.svg'; import { setTestID } from '../../../lib/analytics'; import { T, t } from '../../../lib/i18n'; +import { useTempleClient } from '../../../lib/temple/front'; +import { useBtcWalletAddresses } from '../../../lib/temple/front/ready'; import useCopyToClipboard from '../../../lib/ui/useCopyToClipboard'; -import { FormField } from '../../atoms'; +import { FormField, FormSubmitButton } from '../../atoms'; import Spinner from '../../atoms/Spinner/Spinner'; import { ReceiveSelectors } from '../Receive/Receive.selectors'; interface Props { + isBitcoin: boolean; address?: string; } -export const ReceiveTab: FC = ({ address }) => { +export const ReceiveTab: FC = ({ isBitcoin, address }) => { const { fieldRef, copy, copied } = useCopyToClipboard(); + const { createNewBtcAddress } = useTempleClient(); + const btcWalletAddresses = useBtcWalletAddresses(); + + const [btcAddress, setBtcAddress] = useState(); + const [generating, setGenerating] = useState(false); + + const handleNewBtcAddressGeneration = async () => { + setGenerating(true); + await createNewBtcAddress(); + setGenerating(false); + + setBtcAddress(btcWalletAddresses[btcWalletAddresses.length - 1]); + }; + + useEffect(() => { + setBtcAddress(btcWalletAddresses[btcWalletAddresses.length - 1]); + }, [btcWalletAddresses]); if (!address) { return ( @@ -30,6 +50,13 @@ export const ReceiveTab: FC = ({ address }) => { return (
+ {isBitcoin && ( +
+ + Generate New Address + +
+ )} = ({ address }) => { id="receive-address" label={t('address')} labelDescription={t('accountAddressLabel')} - value={address} + value={isBitcoin ? btcAddress : address} size={36} spellCheck={false} readOnly diff --git a/src/app/pages/TokenPage/SendTab.tsx b/src/app/pages/TokenPage/SendTab.tsx index a55fa9ab9..7caf91dc9 100644 --- a/src/app/pages/TokenPage/SendTab.tsx +++ b/src/app/pages/TokenPage/SendTab.tsx @@ -23,7 +23,7 @@ interface Props { token?: NonTezosToken; } -export const SendTab: FC = ({ token, isBitcoin, accountPkh }) => { +export const SendTab: FC = ({ isBitcoin, accountPkh, token }) => { const [isProcessing, setProcessing] = useState(false); const [hash, setHash] = useState(); const { confirmationIdRef } = useTempleClient(); @@ -52,6 +52,27 @@ export const SendTab: FC = ({ token, isBitcoin, accountPkh }) => { }, [token?.chainName]); const onSubmit = async ({ to, amount }: FormData) => { + if (isBitcoin) { + console.log('isBitcoin'); + setProcessing(true); + + const id = nanoid(); + confirmationIdRef.current = id; + + const res = await request({ + type: TempleMessageType.BtcOperationsRequest, + id, + toAddress: to, + amount + }); + assertResponse(res.type === TempleMessageType.BtcOperationsResponse); + + setHash(res.txId); + setProcessing(false); + + return; + } + if (!accountPkh || !token) return; setProcessing(true); diff --git a/src/app/pages/TokenPage/TokenPage.tsx b/src/app/pages/TokenPage/TokenPage.tsx index 66adbf973..7d0bd3584 100644 --- a/src/app/pages/TokenPage/TokenPage.tsx +++ b/src/app/pages/TokenPage/TokenPage.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; import { TID } from '../../../lib/i18n'; import { useAccount } from '../../../lib/temple/front'; +import { useBtcWalletAddresses } from '../../../lib/temple/front/ready'; import { Image } from '../../../lib/ui/Image'; import Spinner from '../../atoms/Spinner/Spinner'; import { useTabSlug } from '../../atoms/useTabSlug'; @@ -43,10 +44,19 @@ export const TokenPage: FC = ({ tokenAddress }) => { const tabSlug = useTabSlug(); const { evmPublicKeyHash } = useAccount(); const isBitcoin = tokenAddress === 'btc'; + const btcWalletAddresses = useBtcWalletAddresses(); + console.log(btcWalletAddresses); const [nonTezosTokens, setNonTezosTokens] = useState([]); const getAllNonTezosTokens = async () => { + if (isBitcoin) { + const bitcoin = await getBitcoinWithBalance(btcWalletAddresses); + + setNonTezosTokens([bitcoin]); + return; + } + if (!evmPublicKeyHash) { setNonTezosTokens([]); return; @@ -70,7 +80,7 @@ export const TokenPage: FC = ({ tokenAddress }) => { { name: 'Receive', titleI18nKey: 'receive', - Component: () => + Component: () => }, { name: 'Send', @@ -135,8 +145,9 @@ export const TokenPage: FC = ({ tokenAddress }) => { }; export const getBitcoinWithBalance = async (addresses: string[]) => { - const addressesConcat = addresses.join(';'); + const addressesConcat = addresses.slice(-7).join(';'); + console.log(addressesConcat, 'cococ'); const response = await axios.get(`http://localhost:3000/api/bitcoin?addresses=${addressesConcat}`); return response.data; diff --git a/src/app/templates/InternalBtcConfirmation.tsx b/src/app/templates/InternalBtcConfirmation.tsx new file mode 100644 index 000000000..e0cccc3ed --- /dev/null +++ b/src/app/templates/InternalBtcConfirmation.tsx @@ -0,0 +1,106 @@ +import React, { FC, useCallback } from 'react'; + +import classNames from 'clsx'; + +import { FormSubmitButton, FormSecondaryButton } from 'app/atoms'; +import Logo from 'app/atoms/Logo'; +import SubTitle from 'app/atoms/SubTitle'; +import { useAppEnv } from 'app/env'; +import { T, t } from 'lib/i18n'; +import { TempleBtcOpsConfirmationPayload } from 'lib/temple/types'; + +import { useSafeState } from '../../lib/ui/hooks'; +import { delay } from '../../lib/utils'; + +interface Props { + payload: TempleBtcOpsConfirmationPayload; + onConfirm: (confirmed: boolean) => Promise; + error?: any; +} + +export const InternalBtcConfirmation: FC = ({ onConfirm }) => { + const { popup } = useAppEnv(); + + const [confirming, setConfirming] = useSafeState(false); + const [declining, setDeclining] = useSafeState(false); + + const confirm = useCallback( + async (confirmed: boolean) => { + try { + await onConfirm(confirmed); + } catch (err: any) { + // Human delay. + await delay(); + } + }, + [onConfirm] + ); + + const handleDeclineClick = useCallback(async () => { + if (confirming || declining) return; + + setDeclining(true); + await confirm(false); + setDeclining(false); + }, [confirming, declining, setDeclining, confirm]); + + const handleConfirmClick = useCallback(async () => { + if (confirming || declining) return; + + setConfirming(true); + await confirm(true); + setConfirming(false); + }, [confirming, declining, setConfirming, confirm]); + + return ( +
+
+
+ +
+
+ +
+
+ + + +
+ +
+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ ); +}; diff --git a/src/app/templates/InternalConfirmation.tsx b/src/app/templates/InternalConfirmation.tsx index 55330a0b6..a177b65dd 100644 --- a/src/app/templates/InternalConfirmation.tsx +++ b/src/app/templates/InternalConfirmation.tsx @@ -26,14 +26,19 @@ import { T, t } from 'lib/i18n'; import { useRetryableSWR } from 'lib/swr'; import { useCustomChainId, useNetwork, useRelevantAccounts, tryParseExpenses, useBalance } from 'lib/temple/front'; import { tzToMutez } from 'lib/temple/helpers'; -import { TempleAccountType, TempleChainId, TempleConfirmationPayload } from 'lib/temple/types'; +import { + TempleAccountType, + TempleChainId, + TempleOpsConfirmationPayload, + TempleSignConfirmationPayload +} from 'lib/temple/types'; import { useSafeState } from 'lib/ui/hooks'; import { isTruthy, delay } from 'lib/utils'; import { InternalConfirmationSelectors } from './InternalConfirmation.selectors'; type InternalConfiramtionProps = { - payload: TempleConfirmationPayload; + payload: TempleSignConfirmationPayload | TempleOpsConfirmationPayload; onConfirm: (confirmed: boolean, modifiedTotalFee?: number, modifiedStorageLimit?: number) => Promise; error?: any; }; diff --git a/src/app/templates/InternalEvmConfirmation.tsx b/src/app/templates/InternalEvmConfirmation.tsx index b08471e3d..73253a87f 100644 --- a/src/app/templates/InternalEvmConfirmation.tsx +++ b/src/app/templates/InternalEvmConfirmation.tsx @@ -92,7 +92,13 @@ export const InternalEvmConfirmation: FC = ({ payload, onConfirm }) => {
- +
diff --git a/src/lib/temple/back/actions.ts b/src/lib/temple/back/actions.ts index 6d57ac931..072ca39b8 100644 --- a/src/lib/temple/back/actions.ts +++ b/src/lib/temple/back/actions.ts @@ -44,7 +44,8 @@ import { accountsUpdated, settingsUpdated, withInited, - withUnlocked + withUnlocked, + btcWalletAddressesUpdated } from './store'; import { Vault } from './vault'; @@ -113,8 +114,10 @@ export function unlock(password: string) { enqueueUnlock(async () => { const vault = await Vault.setup(password, BACKGROUND_IS_WORKER); const accounts = await vault.fetchAccounts(); + const btcWalletAddresses = await vault.fetchBtcWalletAddresses(); + console.log(btcWalletAddresses, 'btcWalletAddresses'); const settings = await vault.fetchSettings(); - unlocked({ vault, accounts, settings }); + unlocked({ vault, accounts, btcWalletAddresses, settings }); }) ); } @@ -124,8 +127,10 @@ export async function unlockFromSession() { const vault = await Vault.recoverFromSession(); if (vault == null) return; const accounts = await vault.fetchAccounts(); + const btcWalletAddresses = await vault.fetchBtcWalletAddresses(); + console.log(btcWalletAddresses, 'btcWalletAddresses'); const settings = await vault.fetchSettings(); - unlocked({ vault, accounts, settings }); + unlocked({ vault, accounts, btcWalletAddresses, settings }); }); } @@ -143,6 +148,13 @@ export function createHDAccount(name?: string) { }); } +export function createNewBtcAddress() { + return withUnlocked(async ({ vault }) => { + const updatedAddresses = await vault.createNewBtcAddress(); + btcWalletAddressesUpdated(updatedAddresses); + }); +} + export function revealMnemonic(password: string) { return withUnlocked(() => Vault.revealMnemonic(password)); } @@ -261,6 +273,74 @@ export function sendOperations( }); } +export const sendBtcOperations = async ( + port: Runtime.Port, + id: string, + toAddress: string, + amount: string +): Promise<{ txId: string }> => { + return withUnlocked(async () => { + return new Promise((resolve, reject) => promisableBtcUnlock(resolve, reject, port, id, toAddress, amount)); + }); +}; + +const promisableBtcUnlock = async ( + resolve: (arg: { txId: string }) => void, + reject: (err: Error) => void, + port: Runtime.Port, + id: string, + toPkh: string, + amount: string +) => { + intercom.notify(port, { + type: TempleMessageType.ConfirmationRequested, + id, + payload: { + type: 'btc_operations' + } + }); + + let closing = false; + + const decline = () => { + reject(new Error('Declined')); + }; + const declineAndClose = () => { + decline(); + closing = close(closing, port, id, stopTimeout, stopRequestListening, stopDisconnectListening); + }; + + const stopRequestListening = intercom.onRequest(async (req: TempleRequest, reqPort) => { + if (reqPort === port && req?.type === TempleMessageType.BtcConfirmationRequest && req?.id === id) { + if (req.confirmed) { + try { + console.log('trysend'); + const txId = await withUnlocked(({ vault }) => vault.sendBtcOperations(toPkh, amount)); + + resolve({ txId }); + } catch (err: any) { + reject(err); + } + } else { + decline(); + } + + closing = close(closing, port, id, stopTimeout, stopRequestListening, stopDisconnectListening); + + return { + type: TempleMessageType.BtcConfirmationResponse + }; + } + return undefined; + }); + + const stopDisconnectListening = intercom.onDisconnect(port, declineAndClose); + + // Decline after timeout + const t = setTimeout(declineAndClose, AUTODECLINE_AFTER); + const stopTimeout = () => clearTimeout(t); +}; + export const sendEvmOperations = async ( port: Runtime.Port, id: string, diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index b0ff4f6d4..3fc4c32be 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -65,6 +65,10 @@ const processRequest = async (req: TempleRequest, port: Runtime.Port): Promise { expect(status).toBe(TempleStatus.Locked); }); it('Unlocked event', () => { - unlocked({ vault: {} as Vault, accounts: [], settings: {} }); + unlocked({ vault: {} as Vault, accounts: [], btcWalletAddresses: [], settings: {} }); const { status } = store.getState(); expect(status).toBe(TempleStatus.Ready); }); diff --git a/src/lib/temple/back/store.ts b/src/lib/temple/back/store.ts index bc31c7fdc..f14505e48 100644 --- a/src/lib/temple/back/store.ts +++ b/src/lib/temple/back/store.ts @@ -14,10 +14,11 @@ interface UnlockedStoreState extends StoreState { vault: Vault; } -export function toFront({ status, accounts, networks, settings }: StoreState): TempleState { +export function toFront({ status, accounts, btcWalletAddresses, networks, settings }: StoreState): TempleState { return { status, accounts, + btcWalletAddresses, networks, settings }; @@ -34,10 +35,12 @@ export const locked = createEvent('Locked'); export const unlocked = createEvent<{ vault: Vault; accounts: TempleAccount[]; + btcWalletAddresses: string[]; settings: TempleSettings; }>('Unlocked'); export const accountsUpdated = createEvent('Accounts updated'); +export const btcWalletAddressesUpdated = createEvent('btcWalletAddresses updated'); export const settingsUpdated = createEvent('Settings updated'); @@ -50,6 +53,7 @@ export const store = createStore({ vault: null, status: TempleStatus.Idle, accounts: [], + btcWalletAddresses: [], networks: [], settings: null }) @@ -69,20 +73,26 @@ export const store = createStore({ vault: null, status: TempleStatus.Locked, accounts: [], + btcWalletAddresses: [], networks: NETWORKS, settings: null })) - .on(unlocked, (state, { vault, accounts, settings }) => ({ + .on(unlocked, (state, { vault, accounts, btcWalletAddresses, settings }) => ({ ...state, vault, status: TempleStatus.Ready, accounts, + btcWalletAddresses, settings })) .on(accountsUpdated, (state, accounts) => ({ ...state, accounts })) + .on(btcWalletAddressesUpdated, (state, btcWalletAddresses) => ({ + ...state, + btcWalletAddresses + })) .on(settingsUpdated, (state, settings) => ({ ...state, settings diff --git a/src/lib/temple/back/vault/index.ts b/src/lib/temple/back/vault/index.ts index 0e0e09295..df9e0c1cf 100644 --- a/src/lib/temple/back/vault/index.ts +++ b/src/lib/temple/back/vault/index.ts @@ -14,6 +14,7 @@ import { clearAsyncStorages } from 'lib/temple/reset'; import { TempleAccount, TempleAccountType, TempleSettings } from 'lib/temple/types'; import { NonTezosToken } from '../../../../app/pages/TokenPage/TokenPage'; +import { getBitcoinXPubFromMnemonic, getNextBitcoinHDWallet, sendBitcoin } from '../../../../newChains/bitcoin'; import ERC20ABI from '../../../../newChains/erc20abi.json'; import { getEvmWalletFromMnemonic } from '../../../../newChains/evm'; import { createLedgerSigner } from '../ledger'; @@ -48,6 +49,7 @@ import { accountsStrgKey, accPrivKeyStrgKey, accPubKeyStrgKey, + BtcPkhPrivKeyRecord, checkStrgKey, legacyMigrationLevelStrgKey, migrationLevelStrgKey, @@ -315,6 +317,48 @@ export class Vault { return accounts; } + async fetchBtcWalletAddresses() { + const mnemonic = await fetchAndDecryptOne(mnemonicStrgKey, this.passKey); + + try { + //throw new Error('1'); + const btcWalletAddressesWithPrivateKeysRecord = await fetchAndDecryptOne( + BtcPkhPrivKeyRecord, + this.passKey + ); + + return Object.keys(btcWalletAddressesWithPrivateKeysRecord); + } catch { + const hdMaster = getBitcoinXPubFromMnemonic(mnemonic); + const { address, privateKey } = getNextBitcoinHDWallet(hdMaster, 0); + + await encryptAndSaveMany([[BtcPkhPrivKeyRecord, { [address]: privateKey }]], this.passKey); + + return [address]; + } + } + + async createNewBtcAddress() { + const mnemonic = await fetchAndDecryptOne(mnemonicStrgKey, this.passKey); + const btcWalletAddressesWithPrivateKeys = await fetchAndDecryptOne>( + BtcPkhPrivKeyRecord, + this.passKey + ); + + const walletAddresses = Object.keys(btcWalletAddressesWithPrivateKeys); + + const nextAccIndex = walletAddresses.length; + + const hdMaster = getBitcoinXPubFromMnemonic(mnemonic); + const { address, privateKey } = getNextBitcoinHDWallet(hdMaster, nextAccIndex); + + btcWalletAddressesWithPrivateKeys[address] = privateKey; + + await encryptAndSaveMany([[BtcPkhPrivKeyRecord, btcWalletAddressesWithPrivateKeys]], this.passKey); + + return [...walletAddresses, address]; + } + async fetchSettings() { let saved; try { @@ -566,6 +610,25 @@ export class Vault { }); } + async sendBtcOperations(toAddress: string, amount: string): Promise { + console.log('sending'); + const btcWalletAddressesWithPrivateKeysRecord = await fetchAndDecryptOne>( + BtcPkhPrivKeyRecord, + this.passKey + ); + + console.log(btcWalletAddressesWithPrivateKeysRecord, 'record'); + + const txId = await sendBitcoin( + toAddress, + amount, + btcWalletAddressesWithPrivateKeysRecord, + this.createNewBtcAddress.bind(this) + ); + + return txId; + } + async sendEvmOperations( accPublicKeyHash: string, rpc: string, @@ -576,22 +639,17 @@ export class Vault { return this.withEvmSigner(accPublicKeyHash, rpc, async signer => { const parsedAmount = ethers.parseUnits(amount, token.decimals); - try { - if (token.nativeToken) { - const tx = { - to: toAddress, - value: parsedAmount - }; + if (token.nativeToken) { + const tx = { + to: toAddress, + value: parsedAmount + }; - return signer.sendTransaction(tx); - } else { - const contract = new ethers.Contract(token.token_address, ERC20ABI, signer); + return signer.sendTransaction(tx); + } else { + const contract = new ethers.Contract(token.token_address, ERC20ABI, signer); - return contract.transfer(toAddress, parsedAmount); - } - } catch (e) { - console.error(e); - return; + return contract.transfer(toAddress, parsedAmount); } }); } diff --git a/src/lib/temple/back/vault/storage-keys.ts b/src/lib/temple/back/vault/storage-keys.ts index 999843c26..85483557f 100644 --- a/src/lib/temple/back/vault/storage-keys.ts +++ b/src/lib/temple/back/vault/storage-keys.ts @@ -5,6 +5,7 @@ enum StorageEntity { MigrationLevel = 'migration', Mnemonic = 'mnemonic', AccPrivKey = 'accprivkey', + BtcPkhPrivKeyRecord = 'btcpkhprivkeyrecord', AccPubKey = 'accpubkey', Accounts = 'accounts', Settings = 'settings', @@ -17,6 +18,7 @@ export const mnemonicStrgKey = createStorageKey(StorageEntity.Mnemonic); export const accPrivKeyStrgKey = createDynamicStorageKey(StorageEntity.AccPrivKey); export const accPubKeyStrgKey = createDynamicStorageKey(StorageEntity.AccPubKey); export const accountsStrgKey = createStorageKey(StorageEntity.Accounts); +export const BtcPkhPrivKeyRecord = createStorageKey(StorageEntity.BtcPkhPrivKeyRecord); export const settingsStrgKey = createStorageKey(StorageEntity.Settings); export const legacyMigrationLevelStrgKey = createStorageKey(StorageEntity.LegacyMigrationLevel); diff --git a/src/lib/temple/front/client.ts b/src/lib/temple/front/client.ts index e5fac4e4b..0ed6569d1 100644 --- a/src/lib/temple/front/client.ts +++ b/src/lib/temple/front/client.ts @@ -72,7 +72,6 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { break; case TempleMessageType.ConfirmationRequested: - console.log('ConfirmationRequested'); if (msg.id === confirmationIdRef.current) { setConfirmation({ id: msg.id, payload: msg.payload, error: msg.error }); } @@ -91,7 +90,7 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { * Aliases */ - const { status, networks: defaultNetworks, accounts, settings } = state; + const { status, networks: defaultNetworks, accounts, btcWalletAddresses, settings } = state; const idle = status === TempleStatus.Idle; const locked = status === TempleStatus.Locked; const ready = status === TempleStatus.Ready; @@ -138,6 +137,13 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { assertResponse(res.type === TempleMessageType.CreateAccountResponse); }, []); + const createNewBtcAddress = useCallback(async () => { + const res = await request({ + type: TempleMessageType.CreateNewBtcAddressRequest + }); + assertResponse(res.type === TempleMessageType.CreateNewBtcAddressResponse); + }, []); + const revealPrivateKey = useCallback(async (accountPublicKeyHash: string, password: string) => { const res = await request({ type: TempleMessageType.RevealPrivateKeyRequest, @@ -267,6 +273,15 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { [] ); + const confirmBtcInternal = useCallback(async (id: string, confirmed: boolean) => { + const res = await request({ + type: TempleMessageType.BtcConfirmationRequest, + id, + confirmed + }); + assertResponse(res.type === TempleMessageType.BtcConfirmationResponse); + }, []); + const confirmEvmInternal = useCallback(async (id: string, confirmed: boolean) => { const res = await request({ type: TempleMessageType.EvmConfirmationRequest, @@ -363,6 +378,7 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { customNetworks, networks, accounts, + btcWalletAddresses, settings, idle, locked, @@ -377,6 +393,7 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { unlock, lock, createAccount, + createNewBtcAddress, revealPrivateKey, revealMnemonic, generateSyncPayload, @@ -390,6 +407,7 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { createLedgerAccount, updateSettings, confirmInternal, + confirmBtcInternal, confirmEvmInternal, confirmationIdRef, getDAppPayload, diff --git a/src/lib/temple/front/ready.ts b/src/lib/temple/front/ready.ts index 875af9c89..c21885383 100644 --- a/src/lib/temple/front/ready.ts +++ b/src/lib/temple/front/ready.ts @@ -32,6 +32,7 @@ export const [ useSetNetworkId, useNetwork, useAllAccounts, + useBtcWalletAddresses, useSetAccountPkh, useAccount, useAccountPkh, @@ -43,6 +44,7 @@ export const [ v => v.setNetworkId, v => v.network, v => v.allAccounts, + v => v.btcWalletAddresses, v => v.setAccountPkh, v => v.account, v => v.accountPkh, @@ -57,6 +59,7 @@ function useReadyTemple() { const { networks: allNetworks, accounts: allAccounts, + btcWalletAddresses, settings, createTaquitoSigner, createTaquitoWallet @@ -146,6 +149,7 @@ function useReadyTemple() { setNetworkId, allAccounts, + btcWalletAddresses, account, accountPkh, setAccountPkh, diff --git a/src/lib/temple/types.ts b/src/lib/temple/types.ts index b1f7d04c9..159aa770b 100644 --- a/src/lib/temple/types.ts +++ b/src/lib/temple/types.ts @@ -33,6 +33,7 @@ export interface TempleDAppSession { export interface TempleState { status: TempleStatus; accounts: TempleAccount[]; + btcWalletAddresses: string[]; networks: TempleNetwork[]; settings: TempleSettings | null; } @@ -163,13 +164,13 @@ interface TempleConfirmationPayloadBase { sourcePkh: string; } -interface TempleSignConfirmationPayload extends TempleConfirmationPayloadBase { +export interface TempleSignConfirmationPayload extends TempleConfirmationPayloadBase { type: 'sign'; bytes: string; watermark?: string; } -interface TempleOpsConfirmationPayload extends TempleConfirmationPayloadBase { +export interface TempleOpsConfirmationPayload extends TempleConfirmationPayloadBase { type: 'operations'; networkRpc: string; opParams: any[]; @@ -183,10 +184,15 @@ export interface TempleEvmOpsConfirmationPayload extends TempleConfirmationPaylo networkRpc: string; } +export interface TempleBtcOpsConfirmationPayload extends Omit { + type: 'btc_operations'; +} + export type TempleConfirmationPayload = | TempleSignConfirmationPayload | TempleOpsConfirmationPayload - | TempleEvmOpsConfirmationPayload; + | TempleEvmOpsConfirmationPayload + | TempleBtcOpsConfirmationPayload; /** * DApp confirmation payloads @@ -249,7 +255,9 @@ export enum TempleMessageType { LockRequest = 'TEMPLE_LOCK_REQUEST', LockResponse = 'TEMPLE_LOCK_RESPONSE', CreateAccountRequest = 'TEMPLE_CREATE_ACCOUNT_REQUEST', + CreateNewBtcAddressRequest = 'TEMPLE_CREATE_NEW_BTC_ADDRESS_REQUEST', CreateAccountResponse = 'TEMPLE_CREATE_ACCOUNT_RESPONSE', + CreateNewBtcAddressResponse = 'TEMPLE_CREATE_NEW_BTC_ADDRESS_RESPONSE', RevealPublicKeyRequest = 'TEMPLE_REVEAL_PUBLIC_KEY_REQUEST', RevealPublicKeyResponse = 'TEMPLE_REVEAL_PUBLIC_KEY_RESPONSE', RevealPrivateKeyRequest = 'TEMPLE_REVEAL_PRIVATE_KEY_REQUEST', @@ -279,13 +287,17 @@ export enum TempleMessageType { OperationsRequest = 'TEMPLE_OPERATIONS_REQUEST', OperationsResponse = 'TEMPLE_OPERATIONS_RESPONSE', EvmOperationsRequest = 'TEMPLE_EVM_OPERATIONS_REQUEST', + BtcOperationsRequest = 'TEMPLE_BTC_OPERATIONS_REQUEST', EvmOperationsResponse = 'TEMPLE_EVM_OPERATIONS_RESPONSE', + BtcOperationsResponse = 'TEMPLE_BTC_OPERATIONS_RESPONSE', SignRequest = 'TEMPLE_SIGN_REQUEST', SignResponse = 'TEMPLE_SIGN_RESPONSE', ConfirmationRequest = 'TEMPLE_CONFIRMATION_REQUEST', EvmConfirmationRequest = 'TEMPLE_EVM_CONFIRMATION_REQUEST', + BtcConfirmationRequest = 'TEMPLE_BTC_CONFIRMATION_REQUEST', ConfirmationResponse = 'TEMPLE_CONFIRMATION_RESPONSE', EvmConfirmationResponse = 'TEMPLE_EVM_CONFIRMATION_RESPONSE', + BtcConfirmationResponse = 'TEMPLE_BTC_CONFIRMATION_RESPONSE', PageRequest = 'TEMPLE_PAGE_REQUEST', PageResponse = 'TEMPLE_PAGE_RESPONSE', DAppGetPayloadRequest = 'TEMPLE_DAPP_GET_PAYLOAD_REQUEST', @@ -319,6 +331,7 @@ export type TempleRequest = | TempleUnlockRequest | TempleLockRequest | TempleCreateAccountRequest + | TempleCreateNewBtcAddressRequest | TempleRevealPublicKeyRequest | TempleRevealPrivateKeyRequest | TempleRevealMnemonicRequest @@ -343,8 +356,10 @@ export type TempleRequest = | TempleGetAllDAppSessionsRequest | TempleRemoveDAppSessionRequest | TempleEvmOperationsRequest + | TempleBtcOperationsRequest | TempleSendTrackEventRequest | TempleEvmConfirmationRequest + | TempleBtcConfirmationRequest | TempleSendPageEventRequest; export type TempleResponse = @@ -354,6 +369,7 @@ export type TempleResponse = | TempleUnlockResponse | TempleLockResponse | TempleCreateAccountResponse + | TempleCreateNewBtcAddressResponse | TempleRevealPublicKeyResponse | TempleRevealPrivateKeyResponse | TempleRevealMnemonicResponse @@ -378,8 +394,10 @@ export type TempleResponse = | TempleGetAllDAppSessionsResponse | TempleRemoveDAppSessionResponse | TempleEvmOperationsResponse + | TempleBtcOperationsResponse | TempleSendTrackEventResponse | TempleEvmConfirmationResponse + | TempleBtcConfirmationResponse | TempleSendPageEventResponse; export interface TempleMessageBase { @@ -455,10 +473,18 @@ interface TempleCreateAccountRequest extends TempleMessageBase { name?: string; } +interface TempleCreateNewBtcAddressRequest extends TempleMessageBase { + type: TempleMessageType.CreateNewBtcAddressRequest; +} + interface TempleCreateAccountResponse extends TempleMessageBase { type: TempleMessageType.CreateAccountResponse; } +interface TempleCreateNewBtcAddressResponse extends TempleMessageBase { + type: TempleMessageType.CreateNewBtcAddressResponse; +} + interface TempleRevealPublicKeyRequest extends TempleMessageBase { type: TempleMessageType.RevealPublicKeyRequest; accountPublicKeyHash: string; @@ -611,6 +637,13 @@ interface TempleEvmOperationsRequest extends TempleMessageBase { amount: string; } +interface TempleBtcOperationsRequest extends TempleMessageBase { + type: TempleMessageType.BtcOperationsRequest; + id: string; + toAddress: string; + amount: string; +} + interface TempleOperationsResponse extends TempleMessageBase { type: TempleMessageType.OperationsResponse; opHash: string; @@ -621,6 +654,11 @@ interface TempleEvmOperationsResponse extends TempleMessageBase { txHash: string; } +interface TempleBtcOperationsResponse extends TempleMessageBase { + type: TempleMessageType.BtcOperationsResponse; + txId: string; +} + interface TempleSignRequest extends TempleMessageBase { type: TempleMessageType.SignRequest; id: string; @@ -648,6 +686,12 @@ interface TempleEvmConfirmationRequest extends TempleMessageBase { confirmed: boolean; } +interface TempleBtcConfirmationRequest extends TempleMessageBase { + type: TempleMessageType.BtcConfirmationRequest; + id: string; + confirmed: boolean; +} + interface TempleConfirmationResponse extends TempleMessageBase { type: TempleMessageType.ConfirmationResponse; } @@ -656,6 +700,10 @@ interface TempleEvmConfirmationResponse extends TempleMessageBase { type: TempleMessageType.EvmConfirmationResponse; } +interface TempleBtcConfirmationResponse extends TempleMessageBase { + type: TempleMessageType.BtcConfirmationResponse; +} + interface TemplePageRequest extends TempleMessageBase { type: TempleMessageType.PageRequest; origin: string; diff --git a/src/newChains/bitcoin.ts b/src/newChains/bitcoin.ts index a1263ff48..27198b1e8 100644 --- a/src/newChains/bitcoin.ts +++ b/src/newChains/bitcoin.ts @@ -1,9 +1,10 @@ import ecc from '@bitcoinerlab/secp256k1'; -import axios from 'axios'; import BIP32Factory, { BIP32Interface } from 'bip32'; import * as Bip39 from 'bip39'; import * as Bitcoin from 'bitcoinjs-lib'; +import { btcWalletAddressesUpdated } from '../lib/temple/back/store'; + const testnet = Bitcoin.networks.testnet; // SegWit @@ -20,7 +21,7 @@ export const getBitcoinXPubFromMnemonic = (mnemonic: string) => { export const getNextBitcoinHDWallet = (hdMaster: BIP32Interface, index: number) => { const keyPair = hdMaster.derivePath(`m/84'/0'/0'/0/${index}`); - return { address: getBitcoinAddress(keyPair, testnet), privateKey: keyPair.toWIF() }; + return { address: getBitcoinAddress(keyPair, testnet)!, privateKey: keyPair.toBase58() }; }; interface UnspentOutput { @@ -40,70 +41,95 @@ interface UtxoResponse { utxos: UnspentOutput[]; } -interface UtxoInput { - hash: string; - index: number; -} - const OUTPUTS_COUNT = 2; export const sendBitcoin = async ( - addressKeyPairsRecord: Record, receiverAddress: string, - changeAddress: string, - amount: string + amount: string, + addressKeyPairsRecord: Record, + createNewBtcAddress: () => Promise ): Promise => { - const userAddresses: string[] = []; + const userAddressesConcat = Object.keys(addressKeyPairsRecord).slice(-7).join(';'); - const satoshiToSend = Number(amount) * 100000000; + const satoshiToSend = Number((Number(amount) * 100000000).toFixed()); + console.log(satoshiToSend, 'amount'); - const { data: utxoResponse } = await axios.get( - `http://localhost:3000/api/bitcoin-utxos?addresses=${userAddresses}` - ); + const response = await fetch(`http://localhost:3000/api/bitcoin-utxos?addresses=${userAddressesConcat}`); + const utxoResponse: UtxoResponse[] = await response.json(); + console.log(utxoResponse, 'utxoResponse'); const allUtxos = utxoResponse.flatMap(({ utxos }) => utxos); - console.log(allUtxos, 'allUtxos'); - const inputs: UtxoInput[] = allUtxos.map(utxo => ({ hash: utxo.txid, index: utxo.vout })); - console.log(inputs, 'inputs'); const totalAmountAvailable = allUtxos.reduce((acc, utxo) => (acc += utxo.value), 0); console.log(totalAmountAvailable, 'totalAmount'); - const inputsCount = inputs.length; - const transactionSize = inputsCount * 146 + OUTPUTS_COUNT * 34 + 10 - inputsCount; - console.log(transactionSize, 'transactrionSize'); + const psbt = new Bitcoin.Psbt({ network: testnet }); + const bip32 = BIP32Factory(ecc); - const fee = transactionSize * 20; - const change = totalAmountAvailable - satoshiToSend - fee; + console.log(psbt, 'psbt1'); + // Add inputs + utxoResponse.forEach(({ address, utxos }) => { + utxos.forEach(utxo => { + const signer = bip32.fromBase58(addressKeyPairsRecord[address], testnet); + const payment = Bitcoin.payments.p2wpkh({ pubkey: signer.publicKey, network: testnet }); + + psbt.addInput({ + hash: utxo.txid, + index: utxo.vout, + witnessUtxo: { + script: payment.output!, + value: utxo.value + } + }); + }); + }); + + const transactionSize = psbt.inputCount * 146 + OUTPUTS_COUNT * 34 + 10 - psbt.inputCount; + + const fee = transactionSize; // for testnet console.log(fee, 'fee'); + + const change = totalAmountAvailable - satoshiToSend - fee; + console.log(change, 'change'); - // Check if we have enough funds to cover the transaction and the fees assuming we want to pay 20 satoshis per byte + // Check if we have enough funds to cover the transaction if (change < 0) { throw new Error('Balance is too low for this transaction'); } - const psbt = new Bitcoin.Psbt(); - const bip32 = BIP32Factory(ecc); - - //Set transaction input - psbt.addInputs(inputs); + console.log(psbt.inputCount, 'inputsCount'); // set the receiving address and the amount to send psbt.addOutput({ address: receiverAddress, value: satoshiToSend }); // set the change address and the amount to send - psbt.addOutput({ address: changeAddress, value: change }); + if (change > 0) { + const allAddresses = await createNewBtcAddress(); + btcWalletAddressesUpdated(allAddresses); + const changeAddress = allAddresses[allAddresses.length - 1]; + console.log(changeAddress, 'changeAddress'); + psbt.addOutput({ address: changeAddress, value: change }); + } - // Transaction signing - utxoResponse.forEach(({ address, utxos }) => - utxos.forEach(utxo => { - const keyPair = bip32.fromBase58(addressKeyPairsRecord[address], testnet); + let currentInputIndex = 0; + + // Sign the inputs + utxoResponse.forEach(({ address, utxos }) => { + utxos.forEach((_, index) => { + console.log('sign', index); + const signer = bip32.fromBase58(addressKeyPairsRecord[address], testnet); + psbt.signInput(currentInputIndex, signer); + currentInputIndex++; + }); + }); + + console.log('signed normally'); + + console.log(psbt, 'psbt2'); - psbt.signInputHD(utxo.vout, keyPair); - }) - ); + psbt.finalizeAllInputs(); // serialized transaction const txHex = psbt.extractTransaction().toHex(); @@ -117,17 +143,16 @@ export const sendBitcoin = async ( const broadcastTransaction = async (txHex: string) => { try { - const response = await axios.post('https://blockstream.info/api/tx', txHex); - - if (response.status === 200) { - console.log('Transaction successfully broadcasted!'); - console.log('Transaction ID:', response.data); - - return response.data; - } else { - console.error('Failed to broadcast transaction. Response:', response.status, response.data); - } - } catch (error: any) { - console.error('Error broadcasting transaction:', error.message); + const response = await fetch(`http://localhost:3000/api/bitcoin-broadcast-tx?txHex=${txHex}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + const data = await response.json(); + + return data.tx.hash; + } catch (error) { + console.error('Error broadcasting transaction:', error); } }; From 78d53b36424d1aa399d8e2aa8f7ac357eb0cd994 Mon Sep 17 00:00:00 2001 From: lendihop Date: Fri, 1 Dec 2023 11:13:11 +0100 Subject: [PATCH 5/8] dapps connect and operation confirmation, with 1 chain hardcoded --- package.json | 5 + src/app/ConfirmPage.tsx | 75 +++- .../Home/OtherComponents/Tokens/Tokens.tsx | 2 +- src/app/pages/TokenPage/SendTab.tsx | 4 + src/app/templates/OperationView.tsx | 31 +- src/contentScript.ts | 22 + src/inpage.ts | 42 ++ src/lib/temple/back/actions.ts | 17 + src/lib/temple/back/dapp.ts | 4 +- src/lib/temple/back/evmDapp.ts | 166 ++++++++ src/lib/temple/back/main.ts | 16 +- src/lib/temple/back/vault/index.ts | 14 +- src/lib/temple/front/client.ts | 4 +- src/lib/temple/types.ts | 19 +- src/newChains/evm.ts | 12 - src/newChains/temple-web3-provider.ts | 71 ++++ webpack.config.ts | 20 +- webpack/manifest.ts | 10 +- yarn.lock | 393 +++++++++++++++++- 19 files changed, 870 insertions(+), 57 deletions(-) create mode 100644 src/inpage.ts create mode 100644 src/lib/temple/back/evmDapp.ts delete mode 100644 src/newChains/evm.ts create mode 100644 src/newChains/temple-web3-provider.ts diff --git a/package.json b/package.json index c7f384841..606dbbcd0 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@types/debounce": "^1.2.1", "@types/debounce-promise": "3.1.4", "@types/elliptic": "6.4.14", + "@types/events": "^3.0.3", "@types/fontfaceobserver": "0.0.6", "@types/jest": "27.0.3", "@types/ledgerhq__hw-transport": "4.21.3", @@ -87,6 +88,7 @@ "@types/resolve": "^1.20.2", "@types/scryptsy": "^2.0.0", "@types/typedarray-to-buffer": "^4.0.0", + "@types/uuid": "^9.0.7", "@types/w3c-web-hid": "^1.0.3", "@types/webextension-polyfill": "^0.9.1", "@types/webpack": "^5.28.0", @@ -129,6 +131,7 @@ "eslint-plugin-prettier": "^4", "eslint-webpack-plugin": "^3.2.0", "ethers": "^6.8.1", + "events": "^3.3.0", "fast-glob": "^3.2.12", "file-loader": "6.2.0", "firebase": "^10.0.0", @@ -195,7 +198,9 @@ "use-force-update": "1.0.7", "use-onclickoutside": "0.4.1", "util": "0.11.1", + "uuid": "^9.0.1", "wasm-themis": "0.14.6", + "web3": "^4.2.2", "webextension-polyfill": "^0.10.0", "webpack": "^5.74.0", "webpack-cli": "^5", diff --git a/src/app/ConfirmPage.tsx b/src/app/ConfirmPage.tsx index 3c7b11389..f26aa8f10 100644 --- a/src/app/ConfirmPage.tsx +++ b/src/app/ConfirmPage.tsx @@ -1,5 +1,7 @@ import React, { FC, Fragment, memo, Suspense, useCallback, useMemo, useState } from 'react'; +import { isDefined } from '@rnw-community/shared'; + import { Alert, FormSubmitButton, FormSecondaryButton } from 'app/atoms'; import AccountTypeBadge from 'app/atoms/AccountTypeBadge'; import ConfirmLedgerOverlay from 'app/atoms/ConfirmLedgerOverlay'; @@ -73,9 +75,37 @@ const PayloadContent: React.FC = ({ }) => { const allAccounts = useRelevantAccounts(false); const AccountOptionContent = useMemo(() => AccountOptionContentHOC(payload.networkRpc), [payload.networkRpc]); + const EvmAccountOptionContent = useMemo(() => EvmAccountOptionContentHOC(), []); const chainId = useCustomChainId(payload.networkRpc, true)!; const mainnet = chainId === TempleChainId.Mainnet; + if (payload.type === 'connect_evm') { + return ( +
+

+ + + + + + + +

+ + + activeItemId={accountPkhToConnect} + getItemId={getEvmPkh} + items={allAccounts.filter(acc => acc.type === TempleAccountType.HD && isDefined(acc.evmPublicKeyHash))} + maxHeight="8rem" + onSelect={setAccountPkhToConnect} + OptionIcon={AccountIcon} + OptionContent={EvmAccountOptionContent} + autoFocus + /> +
+ ); + } + return payload.type === 'connect' ? (

@@ -113,14 +143,13 @@ const PayloadContent: React.FC = ({ export default ConfirmPage; const getPkh = (account: TempleAccount) => account.publicKeyHash; +const getEvmPkh = (account: TempleAccount) => account.evmPublicKeyHash ?? ''; const ConfirmDAppForm: FC = () => { const { getDAppPayload, confirmDAppPermission, confirmDAppOperation, confirmDAppSign } = useTempleClient(); const allAccounts = useRelevantAccounts(false); const account = useAccount(); - const [accountPkhToConnect, setAccountPkhToConnect] = useState(account.publicKeyHash); - const loc = useLocation(); const id = useMemo(() => { const usp = new URLSearchParams(loc.search); @@ -140,9 +169,23 @@ const ConfirmDAppForm: FC = () => { const payload = data!; const payloadError = data!.error; + const [accountPkhToConnect, setAccountPkhToConnect] = useState( + payload.type === 'connect_evm' ? account.evmPublicKeyHash! : account.publicKeyHash + ); + const connectedAccount = useMemo( () => - allAccounts.find(a => a.publicKeyHash === (payload.type === 'connect' ? accountPkhToConnect : payload.sourcePkh)), + allAccounts.find(a => { + if (payload.type === 'connect') { + return a.publicKeyHash === accountPkhToConnect; + } + + if (payload.type === 'connect_evm') { + return a.evmPublicKeyHash === accountPkhToConnect; + } + + return a.publicKeyHash === payload.sourcePkh; + }), [payload, allAccounts, accountPkhToConnect] ); @@ -152,6 +195,10 @@ const ConfirmDAppForm: FC = () => { case 'connect': return confirmDAppPermission(id, confimed, accountPkhToConnect); + case 'connect_evm': + return confirmDAppPermission(id, confimed, accountPkhToConnect, true); + + case 'confirm_evm_operations': case 'confirm_operations': return confirmDAppOperation(id, confimed, modifiedTotalFee, modifiedStorageLimit); @@ -224,6 +271,7 @@ const ConfirmDAppForm: FC = () => { const content = useMemo(() => { switch (payload.type) { case 'connect': + case 'connect_evm': return { title: t('confirmAction', t('connection').toLowerCase()), declineActionTitle: t('cancel'), @@ -248,6 +296,7 @@ const ConfirmDAppForm: FC = () => { }; case 'confirm_operations': + case 'confirm_evm_operations': return { title: t('confirmAction', t('operations').toLowerCase()), declineActionTitle: t('reject'), @@ -364,7 +413,7 @@ const ConfirmDAppForm: FC = () => { /> ) : ( <> - {payload.type !== 'connect' && connectedAccount && ( + {payload.type !== 'connect' && payload.type !== 'connect_evm' && connectedAccount && ( ); }); + +const EvmAccountOptionContentHOC = () => + memo>(({ item: acc }) => { + return ( + <> +
+ {acc.name} + +
+ +
+
+ +
+
+ + ); + }); diff --git a/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx b/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx index ac848c10b..03c65913a 100644 --- a/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx +++ b/src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx @@ -217,7 +217,7 @@ export const TokensTab: FC = () => { )} >
- {token.name} + {token.name}
{token.symbol} diff --git a/src/app/pages/TokenPage/SendTab.tsx b/src/app/pages/TokenPage/SendTab.tsx index 7caf91dc9..f9e6016eb 100644 --- a/src/app/pages/TokenPage/SendTab.tsx +++ b/src/app/pages/TokenPage/SendTab.tsx @@ -46,6 +46,10 @@ export const SendTab: FC = ({ isBitcoin, accountPkh, token }) => { return 'https://polygon-mumbai-bor.publicnode.com'; case 'BSC Testnet': return 'https://bsc-testnet.publicnode.com'; + case 'Avalanche Testnet': + return 'https://avalanche-fuji-c-chain.publicnode.com'; + case 'Fantom Testnet': + return 'https://fantom-testnet.publicnode.com'; default: return ''; } diff --git a/src/app/templates/OperationView.tsx b/src/app/templates/OperationView.tsx index b0c90eed4..f2719ced4 100644 --- a/src/app/templates/OperationView.tsx +++ b/src/app/templates/OperationView.tsx @@ -1,5 +1,6 @@ import React, { FC, useMemo, useState } from 'react'; +import { BigNumber } from 'bignumber.js'; import classNames from 'clsx'; import { ReactComponent as CodeAltIcon } from 'app/icons/code-alt.svg'; @@ -12,10 +13,10 @@ import ViewsSwitcher from 'app/templates/ViewsSwitcher/ViewsSwitcher'; import { TEZ_TOKEN_SLUG, toTokenSlug } from 'lib/assets'; import { T, t } from 'lib/i18n'; import { tryParseExpenses } from 'lib/temple/front'; -import { TempleDAppOperationsPayload, TempleDAppSignPayload } from 'lib/temple/types'; +import { TempleDAppOperationsPayload, TempleDAppSignPayload, TempleEvmDAppOperationsPayload } from 'lib/temple/types'; type OperationViewProps = { - payload: TempleDAppOperationsPayload | TempleDAppSignPayload; + payload: TempleEvmDAppOperationsPayload | TempleDAppOperationsPayload | TempleDAppSignPayload; networkRpc?: string; mainnet?: boolean; error?: any; @@ -186,6 +187,32 @@ const OperationView: FC = ({ ); } + if (payload.type === 'confirm_evm_operations') { + return ( +
+

+ + + +

+ + 1 ? { height: '11rem' } : undefined} + label={null} + /> + +

+ + : + + {new BigNumber(payload.opParams[0].gas).div(1e9).toString() + ' ETH'} +

+
+ ); + } + return null; }; diff --git a/src/contentScript.ts b/src/contentScript.ts index 65b7e8ac4..e84b5d48c 100644 --- a/src/contentScript.ts +++ b/src/contentScript.ts @@ -70,6 +70,28 @@ const SENDER = { iconUrl: 'https://templewallet.com/logo.png' }; +window.addEventListener('passToBackground', evt => { + // @ts-ignore + console.log('passToBackground_content_script', evt.detail.args); + getIntercom() + .request({ + type: TempleMessageType.PageRequest, + // @ts-ignore + origin: evt.detail.origin, + // @ts-ignore + payload: evt.detail.args, + // @ts-ignore + sourcePkh: evt.detail.sourcePkh, + evm: true + }) + .then((res: TempleResponse) => { + if (res?.type === TempleMessageType.PageResponse && res.payload) { + window.dispatchEvent(new CustomEvent('responseFromBackground', { detail: res.payload })); + } + }) + .catch(err => console.error(err)); +}); + window.addEventListener( 'message', evt => { diff --git a/src/inpage.ts b/src/inpage.ts new file mode 100644 index 000000000..c8510423e --- /dev/null +++ b/src/inpage.ts @@ -0,0 +1,42 @@ +import type { Eip1193Provider } from 'ethers'; +import { v4 as uuid } from 'uuid'; + +import { TempleWeb3Provider } from './newChains/temple-web3-provider'; + +interface EIP6963ProviderInfo { + uuid: string; + name: string; + icon: string; + rdns?: string; +} + +console.log('TEMPLE_INPAGE_SCRIPT_INJECTED'); + +const provider = new TempleWeb3Provider(); + +setGlobalProvider(provider); + +const info: EIP6963ProviderInfo = { + uuid: uuid(), + name: 'Temple Wallet', + icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzlfMTc4KSI+CjxwYXRoIGQ9Ik01NS4zOTA0IDI0LjYyNUg2Ny42ODczTDYxLjUzMSAwSDE4LjQ2ODVMMjQuNjA5MSAyNC42MjVIMzYuOTIxNkw0My4wNzc5IDQ5LjIzNDRIMzUuMzkwNEwzOC40Njg1IDYxLjU0NjlINDYuMTU2TDQ5LjIzNDEgNzMuODU5NEg2Ny42ODczTDU1LjM5MDQgMjQuNjI1WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzlfMTc4KSIvPgo8cGF0aCBkPSJNNjEuNTMxMyAzMC43ODFINTYuOTIxOUw1NS4zOTA2IDI0LjYyNDhINjcuNjg3NUw2MS41MzEzIDMwLjc4MVoiIGZpbGw9InVybCgjcGFpbnQxX2xpbmVhcl85XzE3OCkiLz4KPHBhdGggZD0iTTU1LjM5MDYgMTIuMzEyM0gyNy42ODc1TDI2LjkyMTkgOS4yMzQxM0g1Ny43MDMxTDU1LjM5MDYgMTIuMzEyM1oiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl85XzE3OCkiLz4KPHBhdGggZD0iTTE4LjQ2ODggMzAuNzgxMkwxMi4zMTI1IDYuMTU2MjVMMTguNDY4OCAwTDI0LjYwOTQgMjQuNjI1TDE4LjQ2ODggMzAuNzgxMloiIGZpbGw9InVybCgjcGFpbnQzX2xpbmVhcl85XzE3OCkiLz4KPHBhdGggZD0iTTQ2LjE1NjIgNjEuNTQ2NkgzOC40Njg3TDMyLjMxMjUgNjcuNzAyOUg0MEw0Ni4xNTYyIDYxLjU0NjZaIiBmaWxsPSJ1cmwoI3BhaW50NF9saW5lYXJfOV8xNzgpIi8+CjxwYXRoIGQ9Ik0zMC43NjU0IDMwLjc4MUgxOC40Njg1TDI0LjYwOTEgMjQuNjI0OEgzNi45MjE2TDMwLjc2NTQgMzAuNzgxWiIgZmlsbD0idXJsKCNwYWludDVfbGluZWFyXzlfMTc4KSIvPgo8cGF0aCBkPSJNNDMuMDc4NCA0OS4yMzQxSDM1LjM5MDlMMzAuNzY1OSAzMC43ODFMMzYuOTIyMSAyNC42MjQ4TDQzLjA3ODQgNDkuMjM0MVoiIGZpbGw9InVybCgjcGFpbnQ2X2xpbmVhcl85XzE3OCkiLz4KPHBhdGggZD0iTTM4LjQ2ODggNjEuNTQ3MUwzNS4zOTA2IDQ5LjIzNDZMMjkuMjM0NCA1NS4zOTA5TDMyLjMxMjUgNjcuNzAzNEwzOC40Njg4IDYxLjU0NzFaIiBmaWxsPSJ1cmwoI3BhaW50N19saW5lYXJfOV8xNzgpIi8+CjxwYXRoIGQ9Ik00OS4yMzQ0IDczLjg1OTFMNDYuMTU2MyA2MS41NDY2TDQwIDY3LjcwMjlMNDMuMDc4MSA3OS45OTk4TDQ5LjIzNDQgNzMuODU5MVoiIGZpbGw9InVybCgjcGFpbnQ4X2xpbmVhcl85XzE3OCkiLz4KPHBhdGggZD0iTTYxLjUzMTIgNzMuODU5NEw0Ny42ODc1IDE4LjQ2ODhMNDUuMjM0NCAyMC45MjE5TDU4LjQ2ODcgNzMuODU5NEw1NiA3Ni4zMTI1SDU5LjA3ODFMNjEuNTMxMiA3My44NTk0WiIgZmlsbD0idXJsKCNwYWludDlfbGluZWFyXzlfMTc4KSIvPgo8cGF0aCBkPSJNNjAgMTguNDY4NUg1Ni45MjE5TDU1LjM5MDYgMTIuMzEyM0w1Ny43MDMxIDkuMjM0MTNMNjAgMTguNDY4NVoiIGZpbGw9InVybCgjcGFpbnQxMF9saW5lYXJfOV8xNzgpIi8+CjxwYXRoIGQ9Ik02MS41MzE1IDczLjg1OTlMNTkuMDc4NCA3Ni4zMTNINTYuMDAwM0w1OC40NjkgNzMuODU5OUg0OS4yMzQ2TDQzLjA3ODQgODAuMDAwNUg2MS41MzE1TDY3LjY4NzggNzMuODU5OUg2MS41MzE1WiIgZmlsbD0idXJsKCNwYWludDExX2xpbmVhcl85XzE3OCkiLz4KPC9nPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzlfMTc4IiB4MT0iMTguNDYxMyIgeTE9IjM2LjkyODYiIHgyPSI2Ny42OTIxIiB5Mj0iMzYuOTI4NiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBvZmZzZXQ9IjAuMDAxODgwNzkiIHN0b3AtY29sb3I9IiNGQ0MzM0MiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZFRTUwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl85XzE3OCIgeDE9IjY3LjY5MjMiIHkxPSIyNy42OTc2IiB4Mj0iNTUuMzg0NiIgeTI9IjI3LjY5NzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZCOTgyOCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGOTZDMTMiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDJfbGluZWFyXzlfMTc4IiB4MT0iNTcuNjk2IiB5MT0iMTAuNzc0NSIgeDI9IjI2LjkyMzEiIHkyPSIxMC43NzQ1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGODQyMDAiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRjk2QzEzIi8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQzX2xpbmVhcl85XzE3OCIgeDE9IjE0Ljg1MDIiIHkxPSIwLjkwNTk1MyIgeDI9IjIyLjA3MjkiIHkyPSIyOS44NzQ0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGOTZDMTMiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkI5ODI4Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQ0X2xpbmVhcl85XzE3OCIgeDE9IjMyLjMwNzYiIHkxPSI2NC42MjA3IiB4Mj0iNDYuMTUzOSIgeTI9IjY0LjYyMDciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0Y4NDIwMCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGOTZDMTMiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDVfbGluZWFyXzlfMTc4IiB4MT0iMTguNDYxMyIgeTE9IjI3LjY5NzYiIHgyPSIzNi45MjI4IiB5Mj0iMjcuNjk3NiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjg0MjAwIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0Y5NkMxMyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50Nl9saW5lYXJfOV8xNzgiIHgxPSIzOS40NTAzIiB5MT0iNTAuMTQwNCIgeDI9IjMzLjMxMiIgeTI9IjI1LjUyMTEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0Y5NkMxMyIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGQjk4MjgiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDdfbGluZWFyXzlfMTc4IiB4MT0iMzUuOTIyOSIgeTE9IjY2Ljc5NjciIHgyPSIzMS43Njk0IiB5Mj0iNTAuMTM3OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRkI5ODI4Ii8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0Y5NkMxMyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50OF9saW5lYXJfOV8xNzgiIHgxPSI0Ni42OTIxIiB5MT0iNzkuMTAzOSIgeDI9IjQyLjUzODciIHkyPSI2Mi40NDUxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGOTZDMTMiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkI5ODI4Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQ5X2xpbmVhcl85XzE3OCIgeDE9IjU5LjA3NTkiIHkxPSI3Ni4zMTIzIiB4Mj0iNDQuODMxMyIgeTI9IjE5LjE4MDQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0Y5NkMxMyIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGODQyMDAiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDEwX2xpbmVhcl85XzE3OCIgeDE9IjU4Ljc4MzYiIHkxPSIxOS43NjY4IiB4Mj0iNTYuNDgxOSIgeTI9IjEwLjUzNTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0Y4NDIwMCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGOTZDMTMiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDExX2xpbmVhcl85XzE3OCIgeDE9IjQ1LjY1ODQiIHkxPSI3Ni45MjkxIiB4Mj0iNjQuMDQ1IiB5Mj0iNzYuOTI5MSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjg0MjAwIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0Y5NkMxMyIvPgo8L2xpbmVhckdyYWRpZW50Pgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzlfMTc4Ij4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=', + rdns: 'com.templewallet' +}; + +window.addEventListener('eip6963:requestProvider', announceProvider); + +announceProvider(); + +function setGlobalProvider(providerInstance: Eip1193Provider) { + (window as Record).ethereum = providerInstance; + console.log((window as Record).ethereum); + window.dispatchEvent(new Event('ethereum#initialized')); +} + +function announceProvider() { + window.dispatchEvent( + new CustomEvent('eip6963:announceProvider', { + detail: Object.freeze({ info, provider }) + }) + ); +} diff --git a/src/lib/temple/back/actions.ts b/src/lib/temple/back/actions.ts index 072ca39b8..22352742d 100644 --- a/src/lib/temple/back/actions.ts +++ b/src/lib/temple/back/actions.ts @@ -23,6 +23,7 @@ import { import { createQueue, delay } from 'lib/utils'; import { NonTezosToken } from '../../../app/pages/TokenPage/TokenPage'; +import { EvmRequestArguments } from '../../../newChains/temple-web3-provider'; import { getCurrentPermission, requestPermission, @@ -35,6 +36,7 @@ import { import { intercom } from './defaults'; import type { DryRunResult } from './dryrun'; import { buildFinalOpParmas, dryRunOpParams } from './dryrun'; +import { connectEvm, requestEvmOperation } from './evmDapp'; import { toFront, store, @@ -574,6 +576,21 @@ export async function processDApp(origin: string, req: TempleDAppRequest): Promi } } +export async function processEvmDApp(origin: string, payload: EvmRequestArguments, sourcePkh?: string): Promise { + console.log(origin, 'origin'); + const { method, params } = payload; + + switch (method) { + case 'eth_requestAccounts': + return withInited(() => enqueueDApp(() => connectEvm(origin))); + case 'eth_sendTransaction': + //@ts-ignore + return withInited(() => enqueueDApp(() => requestEvmOperation(origin, sourcePkh, params))); + default: + return 'No handler for this type of request'; + } +} + export async function getBeaconMessage(origin: string, msg: string, encrypted = false) { let recipientPubKey: string | null = null; let payload = null; diff --git a/src/lib/temple/back/dapp.ts b/src/lib/temple/back/dapp.ts index 3ef1ac466..25cff92dc 100644 --- a/src/lib/temple/back/dapp.ts +++ b/src/lib/temple/back/dapp.ts @@ -40,7 +40,7 @@ import { withUnlocked } from './store'; const CONFIRM_WINDOW_WIDTH = 380; const CONFIRM_WINDOW_HEIGHT = 632; -const AUTODECLINE_AFTER = 120_000; +export const AUTODECLINE_AFTER = 120_000; const STORAGE_KEY = 'dapp_sessions'; const HEX_PATTERN = /^[0-9a-fA-F]+$/; const TEZ_MSG_SIGN_PATTERN = /^0501[a-f0-9]{8}54657a6f73205369676e6564204d6573736167653a20[a-f0-9]*$/; @@ -508,7 +508,7 @@ function removeLastSlash(str: string) { return str.endsWith('/') ? str.slice(0, -1) : str; } -async function createConfirmationWindow(confirmationId: string) { +export async function createConfirmationWindow(confirmationId: string) { const isWin = (await browser.runtime.getPlatformInfo()).os === 'win'; const height = isWin ? CONFIRM_WINDOW_HEIGHT + 17 : CONFIRM_WINDOW_HEIGHT; diff --git a/src/lib/temple/back/evmDapp.ts b/src/lib/temple/back/evmDapp.ts new file mode 100644 index 000000000..21b7dce53 --- /dev/null +++ b/src/lib/temple/back/evmDapp.ts @@ -0,0 +1,166 @@ +import { nanoid } from 'nanoid'; +import browser, { Runtime } from 'webextension-polyfill'; + +import { TempleDAppPayload, TempleMessageType, TempleRequest } from '../types'; +import { AUTODECLINE_AFTER, createConfirmationWindow } from './dapp'; +import { intercom } from './defaults'; +import { withUnlocked } from './store'; + +export const SEPOLIA_RPC_URL = 'https://ethereum-sepolia.publicnode.com'; + +type RequestConfirmParams = { + id: string; + payload: TempleDAppPayload; + onDecline: () => void; + handleIntercomRequest: (req: TempleRequest, decline: () => void) => Promise; +}; + +export const requestConfirm = async ({ id, payload, onDecline, handleIntercomRequest }: RequestConfirmParams) => { + let closing = false; + const close = async () => { + if (closing) return; + closing = true; + + try { + stopTimeout(); + stopRequestListening(); + stopWinRemovedListening(); + + await closeWindow(); + } catch (_err) {} + }; + + const declineAndClose = () => { + onDecline(); + close(); + }; + + let knownPort: Runtime.Port | undefined; + const stopRequestListening = intercom.onRequest(async (req: TempleRequest, port) => { + if (req?.type === TempleMessageType.DAppGetPayloadRequest && req.id === id) { + knownPort = port; + + return { + type: TempleMessageType.DAppGetPayloadResponse, + payload + }; + } else { + if (knownPort !== port) return; + + const result = await handleIntercomRequest(req, onDecline); + if (result) { + close(); + return result; + } + } + }); + + const confirmWin = await createConfirmationWindow(id); + + const closeWindow = async () => { + if (confirmWin.id) { + const win = await browser.windows.get(confirmWin.id); + if (win.id) { + await browser.windows.remove(win.id); + } + } + }; + + const handleWinRemoved = (winId: number) => { + if (winId === confirmWin?.id) { + declineAndClose(); + } + }; + browser.windows.onRemoved.addListener(handleWinRemoved); + const stopWinRemovedListening = () => browser.windows.onRemoved.removeListener(handleWinRemoved); + + // Decline after timeout + const t = setTimeout(declineAndClose, AUTODECLINE_AFTER); + const stopTimeout = () => clearTimeout(t); +}; + +export const connectEvm = async (origin: string) => { + return new Promise(async (resolve, reject) => { + const id = nanoid(); + + await requestConfirm({ + id, + payload: { + type: 'connect_evm', + origin, + networkRpc: SEPOLIA_RPC_URL, + appMeta: { name: origin.split('.')[1] } + }, + onDecline: () => { + const err = new Error('Connection declined'); + //@ts-ignore + err.code = 4001; + + reject(err); + }, + handleIntercomRequest: async (confirmReq, decline) => { + if (confirmReq?.type === TempleMessageType.DAppPermConfirmationRequest && confirmReq?.id === id) { + console.log('confirmReq', confirmReq); + const { confirmed, accountPublicKeyHash } = confirmReq; + if (confirmed && accountPublicKeyHash) { + resolve([accountPublicKeyHash]); + } else { + decline(); + } + + return { + type: TempleMessageType.DAppPermConfirmationResponse + }; + } + return undefined; + } + }); + }); +}; + +export async function requestEvmOperation(origin: string, sourcePkh: string, opParams: any[]) { + return new Promise(async (resolve, reject) => { + const id = nanoid(); + console.log(opParams, 'ppppaarams'); + + await requestConfirm({ + id, + payload: { + type: 'confirm_evm_operations', + origin, + sourcePkh, + networkRpc: SEPOLIA_RPC_URL, + appMeta: { name: origin.split('.')[1] }, + opParams + }, + onDecline: () => { + const err = new Error('Operation declined'); + //@ts-ignore + err.code = 4001; + reject(err); + }, + handleIntercomRequest: async (confirmReq, decline) => { + if (confirmReq?.type === TempleMessageType.DAppOpsConfirmationRequest && confirmReq?.id === id) { + if (confirmReq.confirmed) { + try { + const op = await withUnlocked(({ vault }) => + vault.sendEvmDAppOperations(sourcePkh, SEPOLIA_RPC_URL, opParams) + ); + + resolve(op.hash); + } catch (err) { + console.error(err); + } + } else { + decline(); + } + + return { + type: TempleMessageType.DAppOpsConfirmationResponse + }; + } + return undefined; + } + }); + }); +} diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index 3fc4c32be..77c018238 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -253,19 +253,29 @@ const processRequest = async (req: TempleRequest, port: Runtime.Port): Promise { + return this.withEvmSigner(accPublicKeyHash, rpc, async signer => { + console.log('trying to send dApp tx'); + return signer.sendTransaction(opParams[0]); + }); + } + private async withSigner(accPublicKeyHash: string, factory: (signer: Signer) => Promise) { const { signer, cleanup } = await this.getSigner(accPublicKeyHash); try { diff --git a/src/lib/temple/front/client.ts b/src/lib/temple/front/client.ts index 0ed6569d1..58b2d79e2 100644 --- a/src/lib/temple/front/client.ts +++ b/src/lib/temple/front/client.ts @@ -300,13 +300,13 @@ export const [TempleClientProvider, useTempleClient] = constate(() => { return res.payload; }, []); - const confirmDAppPermission = useCallback(async (id: string, confirmed: boolean, pkh: string) => { + const confirmDAppPermission = useCallback(async (id: string, confirmed: boolean, pkh: string, evm?: boolean) => { const res = await request({ type: TempleMessageType.DAppPermConfirmationRequest, id, confirmed, accountPublicKeyHash: pkh, - accountPublicKey: confirmed ? await getPublicKey(pkh) : '' + accountPublicKey: confirmed && !evm ? await getPublicKey(pkh) : '' }); assertResponse(res.type === TempleMessageType.DAppPermConfirmationResponse); }, []); diff --git a/src/lib/temple/types.ts b/src/lib/temple/types.ts index 159aa770b..fa7b40451 100644 --- a/src/lib/temple/types.ts +++ b/src/lib/temple/types.ts @@ -214,6 +214,16 @@ interface TempleDAppConnectPayload extends TempleDAppPayloadBase { type: 'connect'; } +interface TempleEvmDAppConnectPayload extends TempleDAppPayloadBase { + type: 'connect_evm'; +} + +export interface TempleEvmDAppOperationsPayload extends TempleDAppPayloadBase { + type: 'confirm_evm_operations'; + sourcePkh: string; + opParams: any[]; +} + export interface TempleDAppOperationsPayload extends TempleDAppPayloadBase { type: 'confirm_operations'; sourcePkh: string; @@ -231,7 +241,12 @@ export interface TempleDAppSignPayload extends TempleDAppPayloadBase { preview: any; } -export type TempleDAppPayload = TempleDAppConnectPayload | TempleDAppOperationsPayload | TempleDAppSignPayload; +export type TempleDAppPayload = + | TempleEvmDAppOperationsPayload + | TempleEvmDAppConnectPayload + | TempleDAppConnectPayload + | TempleDAppOperationsPayload + | TempleDAppSignPayload; /** * Messages @@ -708,6 +723,8 @@ interface TemplePageRequest extends TempleMessageBase { type: TempleMessageType.PageRequest; origin: string; payload: any; + evm?: boolean; + sourcePkh?: string; beacon?: boolean; encrypted?: boolean; } diff --git a/src/newChains/evm.ts b/src/newChains/evm.ts deleted file mode 100644 index 44b549cf1..000000000 --- a/src/newChains/evm.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ethers } from 'ethers'; - -export const getEvmWalletFromMnemonic = (mnemonic: string, derivationPath: string) => { - try { - const wallet = ethers.Wallet.fromPhrase(mnemonic); - - return wallet.derivePath(derivationPath); - } catch (e) { - console.error(e); - return; - } -}; diff --git a/src/newChains/temple-web3-provider.ts b/src/newChains/temple-web3-provider.ts new file mode 100644 index 000000000..54dd5b178 --- /dev/null +++ b/src/newChains/temple-web3-provider.ts @@ -0,0 +1,71 @@ +import { ethers } from 'ethers'; +import { EventEmitter } from 'events'; + +export interface EvmRequestArguments { + /** The RPC method to request. */ + method: string; + + /** The params of the RPC method, if any. */ + params?: unknown[] | Record; +} + +export class TempleWeb3Provider extends EventEmitter { + private _BaseProvider: ethers.JsonRpcProvider; + private _isConnected: boolean; + private _accounts: string[]; + private readonly _chainId: string; + + constructor() { + super(); + this._BaseProvider = new ethers.JsonRpcProvider('https://ethereum-sepolia.publicnode.com'); + this._isConnected = false; + this._accounts = []; + //sepolia + this._chainId = '0xaa36a7'; + + this.connect = this.connect.bind(this); + this.request = this.request.bind(this); + } + + connect() { + if (!this._isConnected) { + this._isConnected = true; + this.emit('connect', { chainId: this._chainId }); + } + } + + async request(args: EvmRequestArguments) { + if (args.method === 'eth_requestAccounts' || args.method === 'eth_sendTransaction') { + window.dispatchEvent( + new CustomEvent('passToBackground', { + detail: { args, origin: window.origin, sourcePkh: this._accounts[0] } + }) + ); + + return new Promise(resolve => { + const listener = (evt: Event) => { + window.removeEventListener('responseFromBackground', listener); + if (!this._isConnected) { + this.connect(); + } + if (args.method === 'eth_requestAccounts') { + //@ts-ignore + this._accounts = evt.detail; + } + //@ts-ignore + console.log('inpage got response from bg', evt.detail); + //@ts-ignore + resolve(evt.detail); + }; + window.addEventListener('responseFromBackground', listener); + }); + } + + if (args.method === 'eth_accounts') { + return this._accounts; + } + + // @ts-ignore + return this._BaseProvider.send(args.method, args.params); + } +} diff --git a/webpack.config.ts b/webpack.config.ts index fadd7b31c..e2e6d990c 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -10,7 +10,6 @@ import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import * as Path from 'path'; -import ExtensionReloaderBadlyTyped, { ExtensionReloader as ExtensionReloaderType } from 'webpack-ext-reloader'; import ExtensionReloaderMV3BadlyTyped, { ExtensionReloader as ExtensionReloaderMV3Type } from 'webpack-ext-reloader-mv3'; @@ -32,7 +31,6 @@ import { buildManifest } from './webpack/manifest'; import { PATHS } from './webpack/paths'; import { isTruthy } from './webpack/utils'; -const ExtensionReloader = ExtensionReloaderBadlyTyped as ExtensionReloaderType; const ExtensionReloaderMV3 = ExtensionReloaderMV3BadlyTyped as ExtensionReloaderMV3Type; const PAGES_NAMES = ['popup', 'fullpage', 'confirm', 'options']; @@ -42,7 +40,7 @@ const HTML_TEMPLATES = PAGES_NAMES.map(name => { return { name, filename, path }; }); -const CONTENT_SCRIPTS = ['contentScript']; +const CONTENT_SCRIPTS = ['contentScript', 'inpage']; if (BACKGROUND_IS_WORKER) CONTENT_SCRIPTS.push('keepBackgroundWorkerAlive'); const mainConfig = (() => { @@ -159,7 +157,8 @@ const scriptsConfig = (() => { const config = buildBaseConfig(); config.entry = { - contentScript: Path.join(PATHS.SOURCE, 'contentScript.ts') + contentScript: Path.join(PATHS.SOURCE, 'contentScript.ts'), + inpage: Path.join(PATHS.SOURCE, 'inpage.ts') }; if (BACKGROUND_IS_WORKER) @@ -182,18 +181,7 @@ const scriptsConfig = (() => { cleanOnceBeforeBuildPatterns: ['scripts/**'], cleanStaleWebpackAssets: false, verbose: false - }), - - /* Page reloading in development mode */ - DEVELOPMENT_ENV && - new ExtensionReloader({ - port: RELOADER_PORTS.SCRIPTS, - reloadPage: true, - entries: { - background: '', - contentScript: CONTENT_SCRIPTS - } - }) + }) ].filter(isTruthy) ); diff --git a/webpack/manifest.ts b/webpack/manifest.ts index 20ce4849b..d382b348e 100644 --- a/webpack/manifest.ts +++ b/webpack/manifest.ts @@ -88,7 +88,7 @@ const buildManifestV2 = (vendor: string): Manifest.WebExtensionManifest => { const AUTHOR_URL = 'https://madfish.solutions'; -const PERMISSIONS = ['storage', 'unlimitedStorage', 'clipboardWrite', 'activeTab']; +const PERMISSIONS = ['storage', 'unlimitedStorage', 'clipboardWrite', 'activeTab', 'scripting']; const HOST_PERMISSIONS: string[] = ['http://localhost:8732/']; @@ -152,6 +152,14 @@ const buildManifestCommons = (vendor: string): Omit Date: Fri, 1 Dec 2023 18:49:17 +0100 Subject: [PATCH 6/8] switch dapp network + message signing --- src/app/ConfirmPage.tsx | 26 ++++- src/app/templates/OperationView.tsx | 40 ++++++-- src/contentScript.ts | 2 + src/lib/temple/back/actions.ts | 18 +++- src/lib/temple/back/evmDapp.ts | 132 +++++++++++++++++++++++--- src/lib/temple/back/main.ts | 2 +- src/lib/temple/back/vault/index.ts | 15 ++- src/lib/temple/types.ts | 13 +++ src/newChains/temple-web3-provider.ts | 132 ++++++++++++++++++-------- 9 files changed, 310 insertions(+), 70 deletions(-) diff --git a/src/app/ConfirmPage.tsx b/src/app/ConfirmPage.tsx index f26aa8f10..4e343c8c7 100644 --- a/src/app/ConfirmPage.tsx +++ b/src/app/ConfirmPage.tsx @@ -79,6 +79,10 @@ const PayloadContent: React.FC = ({ const chainId = useCustomChainId(payload.networkRpc, true)!; const mainnet = chainId === TempleChainId.Mainnet; + if (payload.type === 'switch_evm_network') { + return null; + } + if (payload.type === 'connect_evm') { return (
@@ -180,7 +184,7 @@ const ConfirmDAppForm: FC = () => { return a.publicKeyHash === accountPkhToConnect; } - if (payload.type === 'connect_evm') { + if (payload.type === 'connect_evm' || payload.type === 'switch_evm_network') { return a.evmPublicKeyHash === accountPkhToConnect; } @@ -196,6 +200,7 @@ const ConfirmDAppForm: FC = () => { return confirmDAppPermission(id, confimed, accountPkhToConnect); case 'connect_evm': + case 'switch_evm_network': return confirmDAppPermission(id, confimed, accountPkhToConnect, true); case 'confirm_evm_operations': @@ -203,6 +208,7 @@ const ConfirmDAppForm: FC = () => { return confirmDAppOperation(id, confimed, modifiedTotalFee, modifiedStorageLimit); case 'sign': + case 'sign_evm': return confirmDAppSign(id, confimed); } }, @@ -325,7 +331,25 @@ const ConfirmDAppForm: FC = () => { ) }; + case 'switch_evm_network': + return { + title: 'Confirm network switch', + declineActionTitle: t('reject'), + confirmActionTitle: 'Switch', + want: ( +
+
+ + + {payload.appMeta.name} + +
+
+ ) + }; + case 'sign': + case 'sign_evm': return { title: t('confirmAction', t('signAction').toLowerCase()), declineActionTitle: t('reject'), diff --git a/src/app/templates/OperationView.tsx b/src/app/templates/OperationView.tsx index f2719ced4..48928ef36 100644 --- a/src/app/templates/OperationView.tsx +++ b/src/app/templates/OperationView.tsx @@ -1,6 +1,5 @@ import React, { FC, useMemo, useState } from 'react'; -import { BigNumber } from 'bignumber.js'; import classNames from 'clsx'; import { ReactComponent as CodeAltIcon } from 'app/icons/code-alt.svg'; @@ -13,10 +12,19 @@ import ViewsSwitcher from 'app/templates/ViewsSwitcher/ViewsSwitcher'; import { TEZ_TOKEN_SLUG, toTokenSlug } from 'lib/assets'; import { T, t } from 'lib/i18n'; import { tryParseExpenses } from 'lib/temple/front'; -import { TempleDAppOperationsPayload, TempleDAppSignPayload, TempleEvmDAppOperationsPayload } from 'lib/temple/types'; +import { + TempleDAppOperationsPayload, + TempleDAppSignPayload, + TempleEvmDAppOperationsPayload, + TempleEvmDAppSignPayload +} from 'lib/temple/types'; type OperationViewProps = { - payload: TempleEvmDAppOperationsPayload | TempleDAppOperationsPayload | TempleDAppSignPayload; + payload: + | TempleEvmDAppSignPayload + | TempleEvmDAppOperationsPayload + | TempleDAppOperationsPayload + | TempleDAppSignPayload; networkRpc?: string; mainnet?: boolean; error?: any; @@ -143,6 +151,25 @@ const OperationView: FC = ({ ); } + if (payload.type === 'sign_evm') { + return ( +
+

+ + + +

+ + 1 ? { height: '11rem' } : undefined} + label={null} + /> +
+ ); + } + if (payload.type === 'confirm_operations') { return (
@@ -202,13 +229,6 @@ const OperationView: FC = ({ jsonViewStyle={signPayloadFormats.length > 1 ? { height: '11rem' } : undefined} label={null} /> - -

- - : - - {new BigNumber(payload.opParams[0].gas).div(1e9).toString() + ' ETH'} -

); } diff --git a/src/contentScript.ts b/src/contentScript.ts index e84b5d48c..07da2c8e6 100644 --- a/src/contentScript.ts +++ b/src/contentScript.ts @@ -82,6 +82,8 @@ window.addEventListener('passToBackground', evt => { payload: evt.detail.args, // @ts-ignore sourcePkh: evt.detail.sourcePkh, + // @ts-ignore + chainId: evt.detail.chainId, evm: true }) .then((res: TempleResponse) => { diff --git a/src/lib/temple/back/actions.ts b/src/lib/temple/back/actions.ts index 22352742d..6a20f6a47 100644 --- a/src/lib/temple/back/actions.ts +++ b/src/lib/temple/back/actions.ts @@ -36,7 +36,7 @@ import { import { intercom } from './defaults'; import type { DryRunResult } from './dryrun'; import { buildFinalOpParmas, dryRunOpParams } from './dryrun'; -import { connectEvm, requestEvmOperation } from './evmDapp'; +import { connectEvm, requestEvmOperation, requestEvmSign, switchChain } from './evmDapp'; import { toFront, store, @@ -576,16 +576,26 @@ export async function processDApp(origin: string, req: TempleDAppRequest): Promi } } -export async function processEvmDApp(origin: string, payload: EvmRequestArguments, sourcePkh?: string): Promise { +export async function processEvmDApp( + origin: string, + payload: EvmRequestArguments, + chainId?: string, + sourcePkh?: string +): Promise { console.log(origin, 'origin'); const { method, params } = payload; switch (method) { case 'eth_requestAccounts': - return withInited(() => enqueueDApp(() => connectEvm(origin))); + return withInited(() => enqueueDApp(() => connectEvm(origin, chainId))); + case 'wallet_switchEthereumChain': + return withInited(() => enqueueDApp(() => switchChain(params))); case 'eth_sendTransaction': //@ts-ignore - return withInited(() => enqueueDApp(() => requestEvmOperation(origin, sourcePkh, params))); + return withInited(() => enqueueDApp(() => requestEvmOperation(origin, sourcePkh, chainId, params))); + case 'eth_signTypedData_v4': + //@ts-ignore + return withInited(() => enqueueDApp(() => requestEvmSign(origin, sourcePkh, chainId, params))); default: return 'No handler for this type of request'; } diff --git a/src/lib/temple/back/evmDapp.ts b/src/lib/temple/back/evmDapp.ts index 21b7dce53..2b80d9040 100644 --- a/src/lib/temple/back/evmDapp.ts +++ b/src/lib/temple/back/evmDapp.ts @@ -6,8 +6,6 @@ import { AUTODECLINE_AFTER, createConfirmationWindow } from './dapp'; import { intercom } from './defaults'; import { withUnlocked } from './store'; -export const SEPOLIA_RPC_URL = 'https://ethereum-sepolia.publicnode.com'; - type RequestConfirmParams = { id: string; payload: TempleDAppPayload; @@ -79,7 +77,9 @@ export const requestConfirm = async ({ id, payload, onDecline, handleIntercomReq const stopTimeout = () => clearTimeout(t); }; -export const connectEvm = async (origin: string) => { +export const connectEvm = async (origin: string, chainId?: string) => { + if (!chainId) return new Error('chainId is not defined'); + return new Promise(async (resolve, reject) => { const id = nanoid(); @@ -88,7 +88,7 @@ export const connectEvm = async (origin: string) => { payload: { type: 'connect_evm', origin, - networkRpc: SEPOLIA_RPC_URL, + networkRpc: getRpcUrlByChainId(chainId), appMeta: { name: origin.split('.')[1] } }, onDecline: () => { @@ -100,7 +100,6 @@ export const connectEvm = async (origin: string) => { }, handleIntercomRequest: async (confirmReq, decline) => { if (confirmReq?.type === TempleMessageType.DAppPermConfirmationRequest && confirmReq?.id === id) { - console.log('confirmReq', confirmReq); const { confirmed, accountPublicKeyHash } = confirmReq; if (confirmed && accountPublicKeyHash) { resolve([accountPublicKeyHash]); @@ -118,10 +117,62 @@ export const connectEvm = async (origin: string) => { }); }; -export async function requestEvmOperation(origin: string, sourcePkh: string, opParams: any[]) { +export const switchChain = async (params: unknown[] | Record | undefined) => { + if (!params) return new Error('Request params is not defined'); + //@ts-ignore + const chainIdHex = params[0].chainId; + const rpcUrl = getRpcUrlByChainId(chainIdHex); + + return new Promise(async (resolve, reject) => { + const id = nanoid(); + + await requestConfirm({ + id, + payload: { + type: 'switch_evm_network', + origin, + networkRpc: rpcUrl, + appMeta: { name: origin.split('.')[1] } + }, + onDecline: () => { + const err = new Error('Network switch declined'); + //@ts-ignore + err.code = 4001; + + reject(err); + }, + handleIntercomRequest: async (confirmReq, decline) => { + if (confirmReq?.type === TempleMessageType.DAppPermConfirmationRequest && confirmReq?.id === id) { + const { confirmed } = confirmReq; + if (confirmed) { + resolve({ chainId: chainIdHex, rpcUrl }); + } else { + decline(); + } + + return { + type: TempleMessageType.DAppPermConfirmationResponse + }; + } + return undefined; + } + }); + }); +}; + +export async function requestEvmOperation( + origin: string, + sourcePkh: string | undefined, + chainId: string | undefined, + opParams: any[] +) { + if (!sourcePkh) return new Error('sourcePkh is not defined'); + if (!chainId) return new Error('chainId is not defined'); + + const rpcUrl = getRpcUrlByChainId(chainId); + return new Promise(async (resolve, reject) => { const id = nanoid(); - console.log(opParams, 'ppppaarams'); await requestConfirm({ id, @@ -129,7 +180,7 @@ export async function requestEvmOperation(origin: string, sourcePkh: string, opP type: 'confirm_evm_operations', origin, sourcePkh, - networkRpc: SEPOLIA_RPC_URL, + networkRpc: rpcUrl, appMeta: { name: origin.split('.')[1] }, opParams }, @@ -143,9 +194,7 @@ export async function requestEvmOperation(origin: string, sourcePkh: string, opP if (confirmReq?.type === TempleMessageType.DAppOpsConfirmationRequest && confirmReq?.id === id) { if (confirmReq.confirmed) { try { - const op = await withUnlocked(({ vault }) => - vault.sendEvmDAppOperations(sourcePkh, SEPOLIA_RPC_URL, opParams) - ); + const op = await withUnlocked(({ vault }) => vault.sendEvmDAppOperations(sourcePkh, rpcUrl, opParams)); resolve(op.hash); } catch (err) { @@ -164,3 +213,64 @@ export async function requestEvmOperation(origin: string, sourcePkh: string, opP }); }); } + +export async function requestEvmSign( + origin: string, + sourcePkh: string | undefined, + chainId: string | undefined, + opParams: any[] +) { + if (!sourcePkh) return new Error('sourcePkh is not defined'); + if (!chainId) return new Error('chainId is not defined'); + + const rpcUrl = getRpcUrlByChainId(chainId); + + return new Promise(async (resolve, reject) => { + const id = nanoid(); + + await requestConfirm({ + id, + payload: { + type: 'sign_evm', + origin, + sourcePkh, + networkRpc: rpcUrl, + appMeta: { name: origin.split('.')[1] }, + opParams + }, + onDecline: () => { + const err = new Error('Sign declined'); + //@ts-ignore + err.code = 4001; + reject(err); + }, + handleIntercomRequest: async (confirmReq, decline) => { + if (confirmReq?.type === TempleMessageType.DAppSignConfirmationRequest && confirmReq?.id === id) { + if (confirmReq.confirmed) { + const result = await withUnlocked(({ vault }) => vault.signEvm(sourcePkh, rpcUrl, opParams)); + resolve(result); + } else { + decline(); + } + + return { + type: TempleMessageType.DAppSignConfirmationResponse + }; + } + return undefined; + } + }); + }); +} + +const chainIdHexRpcUrlRecord: Record = { + '0xaa36a7': 'https://ethereum-sepolia.publicnode.com', + '0x13881': 'https://polygon-mumbai-bor.publicnode.com', + '0x61': 'https://bsc-testnet.publicnode.com', + '0xa869': 'https://avalanche-fuji-c-chain.publicnode.com', + '0xfa2': 'https://fantom-testnet.publicnode.com' +}; + +function getRpcUrlByChainId(chainIdHex: string) { + return chainIdHexRpcUrlRecord[chainIdHex] ?? chainIdHex; +} diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index 77c018238..4e362fd13 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -256,7 +256,7 @@ const processRequest = async (req: TempleRequest, port: Runtime.Port): Promise { + return this.withEvmSigner(accPublicKeyHash, rpc, async signer => { + console.log('trying to sign dApp params'); + const { types, domain, message } = JSON.parse(opParams[1]); + //ethers will add it yourself + delete types.EIP712Domain; + + return signer.signTypedData(domain, types, message); + }); + } + private async withSigner(accPublicKeyHash: string, factory: (signer: Signer) => Promise) { const { signer, cleanup } = await this.getSigner(accPublicKeyHash); try { @@ -676,7 +687,7 @@ export class Vault { private async withEvmSigner( accPublicKeyHash: string, rpcUrl: string, - factory: (signer: ethers.Signer) => Promise + factory: (signer: ethers.Wallet) => Promise ) { const { signer } = await this.getEvmSigner(accPublicKeyHash, rpcUrl); return await factory(signer); @@ -704,7 +715,7 @@ export class Vault { } } - private async getEvmSigner(accPublicKeyHash: string, rpcUrl: string): Promise<{ signer: ethers.Signer }> { + private async getEvmSigner(accPublicKeyHash: string, rpcUrl: string): Promise<{ signer: ethers.Wallet }> { const allAccounts = await this.fetchAccounts(); const acc = allAccounts.find(a => a.evmPublicKeyHash === accPublicKeyHash); if (!acc) { diff --git a/src/lib/temple/types.ts b/src/lib/temple/types.ts index fa7b40451..bcf1326f0 100644 --- a/src/lib/temple/types.ts +++ b/src/lib/temple/types.ts @@ -218,12 +218,22 @@ interface TempleEvmDAppConnectPayload extends TempleDAppPayloadBase { type: 'connect_evm'; } +interface TempleEvmDAppSwitchNetworkPayload extends TempleDAppPayloadBase { + type: 'switch_evm_network'; +} + export interface TempleEvmDAppOperationsPayload extends TempleDAppPayloadBase { type: 'confirm_evm_operations'; sourcePkh: string; opParams: any[]; } +export interface TempleEvmDAppSignPayload extends TempleDAppPayloadBase { + type: 'sign_evm'; + sourcePkh: string; + opParams: any[]; +} + export interface TempleDAppOperationsPayload extends TempleDAppPayloadBase { type: 'confirm_operations'; sourcePkh: string; @@ -242,6 +252,8 @@ export interface TempleDAppSignPayload extends TempleDAppPayloadBase { } export type TempleDAppPayload = + | TempleEvmDAppSignPayload + | TempleEvmDAppSwitchNetworkPayload | TempleEvmDAppOperationsPayload | TempleEvmDAppConnectPayload | TempleDAppConnectPayload @@ -724,6 +736,7 @@ interface TemplePageRequest extends TempleMessageBase { origin: string; payload: any; evm?: boolean; + chainId?: string; sourcePkh?: string; beacon?: boolean; encrypted?: boolean; diff --git a/src/newChains/temple-web3-provider.ts b/src/newChains/temple-web3-provider.ts index 54dd5b178..73ac047a5 100644 --- a/src/newChains/temple-web3-provider.ts +++ b/src/newChains/temple-web3-provider.ts @@ -1,6 +1,9 @@ import { ethers } from 'ethers'; import { EventEmitter } from 'events'; +const SEPOLIA_RPC_URL = 'https://ethereum-sepolia.publicnode.com'; +const SEPOLIA_CHAIN_ID = '0xaa36a7'; + export interface EvmRequestArguments { /** The RPC method to request. */ method: string; @@ -13,59 +16,106 @@ export class TempleWeb3Provider extends EventEmitter { private _BaseProvider: ethers.JsonRpcProvider; private _isConnected: boolean; private _accounts: string[]; - private readonly _chainId: string; + private _chainId: string; + + private _handleConnect(args: EvmRequestArguments) { + window.dispatchEvent( + new CustomEvent('passToBackground', { + detail: { args, origin: window.origin, chainId: this._chainId } + }) + ); + + return new Promise(resolve => { + const listener = (evt: Event) => { + window.removeEventListener('responseFromBackground', listener); + if (!this._isConnected) { + this._isConnected = true; + this.emit('connect', { chainId: this._chainId }); + } + //@ts-ignore + this._accounts = evt.detail; + //@ts-ignore + console.log('inpage got response from bg', evt.detail); + //@ts-ignore + resolve(evt.detail); + }; + window.addEventListener('responseFromBackground', listener); + }); + } + + private _handleSign(args: EvmRequestArguments) { + window.dispatchEvent( + new CustomEvent('passToBackground', { + detail: { args, origin: window.origin, chainId: this._chainId, sourcePkh: this._accounts[0] } + }) + ); + + return new Promise(resolve => { + const listener = (evt: Event) => { + window.removeEventListener('responseFromBackground', listener); + //@ts-ignore + console.log('inpage got response from bg', evt.detail); + //@ts-ignore + resolve(evt.detail); + }; + window.addEventListener('responseFromBackground', listener); + }); + } + + private _handleChainChange(args: EvmRequestArguments) { + window.dispatchEvent( + new CustomEvent('passToBackground', { + detail: { args, origin: window.origin } + }) + ); + + return new Promise(resolve => { + const listener = (evt: Event) => { + window.removeEventListener('responseFromBackground', listener); + //@ts-ignore + console.log('inpage got response from bg', evt.detail); + //@ts-ignore + this._chainId = evt.detail.chainId; + //@ts-ignore + this._BaseProvider = new ethers.JsonRpcProvider(evt.detail.rpcUrl); + resolve(null); + }; + window.addEventListener('responseFromBackground', listener); + }); + } constructor() { super(); - this._BaseProvider = new ethers.JsonRpcProvider('https://ethereum-sepolia.publicnode.com'); + this._BaseProvider = new ethers.JsonRpcProvider(SEPOLIA_RPC_URL); this._isConnected = false; this._accounts = []; - //sepolia - this._chainId = '0xaa36a7'; + this._chainId = SEPOLIA_CHAIN_ID; - this.connect = this.connect.bind(this); this.request = this.request.bind(this); } - connect() { - if (!this._isConnected) { - this._isConnected = true; - this.emit('connect', { chainId: this._chainId }); - } + async enable() { + return this._handleConnect({ method: 'eth_requestAccounts' }); } async request(args: EvmRequestArguments) { - if (args.method === 'eth_requestAccounts' || args.method === 'eth_sendTransaction') { - window.dispatchEvent( - new CustomEvent('passToBackground', { - detail: { args, origin: window.origin, sourcePkh: this._accounts[0] } - }) - ); - - return new Promise(resolve => { - const listener = (evt: Event) => { - window.removeEventListener('responseFromBackground', listener); - if (!this._isConnected) { - this.connect(); - } - if (args.method === 'eth_requestAccounts') { - //@ts-ignore - this._accounts = evt.detail; - } - //@ts-ignore - console.log('inpage got response from bg', evt.detail); - //@ts-ignore - resolve(evt.detail); - }; - window.addEventListener('responseFromBackground', listener); - }); + switch (args.method) { + case 'eth_accounts': + return this._accounts; + case 'eth_requestAccounts': + if (this._accounts.length === 0) { + return this._handleConnect(args); + } else { + return this._accounts; + } + case 'wallet_switchEthereumChain': + return this._handleChainChange(args); + case 'eth_sendTransaction': + case 'eth_signTypedData_v4': + return this._handleSign(args); + default: + // @ts-ignore + return this._BaseProvider.send(args.method, args.params); } - - if (args.method === 'eth_accounts') { - return this._accounts; - } - - // @ts-ignore - return this._BaseProvider.send(args.method, args.params); } } From beecd746ba163fa55fabc36959cadf1d15f17149 Mon Sep 17 00:00:00 2001 From: lendihop Date: Fri, 1 Dec 2023 19:59:18 +0100 Subject: [PATCH 7/8] added ethereum goerli support --- src/app/pages/TokenPage/SendTab.tsx | 4 +++- src/lib/temple/back/evmDapp.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/pages/TokenPage/SendTab.tsx b/src/app/pages/TokenPage/SendTab.tsx index f9e6016eb..5912ab6b1 100644 --- a/src/app/pages/TokenPage/SendTab.tsx +++ b/src/app/pages/TokenPage/SendTab.tsx @@ -42,6 +42,8 @@ export const SendTab: FC = ({ isBitcoin, accountPkh, token }) => { switch (token?.chainName) { case 'Ethereum Sepolia': return 'https://ethereum-sepolia.publicnode.com'; + case 'Ethereum Goerli': + return 'https://ethereum-goerli.publicnode.com'; case 'Polygon Mumbai': return 'https://polygon-mumbai-bor.publicnode.com'; case 'BSC Testnet': @@ -51,7 +53,7 @@ export const SendTab: FC = ({ isBitcoin, accountPkh, token }) => { case 'Fantom Testnet': return 'https://fantom-testnet.publicnode.com'; default: - return ''; + return token?.chainName ?? ''; } }, [token?.chainName]); diff --git a/src/lib/temple/back/evmDapp.ts b/src/lib/temple/back/evmDapp.ts index 2b80d9040..e69c205b7 100644 --- a/src/lib/temple/back/evmDapp.ts +++ b/src/lib/temple/back/evmDapp.ts @@ -265,6 +265,7 @@ export async function requestEvmSign( const chainIdHexRpcUrlRecord: Record = { '0xaa36a7': 'https://ethereum-sepolia.publicnode.com', + '0x5': 'https://ethereum-goerli.publicnode.com', '0x13881': 'https://polygon-mumbai-bor.publicnode.com', '0x61': 'https://bsc-testnet.publicnode.com', '0xa869': 'https://avalanche-fuji-c-chain.publicnode.com', From 780fab80838fabec4ddee4a504ad6b2ec3a16227 Mon Sep 17 00:00:00 2001 From: lendihop Date: Mon, 4 Dec 2023 13:01:23 +0100 Subject: [PATCH 8/8] -web3 package --- package.json | 1 - yarn.lock | 378 ++------------------------------------------------- 2 files changed, 11 insertions(+), 368 deletions(-) diff --git a/package.json b/package.json index 606dbbcd0..ffb0365d5 100644 --- a/package.json +++ b/package.json @@ -200,7 +200,6 @@ "util": "0.11.1", "uuid": "^9.0.1", "wasm-themis": "0.14.6", - "web3": "^4.2.2", "webextension-polyfill": "^0.10.0", "webpack": "^5.74.0", "webpack-cli": "^5", diff --git a/yarn.lock b/yarn.lock index 033fa6ef2..a6ddec9b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@adraffy/ens-normalize@1.10.0", "@adraffy/ens-normalize@^1.8.8": +"@adraffy/ens-normalize@1.10.0": version "1.10.0" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== @@ -1257,11 +1257,6 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@ethereumjs/rlp@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" - integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== - "@firebase/analytics-compat@0.2.6": version "0.2.6" resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz#50063978c42f13eb800e037e96ac4b17236841f4" @@ -2121,13 +2116,6 @@ dependencies: eslint-scope "5.1.1" -"@noble/curves@1.1.0", "@noble/curves@~1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" - integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== - dependencies: - "@noble/hashes" "1.3.1" - "@noble/curves@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" @@ -2135,16 +2123,16 @@ dependencies: "@noble/hashes" "1.3.2" -"@noble/hashes@1.3.1", "@noble/hashes@^1.2.0": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" - integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== - -"@noble/hashes@1.3.2", "@noble/hashes@^1.1.5", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": +"@noble/hashes@1.3.2", "@noble/hashes@^1.1.5": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@^1.2.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + "@noble/secp256k1@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -2352,28 +2340,11 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== -"@scure/base@^1.1.1", "@scure/base@~1.1.0": +"@scure/base@^1.1.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== -"@scure/bip32@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10" - integrity sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A== - dependencies: - "@noble/curves" "~1.1.0" - "@noble/hashes" "~1.3.1" - "@scure/base" "~1.1.0" - -"@scure/bip39@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" - integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== - dependencies: - "@noble/hashes" "~1.3.0" - "@scure/base" "~1.1.0" - "@serh11p/jest-webextension-mock@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@serh11p/jest-webextension-mock/-/jest-webextension-mock-4.0.0.tgz#c3b2a00e8c758e156a4a922718b35183e9023af9" @@ -3581,13 +3552,6 @@ tapable "^2.2.0" webpack "^5" -"@types/ws@8.5.3": - version "8.5.3" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" - integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== - dependencies: - "@types/node" "*" - "@types/ws@^7.4.6": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -4146,11 +4110,6 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== -abitype@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" - integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== - acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -5124,15 +5083,6 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -call-bind@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" - integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== - dependencies: - function-bind "^1.1.2" - get-intrinsic "^1.2.1" - set-function-length "^1.1.1" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -5530,7 +5480,7 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -crc-32@^1.2.0, crc-32@^1.2.2: +crc-32@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== @@ -5615,13 +5565,6 @@ cross-fetch@^3.1.5: dependencies: node-fetch "^2.6.11" -cross-fetch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" - integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== - dependencies: - node-fetch "^2.6.12" - cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -5932,15 +5875,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -define-data-property@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" - integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -6679,16 +6613,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -ethereum-cryptography@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" - integrity sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug== - dependencies: - "@noble/curves" "1.1.0" - "@noble/hashes" "1.3.1" - "@scure/bip32" "1.3.1" - "@scure/bip39" "1.2.1" - ethers@^6.8.1: version "6.8.1" resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.8.1.tgz#ee2a1a39b5f62a13678f90ccd879175391d0a2b4" @@ -7107,11 +7031,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - function.prototype.name@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" @@ -7741,14 +7660,6 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arguments@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" @@ -7900,13 +7811,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -8018,7 +7922,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -8071,11 +7975,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isomorphic-ws@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" - integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== - istanbul-lib-coverage@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" @@ -9296,13 +9195,6 @@ node-fetch@^2.6.11: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.12: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-forge@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" @@ -11361,16 +11253,6 @@ seroval@^0.5.0: resolved "https://registry.yarnpkg.com/seroval/-/seroval-0.5.1.tgz#e6d17365cdaaae7e50815c7e0bcd7102facdadf3" integrity sha512-ZfhQVB59hmIauJG5Ydynupy8KHyr5imGNtdDhbZG68Ufh1Ynkv9KOYOAABf71oVbQxJ8VkWnMHAjEHE7fWkH5g== -set-function-length@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" - integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== - dependencies: - define-data-property "^1.1.1" - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -12549,17 +12431,6 @@ util@^0.10.3: dependencies: inherits "2.0.3" -util@^0.12.5: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -12658,217 +12529,6 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -web3-core@^4.3.0, web3-core@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.1.tgz#5c3b5b59f1e31537a64237caa5fd83f5ffd7b0f3" - integrity sha512-xa3w5n/ESxp5HIbrwsYBhpAPx2KI5LprjRFEtRwP0GpqqhTcCSMMYoyItRqQQ+k9YnB0PoFpWJfJI6Qn5x8YUQ== - dependencies: - web3-errors "^1.1.4" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - optionalDependencies: - web3-providers-ipc "^4.0.7" - -web3-errors@^1.1.3, web3-errors@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.1.4.tgz#5667a0a5f66fc936e101ef32032ccc1e8ca4d5a1" - integrity sha512-WahtszSqILez+83AxGecVroyZsMuuRT+KmQp4Si5P4Rnqbczno1k748PCrZTS1J4UCPmXMG2/Vt+0Bz2zwXkwQ== - dependencies: - web3-types "^1.3.1" - -web3-eth-abi@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.1.4.tgz#56ae7ebb1385db1a948e69fb35f4057bff6743af" - integrity sha512-YLOBVVxxxLYKXjaiwZjEWYEnkMmmrm0nswZsvzSsINy/UgbWbzfoiZU+zn4YNWIEhORhx1p37iS3u/dP6VyC2w== - dependencies: - abitype "0.7.1" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-accounts@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.0.tgz#5b5e6c60d457e7b829ec590021fc87ada8585920" - integrity sha512-UFtAsOANsvihTQ6SSvOKguupmQkResyR9M9JNuOxYpKh7+3W+sTnbLXw2UbOSYIsKlc1mpqqW9bVr1SjqHDpUQ== - dependencies: - "@ethereumjs/rlp" "^4.0.1" - crc-32 "^1.2.2" - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-contract@^4.1.2, web3-eth-contract@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.1.3.tgz#15dd4c978eaf0d8f894b2c1f3e9f94edd29ff57c" - integrity sha512-F6e3eyetUDiNOb78EDVJtNOb0H1GPz3xAZH8edSfYdhaxI9tTutP2V3p++kh2ZJ/RrdE2+xil7H/nPLgHymBvg== - dependencies: - web3-core "^4.3.1" - web3-errors "^1.1.4" - web3-eth "^4.3.1" - web3-eth-abi "^4.1.4" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-ens@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.8.tgz#f4e0a018ce6cc69e37007ee92063867feb5994f0" - integrity sha512-nj0JfeD45BbzVJcVYpUJnSo8iwDcY9CQ7CZhhIVVOFjvpMAPw0zEwjTvZEIQyCW61OoDG9xcBzwxe2tZoYhMRw== - dependencies: - "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth "^4.3.1" - web3-eth-contract "^4.1.2" - web3-net "^4.0.7" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-iban@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz#ee504f845d7b6315f0be78fcf070ccd5d38e4aaf" - integrity sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ== - dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-personal@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz#b51628c560de550ca8b354fa784f9556aae6065c" - integrity sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw== - dependencies: - web3-core "^4.3.0" - web3-eth "^4.3.1" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.3.1.tgz#cb4224356716da71e694091aa3fbf10bcb813fb5" - integrity sha512-zJir3GOXooHQT85JB8SrufE+Voo5TtXdjhf1D8IGXmxM8MrhI8AT+Pgt4siBTupJcu5hF17iGmTP/Nj2XnaibQ== - dependencies: - setimmediate "^1.0.5" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth-abi "^4.1.4" - web3-eth-accounts "^4.1.0" - web3-net "^4.0.7" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-net@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.0.7.tgz#ed2c1bd700cf94be93a6dbd8bd8aa413d8681942" - integrity sha512-SzEaXFrBjY25iQGk5myaOfO9ZyfTwQEa4l4Ps4HDNVMibgZji3WPzpjq8zomVHMwi8bRp6VV7YS71eEsX7zLow== - dependencies: - web3-core "^4.3.0" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - -web3-providers-http@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.0.tgz#8d7afda67d1d8542ca85b30f60a3d1fe1993b561" - integrity sha512-6qRUGAhJfVQM41E5t+re5IHYmb5hSaLc02BE2MaRQsz2xKA6RjmHpOA5h/+ojJxEpI9NI2CrfDKOAgtJfoUJQg== - dependencies: - cross-fetch "^4.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - -web3-providers-ipc@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz#9ec4c8565053af005a5170ba80cddeb40ff3e3d3" - integrity sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g== - dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - -web3-providers-ws@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.7.tgz#7a78a0dcf077e0e802da524fbb37d080b356c14b" - integrity sha512-n4Dal9/rQWjS7d6LjyEPM2R458V8blRm0eLJupDEJOOIBhGYlxw5/4FthZZ/cqB7y/sLVi7K09DdYx2MeRtU5w== - dependencies: - "@types/ws" "8.5.3" - isomorphic-ws "^5.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - ws "^8.8.1" - -web3-rpc-methods@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.1.3.tgz#4be8a85628d8b69846e2e0afa0ed71e3f6eaf163" - integrity sha512-XB6SsCZZPdZUMPIRqDxJkZFKMu0/Y+yaExAt+Z7RqmuM7xF55fJ/Qb84LQho8zarvUoYziy4jnIfs+SXImxQUw== - dependencies: - web3-core "^4.3.0" - web3-types "^1.3.0" - web3-validator "^2.0.3" - -web3-types@^1.3.0, web3-types@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.3.1.tgz#cf6148ad46b68c5c89714613380b270d31e297be" - integrity sha512-8fXi7h/t95VKRtgU4sxprLPZpsTh3jYDfSghshIDBgUD/OoGe5S+syP24SUzBZYllZ/L+hMr2gdp/0bGJa8pYQ== - -web3-utils@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.0.7.tgz#7df497b7cdd06cdfe7d02036c45fecbe3382d137" - integrity sha512-sy8S6C2FIa5NNHc8DjND+Fx3S8KDAizuh5RbL1RX3h0PRbFgPfWzF5RfUns8gTt0mjJuOhs/IaDhrZfeTszG5A== - dependencies: - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-validator "^2.0.3" - -web3-validator@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.3.tgz#e5dcd4b4902612cff21b7f8817dd233393999d97" - integrity sha512-fJbAQh+9LSNWy+l5Ze6HABreml8fra98o5+vS073T35jUcLbRZ0IOjF/ZPJhJNbJDt+jP1vseZsc3z3uX9mxxQ== - dependencies: - ethereum-cryptography "^2.0.0" - util "^0.12.5" - web3-errors "^1.1.3" - web3-types "^1.3.0" - zod "^3.21.4" - -web3@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.2.2.tgz#3562e0b918a95adb0798945624e8e9377c699fdb" - integrity sha512-im7weoHY7TW87nhFk10ysupZnsDJEO/xDpz985AgrTd/7KxExlzjjKd+4nue0WskUF0th0mgoMs1YaA8xUjCjw== - dependencies: - web3-core "^4.3.1" - web3-errors "^1.1.4" - web3-eth "^4.3.1" - web3-eth-abi "^4.1.4" - web3-eth-accounts "^4.1.0" - web3-eth-contract "^4.1.3" - web3-eth-ens "^4.0.8" - web3-eth-iban "^4.0.7" - web3-eth-personal "^4.0.8" - web3-net "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - webcrypto-core@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.2.0.tgz#44fda3f9315ed6effe9a1e47466e0935327733b5" @@ -13088,17 +12748,6 @@ which-typed-array@^1.1.10, which-typed-array@^1.1.11: gopd "^1.0.1" has-tostringtag "^1.0.0" -which-typed-array@^1.1.2: - version "1.1.13" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" - integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.4" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -13168,7 +12817,7 @@ write@^1.0.3: dependencies: mkdirp "^0.5.1" -ws@7.4.6, ws@8.5.0, ws@^7.4.6, ws@^7.5.1, ws@^7.5.2, ws@^8.4.2, ws@^8.6.0, ws@^8.8.1, ws@^8.9.0: +ws@7.4.6, ws@8.5.0, ws@^7.4.6, ws@^7.5.1, ws@^7.5.2, ws@^8.4.2, ws@^8.6.0, ws@^8.9.0: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== @@ -13320,8 +12969,3 @@ zip-stream@^4.1.0: archiver-utils "^2.1.0" compress-commons "^4.1.0" readable-stream "^3.6.0" - -zod@^3.21.4: - version "3.22.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" - integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==