diff --git a/.vscode/settings.json b/.vscode/settings.json index d8cd35c10..998b191f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "directory": "./packages/extension", "changeProcessCWD": true } - ] + ], + "cSpell.words": ["enkrypt", "enkryptcom", "polkadot", "webextension"] } diff --git a/packages/.DS_Store b/packages/.DS_Store new file mode 100644 index 000000000..bee617e0a Binary files /dev/null and b/packages/.DS_Store differ diff --git a/packages/extension-bridge/src/internal.ts b/packages/extension-bridge/src/internal.ts index 05decb9a4..4caff4080 100644 --- a/packages/extension-bridge/src/internal.ts +++ b/packages/extension-bridge/src/internal.ts @@ -223,8 +223,8 @@ const initIntercoms = () => { setTimeout(() => { if (incomingPort) { portMap.delete(portId); - incomingPort = null; incomingPort.disconnect(); + incomingPort = null; } }, 250e3); // on chrome force reconnect as this is a way of keeping the background running forever //https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension } diff --git a/packages/extension/.eslintrc.json b/packages/extension/.eslintrc.json index ad490f96f..2fe2300fe 100644 --- a/packages/extension/.eslintrc.json +++ b/packages/extension/.eslintrc.json @@ -21,6 +21,7 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-var-requires": "off", "vue/multi-word-component-names": "off", - "@typescript-eslint/no-unused-vars": "warn" + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-non-null-assertion": "off" } } diff --git a/packages/extension/configs/rollup.config.base.js b/packages/extension/configs/rollup.config.base.js index 1ec6fb7b3..2337b69e9 100644 --- a/packages/extension/configs/rollup.config.base.js +++ b/packages/extension/configs/rollup.config.base.js @@ -1,6 +1,7 @@ import typescript from "@rollup/plugin-typescript"; import commonjs from "@rollup/plugin-commonjs"; import nodeResolve from "@rollup/plugin-node-resolve"; +import json from "@rollup/plugin-json" import nodePolyfills from "rollup-plugin-node-polyfills"; import { uglify } from "rollup-plugin-uglify"; const enableMinification = process.env.minify === "on"; @@ -14,6 +15,7 @@ const base = { plugins: [ typescript(), commonjs(), + json(), nodePolyfills(), nodeResolve({ preferBuiltins: false }), ], diff --git a/packages/extension/package.json b/packages/extension/package.json index 53e5886de..afff1867c 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,6 +1,6 @@ { "name": "@enkryptcom/extension", - "version": "0.4.4", + "version": "0.5.0", "private": true, "scripts": { "zip": "cd dist; zip -r release.zip *;", @@ -33,23 +33,27 @@ "@types/less": "^3.0.3", "@types/lodash": "^4.14.182", "@types/utf-8-validate": "^5.0.0", + "@vueuse/core": "^8.7.5", "add": "^2.0.6", "bignumber.js": "^9.0.2", "bip39": "^3.0.4", "chai": "^4.3.6", "concurrently": "^7.0.0", "core-js": "^3.21.0", + "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^7.1.4", "eventemitter3": "^4.0.7", "lodash": "^4.17.21", "memoize-one": "^6.0.0", "moment": "^2.29.1", + "nanoevents": "^6.0.2", "qrcode.vue": "^3.3.3", "rollup-plugin-node-builtins": "^2.1.2", "url-parse": "^1.5.10", "vue": "^3.2.31", "vue-router": "4", "vue3-lottie": "^2.1.0", + "vuedraggable": "next", "web3": "^1.7.3", "web3-utils": "^1.7.3", "zxcvbn": "^4.4.2" @@ -60,8 +64,10 @@ "@polkadot/rpc-provider": "^8.0.2", "@polkadot/types": "^8.0.2", "@rollup/plugin-commonjs": "^21.0.2", + "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.1.3", "@rollup/plugin-typescript": "^8.3.1", + "@types/ethereumjs-abi": "^0.6.3", "@types/mocha": "^9.1.0", "@types/url-parse": "^1.4.8", "@types/zxcvbn": "^4.4.1", diff --git a/packages/extension/src/configs/constants.ts b/packages/extension/src/configs/constants.ts index c8eb939dc..4edbac064 100644 --- a/packages/extension/src/configs/constants.ts +++ b/packages/extension/src/configs/constants.ts @@ -1,3 +1,4 @@ -export const EXTENSION_VERSION = "0.0.1"; +import packagejson from "@/../package.json"; +export const EXTENSION_VERSION = packagejson.version; export const EXTENSION_NAMESPACE = "dfc62431af1c3c1258035e5ab4058b6440e507238cf0fe429ea39827a7ee43fc"; //keccak256("enkrypt") diff --git a/packages/extension/src/libs/background/external/handle-persistent-events.ts b/packages/extension/src/libs/background/external/handle-persistent-events.ts new file mode 100644 index 000000000..a242a687b --- /dev/null +++ b/packages/extension/src/libs/background/external/handle-persistent-events.ts @@ -0,0 +1,53 @@ +import PersistentEvents from "@/libs/persistent-events"; +import Browser from "webextension-polyfill"; +import { sendToWindow } from "@/libs/messenger/extension"; +import { OnMessageResponse } from "@enkryptcom/types"; +import { EnkryptProviderEventMethods } from "@/types/provider"; +import type BackgroundHandler from ".."; + +async function handlePersistentEvents(this: BackgroundHandler) { + const persistentEvents = new PersistentEvents(); + const allPersistentEvents = await persistentEvents.getAllEvents(); + const tabs = Object.keys(allPersistentEvents).map((s) => parseInt(s)); + const persistentEventPromises: Promise[] = []; + tabs.forEach((tab) => { + const tabPromise = Browser.tabs + .get(tab) + .then(() => { + const eventPromises: Promise[] = []; + allPersistentEvents[tab].forEach((persistentEvent) => { + const promise = this.externalHandler(persistentEvent.event, { + savePersistentEvents: false, + }).then((newResponse) => { + if ( + !newResponse.error && + newResponse.result !== persistentEvent.response.result + ) { + return sendToWindow( + { + provider: persistentEvent.event.provider, + message: JSON.stringify({ + method: EnkryptProviderEventMethods.persistentEvents, + params: [ + JSON.parse(persistentEvent.event.message), + persistentEvent.response.result, + newResponse.result, + ], + }), + }, + tab + ); + } + }); + eventPromises.push(promise); + }); + return Promise.all(eventPromises); + }) + .catch(() => { + persistentEvents.deleteEvents(tab); + }); + persistentEventPromises.push(tabPromise as any); + }); + await Promise.all(persistentEventPromises); +} +export default handlePersistentEvents; diff --git a/packages/extension/src/libs/background/external/index.ts b/packages/extension/src/libs/background/external/index.ts new file mode 100644 index 000000000..1774a58d2 --- /dev/null +++ b/packages/extension/src/libs/background/external/index.ts @@ -0,0 +1,2 @@ +import handlePersistentEvents from "./handle-persistent-events"; +export { handlePersistentEvents }; diff --git a/packages/extension/src/libs/background/index.ts b/packages/extension/src/libs/background/index.ts index ec12f5f55..dd71873f5 100644 --- a/packages/extension/src/libs/background/index.ts +++ b/packages/extension/src/libs/background/index.ts @@ -1,18 +1,13 @@ import { - ActionSendMessage, InternalMethods, InternalOnMessageResponse, Message, } from "@/types/messenger"; -import { KeyRecord, KeyRecordAdd, RPCRequestType } from "@enkryptcom/types"; +import { RPCRequestType } from "@enkryptcom/types"; import { getCustomError } from "../error"; import KeyRingBase from "../keyring/keyring"; import { sendToWindow } from "@/libs/messenger/extension"; -import { - EnkryptProviderEventMethods, - NodeType, - ProviderName, -} from "@/types/provider"; +import { ProviderName } from "@/types/provider"; import { OnMessageResponse } from "@enkryptcom/types"; import Providers from "@/providers"; import Browser from "webextension-polyfill"; @@ -20,7 +15,17 @@ import TabInfo from "@/libs/utils/tab-info"; import PersistentEvents from "@/libs/persistent-events"; import DomainState from "@/libs/domain-state"; import { TabProviderType, ProviderType, ExternalMessageOptions } from "./types"; -import { getNetworkByName, getProviderNetworkByName } from "../utils/networks"; +import { getProviderNetworkByName } from "../utils/networks"; +import { + sign, + getEthereumPubKey, + ethereumDecrypt, + unlock, + changeNetwork, + sendToTab, + newAccount, +} from "./internal"; +import { handlePersistentEvents } from "./external"; class BackgroundHandler { #keyring: KeyRingBase; @@ -39,48 +44,7 @@ class BackgroundHandler { this.#providers = Providers; } async init(): Promise { - const allPersistentEvents = await this.#persistentEvents.getAllEvents(); - const tabs = Object.keys(allPersistentEvents).map((s) => parseInt(s)); - const persistentEventPromises: Promise[] = []; - tabs.forEach((tab) => { - const tabPromise = Browser.tabs - .get(tab) - .then(() => { - const eventPromises: Promise[] = []; - allPersistentEvents[tab].forEach((persistentEvent) => { - const promise = this.externalHandler(persistentEvent.event, { - savePersistentEvents: false, - }).then((newResponse) => { - if ( - !newResponse.error && - newResponse.result !== persistentEvent.response.result - ) { - return sendToWindow( - { - provider: persistentEvent.event.provider, - message: JSON.stringify({ - method: EnkryptProviderEventMethods.persistentEvents, - params: [ - JSON.parse(persistentEvent.event.message), - persistentEvent.response.result, - newResponse.result, - ], - }), - }, - tab - ); - } - }); - eventPromises.push(promise); - }); - return Promise.all(eventPromises); - }) - .catch(() => { - this.#persistentEvents.deleteEvents(tab); - }); - persistentEventPromises.push(tabPromise as any); - }); - await Promise.all(persistentEventPromises); + await handlePersistentEvents.bind(this)(); } async externalHandler( msg: Message, @@ -150,157 +114,32 @@ class BackgroundHandler { } internalHandler(msg: Message): Promise { const message = JSON.parse(msg.message) as RPCRequestType; - if (message.method === InternalMethods.sign) { - if (!message.params || message.params.length < 2) - return Promise.resolve({ - error: getCustomError("background: invalid params for signing"), - }); - const msgHash = message.params[0] as `0x${string}`; - const account = message.params[1] as KeyRecord; - return this.#keyring - .sign(msgHash, account) - .then((sig) => { - return { - result: JSON.stringify(sig), - }; - }) - .catch((e) => { - return { - error: getCustomError(e.message), - }; - }); - } else if ( - message.method === InternalMethods.getEthereumEncryptionPublicKey - ) { - if (!message.params || message.params.length < 1) - return Promise.resolve({ - error: getCustomError("background: invalid params for public key"), - }); - const account = message.params[0] as KeyRecord; - return this.#keyring - .getEthereumEncryptionPublicKey(account) - .then((pubkey) => { - return { - result: JSON.stringify(pubkey), - }; - }) - .catch((e) => { - return { - error: getCustomError(e.message), - }; - }); - } else if (message.method === InternalMethods.ethereumDecrypt) { - if (!message.params || message.params.length < 2) - return Promise.resolve({ - error: getCustomError("background: invalid params for decrypt"), - }); - const encryptedMessage = message.params[0] as string; - const account = message.params[1] as KeyRecord; - return this.#keyring - .ethereumDecrypt(encryptedMessage, account) - .then((msg) => { - return { - result: JSON.stringify(msg), - }; - }) - .catch((e) => { - return { - error: getCustomError(e.message), - }; - }); - } else if (message.method === InternalMethods.unlock) { - if (!message.params || message.params.length < 1) + switch (message.method) { + case InternalMethods.sign: + return sign(this.#keyring, message); + case InternalMethods.getEthereumEncryptionPublicKey: + return getEthereumPubKey(this.#keyring, message); + case InternalMethods.ethereumDecrypt: + return ethereumDecrypt(this.#keyring, message); + case InternalMethods.unlock: + return unlock(this.#keyring, message); + case InternalMethods.changeNetwork: + return changeNetwork(msg, this.#tabProviders); + case InternalMethods.isLocked: return Promise.resolve({ - error: getCustomError("background: invalid params for unlocking"), + result: JSON.stringify(this.#keyring.isLocked()), }); - const password = message.params[0] as string; - return this.#keyring - .unlock(password) - .then(() => { - return { - result: JSON.stringify(true), - }; - }) - .catch((e) => { - return { - error: getCustomError(e.message), - }; - }); - } else if (message.method === InternalMethods.changeNetwork) { - if (!message.params || message.params.length < 1) + case InternalMethods.sendToTab: + return sendToTab(msg, this.#tabProviders); + case InternalMethods.getNewAccount: + case InternalMethods.saveNewAccount: + return newAccount(this.#keyring, message); + default: return Promise.resolve({ error: getCustomError( - "background: invalid params for change network" + `background: unknown method: ${message.method}` ), }); - const networkName = message.params[0] as string; - const network = getNetworkByName(networkName) as NodeType; - const actionMsg = msg as any as ActionSendMessage; - if ( - actionMsg.provider && - actionMsg.tabId && - this.#tabProviders[actionMsg.provider][actionMsg.tabId] - ) { - this.#tabProviders[actionMsg.provider][ - actionMsg.tabId - ].setRequestProvider(network); - } - return Promise.resolve({ - result: JSON.stringify(true), - }); - } else if (message.method === InternalMethods.isLocked) { - return Promise.resolve({ - result: JSON.stringify(this.#keyring.isLocked()), - }); - } else if (message.method === InternalMethods.sendToTab) { - const actionMsg = msg as any as ActionSendMessage; - if ( - actionMsg.provider && - actionMsg.tabId && - this.#tabProviders[actionMsg.provider][actionMsg.tabId] - ) { - this.#tabProviders[actionMsg.provider][ - actionMsg.tabId - ].sendNotification( - JSON.stringify(message.params?.length ? message.params[0] : {}) - ); - return Promise.resolve({ - result: JSON.stringify(true), - }); - } else { - return Promise.resolve({ - result: JSON.stringify(false), - }); - } - } else if ( - message.method === InternalMethods.getNewAccount || - message.method === InternalMethods.saveNewAccount - ) { - if (!message.params || message.params.length < 1) - return Promise.resolve({ - error: getCustomError("background: invalid params for new account"), - }); - const method = - message.method === InternalMethods.getNewAccount - ? "getNewAccount" - : "saveNewAccount"; - const keyrecord = message.params[0] as KeyRecordAdd; - return this.#keyring - [method](keyrecord) - .then((res) => { - return { - result: JSON.stringify(res), - }; - }) - .catch((e) => { - return { - error: getCustomError(e.message), - }; - }); - } else { - return Promise.resolve({ - error: getCustomError(`background: unknown method: ${message.method}`), - }); } } } diff --git a/packages/extension/src/libs/background/internal/change-network.ts b/packages/extension/src/libs/background/internal/change-network.ts new file mode 100644 index 000000000..4c09594bf --- /dev/null +++ b/packages/extension/src/libs/background/internal/change-network.ts @@ -0,0 +1,38 @@ +import { getCustomError } from "@/libs/error"; +import { getNetworkByName } from "@/libs/utils/networks"; +import { BaseNetwork } from "@/types/base-network"; +import { + ActionSendMessage, + InternalOnMessageResponse, + Message, +} from "@/types/messenger"; +import { RPCRequestType } from "@enkryptcom/types"; +import { TabProviderType } from "../types"; + +const changeNetwork = ( + msg: Message, + tabProviders: TabProviderType +): Promise => { + const message = JSON.parse(msg.message) as RPCRequestType; + if (!message.params || message.params.length < 1) + return Promise.resolve({ + error: getCustomError("background: invalid params for change network"), + }); + const networkName = message.params[0] as string; + const network = getNetworkByName(networkName) as BaseNetwork; + const actionMsg = msg as any as ActionSendMessage; + if ( + actionMsg.provider && + actionMsg.tabId && + tabProviders[actionMsg.provider][actionMsg.tabId] + ) { + tabProviders[actionMsg.provider][actionMsg.tabId].setRequestProvider( + network + ); + } + return Promise.resolve({ + result: JSON.stringify(true), + }); +}; + +export default changeNetwork; diff --git a/packages/extension/src/libs/background/internal/ethereum-decrypt.ts b/packages/extension/src/libs/background/internal/ethereum-decrypt.ts new file mode 100644 index 000000000..ee9ddde01 --- /dev/null +++ b/packages/extension/src/libs/background/internal/ethereum-decrypt.ts @@ -0,0 +1,30 @@ +import { getCustomError } from "@/libs/error"; +import KeyRingBase from "@/libs/keyring/keyring"; +import { InternalOnMessageResponse } from "@/types/messenger"; +import { KeyRecord, RPCRequestType } from "@enkryptcom/types"; + +const ethereumDecrypt = ( + keyring: KeyRingBase, + message: RPCRequestType +): Promise => { + if (!message.params || message.params.length < 2) + return Promise.resolve({ + error: getCustomError("background: invalid params for decrypt"), + }); + const encryptedMessage = message.params[0] as string; + const account = message.params[1] as KeyRecord; + return keyring + .ethereumDecrypt(encryptedMessage, account) + .then((msg) => { + return { + result: JSON.stringify(msg), + }; + }) + .catch((e) => { + return { + error: getCustomError(e.message), + }; + }); +}; + +export default ethereumDecrypt; diff --git a/packages/extension/src/libs/background/internal/ethereum-pub-key.ts b/packages/extension/src/libs/background/internal/ethereum-pub-key.ts new file mode 100644 index 000000000..07380172a --- /dev/null +++ b/packages/extension/src/libs/background/internal/ethereum-pub-key.ts @@ -0,0 +1,29 @@ +import { getCustomError } from "@/libs/error"; +import KeyRingBase from "@/libs/keyring/keyring"; +import { InternalOnMessageResponse } from "@/types/messenger"; +import { KeyRecord, RPCRequestType } from "@enkryptcom/types"; + +const getEthereumPubKey = ( + keyring: KeyRingBase, + message: RPCRequestType +): Promise => { + if (!message.params || message.params.length < 1) + return Promise.resolve({ + error: getCustomError("background: invalid params for public key"), + }); + const account = message.params[0] as KeyRecord; + return keyring + .getEthereumEncryptionPublicKey(account) + .then((pubkey) => { + return { + result: JSON.stringify(pubkey), + }; + }) + .catch((e) => { + return { + error: getCustomError(e.message), + }; + }); +}; + +export default getEthereumPubKey; diff --git a/packages/extension/src/libs/background/internal/index.ts b/packages/extension/src/libs/background/internal/index.ts new file mode 100644 index 000000000..0e4d230aa --- /dev/null +++ b/packages/extension/src/libs/background/internal/index.ts @@ -0,0 +1,16 @@ +import sign from "./sign"; +import getEthereumPubKey from "./ethereum-pub-key"; +import ethereumDecrypt from "./ethereum-decrypt"; +import unlock from "./unlock"; +import changeNetwork from "./change-network"; +import sendToTab from "./send-to-tab"; +import newAccount from "./new-account"; +export { + sign, + getEthereumPubKey, + ethereumDecrypt, + unlock, + changeNetwork, + sendToTab, + newAccount, +}; diff --git a/packages/extension/src/libs/background/internal/new-account.ts b/packages/extension/src/libs/background/internal/new-account.ts new file mode 100644 index 000000000..91638812c --- /dev/null +++ b/packages/extension/src/libs/background/internal/new-account.ts @@ -0,0 +1,32 @@ +import { getCustomError } from "@/libs/error"; +import KeyRingBase from "@/libs/keyring/keyring"; +import { InternalMethods, InternalOnMessageResponse } from "@/types/messenger"; +import { KeyRecordAdd, RPCRequestType } from "@enkryptcom/types"; + +const newAccount = ( + keyring: KeyRingBase, + message: RPCRequestType +): Promise => { + if (!message.params || message.params.length < 1) + return Promise.resolve({ + error: getCustomError("background: invalid params for new account"), + }); + const method = + message.method === InternalMethods.getNewAccount + ? "getNewAccount" + : "saveNewAccount"; + const keyrecord = message.params[0] as KeyRecordAdd; + return keyring[method](keyrecord) + .then((res) => { + return { + result: JSON.stringify(res), + }; + }) + .catch((e) => { + return { + error: getCustomError(e.message), + }; + }); +}; + +export default newAccount; diff --git a/packages/extension/src/libs/background/internal/send-to-tab.ts b/packages/extension/src/libs/background/internal/send-to-tab.ts new file mode 100644 index 000000000..57638bc1d --- /dev/null +++ b/packages/extension/src/libs/background/internal/send-to-tab.ts @@ -0,0 +1,33 @@ +import { + ActionSendMessage, + InternalOnMessageResponse, + Message, +} from "@/types/messenger"; +import { RPCRequestType } from "@enkryptcom/types"; +import { TabProviderType } from "../types"; + +const sendToTab = ( + msg: Message, + tabProviders: TabProviderType +): Promise => { + const message = JSON.parse(msg.message) as RPCRequestType; + const actionMsg = msg as any as ActionSendMessage; + if ( + actionMsg.provider && + actionMsg.tabId && + tabProviders[actionMsg.provider][actionMsg.tabId] + ) { + tabProviders[actionMsg.provider][actionMsg.tabId].sendNotification( + JSON.stringify(message.params?.length ? message.params[0] : {}) + ); + return Promise.resolve({ + result: JSON.stringify(true), + }); + } else { + return Promise.resolve({ + result: JSON.stringify(false), + }); + } +}; + +export default sendToTab; diff --git a/packages/extension/src/libs/background/internal/sign.ts b/packages/extension/src/libs/background/internal/sign.ts new file mode 100644 index 000000000..9ede3ea40 --- /dev/null +++ b/packages/extension/src/libs/background/internal/sign.ts @@ -0,0 +1,30 @@ +import { getCustomError } from "@/libs/error"; +import KeyRingBase from "@/libs/keyring/keyring"; +import { InternalOnMessageResponse } from "@/types/messenger"; +import { KeyRecord, RPCRequestType } from "@enkryptcom/types"; + +const sign = ( + keyring: KeyRingBase, + message: RPCRequestType +): Promise => { + if (!message.params || message.params.length < 2) + return Promise.resolve({ + error: getCustomError("background: invalid params for signing"), + }); + const msgHash = message.params[0] as `0x${string}`; + const account = message.params[1] as KeyRecord; + return keyring + .sign(msgHash, account) + .then((sig) => { + return { + result: JSON.stringify(sig), + }; + }) + .catch((e) => { + return { + error: getCustomError(e.message), + }; + }); +}; + +export default sign; diff --git a/packages/extension/src/libs/background/internal/unlock.ts b/packages/extension/src/libs/background/internal/unlock.ts new file mode 100644 index 000000000..75bca71dc --- /dev/null +++ b/packages/extension/src/libs/background/internal/unlock.ts @@ -0,0 +1,29 @@ +import { getCustomError } from "@/libs/error"; +import KeyRingBase from "@/libs/keyring/keyring"; +import { InternalOnMessageResponse } from "@/types/messenger"; +import { RPCRequestType } from "@enkryptcom/types"; + +const unlock = ( + keyring: KeyRingBase, + message: RPCRequestType +): Promise => { + if (!message.params || message.params.length < 1) + return Promise.resolve({ + error: getCustomError("background: invalid params for unlocking"), + }); + const password = message.params[0] as string; + return keyring + .unlock(password) + .then(() => { + return { + result: JSON.stringify(true), + }; + }) + .catch((e) => { + return { + error: getCustomError(e.message), + }; + }); +}; + +export default unlock; diff --git a/packages/extension/src/libs/cache-fetch/index.ts b/packages/extension/src/libs/cache-fetch/index.ts index 4c0d730ef..03416bdd8 100644 --- a/packages/extension/src/libs/cache-fetch/index.ts +++ b/packages/extension/src/libs/cache-fetch/index.ts @@ -4,7 +4,10 @@ import { InternalStorageNamespace } from "@/types/provider"; import { keccak256 } from "web3-utils"; const STORAGE_TTL = 1000 * 60 * 60 * 24; const TIMESTAMP = "timestamp"; -const cacheFetch = async (options: RequestOptions, ttl: number) => { +const cacheFetch = async ( + options: RequestOptions, + ttl: number = STORAGE_TTL +) => { const storage = new BrowserStorage(InternalStorageNamespace.cacheFetch); const storagetimestamp = await storage.get(TIMESTAMP); if ( diff --git a/packages/extension/src/libs/messenger/eventbus.ts b/packages/extension/src/libs/messenger/eventbus.ts new file mode 100644 index 000000000..cd5840c85 --- /dev/null +++ b/packages/extension/src/libs/messenger/eventbus.ts @@ -0,0 +1,22 @@ +import { createNanoEvents } from "nanoevents"; +const eventBus = createNanoEvents(); +const EventBusEmit = (key: string, message: any): Promise => { + return new Promise((resolve, reject) => { + eventBus.emit(key, message, (err: Error, res: any) => { + if (err) reject(err); + else resolve(res); + }); + }); +}; +const EventBusOn = (key: string, cb: (message: any) => Promise) => { + eventBus.on( + key, + (message: any, ebCB: (err: Error | null, res?: any) => void) => { + cb(message) + .then((res) => ebCB(null, res)) + .catch((err) => ebCB(err)); + } + ); +}; + +export { EventBusEmit, EventBusOn }; diff --git a/packages/extension/src/libs/messenger/extension.ts b/packages/extension/src/libs/messenger/extension.ts index 4fda1bfe1..4eb190765 100644 --- a/packages/extension/src/libs/messenger/extension.ts +++ b/packages/extension/src/libs/messenger/extension.ts @@ -16,6 +16,7 @@ import { } from "@/types/messenger"; import { OnMessageResponse } from "@enkryptcom/types"; import { assert } from "chai"; +import { EventBusEmit, EventBusOn } from "./eventbus"; export const sendToWindow = ( message: SendMessage, @@ -32,6 +33,14 @@ export const setContentScriptNamespace = (): void => { allowWindowMessaging(EXTENSION_NAMESPACE); }; +export const sendToBackgroundFromBackground = ( + message: SendMessage +): Promise => { + return EventBusEmit(MessageType.BACKGROUND_REQUEST, message).then( + (res) => res as unknown as InternalOnMessageResponse + ); +}; + export const sendToBackgroundFromNewWindow = ( message: SendMessage ): Promise => { @@ -76,7 +85,7 @@ const backgroundOnMessage = ( export const backgroundOnMessageFromWindow = (cb: onMessageType): void => { backgroundOnMessage(MessageType.WINDOW_REQUEST, (message) => { assert( - message.sender.context === "window", + message.sender.context === Destination.window, "Message didnt come from window" ); return cb(message); @@ -88,7 +97,7 @@ export const backgroundOnMessageFromNewWindow = ( ): void => { backgroundOnMessage(MessageType.NEWWINDOW_REQUEST, async (message) => { assert( - message.sender.context === "new-window", + message.sender.context === Destination.newWindow, "Message didnt come from new-window" ); return cb(message); @@ -99,7 +108,10 @@ export const backgroundOnMessageFromAction = ( cb: InternalMessageType ): void => { backgroundOnMessage(MessageType.ACTION_REQUEST, async (message) => { - assert(message.sender.context === "popup", "Message didnt come from popup"); + assert( + message.sender.context === Destination.popup, + "Message didnt come from popup" + ); return cb(message); }); }; @@ -109,9 +121,17 @@ export const newWindowOnMessageFromBackground = ( ): void => { backgroundOnMessage(MessageType.NEWWINDOW_REQUEST, async (message) => { assert( - message.sender.context === "background", + message.sender.context === Destination.background, "Message didnt come from background" ); return cb(message); }); }; + +export const backgroundOnMessageFromBackground = ( + cb: InternalMessageType +): void => { + EventBusOn(MessageType.BACKGROUND_REQUEST, async (message) => { + return cb(message); + }); +}; diff --git a/packages/extension/src/libs/networks-state/index.ts b/packages/extension/src/libs/networks-state/index.ts new file mode 100644 index 000000000..1b0e18892 --- /dev/null +++ b/packages/extension/src/libs/networks-state/index.ts @@ -0,0 +1,145 @@ +import BrowserStorage from "../common/browser-storage"; +import { getAllNetworks, POPULAR_NAMES } from "../utils/networks"; +import { InternalStorageNamespace } from "@/types/provider"; +import { IState, StorageKeys, NetworkStorageElement } from "./types"; + +class NetworksState { + private storage: BrowserStorage; + + constructor() { + this.storage = new BrowserStorage(InternalStorageNamespace.networksState); + } + + private async setInitialActiveNetworks(): Promise { + const networks: NetworkStorageElement[] = getAllNetworks().map( + ({ name }) => { + if (POPULAR_NAMES.includes(name)) { + return { name, isActive: true }; + } else { + return { name, isActive: false }; + } + } + ); + + networks + .filter((network) => network.isActive) + .forEach((network, index) => (network.order = index)); + + await this.storage.set(StorageKeys.networkInfo, { networks }); + } + + async setNetworkStatus( + targetNetworkName: string, + isActive: boolean + ): Promise { + const state: IState = await this.storage.get(StorageKeys.networkInfo); + + if (state.networks) { + const targetNetwork = state.networks.find( + (network) => network.name === targetNetworkName + ); + + if (targetNetwork !== undefined) { + targetNetwork.isActive = isActive; + + if (isActive) { + const numActive = state.networks.filter( + (network) => network.isActive + ).length; + + targetNetwork.order = numActive - 1; + } else { + targetNetwork.order = undefined; + const activeNetworks = state.networks.filter( + (network) => network.isActive + ); + + activeNetworks.sort((a, b) => { + if (a.order! < b.order!) return -1; + return 1; + }); + + activeNetworks.forEach((network, index) => { + network.order = index; + }); + } + } + + await this.storage.set(StorageKeys.networkInfo, state); + } + } + + async getActiveNetworkNames(): Promise { + const state: IState | undefined = await this.storage.get( + StorageKeys.networkInfo + ); + if (state && state.networks) { + return state.networks + .filter((network) => network.isActive) + .sort((a, b) => { + if (a.order! < b.order!) return -1; + return 1; + }) + .map(({ name }) => name); + } else { + await this.setInitialActiveNetworks(); + return POPULAR_NAMES; + } + } + + async reorderNetwork( + targetNetworkName: string, + beforeNetworkName: string | undefined + ): Promise { + const state: IState | undefined = await this.storage.get( + StorageKeys.networkInfo + ); + + if (state && state.networks) { + const activeNetworks = state.networks + .filter((network) => network.isActive) + .sort((a, b) => { + if (a.order! < b.order!) return -1; + return 1; + }); + + const targetNetwork = activeNetworks.find( + (network) => network.name === targetNetworkName + ); + + const beforeNetwork = activeNetworks.find( + (network) => network.name === beforeNetworkName + ); + + if (targetNetwork !== undefined) { + if (beforeNetwork === undefined) { + targetNetwork.order = 0; + } else { + targetNetwork.order = beforeNetwork.order! + 1; + } + } + + activeNetworks.sort((a, b) => { + if (a.order! < b.order!) { + return -1; + } else if (a.order === b.order && a.name === targetNetworkName) { + return -1; + } + + return 1; + }); + + activeNetworks.forEach((network, index) => { + network.order = index; + }); + + await this.storage.set(StorageKeys.networkInfo, state); + } + } + + async getState(): Promise { + return this.storage.get(StorageKeys.networkInfo); + } +} + +export default NetworksState; diff --git a/packages/extension/src/libs/networks-state/types.ts b/packages/extension/src/libs/networks-state/types.ts new file mode 100644 index 000000000..ac2d26061 --- /dev/null +++ b/packages/extension/src/libs/networks-state/types.ts @@ -0,0 +1,13 @@ +export enum StorageKeys { + networkInfo = "network-info", +} + +export interface NetworkStorageElement { + name: string; + isActive: boolean; + order?: number; +} + +export interface IState { + networks?: Array<{ name: string; isActive: boolean; order?: number }>; +} diff --git a/packages/extension/src/libs/nft-handlers/mew.ts b/packages/extension/src/libs/nft-handlers/mew.ts new file mode 100644 index 000000000..b0a673de5 --- /dev/null +++ b/packages/extension/src/libs/nft-handlers/mew.ts @@ -0,0 +1,53 @@ +import { NFTCollection, NFTItem } from "@/types/nft"; +import { NodeType } from "@/types/provider"; +import Networks from "@/providers/ethereum/networks"; +import { + ContentRepresentation, + ContentURL, + NFTCollection as MEWNFTCollection, +} from "./types/mew"; +import cacheFetch from "../cache-fetch"; +const MEW_ENDPOINT = "https://mainnet.mewwallet.dev/v3/"; +const CACHE_TTL = 60 * 1000; +const getBestImageURL = (content: ContentURL[]) => { + const priority = [ContentRepresentation.IMAGE]; + for (const pri of priority) { + for (const cont of content) { + if (cont.type === pri) return cont.url; + } + } + return ""; +}; +export default async ( + network: NodeType, + address: string +): Promise => { + const supportedNetworks = [Networks.ethereum.name]; + if (!supportedNetworks.includes(network.name)) + throw new Error("MEW: network not supported"); + const fetchAll = (): Promise => { + const query = `${MEW_ENDPOINT}nfts/account?address=${address}`; + return cacheFetch({ url: query }, CACHE_TTL).then((json) => { + return json as MEWNFTCollection[]; + }); + }; + const allItems = await fetchAll(); + return allItems.map((item) => { + const ret: NFTCollection = { + name: item.name, + description: item.description, + image: item.image, + contract: item.contract_address, + items: item.assets.map((asset) => { + const retAsset: NFTItem = { + contract: item.contract_address, + id: asset.token_id, + image: getBestImageURL(asset.urls), + name: asset.name, + }; + return retAsset; + }), + }; + return ret; + }); +}; diff --git a/packages/extension/src/libs/nft-handlers/rarible.ts b/packages/extension/src/libs/nft-handlers/rarible.ts index d46eb40e4..565cd209f 100644 --- a/packages/extension/src/libs/nft-handlers/rarible.ts +++ b/packages/extension/src/libs/nft-handlers/rarible.ts @@ -1,5 +1,6 @@ import { NFTCollection, NFTItem } from "@/types/nft"; import { NodeType } from "@/types/provider"; +import cacheFetch from "../cache-fetch"; import { ContentRepresentation, NFTContent, @@ -7,14 +8,19 @@ import { RaribleItem, } from "./types/rarible"; const RARIBLE_ENDPOINT = "https://api.rarible.org/v0.1/"; +const ASSET_CACHE_TTL = 60 * 1000; +const COLLECTION_CACHE_TTL = 60 * 60 * 1000; const setCollectionInfo = ( collection: NFTCollection[] ): Promise => { const promises: Promise[] = []; collection.forEach((col) => { - const promise = fetch(`${RARIBLE_ENDPOINT}collections/${col.name}`) - .then((res) => res.json()) - .then((json: RaribleCollectionItem) => json); + const promise = cacheFetch( + { + url: `${RARIBLE_ENDPOINT}collections/${col.name}`, + }, + COLLECTION_CACHE_TTL + ).then((json: RaribleCollectionItem) => json); promises.push(promise); }); return Promise.all(promises).then((colInfo) => { @@ -59,25 +65,26 @@ export default async ( supportedNetworks[network.name] }&owner=ETHEREUM:${address}`; if (continuation) query += `&continuation=${continuation}`; - return fetch(query) - .then((res) => res.json()) - .then((json) => { - const items: RaribleItem[] = json.items; - allItems = allItems.concat(items); - if (json.continuation) return fetchAll(json.continuation); - }); + return cacheFetch( + { + url: query, + }, + ASSET_CACHE_TTL + ).then((json) => { + const items: RaribleItem[] = json.items; + allItems = allItems.concat(items); + if (json.continuation) return fetchAll(json.continuation); + }); }; await fetchAll(); const collection: Record = {}; allItems.forEach((item) => { if (!collection[item.collection]) collection[item.collection] = []; const newItem = { - collection: item.collection, contract: item.contract.split(":")[1], id: item.tokenId, image: getBestImageURL(item.meta?.content || []), name: item.meta?.name || "", - valueUSD: item.bestBidOrder ? item.bestBidOrder.takePriceUsd : "0", }; if (newItem.image && newItem.name) collection[item.collection].push(newItem); @@ -89,8 +96,13 @@ export default async ( description: "", image: "", items, + contract: id.split(":")[1], }); } await setCollectionInfo(nftcollections); - return nftcollections.filter((col) => col.name && col.items.length); + const collections = nftcollections.filter( + (col) => col.name && col.items.length + ); + collections.sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)); + return collections; }; diff --git a/packages/extension/src/libs/nft-handlers/types/mew.ts b/packages/extension/src/libs/nft-handlers/types/mew.ts new file mode 100644 index 000000000..e78bab9cc --- /dev/null +++ b/packages/extension/src/libs/nft-handlers/types/mew.ts @@ -0,0 +1,46 @@ +export enum ContentRepresentation { + IMAGE = "IMAGE", + MEDIA = "MEDIA", +} +export enum SchemaType { + ERC721 = "ERC721", + ERC1155 = "ERC1155", +} +export interface Trait { + trait: string; + count: number; + value: string; + percentage: string; +} +export interface ContentURL { + type: ContentRepresentation; + url: string; +} +export interface NFTAsset { + token_id: string; + name: string; + description: string; + traits: Trait[]; + urls: ContentURL[]; + opensea_url: string; +} +export interface SocialLinks { + website?: string; + discord?: string; +} +export interface CollectionStats { + count: string; + owners: string; +} +export interface NFTCollection { + name: string; + description: string; + image: string; + schema_type: SchemaType; + contract_address: string; + contract_name: string; + contract_symbol: string; + social: SocialLinks; + stats: CollectionStats; + assets: NFTAsset[]; +} diff --git a/packages/extension/src/libs/nft-handlers/types/rarible.ts b/packages/extension/src/libs/nft-handlers/types/rarible.ts index c19d1a932..5c01d284f 100644 --- a/packages/extension/src/libs/nft-handlers/types/rarible.ts +++ b/packages/extension/src/libs/nft-handlers/types/rarible.ts @@ -10,6 +10,7 @@ export enum ContentRepresentation { ORIGINAL = "ORIGINAL", BIG = "BIG", PREVIEW = "PREVIEW", + IMAGE = "IMAGE", } export interface NFTContent { "@type": "IMAGE"; diff --git a/packages/extension/src/libs/nft-state/index.ts b/packages/extension/src/libs/nft-state/index.ts new file mode 100644 index 000000000..da5fbad6f --- /dev/null +++ b/packages/extension/src/libs/nft-state/index.ts @@ -0,0 +1,97 @@ +import { InternalStorageNamespace } from "@/types/provider"; +import BrowserStorage from "../common/browser-storage"; +import { NFTInfo, StorageKey } from "./types"; +class NFTState { + #storage: BrowserStorage; + constructor() { + this.#storage = new BrowserStorage(InternalStorageNamespace.nftState); + } + async getFavoriteNFTs(): Promise { + return this.getState(StorageKey.favoriteNFTs); + } + async getHiddenNFTs(): Promise { + return this.getState(StorageKey.hiddenNFTs); + } + async #setNFTInfo( + collectionIdentifier: string, + nftID: string, + storeKey: StorageKey + ) { + const state = await this.getState(storeKey); + if (!state[collectionIdentifier]) state[collectionIdentifier] = []; + if (!state[collectionIdentifier].includes(nftID)) { + state[collectionIdentifier].push(nftID); + await this.setState(state, storeKey); + } + } + async #deleteNFTInfo( + collectionIdentifier: string, + nftID: string, + storeKey: StorageKey + ) { + const state = await this.getState(storeKey); + if (!state[collectionIdentifier]) state[collectionIdentifier] = []; + state[collectionIdentifier] = state[collectionIdentifier].filter( + (item) => item !== nftID + ); + await this.setState(state, storeKey); + } + async setFavoriteNFT( + collectionIdentifier: string, + nftID: string + ): Promise { + return this.#setNFTInfo( + collectionIdentifier, + nftID, + StorageKey.favoriteNFTs + ); + } + async deleteFavoriteNFT( + collectionIdentifier: string, + nftID: string + ): Promise { + return this.#deleteNFTInfo( + collectionIdentifier, + nftID, + StorageKey.favoriteNFTs + ); + } + async setHiddenNFT( + collectionIdentifier: string, + nftID: string + ): Promise { + return this.#setNFTInfo(collectionIdentifier, nftID, StorageKey.hiddenNFTs); + } + async deleteHiddenNFT( + collectionIdentifier: string, + nftID: string + ): Promise { + return this.#deleteNFTInfo( + collectionIdentifier, + nftID, + StorageKey.hiddenNFTs + ); + } + async getState(storeKey: StorageKey): Promise> { + const allStates: Record = await this.#storage.get( + storeKey + ); + if (!allStates) return {}; + return allStates; + } + async setState( + state: Record, + storeKey: StorageKey + ): Promise { + await this.#storage.set(storeKey, state); + } + async deleteState(storeKey: StorageKey): Promise { + await this.#storage.set(storeKey, {}); + } + async deleteAllStates(): Promise { + const allStores = [StorageKey.favoriteNFTs, StorageKey.hiddenNFTs]; + const promises = allStores.map((store) => this.deleteState(store)); + await Promise.all(promises); + } +} +export default NFTState; diff --git a/packages/extension/src/libs/nft-state/types.ts b/packages/extension/src/libs/nft-state/types.ts new file mode 100644 index 000000000..a94f2e86c --- /dev/null +++ b/packages/extension/src/libs/nft-state/types.ts @@ -0,0 +1,5 @@ +export enum StorageKey { + favoriteNFTs = "favorite-nfts", + hiddenNFTs = "hidden-nfts", +} +export type NFTInfo = Record; diff --git a/packages/extension/src/libs/utils/initialize-wallet.ts b/packages/extension/src/libs/utils/initialize-wallet.ts index 86c551c03..8b23ccbfc 100644 --- a/packages/extension/src/libs/utils/initialize-wallet.ts +++ b/packages/extension/src/libs/utils/initialize-wallet.ts @@ -7,12 +7,12 @@ export default async (mnemonic: string, password: string): Promise => { await kr.unlock(password); await kr.saveNewAccount({ basePath: EthereumNetworks.ethereum.basePath, - name: "Account 1", + name: "EVM Account 1", type: EthereumNetworks.ethereum.signer[0], }); await kr.saveNewAccount({ basePath: PolkadotNetworks.polkadot.basePath, - name: "Account 2", + name: "Substrate Account 1", type: PolkadotNetworks.polkadot.signer[0], }); }; diff --git a/packages/extension/src/libs/utils/networks.ts b/packages/extension/src/libs/utils/networks.ts index 57b7bd8f1..32e560e47 100644 --- a/packages/extension/src/libs/utils/networks.ts +++ b/packages/extension/src/libs/utils/networks.ts @@ -1,31 +1,40 @@ -import { NodeType, ProviderName } from "@/types/provider"; +import { ProviderName } from "@/types/provider"; +import { NetworkNames } from "@enkryptcom/types"; import EthereumNetworks from "@/providers/ethereum/networks"; import PolkadotNetworks from "@/providers/polkadot/networks"; -const providerNetworks: Record> = { +import { BaseNetwork } from "@/types/base-network"; +const providerNetworks: Record> = { [ProviderName.ethereum]: EthereumNetworks, [ProviderName.polkadot]: PolkadotNetworks, [ProviderName.enkrypt]: {}, }; -const getAllNetworks = (): NodeType[] => { - return (Object.values(EthereumNetworks) as NodeType[]).concat( - Object.values(PolkadotNetworks) as NodeType[] +const getAllNetworks = (): BaseNetwork[] => { + return (Object.values(EthereumNetworks) as BaseNetwork[]).concat( + Object.values(PolkadotNetworks) as BaseNetwork[] ); }; -const getNetworkByName = (name: string): NodeType | undefined => { +const getNetworkByName = (name: string): BaseNetwork | undefined => { return getAllNetworks().find((net) => net.name === name); }; const getProviderNetworkByName = ( provider: ProviderName, networkName: string -): NodeType | undefined => { - return (Object.values(providerNetworks[provider]) as NodeType[]).find( +): BaseNetwork | undefined => { + return (Object.values(providerNetworks[provider]) as BaseNetwork[]).find( (net) => net.name === networkName ); }; -const DEFAULT_NETWORK_NAME = "ETH"; +const DEFAULT_NETWORK_NAME = NetworkNames.Ethereum; +const POPULAR_NAMES = [ + NetworkNames.Ethereum, + NetworkNames.Matic, + NetworkNames.Polkadot, + NetworkNames.Moonbeam, +]; export { getAllNetworks, getNetworkByName, getProviderNetworkByName, DEFAULT_NETWORK_NAME, + POPULAR_NAMES, }; diff --git a/packages/extension/src/libs/utils/scroll-settings.ts b/packages/extension/src/libs/utils/scroll-settings.ts new file mode 100644 index 000000000..2bd984e12 --- /dev/null +++ b/packages/extension/src/libs/utils/scroll-settings.ts @@ -0,0 +1,11 @@ +export default ({ + suppressScrollX = false, + suppressScrollY = false, + wheelPropagation = false, +}) => { + return { + suppressScrollY, + suppressScrollX, + wheelPropagation, + }; +}; diff --git a/packages/extension/src/libs/window-promise/handler.ts b/packages/extension/src/libs/window-promise/handler.ts index 95bd4375d..aaa67e62f 100644 --- a/packages/extension/src/libs/window-promise/handler.ts +++ b/packages/extension/src/libs/window-promise/handler.ts @@ -10,11 +10,15 @@ import { } from "@/types/provider"; import { Destination, InternalOnMessageResponse } from "@/types/messenger"; import PublicKeyRing from "@/libs/keyring/public-keyring"; -import type { WindoPromiseType } from "@/types/ui"; +import type { WindowPromiseType } from "@/types/ui"; import { getCustomError } from "@/libs/error"; import { RPCRequestType } from "@enkryptcom/types"; -export default (): WindoPromiseType => { +export default (paramCount: number): Promise => { + let PromResolve: (val: WindowPromiseType) => void; + const RetPromise: Promise = new Promise((resolve) => { + PromResolve = resolve; + }); const options: UnwrapNestedRefs = reactive({ url: "", domain: "", @@ -57,22 +61,33 @@ export default (): WindoPromiseType => { }); } const RPCRequest = JSON.parse(message.message) as ProviderRPCRequest; + if ( + paramCount > 0 && + (!RPCRequest.params || RPCRequest.params.length !== paramCount) + ) { + return Promise.resolve({ + error: getCustomError( + "window-promise-handler: invalid number of params" + ), + }); + } options.domain = RPCRequest.options?.domain as string; options.url = RPCRequest.options?.url as string; options.faviconURL = RPCRequest.options?.faviconURL as string; options.title = RPCRequest.options?.title as string; Request.value = RPCRequest; + PromResolve({ + KeyRing, + Request, + options, + sendToBackground, + Resolve: PromiseResolve, + }); return new Promise((resolve) => { PromiseResolve.value = resolve; }); } ); }); - return { - PromiseResolve, - options, - Request, - KeyRing, - sendToBackground, - }; + return RetPromise; }; diff --git a/packages/extension/src/libs/window-promise/promise.ts b/packages/extension/src/libs/window-promise/promise.ts index 32535e46d..0d3618988 100644 --- a/packages/extension/src/libs/window-promise/promise.ts +++ b/packages/extension/src/libs/window-promise/promise.ts @@ -1,11 +1,15 @@ -import { sendToNewWindowFromBackground } from "@/libs/messenger/extension"; +import { + sendToBackgroundFromBackground, + sendToNewWindowFromBackground, +} from "@/libs/messenger/extension"; import { ProviderName } from "@/types/provider"; import Browser from "webextension-polyfill"; -import { InternalOnMessageResponse } from "@/types/messenger"; +import { InternalMethods, InternalOnMessageResponse } from "@/types/messenger"; import { getCustomError, getError } from "../error"; import getUiPath from "../utils/get-ui-path"; -import { routes as UIRoutes } from "@/ui/provider-pages/enkrypt/routes"; +import UIRoutes from "@/ui/provider-pages/enkrypt/routes/names"; import { ErrorCodes } from "@/providers/ethereum/types"; + const UNLOCK_PATH = getUiPath(UIRoutes.unlock.path, ProviderName.enkrypt); class WindowPromise { private async getRawResponse( @@ -31,8 +35,8 @@ class WindowPromise { url: "#", type: "popup", focused: true, - height: 500, - width: 400, + height: 600, + width: 460, }); const tabId: number | undefined = windowInfo.tabs?.length ? windowInfo.tabs[0].id @@ -55,7 +59,14 @@ class WindowPromise { }); }; const executePromise = async (): Promise => { - if (unlockKeyring) { + const isKeyRingLocked = await sendToBackgroundFromBackground({ + provider: ProviderName.enkrypt, + message: JSON.stringify({ + method: InternalMethods.isLocked, + params: [], + }), + }).then((res) => JSON.parse(res.result as string)); + if (unlockKeyring && isKeyRingLocked) { const unlockKeyring = await this.getRawResponse( Browser.runtime.getURL(UNLOCK_PATH), msg, diff --git a/packages/extension/src/manifest/manifest-chrome.json b/packages/extension/src/manifest/manifest-chrome.json index 6ddb707f3..2e9241162 100644 --- a/packages/extension/src/manifest/manifest-chrome.json +++ b/packages/extension/src/manifest/manifest-chrome.json @@ -15,7 +15,7 @@ "manifest_version": 3, "name": "Enkrypt", "short_name": "Enkrypt", - "permissions": ["storage", "unlimitedStorage", "notifications", "tabs"], + "permissions": ["storage", "unlimitedStorage", "notifications", "tabs", "clipboardRead", "clipboardWrite"], "background": { "service_worker": "scripts/background.js" }, @@ -37,7 +37,7 @@ "all_frames": false } ], - "description": "Enkrypt Chrome Extension - One extension to rule them all", + "description": "Enkrypt Chrome Extension - Everything in the blockchain made easy", "icons": { "16": "assets/img/icons/icon16.png", "32": "assets/img/icons/icon32.png", diff --git a/packages/extension/src/providers/ethereum/index.ts b/packages/extension/src/providers/ethereum/index.ts index 70b344e2d..2fb2d6536 100644 --- a/packages/extension/src/providers/ethereum/index.ts +++ b/packages/extension/src/providers/ethereum/index.ts @@ -1,4 +1,4 @@ -import { EthereumNodeType } from "./types"; +import { BaseNetwork } from "@/types/base-network"; import getRequestProvider, { RequestClass } from "@enkryptcom/request"; import Networks from "./networks"; import { MiddlewareFunction, OnMessageResponse } from "@enkryptcom/types"; @@ -6,18 +6,18 @@ import Middlewares from "./methods"; import EventEmitter from "eventemitter3"; import { BackgroundProviderInterface, - NodeType, ProviderName, ProviderRPCRequest, } from "@/types/provider"; import GetUIPath from "@/libs/utils/get-ui-path"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import UIRoutes from "./ui/routes/names"; +import { EvmNetwork } from "./types/evm-network"; class EthereumProvider extends EventEmitter implements BackgroundProviderInterface { - network: EthereumNodeType; + network: EvmNetwork; requestProvider: RequestClass; middlewares: MiddlewareFunction[] = []; namespace: string; @@ -26,7 +26,7 @@ class EthereumProvider toWindow: (message: string) => void; constructor( toWindow: (message: string) => void, - network: EthereumNodeType = Networks.ethereum + network: EvmNetwork = Networks.ethereum ) { super(); this.network = network; @@ -42,8 +42,8 @@ class EthereumProvider private setMiddleWares(): void { this.middlewares = Middlewares.map((mw) => mw.bind(this)); } - setRequestProvider(network: NodeType): void { - this.network = network as EthereumNodeType; + setRequestProvider(network: BaseNetwork): void { + this.network = network as EvmNetwork; this.requestProvider.changeNetwork(network.node); } async isPersistentEvent(request: ProviderRPCRequest): Promise { diff --git a/packages/extension/src/providers/ethereum/inject.ts b/packages/extension/src/providers/ethereum/inject.ts index c179cf6b3..0d97c64d0 100644 --- a/packages/extension/src/providers/ethereum/inject.ts +++ b/packages/extension/src/providers/ethereum/inject.ts @@ -24,6 +24,7 @@ export class Provider extends EventEmitter implements ProviderInterface { name: ProviderName; type: ProviderType; version: string = EXTENSION_VERSION; + autoRefreshOnNetworkChange = false; sendMessageHandler: SendMessageHandler; constructor(options: ProviderOptions) { super(); @@ -73,12 +74,43 @@ export class Provider extends EventEmitter implements ProviderInterface { handleIncomingMessage(this, msg); } } + +const ProxyHandler = { + proxymethods: ["request", "sendAsync", "send"], + writableVars: ["autoRefreshOnNetworkChange"], + ownKeys(target: Provider) { + return Object.keys(target).concat(this.proxymethods); + }, + set(target: Provider, name: keyof Provider, value: any) { + if (!this.ownKeys(target).includes(name)) this.proxymethods.push(name); + return Reflect.set(target, name, value); + }, + getOwnPropertyDescriptor(target: Provider, name: keyof Provider) { + return { + value: this.get(target, name), + configurable: true, + writable: this.writableVars.includes(name), + enumerable: true, + }; + }, + get(target: Provider, prop: keyof Provider) { + if (typeof target[prop] === "function") { + return (target[prop] as () => any).bind(target); + } + return target[prop]; + }, + has(target: Provider, name: keyof Provider) { + return this.ownKeys(target).includes(name); + }, +}; const injectDocument = ( document: EnkryptWindow | Window, options: ProviderOptions ): void => { const provider = new Provider(options); - document[options.name] = provider; + document[options.name] = new Proxy(provider, ProxyHandler); //proxy is needed due to web3js 1.3.0 callbackify issue. Used in superrare document["enkrypt"]["providers"][options.name] = provider; }; export default injectDocument; + +// if anyone change anything in this file, please make sure to test it against superrare.com and app.multichain.org diff --git a/packages/extension/src/providers/ethereum/libs/abi/erc20.ts b/packages/extension/src/providers/ethereum/libs/abi/erc20.ts index e899b8a1c..92edb05a6 100644 --- a/packages/extension/src/providers/ethereum/libs/abi/erc20.ts +++ b/packages/extension/src/providers/ethereum/libs/abi/erc20.ts @@ -1,4 +1,27 @@ export default [ + { + constant: false, + inputs: [ + { + name: "_to", + type: "address", + }, + { + name: "_value", + type: "uint256", + }, + ], + name: "transfer", + outputs: [ + { + name: "", + type: "bool", + }, + ], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, { constant: true, inputs: [], diff --git a/packages/extension/src/providers/ethereum/libs/api.ts b/packages/extension/src/providers/ethereum/libs/api.ts index 833901447..dcf1a701c 100644 --- a/packages/extension/src/providers/ethereum/libs/api.ts +++ b/packages/extension/src/providers/ethereum/libs/api.ts @@ -1,29 +1,38 @@ import { ProviderAPIInterface } from "@/types/provider"; +import { isArray } from "lodash"; import Web3 from "web3"; -import { fromWei } from "web3-utils"; import { ERC20TokenInfo } from "../types"; import erc20 from "./abi/erc20"; class API implements ProviderAPIInterface { node: string; web3: Web3; + constructor(node: string) { this.node = node; this.web3 = new Web3(node); } + + public get api() { + return this; + } + // eslint-disable-next-line @typescript-eslint/no-empty-function async init(): Promise {} getBalance(address: string): Promise { return this.web3.eth.getBalance(address); } - getBaseBalance(address: string): Promise { - return this.getBalance(address).then((bal) => fromWei(bal)); - } getTokenInfo = async (contractAddress: string): Promise => { const contract = new this.web3.eth.Contract(erc20 as any, contractAddress); try { - const name = await contract.methods.name().call(); - const symbol = await contract.methods.symbol().call(); - const decimals = await contract.methods.decimals().call(); + const results = await Promise.all([ + contract.methods.name().call(), + contract.methods.symbol().call(), + contract.methods.decimals().call(), + ]); + const name = results[0]; + const symbol = results[1]; + const decimals = results[2]; + if (isArray(name) || isArray(symbol) || isArray(decimals)) throw ""; return { name, symbol, diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts new file mode 100644 index 000000000..21041dc5a --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts @@ -0,0 +1,10 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { SupportedNetworkNames } from "./types/tokenbalance-mew"; +const tokenList: Record = { + [NetworkNames.Binance]: + "https://tokens.coingecko.com/binance-smart-chain/all.json", + [NetworkNames.Ethereum]: "https://tokens.coingecko.com/ethereum/all.json", + [NetworkNames.Matic]: "https://tokens.coingecko.com/polygon-pos/all.json", +}; + +export default tokenList; diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/tokenbalance-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/tokenbalance-mew.ts index 2588d00fa..e0dca88c4 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/tokenbalance-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/tokenbalance-mew.ts @@ -1,7 +1,8 @@ -import { AssetsType, NodeType } from "@/types/provider"; +import { AssetsType } from "@/types/provider"; import { CGToken, SupportedNetwork, + SupportedNetworkNames, TokenBalance, } from "./types/tokenbalance-mew"; import MarketData from "@/libs/market-data"; @@ -15,33 +16,39 @@ import { } from "@/libs/utils/number-formatter"; import API from "@/providers/ethereum/libs/api"; import Sparkline from "@/libs/sparkline"; -import { EthereumNodeType } from "@/providers/ethereum/types"; +import { BaseNetwork } from "@/types/base-network"; +import { EvmNetwork } from "../../types/evm-network"; +import TokenLists from "./token-lists"; +import networks from "../../networks"; +import { NetworkNames } from "@enkryptcom/types"; +import { NATIVE_TOKEN_ADDRESS } from "../common"; const API_ENPOINT = "https://tokenbalance.mewapi.io/"; -const NATIVE_CONTRACT = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; const TOKEN_FETCH_TTL = 1000 * 60 * 60; -export default (network: NodeType, address: string): Promise => { - const supportedNetworks: Record = { - BNB: { +export default ( + network: BaseNetwork, + address: string +): Promise => { + const supportedNetworks: Record = { + [NetworkNames.Binance]: { tbName: "bsc", - tokenurl: "https://tokens.coingecko.com/binance-smart-chain/all.json", - cgPlatform: "binance-smart-chain", + tokenurl: TokenLists[NetworkNames.Binance], + cgPlatform: networks.bsc.coingeckoID as string, }, - ETH: { + [NetworkNames.Ethereum]: { tbName: "eth", - tokenurl: "https://tokens.coingecko.com/ethereum/all.json", - cgPlatform: "ethereum", + tokenurl: TokenLists[NetworkNames.Ethereum], + cgPlatform: networks.ethereum.coingeckoID as string, }, - MATIC: { + [NetworkNames.Matic]: { tbName: "matic", - tokenurl: "https://tokens.coingecko.com/polygon-pos/all.json", - cgPlatform: "polygon-pos", + tokenurl: TokenLists[NetworkNames.Matic], + cgPlatform: networks.matic.coingeckoID as string, }, }; if (!Object.keys(supportedNetworks).includes(network.name)) throw new Error("TOKENBALANCE-MEW: network not supported"); - const query = `${API_ENPOINT}${ - supportedNetworks[network.name].tbName - }?address=${address}`; + const networkName = network.name as SupportedNetworkNames; + const query = `${API_ENPOINT}${supportedNetworks[networkName].tbName}?address=${address}`; return fetch(query) .then((res) => res.json()) .then(async (json) => { @@ -58,16 +65,16 @@ export default (network: NodeType, address: string): Promise => { ); const marketInfo = await marketData.getMarketInfoByContracts( Object.keys(balances).filter( - (contract) => contract !== NATIVE_CONTRACT + (contract) => contract !== NATIVE_TOKEN_ADDRESS ), - supportedNetworks[network.name].cgPlatform + supportedNetworks[networkName].cgPlatform ); - marketInfo[NATIVE_CONTRACT] = nativeMarket[0]; + marketInfo[NATIVE_TOKEN_ADDRESS] = nativeMarket[0]; const assets: AssetsType[] = []; const tokenInfo: Record = await cacheFetch( { - url: supportedNetworks[network.name].tokenurl, + url: supportedNetworks[networkName].tokenurl, }, TOKEN_FETCH_TTL ).then((json) => { @@ -79,11 +86,11 @@ export default (network: NodeType, address: string): Promise => { return tObject; }); - tokenInfo[NATIVE_CONTRACT] = { - chainId: (network as EthereumNodeType).chainID, + tokenInfo[NATIVE_TOKEN_ADDRESS] = { + chainId: (network as EvmNetwork).chainID, name: network.name_long, decimals: 18, - address: NATIVE_CONTRACT, + address: NATIVE_TOKEN_ADDRESS, logoURI: network.icon, symbol: network.currencyName, }; @@ -116,7 +123,7 @@ export default (network: NodeType, address: string): Promise => { priceChangePercentage: market.price_change_percentage_7d_in_currency || 0, }; - if (address !== NATIVE_CONTRACT) assets.push(asset); + if (address !== NATIVE_TOKEN_ADDRESS) assets.push(asset); else nativeAsset = asset; } else { unknownTokens.push(address); diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts index 3fefff4ef..89b766bdb 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts @@ -1,3 +1,5 @@ +import { NetworkNames } from "@enkryptcom/types"; + export interface TokenBalance { contract: string; balance: string; @@ -15,3 +17,8 @@ export interface CGToken { decimals: number; logoURI: string; } + +export type SupportedNetworkNames = + | NetworkNames.Binance + | NetworkNames.Ethereum + | NetworkNames.Matic; diff --git a/packages/extension/src/providers/ethereum/libs/common.ts b/packages/extension/src/providers/ethereum/libs/common.ts new file mode 100644 index 000000000..0b5888004 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/common.ts @@ -0,0 +1,2 @@ +const NATIVE_TOKEN_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; +export { NATIVE_TOKEN_ADDRESS }; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/data-decoder.ts b/packages/extension/src/providers/ethereum/libs/transaction/data-decoder.ts new file mode 100644 index 000000000..596ba63ba --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/transaction/data-decoder.ts @@ -0,0 +1,65 @@ +import { bufferToHex, hexToBuffer } from "@enkryptcom/utils"; +import { isHexStrict, toHex } from "web3-utils"; +import { rawDecode } from "ethereumjs-abi"; +import { DataDecodeResponse } from "./types"; +import funcSigs from "./lists/4bytes"; +import tokenSigs from "./lists/tokenSigs"; +const getParams = (functionName: string): string[] => { + const regExp = /\(([^)]+)\)/; + const params = regExp.exec(functionName); + if (!params || params?.length < 1) return []; + return params[1].split(","); +}; +class DataDecode { + data: Buffer; + readonly functionSig: string; + readonly valueData: string; + readonly isTokenAction: boolean; + readonly value: string; + readonly to?: string; + constructor({ + data = "0x", + value = "0x0", + to, + }: { + data: string; + value: string; + to?: string; + }) { + if (!isHexStrict(data)) throw new Error("data-decoder: not a valid hex"); + this.data = hexToBuffer(data); + this.functionSig = bufferToHex(this.data.slice(0, 4)); + this.valueData = bufferToHex(this.data.slice(4)); + this.isTokenAction = Object.values(tokenSigs).includes(this.functionSig); + this.value = value; + this.to = to; + } + decode(): DataDecodeResponse { + const sig = funcSigs[this.functionSig]; + if (!sig) + return { + decoded: false, + values: [bufferToHex(this.data)], + isToken: this.isTokenAction, + }; + try { + const params = getParams(sig[0]); + const decoded = rawDecode(params, hexToBuffer(this.valueData)); + return { + decoded: true, + values: decoded.map((a) => toHex(a)), + function: sig[0], + isToken: this.isTokenAction, + }; + } catch (e) { + return { + decoded: false, + values: [bufferToHex(this.data)], + function: sig[0], + isToken: this.isTokenAction, + }; + } + } +} + +export default DataDecode; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/decoder.ts b/packages/extension/src/providers/ethereum/libs/transaction/decoder.ts new file mode 100644 index 000000000..9360124d7 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/transaction/decoder.ts @@ -0,0 +1,87 @@ +import { EvmNetwork } from "../../types/evm-network"; +import { DecodedTx, EthereumTransaction } from "./types"; +import TokenLists from "../assets-handlers/token-lists"; +import cacheFetch from "@/libs/cache-fetch"; +import { + CGToken, + SupportedNetworkNames, +} from "../assets-handlers/types/tokenbalance-mew"; +import DataDecode from "./data-decoder"; +import { bufferToHex } from "@enkryptcom/utils"; +import type EvmApi from "../api"; +import MarketData from "@/libs/market-data"; +const decodeTx = async ( + tx: EthereumTransaction, + network: EvmNetwork +): Promise => { + const isContractCreation = tx.to ? false : true; + let tokenName: string = network.currencyName; + let tokenValue: string = tx.value && tx.value != "0x" ? tx.value : "0x0"; + let tokenDecimals: number = network.decimals; + let tokenImage: string = network.icon; + let CGToken: string | undefined = network.coingeckoID; + let currentPriceUSD = 0; + const marketData = new MarketData(); + const dataDecoder = new DataDecode({ + data: tx.data as string, + to: tx.to, + value: tx.value as string, + }); + const setInfoFromNetwork = (): Promise => { + return (network.api() as Promise).then((api) => { + return api.getTokenInfo(tx.to as string).then((tokenInfo) => { + tokenName = tokenInfo.name; + tokenDecimals = tokenInfo.decimals; + tokenValue = dataDecoder.decode().values[1]; + CGToken = undefined; + currentPriceUSD = 0; + }); + }); + }; + if (tokenValue === "0x0" && dataDecoder.isTokenAction) { + if (TokenLists[network.name as SupportedNetworkNames]) { + await cacheFetch({ + url: TokenLists[network.name as SupportedNetworkNames], + }).then((json) => { + const tokens: CGToken[] = json.tokens; + const curToken = tokens.find((t) => t.address === tx.to?.toLowerCase()); + if (curToken) { + tokenName = curToken.name; + tokenImage = curToken.logoURI; + tokenDecimals = curToken.decimals; + tokenValue = dataDecoder.decode().values[1]; + return marketData + .getMarketInfoByContracts([tx.to!], network.coingeckoID!) + .then((marketInfo) => { + if (marketInfo[tx.to!]) { + currentPriceUSD = marketInfo[tx.to!]!.current_price; + CGToken = marketInfo[tx.to!]!.id; + } + }); + } else { + return setInfoFromNetwork(); + } + }); + } else { + await setInfoFromNetwork(); + } + } + if (CGToken === network.coingeckoID && network.coingeckoID) { + await marketData.getMarketData([CGToken!]).then((marketInfo) => { + currentPriceUSD = marketInfo[0]!.current_price; + }); + } + return { + isContractCreation, + dataHex: bufferToHex(dataDecoder.data), + toAddress: tx.to, + decodedHex: dataDecoder.decode().values, + tokenDecimals, + tokenImage, + tokenName, + tokenValue, + currentPriceUSD, + }; +}; + +export { decodeTx }; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/gas-utils.ts b/packages/extension/src/providers/ethereum/libs/transaction/gas-utils.ts index 1e4916804..8c1d8bbba 100644 --- a/packages/extension/src/providers/ethereum/libs/transaction/gas-utils.ts +++ b/packages/extension/src/providers/ethereum/libs/transaction/gas-utils.ts @@ -7,6 +7,8 @@ const MED_CONST = 21428571428.571; const MED_MULTIPLIER = 1.0714285714286; const FAST_CONST = 42857142857.145; const FAST_MULTIPLIER = 1.1428571428571; +const FASTEST_CONST = 64285714285.7; +const FASTEST_MULTIPLIER = 1.21828571429; const LIMITER = 25000000000; const getEconomy = (gasPrice: string): BN => { @@ -30,6 +32,15 @@ const getFast = (gasPrice: string): BN => { } return toBN(new BigNumber(gasPrice).times(1.5).toFixed(0)); }; +const getFastest = (gasPrice: string): BN => { + const gpBN = toBN(gasPrice); + if (gpBN.gt(toBN(LIMITER))) { + let initialValue = new BigNumber(gasPrice).times(FASTEST_MULTIPLIER); + initialValue = initialValue.plus(FASTEST_CONST); + return toBN(new BigNumber(initialValue).toFixed(0)); + } + return toBN(new BigNumber(gasPrice).times(1.75).toFixed(0)); +}; const getGasBasedOnType = ( gasPrice: string, @@ -42,6 +53,8 @@ const getGasBasedOnType = ( return getRegular(gasPrice); case GasPriceTypes.FAST: return getFast(gasPrice); + case GasPriceTypes.FASTEST: + return getFastest(gasPrice); default: return getEconomy(gasPrice); } @@ -69,6 +82,9 @@ const getPriorityFeeBasedOnType = ( case GasPriceTypes.FAST: returnVal = mediumTip.muln(1.25); break; + case GasPriceTypes.FASTEST: + returnVal = mediumTip.muln(1.5); + break; default: returnVal = minFee; } @@ -87,9 +103,37 @@ const getBaseFeeBasedOnType = ( return baseFeeBN.muln(1.5); case GasPriceTypes.FAST: return baseFeeBN.muln(1.75); + case GasPriceTypes.FASTEST: + return baseFeeBN.muln(2); default: return baseFeeBN; } }; - -export { getBaseFeeBasedOnType, getPriorityFeeBasedOnType, getGasBasedOnType }; +const FeeDescriptions = { + [GasPriceTypes.ECONOMY]: { + title: "Economy", + description: "Will likely go trough unless activity increases", + eta: "15 mins", + }, + [GasPriceTypes.REGULAR]: { + title: "Recommended", + description: "Will reliably go through in most scenatios", + eta: "5 mins", + }, + [GasPriceTypes.FAST]: { + title: "Higher priority", + description: "Will go through even if there is a sudden activity increase", + eta: "2 mins", + }, + [GasPriceTypes.FASTEST]: { + title: "Highest priority", + description: "Will go through, fast, in 99.99% of the cases", + eta: "1 min", + }, +}; +export { + getBaseFeeBasedOnType, + getPriorityFeeBasedOnType, + getGasBasedOnType, + FeeDescriptions, +}; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/index.ts b/packages/extension/src/providers/ethereum/libs/transaction/index.ts index cb420ef7a..227489d71 100644 --- a/packages/extension/src/providers/ethereum/libs/transaction/index.ts +++ b/packages/extension/src/providers/ethereum/libs/transaction/index.ts @@ -3,10 +3,16 @@ import { EthereumTransaction, FinalizedFeeMarketEthereumTransaction, FinalizedLegacyEthereumTransaction, + GasCosts, GasPriceTypes, + TransactionOptions, } from "./types"; import { numberToHex, toBN } from "web3-utils"; -import { getBaseFeeBasedOnType, getPriorityFeeBasedOnType } from "./gas-utils"; +import { + getBaseFeeBasedOnType, + getGasBasedOnType, + getPriorityFeeBasedOnType, +} from "./gas-utils"; import Common, { Hardfork } from "@ethereumjs/common"; import { FeeMarketEIP1559Transaction, @@ -27,9 +33,16 @@ class Transaction { data: this.tx.data || "0x", }); } - async finalizeTransaction(): Promise< - FinalizedFeeMarketEthereumTransaction | FinalizedLegacyEthereumTransaction - > { + async finalizeTransaction(options: TransactionOptions): Promise<{ + transaction: + | FinalizedFeeMarketEthereumTransaction + | FinalizedLegacyEthereumTransaction; + gasPrice?: string; + baseFeePerGas?: string; + maxPriorityFeePerGas?: string; + maxFeePerGas?: string; + gasLimit: string; + }> { const { isFeeMarketNetwork, baseFeePerGas } = await this.web3.eth .getBlockNumber() .then((blockNum) => { @@ -54,20 +67,26 @@ class Transaction { gasLimit: this.tx.gasLimit || (numberToHex(await this.estimateGas()) as `0x${string}`), - gasPrice: numberToHex(gasPrice) as `0x${string}`, + gasPrice: numberToHex( + getGasBasedOnType(gasPrice, options.gasPriceType) + ) as `0x${string}`, nonce: this.tx.nonce || (numberToHex(nonce) as `0x${string}`), value: this.tx.value || "0x0", }; - return legacyTx; + return { + transaction: legacyTx, + gasPrice: gasPrice, + gasLimit: legacyTx.gasLimit, + }; } else { let maxFeePerGas = getBaseFeeBasedOnType( baseFeePerGas?.toString() as string, - GasPriceTypes.REGULAR + options.gasPriceType ); const maxPriorityFeePerGas = getPriorityFeeBasedOnType( baseFeePerGas?.toString() as string, gasPrice.toString(), - GasPriceTypes.REGULAR + options.gasPriceType ); if (maxPriorityFeePerGas.gt(maxFeePerGas)) { maxFeePerGas = maxPriorityFeePerGas; @@ -89,41 +108,92 @@ class Transaction { type: "0x02", accessList: this.tx.accessList || [], }; - return feeMarketTx; + return { + transaction: feeMarketTx, + gasLimit: feeMarketTx.gasLimit, + baseFeePerGas: numberToHex(baseFeePerGas!), + maxFeePerGas: numberToHex(maxFeePerGas), + maxPriorityFeePerGas: numberToHex(maxPriorityFeePerGas), + }; } } - async getFinalizedTransaction(): Promise< - LegacyTransaction | FeeMarketEIP1559Transaction - > { - const finalizedTx = await this.finalizeTransaction(); + async getFinalizedTransaction( + options: TransactionOptions + ): Promise { + const { transaction } = await this.finalizeTransaction(options); - if (!finalizedTx.maxFeePerGas) { + if (!transaction.maxFeePerGas) { const common = Common.custom({ - chainId: toBN(finalizedTx.chainId), + chainId: toBN(transaction.chainId), }); return LegacyTransaction.fromTxData( - finalizedTx as FinalizedLegacyEthereumTransaction, + transaction as FinalizedLegacyEthereumTransaction, { common, } ); } else { const common = Common.custom({ - chainId: toBN(finalizedTx.chainId), + chainId: toBN(transaction.chainId), defaultHardfork: Hardfork.London, }); return FeeMarketEIP1559Transaction.fromTxData( - finalizedTx as FinalizedFeeMarketEthereumTransaction, + transaction as FinalizedFeeMarketEthereumTransaction, { common, } ); } } - async getMessageToSign(): Promise { - const tx = await this.getFinalizedTransaction(); + async getMessageToSign(options: TransactionOptions): Promise { + const tx = await this.getFinalizedTransaction(options); return tx.getMessageToSign(true); } + async getGasCosts(): Promise { + const { gasLimit, gasPrice, baseFeePerGas } = + await this.finalizeTransaction({ + gasPriceType: GasPriceTypes.ECONOMY, + }); + if (gasPrice) { + return { + [GasPriceTypes.ECONOMY]: numberToHex( + getGasBasedOnType(gasPrice, GasPriceTypes.ECONOMY).mul(toBN(gasLimit)) + ), + [GasPriceTypes.REGULAR]: numberToHex( + getGasBasedOnType(gasPrice, GasPriceTypes.REGULAR).mul(toBN(gasLimit)) + ), + [GasPriceTypes.FAST]: numberToHex( + getGasBasedOnType(gasPrice, GasPriceTypes.FAST).mul(toBN(gasLimit)) + ), + [GasPriceTypes.FASTEST]: numberToHex( + getGasBasedOnType(gasPrice, GasPriceTypes.FASTEST).mul(toBN(gasLimit)) + ), + }; + } else { + return { + [GasPriceTypes.ECONOMY]: numberToHex( + getBaseFeeBasedOnType(baseFeePerGas!, GasPriceTypes.ECONOMY).mul( + toBN(gasLimit) + ) + ), + [GasPriceTypes.REGULAR]: numberToHex( + getBaseFeeBasedOnType(baseFeePerGas!, GasPriceTypes.REGULAR).mul( + toBN(gasLimit) + ) + ), + [GasPriceTypes.FAST]: numberToHex( + getBaseFeeBasedOnType(baseFeePerGas!, GasPriceTypes.FAST).mul( + toBN(gasLimit) + ) + ), + [GasPriceTypes.FASTEST]: numberToHex( + getBaseFeeBasedOnType(baseFeePerGas!, GasPriceTypes.FASTEST).mul( + toBN(gasLimit) + ) + ), + }; + } + } } export default Transaction; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/lists/4bytes.ts b/packages/extension/src/providers/ethereum/libs/transaction/lists/4bytes.ts new file mode 100644 index 000000000..3629c518b --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/transaction/lists/4bytes.ts @@ -0,0 +1,7 @@ +const sigs: Record = { + "0xa9059cbb": ["transfer(address,uint256)"], + "0x23b872dd": ["transferFrom(address,address,uint256)"], + "0x095ea7b3": ["approve(address,uint256)"], +}; + +export default sigs; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/lists/tokenSigs.ts b/packages/extension/src/providers/ethereum/libs/transaction/lists/tokenSigs.ts new file mode 100644 index 000000000..4bb9f5067 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/transaction/lists/tokenSigs.ts @@ -0,0 +1,5 @@ +export default { + transfer: "0xa9059cbb", + transferFrom: "0x23b872dd", + approve: "0x095ea7b3", +}; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/types.ts b/packages/extension/src/providers/ethereum/libs/transaction/types.ts index 12ae13daa..f4f30618c 100644 --- a/packages/extension/src/providers/ethereum/libs/transaction/types.ts +++ b/packages/extension/src/providers/ethereum/libs/transaction/types.ts @@ -41,9 +41,37 @@ export interface FinalizedFeeMarketEthereumTransaction value: `0x${string}`; type: "0x02"; } - +export interface DecodedTx { + toAddress?: string; + isContractCreation: boolean; + currentPriceUSD: number; + tokenValue: string; + tokenDecimals: number; + tokenName: string; + tokenImage: string; + dataHex: string; + decodedHex?: string[]; +} export enum GasPriceTypes { - ECONOMY, - REGULAR, - FAST, + ECONOMY = "ECONOMY", + REGULAR = "REGULAR", + FAST = "FAST", + FASTEST = "FASTEST", +} + +export interface TransactionOptions { + gasPriceType: GasPriceTypes; +} +export interface DataDecodeResponse { + decoded: boolean; + values: string[]; + function?: string; + isToken: boolean; +} + +export interface GasCosts { + [GasPriceTypes.ECONOMY]: string; + [GasPriceTypes.REGULAR]: string; + [GasPriceTypes.FAST]: string; + [GasPriceTypes.FASTEST]: string; } diff --git a/packages/extension/src/providers/ethereum/methods/index.ts b/packages/extension/src/providers/ethereum/methods/index.ts index aa688e56a..798076ac0 100644 --- a/packages/extension/src/providers/ethereum/methods/index.ts +++ b/packages/extension/src/providers/ethereum/methods/index.ts @@ -7,6 +7,7 @@ import ethSignTransaction from "./eth_signTransaction"; import ethSignTypedData from "./eth_signTypedData"; import ethgetEncryptionKey from "./eth_getEncryptionPublicKey"; import ethDecrypt from "./eth_decrypt"; +import personalEcRecover from "./personal_ecRecover"; export default [ ethSendTransaction, ethSign, @@ -17,4 +18,5 @@ export default [ ethSignTypedData, ethgetEncryptionKey, ethDecrypt, + personalEcRecover, ]; diff --git a/packages/extension/src/providers/ethereum/methods/personal_ecRecover.ts b/packages/extension/src/providers/ethereum/methods/personal_ecRecover.ts new file mode 100644 index 000000000..a6efce8a4 --- /dev/null +++ b/packages/extension/src/providers/ethereum/methods/personal_ecRecover.ts @@ -0,0 +1,32 @@ +import { getCustomError } from "@/libs/error"; +import { MiddlewareFunction } from "@enkryptcom/types"; +import { hexToBuffer } from "@enkryptcom/utils"; +import { + ecrecover, + fromRpcSig, + hashPersonalMessage, + publicToAddress, +} from "ethereumjs-util"; +import EthereumProvider from ".."; +const method: MiddlewareFunction = function ( + this: EthereumProvider, + payload, + res, + next +): void { + if (payload.method !== "personal_ecRecover") return next(); + else { + if (!payload.params || payload.params.length < 2) { + return res(getCustomError("eth_sign: invalid params")); + } + try { + const hashedMessage = hashPersonalMessage(hexToBuffer(payload.params[0])); + const { v, r, s } = fromRpcSig(payload.params[1]); + const recoveredPubKey = ecrecover(hashedMessage, v, r, s); + return res(null, "0x" + publicToAddress(recoveredPubKey).toString("hex")); + } catch (e) { + return res(getCustomError((e as Error).message)); + } + } +}; +export default method; diff --git a/packages/extension/src/providers/ethereum/networks/bsc.ts b/packages/extension/src/providers/ethereum/networks/bsc.ts index 976a792d0..135d0ff6f 100644 --- a/packages/extension/src/providers/ethereum/networks/bsc.ts +++ b/packages/extension/src/providers/ethereum/networks/bsc.ts @@ -1,13 +1,10 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; -const bscNode: EthereumNodeType = { - name: "BNB", - name_long: "BNB Chain", + +const bscOptions: EvmNetworkOptions = { + name: NetworkNames.Binance, + name_long: "BNB Smart Chain", homePage: "https://www.binance.org/en/smartChain", blockExplorerTX: "https://bscscan.com/tx/[[txHash]]", blockExplorerAddr: "https://bscscan.com/address/[[address]]", @@ -16,18 +13,12 @@ const bscNode: EthereumNodeType = { currencyName: "BNB", node: "wss://nodes.mewapi.io/ws/bsc", icon: require("./icons/bsc.svg"), - signer: [SignerType.secp256k1], gradient: "#E6007A", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, coingeckoID: "binancecoin", - identicon: createIcon, - assetsHandler: tokenbalanceMew, basePath: "m/44'/714'", + assetsHandler: tokenbalanceMew, }; -bscNode.api = async () => { - const api = new API(bscNode.node); - await api.init(); - return api; -}; -export default bscNode; + +const bsc = new EvmNetwork(bscOptions); + +export default bsc; diff --git a/packages/extension/src/providers/ethereum/networks/etc.ts b/packages/extension/src/providers/ethereum/networks/etc.ts index 3a55fbace..72f97e5d8 100644 --- a/packages/extension/src/providers/ethereum/networks/etc.ts +++ b/packages/extension/src/providers/ethereum/networks/etc.ts @@ -1,11 +1,8 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; -const etcNode: EthereumNodeType = { - name: "ETC", +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const etcOptions: EvmNetworkOptions = { + name: NetworkNames.EthereumClassic, name_long: "Ethereum Classic", homePage: "https://ethereumclassic.org/", blockExplorerTX: "https://blockscout.com/etc/mainnet/tx/[[txHash]]", @@ -15,17 +12,11 @@ const etcNode: EthereumNodeType = { currencyName: "ETC", node: "wss://www.ethercluster.com/ws-etc", icon: require("./icons/etc.svg"), - signer: [SignerType.secp256k1], gradient: "#53CBC9", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, - coingeckoID: "ethereum-classic", - identicon: createIcon, basePath: "m/44'/61'/0'/0", + coingeckoID: "ethereum-classic", }; -etcNode.api = async () => { - const api = new API(etcNode.node); - await api.init(); - return api; -}; -export default etcNode; + +const etc = new EvmNetwork(etcOptions); + +export default etc; diff --git a/packages/extension/src/providers/ethereum/networks/eth.ts b/packages/extension/src/providers/ethereum/networks/eth.ts index b1c11d52c..d8cdc11c0 100644 --- a/packages/extension/src/providers/ethereum/networks/eth.ts +++ b/packages/extension/src/providers/ethereum/networks/eth.ts @@ -1,13 +1,10 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; -import rarible from "@/libs/nft-handlers/rarible"; -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import createIcon from "../libs/blockies"; -import { EthereumNodeType } from "../types"; -const ethNode: EthereumNodeType = { - name: "ETH", +import mewNFTHandler from "@/libs/nft-handlers/mew"; + +const ethOptions: EvmNetworkOptions = { + name: NetworkNames.Ethereum, name_long: "Ethereum", homePage: "https://ethereum.org", blockExplorerTX: "https://etherscan.io/tx/[[txHash]]", @@ -17,19 +14,12 @@ const ethNode: EthereumNodeType = { currencyName: "ETH", node: "wss://nodes.mewapi.io/ws/eth", icon: require("./icons/eth.svg"), - signer: [SignerType.secp256k1], gradient: "#8247E5", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, coingeckoID: "ethereum", - NFTHandler: rarible, - identicon: createIcon, + NFTHandler: mewNFTHandler, assetsHandler: tokenbalanceMew, - basePath: "m/44'/60'/0'/0", }; -ethNode.api = async () => { - const api = new API(ethNode.node); - await api.init(); - return api; -}; -export default ethNode; + +const eth = new EvmNetwork(ethOptions); + +export default eth; diff --git a/packages/extension/src/providers/ethereum/networks/goerli.ts b/packages/extension/src/providers/ethereum/networks/goerli.ts index c6e897b5f..f99c2ecff 100644 --- a/packages/extension/src/providers/ethereum/networks/goerli.ts +++ b/packages/extension/src/providers/ethereum/networks/goerli.ts @@ -1,11 +1,8 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; -const goerliNode: EthereumNodeType = { - name: "GOERLI", +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const goerliOptions: EvmNetworkOptions = { + name: NetworkNames.Goerli, name_long: "Goerli", homePage: "https://github.com/goerli/testnet", blockExplorerTX: "https://goerli.etherscan.io/tx/[[txHash]]", @@ -15,17 +12,10 @@ const goerliNode: EthereumNodeType = { currencyName: "GöETH", node: "wss://nodes.mewapi.io/ws/goerli", icon: require("./icons/eth.svg"), - signer: [SignerType.secp256k1], gradient: "#C4C4C4", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, - identicon: createIcon, coingeckoID: "ethereum", - basePath: "m/44'/60'/0'/0", }; -goerliNode.api = async () => { - const api = new API(goerliNode.node); - await api.init(); - return api; -}; -export default goerliNode; + +const goerli = new EvmNetwork(goerliOptions); + +export default goerli; diff --git a/packages/extension/src/providers/ethereum/networks/icons/karura-evm.svg b/packages/extension/src/providers/ethereum/networks/icons/karura-evm.svg new file mode 100644 index 000000000..a077e56a6 --- /dev/null +++ b/packages/extension/src/providers/ethereum/networks/icons/karura-evm.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/extension/src/providers/ethereum/networks/icons/moonbeam.png b/packages/extension/src/providers/ethereum/networks/icons/moonbeam.png deleted file mode 100644 index 6c7fc7f98..000000000 Binary files a/packages/extension/src/providers/ethereum/networks/icons/moonbeam.png and /dev/null differ diff --git a/packages/extension/src/providers/ethereum/networks/icons/moonbeam.svg b/packages/extension/src/providers/ethereum/networks/icons/moonbeam.svg new file mode 100644 index 000000000..c24d8a3e0 --- /dev/null +++ b/packages/extension/src/providers/ethereum/networks/icons/moonbeam.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/extension/src/providers/ethereum/networks/icons/moonriver.svg b/packages/extension/src/providers/ethereum/networks/icons/moonriver.svg new file mode 100644 index 000000000..6419d25e5 --- /dev/null +++ b/packages/extension/src/providers/ethereum/networks/icons/moonriver.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/extension/src/providers/ethereum/networks/index.ts b/packages/extension/src/providers/ethereum/networks/index.ts index c01425d78..05c1724d1 100644 --- a/packages/extension/src/providers/ethereum/networks/index.ts +++ b/packages/extension/src/providers/ethereum/networks/index.ts @@ -7,6 +7,8 @@ import etcNode from "./etc"; import maticNode from "./matic"; import bscNode from "./bsc"; import moonbeamNode from "./moonbeam"; +import moonriverNode from "./moonriver"; +import karuraEvmNode from "./karura"; export default { goerli: goerliNode, @@ -18,4 +20,6 @@ export default { matic: maticNode, bsc: bscNode, moonbeam: moonbeamNode, + moonriver: moonriverNode, + karuraEvm: karuraEvmNode, }; diff --git a/packages/extension/src/providers/ethereum/networks/karura.ts b/packages/extension/src/providers/ethereum/networks/karura.ts new file mode 100644 index 000000000..266439cd7 --- /dev/null +++ b/packages/extension/src/providers/ethereum/networks/karura.ts @@ -0,0 +1,21 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const karuraOptions: EvmNetworkOptions = { + name: NetworkNames.KaruraEVM, + name_long: "Karura EVM", + homePage: "https://karura.network", + blockExplorerTX: "https://blockscout.karura.network/tx/[[txHash]]", + blockExplorerAddr: "https://blockscout.karura.network/address/[[address]]", + chainID: 686, + isTestNetwork: false, + currencyName: "KAR", + node: "https://eth-rpc-karura.aca-api.network/eth/http", + icon: require("./icons/karura-evm.svg"), + gradient: "#FF4C3B", + coingeckoID: "karura", +}; + +const karura = new EvmNetwork(karuraOptions); + +export default karura; diff --git a/packages/extension/src/providers/ethereum/networks/kov.ts b/packages/extension/src/providers/ethereum/networks/kov.ts index 0f5325029..8b30bfe34 100644 --- a/packages/extension/src/providers/ethereum/networks/kov.ts +++ b/packages/extension/src/providers/ethereum/networks/kov.ts @@ -1,11 +1,8 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; -const kovanNode: EthereumNodeType = { - name: "KOV", +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const kovOptions: EvmNetworkOptions = { + name: NetworkNames.Kovan, name_long: "Kovan", homePage: "https://github.com/kovan-testnet", blockExplorerTX: "https://kovan.etherscan.io/tx/[[txHash]]", @@ -15,16 +12,9 @@ const kovanNode: EthereumNodeType = { currencyName: "KOV", node: "wss://nodes.mewapi.io/ws/kovan", icon: require("./icons/eth.svg"), - signer: [SignerType.secp256k1], gradient: "#E6007A", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, - identicon: createIcon, - basePath: "m/44'/60'/0'/0", }; -kovanNode.api = async () => { - const api = new API(kovanNode.node); - await api.init(); - return api; -}; -export default kovanNode; + +const kov = new EvmNetwork(kovOptions); + +export default kov; diff --git a/packages/extension/src/providers/ethereum/networks/matic.ts b/packages/extension/src/providers/ethereum/networks/matic.ts index 98ea2fe94..40a6e9644 100644 --- a/packages/extension/src/providers/ethereum/networks/matic.ts +++ b/packages/extension/src/providers/ethereum/networks/matic.ts @@ -1,13 +1,10 @@ -import rarible from "@/libs/nft-handlers/rarible"; -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; -const maticNode: EthereumNodeType = { - name: "MATIC", +import RaribleNFTHandler from "@/libs/nft-handlers/rarible"; + +const maticOptions: EvmNetworkOptions = { + name: NetworkNames.Matic, name_long: "Polygon (Matic)", homePage: "https://polygonscan.com/", blockExplorerTX: "https://polygonscan.com/tx/[[txHash]]", @@ -17,19 +14,12 @@ const maticNode: EthereumNodeType = { currencyName: "MATIC", node: "wss://nodes.mewapi.io/ws/matic", icon: require("./icons/matic.svg"), - signer: [SignerType.secp256k1], gradient: "#53CBC9", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, coingeckoID: "matic-network", - NFTHandler: rarible, - identicon: createIcon, + NFTHandler: RaribleNFTHandler, assetsHandler: tokenbalanceMew, - basePath: "m/44'/60'/0'/0", }; -maticNode.api = async () => { - const api = new API(maticNode.node); - await api.init(); - return api; -}; -export default maticNode; + +const matic = new EvmNetwork(maticOptions); + +export default matic; diff --git a/packages/extension/src/providers/ethereum/networks/moonbeam.ts b/packages/extension/src/providers/ethereum/networks/moonbeam.ts index a88fe647e..35f9a42f7 100644 --- a/packages/extension/src/providers/ethereum/networks/moonbeam.ts +++ b/packages/extension/src/providers/ethereum/networks/moonbeam.ts @@ -1,11 +1,8 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; -const moonbeamNode: EthereumNodeType = { - name: "GLMR", +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const moonbeamOptions: EvmNetworkOptions = { + name: NetworkNames.Moonbeam, name_long: "Moonbeam", homePage: "https://moonbeam.network", blockExplorerTX: "https://moonscan.io/tx/[[txHash]]", @@ -14,18 +11,11 @@ const moonbeamNode: EthereumNodeType = { isTestNetwork: false, currencyName: "GLMR", node: "wss://wss.api.moonbeam.network/", - icon: require("./icons/moonbeam.png"), - signer: [SignerType.secp256k1], + icon: require("./icons/moonbeam.svg"), gradient: "#8247E5", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, coingeckoID: "moonbeam", - identicon: createIcon, - basePath: "m/44'/60'/0'/0", }; -moonbeamNode.api = async () => { - const api = new API(moonbeamNode.node); - await api.init(); - return api; -}; -export default moonbeamNode; + +const moonbeam = new EvmNetwork(moonbeamOptions); + +export default moonbeam; diff --git a/packages/extension/src/providers/ethereum/networks/moonriver.ts b/packages/extension/src/providers/ethereum/networks/moonriver.ts new file mode 100644 index 000000000..941def5b9 --- /dev/null +++ b/packages/extension/src/providers/ethereum/networks/moonriver.ts @@ -0,0 +1,21 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const moonriverOptions: EvmNetworkOptions = { + name: NetworkNames.Moonriver, + name_long: "Moonriver", + homePage: "https://moonbeam.network/networks/moonriver/", + blockExplorerTX: "https://moonriver.moonscan.io//tx/[[txHash]]", + blockExplorerAddr: "https://moonriver.moonscan.io/[[address]]", + chainID: 1285, + isTestNetwork: false, + currencyName: "MOVR", + node: "wss://wss.api.moonriver.moonbeam.network", + icon: require("./icons/moonriver.svg"), + gradient: "#f2b606", + coingeckoID: "moonriver", +}; + +const moonriver = new EvmNetwork(moonriverOptions); + +export default moonriver; diff --git a/packages/extension/src/providers/ethereum/networks/rin.ts b/packages/extension/src/providers/ethereum/networks/rin.ts index 1d76ced70..83a07f636 100644 --- a/packages/extension/src/providers/ethereum/networks/rin.ts +++ b/packages/extension/src/providers/ethereum/networks/rin.ts @@ -1,11 +1,8 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; -const rinkebyNode: EthereumNodeType = { - name: "RIN", +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const rinOptions: EvmNetworkOptions = { + name: NetworkNames.Rinkeby, name_long: "Rinkeby", homePage: "https://www.rinkeby.io/", blockExplorerTX: "https://rinkeby.etherscan.io/tx/[[txHash]]", @@ -15,16 +12,9 @@ const rinkebyNode: EthereumNodeType = { currencyName: "RIN", node: "wss://nodes.mewapi.io/ws/rinkeby", icon: require("./icons/eth.svg"), - signer: [SignerType.secp256k1], gradient: "#C4C4C4", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, - identicon: createIcon, - basePath: "m/44'/60'/0'/0", }; -rinkebyNode.api = async () => { - const api = new API(rinkebyNode.node); - await api.init(); - return api; -}; -export default rinkebyNode; + +const rin = new EvmNetwork(rinOptions); + +export default rin; diff --git a/packages/extension/src/providers/ethereum/networks/rop.ts b/packages/extension/src/providers/ethereum/networks/rop.ts index 4d33f4f7d..17480ef61 100644 --- a/packages/extension/src/providers/ethereum/networks/rop.ts +++ b/packages/extension/src/providers/ethereum/networks/rop.ts @@ -1,11 +1,8 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { toChecksumAddress } from "ethereumjs-util"; -import API from "../libs/api"; -import { EthereumNodeType } from "../types"; -import createIcon from "../libs/blockies"; -const ropstenNode: EthereumNodeType = { - name: "ROP", +import { NetworkNames } from "@enkryptcom/types"; +import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; + +const ropOptions: EvmNetworkOptions = { + name: NetworkNames.Ropsten, name_long: "Ropsten", homePage: "https://github.com/ethereum/ropsten", blockExplorerTX: "https://ropsten.etherscan.io/tx/[[txHash]]", @@ -15,16 +12,10 @@ const ropstenNode: EthereumNodeType = { currencyName: "ROP", node: "wss://nodes.mewapi.io/ws/rop", icon: require("./icons/eth.svg"), - signer: [SignerType.secp256k1], + basePath: "m/44'/1'/0'/0", gradient: "#E6007A", - displayAddress: (address: string) => toChecksumAddress(address), - provider: ProviderName.ethereum, - identicon: createIcon, - basePath: "m/44'/60'/0'/0", }; -ropstenNode.api = async () => { - const api = new API(ropstenNode.node); - await api.init(); - return api; -}; -export default ropstenNode; + +const rop = new EvmNetwork(ropOptions); + +export default rop; diff --git a/packages/extension/src/providers/ethereum/tests/ethereum.data.decode.mocha.ts b/packages/extension/src/providers/ethereum/tests/ethereum.data.decode.mocha.ts new file mode 100644 index 000000000..55eef6449 --- /dev/null +++ b/packages/extension/src/providers/ethereum/tests/ethereum.data.decode.mocha.ts @@ -0,0 +1,30 @@ +import DataDecode from "../libs/transaction/data-decoder"; +import { expect } from "chai"; +import { BN } from "ethereumjs-util"; +import { numberToHex } from "web3-utils"; +describe("Test Ethereum data decoding", () => { + it("should decode correct token transfer info", async () => { + const dataDecoder = new DataDecode({ + data: "0xa9059cbb00000000000000000000000092eefc435008af9dfc428e9f84c2a6c0fd385e8f0000000000000000000000000000000000000000000000000000000017442ab1", + value: "0x0", + }); + expect(dataDecoder.functionSig).to.eq("0xa9059cbb"); + expect(dataDecoder.decode().decoded).to.eq(true); + expect(dataDecoder.decode().isToken).to.eq(true); + expect(dataDecoder.decode().values[0]).to.eq( + "0x92eefc435008af9dfc428e9f84c2a6c0fd385e8f" + ); + expect(dataDecoder.decode().values[1]).to.eq( + numberToHex(new BN("390343345")) + ); + }); + it("should not decode unknown data", async () => { + const dataDecoder = new DataDecode({ + data: "0xa9058cbb0000000000", + value: "0x0", + }); + expect(dataDecoder.functionSig).to.eq("0xa9058cbb"); + expect(dataDecoder.decode().decoded).to.eq(false); + expect(dataDecoder.decode().isToken).to.eq(false); + }); +}); diff --git a/packages/extension/src/providers/ethereum/tests/ethereum.events.mocha.ts b/packages/extension/src/providers/ethereum/tests/ethereum.events.mocha.ts index c45dab397..33931dd8a 100644 --- a/packages/extension/src/providers/ethereum/tests/ethereum.events.mocha.ts +++ b/packages/extension/src/providers/ethereum/tests/ethereum.events.mocha.ts @@ -29,7 +29,6 @@ describe("Test injected Ethereum", () => { expect(provider.name).to.equal(ProviderName.ethereum); expect(provider.chainId).to.equal("0x1"); expect(provider.isEnkrypt).to.equal(true); - expect(provider).to.deep.equal(tempWindow.enkrypt.providers[provider.name]); }); }); diff --git a/packages/extension/src/providers/ethereum/types/evm-network.ts b/packages/extension/src/providers/ethereum/types/evm-network.ts new file mode 100644 index 000000000..f35ce2ade --- /dev/null +++ b/packages/extension/src/providers/ethereum/types/evm-network.ts @@ -0,0 +1,101 @@ +import { formatFloatingPointValue } from "@/libs/utils/number-formatter"; +import { fromBase } from "@/libs/utils/units"; +import { BaseNetwork } from "@/types/base-network"; +import { BaseToken } from "@/types/base-token"; +import { NFTCollection } from "@/types/nft"; +import { AssetsType, ProviderName } from "@/types/provider"; +import { NetworkNames, SignerType } from "@enkryptcom/types"; +import { toChecksumAddress } from "ethereumjs-util"; +import API from "../libs/api"; +import createIcon from "../libs/blockies"; +import { NATIVE_TOKEN_ADDRESS } from "../libs/common"; + +export interface EvmNetworkOptions { + name: NetworkNames; + name_long: string; + homePage: string; + blockExplorerTX: string; + blockExplorerAddr: string; + chainID: number; + isTestNetwork: boolean; + currencyName: string; + node: string; + icon: string; + gradient: string; + coingeckoID?: string; + basePath?: string; + NFTHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; + assetsHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; +} + +export class EvmNetwork extends BaseNetwork { + public chainID: number; + + private assetsHandler: + | ((network: BaseNetwork, address: string) => Promise) + | undefined; + private NFTHandler: + | ((network: BaseNetwork, address: string) => Promise) + | undefined; + constructor(options: EvmNetworkOptions) { + const api = async () => { + const api = new API(options.node); + await api.init(); + return api; + }; + + const baseOptions = { + signer: [SignerType.secp256k1], + provider: ProviderName.ethereum, + displayAddress: (address: string) => toChecksumAddress(address), + identicon: createIcon, + basePath: options.basePath ? options.basePath : "m/44'/60'/0'/0", + decimals: 18, + api, + ...options, + }; + + super(baseOptions); + + this.chainID = options.chainID; + this.assetsHandler = options.assetsHandler; + this.NFTHandler = options.NFTHandler; + } + + public getAllTokens(): BaseToken[] { + return []; + } + + public async getAllTokenInfo(address: string): Promise { + if (this.assetsHandler) { + return this.assetsHandler(this, address); + } else { + const api = await this.api(); + const balance = await (api as API).getBalance(address); + const nativeAsset: AssetsType = { + name: this.name_long, + symbol: this.name, + icon: this.icon, + balance, + balancef: formatFloatingPointValue(fromBase(balance, this.decimals)) + .value, + balanceUSD: 0, + balanceUSDf: "0", + value: "0", + valuef: "0", + decimals: this.decimals, + sparkline: "", + priceChangePercentage: 0, + contract: NATIVE_TOKEN_ADDRESS, + }; + + return [nativeAsset]; + } + } +} diff --git a/packages/extension/src/providers/ethereum/ui/common/default-vals.ts b/packages/extension/src/providers/ethereum/ui/common/default-vals.ts new file mode 100644 index 000000000..e65403cf9 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/common/default-vals.ts @@ -0,0 +1,29 @@ +import { GasPriceTypes } from "../../libs/transaction/types"; + +const defaultGasCostVals = { + [GasPriceTypes.ECONOMY]: { + nativeValue: "0", + fiatValue: "0.00", + nativeSymbol: "ETH", + fiatSymbol: "USD", + }, + [GasPriceTypes.REGULAR]: { + nativeValue: "0", + fiatValue: "0.00", + nativeSymbol: "ETH", + fiatSymbol: "USD", + }, + [GasPriceTypes.FAST]: { + nativeValue: "0", + fiatValue: "0.00", + nativeSymbol: "ETH", + fiatSymbol: "USD", + }, + [GasPriceTypes.FASTEST]: { + nativeValue: "0", + fiatValue: "0.00", + nativeSymbol: "ETH", + fiatSymbol: "USD", + }, +}; +export { defaultGasCostVals }; diff --git a/packages/extension/src/providers/ethereum/ui/eth-connect-dapp.vue b/packages/extension/src/providers/ethereum/ui/eth-connect-dapp.vue new file mode 100644 index 000000000..c830cba84 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/eth-connect-dapp.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/eth-decrypt.vue b/packages/extension/src/providers/ethereum/ui/eth-decrypt.vue index c1f98c0d7..7bde12514 100644 --- a/packages/extension/src/providers/ethereum/ui/eth-decrypt.vue +++ b/packages/extension/src/providers/ethereum/ui/eth-decrypt.vue @@ -1,95 +1,103 @@ - - diff --git a/packages/extension/src/providers/ethereum/ui/eth-sign-message.vue b/packages/extension/src/providers/ethereum/ui/eth-sign-message.vue index 480073826..e470b8130 100644 --- a/packages/extension/src/providers/ethereum/ui/eth-sign-message.vue +++ b/packages/extension/src/providers/ethereum/ui/eth-sign-message.vue @@ -1,107 +1,113 @@ + + diff --git a/packages/extension/src/providers/ethereum/ui/routes/index.ts b/packages/extension/src/providers/ethereum/ui/routes/index.ts index fbc62a29d..17336a27e 100644 --- a/packages/extension/src/providers/ethereum/ui/routes/index.ts +++ b/packages/extension/src/providers/ethereum/ui/routes/index.ts @@ -1,9 +1,10 @@ import ethAccounts from "../eth-accounts.vue"; import ethSign from "../eth-sign-message.vue"; -import ethSendTransaction from "../eth-send-transaction.vue"; +import ethSendTransaction from "../eth-verify-transaction.vue"; import ethSignTypedData from "../eth-sign-typedata.vue"; import ethEncryptionKey from "../eth-get-encryption-key.vue"; import ethDecrypt from "../eth-decrypt.vue"; +import ethConnectDApp from "../eth-connect-dapp.vue"; import { RouteRecordRaw } from "vue-router"; import RouteNames from "./names"; const routes = Object.assign({}, RouteNames); @@ -14,6 +15,7 @@ routes.ethSendTransaction.component = ethSendTransaction; routes.ethSignTypedData.component = ethSignTypedData; routes.ethGetEncryptionKey.component = ethEncryptionKey; routes.ethDecrypt.component = ethDecrypt; +routes.ethConnectDApp.component = ethConnectDApp; export default (namespace: string): RouteRecordRaw[] => { return Object.values(routes).map((route) => { route.path = `/${namespace}/${route.path}`; diff --git a/packages/extension/src/providers/ethereum/ui/routes/names.ts b/packages/extension/src/providers/ethereum/ui/routes/names.ts index 888b43d70..70193fcdb 100644 --- a/packages/extension/src/providers/ethereum/ui/routes/names.ts +++ b/packages/extension/src/providers/ethereum/ui/routes/names.ts @@ -29,4 +29,9 @@ export default { name: "ethDecrypt", component: {}, }, + ethConnectDApp: { + path: "eth-conncet-dapp", + name: "ethConnectDApp", + component: {}, + }, }; diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-address-input.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-address-input.vue new file mode 100644 index 000000000..8289ae897 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-address-input.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-address-item.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-address-item.vue new file mode 100644 index 000000000..aafb066ab --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-address-item.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-alert.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-alert.vue new file mode 100644 index 000000000..1e1980d19 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-alert.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-contacts-list.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-contacts-list.vue new file mode 100644 index 000000000..c7b8070cc --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-contacts-list.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-fee-select.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-fee-select.vue new file mode 100644 index 000000000..1b749230a --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-fee-select.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-header.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-header.vue new file mode 100644 index 000000000..92038e493 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-header.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-input-amount.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-input-amount.vue new file mode 100644 index 000000000..7f360484e --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-input-amount.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-nft-select.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-nft-select.vue new file mode 100644 index 000000000..7fbbbe7ab --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-nft-select.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue new file mode 100644 index 000000000..1a1cb52a0 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue new file mode 100644 index 000000000..cd6bbd5a8 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue @@ -0,0 +1,446 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-account.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-account.vue new file mode 100644 index 000000000..29c13a4ce --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-account.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-amount.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-amount.vue new file mode 100644 index 000000000..939799a9f --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-amount.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-fee.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-fee.vue new file mode 100644 index 000000000..5d5439c2b --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-fee.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-network.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-network.vue new file mode 100644 index 000000000..a2cd9b8c1 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-network.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-nft.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-nft.vue new file mode 100644 index 000000000..1934108a2 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/components/verify-transaction-nft.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/index.vue new file mode 100644 index 000000000..8f70c0753 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/index.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/styles/common-popup.less b/packages/extension/src/providers/ethereum/ui/styles/common-popup.less index edd33b3fa..f8d68b8e2 100644 --- a/packages/extension/src/providers/ethereum/ui/styles/common-popup.less +++ b/packages/extension/src/providers/ethereum/ui/styles/common-popup.less @@ -2,11 +2,52 @@ .common-popup { width: 100%; + height: 100%; - &__logo { - margin-bottom: 8px; + &__header { + padding: 28px 0 8px 0; } + &__content { + height: calc(~"100% - 52px"); + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + } + + &__wrap { + width: 100%; + } + + &__network { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + position: absolute; + right: 56px; + top: 28px; + + img { + width: 16px; + height: 16px; + margin-right: 8px; + } + + p { + font-style: normal; + font-weight: 500; + font-size: 12px; + line-height: 16px; + text-align: right; + letter-spacing: 0.5px; + color: @primaryLabel; + margin: 0; + } + } + h2 { font-style: normal; font-weight: 700; @@ -25,6 +66,14 @@ padding: 10px 16px; width: 100%; margin: 0 0 16px 0; + + &.no-inset { + margin: 0; + } + + &.no-padding { + padding: 0; + } } &__message { @@ -38,6 +87,7 @@ height: auto; max-height: 180px; overflow: auto; + word-break: break-all; } &__account { @@ -122,13 +172,48 @@ flex-direction: row; width: 100%; box-sizing: border-box; - + position: absolute; + left: 0; + bottom: 0; + font-size: 0; + padding: 24px; + box-sizing: border-box; + background-color: @white; &-cancel { - width: 108px; + width: 172px; } - &-send { - width: 232px; + width: 232px; } + + &.border { + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.05), + 0px 0px 1px rgba(0, 0, 0, 0.25); + } + } + + &__scroll-area { + position: relative; + margin: auto; + width: calc(~"100% + 53px"); + height: calc(~"100% - 88px"); + margin: 0; + padding: 0 53px 0 0 !important; + margin-right: -53px; + box-sizing: border-box; + + &.ps--active-y { + padding-bottom: 0 !important; + } + + & > .ps__rail-y { + right: 0 !important; + } + } +} + +.ps--active-y { + .common-popup__content { + height: auto; } } \ No newline at end of file diff --git a/packages/extension/src/providers/ethereum/ui/styles/verify-transaction.less b/packages/extension/src/providers/ethereum/ui/styles/verify-transaction.less new file mode 100644 index 000000000..7499dd625 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/styles/verify-transaction.less @@ -0,0 +1,266 @@ +.provider-verify-transaction { + &__block { + background: @lightBg; + border: 1px solid @gray01; + box-sizing: border-box; + border-radius: 12px; + padding: 10px 16px; + width: 100%; + margin: 0 0 16px 0; + } + &__amount { + display: flex; + justify-content: flex-start; + align-items: flex-start; + flex-direction: row; + + img { + box-shadow: inset 0px 0px 1px rgba(0, 0, 0, 0.16); + width: 32px; + height: 32px; + margin-right: 12px; + border-radius: 100%; + } + + &-info { + h4 { + font-style: normal; + font-weight: 700; + font-size: 24px; + line-height: 32px; + color: @primaryLabel; + margin: 0; + + span { + font-variant: small-caps; + } + } + + p { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @secondaryLabel; + margin: 0; + } + } + } + &__account { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + img { + width: 32px; + height: 32px; + margin-right: 12px; + border-radius: 100%; + } + &-info { + h4 { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0; + } + + div { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + + p { + &:first-child { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + margin: 0 8px 0 0; + + span { + font-variant: small-caps; + } + } + + &:last-child { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @tertiaryLabel; + margin: 0; + word-break: break-all; + } + } + } + } + } + &__error { + margin: 12px 0 0 0; + border-radius: 10px; + padding: 0 0 0 44px; + position: relative; + box-sizing: border-box; + svg { + position: absolute; + left: 0; + top: 0; + } + p { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @error; + margin: 0; + a { + color: @error; + &:hover { + text-decoration: none; + } + } + } + } + &__info { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + padding: 6px 0; + margin-bottom: 6px; + img { + width: 32px; + height: 32px; + margin-right: 12px; + } + &-info { + h4 { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0; + } + + p { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @tertiaryLabel; + margin: 0; + word-break: break-all; + } + } + } + &__data { + text-align: center; + padding-top: 4px; + padding-bottom: 20px; + &-link { + border-radius: 6px; + transition: background 300ms ease-in-out; + display: inline-block; + cursor: pointer; + text-decoration: none; + padding: 4px 24px 4px 8px; + position: relative; + font-style: normal; + font-weight: 500; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.8px; + color: @primaryLabel; + + &:hover { + background: rgba(0, 0, 0, 0.04); + } + + svg { + position: absolute; + right: 4px; + top: 4px; + -moz-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -o-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + transition: all 0.3s ease-in-out; + } + + &.open { + svg { + position: absolute; + right: 4px; + top: 4px; + -moz-transform: rotate(180deg); + -webkit-transform: rotate(180deg); + -o-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); + } + } + } + + &-text { + padding-top: 12px; + text-align: left; + + p { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + margin: 0; + + a { + color: @secondaryLabel; + } + } + } + } + &__buttons { + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; + width: 100%; + box-sizing: border-box; + position: absolute; + left: 0; + bottom: 0; + font-size: 0; + padding: 24px; + box-sizing: border-box; + background-color: @white; + &-cancel { + width: 172px; + } + &-send { + width: 232px; + } + + &.border { + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.05), + 0px 0px 1px rgba(0, 0, 0, 0.25); + } + } + } \ No newline at end of file diff --git a/packages/extension/src/providers/ethereum/ui/types.ts b/packages/extension/src/providers/ethereum/ui/types.ts new file mode 100644 index 000000000..e45496d30 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/types.ts @@ -0,0 +1,32 @@ +import { ToTokenData } from "@/ui/action/types/token"; +import { GasPriceTypes } from "../libs/transaction/types"; + +export interface GasFeeInfo { + nativeValue: string; + fiatValue: string; + nativeSymbol: string; + fiatSymbol: string; +} +export interface GasFeeType { + [GasPriceTypes.ECONOMY]: GasFeeInfo; + [GasPriceTypes.REGULAR]: GasFeeInfo; + [GasPriceTypes.FAST]: GasFeeInfo; + [GasPriceTypes.FASTEST]: GasFeeInfo; +} + +export interface SendTransactionDataType { + chainId: `0x${string}`; + from: `0x${string}`; + value: `0x${string}`; + to: `0x${string}`; + data: `0x${string}`; +} +export interface VerifyTransactionParams { + fromAddress: string; + fromAddressName: string; + toAddress: string; + toToken: ToTokenData; + gasFee: GasFeeInfo; + gasPriceType: GasPriceTypes; + TransactionData: SendTransactionDataType; +} diff --git a/packages/extension/src/providers/polkadot/index.ts b/packages/extension/src/providers/polkadot/index.ts index 392a9c99c..1b6d68661 100644 --- a/packages/extension/src/providers/polkadot/index.ts +++ b/packages/extension/src/providers/polkadot/index.ts @@ -4,13 +4,13 @@ import Middlewares from "./methods"; import EventEmitter from "eventemitter3"; import { BackgroundProviderInterface, - NodeType, ProviderName, ProviderRPCRequest, } from "@/types/provider"; import GetUIPath from "@/libs/utils/get-ui-path"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import UIRoutes from "./ui/routes/names"; +import { BaseNetwork } from "@/types/base-network"; class PolkadotProvider extends EventEmitter implements BackgroundProviderInterface @@ -35,7 +35,7 @@ class PolkadotProvider private setMiddleWares(): void { this.middlewares = Middlewares.map((mw) => mw.bind(this)); } - setRequestProvider(network: NodeType): void { + setRequestProvider(network: BaseNetwork): void { this.requestProvider.changeNetwork(network.node); } request(request: ProviderRPCRequest): Promise { diff --git a/packages/extension/src/providers/polkadot/libs/api.ts b/packages/extension/src/providers/polkadot/libs/api.ts index fe7150a65..e2fb1bfcd 100644 --- a/packages/extension/src/providers/polkadot/libs/api.ts +++ b/packages/extension/src/providers/polkadot/libs/api.ts @@ -2,7 +2,6 @@ import { ProviderAPIInterface } from "@/types/provider"; import { ApiPromise, WsProvider } from "@polkadot/api"; import { PolkadotAPIOptions } from "../types"; import { AccountInfoWithRefCount } from "@polkadot/types/interfaces"; -import { fromBase } from "@/libs/utils/units"; class API implements ProviderAPIInterface { node: string; decimals: number; @@ -22,8 +21,5 @@ class API implements ProviderAPIInterface { await this.api.query.system.account(address); return balance.free.toString(); } - getBaseBalance(address: string): Promise { - return this.getBalance(address).then((bal) => fromBase(bal, this.decimals)); - } } export default API; diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/index.ts b/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/index.ts deleted file mode 100644 index fa2b5004c..000000000 --- a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/index.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { AssetsType, NodeType } from "@/types/provider"; -import supported from "./supportedTokens"; -import { options } from "@acala-network/api"; -import { ApiPromise, WsProvider } from "@polkadot/api"; -import MarketData from "@/libs/market-data"; -import { OrmlTokensAccountData } from "@acala-network/types/interfaces/types-lookup"; -import BigNumber from "bignumber.js"; -import { fromBase } from "@/libs/utils/units"; -import { - formatFiatValue, - formatFloatingPointValue, -} from "@/libs/utils/number-formatter"; -import Sparkline from "@/libs/sparkline"; -import { AccountInfoWithRefCount } from "@polkadot/types/interfaces"; - -export default async ( - network: NodeType, - address: string -): Promise => { - const provider = new WsProvider(network.node); - const api = new ApiPromise(options({ provider })); - await api.isReadyOrError; - const balancePromises = supported.map(async (token) => { - if (token.crowdloanId) { - return api.query.tokens.accounts(address, { - LiquidCrowdloan: token.crowdloanId, - }); - } else if (token.native) { - return api.query.system - .account(address) - .then(({ data }) => data); - } - return api.query.tokens.accounts(address, { - Token: token.symbol, - }); - }); - const marketData = new MarketData(); - const market = await marketData.getMarketData( - supported.map((supported) => supported.coingeckoID) - ); - const balances = (await Promise.all( - balancePromises - )) as unknown as OrmlTokensAccountData[]; - - const tokens: AssetsType[] = supported.map((st, idx) => { - const userBalance = fromBase(balances[idx].free.toString(), st.decimals); - const usdBalance = new BigNumber(userBalance).times( - market[idx]?.current_price || 0 - ); - return { - balance: balances[idx].free.toString(), - balancef: formatFloatingPointValue(userBalance).value, - balanceUSD: usdBalance.toNumber(), - balanceUSDf: formatFiatValue(usdBalance.toString()).value, - decimals: st.decimals, - icon: st.image, - name: st.name, - symbol: st.symbol, - priceChangePercentage: - market[idx]?.price_change_percentage_7d_in_currency || 0, - sparkline: market[idx] - ? new Sparkline(market[idx]?.sparkline_in_7d.price, 25).dataUri - : "", - value: market[idx]?.current_price.toString() || "0", - valuef: formatFloatingPointValue( - market[idx]?.current_price.toString() || "0" - ).value, - }; - }); - const sorted = [...tokens].filter((val, idx) => idx !== 0); - sorted.sort((a, b) => { - if (a.balanceUSD < b.balanceUSD) return 1; - else if (a.balanceUSD > b.balanceUSD) return -1; - else return 0; - }); - sorted.unshift(tokens[0]); - return sorted; -}; diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/supportedTokens.ts b/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/supportedTokens.ts deleted file mode 100644 index cb9877b98..000000000 --- a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/supportedTokens.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { AcalaTokenType } from "./types"; - -const supported: AcalaTokenType[] = [ - { - name: "Acala", - symbol: "ACA", - decimals: 12, - image: require("./icons/ACA.png"), - coingeckoID: "acala", - native: true, - }, - { - name: "Acala Dollar", - symbol: "AUSD", - decimals: 12, - image: require("./icons/AUSD.png"), - coingeckoID: "usd-coin", - }, - { - name: "Polkadot", - symbol: "DOT", - decimals: 10, - image: require("./icons/DOT.png"), - coingeckoID: "polkadot", - }, - { - name: "Liquid DOT", - symbol: "LDOT", - decimals: 10, - image: require("./icons/LDOT.png"), - coingeckoID: "polkadot", - }, - { - name: "Crowdloan DOT", - symbol: "lcDOT", - decimals: 10, - image: require("./icons/LDOT.png"), - crowdloanId: 13, - coingeckoID: "polkadot", - }, -]; -export default supported; diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/types.ts b/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/types.ts deleted file mode 100644 index ca24d7d15..000000000 --- a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface AcalaTokenType { - name: string; - symbol: string; - image: string; - decimals: number; - coingeckoID: string; - crowdloanId?: number; - native?: boolean; -} diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/dot/index.ts b/packages/extension/src/providers/polkadot/libs/assets-handlers/dot/index.ts deleted file mode 100644 index 493ddd230..000000000 --- a/packages/extension/src/providers/polkadot/libs/assets-handlers/dot/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { AssetsType, NodeType } from "@/types/provider"; -import { options } from "@acala-network/api"; -import { ApiPromise, WsProvider } from "@polkadot/api"; -import MarketData from "@/libs/market-data"; -import BigNumber from "bignumber.js"; -import { fromBase } from "@/libs/utils/units"; -import { - formatFiatValue, - formatFloatingPointValue, -} from "@/libs/utils/number-formatter"; -import Sparkline from "@/libs/sparkline"; -import { - AccountData, - AccountInfoWithRefCount, -} from "@polkadot/types/interfaces"; -import { PolkadotNodeType } from "@/providers/polkadot/types"; - -export default async ( - network: NodeType, - address: string -): Promise => { - const provider = new WsProvider(network.node); - const api = new ApiPromise(options({ provider })); - await api.isReadyOrError; - const supported = [network as PolkadotNodeType]; - const balancePromises = supported.map(() => { - return api.query.system - .account(address) - .then(({ data }) => data); - }); - const marketData = new MarketData(); - const market = await marketData.getMarketData( - supported - .filter((s) => !!s.coingeckoID) - .map((supported) => supported.coingeckoID as string) - ); - const balances = (await Promise.all( - balancePromises - )) as unknown as AccountData[]; - - const tokens: AssetsType[] = supported.map((st, idx) => { - const userBalance = fromBase(balances[idx].free.toString(), st.decimals); - const usdBalance = new BigNumber(userBalance).times( - market[idx]?.current_price || 0 - ); - return { - balance: balances[idx].free.toString(), - balancef: formatFloatingPointValue(userBalance).value, - balanceUSD: usdBalance.toNumber(), - balanceUSDf: formatFiatValue(usdBalance.toString()).value, - decimals: st.decimals, - icon: st.icon, - name: st.name, - symbol: st.currencyName, - priceChangePercentage: - market[idx]?.price_change_percentage_7d_in_currency || 0, - sparkline: market[idx] - ? new Sparkline(market[idx]?.sparkline_in_7d.price, 25).dataUri - : "", - value: market[idx]?.current_price.toString() || "0", - valuef: formatFloatingPointValue( - market[idx]?.current_price.toString() || "0" - ).value, - }; - }); - const sorted = [...tokens].filter((val, idx) => idx !== 0); - sorted.sort((a, b) => { - if (a.balanceUSD < b.balanceUSD) return 1; - else if (a.balanceUSD > b.balanceUSD) return -1; - else return 0; - }); - sorted.unshift(tokens[0]); - return sorted; -}; diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/dot/types.ts b/packages/extension/src/providers/polkadot/libs/assets-handlers/dot/types.ts deleted file mode 100644 index c523d98ae..000000000 --- a/packages/extension/src/providers/polkadot/libs/assets-handlers/dot/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface TokenType { - name: string; - symbol: string; - image: string; - decimals: number; - coingeckoID: string; -} diff --git a/packages/extension/src/providers/polkadot/methods/dot_signer_signPayload.ts b/packages/extension/src/providers/polkadot/methods/dot_signer_signPayload.ts index b7be1f8a6..e28bd7d3b 100644 --- a/packages/extension/src/providers/polkadot/methods/dot_signer_signPayload.ts +++ b/packages/extension/src/providers/polkadot/methods/dot_signer_signPayload.ts @@ -3,10 +3,8 @@ import EthereumProvider from ".."; import { WindowPromise } from "@/libs/window-promise"; import { ProviderRPCRequest } from "@/types/provider"; import { SignerPayloadJSON } from "@polkadot/types/types"; -import { TypeRegistry } from "@polkadot/types"; import { polkadotEncodeAddress, - signPayload, payloadSignTransform, } from "../libs/signing-utils"; import { getCustomError } from "@/libs/error"; @@ -23,19 +21,19 @@ const method: MiddlewareFunction = function ( const reqPayload = payload.params[0] as SignerPayloadJSON; this.KeyRing.getAccount(polkadotEncodeAddress(reqPayload.address)) .then((account) => { - const registry = new TypeRegistry(); - registry.setSignedExtensions(reqPayload.signedExtensions); - const extType = registry.createType("ExtrinsicPayload", reqPayload, { - version: reqPayload.version, - }); - const signMsg = signPayload(extType); + // const registry = new TypeRegistry(); + // registry.setSignedExtensions(reqPayload.signedExtensions); + // const extType = registry.createType("ExtrinsicPayload", reqPayload, { + // version: reqPayload.version, + // }); + // const signMsg = signPayload(extType); const windowPromise = new WindowPromise(); windowPromise .getResponse( this.getUIPath(this.UIRoutes.dotTxApprove.path), JSON.stringify({ ...payload, - params: [signMsg, account], + params: [reqPayload, account], }), true ) diff --git a/packages/extension/src/providers/polkadot/networks/acala.ts b/packages/extension/src/providers/polkadot/networks/acala.ts deleted file mode 100644 index 2025e5da2..000000000 --- a/packages/extension/src/providers/polkadot/networks/acala.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { polkadotEncodeAddress } from "@enkryptcom/utils"; -import API from "../libs/api"; -import acalaAssetsHandler from "../libs/assets-handlers/acala"; -import createIcon from "../libs/blockies"; -import { PolkadotNodeType } from "../types"; -const prefix = 10; -const acaNode: PolkadotNodeType = { - name: "ACA", - name_long: "Acala", - homePage: "https://acala.network/", - blockExplorerTX: "https://acala.subscan.io/extrinsic/[[txHash]]", - blockExplorerAddr: "https://acala.subscan.io/account/[[address]]", - isTestNetwork: false, - currencyName: "ACA", - icon: require("./icons/acala.svg"), - decimals: 12, - prefix, - signer: [SignerType.sr25519, SignerType.ed25519], - gradient: "#53CBC9", - node: "wss://acala-rpc-0.aca-api.network/", - displayAddress: (address: string) => polkadotEncodeAddress(address, prefix), - provider: ProviderName.polkadot, - coingeckoID: "acala", - identicon: createIcon, - assetsHandler: acalaAssetsHandler, - basePath: "//", -}; -acaNode.api = async () => { - const api = new API(acaNode.node, { decimals: acaNode.decimals }); - await api.init(); - return api; -}; -export default acaNode; diff --git a/packages/extension/src/providers/polkadot/networks/acala/acala.ts b/packages/extension/src/providers/polkadot/networks/acala/acala.ts new file mode 100644 index 000000000..83ba8bbe1 --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/acala/acala.ts @@ -0,0 +1,30 @@ +import { NetworkNames } from "@enkryptcom/types"; +import assets from "./assets/acala-assets"; +import { + SubstrateNetwork, + SubstrateNetworkOptions, +} from "../../types/substrate-network"; + +const acalaOptions: SubstrateNetworkOptions = { + name: NetworkNames.Acala, + name_long: "Acala", + homePage: "https://acala.network/", + blockExplorerTX: "https://acala.subscan.io/extrinsic/[[txHash]]", + blockExplorerAddr: "https://acala.subscan.io/account/[[address]]", + isTestNetwork: false, + currencyName: "ACA", + icon: require("../icons/acala.svg"), + decimals: 12, + prefix: 12, + gradient: "#53CBC9", + node: "wss://acala-rpc-0.aca-api.network/", + coingeckoID: "acala", + genesisHash: + "0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c", +}; + +const acala = new SubstrateNetwork(acalaOptions); + +acala.assets = assets; + +export default acala; diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/acala-assets.ts b/packages/extension/src/providers/polkadot/networks/acala/assets/acala-assets.ts new file mode 100644 index 000000000..a5245ebc6 --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/acala/assets/acala-assets.ts @@ -0,0 +1,57 @@ +import { SubstrateNativeToken } from "@/providers/polkadot/types/substrate-native-token"; +import { BaseToken } from "@/types/base-token"; +import { toBN } from "web3-utils"; +import { AcalaOrmlAsset } from "../types/acala-orml-asset"; + +const assets: BaseToken[] = [ + new SubstrateNativeToken({ + name: "Acala", + symbol: "ACA", + coingeckoID: "acala", + icon: require("./icons/ACA.png"), + decimals: 12, + existentialDeposit: toBN("100000000000"), + }), + new AcalaOrmlAsset({ + name: "Polkadot", + symbol: "DOT", + coingeckoID: "polkadot", + icon: require("./icons/DOT.png"), + decimals: 10, + assetType: "token", + lookupValue: "DOT", + existentialDeposit: toBN("100000000"), + }), + new AcalaOrmlAsset({ + name: "Crowdloan DOT", + symbol: "lcDOT", + coingeckoID: "polkadot", + icon: require("./icons/LCDOT.png"), + decimals: 10, + assetType: "liquidCrowdloan", + lookupValue: 13, + existentialDeposit: toBN("100000000"), + }), + new AcalaOrmlAsset({ + name: "Acala Dollar", + symbol: "AUSD", + decimals: 12, + icon: require("./icons/AUSD.png"), + coingeckoID: "usd-coin", + assetType: "token", + lookupValue: "aUSD", + existentialDeposit: toBN("100000000000"), + }), + new AcalaOrmlAsset({ + name: "Liquid DOT", + symbol: "LDOT", + decimals: 10, + icon: require("./icons/LDOT.png"), + coingeckoID: "polkadot", + assetType: "token", + lookupValue: "LDOT", + existentialDeposit: toBN("500000000"), + }), +]; + +export default assets; diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/ACA.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/ACA.png similarity index 100% rename from packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/ACA.png rename to packages/extension/src/providers/polkadot/networks/acala/assets/icons/ACA.png diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/AIR.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/AIR.png new file mode 100644 index 000000000..2bc509602 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/AIR.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/ARIS.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/ARIS.png new file mode 100644 index 000000000..605cd53fe Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/ARIS.png differ diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/AUSD.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/AUSD.png similarity index 100% rename from packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/AUSD.png rename to packages/extension/src/providers/polkadot/networks/acala/assets/icons/AUSD.png diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/BNC.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/BNC.png new file mode 100644 index 000000000..002ca36a8 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/BNC.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/BSX.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/BSX.png new file mode 100644 index 000000000..bf2cb0e43 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/BSX.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/CRAB.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/CRAB.png new file mode 100644 index 000000000..16cce6142 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/CRAB.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/CSM.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/CSM.png new file mode 100644 index 000000000..69e9dccf6 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/CSM.png differ diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/DOT.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/DOT.png similarity index 100% rename from packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/DOT.png rename to packages/extension/src/providers/polkadot/networks/acala/assets/icons/DOT.png diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/EQD.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/EQD.png new file mode 100644 index 000000000..a562556b7 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/EQD.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/GENS.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/GENS.png new file mode 100644 index 000000000..04571b315 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/GENS.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/HKO.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/HKO.png new file mode 100644 index 000000000..ac8ac87a4 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/HKO.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KAR.svg b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KAR.svg new file mode 100644 index 000000000..dd53bda44 --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KAR.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KBTC.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KBTC.png new file mode 100644 index 000000000..da27ee1ad Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KBTC.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KICO.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KICO.png new file mode 100644 index 000000000..365540f33 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KICO.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KINT.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KINT.png new file mode 100644 index 000000000..5626d94c5 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KINT.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KMA.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KMA.png new file mode 100644 index 000000000..d335b2913 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KMA.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KSM.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KSM.png new file mode 100644 index 000000000..00dd41637 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/KSM.png differ diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/LCDOT.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/LCDOT.png similarity index 100% rename from packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/LCDOT.png rename to packages/extension/src/providers/polkadot/networks/acala/assets/icons/LCDOT.png diff --git a/packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/LDOT.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/LDOT.png similarity index 100% rename from packages/extension/src/providers/polkadot/libs/assets-handlers/acala/icons/LDOT.png rename to packages/extension/src/providers/polkadot/networks/acala/assets/icons/LDOT.png diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/LKSM.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/LKSM.png new file mode 100644 index 000000000..c90daa65b Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/LKSM.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/MOVR.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/MOVR.png new file mode 100644 index 000000000..40ba8a7c6 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/MOVR.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/NEER.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/NEER.png new file mode 100644 index 000000000..ff11365c9 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/NEER.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/PHA.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/PHA.png new file mode 100644 index 000000000..eed3f5347 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/PHA.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/QTZ.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/QTZ.png new file mode 100644 index 000000000..cd1917762 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/QTZ.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/RMRK.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/RMRK.png new file mode 100644 index 000000000..3d318f1d6 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/RMRK.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TAI.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TAI.png new file mode 100644 index 000000000..b9e068d62 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TAI.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TEER.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TEER.png new file mode 100644 index 000000000..a802443fd Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TEER.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TUR.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TUR.png new file mode 100644 index 000000000..3d2eafce5 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/TUR.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/USDC.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/USDC.png new file mode 100644 index 000000000..3c9ea9f32 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/USDC.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/USDT.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/USDT.png new file mode 100644 index 000000000..5dcf007e8 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/USDT.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/icons/taiKSM.png b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/taiKSM.png new file mode 100644 index 000000000..625382725 Binary files /dev/null and b/packages/extension/src/providers/polkadot/networks/acala/assets/icons/taiKSM.png differ diff --git a/packages/extension/src/providers/polkadot/networks/acala/assets/karura-assets.ts b/packages/extension/src/providers/polkadot/networks/acala/assets/karura-assets.ts new file mode 100644 index 000000000..42c6d6ff0 --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/acala/assets/karura-assets.ts @@ -0,0 +1,273 @@ +import { SubstrateNativeToken } from "@/providers/polkadot/types/substrate-native-token"; +import { BaseToken } from "@/types/base-token"; +import { toBN } from "web3-utils"; +import { AcalaOrmlAsset } from "../types/acala-orml-asset"; + +const assets: BaseToken[] = [ + new SubstrateNativeToken({ + name: "Karura", + symbol: "KAR", + decimals: 12, + icon: require("./icons/KAR.svg"), + coingeckoID: "acala", + existentialDeposit: toBN("100000000000"), + }), + new AcalaOrmlAsset({ + name: "Acala Dollar", + symbol: "AUSD", + decimals: 12, + icon: require("./icons/AUSD.png"), + coingeckoID: "usd-coin", + assetType: "token", + lookupValue: "kUSD", + existentialDeposit: toBN("10000000000"), + }), + new AcalaOrmlAsset({ + name: "Kusama", + symbol: "KSM", + decimals: 12, + icon: require("./icons/KSM.png"), + coingeckoID: "kusama", + assetType: "token", + lookupValue: "KSM", + existentialDeposit: toBN("100000000"), + }), + new AcalaOrmlAsset({ + name: "Liquid KSM", + symbol: "LKSM", + decimals: 12, + icon: require("./icons/LKSM.png"), + coingeckoID: "kusama", + assetType: "token", + lookupValue: "KSM", + existentialDeposit: toBN("500000000"), + }), + new AcalaOrmlAsset({ + name: "Bifrost", + symbol: "BNC", + decimals: 12, + icon: require("./icons/BNC.png"), + coingeckoID: "bifrost-native-coin", + assetType: "token", + lookupValue: "BNC", + existentialDeposit: toBN("8000000000"), + }), + new AcalaOrmlAsset({ + name: "Kintsugi", + symbol: "KINT", + decimals: 12, + icon: require("./icons/KINT.png"), + coingeckoID: "kintsugi", + assetType: "token", + lookupValue: "KINT", + existentialDeposit: toBN("133330000"), + }), + new AcalaOrmlAsset({ + name: "Phala", + symbol: "PHA", + decimals: 12, + icon: require("./icons/PHA.png"), + coingeckoID: "pha", + assetType: "token", + lookupValue: "PHA", + existentialDeposit: toBN("40000000000"), + }), + new AcalaOrmlAsset({ + name: "Crab", + symbol: "CRAB", + decimals: 18, + icon: require("./icons/CRAB.png"), + coingeckoID: "darwinia-crab-network-native-token", + assetType: "foreignAsset", + lookupValue: 13, + existentialDeposit: toBN("1000000000000000000"), + }), + new AcalaOrmlAsset({ + name: "Crust Shadow", + symbol: "CSM", + decimals: 12, + icon: require("./icons/CSM.png"), + coingeckoID: "crust-shadow", + assetType: "foreignAsset", + lookupValue: 5, + existentialDeposit: toBN("1000000000000"), + }), + new AcalaOrmlAsset({ + name: "Genshiro", + symbol: "GEN", + decimals: 9, + icon: require("./icons/GENS.png"), + coingeckoID: "genshiro", + assetType: "foreignAsset", + lookupValue: 14, + existentialDeposit: toBN("1000000000000"), + }), + new AcalaOrmlAsset({ + name: "Kintsugi Wrapped BTC", + symbol: "KBTC", + decimals: 8, + icon: require("./icons/KBTC.png"), + coingeckoID: "bitcoin", + assetType: "token", + lookupValue: "kBTC", + existentialDeposit: toBN("66"), + }), + new AcalaOrmlAsset({ + name: "Calamari", + symbol: "KMA", + decimals: 12, + icon: require("./icons/KMA.png"), + coingeckoID: "calamari-network", + assetType: "foreignAsset", + lookupValue: 10, + existentialDeposit: toBN("100000000000"), + }), + new AcalaOrmlAsset({ + name: "Moonriver", + symbol: "MOVR", + decimals: 18, + icon: require("./icons/MOVR.png"), + coingeckoID: "moonriver", + assetType: "foreignAsset", + lookupValue: 3, + existentialDeposit: toBN("1000000000000000"), + }), + new AcalaOrmlAsset({ + name: "Quartz", + symbol: "QTZ", + decimals: 18, + icon: require("./icons/QTZ.png"), + coingeckoID: "quartz", + assetType: "foreignAsset", + lookupValue: 2, + existentialDeposit: toBN("1000000000000000000"), + }), + new AcalaOrmlAsset({ + name: "Integritee", + symbol: "TEER", + decimals: 12, + icon: require("./icons/TEER.png"), + coingeckoID: "integritee", + assetType: "foreignAsset", + lookupValue: 8, + existentialDeposit: toBN("100000000000"), + }), + + // const usdc = new AcalaOrmlAsset( + // { + // name: "USD Coin", + // symbol: "USDC", + // decimals: 6, + // icon: require("./icons/USDC.png"), + // erc20Address: "0x1f3a10587a20114ea25ba1b388ee2dd4a337ce27", + // coingeckoID: "usd-coin", + // }); + + new AcalaOrmlAsset({ + name: "Tether", + symbol: "USDT", + decimals: 6, + icon: require("./icons/USDT.png"), + coingeckoID: "tether", + assetType: "foreignAsset", + lookupValue: 7, + existentialDeposit: toBN("10000"), + }), + + new AcalaOrmlAsset({ + name: "Altair", + symbol: "AIR", + decimals: 18, + icon: require("./icons/AIR.png"), + coingeckoID: "altair", + assetType: "foreignAsset", + lookupValue: 12, + existentialDeposit: toBN("100000000000000000"), + }), + new AcalaOrmlAsset({ + name: "PolarisDAO", + symbol: "ARIS", + decimals: 8, + icon: require("./icons/ARIS.png"), + assetType: "foreignAsset", + lookupValue: 1, + existentialDeposit: toBN("1000000000000"), + }), + new AcalaOrmlAsset({ + name: "Turing", + symbol: "TUR", + decimals: 10, + icon: require("./icons/TUR.png"), + assetType: "foreignAsset", + lookupValue: 16, + existentialDeposit: toBN("40000000000"), + }), + new AcalaOrmlAsset({ + name: "Metaverse.Network", + symbol: "NEER", + decimals: 18, + icon: require("./icons/NEER.png"), + assetType: "foreignAsset", + lookupValue: 9, + existentialDeposit: toBN("100000000000000000"), + }), + + new AcalaOrmlAsset({ + name: "Kico", + symbol: "KICO", + decimals: 14, + icon: require("./icons/KICO.png"), + assetType: "foreignAsset", + lookupValue: 6, + existentialDeposit: toBN("100000000000000"), + }), + new AcalaOrmlAsset({ + name: "Heiko", + symbol: "HKO", + decimals: 12, + icon: require("./icons/HKO.png"), + assetType: "foreignAsset", + lookupValue: 4, + existentialDeposit: toBN("100000000000"), + }), + new AcalaOrmlAsset({ + name: "RMRK", + symbol: "RMRK", + decimals: 10, + icon: require("./icons/RMRK.png"), + coingeckoID: "rmrk", + assetType: "foreignAsset", + lookupValue: 0, + existentialDeposit: toBN("100000000"), + }), + new AcalaOrmlAsset({ + name: "Basilisk", + symbol: "BSX", + decimals: 12, + icon: require("./icons/BSX.png"), + assetType: "foreignAsset", + lookupValue: 12, + existentialDeposit: toBN("1000000000000"), + }), + new AcalaOrmlAsset({ + name: "Taiga", + symbol: "TAI", + decimals: 12, + icon: require("./icons/TAI.png"), + assetType: "token", + lookupValue: "TAI", + existentialDeposit: toBN("100000000"), + }), + new AcalaOrmlAsset({ + name: "Taiga KSM", + symbol: "taiKSM", + decimals: 12, + icon: require("./icons/taiKSM.png"), + coingeckoID: "kusama", + assetType: "stableAssetPoolToken", + lookupValue: 0, + existentialDeposit: toBN("100000000"), + }), +]; + +export default assets; diff --git a/packages/extension/src/providers/polkadot/networks/acala/karura.ts b/packages/extension/src/providers/polkadot/networks/acala/karura.ts new file mode 100644 index 000000000..3dfa327fd --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/acala/karura.ts @@ -0,0 +1,30 @@ +import { NetworkNames } from "@enkryptcom/types"; +import assets from "./assets/karura-assets"; +import { + SubstrateNetwork, + SubstrateNetworkOptions, +} from "../../types/substrate-network"; + +const karuraOptions: SubstrateNetworkOptions = { + name: NetworkNames.Karura, + name_long: "Karura", + homePage: "https://karura.network/", + blockExplorerTX: "https://karura.subscan.io/extrinsic/[[txHash]]", + blockExplorerAddr: "https://karura.subscan.io/account/[[address]]", + isTestNetwork: false, + currencyName: "KAR", + icon: require("../icons/karura.svg"), + decimals: 12, + prefix: 8, + gradient: "#FF4C3B", + node: "wss://karura.api.onfinality.io/public-ws", + coingeckoID: "karura", + genesisHash: + "0xbaf5aabe40646d11f0ee8abbdc64f4a4b7674925cba08e4a05ff9ebed6e2126b", +}; + +const karura = new SubstrateNetwork(karuraOptions); + +karura.assets = assets; + +export default karura; diff --git a/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts b/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts new file mode 100644 index 000000000..338119b05 --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts @@ -0,0 +1,39 @@ +import { BaseToken, BaseTokenOptions } from "@/types/base-token"; +import { ApiPromise } from "@polkadot/api"; +import { OrmlTokensAccountData } from "@acala-network/types/interfaces/types-lookup"; + +type AssetType = + | "token" + | "foreignAsset" + | "stableAssetPoolToken" + | "liquidCrowdloan"; + +interface AcalaOrmlAssetOptions extends BaseTokenOptions { + assetType: AssetType; + lookupValue: string | number; +} + +export class AcalaOrmlAsset extends BaseToken { + public assetType: AssetType; + public lookupValue: string | number; + + constructor(options: AcalaOrmlAssetOptions) { + super(options); + + this.assetType = options.assetType; + this.lookupValue = options.lookupValue; + } + + public async getUserBalance(api: ApiPromise, address: any): Promise { + const tokenLookup: Record = {}; + tokenLookup[this.assetType] = this.lookupValue; + + return api.query.tokens.accounts(address, tokenLookup).then((res) => { + return (res as unknown as OrmlTokensAccountData).free.toString(); + }); + } + + public async send(api: any, to: string, amount: string): Promise { + return (api as ApiPromise).tx.balances.transferKeepAlive(to, amount); + } +} diff --git a/packages/extension/src/providers/polkadot/networks/icons/karura.svg b/packages/extension/src/providers/polkadot/networks/icons/karura.svg new file mode 100644 index 000000000..dd53bda44 --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/icons/karura.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/extension/src/providers/polkadot/networks/icons/westend.svg b/packages/extension/src/providers/polkadot/networks/icons/westend.svg new file mode 100644 index 000000000..3e0599e60 --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/icons/westend.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/extension/src/providers/polkadot/networks/index.ts b/packages/extension/src/providers/polkadot/networks/index.ts index 3d6257e85..b7679f4c8 100644 --- a/packages/extension/src/providers/polkadot/networks/index.ts +++ b/packages/extension/src/providers/polkadot/networks/index.ts @@ -1,9 +1,13 @@ -import acaNode from "./acala"; +import acaNode from "./acala/acala"; import dotNode from "./polkadot"; import ksmNode from "./kusama"; +import karNode from "./acala/karura"; +import wndNode from "./westend"; export default { acala: acaNode, + karura: karNode, polkadot: dotNode, kusama: ksmNode, + westend: wndNode, }; diff --git a/packages/extension/src/providers/polkadot/networks/kusama.ts b/packages/extension/src/providers/polkadot/networks/kusama.ts index e6a4cfa78..7d07fdb46 100644 --- a/packages/extension/src/providers/polkadot/networks/kusama.ts +++ b/packages/extension/src/providers/polkadot/networks/kusama.ts @@ -1,13 +1,13 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { polkadotEncodeAddress } from "@enkryptcom/utils"; -import API from "../libs/api"; -import dot from "../libs/assets-handlers/dot"; -import createIcon from "../libs/blockies"; -import { PolkadotNodeType } from "../types"; -const prefix = 2; -const dotNode: PolkadotNodeType = { - name: "KSM", +import { NetworkNames } from "@enkryptcom/types"; +import { toBN } from "web3-utils"; +import { SubstrateNativeToken } from "../types/substrate-native-token"; +import { + SubstrateNetwork, + SubstrateNetworkOptions, +} from "../types/substrate-network"; + +const ksmOptions: SubstrateNetworkOptions = { + name: NetworkNames.Kusama, name_long: "Kusama", homePage: "https://kusama.network/", blockExplorerTX: "https://polkascan.io/kusama/transaction/[[txHash]]", @@ -16,20 +16,25 @@ const dotNode: PolkadotNodeType = { currencyName: "KSM", icon: require("./icons/kusama.svg"), decimals: 12, - prefix, - signer: [SignerType.sr25519, SignerType.ed25519], + prefix: 2, gradient: "#82D359", node: "wss://kusama-rpc.polkadot.io/", - displayAddress: (address: string) => polkadotEncodeAddress(address, prefix), - provider: ProviderName.polkadot, coingeckoID: "kusama", - identicon: createIcon, - assetsHandler: dot, - basePath: "//", + genesisHash: + "0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe", }; -dotNode.api = async () => { - const api = new API(dotNode.node, { decimals: dotNode.decimals }); - await api.init(); - return api; -}; -export default dotNode; + +const ksm = new SubstrateNetwork(ksmOptions); + +const nativeAsset = new SubstrateNativeToken({ + name: "Kusama", + symbol: "KSM", + coingeckoID: "kusama", + icon: require("./icons/kusama.svg"), + decimals: 12, + existentialDeposit: toBN("33333300"), +}); + +ksm.assets = [nativeAsset]; + +export default ksm; diff --git a/packages/extension/src/providers/polkadot/networks/polkadot.ts b/packages/extension/src/providers/polkadot/networks/polkadot.ts index ce7baee1d..c063203c8 100644 --- a/packages/extension/src/providers/polkadot/networks/polkadot.ts +++ b/packages/extension/src/providers/polkadot/networks/polkadot.ts @@ -1,13 +1,13 @@ -import { ProviderName } from "@/types/provider"; -import { SignerType } from "@enkryptcom/types"; -import { polkadotEncodeAddress } from "@enkryptcom/utils"; -import API from "../libs/api"; -import dot from "../libs/assets-handlers/dot"; -import createIcon from "../libs/blockies"; -import { PolkadotNodeType } from "../types"; -const prefix = 0; -const dotNode: PolkadotNodeType = { - name: "DOT", +import { NetworkNames } from "@enkryptcom/types"; +import { toBN } from "web3-utils"; +import { SubstrateNativeToken } from "../types/substrate-native-token"; +import { + SubstrateNetwork, + SubstrateNetworkOptions, +} from "../types/substrate-network"; + +const polkadotOptions: SubstrateNetworkOptions = { + name: NetworkNames.Polkadot, name_long: "Polkadot", homePage: "https://polkadot.network", blockExplorerTX: "https://polkascan.io/polkadot/transaction/[[txHash]]", @@ -16,20 +16,25 @@ const dotNode: PolkadotNodeType = { currencyName: "DOT", icon: require("./icons/polkadot.svg"), decimals: 10, - prefix, - signer: [SignerType.sr25519, SignerType.ed25519], + prefix: 0, gradient: "#8247E5", node: "wss://rpc.polkadot.io/", - displayAddress: (address: string) => polkadotEncodeAddress(address, prefix), - provider: ProviderName.polkadot, coingeckoID: "polkadot", - identicon: createIcon, - assetsHandler: dot, - basePath: "//", + genesisHash: + "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3", }; -dotNode.api = async () => { - const api = new API(dotNode.node, { decimals: dotNode.decimals }); - await api.init(); - return api; -}; -export default dotNode; + +const polkadot = new SubstrateNetwork(polkadotOptions); + +const nativeAsset = new SubstrateNativeToken({ + name: "Polkadot", + symbol: "DOT", + coingeckoID: "polkadot", + icon: require("./icons/polkadot.svg"), + decimals: 10, + existentialDeposit: toBN("10000000000"), +}); + +polkadot.assets = [nativeAsset]; + +export default polkadot; diff --git a/packages/extension/src/providers/polkadot/networks/westend.ts b/packages/extension/src/providers/polkadot/networks/westend.ts new file mode 100644 index 000000000..df24bdd5d --- /dev/null +++ b/packages/extension/src/providers/polkadot/networks/westend.ts @@ -0,0 +1,38 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { toBN } from "web3-utils"; +import { SubstrateNativeToken } from "../types/substrate-native-token"; +import { + SubstrateNetwork, + SubstrateNetworkOptions, +} from "../types/substrate-network"; + +const wndOptions: SubstrateNetworkOptions = { + name: NetworkNames.Westend, + name_long: "Westend", + homePage: "https://polkadot.network", + blockExplorerTX: "https://westend.subscan.io/extrinsic/[[txHash]]", + blockExplorerAddr: "https://westend.subscan.io/extrinsic/[[address]]", + isTestNetwork: true, + currencyName: "WND", + icon: require("./icons/westend.svg"), + decimals: 12, + prefix: 42, + gradient: "#8247E5", + node: "wss://westend-rpc.dwellir.com", + genesisHash: + "0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e", +}; + +const wnd = new SubstrateNetwork(wndOptions); + +const nativeAsset = new SubstrateNativeToken({ + name: "Westend", + symbol: "WND", + icon: require("./icons/westend.svg"), + decimals: 12, + existentialDeposit: toBN("10000000000"), +}); + +wnd.assets = [nativeAsset]; + +export default wnd; diff --git a/packages/extension/src/providers/polkadot/types/substrate-native-token.ts b/packages/extension/src/providers/polkadot/types/substrate-native-token.ts new file mode 100644 index 000000000..856e751b1 --- /dev/null +++ b/packages/extension/src/providers/polkadot/types/substrate-native-token.ts @@ -0,0 +1,40 @@ +import { + BaseToken, + BaseTokenOptions, + SendOptions, + TransferType, +} from "@/types/base-token"; +import { ApiPromise } from "@polkadot/api"; +import { AccountInfoWithRefCount } from "@polkadot/types/interfaces"; + +export class SubstrateNativeToken extends BaseToken { + constructor(options: BaseTokenOptions) { + super(options); + } + + public async getUserBalance(api: ApiPromise, address: any): Promise { + return api.query.system + .account(address) + .then(({ data }) => data.free.toString()); + } + + public async send( + api: any, + to: string, + amount: string, + options: SendOptions + ): Promise { + const transferType: TransferType = options ? options.type : "transfer"; + + switch (transferType) { + case "transfer": + return (api as ApiPromise).tx.balances.transfer(to, amount); + case "keepAlive": + return (api as ApiPromise).tx.balances.transferKeepAlive(to, amount); + case "all": + return (api as ApiPromise).tx.balances.transferAll(to, false); + case "allKeepAlive": + return (api as ApiPromise).tx.balances.transferAll(to, true); + } + } +} diff --git a/packages/extension/src/providers/polkadot/types/substrate-network.ts b/packages/extension/src/providers/polkadot/types/substrate-network.ts new file mode 100644 index 000000000..3d2663fb7 --- /dev/null +++ b/packages/extension/src/providers/polkadot/types/substrate-network.ts @@ -0,0 +1,161 @@ +import { BaseNetwork, BaseNetworkOptions } from "@/types/base-network"; +import SubstrateAPI from "@/providers/polkadot/libs/api"; +import { AssetsType } from "@/types/provider"; +import { BaseToken } from "@/types/base-token"; +import { ProviderName } from "@/types/provider"; +import { NetworkNames, SignerType } from "@enkryptcom/types"; +import { polkadotEncodeAddress } from "@enkryptcom/utils"; +import createIcon from "../libs/blockies"; +import MarketData from "@/libs/market-data"; +import { fromBase } from "@/libs/utils/units"; +import BigNumber from "bignumber.js"; +import { + formatFiatValue, + formatFloatingPointValue, +} from "@/libs/utils/number-formatter"; +import Sparkline from "@/libs/sparkline"; +import { SubstrateNativeToken } from "./substrate-native-token"; + +export interface SubstrateNetworkOptions { + name: NetworkNames; + name_long: string; + homePage: string; + blockExplorerTX: string; + blockExplorerAddr: string; + isTestNetwork: boolean; + currencyName: string; + icon: string; + decimals: number; + prefix: number; + gradient: string; + node: string; + coingeckoID?: string; + genesisHash: string; + transferMethods?: Record any>; +} + +export class SubstrateNetwork extends BaseNetwork { + public prefix: number; + public assets: BaseToken[] = []; + public genesisHash: string; + public transferMethods: Record any> = { + "balances.transferKeepAlive": (args: any) => { + console.log(args); + const to = args.dest["Id"]; + const token = new SubstrateNativeToken({ + name: this.name_long, + symbol: this.name, + coingeckoID: this.coingeckoID, + decimals: this.decimals, + icon: this.icon, + }); + const amount = + Number(args.value.replaceAll(",", "")) / 10 ** this.decimals; + return { to, token, amount }; + }, + }; + + constructor(options: SubstrateNetworkOptions) { + const api = async () => { + const api = new SubstrateAPI(options.node, { + decimals: options.decimals, + }); + await api.init(); + return api; + }; + + const baseOptions: BaseNetworkOptions = { + basePath: "//", + identicon: createIcon, + signer: [SignerType.sr25519, SignerType.ed25519], + displayAddress: (address: string) => + polkadotEncodeAddress(address, options.prefix), + provider: ProviderName.polkadot, + api, + ...options, + }; + super(baseOptions); + this.prefix = options.prefix; + this.genesisHash = options.genesisHash; + + if (options.transferMethods) { + this.transferMethods = { + ...options.transferMethods, + ...this.transferMethods, + }; + } + } + + public getAllTokens(): BaseToken[] { + return this.assets; + } + + public async getAllTokenInfo(address: string): Promise { + const supported = this.assets; + + if (supported.length === 0) { + const nativeToken = new SubstrateNativeToken({ + name: this.name_long, + symbol: this.name, + coingeckoID: this.coingeckoID, + decimals: this.decimals, + icon: this.icon, + }); + + supported.push(nativeToken); + } + + const api = await this.api(); + + const balancePromises = supported.map((token) => + token.getUserBalance((api as SubstrateAPI).api, address) + ); + const marketData = new MarketData(); + const market = await marketData.getMarketData( + supported + .filter(({ coingeckoID }) => coingeckoID !== undefined) + .map(({ coingeckoID }) => coingeckoID as string) + ); + + const balances = (await Promise.all( + balancePromises + )) as unknown as number[]; + + const tokens: AssetsType[] = supported.map((st, idx) => { + const userBalance = fromBase(balances[idx].toString(), st.decimals); + const usdBalance = new BigNumber(userBalance).times( + market[idx]?.current_price || 0 + ); + return { + balance: balances[idx].toString(), + balancef: formatFloatingPointValue(userBalance).value, + balanceUSD: usdBalance.toNumber(), + balanceUSDf: formatFiatValue(usdBalance.toString()).value, + decimals: st.decimals, + icon: st.icon, + name: st.name, + symbol: st.symbol, + priceChangePercentage: + market[idx]?.price_change_percentage_7d_in_currency || 0, + sparkline: market[idx] + ? new Sparkline(market[idx]?.sparkline_in_7d.price, 25).dataUri + : "", + value: market[idx]?.current_price.toString() || "0", + valuef: formatFloatingPointValue( + market[idx]?.current_price.toString() || "0" + ).value, + baseToken: st, + }; + }); + + const sorted = [...tokens].filter((val, idx) => idx !== 0); + sorted.sort((a, b) => { + if (a.balanceUSD < b.balanceUSD) return 1; + else if (a.balanceUSD > b.balanceUSD) return -1; + else return 0; + }); + sorted.unshift(tokens[0]); + + return sorted; + } +} diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/blind-approvetx.vue b/packages/extension/src/providers/polkadot/ui/custom-views/blind-approvetx.vue new file mode 100644 index 000000000..1487386e0 --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/blind-approvetx.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/index.ts b/packages/extension/src/providers/polkadot/ui/custom-views/index.ts new file mode 100644 index 000000000..25878dc23 --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/index.ts @@ -0,0 +1,21 @@ +import { Component } from "vue"; +import { SubstrateNetwork } from "../../types/substrate-network"; +import BlindVerifyView from "./blind-approvetx.vue"; +import mapping from "./mappings"; + +export const getViewAndProps = ( + network: SubstrateNetwork, + method: string, + data: any +) => { + if (mapping[network.name_long] && mapping[network.name_long][method]) { + const [view, getProps] = mapping[network.name_long][method]; + const props = getProps(network, data); + + if (props) { + return [view, props]; + } + } + + return [BlindVerifyView, {}] as [Component, any]; +}; diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/mappings/acala.ts b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/acala.ts new file mode 100644 index 000000000..ce95d064f --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/acala.ts @@ -0,0 +1,84 @@ +import { nativeTransfer } from "./substrate"; +import TransferView from "../transfer-approvetx.vue"; +import { SubstrateNetwork } from "@/providers/polkadot/types/substrate-network"; +import { AcalaOrmlAsset } from "@/providers/polkadot/networks/acala/types/acala-orml-asset"; +import { MethodMap, TransferProps } from "../types"; +import BigNumber from "bignumber.js"; +import { SubstrateNativeToken } from "@/providers/polkadot/types/substrate-native-token"; +import { polkadotEncodeAddress } from "@enkryptcom/utils"; + +const transferCurrency = ( + network: SubstrateNetwork, + args: any +): TransferProps | null => { + const to = args.dest + ? polkadotEncodeAddress(args.dest.Id, network.prefix) ?? null + : null; + const assetType = ( + args.currency_id ? Object.keys(args.currency_id) : [null] + )[0]; + const rawAmount: string | null = args.amount ?? null; + + if (to !== null && assetType !== null && rawAmount !== null) { + const baseToken = network.assets.find((token) => { + const ormlToken = token as AcalaOrmlAsset; + if (!ormlToken.assetType) return false; + + if ( + ormlToken.assetType.toLowerCase() === assetType.toLowerCase() && + ormlToken.lookupValue.toString().toLowerCase() === + args.currency_id[assetType].toString().toLowerCase() + ) + if ((token as AcalaOrmlAsset).assetType) { + return ( + (token as AcalaOrmlAsset).assetType.toLowerCase() === + assetType.toLowerCase() + ); + } else { + return false; + } + }); + + if (baseToken) { + const amount = new BigNumber(rawAmount.replaceAll(",", "")).div( + new BigNumber(10 ** baseToken.decimals) + ); + return { to, token: baseToken, amount: amount.toString() }; + } + } + + return null; +}; + +const transferNativeCurrency = ( + network: SubstrateNetwork, + args: any +): TransferProps | null => { + const to = args.dest + ? polkadotEncodeAddress(args.dest.Id, network.prefix) ?? null + : null; + const token = new SubstrateNativeToken({ + decimals: network.decimals, + icon: network.icon, + name: network.name_long, + symbol: network.name, + }); + const rawAmount: string | null = args.amount ?? null; + + if (to !== null && rawAmount !== null) { + const amount = new BigNumber(rawAmount.replaceAll(",", "")).div( + new BigNumber(10 ** token.decimals) + ); + + return { to, token, amount: amount.toString() }; + } + return null; +}; + +const methodsToArgs: MethodMap = { + "balances.transferKeepAlive": [TransferView, nativeTransfer], + "currencies.transfer": [TransferView, transferCurrency], + "currencies.transferNativeCurrency": [TransferView, transferNativeCurrency], +}; + +export default methodsToArgs; diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/mappings/index.ts b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/index.ts new file mode 100644 index 000000000..434c8b61e --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/index.ts @@ -0,0 +1,11 @@ +import polkadotMap from "./polkadot"; +import acalaMap from "./acala"; +import type { MethodMap } from "../types"; + +const mapping: Record = { + Polkadot: polkadotMap, + Acala: acalaMap, + Karura: acalaMap, +}; + +export default mapping; diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/mappings/polkadot.ts b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/polkadot.ts new file mode 100644 index 000000000..d7c23978f --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/polkadot.ts @@ -0,0 +1,9 @@ +import { nativeTransfer } from "./substrate"; +import TransferView from "../transfer-approvetx.vue"; +import { MethodMap } from "../types"; + +const methodsToArgs: MethodMap = { + "balances.transferKeepAlive": [TransferView, nativeTransfer], +}; + +export default methodsToArgs; diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/mappings/substrate.ts b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/substrate.ts new file mode 100644 index 000000000..60ff8ddf5 --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/mappings/substrate.ts @@ -0,0 +1,17 @@ +import { SubstrateNativeToken } from "../../../types/substrate-native-token"; +import { SubstrateNetwork } from "../../../types/substrate-network"; +import { polkadotEncodeAddress } from "@enkryptcom/utils"; + +export const nativeTransfer = (network: SubstrateNetwork, data: any) => { + const to = polkadotEncodeAddress(data.dest["Id"], network.prefix); + const token = new SubstrateNativeToken({ + name: network.name_long, + symbol: network.name, + coingeckoID: network.coingeckoID, + decimals: network.decimals, + icon: network.icon, + }); + const amount = + Number(data.value.replaceAll(",", "")) / 10 ** network.decimals; + return { to, token, amount }; +}; diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/transfer-approvetx.vue b/packages/extension/src/providers/polkadot/ui/custom-views/transfer-approvetx.vue new file mode 100644 index 000000000..be14732a2 --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/transfer-approvetx.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/packages/extension/src/providers/polkadot/ui/custom-views/types.ts b/packages/extension/src/providers/polkadot/ui/custom-views/types.ts new file mode 100644 index 000000000..ac9ca8874 --- /dev/null +++ b/packages/extension/src/providers/polkadot/ui/custom-views/types.ts @@ -0,0 +1,13 @@ +import { BaseToken } from "@/types/base-token"; +import { SubstrateNetwork } from "../../types/substrate-network"; + +export interface TransferProps { + to: string; + token: BaseToken; + amount: string; +} + +export type MethodMap = Record< + string, + [any, (network: SubstrateNetwork, args: any) => any] +>; diff --git a/packages/extension/src/providers/polkadot/ui/dot-accounts.vue b/packages/extension/src/providers/polkadot/ui/dot-accounts.vue index a0acfce63..43f0a1159 100644 --- a/packages/extension/src/providers/polkadot/ui/dot-accounts.vue +++ b/packages/extension/src/providers/polkadot/ui/dot-accounts.vue @@ -3,7 +3,7 @@

Polkadot Account Request