diff --git a/.eslintrc.js b/.eslintrc.js index e0e0644..e315a62 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,7 +7,7 @@ module.exports = { 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier - 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + 'plugin:prettier/recommended' // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. ], ignorePatterns: ['.eslintrc.js'], env: { @@ -19,45 +19,63 @@ module.exports = { sourceType: 'module', // Allows for the use of imports // project: 'tsconfig.json', ecmaFeatures: { - jsx: true, // Allows for the parsing of JSX + jsx: true // Allows for the parsing of JSX }, project: './tsconfig.json' }, parser: '@typescript-eslint/parser', // Specifies the ESLint parser settings: { react: { - version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use - }, + version: 'detect' // Tells eslint-plugin-react to automatically detect the version of React to use + } }, rules: { // 'react/prop-types': 0, // no requiring of prop types 'react/display-name': 0, // allow anonymous components - 'strict': [2, 'safe'], - 'no-debugger': 2, - 'brace-style': [2, '1tbs', { 'allowSingleLine': true }], - 'no-trailing-spaces': [2, { 'skipBlankLines': true }], - 'keyword-spacing': 2, + strict: [2, 'safe'], + 'no-debugger': 'error', + 'brace-style': ['error', '1tbs', {allowSingleLine: true}], + 'keyword-spacing': ['error'], + 'comma-spacing': 'error', + 'object-curly-spacing': ['error', 'never'], + 'object-curly-newline': ['error', {multiline: true}], + 'array-bracket-spacing': ['error', 'never'], 'spaced-comment': [2, 'always'], 'vars-on-top': 0, // Disable: all 'var' declarations must be at the top of the function scope 'no-undef': 0, 'no-undefined': 0, - 'comma-dangle': [0, 'never'], - 'quotes': [2, 'double'], - 'semi': 1, + 'comma-dangle': ['error', 'never'], + quotes: ['error', 'single', {allowTemplateLiterals: true}], + semi: ['error', 'always'], 'guard-for-in': 0, // allow iterating with for..in without checking for Object.hasOwnProperty 'no-eval': 2, 'no-with': 2, 'valid-typeof': 2, - 'no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }], - '@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }], + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + {argsIgnorePattern: '^_', varsIgnorePattern: '^_'} + ], 'no-continue': 1, - 'no-unreachable': 1, - 'no-unused-expressions': 1, + 'no-unreachable': 'error', + 'no-unused-expressions': 'error', + 'no-duplicate-imports': 'error', 'no-magic-numbers': 0, 'no-inner-declarations': 0, 'no-constant-condition': 0, 'no-empty-function': 0, - 'max-len': [1, 120, 4], + 'arrow-body-style': ['error', 'as-needed'], + 'max-len': [ + 'warn', + { + code: 100, + ignoreStrings: true, + ignoreRegExpLiterals: true, + ignoreTemplateLiterals: true, + tabWidth: 4, + ignoreComments: true + } + ], 'react/prefer-es6-class': 1, '@typescript-eslint/explicit-function-return-type': 0, '@typescript-eslint/no-use-before-define': 0, @@ -68,5 +86,31 @@ module.exports = { '@typescript-eslint/no-extra-semi': 0, '@typescript-eslint/no-empty-function': ['off'], '@typescript-eslint/ban-ts-comment': 0, - }, + semi: ['error', 'always'], + 'linebreak-style': ['error', 'unix'], + curly: ['error', 'multi-line'], + indent: [ + 'error', + 4, + { + SwitchCase: 1, + MemberExpression: 1, + ArrayExpression: 1, + ObjectExpression: 1, + VariableDeclarator: 1, + CallExpression: {arguments: 1}, + offsetTernaryExpressions: true + } + ], + 'space-in-parens': ['error', 'never'], + 'no-case-declarations': 'warn', + eqeqeq: ['error', 'always', {null: 'ignore'}], + 'prefer-const': ['error', {destructuring: 'all', ignoreReadBeforeAssign: false}], + 'no-multiple-empty-lines': ['warn', {max: 1, maxEOF: 0}], + '@typescript-eslint/no-unsafe-call': 'warn', // TODO: fix and delete rule + '@typescript-eslint/no-unsafe-assignment': 'warn', // TODO: fix and delete rule + '@typescript-eslint/no-unsafe-member-access': 'warn', // TODO: fix and delete rule + '@typescript-eslint/restrict-template-expressions': 'warn', // TODO: fix and delete rule + '@typescript-eslint/no-unsafe-return': 'warn' // TODO: fix and delete rule + } }; diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..73a76b9 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,20 @@ +# This workflow will run a lint check +name: Run ESLint + +on: + pull_request: + branches: [ develop ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + - name: Install dependencies + run: npm ci + - name: Run ESLint + run: npm run lint diff --git a/.gitignore b/.gitignore index 6b2d557..2761f45 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ npm-debug.log /.parcel-cache .DS_Store web-ext-artifacts - +/.idea diff --git a/.nvmrc b/.nvmrc index e1b60f0..6f7f377 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v14.8.0 +v16 diff --git a/.prettierignore b/.prettierignore index c1710a6..f0966f1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ node_modules/* -*.snap \ No newline at end of file +*.snap diff --git a/.prettierrc.js b/.prettierrc.js index 5c9d522..7a6ea4d 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,6 +1,10 @@ module.exports = { semi: true, - trailingComma: "all", - singleQuote: false, + trailingComma: "none", + singleQuote: true, + printWidth: 100, tabWidth: 4, + useTabs: false, + bracketSpacing: false, + arrowParens: "avoid", }; diff --git a/package-lock.json b/package-lock.json index a2da0c6..505768c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pointnetwork-browser-extension", - "version": "0.0.41", + "version": "0.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pointnetwork-browser-extension", - "version": "0.0.41", + "version": "0.1.1", "dependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", diff --git a/package.json b/package.json index 7365f27..5712f15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pointnetwork-browser-extension", - "version": "0.0.41", + "version": "0.1.1", "description": "Point Browser Extension", "alias": { "pointsdk": "./src" @@ -83,6 +83,7 @@ "scripts": { "start": "npm run cleanup && parcel src/manifest.json --host localhost --target dev", "build": "npm run cleanup && parcel build src/manifest.json --target prod --no-source-maps", + "watch": "npm run cleanup && parcel watch src/manifest.json --target prod", "build:sdk": "npx parcel@2.4.1 build src/pointsdk/browser.ts --target sdk", "cleanup": "rm -rf dist .cache .parcel-cache", "test": "jest --config=jest.config.js", diff --git a/src/background/confirmationWindowApi.ts b/src/background/confirmationWindowApi.ts index f9c18cb..a503609 100644 --- a/src/background/confirmationWindowApi.ts +++ b/src/background/confirmationWindowApi.ts @@ -1,4 +1,4 @@ -import browser from "webextension-polyfill"; +import browser from 'webextension-polyfill'; let windowId: number | null = null; export const displayConfirmationWindow = async ( @@ -7,21 +7,21 @@ export const displayConfirmationWindow = async ( host: string, network: string, params = {}, - decodedTxData = {}, + decodedTxData = {} ) => { const query = new URLSearchParams(); - query.append("reqId", reqId); - query.append("pointId", pointId); - query.append("host", host); - query.append("network", network); - query.append("params", JSON.stringify(params)); - query.append("decodedTxData", JSON.stringify(decodedTxData)); + query.append('reqId', reqId); + query.append('pointId', pointId); + query.append('host', host); + query.append('network', network); + query.append('params', JSON.stringify(params)); + query.append('decodedTxData', JSON.stringify(decodedTxData)); const win = await browser.windows.create({ - type: "detached_panel", - width: 400, + type: 'detached_panel', + width: 512, height: 600, - url: `./confirmation-window/index.html?${query.toString()}`, + url: `./confirmation-window/index.html?${query.toString()}` }); windowId = win.id!; }; diff --git a/src/background/index.ts b/src/background/index.ts index 7996334..82d8548 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,88 +1,67 @@ -import browser from "webextension-polyfill"; +import browser from 'webextension-polyfill'; import { rpcListener, confirmationWindowListener, registerHandlerListener, setAuthTokenHandler, - getAuthToken, -} from "pointsdk/background/messaging"; + getAuthToken +} from 'pointsdk/background/messaging'; const setChainIds = async () => { try { const token = (await getAuthToken()).token; - const networksRes = await fetch( - "https://point/v1/api/blockchain/networks", - { - headers: { - "X-Point-Token": `Bearer ${token}`, - }, - }, - ); - const oldDefaultNetwork = await browser.storage.local.get( - "default_network", - ); - const { networks, default_network } = await networksRes.json(); + const opts = {headers: {'X-Point-Token': `Bearer ${token}`}}; + const networksRes = await fetch('https://point/v1/api/blockchain/networks', opts); + const oldDefaultNetwork = await browser.storage.local.get('default_network'); + const {networks, default_network} = await networksRes.json(); // TODO: if the default network changed we need to update it everywhere // therefore we need to clear the prevoius setting, but this is not polite // need to think of a better solution... if (oldDefaultNetwork && oldDefaultNetwork !== default_network) { - await browser.storage.local.remove("chainIdGlobal"); // it will be set up below + await browser.storage.local.remove('chainIdGlobal'); // it will be set up below const storage = await browser.storage.local.get(null); for (const key in storage) { - if (key.startsWith("chainId")) { - await browser.storage.local.set({ - [key]: default_network as string, - }); + if (key.startsWith('chainId')) { + await browser.storage.local.set({[key]: default_network as string}); } } } await browser.storage.local.set({ networks: JSON.stringify(networks), - default_network, + default_network }); - const { chainIdGlobal } = await browser.storage.local.get( - "chainIdGlobal", - ); + const {chainIdGlobal} = await browser.storage.local.get('chainIdGlobal'); if (!chainIdGlobal || !(chainIdGlobal in networks)) { - await browser.storage.local.set({ - chainIdGlobal: default_network, - }); + await browser.storage.local.set({chainIdGlobal: default_network}); } } catch (e) { - console.error("Failed to fetch networks info from the node: ", e); + console.error('Failed to fetch networks info from the node: ', e); setTimeout(() => { void setChainIds(); }, 1000); } }; -setChainIds(); +void setChainIds(); browser.runtime.onMessage.addListener(async (message, sender) => { switch (message.__message_type) { - case "rpc": + case 'rpc': return rpcListener(message); - case "registerHandler": + case 'registerHandler': return registerHandlerListener(message); - case "setAuthToken": + case 'setAuthToken': if (!sender.url?.match(/^https:\/\/point/)) { - console.error( - "Attempt to set auth token from unauthorized host", - ); + console.error('Attempt to set auth token from unauthorized host'); break; } return setAuthTokenHandler(message); - case "getAuthToken": + case 'getAuthToken': return getAuthToken(); default: - if (sender.url?.match("confirmation-window")) { + if (sender.url?.match('confirmation-window')) { return confirmationWindowListener(message); } - console.error( - "Unexpected runtime message: ", - message, - " from sender: ", - sender, - ); + console.error('Unexpected runtime message: ', message, ' from sender: ', sender); } }); diff --git a/src/background/messaging.ts b/src/background/messaging.ts index aaca05e..7dfbe42 100644 --- a/src/background/messaging.ts +++ b/src/background/messaging.ts @@ -1,100 +1,88 @@ -import { v4 as uuid } from "uuid"; -import { - closeConfirmationWindow, - displayConfirmationWindow, -} from "./confirmationWindowApi"; -import { sign } from "jsonwebtoken"; +import {v4 as uuid} from 'uuid'; +import {closeConfirmationWindow, displayConfirmationWindow} from './confirmationWindowApi'; +import {sign} from 'jsonwebtoken'; -const socket = new WebSocket("wss://point/ws"); +const socket = new WebSocket('wss://point/ws'); const responseHandlers: Record void> = {}; socket.onopen = () => { - console.log("WS Connection with point node established"); + console.log('WS Connection with point node established'); }; -socket.onmessage = (e) => { +socket.onmessage = e => { const payload = JSON.parse(e.data); - console.log("Message from node: ", payload); + console.log('Message from node: ', payload); if (!payload?.request?.__point_id) { - console.error( - "Unexpected message without __point_id from point node: ", - payload, - ); + console.error('Unexpected message without __point_id from point node: ', payload); return; } if (!responseHandlers[payload.request.__point_id]) { - console.error("No handler for message from point node: ", payload); + console.error('No handler for message from point node: ', payload); return; } if (payload.data) { if (payload.data.reqId) { const params = - payload.request.method === "solana_sendTransaction" && + payload.request.method === 'solana_sendTransaction' && Array.isArray(payload.request.params[0].instructions) ? payload.request.params[0].instructions[0] : payload.request.params[0]; - displayConfirmationWindow( + void displayConfirmationWindow( payload.data.reqId, payload.request.__point_id, payload.request.__hostname, payload.data.network, params, - payload.data.decodedTxData, + payload.data.decodedTxData ); } else { responseHandlers[payload.request.__point_id](payload.data); delete responseHandlers[payload.request.__point_id]; } } else { - console.error( - "Unexpected message without data from point node: ", - payload, - ); + console.error('Unexpected message without data from point node: ', payload); } }; -socket.onerror = (err) => { - console.error("WS error: ", err); +socket.onerror = err => { + console.error('WS error: ', err); }; export const rpcListener = async (message: any) => { const messageId = uuid(); let network; switch (message.__provider) { - case "eth": - const globalChainId = ( - await browser.storage.local.get("chainIdGlobal") - ).chainIdGlobal as string; - const { host } = new URL(message.__hostname); - const hostChainId = ( - await browser.storage.local.get(`chainId_${host}`) - )[`chainId_${host}`] as string; + case 'eth': + const globalChainId = (await browser.storage.local.get('chainIdGlobal')) + .chainIdGlobal as string; + const {host} = new URL(message.__hostname); + const hostChainId = (await browser.storage.local.get(`chainId_${host}`))[ + `chainId_${host}` + ] as string; network = hostChainId ?? globalChainId; break; - case "solana": - network = "solana_devnet"; // TODO + case 'solana': + network = 'solana_devnet'; // TODO break; default: - throw new Error( - `Unknown or missing provider type ${message.__provider}`, - ); + throw new Error(`Unknown or missing provider type ${message.__provider}`); } const msg = { ...message, network, - type: "rpc", + type: 'rpc', __point_id: messageId, - __point_token: (await getAuthToken()).token, + __point_token: (await getAuthToken()).token }; - console.log("Sending msg to node: ", msg); + console.log('Sending msg to node: ', msg); socket.send(JSON.stringify(msg)); - return new Promise((resolve) => { + return new Promise(resolve => { responseHandlers[messageId] = resolve; }); }; @@ -102,22 +90,18 @@ export const rpcListener = async (message: any) => { export const confirmationWindowListener = async (message: any) => { if (message.confirm) { const msg = { - method: "eth_confirmTransaction", - type: "rpc", + method: 'eth_confirmTransaction', + type: 'rpc', __point_id: message.pointId, __point_token: (await getAuthToken()).token, - params: [ - { - reqId: message.reqId, - }, - ], + params: [{reqId: message.reqId}] }; - console.log("Sending confirmation msg to node, ", msg); + console.log('Sending confirmation msg to node, ', msg); socket.send(JSON.stringify(msg)); } else { responseHandlers[message.pointId]({ code: 4001, - message: "User rejected the request", + message: 'User rejected the request' }); delete responseHandlers[message.pointId]; } @@ -125,40 +109,33 @@ export const confirmationWindowListener = async (message: any) => { }; export const registerHandlerListener = async (message: any) => - new Promise((resolve) => { + new Promise(resolve => { responseHandlers[message.messageId] = resolve; }); export const setAuthTokenHandler = async (message: any) => { - const oldToken = await browser.storage.local.get("point_token"); + const oldToken = await browser.storage.local.get('point_token'); if (oldToken) { // Checking if new token is correct, if we are replacing the token, // otherwise just inject it - const jwt = sign({ payload: "point_token" }, message.token, { - expiresIn: "10s", - }); - const res = await fetch("https://point/v1/api/blockchain/networks", { - headers: { - "X-Point-Token": `Bearer ${jwt}`, - }, - }); + const jwt = sign({payload: 'point_token'}, message.token, {expiresIn: '10s'}); + const opts = {headers: {'X-Point-Token': `Bearer ${jwt}`}}; + const res = await fetch('https://point/v1/api/blockchain/networks', opts); if (res.status !== 200) { // Not throwing error here, otherwise explorer will not redirect // Just ignoring the token instead - return { ok: true }; + return {ok: true}; } } - await browser.storage.local.set({ point_token: message.token }); - return { ok: true }; + await browser.storage.local.set({point_token: message.token}); + return {ok: true}; }; export const getAuthToken = async () => { - const { point_token } = await browser.storage.local.get("point_token"); + const {point_token} = await browser.storage.local.get('point_token'); if (!point_token) { - throw new Error("Point token not set"); + throw new Error('Point token not set'); } - const jwt = sign({ payload: "point_token" }, point_token, { - expiresIn: "10s", - }); - return { token: jwt }; + const jwt = sign({payload: 'point_token'}, point_token, {expiresIn: '10s'}); + return {token: jwt}; }; diff --git a/src/confirmation-window/App.tsx b/src/confirmation-window/App.tsx index 2e31a05..4bc4f8e 100644 --- a/src/confirmation-window/App.tsx +++ b/src/confirmation-window/App.tsx @@ -1,7 +1,7 @@ -import React, { FunctionComponent } from "react"; -import { BrowserRouter } from "react-router-dom"; -import UIThemeProvider from "pointsdk/theme/UIThemeProvider"; -import ConfirmationWindow from "./ConfirmationWindow"; +import React, {FunctionComponent} from 'react'; +import {BrowserRouter} from 'react-router-dom'; +import UIThemeProvider from 'pointsdk/theme/UIThemeProvider'; +import ConfirmationWindow from './ConfirmationWindow'; const App: FunctionComponent = () => ( diff --git a/src/confirmation-window/ConfirmationWindow.tsx b/src/confirmation-window/ConfirmationWindow.tsx index cee6be2..f1826e9 100644 --- a/src/confirmation-window/ConfirmationWindow.tsx +++ b/src/confirmation-window/ConfirmationWindow.tsx @@ -1,29 +1,49 @@ -import React, { ReactEventHandler, useMemo } from "react"; -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import Typography from "@mui/material/Typography"; -import useTheme from "@mui/material/styles/useTheme"; -import { useLocation } from "react-router-dom"; -import browser from "webextension-polyfill"; -import TxDetails from "./components/TxDetails"; +import React, {ReactEventHandler, useMemo} from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; +import {useLocation} from 'react-router-dom'; +import browser from 'webextension-polyfill'; +import TxDetails from './components/TxDetails'; +import {DecodedTxInput} from '../pointsdk/index.d'; const ConfirmationWindow = () => { const theme = useTheme(); - const { search } = useLocation(); + const {search} = useLocation(); const query = useMemo(() => new URLSearchParams(search), [search]); + const network = useMemo(() => query.get('network') || '', [query]); + + const rawParams = useMemo((): Record => { + try { + const str = query.get('params'); + return str ? (JSON.parse(str) as Record) : {}; + } catch { + return {}; + } + }, [query]); + + const decodedTxData = useMemo((): DecodedTxInput | null => { + try { + const str = query.get('decodedTxData'); + return str ? (JSON.parse(str) as DecodedTxInput) : null; + } catch { + return null; + } + }, [query]); const handleAllow: ReactEventHandler = async () => { await browser.runtime.sendMessage({ confirm: true, - reqId: query.get("reqId"), - pointId: query.get("pointId"), + reqId: query.get('reqId'), + pointId: query.get('pointId') }); }; const handleCancel: ReactEventHandler = async () => { await browser.runtime.sendMessage({ confirm: false, - pointId: query.get("pointId"), + pointId: query.get('pointId') }); }; @@ -42,32 +62,28 @@ const ConfirmationWindow = () => { pb={1} pt={2} sx={{ - overflowWrap: "break-word", - wordWrap: "break-word", + overflowWrap: 'break-word', + wordWrap: 'break-word' }} > - {query.get("host")?.replace(/^https?:\/\//, "")} + {query.get('host')?.replace(/^https?:\/\//, '')} + + + is trying to send a transaction to the blockchain on your behalf. - is trying to send a transaction - + - + diff --git a/src/confirmation-window/components/Address.tsx b/src/confirmation-window/components/Address.tsx index c4b3821..816c5a8 100644 --- a/src/confirmation-window/components/Address.tsx +++ b/src/confirmation-window/components/Address.tsx @@ -1,18 +1,19 @@ -import React, { useState, useEffect } from "react"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import CircularProgress from "@mui/material/CircularProgress"; -import useTheme from "@mui/material/styles/useTheme"; -import Label from "./Label"; +import React, {useState, useEffect} from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import CircularProgress from '@mui/material/CircularProgress'; +import useTheme from '@mui/material/styles/useTheme'; +import Label from './Label'; type Props = { label: string; address: string; + highlight: boolean; }; -const Address = ({ label, address }: Props) => { - const [userAddress, setUserAddress] = useState(""); - const [identity, setIdentity] = useState(""); +const Address = ({label, address, highlight}: Props) => { + const [userAddress, setUserAddress] = useState(''); + const [identity, setIdentity] = useState(''); const [loading, setLoading] = useState(false); const theme = useTheme(); @@ -21,24 +22,19 @@ const Address = ({ label, address }: Props) => { setLoading(true); try { - const { data } = await window.point.wallet.address(); + const {data} = await window.point.wallet.address(); setUserAddress(data.address); } catch (e) { - console.error("Failed to fetch user address", e); + console.error('Failed to fetch user address', e); } try { - const { data } = await window.point.identity.ownerToIdentity({ - owner: address, - }); + const {data} = await window.point.identity.ownerToIdentity({owner: address}); if (data && data.identity) { setIdentity(data.identity); } } catch (e) { - console.error( - `Failed to get identity for address ${address}`, - e, - ); + console.error(`Failed to get identity for address ${address}`, e); } setLoading(false); @@ -59,26 +55,26 @@ const Address = ({ label, address }: Props) => { - {identity} {isUserAddress ? "(You)" : ""} + {identity} {isUserAddress ? '(You)' : ''} ) : null} {address} diff --git a/src/confirmation-window/components/DecodedData.tsx b/src/confirmation-window/components/DecodedData.tsx index ace7063..ab6efc7 100644 --- a/src/confirmation-window/components/DecodedData.tsx +++ b/src/confirmation-window/components/DecodedData.tsx @@ -1,26 +1,24 @@ -import React from "react"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import useTheme from "@mui/material/styles/useTheme"; -import Label from "./Label"; -import { DecodedTxInput } from "../../pointsdk/index.d"; +import React from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; +import Label from './Label'; +import TxParamValue from './TxParamValue'; +import {DecodedTxInput} from '../../pointsdk/index.d'; type Props = { data: DecodedTxInput; }; -const DecodedData = ({ data }: Props) => { +const DecodedData = ({data}: Props) => { const theme = useTheme(); return ( - - + + Contract Method: { {data.params && data.params.length > 0 ? ( <> - + Method Params: -
    - {data.params.map((p) => ( +
      + {data.params.map(p => (
    • - {p.name}: {p.value} + {p.name}:
    • ))} diff --git a/src/confirmation-window/components/GasEstimate.tsx b/src/confirmation-window/components/GasEstimate.tsx new file mode 100644 index 0000000..988657c --- /dev/null +++ b/src/confirmation-window/components/GasEstimate.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import {formatEther} from '@ethersproject/units'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; +import {DecodedTxInput} from '../../pointsdk/index.d'; +import Label from './Label'; + +type Props = Pick; + +const GasEstimate = ({gas}: Props) => { + const theme = useTheme(); + + const parsedAmount = gas.currency.toUpperCase().includes('SOL') + ? gas.value + : formatEther(gas.value); + + return ( + + + + {parsedAmount} {gas.currency.toUpperCase()} + + + ); +}; + +export default GasEstimate; diff --git a/src/confirmation-window/components/GasEstimateTokenTransfer.tsx b/src/confirmation-window/components/GasEstimateTokenTransfer.tsx new file mode 100644 index 0000000..aafcf79 --- /dev/null +++ b/src/confirmation-window/components/GasEstimateTokenTransfer.tsx @@ -0,0 +1,31 @@ +import React, {useState, useEffect} from 'react'; +import useCurrency from '../../utils/use-currency'; +import useTokens from '../../utils/use-tokens'; +import {getTokenTransferGasEstimate} from '../../utils/gas'; +import GasEstimate from './GasEstimate'; + +type Props = { + network: string; + toAddress: string; +}; + +const GasEstimateTokenTransfer = ({network, toAddress}: Props) => { + const {currency, loading: loadingCurrency} = useCurrency(network); + const {tokens, loading: loadingTokens} = useTokens(network); + const [gas, setGas] = useState(''); + + useEffect(() => { + async function getGas() { + const resp = await getTokenTransferGasEstimate(tokens, toAddress); + setGas(resp); + } + + if (!loadingCurrency && !loadingTokens) { + void getGas(); + } + }, [tokens, toAddress, loadingTokens, loadingCurrency]); + + return gas ? : null; +}; + +export default GasEstimateTokenTransfer; diff --git a/src/confirmation-window/components/Label.tsx b/src/confirmation-window/components/Label.tsx index 13562d6..0b09a1b 100644 --- a/src/confirmation-window/components/Label.tsx +++ b/src/confirmation-window/components/Label.tsx @@ -1,16 +1,12 @@ -import React from "react"; -import Typography from "@mui/material/Typography"; +import React from 'react'; +import Typography from '@mui/material/Typography'; type Props = { children: string; }; -const Label = ({ children }: Props) => ( - +const Label = ({children}: Props) => ( + {children} ); diff --git a/src/confirmation-window/components/Price.tsx b/src/confirmation-window/components/Price.tsx index 6987dc7..cc3a311 100644 --- a/src/confirmation-window/components/Price.tsx +++ b/src/confirmation-window/components/Price.tsx @@ -1,37 +1,25 @@ -import React, { useState, useEffect } from "react"; -import { formatEther } from "@ethersproject/units"; -import browser from "webextension-polyfill"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import useTheme from "@mui/material/styles/useTheme"; -import CircularProgress from "@mui/material/CircularProgress"; -import Label from "./Label"; +import React from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; +import CircularProgress from '@mui/material/CircularProgress'; +import Label from './Label'; +import useCurrency from '../../utils/use-currency'; +import useTokens from '../../utils/use-tokens'; +import {formatAmount} from '../../utils/format'; type Props = { label: string; value: string; network: string; + to: string; }; -const Price = ({ label, value, network }: Props) => { +const Price = ({label, value, network, to}: Props) => { const theme = useTheme(); - const [currency, setCurrency] = useState(""); - const [loading, setLoading] = useState(false); - - useEffect(() => { - async function fetchData() { - setLoading(true); - try { - const networksRes = await browser.storage.local.get("networks"); - const networks = JSON.parse(networksRes.networks); - setCurrency(networks[network]?.currency_name ?? "ETH"); - } catch (e) { - console.error("Failed to get networks", e); - } - setLoading(false); - } - void fetchData(); - }, []); + const {currency, loading} = useCurrency(network); + const {tokens} = useTokens(network); + const formattedAmount = formatAmount(value, currency, tokens, to); return ( @@ -40,7 +28,7 @@ const Price = ({ label, value, network }: Props) => { ) : ( - {formatEther(value)} {currency} + {formattedAmount} )} diff --git a/src/confirmation-window/components/RawData.tsx b/src/confirmation-window/components/RawData.tsx index 3fe378c..e84df4d 100644 --- a/src/confirmation-window/components/RawData.tsx +++ b/src/confirmation-window/components/RawData.tsx @@ -1,30 +1,50 @@ -import React from "react"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import useTheme from "@mui/material/styles/useTheme"; -import Label from "./Label"; +import React from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; +import Label from './Label'; type Props = { label: string; data: string | Record; }; -const RawData = ({ label, data }: Props) => { +const spanStyles = { + fontFamily: 'monospace', + fontSize: '0.875rem', + marginRight: 4, + overflowWrap: 'break-word', + wordWrap: 'break-word' +} as const; + +const RawData = ({label, data}: Props) => { const theme = useTheme(); return ( - - {typeof data === "string" ? data : JSON.stringify(data)} - + {typeof data !== 'string' ? ( + + {JSON.stringify(data)} + + ) : ( + data + .substring(2) // remove initial '0x' so we can nicely print 8-character blocks + .match(/(.{1,8})/g) + ?.map((val, idx) => ( + + {val} + + )) + )} ); }; diff --git a/src/confirmation-window/components/TxDetails.tsx b/src/confirmation-window/components/TxDetails.tsx index ebec1ef..6807d3b 100644 --- a/src/confirmation-window/components/TxDetails.tsx +++ b/src/confirmation-window/components/TxDetails.tsx @@ -1,46 +1,25 @@ -import React, { useMemo, useEffect, Fragment } from "react"; -import { useLocation } from "react-router-dom"; -import { generate } from "geopattern"; -import { BigNumber } from "@ethersproject/bignumber"; -import { DecodedTxInput } from "../../pointsdk/index.d"; -import Address from "./Address"; -import Price from "./Price"; -import RawData from "./RawData"; -import DecodedData from "./DecodedData"; +import React, {useEffect, Fragment} from 'react'; +import {generate} from 'geopattern'; +import {DecodedTxInput} from '../../pointsdk/index.d'; +import Address from './Address'; +import Price from './Price'; +import RawData from './RawData'; +import DecodedData from './DecodedData'; +import GasEstimate from './GasEstimate'; +import GasEstimateTokenTransfer from './GasEstimateTokenTransfer'; -const TxDetails = () => { - const { search } = useLocation(); - const query = useMemo(() => new URLSearchParams(search), [search]); - - const rawParams = useMemo((): Record => { - try { - const str = query.get("params"); - return str ? (JSON.parse(str) as Record) : {}; - } catch { - return {}; - } - }, [query]); - - const decodedTxData = useMemo((): DecodedTxInput | null => { - try { - const str = query.get("decodedTxData"); - return str ? (JSON.parse(str) as DecodedTxInput) : null; - } catch { - return null; - } - }, [query]); - - const network = useMemo(() => query.get("network") || "", [query]); +type Props = { + rawParams: Record; + decodedTxData: DecodedTxInput | null; + network: string; +}; +const TxDetails = ({rawParams, decodedTxData, network}: Props) => { useEffect(() => { async function drawBg() { try { - const { - data: { hash }, - } = await window.point.wallet.hash(); - document.body.style.backgroundImage = generate( - String(hash), - ).toDataUrl(); + const {data} = (await window.point.wallet.hash()) as {data: {hash: unknown}}; + document.body.style.backgroundImage = generate(String(data.hash)).toDataUrl(); } catch (e) { console.error(e); } @@ -50,26 +29,39 @@ const TxDetails = () => { return ( <> + {decodedTxData?.gas?.value && decodedTxData?.gas?.currency ? ( + + ) : null} + {rawParams?.value ? ( + + ) : null} {Object.entries(rawParams).map(([key, value], idx) => { switch (key) { - case "from": - case "to": + case 'from': + case 'to': + case 'beneficiary': return ( -
      +
      ); - case "value": - case "gas": - case "gasPrice": + case 'value': + case 'gas': + case 'gasPrice': return ( ); - case "data": - if (decodedTxData) { + case 'data': + if (decodedTxData && JSON.stringify(decodedTxData) !== '{}') { return ( @@ -78,25 +70,14 @@ const TxDetails = () => { ); } return ; - case "domain": - return decodedTxData ? ( + case 'domain': + return decodedTxData && JSON.stringify(decodedTxData) !== '{}' ? ( ) : null; default: return ; } })} - - {/* TODO: shouldn't gasPrice be multiplied by the gas limit? */} - {rawParams.value && rawParams.gasPrice ? ( - - ) : null} ); }; diff --git a/src/confirmation-window/components/TxParamValue.tsx b/src/confirmation-window/components/TxParamValue.tsx new file mode 100644 index 0000000..9b38fa8 --- /dev/null +++ b/src/confirmation-window/components/TxParamValue.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; +import {Param, ParamMetaType} from '../../pointsdk/index.d'; + +type Props = {param: Param}; + +const TxParamValue = ({param}: Props) => { + const theme = useTheme(); + + switch (param.meta?.type) { + case ParamMetaType.ZERO_CONTENT: + return no content; + case ParamMetaType.STORAGE_ID: + return ( + + content in storage → {param.value} + + ); + case ParamMetaType.TX_HASH: + return Blockchain Transaction Hash → {param.value}; + case ParamMetaType.NOT_FOUND: + return {param.value} (?); + case ParamMetaType.IDENTITIES: + return ( +
        + {(param.meta.identities || []).map(identity => ( +
      • + {identity.handle ? ( + + {identity.handle} + + ) : null} + + + {identity.address} + +
      • + ))} +
      + ); + default: + return {param.value}; + } +}; + +export default TxParamValue; diff --git a/src/confirmation-window/index.tsx b/src/confirmation-window/index.tsx index dcbcbc3..d105e62 100644 --- a/src/confirmation-window/index.tsx +++ b/src/confirmation-window/index.tsx @@ -1,11 +1,11 @@ -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import App from "./App"; -import getSdk from "pointsdk/pointsdk/sdk"; -import getEthProvider from "pointsdk/pointsdk/ethProvider"; -import getSolanaProvider from "pointsdk/pointsdk/solanaProvider"; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import App from './App'; +import getSdk from 'pointsdk/pointsdk/sdk'; +import getEthProvider from 'pointsdk/pointsdk/ethProvider'; +import getSolanaProvider from 'pointsdk/pointsdk/solanaProvider'; const version = browser.runtime.getManifest().version; -const point = getSdk("https://confirmation-window", version); +const point = getSdk('https://confirmation-window', version); const ethereum = getEthProvider(); const solana = getSolanaProvider(); @@ -14,13 +14,10 @@ const renderWindow = () => { window.point = point; window.ethereum = ethereum; window.solana = solana; - ReactDOM.render( - , - document.getElementById("point-confirmation-window"), - ); + ReactDOM.render(, document.getElementById('point-confirmation-window')); } catch (e) { - console.error("Failed to render confirmation window", e); + console.error('Failed to render confirmation window', e); } }; -document.addEventListener("DOMContentLoaded", renderWindow); +document.addEventListener('DOMContentLoaded', renderWindow); diff --git a/src/constants/gas.ts b/src/constants/gas.ts new file mode 100644 index 0000000..6665491 --- /dev/null +++ b/src/constants/gas.ts @@ -0,0 +1,2 @@ +export const GAS_UNITS_NATIVE_TOKEN_TRANSFER = 21_000; +export const GAS_UNITS_ERC20_TOKEN_TRANSFER = 65_000; diff --git a/src/constants/networks.ts b/src/constants/networks.ts index 8de33f0..78402da 100644 --- a/src/constants/networks.ts +++ b/src/constants/networks.ts @@ -1,19 +1,19 @@ const NETWORKS = { ynet: { - name: "Ynet", - type: "eth", - currency: "yPoint", + name: 'Ynet', + type: 'eth', + currency: 'yPoint' }, rinkeby: { - name: "Rinkeby", - type: "eth", - currency: "rinkebyEth", + name: 'Rinkeby', + type: 'eth', + currency: 'rinkebyEth' }, solana_devnet: { - name: "Solana Devnet", - type: "solana", - currency: "DevSol", - }, + name: 'Solana Devnet', + type: 'solana', + currency: 'DevSol' + } // solana: { // name: "Solana", // type: "solana", diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 2c3508c..f643ba7 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -2,18 +2,18 @@ const TOKENS = { ynet: [], rinkeby: [ { - name: "USDC", - address: "0x87284d4150b0FADe12255A7d208AD46526C519ee", + name: 'USDC', + address: '0x87284d4150b0FADe12255A7d208AD46526C519ee' }, { - name: "USDT", - address: "0xd92e713d051c37ebb2561803a3b5fbabc4962431", + name: 'USDT', + address: '0xd92e713d051c37ebb2561803a3b5fbabc4962431' }, { - name: "DAI", - address: "0x95b58a6Bff3D14B7DB2f5cb5F0Ad413DC2940658", - }, - ], + name: 'DAI', + address: '0x95b58a6Bff3D14B7DB2f5cb5F0Ad413DC2940658' + } + ] } as const; export default TOKENS; diff --git a/src/manifest.json b/src/manifest.json index e8211dd..7f9e7f4 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Point Network", "description": "A Browser Extension for Point Network", - "version": "0.0.41", + "version": "0.1.1", "browser_action": { "default_icon": "./assets/icons/icon-16.png", "default_popup": "./popup/index.html" diff --git a/src/pointsdk/__tests__/index.ts b/src/pointsdk/__tests__/index.ts index 8aec394..b5a1e44 100644 --- a/src/pointsdk/__tests__/index.ts +++ b/src/pointsdk/__tests__/index.ts @@ -1,9 +1,9 @@ -import point from "../"; +import point from '../'; -describe("Poin SDK", () => { - test("has correct structure", () => { - expect(point).toHaveProperty("contract"); - expect(point).toHaveProperty("wallet"); - expect(point).toHaveProperty("storage"); +describe('Poin SDK', () => { + test('has correct structure', () => { + expect(point).toHaveProperty('contract'); + expect(point).toHaveProperty('wallet'); + expect(point).toHaveProperty('storage'); }); }); diff --git a/src/pointsdk/browser.ts b/src/pointsdk/browser.ts index 1ac52d1..d7d4c09 100644 --- a/src/pointsdk/browser.ts +++ b/src/pointsdk/browser.ts @@ -1,8 +1,8 @@ -import getSdk from "pointsdk/pointsdk/sdk"; -import getEthProvider from "pointsdk/pointsdk/ethProvider"; -import getSolanaProvider from "pointsdk/pointsdk/solanaProvider"; -import NETWORKS from "pointsdk/constants/networks"; -import swal from "sweetalert2"; +import getSdk from 'pointsdk/pointsdk/sdk'; +import getEthProvider from 'pointsdk/pointsdk/ethProvider'; +import getSolanaProvider from 'pointsdk/pointsdk/solanaProvider'; +import NETWORKS from 'pointsdk/constants/networks'; +import swal from 'sweetalert2'; // eslint-disable-next-line prettier/prettier window.point = getSdk(window.location.origin, String(process.env.PACKAGE_VERSION), swal); diff --git a/src/pointsdk/ethProvider.ts b/src/pointsdk/ethProvider.ts index 1514950..5446c09 100644 --- a/src/pointsdk/ethProvider.ts +++ b/src/pointsdk/ethProvider.ts @@ -4,20 +4,17 @@ const getEthProvider = () => { const id = Math.random(); const handler = (e: MessageEvent) => { - if ( - e.data.__page_req_id === id && - e.data.__direction === "to_page" - ) { - window.removeEventListener("message", handler); + if (e.data.__page_req_id === id && e.data.__direction === 'to_page') { + window.removeEventListener('message', handler); if (e.data.code) { reject({ code: e.data.code, - message: e.data.message, + message: e.data.message }); } else if (e.data.error) { reject({ code: e.data.error.code, - message: e.data.error.message, + message: e.data.error.message }); } else { resolve(e.data.result); @@ -25,21 +22,21 @@ const getEthProvider = () => { } }; - window.addEventListener("message", handler); + window.addEventListener('message', handler); window.postMessage({ ...request, - __provider: "eth", - __message_type: "rpc", + __provider: 'eth', + __message_type: 'rpc', __page_req_id: id, - __direction: "to_bg", + __direction: 'to_bg' }); }); } return { request: handleRequest, - send: (method: string) => handleRequest({ method }), + send: (method: string) => handleRequest({method}) }; }; diff --git a/src/pointsdk/index.d.ts b/src/pointsdk/index.d.ts index f9e90bf..05a8fad 100644 --- a/src/pointsdk/index.d.ts +++ b/src/pointsdk/index.d.ts @@ -18,7 +18,7 @@ export type ContractEventsRequest = { host: string; contractName: string; event: string; - filter?: Object; + filter?: Record; }; export type ContractSendRequest = { @@ -37,35 +37,77 @@ export type WalletSendRequest = { export type URLSearchQuery = ConstructorParameters[0]; -export type StorageGetRequest = { id: string; encoding?: string } & Record< - string, - string ->; +export type StorageGetRequest = {id: string; encoding?: string} & Record; -export type OwnerToIdentityRequest = { owner: string } & Record; +export type OwnerToIdentityRequest = {owner: string} & Record; -export type IdentityToOwnerRequest = { identity: string } & Record< - string, - string ->; +export type IdentityToOwnerRequest = {identity: string} & Record; -export type EncryptDataRequest = { publicKey: string; data: string } & Record< - string, - string ->; +export type EncryptDataRequest = {publicKey: string; data: string} & Record; -export type DecryptDataRequest = { data: string } & Record; +export type DecryptDataRequest = {data: string} & Record; -export type PublicKeyByIdentityRequest = { identity: string } & Record< - string, - string ->; +export type PublicKeyByIdentityRequest = {identity: string} & Record; export type StoragePutStringRequest = { id: string; encoding?: string; } & Record; +export type StoragePubsubPublishRequest = { + topic: string; + data: string; +}; + +export type StoragePubsubPublishForIdentityRequest = { + identity: string; + data: any; + options: Record; +}; + +export type HostStorageSetRequest = { + path: Array; + value: any; +} + +export type HostStorageGetRequest = { + path: Array; +} + +export type HostStorageLenRequest = { + path: Array; +} + +export type HostStorageDirRequest = { + path: Array; +} + +export type HostStorageAppendRequest = { + path: Array; + value: any; +} + +export type HostStorageUnsetRequest = { + path: Array; +} + +export type HostStorageRemoveAtRequest = { + path: Array; + index: number; +} + +export type HostStorageReplaceAtRequest = { + path: Array; + index: number; + value: any; +} + +export type HostStorageInsertAtRequest = { + path: Array; + index: number; + value: any; +} + export type SubscriptionOptions = Record; export type SubscriptionParams = { @@ -73,7 +115,7 @@ export type SubscriptionParams = { event: string; } & SubscriptionOptions; -export type SubscriptionRequest = { type: string; params: SubscriptionParams }; +export type SubscriptionRequest = {type: string; params: SubscriptionParams}; export type SubscriptionEventMetaData = { type: string; @@ -81,25 +123,23 @@ export type SubscriptionEventMetaData = { subscriptionId: string | null; }; -export type SubscriptionEvent = SubscriptionEventMetaData & { data: T }; +export type SubscriptionEvent = SubscriptionEventMetaData & {data: T}; export type MessageQueueConfig = { type: string; params?: SubscriptionParams | Record; }; -export type SubscriptionMessages = { [subscriptionId: string]: any[] }; +export type SubscriptionMessages = {[subscriptionId: string]: any[]}; -export type SubscriptionErrors = { [subscriptionId: string]: Error | null }; +export type SubscriptionErrors = {[subscriptionId: string]: Error | null}; export type ZProxyWSOptions = { messageQueueSizeLimit?: number; }; export type ZProxyWS = WebSocket & { - subscribeToContractEvent: ( - cfg: SubscriptionParams, - ) => Promise<() => Promise>; + subscribeToContractEvent: (cfg: SubscriptionParams) => Promise<() => Promise>; }; export type IdentityData = { @@ -107,32 +147,30 @@ export type IdentityData = { identity: string; address: string; publicKey: string; - network: "solana" | "ethereum" | "point"; + network: 'solana' | 'ethereum' | 'point'; }; export type PointType = { version: string; status: { - ping: () => Promise<"pong">; + ping: () => Promise<'pong'>; }; contract: { load: (request: ContractLoadRequest) => Promise; call: (request: ContractCallRequest) => Promise; send: (request: ContractSendRequest) => Promise; events: (request: ContractEventsRequest) => Promise; - subscribe: ( - request: SubscriptionParams, - ) => Promise<() => Promise>; + subscribe: (request: SubscriptionParams) => Promise<() => Promise>; }; storage: { postFile: (request: FormData) => Promise; encryptAndPostFile: ( request: FormData, identities: string[], - metadata?: string[], + metadata?: string[] ) => Promise; getString: (request: StorageGetRequest) => Promise; - getFile: (config: { id: string }) => Promise; + getFile: (config: {id: string}) => Promise; getEncryptedFile: (config: { id: string; eSymmetricObj?: string; @@ -149,27 +187,57 @@ export type PointType = { encryptData: (request: EncryptDataRequest) => Promise; decryptData: (request: DecryptDataRequest) => Promise; decryptSymmetricKey: (request: DecryptDataRequest) => Promise; - decryptDataWithDecryptedKey: ( - request: DecryptDataRequest, - ) => Promise; + decryptDataWithDecryptedKey: (request: DecryptDataRequest) => Promise; }; identity: { ownerToIdentity: (request: OwnerToIdentityRequest) => Promise; identityToOwner: (request: IdentityToOwnerRequest) => Promise; - publicKeyByIdentity: ( - request: PublicKeyByIdentityRequest, - ) => Promise; + publicKeyByIdentity: (request: PublicKeyByIdentityRequest) => Promise; me: () => Promise; }; }; -type Param = { +export enum ParamMetaType { + STORAGE_ID = 'storage_id', + ZERO_CONTENT = 'zero_content', + TX_HASH = 'tx_hash', + NOT_FOUND = 'not_found', + IDENTITIES = 'identities' +} + +type IdentityMeta = { + handle: string; + address: string; +}; + +export type Param = { name: string; value: string; type: string; + meta?: { + type: ParamMetaType; + identities?: IdentityMeta[]; + }; }; export type DecodedTxInput = { name: string; params: Param[]; + gas: { + value: string; + currency: string; + }; +}; + +export type Token = { + name: string; + address: string; +}; + +export type Network = { + type: string; + name: string; + currency_name: string; + currency_code: string; + tokens?: Token[]; }; diff --git a/src/pointsdk/index.ts b/src/pointsdk/index.ts index 6efac04..0c90eed 100644 --- a/src/pointsdk/index.ts +++ b/src/pointsdk/index.ts @@ -1,33 +1,27 @@ -import browser from "webextension-polyfill"; -import getSdk from "pointsdk/pointsdk/sdk"; -import getEthProvider from "pointsdk/pointsdk/ethProvider"; -import getSolanaProvider from "pointsdk/pointsdk/solanaProvider"; -import NETWORKS from "pointsdk/constants/networks"; +import browser from 'webextension-polyfill'; +import getSdk from 'pointsdk/pointsdk/sdk'; +import getEthProvider from 'pointsdk/pointsdk/ethProvider'; +import getSolanaProvider from 'pointsdk/pointsdk/solanaProvider'; +import NETWORKS from 'pointsdk/constants/networks'; const version = browser.runtime.getManifest().version; try { window.wrappedJSObject.eval( - `window.point = (${getSdk.toString()})(window.location.origin, "${version}");`, - ); - window.wrappedJSObject.eval( - `window.point.networks = ${JSON.stringify(NETWORKS)};`, + `window.point = (${getSdk.toString()})(window.location.origin, "${version}");` ); + window.wrappedJSObject.eval(`window.point.networks = ${JSON.stringify(NETWORKS)};`); } catch (e) { - console.error("Failed to inject point sdk: ", e); + console.error('Failed to inject point sdk: ', e); } try { - window.wrappedJSObject.eval( - `window.ethereum = (${getEthProvider.toString()})();`, - ); + window.wrappedJSObject.eval(`window.ethereum = (${getEthProvider.toString()})();`); } catch (e) { - console.error("Failed to inject window.ethereum: ", e); + console.error('Failed to inject window.ethereum: ', e); } try { - window.wrappedJSObject.eval( - `window.solana = (${getSolanaProvider.toString()})();`, - ); + window.wrappedJSObject.eval(`window.solana = (${getSolanaProvider.toString()})();`); } catch (e) { - console.error("Failed to inject window.solana: ", e); + console.error('Failed to inject window.solana: ', e); } diff --git a/src/pointsdk/pageMessaging.ts b/src/pointsdk/pageMessaging.ts index 7fa432e..f44f773 100644 --- a/src/pointsdk/pageMessaging.ts +++ b/src/pointsdk/pageMessaging.ts @@ -1,29 +1,28 @@ -import browser from "webextension-polyfill"; +import browser from 'webextension-polyfill'; -window.addEventListener("message", async (e) => { +// eslint-disable-next-line @typescript-eslint/no-misused-promises +window.addEventListener('message', async e => { if ( - ["rpc", "registerHandler", "setAuthToken", "getAuthToken"].includes( - e.data.__message_type, - ) + ['rpc', 'registerHandler', 'setAuthToken', 'getAuthToken'].includes(e.data.__message_type) ) { - const { __direction, __page_req_id, ...payload } = e.data; + const {__direction, __page_req_id, ...payload} = e.data; try { const res = await browser.runtime.sendMessage({ ...payload, - __hostname: e.origin, + __hostname: e.origin }); window.postMessage({ ...res, __page_req_id, - __direction: "to_page", + __direction: 'to_page' }); } catch (err) { - console.error("Error processing request: ", err); + console.error('Error processing request: ', err); window.postMessage({ code: err.code ?? 500, message: err.message, __page_req_id, - __direction: "to_page", + __direction: 'to_page' }); } } diff --git a/src/pointsdk/sdk.ts b/src/pointsdk/sdk.ts index c93a45b..e53b0a8 100644 --- a/src/pointsdk/sdk.ts +++ b/src/pointsdk/sdk.ts @@ -1,3 +1,5 @@ +/* eslint-disable indent */ +/* eslint-disable object-curly-newline */ import { ZProxyWS, PointType, @@ -6,6 +8,8 @@ import { ZProxyWSOptions, StorageGetRequest, StoragePutStringRequest, + StoragePubsubPublishRequest, + StoragePubsubPublishForIdentityRequest, OwnerToIdentityRequest, PublicKeyByIdentityRequest, IdentityToOwnerRequest, @@ -21,7 +25,16 @@ import { SubscriptionEvent, SubscriptionParams, IdentityData, -} from "./index.d"; + HostStorageRemoveAtRequest, + HostStorageReplaceAtRequest, + HostStorageSetRequest, + HostStorageGetRequest, + HostStorageLenRequest, + HostStorageDirRequest, + HostStorageAppendRequest, + HostStorageUnsetRequest, + HostStorageInsertAtRequest, +} from './index.d'; const getSdk = (host: string, version: string, swal: any): PointType => { class PointSDKRequestError extends Error {} @@ -31,16 +44,18 @@ const getSdk = (host: string, version: string, swal: any): PointType => { class SubscriptionRequestTimeout extends Error {} class SubscriptionError extends Error {} + // eslint-disable-next-line @typescript-eslint/require-await const gatewayAlert = async () => { swal.fire({ - title: "Demo mode", + title: 'Demo mode', html: `You cannot make writing operations right here but you can download Point Browser and have the full web3 experience. https://pointnetwork.io/download`, - icon: "info", + icon: 'info' }); - throw new Error("Point demo does not support this operation"); + throw new Error('Point demo does not support this operation'); }; + // eslint-disable-next-line @typescript-eslint/require-await const getAuthToken = async () => window.top.IS_GATEWAY ? window.top.POINT_JWT @@ -48,15 +63,12 @@ const getSdk = (host: string, version: string, swal: any): PointType => { const id = Math.random(); const handler = (e: MessageEvent) => { - if ( - e.data.__page_req_id === id && - e.data.__direction === "to_page" - ) { - window.removeEventListener("message", handler); + if (e.data.__page_req_id === id && e.data.__direction === 'to_page') { + window.removeEventListener('message', handler); if (e.data.code) { reject({ code: e.data.code, - message: e.data.message, + message: e.data.message }); } else { resolve(e.data.token); @@ -64,58 +76,55 @@ const getSdk = (host: string, version: string, swal: any): PointType => { } }; - window.addEventListener("message", handler); + window.addEventListener('message', handler); window.postMessage({ __page_req_id: id, - __message_type: "getAuthToken", - __direction: "to_bg", + __message_type: 'getAuthToken', + __direction: 'to_bg' }); }); - const apiCall = async ( - path: string, - config?: RequestInit, - internal?: boolean, - ) => { + const apiCall = async (path: string, config?: RequestInit, internal?: boolean) => { try { const token = await getAuthToken(); // @ts-ignore, https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#xhr_and_fetch const response = await window.top.fetch( - `${host}${internal ? "/point_api/" : "/v1/api/"}${path}`, + `${host}${internal ? '/point_api/' : '/v1/api/'}${path}`, { - cache: "no-cache", - credentials: "include", + cache: 'no-cache', + credentials: 'include', keepalive: true, ...config, headers: { - "Content-Type": "application/json", - "X-Point-Token": `Bearer ${token}`, - ...config?.headers, - }, - }, + 'Content-Type': 'application/json', + 'X-Point-Token': `Bearer ${token}`, + ...config?.headers + } + } ); if (!response.ok) { - const { ok, status, statusText, headers } = response; - console.error("SDK call failed:", { - // @ts-ignore - ok, - status, - statusText, - headers: Object.fromEntries([...headers.entries()]), - }); - throw new PointSDKRequestError("Point SDK request failed"); + // const {ok, status, statusText, headers} = response; + const responseBody = await response.text(); + // console.error('SDK call failed:', { + // // @ts-ignore + // ok, + // status, + // statusText, + // headers: Object.fromEntries([...headers.entries()]) + // }); + throw new PointSDKRequestError('Point SDK request failed: ' + responseBody); } try { return (await response.json()) as T; } catch (e) { - console.error("Point API response parsing error:", e); + console.error('Point API response parsing error:', e); throw e; } } catch (e) { - console.error("Point API call failed:", e); + // console.error('Point API call failed:', e); throw e; } }; @@ -125,36 +134,36 @@ const getSdk = (host: string, version: string, swal: any): PointType => { const token = await getAuthToken(); // @ts-ignore, https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#xhr_and_fetch const response = await window.top.fetch(`${host}/${path}`, { - cache: "no-cache", - credentials: "include", + cache: 'no-cache', + credentials: 'include', keepalive: true, ...config, headers: { - "X-Point-Token": `Bearer ${token}`, - ...(config?.headers ?? {}), - }, + 'X-Point-Token': `Bearer ${token}`, + ...(config?.headers ?? {}) + } }); if (!response.ok) { - const { ok, status, statusText, headers } = response; - console.error("SDK ZProxy call failed:", { + const {ok, status, statusText, headers} = response; + console.error('SDK ZProxy call failed:', { // @ts-ignore ok, status, statusText, - headers: Object.fromEntries([...headers.entries()]), + headers: Object.fromEntries([...headers.entries()]) }); - throw new PointSDKRequestError("Point SDK request failed"); + throw new PointSDKRequestError('Point SDK request failed'); } try { return (await response.json()) as T; } catch (e) { - console.error("Point API response parsing error:", e); + console.error('Point API response parsing error:', e); throw e; } } catch (e) { - console.error("Point API call failed:", e); + console.error('Point API call failed:', e); throw e; } }; @@ -164,42 +173,40 @@ const getSdk = (host: string, version: string, swal: any): PointType => { pathname: string, query?: URLSearchQuery, headers?: HeadersInit, - internal?: boolean, + internal?: boolean ): Promise { return apiCall( - `${pathname}${query ? "?" : ""}${new URLSearchParams( - query, - ).toString()}`, + `${pathname}${query ? '?' : ''}${new URLSearchParams(query).toString()}`, { - method: "GET", - headers, + method: 'GET', + headers }, - internal, + internal ); }, post( pathname: string, body: any, headers?: HeadersInit, - internal?: boolean, + internal?: boolean ): Promise { return apiCall( pathname, { - method: "POST", + method: 'POST', headers, body: JSON.stringify({ ...body, - _csrf: window.localStorage.getItem("csrf_token"), - }), + _csrf: window.localStorage.getItem('csrf_token') + }) }, - internal, + internal ); }, postFile(pathname: string, file: FormData): Promise { return zproxyStorageCall(pathname, { - method: "POST", - body: file, + method: 'POST', + body: file // headers NOT required when passing FormData object }); }, @@ -207,21 +214,21 @@ const getSdk = (host: string, version: string, swal: any): PointType => { pathname: string, file: FormData, identities: string[], - metadata?: string[], + metadata?: string[] ): Promise { return zproxyStorageCall(pathname, { - method: "POST", + method: 'POST', body: file, headers: { - identities: identities.join(","), - metadata: metadata?.join(",") ?? "", - }, + identities: identities.join(','), + metadata: metadata?.join(',') ?? '' + } }); - }, + } }; function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } function Promised(): PromisedValue { @@ -235,22 +242,17 @@ const getSdk = (host: string, version: string, swal: any): PointType => { }), { resolve, - reject, - }, + reject + } ); } function SubscriptionTimeout(ms: number): Promise { return new Promise((_, reject) => setTimeout( - () => - reject( - new SubscriptionRequestTimeout( - "Subscription confirmation timeout", - ), - ), - ms, - ), + () => reject(new SubscriptionRequestTimeout('Subscription confirmation timeout')), + ms + ) ); } @@ -259,32 +261,28 @@ const getSdk = (host: string, version: string, swal: any): PointType => { const errorsBySubscriptionId: SubscriptionErrors = {}; const SUBSCRIPTION_EVENT_TYPES = { - CONFIRMATION: "subscription_confirmation", - CANCELLATION: "subscription_cancellation", - EVENT: "subscription_event", - ERROR: "subscription_error", + CONFIRMATION: 'subscription_confirmation', + CANCELLATION: 'subscription_cancellation', + EVENT: 'subscription_event', + ERROR: 'subscription_error' }; const SUBSCRIPTION_REQUEST_TYPES = { - SUBSCRIBE: "subscribeContractEvent", - UNSUBSCRIBE: "removeSubscriptionById", + SUBSCRIBE: 'subscribeContractEvent', + UNSUBSCRIBE: 'removeSubscriptionById' }; - const getSubscriptionRequestId = ({ - type, - params: { contract, event } = {}, - }: MessageQueueConfig) => `${type}_${contract}_${event}`; + const getSubscriptionRequestId = ({type, params: {contract, event} = {}}: MessageQueueConfig) => + `${type}_${contract}_${event}`; const getMessageQueue = (subscriptionId: string): T[] => - messagesBySubscriptionId[subscriptionId] || - (messagesBySubscriptionId[subscriptionId] = []); + messagesBySubscriptionId[subscriptionId] || (messagesBySubscriptionId[subscriptionId] = []); - const subscriptionIdsByRequestId: Record> = - {}; + const subscriptionIdsByRequestId: Record> = {}; const wsConnect = ( host: string, - { messageQueueSizeLimit = 1000 } = {} as ZProxyWSOptions, + {messageQueueSizeLimit = 1000} = {} as ZProxyWSOptions ): Promise => new Promise((resolve, reject) => { if (socketsByHost[host] !== undefined) { @@ -298,24 +296,22 @@ const getSdk = (host: string, version: string, swal: any): PointType => { resolve( Object.assign((socketsByHost[host] = ws), { async subscribeToContractEvent( - params: SubscriptionParams, + params: SubscriptionParams ): Promise<() => Promise> { const metaData = { type: SUBSCRIPTION_REQUEST_TYPES.SUBSCRIBE, params, - __point_token: await getAuthToken(), + __point_token: await getAuthToken() }; - const requestId = - getSubscriptionRequestId(metaData); + const requestId = getSubscriptionRequestId(metaData); - subscriptionIdsByRequestId[requestId] = - Promised(); + subscriptionIdsByRequestId[requestId] = Promised(); - await ws.send(JSON.stringify(metaData)); + ws.send(JSON.stringify(metaData)); const subscriptionId = (await Promise.race([ subscriptionIdsByRequestId[requestId], - SubscriptionTimeout(10000), + SubscriptionTimeout(10000) ])) as string; const queue = getMessageQueue(subscriptionId); @@ -325,9 +321,7 @@ const getSdk = (host: string, version: string, swal: any): PointType => { while (true) { try { const queueError = - errorsBySubscriptionId[ - subscriptionId - ]; + errorsBySubscriptionId[subscriptionId]; if (queueError) { throw queueError; } @@ -337,10 +331,7 @@ const getSdk = (host: string, version: string, swal: any): PointType => { await sleep(100); } } catch (e) { - console.error( - "subscribed message error:", - e, - ); + console.error('subscribed message error:', e); throw e; } } @@ -350,28 +341,26 @@ const getSdk = (host: string, version: string, swal: any): PointType => { return ws.send( JSON.stringify({ type: SUBSCRIPTION_REQUEST_TYPES.UNSUBSCRIBE, - params: { subscriptionId }, - __point_token: - await getAuthToken(), - }), + params: {subscriptionId}, + __point_token: await getAuthToken() + }) ); - }, - }, + } + } ); - }, - }) as ZProxyWS, + } + }) as ZProxyWS ); - ws.onerror = (e) => { + ws.onerror = e => { for (const queueId in messagesBySubscriptionId) { if (!errorsBySubscriptionId[queueId]) { - errorsBySubscriptionId[queueId] = - new ZProxyWSConnectionError(e.toString()); + errorsBySubscriptionId[queueId] = new ZProxyWSConnectionError(e.toString()); } } }; - ws.onclose = (e) => { + ws.onclose = e => { delete socketsByHost[host]; if (e.code === 1000 || e.code === 1001) { // 1000 -> CLOSE_NORMAL (normal socket shut down) @@ -380,38 +369,34 @@ const getSdk = (host: string, version: string, swal: any): PointType => { } else { for (const queueId in messagesBySubscriptionId) { if (!errorsBySubscriptionId[queueId]) { - errorsBySubscriptionId[queueId] = - new ZProxyWSConnectionClosed(e.toString()); + errorsBySubscriptionId[queueId] = new ZProxyWSConnectionClosed( + e.toString() + ); } } reject(); } }; - ws.onmessage = (e) => { + ws.onmessage = e => { try { - const { - type, - request, - subscriptionId, - data, - }: SubscriptionEvent = JSON.parse(e.data); + const {type, request, subscriptionId, data}: SubscriptionEvent = + JSON.parse(e.data); switch (type) { case SUBSCRIPTION_EVENT_TYPES.CONFIRMATION: { const requestId = getSubscriptionRequestId(request); - const { resolve, reject } = - subscriptionIdsByRequestId[requestId] || {}; + const {resolve, reject} = subscriptionIdsByRequestId[requestId] || {}; - if (typeof subscriptionId !== "string") { - if (typeof reject === "function") { + if (typeof subscriptionId !== 'string') { + if (typeof reject === 'function') { reject( new SubscriptionError( - `Invalid subscription id "${subscriptionId}" for request id: "${requestId}"`, - ), + `Invalid subscription id "${subscriptionId}" for request id: "${requestId}"` + ) ); } - } else if (typeof resolve === "function") { + } else if (typeof resolve === 'function') { resolve(subscriptionId); } break; @@ -423,7 +408,7 @@ const getSdk = (host: string, version: string, swal: any): PointType => { type, request, subscriptionId, - data, + data }); delete messagesBySubscriptionId[subscriptionId]; @@ -439,52 +424,47 @@ const getSdk = (host: string, version: string, swal: any): PointType => { if (queue.length > messageQueueSizeLimit) { errorsBySubscriptionId[subscriptionId] = new MessageQueueOverflow( - "ZProxy WS message queue overflow", + 'ZProxy WS message queue overflow' ); } else { queue.push(data); } } else { - console.error( - "Unable to identify subscription channel", - { - subscriptionId, - request, - data, - }, - ); + console.error('Unable to identify subscription channel', { + subscriptionId, + request, + data + }); } break; } case SUBSCRIPTION_EVENT_TYPES.ERROR: { if (subscriptionId) { - errorsBySubscriptionId[subscriptionId] = - new SubscriptionError(JSON.stringify(data)); - } else { - console.error( - "Unable to identify subscription channel", - { - subscriptionId, - request, - data, - }, + errorsBySubscriptionId[subscriptionId] = new SubscriptionError( + JSON.stringify(data) ); + } else { + console.error('Unable to identify subscription channel', { + subscriptionId, + request, + data + }); } break; } default: { - console.error("Unsupported event type:", { + console.error('Unsupported event type:', { type, request, subscriptionId, - data, + data }); } } } catch (e) { - console.error("Web Socket onmessage error:", e); + console.error('Web Socket onmessage error:', e); } }; }); @@ -494,15 +474,12 @@ const getSdk = (host: string, version: string, swal: any): PointType => { const id = Math.random(); const handler = (e: MessageEvent) => { - if ( - e.data.__page_req_id === id && - e.data.__direction === "to_page" - ) { - window.removeEventListener("message", handler); + if (e.data.__page_req_id === id && e.data.__direction === 'to_page') { + window.removeEventListener('message', handler); if (e.data.code) { reject({ code: e.data.code, - message: e.data.message, + message: e.data.message }); } else { resolve(e.data.result); @@ -510,440 +487,420 @@ const getSdk = (host: string, version: string, swal: any): PointType => { } }; - window.addEventListener("message", handler); + window.addEventListener('message', handler); window.postMessage({ messageId, - __message_type: "registerHandler", + __message_type: 'registerHandler', __page_req_id: id, - __direction: "to_bg", + __direction: 'to_bg' }); }); return { version: version, - status: { - ping: () => api.get<"pong">("status/ping"), - }, + status: {ping: () => api.get<'pong'>('status/ping')}, contract: { - load: ({ contract, ...args }: ContractLoadRequest) => + load: ({contract, ...args}: ContractLoadRequest) => api.get(`contract/load/${contract}`, args), - call: async ({ - contract, - method, - params, - }: ContractCallRequest) => { + call: async ({contract, method, params}: ContractCallRequest) => { if (window.top.IS_GATEWAY) { - const res = await api.post("contract/safe_call", { + const res = await api.post('contract/safe_call', { contract, method, - params, + params }); return res.data; } const { - data: { abi, address }, + data: {abi, address} } = await api.get(`contract/load/${contract}`, {}); - const jsonInterface = abi.find( - (entry) => entry.name === method, - ); + const jsonInterface = abi.find(entry => entry.name === method); if (!jsonInterface) { - throw new Error( - `Method ${method} not found in contract ${contract}`, - ); + throw new Error(`Method ${method} not found in contract ${contract}`); } const preparedParams = params ?? []; if (preparedParams.length !== jsonInterface.inputs.length) { throw new Error( - `Invalid number of params, expected ${jsonInterface.inputs.length}, got ${preparedParams.length}`, + `Invalid number of params, expected ${jsonInterface.inputs.length}, got ${preparedParams.length}` ); } for (let i = 0; i < preparedParams.length; i++) { if ( - jsonInterface.inputs[i].internalType === "bytes32" && - typeof preparedParams[i] === "string" && - !preparedParams[i].startsWith("0x") + jsonInterface.inputs[i].internalType === 'bytes32' && + typeof preparedParams[i] === 'string' && + !preparedParams[i].startsWith('0x') ) { preparedParams[i] = `0x${preparedParams[i]}`; } } - const { data } = await api.post("contract/encodeFunctionCall", { + const {data} = await api.post('contract/encodeFunctionCall', { jsonInterface, - params: preparedParams, + params: preparedParams }); - const accounts = await window.top.ethereum.request({ - method: "eth_requestAccounts", - }); + const accounts = await window.top.ethereum.request({method: 'eth_requestAccounts'}); switch (jsonInterface.stateMutability) { - case "view": - case "pure": + case 'view': + case 'pure': const rawRes = await window.top.ethereum.request({ - method: "eth_call", + method: 'eth_call', params: [ { from: accounts[0], to: address, - data, + data }, - "latest", - ], + 'latest' + ] }); - const decodedRes = await api.post( - "contract/decodeParameters", - { - typesArray: jsonInterface.outputs, - hexString: rawRes, - }, - ); + const decodedRes = await api.post('contract/decodeParameters', { + typesArray: jsonInterface.outputs, + hexString: rawRes + }); - return { data: decodedRes.data[0] }; - case "nonpayable": + return {data: decodedRes.data[0]}; + case 'nonpayable': return window.top.ethereum.request({ - meta: { contract }, - method: "eth_sendTransaction", + meta: {contract}, + method: 'eth_sendTransaction', params: [ { from: accounts[0], to: address, - data, - }, - ], + data + } + ] }); - case "payable": - throw new Error( - "Do not use call for payable functions, use send instead", - ); + case 'payable': + throw new Error('Do not use call for payable functions, use send instead'); default: throw new Error( - `Unexpected function state mutability ${jsonInterface.stateMutability}`, + `Unexpected function state mutability ${jsonInterface.stateMutability}` ); } }, send: window.top.IS_GATEWAY ? gatewayAlert - : async ({ - contract, - method, - params, - value, - }: ContractSendRequest) => { + : async ({contract, method, params, value}: ContractSendRequest) => { const { - data: { abi, address }, + data: {abi, address} } = await api.get(`contract/load/${contract}`, {}); - const accounts = await window.top.ethereum.request({ - method: "eth_requestAccounts", - }); + const req = {method: 'eth_requestAccounts'}; + const accounts = await window.top.ethereum.request(req); - const jsonInterface = abi.find( - (entry) => entry.name === method, - ); + const jsonInterface = abi.find(entry => entry.name === method); if (!jsonInterface) { - throw new Error( - `Method ${method} not found in contract ${contract}`, - ); + throw new Error(`Method ${method} not found in contract ${contract}`); } const preparedParams = params ?? []; - if ( - preparedParams.length !== jsonInterface.inputs.length - ) { + if (preparedParams.length !== jsonInterface.inputs.length) { throw new Error( - `Invalid number of params, expected ${jsonInterface.inputs.length}, got ${preparedParams.length}`, + `Invalid number of params, expected ${jsonInterface.inputs.length}, got ${preparedParams.length}` ); } for (let i = 0; i < preparedParams.length; i++) { if ( - jsonInterface.inputs[i].internalType === - "bytes32" && - typeof preparedParams[i] === "string" && - !preparedParams[i].startsWith("0x") + jsonInterface.inputs[i].internalType === 'bytes32' && + typeof preparedParams[i] === 'string' && + !preparedParams[i].startsWith('0x') ) { preparedParams[i] = `0x${preparedParams[i]}`; } } - if ( - ["view", "pure"].includes( - jsonInterface.stateMutability, - ) - ) { + if (['view', 'pure'].includes(jsonInterface.stateMutability)) { throw new Error( - `Method ${method} is a view one, use call instead of send`, + `Method ${method} is a view one, use call instead of send` ); } - const { data } = await api.post( - "contract/encodeFunctionCall", - { - jsonInterface, - params: params ?? [], - }, - ); + const {data} = await api.post('contract/encodeFunctionCall', { + jsonInterface, + params: params ?? [] + }); return window.top.ethereum.request({ - meta: { contract }, - method: "eth_sendTransaction", + meta: {contract}, + method: 'eth_sendTransaction', params: [ { from: accounts[0], to: address, data, - value, - }, - ], + value + } + ] }); }, - events: (args: ContractEventsRequest) => - api.post("contract/events", args), - async subscribe({ - contract, - event, - ...options - }: SubscriptionParams) { - if (typeof contract !== "string") { - throw new PointSDKRequestError( - `Invalid contract ${contract}`, - ); + events: (args: ContractEventsRequest) => api.post('contract/events', args), + async subscribe({contract, event, ...options}: SubscriptionParams) { + if (typeof contract !== 'string') { + throw new PointSDKRequestError(`Invalid contract ${contract}`); } - if (typeof event !== "string") { + if (typeof event !== 'string') { throw new PointSDKRequestError(`Invalid event ${event}`); } const url = new URL(host); - url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; - url.pathname += url.pathname.endsWith("/") ? "ws" : "/ws"; + url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; + url.pathname += url.pathname.endsWith('/') ? 'ws' : '/ws'; const socket = await wsConnect(url.toString()); if (!socket) { - throw new PointSDKRequestError( - "Failed to establish web socket connection", - ); + throw new PointSDKRequestError('Failed to establish web socket connection'); } return socket.subscribeToContractEvent({ contract, event, - ...options, + ...options }); - }, + } }, storage: { postFile: window.top.IS_GATEWAY ? gatewayAlert - : (file: FormData) => api.postFile("_storage/", file), - encryptAndPostFile: ( - file: FormData, - identities: string[], - metadata?: string[], - ) => + : (file: FormData) => api.postFile('_storage/', file), + encryptAndPostFile: (file: FormData, identities: string[], metadata?: string[]) => window.top.IS_GATEWAY ? gatewayAlert - : api.encryptAndPostFile( - "_encryptedStorage/", - file, - identities, - metadata, - ), - getString: ({ id, ...args }: StorageGetRequest) => + : api.encryptAndPostFile('_encryptedStorage/', file, identities, metadata), + getString: ({id, ...args}: StorageGetRequest) => api.get(`storage/getString/${id}`, args), - getFile: async ({ id }) => { + getFile: async ({id}) => { const token = await getAuthToken(); - const res = await window.top.fetch(`${host}/_storage/${id}`, { - headers: { - "X-Point-Token": `Bearer ${token}`, - }, - }); + const opts = {headers: {'X-Point-Token': `Bearer ${token}`}}; + const res = await window.top.fetch(`${host}/_storage/${id}`, opts); return res.blob(); }, - getEncryptedFile: async ({ id, eSymmetricObj, symmetricObj }) => { + getEncryptedFile: async ({id, eSymmetricObj, symmetricObj}) => { if (!!eSymmetricObj === !!symmetricObj) { throw new Error( - "Either eSymmetricObj or symmetricObj should be specified, and only one of them", + 'Either eSymmetricObj or symmetricObj should be specified, and only one of them' ); } const token = await getAuthToken(); const res = await window.top.fetch( `${host}/_encryptedStorage/${id}${ - eSymmetricObj ? `?eSymmetricObj=${eSymmetricObj}` : "" - }${symmetricObj ? `?symmetricObj=${symmetricObj}` : ""}`, - { - headers: { - "X-Point-Token": `Bearer ${token}`, - }, - }, + eSymmetricObj ? `?eSymmetricObj=${eSymmetricObj}` : '' + }${symmetricObj ? `?symmetricObj=${symmetricObj}` : ''}`, + {headers: {'X-Point-Token': `Bearer ${token}`}} ); return res.blob(); }, putString: window.top.IS_GATEWAY ? gatewayAlert - : (data: StoragePutStringRequest) => - api.post("storage/putString", data), + : (data: StoragePutStringRequest) => api.post('storage/putString', data), + pubsubPublish: + (data: StoragePubsubPublishRequest) => + api.post('storage/pubsub/publish/' + data.topic, data.data), + pubsubPublishForIdentity: + (req: StoragePubsubPublishForIdentityRequest) => + api.post('storage/pubsub/publishForIdentity', {data: JSON.stringify(req.data), identity: req.identity, options: JSON.stringify(req.options||'')}) + }, + hostStorage: { + set: + (data: HostStorageSetRequest) => + api.post( + 'storage/host/set', + {data: JSON.stringify(data)} + ), + get: + (data: HostStorageGetRequest) => + api.get( + 'storage/host/get', + {data: JSON.stringify(data)} + ), + len: + (data: HostStorageLenRequest) => + api.get( + 'storage/host/len', + {data: JSON.stringify(data)} + ), + dir: + (data: HostStorageDirRequest) => + api.get( + 'storage/host/dir', + {data: JSON.stringify(data)} + ), + append: + (data: HostStorageAppendRequest) => + api.post( + 'storage/host/append', + {data: JSON.stringify(data)} + ), + unset: + (data: HostStorageUnsetRequest) => + api.post( + 'storage/host/unset', + {data: JSON.stringify(data)} + ), + removeAt: + (data: HostStorageRemoveAtRequest) => + api.post( + 'storage/host/removeAt', + {data: JSON.stringify(data)} + ), + replaceAt: + (data: HostStorageReplaceAtRequest) => + api.post( + 'storage/host/replaceAt', + {data: JSON.stringify(data)} + ), + insertAt: + (data: HostStorageInsertAtRequest) => + api.post( + 'storage/host/insertAt', + {data: JSON.stringify(data)} + ), }, wallet: { - address: () => api.get("wallet/address"), - ...(host === "https://confirmation-window" && !window.top.IS_GATEWAY - ? { - hash: () => api.get("wallet/hash", {}, {}, true), - } + address: () => api.get('wallet/address'), + ...(host === 'https://confirmation-window' && !window.top.IS_GATEWAY + ? {hash: () => api.get('wallet/hash', {}, {}, true)} : {}), - publicKey: () => api.get("wallet/publicKey", {}), - balance: (network) => { + publicKey: () => api.get('wallet/publicKey', {}), + balance: network => { if (!network) { - throw new Error("No network specified"); + throw new Error('No network specified'); } - return api.get("wallet/balance", { network }); + return api.get('wallet/balance', {network}); }, send: window.top.IS_GATEWAY ? gatewayAlert - : async ({ to, network, value }) => { - const { networks, default_network } = await api.get( - "blockchain/networks", - ); + : async ({to, network, value}) => { + const {networks, default_network} = await api.get('blockchain/networks'); const chain = network ?? default_network; if (!networks[chain]) { throw new Error(`Unknown network ${chain}`); } switch (networks[chain].type) { - case "eth": - const accounts = - await window.top.ethereum.request({ - method: "eth_requestAccounts", - }); + case 'eth': + const req = {method: 'eth_requestAccounts'}; + const accounts = await window.top.ethereum.request(req); return window.top.ethereum.request({ - method: "eth_sendTransaction", + method: 'eth_sendTransaction', params: [ { from: accounts[0], to, - value, - }, + value + } ], - chain, + chain }); - case "solana": + case 'solana': return window.top.solana.request({ - method: "solana_sendTransaction", + method: 'solana_sendTransaction', params: [ { to, - lamports: value, - }, + lamports: value + } ], - chain, + chain }); default: - throw new Error( - `Unexpected network type ${networks[chain].type}`, - ); + throw new Error(`Unexpected network type ${networks[chain].type}`); } }, encryptData: window.top.IS_GATEWAY ? gatewayAlert - : ({ publicKey, data, ...args }: EncryptDataRequest) => - api.post("wallet/encryptData", { + : ({publicKey, data, ...args}: EncryptDataRequest) => + api.post('wallet/encryptData', { publicKey, data, - ...args, + ...args }), decryptData: window.top.IS_GATEWAY ? gatewayAlert - : ({ data, ...args }: DecryptDataRequest) => - api.post("wallet/decryptData", { + : ({data, ...args}: DecryptDataRequest) => + api.post('wallet/decryptData', { data, - ...args, + ...args }), decryptSymmetricKey: window.top.IS_GATEWAY ? gatewayAlert - : ({ data, ...args }: DecryptDataRequest) => - api.post("wallet/decryptSymmetricKey", { + : ({data, ...args}: DecryptDataRequest) => + api.post('wallet/decryptSymmetricKey', { data, - ...args, + ...args }), decryptDataWithDecryptedKey: window.top.IS_GATEWAY ? gatewayAlert - : ({ data, ...args }: DecryptDataRequest) => - api.post("wallet/decryptDataWithDecryptedKey", { + : ({data, ...args}: DecryptDataRequest) => + api.post('wallet/decryptDataWithDecryptedKey', { data, - ...args, - }), + ...args + }) }, identity: { - publicKeyByIdentity: ({ - identity, - ...args - }: PublicKeyByIdentityRequest) => + publicKeyByIdentity: ({identity, ...args}: PublicKeyByIdentityRequest) => api.get(`identity/publicKeyByIdentity/${identity}`, args), - identityToOwner: ({ - identity, - ...args - }: IdentityToOwnerRequest) => + identityToOwner: ({identity, ...args}: IdentityToOwnerRequest) => api.get(`identity/identityToOwner/${identity}`, args), - ownerToIdentity: ({ owner, ...args }: OwnerToIdentityRequest) => + ownerToIdentity: ({owner, ...args}: OwnerToIdentityRequest) => api.get(`identity/ownerToIdentity/${owner}`, args), - me: () => api.get("identity/isIdentityRegistered/"), + me: () => api.get('identity/isIdentityRegistered/') }, - ...(host === "https://point" && !window.top.IS_GATEWAY + ...(host === 'https://point' && !window.top.IS_GATEWAY ? { point: { - wallet_send: async ({ to, network, value }) => { + wallet_send: async ({to, network, value}) => { const messageId = String(Math.random()); await Promise.all([ waitForNodeResponse(messageId), (async () => { const res = await api.post( - "wallet/send", + 'wallet/send', { to, network, value, - messageId, + messageId }, {}, - true, + true ); if (res.status !== 200) { - throw new Error("Failed to send token"); + throw new Error('Failed to send token'); } - })(), + })() ]); }, - wallet_send_token: async ({ - to, - network, - tokenAddress, - value, - }) => { + wallet_send_token: async ({to, network, tokenAddress, value}) => { const messageId = String(Math.random()); await Promise.all([ waitForNodeResponse(messageId), (async () => { const res = await api.post( - "wallet/sendToken", + 'wallet/sendToken', { to, network, value, tokenAddress, - messageId, + messageId }, {}, - true, + true ); if (res.status !== 200) { - throw new Error("Failed to send token"); + throw new Error('Failed to send token'); } - })(), + })() ]); }, set_auth_token: (token: string) => @@ -953,16 +910,13 @@ const getSdk = (host: string, version: string, swal: any): PointType => { const handler = (e: MessageEvent) => { if ( e.data.__page_req_id === id && - e.data.__direction === "to_page" + e.data.__direction === 'to_page' ) { - window.removeEventListener( - "message", - handler, - ); + window.removeEventListener('message', handler); if (e.data.code) { reject({ code: e.data.code, - message: e.data.message, + message: e.data.message }); } else { resolve(e.data); @@ -970,25 +924,24 @@ const getSdk = (host: string, version: string, swal: any): PointType => { } }; - window.addEventListener("message", handler); + window.addEventListener('message', handler); window.postMessage({ token, __page_req_id: id, - __message_type: "setAuthToken", - __direction: "to_bg", + __message_type: 'setAuthToken', + __direction: 'to_bg' }); }), get_auth_token: getAuthToken, - link_point_to_sol: (domain: string) => { - return window.top.solana.request({ - method: "solana_snsWriteRequest", - params: [{ domain }], - }); - }, - }, + link_point_to_sol: (domain: string) => + window.top.solana.request({ + method: 'solana_snsWriteRequest', + params: [{domain}] + }) + } } - : {}), + : {}) }; }; diff --git a/src/pointsdk/solanaProvider.ts b/src/pointsdk/solanaProvider.ts index f73477a..5c39cbb 100644 --- a/src/pointsdk/solanaProvider.ts +++ b/src/pointsdk/solanaProvider.ts @@ -4,20 +4,17 @@ const getSolanaProvider = () => { const id = Math.random(); const handler = (e: MessageEvent) => { - if ( - e.data.__page_req_id === id && - e.data.__direction === "to_page" - ) { - window.removeEventListener("message", handler); + if (e.data.__page_req_id === id && e.data.__direction === 'to_page') { + window.removeEventListener('message', handler); if (e.data.code) { reject({ code: e.data.code, - message: e.data.message, + message: e.data.message }); } else if (e.data.error) { reject({ code: e.data.error.code, - message: e.data.error.message, + message: e.data.error.message }); } else { resolve(e.data.result); @@ -25,32 +22,28 @@ const getSolanaProvider = () => { } }; - window.addEventListener("message", handler); + window.addEventListener('message', handler); window.postMessage({ ...request, - __message_type: "rpc", - __provider: "solana", + __message_type: 'rpc', + __provider: 'solana', __page_req_id: id, - __direction: "to_bg", + __direction: 'to_bg' }); }); return { connect: async () => { - const res = await handleRequest({ - method: "solana_requestAccount", - }); - return { - publicKey: res.publicKey, - }; + const res = await handleRequest({method: 'solana_requestAccount'}); + return {publicKey: res.publicKey}; }, disconnect: async () => {}, // TODO request: handleRequest, signAndSendTransaction: (tx: any) => handleRequest({ - method: "solana_sendTransaction", - params: [tx.toJSON()], - }), + method: 'solana_sendTransaction', + params: [tx.toJSON()] + }) }; }; diff --git a/src/popup/App.tsx b/src/popup/App.tsx index 160addf..20d59f5 100644 --- a/src/popup/App.tsx +++ b/src/popup/App.tsx @@ -1,6 +1,6 @@ -import React, { FunctionComponent } from "react"; -import { BlockchainContext, useBlockchain } from "./context/blockchain"; -import Layout from "./components/Layout"; +import React, {FunctionComponent} from 'react'; +import {BlockchainContext, useBlockchain} from './context/blockchain'; +import Layout from './components/Layout'; const App: FunctionComponent = () => { const blockchainContext = useBlockchain(); diff --git a/src/popup/components/Balance.tsx b/src/popup/components/Balance.tsx index e44385f..60baa30 100644 --- a/src/popup/components/Balance.tsx +++ b/src/popup/components/Balance.tsx @@ -1,25 +1,23 @@ -import React, { FunctionComponent, useContext } from "react"; -import Typography from "@mui/material/Typography"; -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import SendIcon from "@mui/icons-material/Send"; -import { BlockchainContext } from "pointsdk/popup/context/blockchain"; +import React, {FunctionComponent, useContext} from 'react'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import SendIcon from '@mui/icons-material/Send'; +import {BlockchainContext} from 'pointsdk/popup/context/blockchain'; const Balance: FunctionComponent = () => { - const { balance, chainId, networks } = useContext(BlockchainContext); + const {balance, chainId, networks} = useContext(BlockchainContext); return ( - + Available Balance {balance} - - {networks[chainId]?.currency_name.toUpperCase()} - + {networks[chainId]?.currency_name.toUpperCase()}