From 0e209163707dc96f8a85c4d7a46b90c4af32b262 Mon Sep 17 00:00:00 2001 From: kautuk kundan Date: Fri, 19 Nov 2021 23:00:22 +0530 Subject: [PATCH 1/5] updated confirm to use functional component --- extension/source/Background/promptUser.ts | 5 +- extension/source/Confirm/Confirm.tsx | 92 +++++++++-------------- 2 files changed, 37 insertions(+), 60 deletions(-) diff --git a/extension/source/Background/promptUser.ts b/extension/source/Background/promptUser.ts index 3a17af6f..631facf5 100644 --- a/extension/source/Background/promptUser.ts +++ b/extension/source/Background/promptUser.ts @@ -5,7 +5,6 @@ import getPropOrUndefined from '../helpers/getPropOrUndefined'; export default function promptUser(opt: { promptText: string; - buttons?: string[]; }): Promise { const cleanupTasks = new TaskQueue(); @@ -24,9 +23,7 @@ export default function promptUser(opt: { const popup = await browser.windows.create({ url: browser.runtime.getURL( - `confirm.html?promptText=${opt.promptText}&id=${id}&buttons=${( - opt.buttons ?? ['Yes', 'No'] - ).join(',')}`, + `confirm.html?promptText=${opt.promptText}&id=${id}`, ), type: 'popup', width: popupWidth, diff --git a/extension/source/Confirm/Confirm.tsx b/extension/source/Confirm/Confirm.tsx index 099263ff..fb31fff3 100644 --- a/extension/source/Confirm/Confirm.tsx +++ b/extension/source/Confirm/Confirm.tsx @@ -1,69 +1,49 @@ -import * as React from 'react'; +import React, { useEffect, useState } from 'react'; import { browser } from 'webextension-polyfill-ts'; import TaskQueue from '../common/TaskQueue'; -import Button from '../components/Button'; -import CompactQuillHeading from '../components/CompactQuillHeading'; - -type Props = { - _?: undefined; -}; -type State = { - _?: undefined; -}; +// hooks and services -export default class Popup extends React.Component { - cleanupTasks = new TaskQueue(); +// components, styles and UI +import Button from '../components/Button'; +import CompactQuillHeading from '../components/CompactQuillHeading'; - constructor(props: Props) { - super(props); +// interfaces +export interface ConfirmProps {} - this.state = {}; - } +const Confirm: React.FunctionComponent = () => { + const [id, setId] = useState(); + const [prompt, setPromt] = useState(); - componentWillUnmount(): void { - this.cleanupTasks.run(); - } + const cleanupTasks = new TaskQueue(); - render(): React.ReactNode { + useEffect(() => { const params = new URL(window.location.href).searchParams; - const id = params.get('id'); - const promptText = params.get('promptText') ?? '(promptText not set)'; - const buttons = parseButtons(params.get('buttons')); + setId(params.get('id')); + setPromt(params.get('promptText') || '(promptText not set)'); - return ( -
-
- -
-
-
{promptText}
-
- {buttons.map((btnText, i) => ( - - ))} -
-
- ); - } -} + return cleanupTasks.run(); + }, []); -function parseButtons(buttonsStr: string | null): string[] { - if (buttonsStr === null) { - return ['(buttons not set)']; - } + const respondTx = (result: string) => { + browser.runtime.sendMessage(undefined, { id, result }); + }; - if (buttonsStr === '') { - return []; - } + return ( +
+
+ +
+
+
{prompt}
+
+ + +
+
+ ); +}; - return buttonsStr.split(','); -} +export default Confirm; From ac4de066b69e8bc091d01a1514ac412d597fd35e Mon Sep 17 00:00:00 2001 From: kautuk kundan Date: Mon, 22 Nov 2021 03:45:13 +0530 Subject: [PATCH 2/5] fetch function signature from parity registry --- extension/.env.example | 1 + extension/source/Background/RequestHandler.ts | 14 ++-- extension/source/Background/promptUser.ts | 4 +- extension/source/Confirm/Confirm.tsx | 37 ++++++++--- extension/source/Confirm/styles.scss | 7 +- .../Popup/helpers/formatCompactAddress.ts | 2 +- extension/source/env.ts | 1 + extension/source/hooks/useInputDecode.tsx | 65 +++++++++++++++++++ 8 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 extension/source/hooks/useInputDecode.tsx diff --git a/extension/.env.example b/extension/.env.example index f2b4f3bb..6c84b018 100644 --- a/extension/.env.example +++ b/extension/.env.example @@ -3,5 +3,6 @@ PRIVATE_KEY_STORAGE_KEY=default-private-key AGGREGATOR_URL=http://localhost:3000 CHAIN_RPC_URL=http://localhost:8545 CREATE_TX_URL= +PROVIDER_URL= VERIFICATION_GATEWAY_ADDRESS=0xCbd1D63d0Ca2228484a7772733717e934CD52aC3 diff --git a/extension/source/Background/RequestHandler.ts b/extension/source/Background/RequestHandler.ts index 6bb7291f..a1ec4805 100644 --- a/extension/source/Background/RequestHandler.ts +++ b/extension/source/Background/RequestHandler.ts @@ -59,16 +59,10 @@ export default function RequestHandler( throw new Error('No wallet available'); } - let promptText: string; - if (tx.data === '0x') { - promptText = `ETH Transfer - ${formatBalance(tx.value, 'ETH')} - to ${formatCompactAddress(tx.to)}`; - } else { - promptText = `Contract Interaction ${formatCompactAddress(tx.to)} - value ${formatBalance(tx.value, 'ETH')} - data ${tx.data}`; - } + const promptText = ` + &to=${formatCompactAddress(tx.to)} + &data=${tx.data} + &value=${formatBalance(tx.value, 'ETH')}`; const promptResult = await promptUser({ promptText, diff --git a/extension/source/Background/promptUser.ts b/extension/source/Background/promptUser.ts index 631facf5..d5a2b4aa 100644 --- a/extension/source/Background/promptUser.ts +++ b/extension/source/Background/promptUser.ts @@ -22,9 +22,7 @@ export default function promptUser(opt: { } const popup = await browser.windows.create({ - url: browser.runtime.getURL( - `confirm.html?promptText=${opt.promptText}&id=${id}`, - ), + url: browser.runtime.getURL(`confirm.html?${opt.promptText}&id=${id}`), type: 'popup', width: popupWidth, height: 500, diff --git a/extension/source/Confirm/Confirm.tsx b/extension/source/Confirm/Confirm.tsx index fb31fff3..5fce5e30 100644 --- a/extension/source/Confirm/Confirm.tsx +++ b/extension/source/Confirm/Confirm.tsx @@ -7,20 +7,27 @@ import TaskQueue from '../common/TaskQueue'; // components, styles and UI import Button from '../components/Button'; import CompactQuillHeading from '../components/CompactQuillHeading'; +import { useInputDecode } from '../hooks/useInputDecode'; // interfaces export interface ConfirmProps {} const Confirm: React.FunctionComponent = () => { - const [id, setId] = useState(); - const [prompt, setPromt] = useState(); + const [id, setId] = useState(null); + const [to, setTo] = useState(null); + const [value, setValue] = useState(null); + const [data, setData] = useState(''); + + const { loading, method } = useInputDecode(data); const cleanupTasks = new TaskQueue(); useEffect(() => { const params = new URL(window.location.href).searchParams; setId(params.get('id')); - setPromt(params.get('promptText') || '(promptText not set)'); + setTo(params.get('to')); + setValue(params.get('value')); + setData(params.get('data') || '0x'); return cleanupTasks.run(); }, []); @@ -35,12 +42,24 @@ const Confirm: React.FunctionComponent = () => {
-
{prompt}
-
- - + {loading ? ( + 'loading...' + ) : ( + <> +
{method}
+
to: {to}
+
value: {value}
+
+ data: +
{data}
+
+ + + + + )}
); diff --git a/extension/source/Confirm/styles.scss b/extension/source/Confirm/styles.scss index e9d067cc..8d006797 100644 --- a/extension/source/Confirm/styles.scss +++ b/extension/source/Confirm/styles.scss @@ -1,4 +1,4 @@ -@import "../styles/quill"; +@import '../styles/quill'; .quill { .prompt { @@ -7,4 +7,9 @@ flex-direction: column; gap: 12px; } + + .data { + word-wrap: break-word; + opacity: 0.5; + } } diff --git a/extension/source/Popup/helpers/formatCompactAddress.ts b/extension/source/Popup/helpers/formatCompactAddress.ts index ecb7d616..3cd5b521 100644 --- a/extension/source/Popup/helpers/formatCompactAddress.ts +++ b/extension/source/Popup/helpers/formatCompactAddress.ts @@ -1,3 +1,3 @@ export default function formatCompactAddress(address: string): string { - return `0x ${address.slice(2, 6)} ... ${address.slice(-4)}`; + return `0x${address.slice(2, 6)}...${address.slice(-4)}`; } diff --git a/extension/source/env.ts b/extension/source/env.ts index f64f381c..4d3f7b28 100644 --- a/extension/source/env.ts +++ b/extension/source/env.ts @@ -1,6 +1,7 @@ import { requireEnv, requireIntEnv } from './helpers/envTools'; export const CHAIN_ID = requireIntEnv(process.env.CHAIN_ID); +export const PROVIDER_URL = requireEnv(process.env.PROVIDER_URL); export const PRIVATE_KEY_STORAGE_KEY = requireEnv( process.env.PRIVATE_KEY_STORAGE_KEY, diff --git a/extension/source/hooks/useInputDecode.tsx b/extension/source/hooks/useInputDecode.tsx new file mode 100644 index 00000000..c99e74de --- /dev/null +++ b/extension/source/hooks/useInputDecode.tsx @@ -0,0 +1,65 @@ +import { ethers } from 'ethers'; +import { useEffect, useState } from 'react'; +import { PROVIDER_URL } from '../env'; + +const getParitySigRegistry = () => { + const provider = new ethers.providers.JsonRpcProvider(PROVIDER_URL); + const address = '0x44691B39d1a75dC4E0A0346CBB15E310e6ED1E86'; + const abi = [ + { + constant: true, + inputs: [{ name: '', type: 'bytes4' }], + name: 'entries', + outputs: [{ name: '', type: 'string' }], + payable: false, + type: 'function', + }, + ]; + + return new ethers.Contract(address, abi, provider); +}; + +export const useInputDecode = (functionData: string) => { + const [loading, setLoading] = useState(true); + const [method, setMethod] = useState('CONTRACT INTERACTION'); + + const getMethodFromOnChainRegistry = async (data: string) => { + if (data === '0x') return 'SENDING ETH'; + + const methodID = ethers.utils.hexDataSlice(data, 0, 4); + const registry = getParitySigRegistry(); + + return registry.entries(methodID); + }; + + useEffect(() => { + const getMethod = async () => { + setLoading(true); + + let method; + try { + method = await getMethodFromOnChainRegistry( + functionData?.replace(/\s+/g, ''), + ); + } catch (error) { + console.log({ error }); + } + + if (method) { + setMethod( + method + .split('(')[0] + .replace(/([a-z](?=[A-Z]))/g, '$1 ') + .toUpperCase(), + ); + } + setLoading(false); + }; + + if (functionData) { + getMethod(); + } + }, [functionData]); + + return { loading, method }; +}; From cdd12aa03f0f2e27755e2950cc262f4832e5d715 Mon Sep 17 00:00:00 2001 From: kautuk kundan Date: Mon, 22 Nov 2021 05:04:51 +0530 Subject: [PATCH 3/5] fetch function signature from etherscan --- contracts/shared/lib/hubble-bls | 1 + extension/.env.example | 1 + extension/source/Background/RequestHandler.ts | 6 +- extension/source/Confirm/Confirm.tsx | 27 ++++----- extension/source/env.ts | 1 + extension/source/hooks/useInputDecode.tsx | 55 +++++++++++++------ 6 files changed, 54 insertions(+), 37 deletions(-) create mode 160000 contracts/shared/lib/hubble-bls diff --git a/contracts/shared/lib/hubble-bls b/contracts/shared/lib/hubble-bls new file mode 160000 index 00000000..b377044a --- /dev/null +++ b/contracts/shared/lib/hubble-bls @@ -0,0 +1 @@ +Subproject commit b377044abb5d5bca441647a748219e5adbd556f6 diff --git a/extension/.env.example b/extension/.env.example index 6c84b018..777d5f14 100644 --- a/extension/.env.example +++ b/extension/.env.example @@ -4,5 +4,6 @@ AGGREGATOR_URL=http://localhost:3000 CHAIN_RPC_URL=http://localhost:8545 CREATE_TX_URL= PROVIDER_URL= +ETHERSCAN_KEY= VERIFICATION_GATEWAY_ADDRESS=0xCbd1D63d0Ca2228484a7772733717e934CD52aC3 diff --git a/extension/source/Background/RequestHandler.ts b/extension/source/Background/RequestHandler.ts index a1ec4805..4377bd2e 100644 --- a/extension/source/Background/RequestHandler.ts +++ b/extension/source/Background/RequestHandler.ts @@ -4,8 +4,6 @@ import App from '../App'; import addErrorContext from '../common/addErrorContext'; import RpcMap from '../common/RpcMap'; import validateOptionalStringRecord from '../common/validateOptionalStringRecord'; -import formatBalance from '../Popup/helpers/formatBalance'; -import formatCompactAddress from '../Popup/helpers/formatCompactAddress'; import promptUser from './promptUser'; export default function RequestHandler( @@ -60,9 +58,9 @@ export default function RequestHandler( } const promptText = ` - &to=${formatCompactAddress(tx.to)} + &to=${tx.to} &data=${tx.data} - &value=${formatBalance(tx.value, 'ETH')}`; + &value=${tx.value}`; const promptResult = await promptUser({ promptText, diff --git a/extension/source/Confirm/Confirm.tsx b/extension/source/Confirm/Confirm.tsx index 5fce5e30..9f5782d2 100644 --- a/extension/source/Confirm/Confirm.tsx +++ b/extension/source/Confirm/Confirm.tsx @@ -1,32 +1,29 @@ +import { ethers } from 'ethers'; import React, { useEffect, useState } from 'react'; import { browser } from 'webextension-polyfill-ts'; import TaskQueue from '../common/TaskQueue'; -// hooks and services - // components, styles and UI import Button from '../components/Button'; import CompactQuillHeading from '../components/CompactQuillHeading'; import { useInputDecode } from '../hooks/useInputDecode'; +import formatCompactAddress from '../Popup/helpers/formatCompactAddress'; -// interfaces -export interface ConfirmProps {} - -const Confirm: React.FunctionComponent = () => { - const [id, setId] = useState(null); - const [to, setTo] = useState(null); - const [value, setValue] = useState(null); +const Confirm: React.FunctionComponent = () => { + const [id, setId] = useState(); + const [to, setTo] = useState('0x'); + const [value, setValue] = useState('0'); const [data, setData] = useState(''); - const { loading, method } = useInputDecode(data); + const { loading, method } = useInputDecode(data, to || '0x'); const cleanupTasks = new TaskQueue(); useEffect(() => { const params = new URL(window.location.href).searchParams; - setId(params.get('id')); - setTo(params.get('to')); - setValue(params.get('value')); + setId(params.get('id') || '0'); + setTo(params.get('to') || '0x'); + setValue(params.get('value') || '0'); setData(params.get('data') || '0x'); return cleanupTasks.run(); @@ -47,8 +44,8 @@ const Confirm: React.FunctionComponent = () => { ) : ( <>
{method}
-
to: {to}
-
value: {value}
+
to: {formatCompactAddress(to)}
+
value: {ethers.utils.formatEther(value)} ETH
data:
{data}
diff --git a/extension/source/env.ts b/extension/source/env.ts index 4d3f7b28..ca0b7ca9 100644 --- a/extension/source/env.ts +++ b/extension/source/env.ts @@ -2,6 +2,7 @@ import { requireEnv, requireIntEnv } from './helpers/envTools'; export const CHAIN_ID = requireIntEnv(process.env.CHAIN_ID); export const PROVIDER_URL = requireEnv(process.env.PROVIDER_URL); +// export const ETHERSCAN_KEY = requireEnv(process.env.ETHERSCAN_KEY); export const PRIVATE_KEY_STORAGE_KEY = requireEnv( process.env.PRIVATE_KEY_STORAGE_KEY, diff --git a/extension/source/hooks/useInputDecode.tsx b/extension/source/hooks/useInputDecode.tsx index c99e74de..d6534ae4 100644 --- a/extension/source/hooks/useInputDecode.tsx +++ b/extension/source/hooks/useInputDecode.tsx @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; import { useEffect, useState } from 'react'; import { PROVIDER_URL } from '../env'; +import axios from 'axios'; const getParitySigRegistry = () => { const provider = new ethers.providers.JsonRpcProvider(PROVIDER_URL); @@ -19,39 +20,57 @@ const getParitySigRegistry = () => { return new ethers.Contract(address, abi, provider); }; -export const useInputDecode = (functionData: string) => { - const [loading, setLoading] = useState(true); - const [method, setMethod] = useState('CONTRACT INTERACTION'); +const getMethodFromOnChainRegistry = async (data: string) => { + if (data === '0x') return 'SENDING ETH'; + + const methodID = ethers.utils.hexDataSlice(data, 0, 4); + const registry = getParitySigRegistry(); + + return registry.entries(methodID); +}; + +const getMethodFromEtherscan = async (to: string, data: string) => { + const res = await axios.get( + `https://api.etherscan.io/api?module=contract&action=getabi&address=${to}`, + ); - const getMethodFromOnChainRegistry = async (data: string) => { - if (data === '0x') return 'SENDING ETH'; + if (res.data.result !== 'Contract source code not verified') { + const iface = new ethers.utils.Interface(res.data.result); + return iface.parseTransaction({ data, value: 1 }).name; + } - const methodID = ethers.utils.hexDataSlice(data, 0, 4); - const registry = getParitySigRegistry(); + throw 'Unverified Contract'; +}; + +const formatMethod = (method: string) => { + return method + .split('(')[0] + .replace(/([a-z](?=[A-Z]))/g, '$1 ') + .toUpperCase(); +}; - return registry.entries(methodID); - }; +export const useInputDecode = (functionData: string, to: string) => { + const [loading, setLoading] = useState(true); + const [method, setMethod] = useState('CONTRACT INTERACTION'); useEffect(() => { const getMethod = async () => { setLoading(true); + const data = functionData?.replace(/\s+/g, ''); + let method; try { - method = await getMethodFromOnChainRegistry( - functionData?.replace(/\s+/g, ''), - ); + method = await getMethodFromOnChainRegistry(data); + if (!method) { + method = await getMethodFromEtherscan(to, data); + } } catch (error) { console.log({ error }); } if (method) { - setMethod( - method - .split('(')[0] - .replace(/([a-z](?=[A-Z]))/g, '$1 ') - .toUpperCase(), - ); + setMethod(formatMethod(method)); } setLoading(false); }; From 4908dd471c9babc14229cdcee309af5a26d1e5a9 Mon Sep 17 00:00:00 2001 From: kautuk kundan Date: Mon, 22 Nov 2021 05:16:08 +0530 Subject: [PATCH 4/5] minor change when setting default values of states --- extension/source/Confirm/Confirm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/source/Confirm/Confirm.tsx b/extension/source/Confirm/Confirm.tsx index 9f5782d2..fdd9f50f 100644 --- a/extension/source/Confirm/Confirm.tsx +++ b/extension/source/Confirm/Confirm.tsx @@ -13,9 +13,9 @@ const Confirm: React.FunctionComponent = () => { const [id, setId] = useState(); const [to, setTo] = useState('0x'); const [value, setValue] = useState('0'); - const [data, setData] = useState(''); + const [data, setData] = useState('0x'); - const { loading, method } = useInputDecode(data, to || '0x'); + const { loading, method } = useInputDecode(data, to); const cleanupTasks = new TaskQueue(); From fcec9fccddf4a6ba34a7fa8a5684854e924b8674 Mon Sep 17 00:00:00 2001 From: kautuk kundan Date: Tue, 23 Nov 2021 23:15:05 +0530 Subject: [PATCH 5/5] refactored input decode hook --- extension/source/hooks/useInputDecode.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/extension/source/hooks/useInputDecode.tsx b/extension/source/hooks/useInputDecode.tsx index d6534ae4..e1320f45 100644 --- a/extension/source/hooks/useInputDecode.tsx +++ b/extension/source/hooks/useInputDecode.tsx @@ -59,19 +59,17 @@ export const useInputDecode = (functionData: string, to: string) => { const data = functionData?.replace(/\s+/g, ''); - let method; try { - method = await getMethodFromOnChainRegistry(data); - if (!method) { - method = await getMethodFromEtherscan(to, data); + const registryPromise = getMethodFromOnChainRegistry(data); + const etherScanPromise = getMethodFromEtherscan(to, data); + const method = (await registryPromise) ?? (await etherScanPromise); + if (method) { + setMethod(formatMethod(method)); } } catch (error) { console.log({ error }); } - if (method) { - setMethod(formatMethod(method)); - } setLoading(false); };