From 45e8a062dd6f27107567661e43d0c8a91b3a9f34 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 8 Mar 2023 12:35:13 -0400 Subject: [PATCH 001/251] global inject progress Signed-off-by: Ruben --- src/entries/background/index.ts | 36 +++++++++++ src/entries/content/index.ts | 40 ++++++++++--- src/entries/scripts/contentScript.ts | 36 +++++++++-- src/manifest.ts | 2 + src/pages/index.ts | 1 + src/pages/send/Send.tsx | 8 +-- src/pages/signMessage/SignMessage.tsx | 86 +++++++++++++++++++++++++++ src/pages/signMessage/index.ts | 1 + src/routes/index.tsx | 15 +++++ src/routes/paths.ts | 1 + 10 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 src/pages/signMessage/SignMessage.tsx create mode 100644 src/pages/signMessage/index.ts diff --git a/src/entries/background/index.ts b/src/entries/background/index.ts index e69de29b..a078f76b 100644 --- a/src/entries/background/index.ts +++ b/src/entries/background/index.ts @@ -0,0 +1,36 @@ +import Extension from "../../Extension"; + +chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { + console.log("message in background", { + request, + sender, + }); + + if (request.from === "signed_tab") { + chrome.windows.remove(request.id); + + return; + } + + chrome.windows.create({ + // tabId: 1, + url: chrome.runtime.getURL("src/entries/popup/index.html?query=1"), + type: "popup", + top: 0, + left: 0, + width: 357, + height: 600, + }); + // if (request) + // getSelectedAccount() + // .then((res) => sendResponse({ account: res })) + // .catch((err) => { + // console.log("extension error"); + // sendResponse({ err: String(err) }); + // }); + return true; +}); + +const getSelectedAccount = () => { + return Extension.getSelectedAccount(); +}; diff --git a/src/entries/content/index.ts b/src/entries/content/index.ts index 4db4efc1..b12208d1 100644 --- a/src/entries/content/index.ts +++ b/src/entries/content/index.ts @@ -1,12 +1,34 @@ -// function inject() { -// const file = chrome.runtime.getURL("src/entries/scripts/index.js"); +function inject() { + const file = chrome.runtime.getURL("src/entries/scripts/index.js"); -// const script = document.createElement("script"); -// script.setAttribute("type", "text/javascript"); -// script.setAttribute("src", file); -// document.body.append(script); + const script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.setAttribute("src", file); + document.body.append(script); +} -// console.log("content loaded4"); -// } +inject(); -// inject(); +window.addEventListener("message", async function (e) { + console.log("content", e); + + // if (e.data["from"] === "signed_tab") { + // chrome.windows.remove(-2); + // } + + if (e.data["from"] === "kuma") { + const response = await chrome.runtime.sendMessage(e.data); + + // console.log("content", response); + // console.log(e); + e.source?.postMessage( + { + to: e.data["method"], + ...response, + }, + { + targetOrigin: e.origin, + } + ); + } +}); diff --git a/src/entries/scripts/contentScript.ts b/src/entries/scripts/contentScript.ts index 9cae53bf..cd3b85d4 100644 --- a/src/entries/scripts/contentScript.ts +++ b/src/entries/scripts/contentScript.ts @@ -1,4 +1,32 @@ -// console.log("hola"); -// (window as ob).kuma = { -// hello: () => "hello", -// }; +import { FaSmileBeam } from "react-icons/fa"; + +console.log("hola"); +(window as any).kuma = { + hello: () => "hello", + connect: async () => { + return new Promise((res, rej) => { + try { + window.postMessage({ + connect: "connect", + from: "kuma", + method: "connect", + }); + + window.addEventListener("message", async function response(e) { + if (e.data["to"] === "connect") { + res(e.data); + window.removeEventListener("message", response); + } + }); + } catch (error) { + rej(error); + } + }); + }, + open: () => { + window.postMessage({ + from: "kuma", + method: "open", + }); + }, +}; diff --git a/src/manifest.ts b/src/manifest.ts index 22606778..8007dd49 100755 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -19,6 +19,8 @@ const commonManifest = { "storage", "activeTab", "scripting", + "nativeMessaging", + "tabs", ] as chrome.runtime.ManifestPermissions[], version: pkg.version, }; diff --git a/src/pages/index.ts b/src/pages/index.ts index 7f0b52a8..1fd22db7 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -5,4 +5,5 @@ export * from "./manageAssets"; export * from "./send"; export * from "./settings"; export * from "./signIn"; +export * from "./signMessage"; export * from "./welcome"; diff --git a/src/pages/send/Send.tsx b/src/pages/send/Send.tsx index 5bc72ed9..8058ce84 100644 --- a/src/pages/send/Send.tsx +++ b/src/pages/send/Send.tsx @@ -127,7 +127,7 @@ export const Send = () => { let date; let reference = ""; - if (selectedAccount.type.includes("EVM")) { + if (selectedAccount?.type?.includes("EVM")) { const pk = await Extension.showPrivateKey(); const wallet = new ethers.Wallet( @@ -204,7 +204,7 @@ export const Send = () => { if (!evmTx?.to) return; (async () => { try { - if (selectedAccount.type.includes("EVM")) { + if (selectedAccount?.type?.includes("EVM")) { const [feeData, gasLimit] = await Promise.all([ (api as ethers.providers.JsonRpcProvider).getFeeData(), (api as ethers.providers.JsonRpcProvider).estimateGas(evmTx), @@ -241,7 +241,7 @@ export const Send = () => { return; } - if (selectedAccount.type.includes("EVM")) { + if (selectedAccount?.type?.includes("EVM")) { setEvmTx((prevState) => ({ ...prevState, value: amount, @@ -296,7 +296,7 @@ export const Send = () => { return () => clearTimeout(getData); }, [amount, destinationAccount?.address, destinationIsInvalid]); - const originAccountIsEVM = selectedAccount.type.includes("EVM"); + const originAccountIsEVM = selectedAccount?.type?.includes("EVM"); const canContinue = (Number(amount) > 0 && destinationAccount?.address) || loadingFee; diff --git a/src/pages/signMessage/SignMessage.tsx b/src/pages/signMessage/SignMessage.tsx new file mode 100644 index 00000000..9199b8e0 --- /dev/null +++ b/src/pages/signMessage/SignMessage.tsx @@ -0,0 +1,86 @@ +import { FC } from "react"; +import { LoadingButton, PageWrapper } from "@src/components/common"; +import { useAccountContext, useNetworkContext } from "@src/providers"; +import { useTranslation } from "react-i18next"; +import Extension from "@src/Extension"; +import { ethers } from "ethers"; +import { ApiPromise } from "@polkadot/api"; +import { Keyring } from "@polkadot/keyring"; +import { u8aToHex } from "@polkadot/util"; + +interface SignMessageProps { + message: string; +} + +export const SignMessage: FC = ({ message }) => { + const { t } = useTranslation("sign_message"); + + const { + state: { api, type }, + } = useNetworkContext(); + const { + state: { selectedAccount }, + } = useAccountContext(); + + const sign = async () => { + try { + let signedMessage = ""; + if (type.toLowerCase() === "wasm") { + const mnemonic = await Extension.showSeed(); + const keyring = new Keyring({ type: "sr25519" }); + + const signer = keyring.addFromMnemonic(mnemonic as string); + const _message = await signer.sign(message); + signedMessage = u8aToHex(_message); + + console.log("sign wasm:"); + console.log(signedMessage); + } + + if (type.toLowerCase() === "evm") { + const pk = await Extension.showPrivateKey(); + + const signer = new ethers.Wallet( + pk as string, + api as ethers.providers.JsonRpcProvider + ); + + signedMessage = await signer.signMessage(message); + console.log("sign evm:"); + console.log(signedMessage); + } + + const { id } = await chrome.windows.getCurrent(); + + await chrome.runtime.sendMessage({ + message: signedMessage, + from: "signed_tab", + id, + }); + } catch (error) { + console.log(error); + } + }; + + return ( + +
+
+

{t("title")}

+
+
+

account:

+

{selectedAccount?.value?.address || ""}

+
+
+

Message:

+

{message}

+
+
+ {t("cancel")} + {t("Sign")} +
+
+
+ ); +}; diff --git a/src/pages/signMessage/index.ts b/src/pages/signMessage/index.ts new file mode 100644 index 00000000..a978633a --- /dev/null +++ b/src/pages/signMessage/index.ts @@ -0,0 +1 @@ +export * from "./SignMessage"; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 27f571d5..bc9600ce 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -9,6 +9,7 @@ import { ManageAssets, Send, SignIn, + SignMessage, Welcome, } from "@src/pages"; import { @@ -41,6 +42,7 @@ import { SETTINGS_MANAGE_NETWORKS, SETTINGS_SECURITY, SIGNIN, + SIGN_MESSAGE, } from "./paths"; import { Decrypt } from "@src/components/decrypt"; @@ -102,6 +104,19 @@ export const Routes = () => { setHomeRoute(); }; + if (location.search) { + return ( + + + } + /> + + + ); + } + return ( diff --git a/src/routes/paths.ts b/src/routes/paths.ts index b13b774d..44d20895 100644 --- a/src/routes/paths.ts +++ b/src/routes/paths.ts @@ -13,3 +13,4 @@ export const SETTINGS_GENERAL = "/settings-general"; export const SETTINGS_MANAGE_NETWORKS = "/settings-manage-networks"; export const SETTINGS_SECURITY = "/settings-security"; export const SIGNIN = "/sign-in"; +export const SIGN_MESSAGE = "/sign-message"; From 1bd2a7090e557c36aee31dcfdda2182493babb86 Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 9 Mar 2023 12:24:19 -0400 Subject: [PATCH 002/251] content script progress Signed-off-by: Ruben --- src/components/common/LoadingButton.tsx | 2 +- src/components/common/PageWrapper.tsx | 2 +- src/entries/background/index.ts | 98 +++++++++++++++++++------ src/entries/content/content.tsx | 20 ----- src/entries/content/index.ts | 72 ++++++++++++++---- src/entries/content/style.css | 0 src/entries/scripts/contentScript.ts | 44 +++++------ src/manifest.ts | 1 - src/pages/signMessage/SignMessage.tsx | 75 +++++++++++++------ src/routes/index.tsx | 2 +- src/utils/utils.ts | 27 +++++++ utils/plugins/copy-content-style.ts | 5 -- 12 files changed, 238 insertions(+), 110 deletions(-) delete mode 100644 src/entries/content/content.tsx delete mode 100644 src/entries/content/style.css diff --git a/src/components/common/LoadingButton.tsx b/src/components/common/LoadingButton.tsx index 17ff81b3..9ed5fb02 100644 --- a/src/components/common/LoadingButton.tsx +++ b/src/components/common/LoadingButton.tsx @@ -10,7 +10,7 @@ interface LoadinButtonProps { } const DEFAULT_CLASSNAME = - "min-w-[75px] min-h-[35px] border bg-custom-green-bg text-white rounded-md px-4 py-2 m-2 transition duration-500 ease select-none hover:bg-custom-green-bg focus:outline-none focus:shadow-outline disabled:opacity-50"; + "min-w-[75px] min-h-[35px] border-[0.5px] bg-custom-green-bg text-white rounded-xl px-4 py-2 m-2 transition duration-500 ease select-none hover:bg-custom-green-bg focus:outline-none focus:shadow-outline disabled:opacity-50"; const DEFAULT_SPINNER_CLASSNAME = "mx-auto animate-spin fill-white"; export const LoadingButton: FC = ({ diff --git a/src/components/common/PageWrapper.tsx b/src/components/common/PageWrapper.tsx index 6f2f29ac..fe577f43 100644 --- a/src/components/common/PageWrapper.tsx +++ b/src/components/common/PageWrapper.tsx @@ -12,7 +12,7 @@ export const PageWrapper: FC = ({ const defaultContentClassName = "flex py-6 px-4"; return ( -
+
{children}
); diff --git a/src/entries/background/index.ts b/src/entries/background/index.ts index a078f76b..c171ce7c 100644 --- a/src/entries/background/index.ts +++ b/src/entries/background/index.ts @@ -1,34 +1,90 @@ +import { makeQuerys } from "@src/utils/utils"; import Extension from "../../Extension"; -chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { - console.log("message in background", { - request, - sender, - }); - - if (request.from === "signed_tab") { - chrome.windows.remove(request.id); +const openPopUp = (params: any) => { + const querys = makeQuerys(params); - return; - } + // console.log(querys); - chrome.windows.create({ - // tabId: 1, - url: chrome.runtime.getURL("src/entries/popup/index.html?query=1"), + return chrome.windows.create({ + url: chrome.runtime.getURL(`src/entries/popup/index.html${querys}`), type: "popup", top: 0, left: 0, width: 357, height: 600, + focused: true, }); - // if (request) - // getSelectedAccount() - // .then((res) => sendResponse({ account: res })) - // .catch((err) => { - // console.log("extension error"); - // sendResponse({ err: String(err) }); - // }); - return true; +}; + +// read messages from content +chrome.runtime.onMessage.addListener(async function ( + request, + sender, + sendResponse +) { + if (request.origin === "kuma") { + // console.log("bg listener", request); + + try { + switch (request.method) { + case "sign_message": { + await openPopUp({ ...request, tabId: sender.tab?.id }); + return; + } + + case "sign_message_response": { + if (request.from !== "popup") return; + await chrome.tabs.sendMessage(Number(request.toTabId), { + ...request, + from: "bg", + }); + if (request.fromWindowId) + await chrome.windows.remove(request.fromWindowId as number); + + return; + } + + case "get_account_info": { + getSelectedAccount().then(sendResponse).catch(sendResponse); + return; + } + + default: + break; + } + } catch (error) { + console.log(error); + await chrome.tabs.sendMessage(Number(request.toTabId), { + ...{ + ...request, + method: `${request.method}_response`, + }, + from: "bg", + error, + }); + return error; + } + + return true; + } +}); + +chrome.runtime.onConnect.addListener(function (port) { + if (port.name === "sign_message") { + port.onDisconnect.addListener(async function (port) { + const queries = port.sender?.url?.split("?")[1]; + const { tabId, origin, method } = Object.fromEntries( + new URLSearchParams(queries) + ); + await chrome.tabs.sendMessage(Number(tabId), { + origin, + method: `${method}_response`, + response: null, + from: "bg", + }); + }); + } }); const getSelectedAccount = () => { diff --git a/src/entries/content/content.tsx b/src/entries/content/content.tsx deleted file mode 100644 index c639aae7..00000000 --- a/src/entries/content/content.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { createRoot } from "react-dom/client"; -import "@src/base.css"; -import { Main } from "@src/main"; - -// function init() { -// const rootContainer = document.querySelector("#__root"); -// if (!rootContainer) throw new Error("Can't find Newtab root element"); -// const root = createRoot(rootContainer); -// root.render(
); -// } - -// init(); - -// refreshOnUpdate("pages/content/components/Demo"); - -const root = document.createElement("div"); -root.id = "chrome-extension-boilerplate-react-vite-content-view-root"; -document.body.append(root); - -createRoot(root).render(
); diff --git a/src/entries/content/index.ts b/src/entries/content/index.ts index b12208d1..4e54658a 100644 --- a/src/entries/content/index.ts +++ b/src/entries/content/index.ts @@ -9,26 +9,72 @@ function inject() { inject(); +// read messages from injected object +// messages MUST have origin: "kuma" atribute window.addEventListener("message", async function (e) { - console.log("content", e); - - // if (e.data["from"] === "signed_tab") { - // chrome.windows.remove(-2); - // } - - if (e.data["from"] === "kuma") { - const response = await chrome.runtime.sendMessage(e.data); - - // console.log("content", response); - // console.log(e); + const data = e.data; + if (data["origin"] === "kuma") { + const response = await chrome.runtime.sendMessage(data); e.source?.postMessage( { - to: e.data["method"], - ...response, + response_method: data.method, + data: response, }, { targetOrigin: e.origin, } ); + return; + // switch (data.method) { + // case "sign_message": { + // const response = await chrome.runtime.sendMessage(data); + // e.source?.postMessage( + // { + // response_method: data.method, + // data: response, + // }, + // { + // targetOrigin: e.origin, + // } + // ); + // return; + // } + // // case "get_account_info": { + // // const response = await chrome.runtime.sendMessage(e.data); + // // e.source?.postMessage( + // // { + // // response_method: data.method, + // // data: response, + // // }, + // // { + // // targetOrigin: e.origin, + // // } + // // ); + // // return; + // // } + // default: + // break; + // } + } +}); + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.origin === "kuma") { + // console.log("content listener", request); + if (request.method.endsWith("_response") && request.from === "bg") { + // console.log(request); + window.postMessage({ ...request, from: "content" }); + return ""; + } + + // switch (request.method) { + // case "sign_message_response": + // window.postMessage(request); + // return ""; + // break; + + // default: + // break; + // } } }); diff --git a/src/entries/content/style.css b/src/entries/content/style.css deleted file mode 100644 index e69de29b..00000000 diff --git a/src/entries/scripts/contentScript.ts b/src/entries/scripts/contentScript.ts index cd3b85d4..8266ca12 100644 --- a/src/entries/scripts/contentScript.ts +++ b/src/entries/scripts/contentScript.ts @@ -1,32 +1,26 @@ -import { FaSmileBeam } from "react-icons/fa"; +interface KumaProps { + method: string; + params: object; +} -console.log("hola"); (window as any).kuma = { - hello: () => "hello", - connect: async () => { + call: ({ method, params }: KumaProps) => { return new Promise((res, rej) => { - try { - window.postMessage({ - connect: "connect", - from: "kuma", - method: "connect", - }); + window.postMessage({ + origin: "kuma", + method: method, + params: params, + }); - window.addEventListener("message", async function response(e) { - if (e.data["to"] === "connect") { - res(e.data); - window.removeEventListener("message", response); - } - }); - } catch (error) { - rej(error); - } - }); - }, - open: () => { - window.postMessage({ - from: "kuma", - method: "open", + window.addEventListener("message", async function response(e) { + if ( + e.data["method"] === `${method}_response` && + e.data["from"] === "content" + ) { + res(e.data?.response); + window.removeEventListener("message", response); + } + }); }); }, }; diff --git a/src/manifest.ts b/src/manifest.ts index 8007dd49..0e969fcd 100755 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -8,7 +8,6 @@ const commonManifest = { matches: ["http://*/*", "https://*/*", ""], run_at: "document_end", js: ["src/entries/content/index.js"], - css: ["contentStyle.css"], }, ], icons: { diff --git a/src/pages/signMessage/SignMessage.tsx b/src/pages/signMessage/SignMessage.tsx index 9199b8e0..1d637b89 100644 --- a/src/pages/signMessage/SignMessage.tsx +++ b/src/pages/signMessage/SignMessage.tsx @@ -1,18 +1,18 @@ -import { FC } from "react"; +import { FC, useEffect } from "react"; import { LoadingButton, PageWrapper } from "@src/components/common"; import { useAccountContext, useNetworkContext } from "@src/providers"; import { useTranslation } from "react-i18next"; import Extension from "@src/Extension"; import { ethers } from "ethers"; -import { ApiPromise } from "@polkadot/api"; import { Keyring } from "@polkadot/keyring"; import { u8aToHex } from "@polkadot/util"; +import { parseIncomingQuery } from "@src/utils/utils"; interface SignMessageProps { - message: string; + query: string; } -export const SignMessage: FC = ({ message }) => { +export const SignMessage: FC = ({ query }) => { const { t } = useTranslation("sign_message"); const { @@ -22,6 +22,17 @@ export const SignMessage: FC = ({ message }) => { state: { selectedAccount }, } = useAccountContext(); + const { params, ...metadata } = parseIncomingQuery(query); + + // console.log({ + // params, + // metadata, + // }); + + useEffect(() => { + chrome.runtime.connect({ name: "sign_message" }); + }, []); + const sign = async () => { try { let signedMessage = ""; @@ -30,7 +41,7 @@ export const SignMessage: FC = ({ message }) => { const keyring = new Keyring({ type: "sr25519" }); const signer = keyring.addFromMnemonic(mnemonic as string); - const _message = await signer.sign(message); + const _message = await signer.sign(params?.message); signedMessage = u8aToHex(_message); console.log("sign wasm:"); @@ -45,7 +56,7 @@ export const SignMessage: FC = ({ message }) => { api as ethers.providers.JsonRpcProvider ); - signedMessage = await signer.signMessage(message); + signedMessage = await signer.signMessage(params?.message); console.log("sign evm:"); console.log(signedMessage); } @@ -53,31 +64,51 @@ export const SignMessage: FC = ({ message }) => { const { id } = await chrome.windows.getCurrent(); await chrome.runtime.sendMessage({ - message: signedMessage, - from: "signed_tab", - id, + from: "popup", + origin: metadata.origin, + method: `${metadata.method}_response`, + fromWindowId: id, + toTabId: metadata.tabId, + response: { + message: signedMessage, + }, }); } catch (error) { console.log(error); } }; + const onClose = async () => { + const { id } = await chrome.windows.getCurrent(); + + await chrome.runtime.sendMessage({ + from: "popup", + origin: metadata.origin, + method: `${metadata.method}_response`, + fromWindowId: id, + toTabId: metadata.tabId, + response: null, + }); + }; + return ( - -
-
-

{t("title")}

-
-
-

account:

-

{selectedAccount?.value?.address || ""}

-
-
-

Message:

-

{message}

+ +
+
+
+

{t("title")}

+
+
+

account:

+

{selectedAccount?.value?.address || ""}

+
+
+

Message:

+

{params?.message || ""}

+
- {t("cancel")} + {t("cancel")} {t("Sign")}
diff --git a/src/routes/index.tsx b/src/routes/index.tsx index bc9600ce..53202a54 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -110,7 +110,7 @@ export const Routes = () => { } + element={} /> diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 9f2fac9e..ccec0b08 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -14,3 +14,30 @@ export const formatDate = (date: number) => { return formattedDate; }; + +export const parseIncomingQuery = (query: string) => { + const _obj = Object.fromEntries(new URLSearchParams(query)); + + Object.keys(_obj).forEach((key) => { + const atribute = _obj[key]; + if (atribute.startsWith("{") && atribute.endsWith("}")) { + _obj[key] = JSON.parse(atribute); + } + }); + + return _obj; +}; + +export const makeQuerys = (params: any) => { + return ( + "?" + + Object.keys(params) + .map((key) => { + if (typeof params[key] === "object") { + return `${key}=${JSON.stringify(params[key])}`; + } + return `${key}=${encodeURIComponent(params[key])}`; + }) + .join("&") + ); +}; diff --git a/utils/plugins/copy-content-style.ts b/utils/plugins/copy-content-style.ts index 5d8c45c7..ae3fae1b 100644 --- a/utils/plugins/copy-content-style.ts +++ b/utils/plugins/copy-content-style.ts @@ -1,4 +1,3 @@ -import * as fs from "fs"; import * as path from "path"; import colorLog from "../log"; import { PluginOption } from "vite"; @@ -6,15 +5,11 @@ import { PluginOption } from "vite"; const { resolve } = path; const root = resolve(__dirname, "..", ".."); -const contentStyle = resolve(root, "src", "entries", "content", "style.css"); -const outDir = resolve(__dirname, "..", "..", "public"); export default function copyContentStyle(): PluginOption { return { name: "make-manifest", buildEnd() { - fs.copyFileSync(contentStyle, resolve(outDir, "contentStyle.css")); - colorLog("contentStyle copied", "success"); }, }; From 24cd292557086330a3bf45449e20f941a56cc25c Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 9 Mar 2023 15:51:03 -0400 Subject: [PATCH 003/251] get account info method + fixes Signed-off-by: Ruben --- src/entries/background/index.ts | 16 +++++++++++- src/entries/content/index.ts | 44 +-------------------------------- 2 files changed, 16 insertions(+), 44 deletions(-) diff --git a/src/entries/background/index.ts b/src/entries/background/index.ts index c171ce7c..c78a831b 100644 --- a/src/entries/background/index.ts +++ b/src/entries/background/index.ts @@ -46,7 +46,21 @@ chrome.runtime.onMessage.addListener(async function ( } case "get_account_info": { - getSelectedAccount().then(sendResponse).catch(sendResponse); + getSelectedAccount() + .then(async (res) => { + await chrome.tabs.sendMessage(Number(sender.tab?.id), { + origin: "kuma", + method: `${request.method}_response`, + response: { + address: res?.value.address, + type: res?.type, + }, + from: "bg", + }); + }) + .catch((err) => { + console.log(err); + }); return; } diff --git a/src/entries/content/index.ts b/src/entries/content/index.ts index 4e54658a..b08fbe44 100644 --- a/src/entries/content/index.ts +++ b/src/entries/content/index.ts @@ -25,56 +25,14 @@ window.addEventListener("message", async function (e) { } ); return; - // switch (data.method) { - // case "sign_message": { - // const response = await chrome.runtime.sendMessage(data); - // e.source?.postMessage( - // { - // response_method: data.method, - // data: response, - // }, - // { - // targetOrigin: e.origin, - // } - // ); - // return; - // } - // // case "get_account_info": { - // // const response = await chrome.runtime.sendMessage(e.data); - // // e.source?.postMessage( - // // { - // // response_method: data.method, - // // data: response, - // // }, - // // { - // // targetOrigin: e.origin, - // // } - // // ); - // // return; - // // } - // default: - // break; - // } } }); -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { +chrome.runtime.onMessage.addListener((request) => { if (request.origin === "kuma") { - // console.log("content listener", request); if (request.method.endsWith("_response") && request.from === "bg") { - // console.log(request); window.postMessage({ ...request, from: "content" }); return ""; } - - // switch (request.method) { - // case "sign_message_response": - // window.postMessage(request); - // return ""; - // break; - - // default: - // break; - // } } }); From 71ec6ecae1a8a800f06927fbe21fe89de1bedb87 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 10 Mar 2023 09:07:34 -0400 Subject: [PATCH 004/251] listen extrinsic Signed-off-by: Ruben --- src/pages/balance/components/Assets.tsx | 28 +++---- src/pages/balance/components/TotalBalance.tsx | 16 ++-- src/pages/send/Send.tsx | 82 +++++++++++-------- 3 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/pages/balance/components/Assets.tsx b/src/pages/balance/components/Assets.tsx index a7fce0ee..21329776 100644 --- a/src/pages/balance/components/Assets.tsx +++ b/src/pages/balance/components/Assets.tsx @@ -2,10 +2,10 @@ import { BsArrowUpRight } from "react-icons/bs"; import { Asset } from "../Balance"; import { formatAmountWithDecimals } from "@src/utils/assets"; import { Loading } from "@src/components/common"; -// import { ImCoinDollar } from "react-icons/im"; -// import { useTranslation } from "react-i18next"; -// import { useNavigate } from "react-router-dom"; -// import { MANAGE_ASSETS } from "@src/routes/paths"; +import { ImCoinDollar } from "react-icons/im"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { MANAGE_ASSETS } from "@src/routes/paths"; export const Assets = ({ assets = [], @@ -14,8 +14,8 @@ export const Assets = ({ assets: Asset[]; isLoading: boolean; }) => { - // const { t } = useTranslation("balance"); - // const navigate = useNavigate(); + const { t } = useTranslation("balance"); + const navigate = useNavigate(); if (isLoading) return ; @@ -34,16 +34,16 @@ export const Assets = ({
- + + +
))} - {/*
+
-
*/} +
); }; diff --git a/src/pages/balance/components/TotalBalance.tsx b/src/pages/balance/components/TotalBalance.tsx index 461105e4..e0469397 100644 --- a/src/pages/balance/components/TotalBalance.tsx +++ b/src/pages/balance/components/TotalBalance.tsx @@ -1,9 +1,9 @@ import { FC } from "react"; -// import { BsArrowUpRight, BsArrowDownLeft } from "react-icons/bs"; -// import { useTranslation } from "react-i18next"; +import { BsArrowUpRight, BsArrowDownLeft } from "react-icons/bs"; +import { useTranslation } from "react-i18next"; import { formatAmountWithDecimals } from "@src/utils/assets"; -// import { useNavigate } from "react-router-dom"; -// import { SEND } from "@src/routes/paths"; +import { useNavigate } from "react-router-dom"; +import { SEND } from "@src/routes/paths"; interface TotalBalanceProps { balance?: number; @@ -14,8 +14,8 @@ export const TotalBalance: FC = ({ balance = 0, accountName = "", }) => { - // const { t } = useTranslation("balance"); - // const navigate = useNavigate(); + const { t } = useTranslation("balance"); + const navigate = useNavigate(); return (
@@ -26,7 +26,7 @@ export const TotalBalance: FC = ({

$

{formatAmountWithDecimals(balance, 5)}

- {/*
+
-
*/} +
); }; diff --git a/src/pages/send/Send.tsx b/src/pages/send/Send.tsx index 8058ce84..881df57f 100644 --- a/src/pages/send/Send.tsx +++ b/src/pages/send/Send.tsx @@ -151,37 +151,56 @@ export const Send = () => { const keyring = new Keyring({ type: "sr25519" }); const sender = keyring.addFromMnemonic(seed as string); - const res = await extrinsic?.signAndSend(sender); + const unsub = await extrinsic?.signAndSend( + sender, + ({ events, status, txHash, ...rest }) => { + console.log(`Current status is ${status.type}`); + + if (status.isFinalized) { + console.log( + `Transaction included at blockHash ${status.asFinalized}` + ); + console.log(`Transaction hash ${txHash.toHex()}`); + + // Loop through Vec to display all events + events.forEach(({ phase, event: { data, method, section } }) => { + console.log(`\t' ${phase}: ${section}.${method}:: ${data}`); + }); + + unsub(); + } + } + ); - hash = res.toHuman(); - date = Date.now(); - reference = "wasm"; + // hash = res.toHuman(); + // date = Date.now(); + // reference = "wasm"; } - const activity = { - address: destinationAccount.address, - type: RecordType.TRANSFER, - reference, - hash, - status: RecordStatus.PENDING, - createdAt: date, - lastUpdated: date, - error: undefined, - network: selectedChain?.name || "", - recipientNetwork: selectedChain?.name || "", - data: { - symbol: String(selectedChain?.nativeCurrency.symbol), - from: selectedAccount.value.address, - to: destinationAccount.address, - gas: "0", - gasPrice: "0", - value: amount, - } as TransferData, - }; - - await Extension.addActivity(hash, activity as Record); + // const activity = { + // address: destinationAccount, + // type: RecordType.TRANSFER, + // reference, + // hash, + // status: RecordStatus.PENDING, + // createdAt: date, + // lastUpdated: date, + // error: undefined, + // network: selectedChain?.name || "", + // recipientNetwork: selectedChain?.name || "", + // data: { + // symbol: String(selectedChain?.nativeCurrency.symbol), + // from: selectedAccount.value.address, + // to: destinationAccount, + // gas: "0", + // gasPrice: "0", + // value: amount, + // } as TransferData, + // }; + + // await Extension.addActivity(hash, activity as Record); showSuccessToast(t("tx_saved")); - navigate(BALANCE); + // navigate(BALANCE); } catch (error) { console.log(error); showErrorToast(error as string); @@ -245,7 +264,7 @@ export const Send = () => { setEvmTx((prevState) => ({ ...prevState, value: amount, - to: destinationAccount?.address || "", + to: destinationAccount || "", })); return; } @@ -262,7 +281,7 @@ export const Send = () => { const _amount = Number(amount) * currencyUnits; const extrinsic = await (api as ApiPromise).tx.balances.transfer( - destinationAccount.address, + destinationAccount, _amount ); @@ -294,12 +313,11 @@ export const Send = () => { }, 1000); return () => clearTimeout(getData); - }, [amount, destinationAccount?.address, destinationIsInvalid]); + }, [amount, destinationAccount, destinationIsInvalid]); const originAccountIsEVM = selectedAccount?.type?.includes("EVM"); - const canContinue = - (Number(amount) > 0 && destinationAccount?.address) || loadingFee; + const canContinue = Number(amount) > 0 && destinationAccount && !loadingFee; return ( From 692d6bab55c9a291af51a3187560027e4e789c84 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 10 Mar 2023 15:42:30 -0400 Subject: [PATCH 005/251] save extrinsic number in wasm transfers Signed-off-by: Ruben --- src/constants/chains.ts | 2 +- src/pages/balance/components/Activity.tsx | 13 ++- src/pages/send/Send.tsx | 101 ++++++++++++++-------- 3 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/constants/chains.ts b/src/constants/chains.ts index 6a9761cd..73c1f770 100644 --- a/src/constants/chains.ts +++ b/src/constants/chains.ts @@ -229,7 +229,7 @@ export const TESTNETS: Chain[] = [ explorers: [ { name: "", - url: "", + url: "https://rockmine.subscan.io/", }, ], supportedAccounts: [WASM], diff --git a/src/pages/balance/components/Activity.tsx b/src/pages/balance/components/Activity.tsx index 94d6a25e..82e5362b 100644 --- a/src/pages/balance/components/Activity.tsx +++ b/src/pages/balance/components/Activity.tsx @@ -10,10 +10,15 @@ import Contact from "@src/storage/entities/registry/Contact"; import { formatDate } from "@src/utils/utils"; import { CHAINS } from "@src/constants/chains"; import { useAccountContext } from "@src/providers/accountProvider/AccountProvider"; +import { useNetworkContext } from "@src/providers"; export const Activity = () => { const { t } = useTranslation("activity"); + const { + state: { type }, + } = useNetworkContext(); + const { state: { selectedAccount }, } = useAccountContext(); @@ -65,7 +70,11 @@ export const Activity = () => { (chain) => chain.name.toLowerCase() === network.toLowerCase() ) || {}; const { url } = explorers?.[0] || {}; - return `${url}tx/${hash}`; + if (type.toLowerCase() === "wasm") { + return `${url}extrinsic/${hash}`; + } else { + return `${url}tx/${hash}`; + } }; const getContactName = (address: string) => { @@ -145,6 +154,8 @@ export const Activity = () => { {tCommon("view_in_scanner")} diff --git a/src/pages/send/Send.tsx b/src/pages/send/Send.tsx index 881df57f..10fd166a 100644 --- a/src/pages/send/Send.tsx +++ b/src/pages/send/Send.tsx @@ -153,54 +153,81 @@ export const Send = () => { const sender = keyring.addFromMnemonic(seed as string); const unsub = await extrinsic?.signAndSend( sender, - ({ events, status, txHash, ...rest }) => { + async ({ events, status, txHash, ...rest }) => { console.log(`Current status is ${status.type}`); + if (status.isInBlock) { + console.log("Included at block hash", status.asInBlock.toHex()); + console.log("Events:"); + } + if (status.isFinalized) { - console.log( - `Transaction included at blockHash ${status.asFinalized}` + const failedEvents = events.filter(({ event }) => + api.events.system.ExtrinsicFailed.is(event) ); - console.log(`Transaction hash ${txHash.toHex()}`); - // Loop through Vec to display all events - events.forEach(({ phase, event: { data, method, section } }) => { - console.log(`\t' ${phase}: ${section}.${method}:: ${data}`); - }); + console.log("failed events", failedEvents); + + if (failedEvents.length > 0) { + console.log("update to failed"); + } else { + let number = 0; + + events.forEach( + ({ event: { data, method, section }, phase, ...evt }) => { + // console.log("evt", evt); + + number = phase.toJSON().applyExtrinsic; + + console.log( + "\t", + phase.toString(), + `: ${section}.${method}`, + data.toString() + ); + } + ); + + const { block } = await (api as ApiPromise).rpc.chain.getBlock( + status.asFinalized.toHex() + ); + const extrinsic = `${block.header.number.toNumber()}-${number}`; + // console.log("block", `${blockNumber}-${number}`); + // console.log("Finalized block hash", status.asFinalized.toHex()); + + date = Date.now(); + reference = "wasm"; + const activity = { + address: destinationAccount, + type: RecordType.TRANSFER, + reference, + hash: extrinsic, + status: RecordStatus.PENDING, + createdAt: date, + lastUpdated: date, + error: undefined, + network: selectedChain?.name || "", + recipientNetwork: selectedChain?.name || "", + data: { + symbol: String(selectedChain?.nativeCurrency.symbol), + from: selectedAccount.value.address, + to: destinationAccount, + gas: "0", + gasPrice: "0", + value: amount, + } as TransferData, + }; + + await Extension.addActivity(hash, activity as Record); + showSuccessToast(t("tx_saved")); + navigate(BALANCE); + } unsub(); } } ); - - // hash = res.toHuman(); - // date = Date.now(); - // reference = "wasm"; } - - // const activity = { - // address: destinationAccount, - // type: RecordType.TRANSFER, - // reference, - // hash, - // status: RecordStatus.PENDING, - // createdAt: date, - // lastUpdated: date, - // error: undefined, - // network: selectedChain?.name || "", - // recipientNetwork: selectedChain?.name || "", - // data: { - // symbol: String(selectedChain?.nativeCurrency.symbol), - // from: selectedAccount.value.address, - // to: destinationAccount, - // gas: "0", - // gasPrice: "0", - // value: amount, - // } as TransferData, - // }; - - // await Extension.addActivity(hash, activity as Record); - showSuccessToast(t("tx_saved")); - // navigate(BALANCE); } catch (error) { console.log(error); showErrorToast(error as string); From cd9bfbdcf0f6d9de521abe2338b04e8a2b12f52c Mon Sep 17 00:00:00 2001 From: Ruben Date: Tue, 14 Mar 2023 10:51:28 -0400 Subject: [PATCH 006/251] txProvider with wasm tx Signed-off-by: Ruben --- src/Extension.ts | 9 + src/entries/background/index.ts | 2 +- src/main.tsx | 11 +- src/pages/balance/Balance.tsx | 5 +- src/pages/balance/components/Activity.tsx | 52 ++-- src/pages/balance/components/Settings.tsx | 40 +-- src/pages/send/Send.tsx | 102 ++------ src/providers/index.ts | 1 + src/providers/txProvider/TxProvider.tsx | 305 +++++++++++++++++++++- src/storage/entities/activity/Activity.ts | 4 +- 10 files changed, 392 insertions(+), 139 deletions(-) diff --git a/src/Extension.ts b/src/Extension.ts index 7d9bb0ff..188d8d39 100644 --- a/src/Extension.ts +++ b/src/Extension.ts @@ -18,6 +18,7 @@ import Record from "./storage/entities/activity/Record"; import Activity from "./storage/entities/activity/Activity"; import Chains from "./storage/entities/Chains"; import Register from "./storage/entities/registry/Register"; +import { RecordStatus } from "./storage/entities/activity/types"; export default class Extension { private static async init( @@ -270,5 +271,13 @@ export default class Extension { const register = new Register(address, Date.now()); await Registry.addRecent(network, register); } + + static async updateActivity( + txHash: string, + status: RecordStatus, + error?: string | undefined + ) { + await Activity.updateRecordStatus(txHash, status, error); + } /* c8 ignore stop */ } diff --git a/src/entries/background/index.ts b/src/entries/background/index.ts index c78a831b..0519c8ca 100644 --- a/src/entries/background/index.ts +++ b/src/entries/background/index.ts @@ -7,7 +7,7 @@ const openPopUp = (params: any) => { // console.log(querys); return chrome.windows.create({ - url: chrome.runtime.getURL(`src/entries/popup/index.html${querys}`), + url: chrome.runtime.getURL(`src/entries/popup/index.html${querys}`), // ?method="sign_message¶ms={}" type: "popup", top: 0, left: 0, diff --git a/src/main.tsx b/src/main.tsx index be240e79..cfe48fc7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,9 @@ -import { AccountProvider, AuthProvider, NetworkProvider } from "./providers"; +import { + AccountProvider, + AuthProvider, + NetworkProvider, + TxProvider, +} from "./providers"; import { Routes } from "./routes"; import { ToastContainer } from "react-toastify"; import "./utils/i18n"; @@ -10,7 +15,9 @@ export const Main = () => { - + + + diff --git a/src/pages/balance/Balance.tsx b/src/pages/balance/Balance.tsx index b110dec4..42cd6ca1 100644 --- a/src/pages/balance/Balance.tsx +++ b/src/pages/balance/Balance.tsx @@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next"; import { ethers } from "ethers"; import AccountEntity from "@src/storage/entities/Account"; import { Activity, Assets, Header, Footer, TotalBalance } from "./components"; +import { useLocation } from "react-router-dom"; export interface Asset { name: string; @@ -21,6 +22,8 @@ export interface Asset { } export const Balance = () => { + const { state } = useLocation(); + const { state: { api, selectedChain, rpc, type }, } = useNetworkContext(); @@ -116,7 +119,7 @@ export const Balance = () => { balance={totalBalance} /> - + {TABS.map((tab) => ( { const { t } = useTranslation("activity"); @@ -23,34 +28,22 @@ export const Activity = () => { state: { selectedAccount }, } = useAccountContext(); + const { + state: { activity }, + } = useTxContext(); + const { t: tCommon } = useTranslation("common"); const [isLoading, setIsLoading] = useState(true); const [search, setSearch] = useState("" as string); - const [records, setRecords] = useState([] as Record[]); const [contacts, setContacts] = useState([] as Contact[]); const { showErrorToast } = useToast(); useEffect(() => { if (selectedAccount) { - getActivity(); getContacts(); } }, [selectedAccount.key]); - const getActivity = async () => { - try { - setIsLoading(true); - const records = await Extension.getActivity(); - setRecords(records); - } catch (error) { - console.error(error); - setRecords([]); - showErrorToast(tCommon(error as string)); - } finally { - setIsLoading(false); - } - }; - const getContacts = async () => { try { setIsLoading(true); @@ -105,9 +98,9 @@ export const Activity = () => { const filteredRecords = useMemo(() => { const _search = search.trim().toLocaleLowerCase(); - if (!_search) return records; + if (!_search) return activity; - return records + return activity .filter(({ hash, reference, address }) => { return ( hash.toLowerCase().includes(_search) || @@ -116,7 +109,7 @@ export const Activity = () => { ); }) .sort((a, b) => (b.lastUpdated as number) - (a.lastUpdated as number)); - }, [search, records]); + }, [search, activity]); if (isLoading) { return ; @@ -134,7 +127,7 @@ export const Activity = () => { />
- {records.length === 0 && ( + {activity.length === 0 && (

{t("empty")}

@@ -143,11 +136,11 @@ export const Activity = () => { ({ address, status, lastUpdated, data, network, hash }) => (
-
- -
+
+ +

{getContactName(address)}

{`${formatDate(lastUpdated as number)} - `} @@ -160,6 +153,13 @@ export const Activity = () => { {tCommon("view_in_scanner")}

+

+ {status} +

diff --git a/src/pages/balance/components/Settings.tsx b/src/pages/balance/components/Settings.tsx index f0e9f2d0..0d727a75 100644 --- a/src/pages/balance/components/Settings.tsx +++ b/src/pages/balance/components/Settings.tsx @@ -6,10 +6,10 @@ import Extension from "@src/Extension"; import { useTranslation } from "react-i18next"; import { SETTINGS_GENERAL, - // SETTINGS_ADVANCED, - // SETTINGS_CONTACTS, - // SETTINGS_SECURITY, - // SETTINGS_BUG, + SETTINGS_ADVANCED, + SETTINGS_CONTACTS, + SETTINGS_SECURITY, + SETTINGS_BUG, SIGNIN, } from "@src/routes/paths"; import { ICON_SIZE } from "@src/constants/icons"; @@ -19,22 +19,22 @@ const OPTIONS = [ text: "general", href: SETTINGS_GENERAL, }, - // { - // text: "advanced", - // href: SETTINGS_ADVANCED, - // }, - // { - // text: "contacts", - // href: SETTINGS_CONTACTS, - // }, - // { - // text: "security", - // href: SETTINGS_SECURITY, - // }, - // { - // text: "bug_report", - // href: SETTINGS_BUG, - // }, + { + text: "advanced", + href: SETTINGS_ADVANCED, + }, + { + text: "contacts", + href: SETTINGS_CONTACTS, + }, + { + text: "security", + href: SETTINGS_SECURITY, + }, + { + text: "bug_report", + href: SETTINGS_BUG, + }, ]; export const Settings = () => { diff --git a/src/pages/send/Send.tsx b/src/pages/send/Send.tsx index 10fd166a..fa845d0c 100644 --- a/src/pages/send/Send.tsx +++ b/src/pages/send/Send.tsx @@ -13,12 +13,15 @@ import { useEffect, useMemo, useState, useCallback } from "react"; import { SelectableAsset } from "./components/SelectableAsset"; import { NumericFormat } from "react-number-format"; import { FormProvider, useForm } from "react-hook-form"; -import { useNetworkContext } from "@src/providers"; +import { + useAccountContext, + useNetworkContext, + useTxContext, +} from "@src/providers"; import { yupResolver } from "@hookform/resolvers/yup"; import { number, object, string } from "yup"; import { ethers } from "ethers"; import { useToast } from "@src/hooks"; -import { useAccountContext } from "@src/providers/accountProvider/AccountProvider"; import { ApiPromise } from "@polkadot/api"; import { decodeAddress, encodeAddress, Keyring } from "@polkadot/keyring"; import Record from "@src/storage/entities/activity/Record"; @@ -32,6 +35,7 @@ import { BALANCE } from "@src/routes/paths"; import { BiLeftArrowAlt } from "react-icons/bi"; import { isHex } from "@polkadot/util"; import { isAddress } from "ethers/lib/utils"; +import { AccountType } from "@src/accounts/types"; export const Send = () => { const { t } = useTranslation("send"); @@ -46,6 +50,8 @@ export const Send = () => { state: { selectedAccount }, } = useAccountContext(); + const { addTxToQueue } = useTxContext(); + const schema = useMemo(() => { return object({ from: object() @@ -151,82 +157,21 @@ export const Send = () => { const keyring = new Keyring({ type: "sr25519" }); const sender = keyring.addFromMnemonic(seed as string); - const unsub = await extrinsic?.signAndSend( - sender, - async ({ events, status, txHash, ...rest }) => { - console.log(`Current status is ${status.type}`); - if (status.isInBlock) { - console.log("Included at block hash", status.asInBlock.toHex()); - console.log("Events:"); - } + addTxToQueue({ + type: AccountType.WASM, + tx: extrinsic, + sender, + destinationAccount, + amount, + }); - if (status.isFinalized) { - const failedEvents = events.filter(({ event }) => - api.events.system.ExtrinsicFailed.is(event) - ); - - console.log("failed events", failedEvents); - - if (failedEvents.length > 0) { - console.log("update to failed"); - } else { - let number = 0; - - events.forEach( - ({ event: { data, method, section }, phase, ...evt }) => { - // console.log("evt", evt); - - number = phase.toJSON().applyExtrinsic; - - console.log( - "\t", - phase.toString(), - `: ${section}.${method}`, - data.toString() - ); - } - ); - - const { block } = await (api as ApiPromise).rpc.chain.getBlock( - status.asFinalized.toHex() - ); - const extrinsic = `${block.header.number.toNumber()}-${number}`; - // console.log("block", `${blockNumber}-${number}`); - // console.log("Finalized block hash", status.asFinalized.toHex()); - - date = Date.now(); - reference = "wasm"; - const activity = { - address: destinationAccount, - type: RecordType.TRANSFER, - reference, - hash: extrinsic, - status: RecordStatus.PENDING, - createdAt: date, - lastUpdated: date, - error: undefined, - network: selectedChain?.name || "", - recipientNetwork: selectedChain?.name || "", - data: { - symbol: String(selectedChain?.nativeCurrency.symbol), - from: selectedAccount.value.address, - to: destinationAccount, - gas: "0", - gasPrice: "0", - value: amount, - } as TransferData, - }; - - await Extension.addActivity(hash, activity as Record); - showSuccessToast(t("tx_saved")); - navigate(BALANCE); - } - - unsub(); - } - } - ); + showSuccessToast(t("tx_send")); + navigate(BALANCE, { + state: { + tab: "activity", + }, + }); } } catch (error) { console.log(error); @@ -334,7 +279,10 @@ export const Send = () => { "estimated total": `${total} ${currencySymbol}`, }); } catch (error) { - console.error(error); + console.error(String(error)); + setWasmFees({ + "": `${String(error).split("Error:")[1]}`, + }); } setLoadingFee(false); }, 1000); diff --git a/src/providers/index.ts b/src/providers/index.ts index 4a884ce4..a47ce984 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,3 +1,4 @@ export { AccountProvider, useAccountContext } from "./accountProvider"; export { AuthProvider, useAuthContext } from "./authProvider"; export { NetworkProvider, useNetworkContext } from "./networkProvider"; +export { TxProvider, useTxContext } from "./txProvider"; diff --git a/src/providers/txProvider/TxProvider.tsx b/src/providers/txProvider/TxProvider.tsx index c8e38e11..bc98b871 100644 --- a/src/providers/txProvider/TxProvider.tsx +++ b/src/providers/txProvider/TxProvider.tsx @@ -6,26 +6,36 @@ import { useContext, useEffect, useReducer, + useState, } from "react"; -import Extension from "../../Extension"; -import { AccountFormType } from "@src/pages"; import { useTranslation } from "react-i18next"; -import { useState } from "react"; +import { AccountType } from "@src/accounts/types"; +import { useNetworkContext } from "../networkProvider/NetworkProvider"; +import { + RecordStatus, + RecordType, + TransferData, +} from "@src/storage/entities/activity/types"; +import { useAccountContext } from "../accountProvider"; +import Extension from "@src/Extension"; +import Record from "@src/storage/entities/activity/Record"; +import { SubmittableExtrinsic } from "@polkadot/api/types"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { ApiPromise } from "@polkadot/api"; interface InitialState { - queue: any; + queue: newTx[]; + activity: any[]; } const initialState: InitialState = { queue: [], + activity: [], }; interface TxContext { state: InitialState; - // createAccount: (account: AccountFormType) => Promise; - // importAccount: (account: AccountFormType) => Promise; - // deriveAccount: (account: AccountFormType) => Promise; - // restorePassword: (account: AccountFormType) => Promise; + addTxToQueue: (newTx: newTx) => void; } const TxContext = createContext({} as TxContext); @@ -38,29 +48,302 @@ const reducer = (state: InitialState, action: any): InitialState => { // isInit: false, // }; // } + + case "add-tx-to-queue": { + const { tx } = action.payload; + + return { + ...state, + queue: [...state.queue, tx], + }; + } + case "add-activity": { + const { tx } = action.payload; + + return { + ...state, + activity: [tx, ...state.activity], + }; + } + case "init-activity": { + const { activity } = action.payload; + + return { + ...state, + activity, + }; + } + case "update-activity-status": { + const { hash, status, error } = action.payload; + const activity = state.activity.map((act) => + act.hash === hash ? { ...act, status, error } : act + ); + return { + ...state, + activity, + }; + } + case "remove-from-queue": { + const { index } = action.payload; + const queue = state.queue.filter((_, _index) => _index !== index); + + return { + ...state, + queue, + }; + } default: return state; } }; +interface newTx { + type: AccountType; + tx: SubmittableExtrinsic<"promise">; + sender: any; + destinationAccount: string; + amount: number; +} + +interface ProcessWasmTxProps { + tx: newTx; +} + export const TxProvider: FC = ({ children }) => { const { t: tCommon } = useTranslation("common"); const { showErrorToast } = useToast(); - const [state] = useReducer(reducer, initialState); + const { + state: { api, selectedChain }, + } = useNetworkContext(); + + const { + state: { selectedAccount }, + } = useAccountContext(); + + const [state, dispatch] = useReducer(reducer, initialState); + const [isSearching, setisSearching] = useState(false); - const addTx = () => { + const addTxToQueue = (newTx: newTx) => { /// + dispatch({ + type: "add-tx-to-queue", + payload: { + tx: newTx, + }, + }); }; useEffect(() => { - /// + if (state.queue.length > 0) { + const lasIndex = state.queue.length - 1; + const lastTx = state.queue[lasIndex]; + + if (lastTx.type === AccountType.WASM) { + processWasmTx(lastTx); + } + } }, [state.queue]); + useEffect(() => { + if (selectedAccount.key && api) { + (async () => { + const records = await Extension.getActivity(); + dispatch({ + type: "init-activity", + payload: { + activity: records, + }, + }); + })(); + } + }, [selectedAccount.key, api]); + + useEffect(() => { + if (state.activity.length > 0 && !isSearching) { + setisSearching(true); + for (const activity of state.activity) { + if (activity.status === RecordStatus.PENDING) { + searchTx(Number(activity?.fromBlock), activity.hash); + } + } + } + }, [state.activity, isSearching]); + + const processWasmTx = async ({ + amount, + destinationAccount, + sender, + tx: extrinsic, + type, + }: newTx) => { + const a = await (api as ApiPromise).rpc.chain.getBlock(); + + const unsub = await extrinsic?.signAndSend( + sender, + async ({ events, txHash, status }: ISubmittableResult) => { + if (String(status.type) === "Ready") { + const hash = txHash.toString(); + const date = Date.now(); + const reference = "wasm"; + const activity = { + fromBlock: a.block.header.number.toString(), + address: destinationAccount, + type: RecordType.TRANSFER, + reference, + hash, + status: RecordStatus.PENDING, + createdAt: date, + lastUpdated: date, + error: undefined, + network: selectedChain?.name || "", + recipientNetwork: selectedChain?.name || "", + data: { + symbol: String(selectedChain?.nativeCurrency.symbol), + from: selectedAccount.value.address, + to: destinationAccount, + gas: "0", + gasPrice: "0", + value: String(amount), + } as TransferData, + }; + dispatch({ + type: "add-activity", + payload: { + tx: activity, + }, + }); + await Extension.addActivity(hash, activity as Record); + console.log("save record"); + } + if (status.isFinalized) { + const failedEvents = events.filter(({ event }) => + api.events.system.ExtrinsicFailed.is(event) + ); + console.log("failed events", failedEvents); + let status = RecordStatus.PENDING; + let error = undefined; + if (failedEvents.length > 0) { + console.log("update to failed"); + failedEvents.forEach( + ({ + event: { + data: [_error, info], + }, + }) => { + if (_error.isModule) { + // for module errors, we have the section indexed, lookup + const decoded = api.registry.findMetaError(_error.asModule); + const { docs, method, section } = decoded; + error = `${section}.${method}: ${docs.join(" ")}`; + console.log(error); + } else { + // Other, CannotLookup, BadOrigin, no extra info + error = _error.toString(); + console.log(error); + } + } + ); + status = RecordStatus.FAIL; + } else { + status = RecordStatus.SUCCESS; + } + const hash = txHash.toString(); + console.log("should update:", { + hash, + status, + }); + dispatch({ + type: "update-activity-status", + payload: { + hash, + status, + error, + }, + }); + await Extension.updateActivity(hash, status, error); + unsub(); + } + } + ); + }; + + const searchTx = async (blockNumber: any, extHash: any) => { + let number = blockNumber; + let finish = false; + while (!finish) { + const hash = await (api as ApiPromise).rpc.chain.getBlockHash(number); + const { block } = await (api as ApiPromise).rpc.chain.getBlock(hash); + + const apiAt = await api.at(block.header.hash); + const allRecords = await apiAt.query.system.events(); + + for (const [ + index, + { + method: { method, section }, + hash, + }, + ] of block.extrinsics.entries()) { + // console.log(); + if (hash.toString() === extHash) { + console.log("found at: ", number); + allRecords + // filter the specific events based on the phase and then the + // index of our extrinsic in the block + .filter( + ({ phase }) => + phase.isApplyExtrinsic && phase.asApplyExtrinsic.eq(index) + ) + // test the events against the specific types we are looking for + .forEach(async ({ event }) => { + let status = RecordStatus.PENDING; + let error = undefined; + if (api.events.system.ExtrinsicSuccess.is(event)) { + status = RecordStatus.SUCCESS; + } else if (api.events.system.ExtrinsicFailed.is(event)) { + const [dispatchError] = event.data; + + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule + ); + + error = `${decoded.section}.${decoded.name}`; + } else { + error = dispatchError.toString(); + } + status = RecordStatus.FAIL; + } + + await Extension.updateActivity(hash.toString(), status, error); + + // dispatch({ + // type: "update-activity-status", + // payload: { + // hash, + // status, + // error, + // }, + // }); + }); + finish = true; + break; + } + } + + number++; + if (number === blockNumber + 10) { + finish = true; + } + } + }; + return ( {children} diff --git a/src/storage/entities/activity/Activity.ts b/src/storage/entities/activity/Activity.ts index 971577a7..61d6e6e5 100644 --- a/src/storage/entities/activity/Activity.ts +++ b/src/storage/entities/activity/Activity.ts @@ -38,7 +38,8 @@ export default class Activity extends BaseEntity { static async updateRecordStatus( txHash: string, - status: RecordStatus + status: RecordStatus, + error: string | undefined ): Promise { const { key } = (await SelectedAccount.get()) || {}; if (!key) throw new Error("failed_to_add_record"); @@ -48,6 +49,7 @@ export default class Activity extends BaseEntity { if (!record) throw new Error("failed_to_update_record_status"); record.status = status; record.lastUpdated = Date.now(); + record.error = error; activity.addRecord(key, txHash, record); await Activity.set(activity); } From 07f616b594a555d78da524111e38961c682186d2 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 15 Mar 2023 07:15:37 -0400 Subject: [PATCH 007/251] tx provider with evm tx Signed-off-by: Ruben --- src/pages/send/Send.tsx | 81 ++++++------- src/providers/txProvider/TxProvider.tsx | 145 ++++++++++++++++++------ 2 files changed, 155 insertions(+), 71 deletions(-) diff --git a/src/pages/send/Send.tsx b/src/pages/send/Send.tsx index fa845d0c..34847d1e 100644 --- a/src/pages/send/Send.tsx +++ b/src/pages/send/Send.tsx @@ -9,7 +9,7 @@ import { TbChevronRight } from "react-icons/tb"; import { SelectableChain } from "./components/SelectableChain"; import Extension from "@src/Extension"; import { Destination } from "./components/Destination"; -import { useEffect, useMemo, useState, useCallback } from "react"; +import { useEffect, useMemo, useState } from "react"; import { SelectableAsset } from "./components/SelectableAsset"; import { NumericFormat } from "react-number-format"; import { FormProvider, useForm } from "react-hook-form"; @@ -24,12 +24,6 @@ import { ethers } from "ethers"; import { useToast } from "@src/hooks"; import { ApiPromise } from "@polkadot/api"; import { decodeAddress, encodeAddress, Keyring } from "@polkadot/keyring"; -import Record from "@src/storage/entities/activity/Record"; -import { - RecordType, - RecordStatus, - TransferData, -} from "@src/storage/entities/activity/types"; import { useNavigate } from "react-router-dom"; import { BALANCE } from "@src/routes/paths"; import { BiLeftArrowAlt } from "react-icons/bi"; @@ -43,7 +37,7 @@ export const Send = () => { const { showErrorToast, showSuccessToast } = useToast(); const { - state: { selectedChain, api }, + state: { selectedChain, api, type }, } = useNetworkContext(); const { @@ -129,10 +123,6 @@ export const Send = () => { const onSubmit = handleSubmit(async (data) => { setIsSendingTx(true); try { - let hash = ""; - let date; - let reference = ""; - if (selectedAccount?.type?.includes("EVM")) { const pk = await Extension.showPrivateKey(); @@ -141,17 +131,18 @@ export const Send = () => { api as ethers.providers.JsonRpcProvider ); - const res = await wallet.sendTransaction({ + const tx = await wallet.sendTransaction({ ...evmTx, - value: Number( - Number(evmTx.value) * - 10 ** (selectedChain?.nativeCurrency?.decimals || 1) - ), + value: ethers.utils.parseEther(amount), }); - hash = res.hash; - date = Date.now(); - reference = "evm"; + addTxToQueue({ + type: AccountType.EVM, + tx, + sender: wallet, + destinationAccount, + amount, + }); } else { const seed = await Extension.showSeed(); @@ -165,32 +156,21 @@ export const Send = () => { destinationAccount, amount, }); - - showSuccessToast(t("tx_send")); - navigate(BALANCE, { - state: { - tab: "activity", - }, - }); } + + showSuccessToast(t("tx_send")); + navigate(BALANCE, { + state: { + tab: "activity", + }, + }); } catch (error) { - console.log(error); showErrorToast(error as string); } finally { setIsSendingTx(false); } }); - const formatEthAmount = useCallback( - (amount: any, unitName?: any) => { - return ethers.utils.formatUnits( - amount, - unitName || (selectedChain?.nativeCurrency.decimals as number) - ); - }, - [selectedChain?.nativeCurrency.decimals] - ); - useEffect(() => { if (!evmTx?.to) return; (async () => { @@ -294,6 +274,8 @@ export const Send = () => { const canContinue = Number(amount) > 0 && destinationAccount && !loadingFee; + console.log(type); + return ( @@ -358,6 +340,29 @@ export const Send = () => {
+ + {type === "WASM" ? ( + <> +
+

{t("tip")}

+ { + setValue("amount", value); + }} + allowedDecimalSeparators={["%"]} + /> + + {/* */} +
+ + ) : ( + <> + )} + {loadingFee ? ( ) : ( diff --git a/src/providers/txProvider/TxProvider.tsx b/src/providers/txProvider/TxProvider.tsx index bc98b871..c18747f3 100644 --- a/src/providers/txProvider/TxProvider.tsx +++ b/src/providers/txProvider/TxProvider.tsx @@ -19,9 +19,10 @@ import { import { useAccountContext } from "../accountProvider"; import Extension from "@src/Extension"; import Record from "@src/storage/entities/activity/Record"; -import { SubmittableExtrinsic } from "@polkadot/api/types"; +import { AddressOrPair, SubmittableExtrinsic } from "@polkadot/api/types"; import { ISubmittableResult } from "@polkadot/types/types"; import { ApiPromise } from "@polkadot/api"; +import { ethers } from "ethers"; interface InitialState { queue: newTx[]; @@ -97,10 +98,12 @@ const reducer = (state: InitialState, action: any): InitialState => { } }; +type polkadotExtrinsic = SubmittableExtrinsic<"promise">; +type evmTx = ethers.providers.TransactionResponse; interface newTx { type: AccountType; - tx: SubmittableExtrinsic<"promise">; - sender: any; + tx: polkadotExtrinsic | evmTx; + sender: AddressOrPair | ethers.Wallet; destinationAccount: string; amount: number; } @@ -125,7 +128,6 @@ export const TxProvider: FC = ({ children }) => { const [isSearching, setisSearching] = useState(false); const addTxToQueue = (newTx: newTx) => { - /// dispatch({ type: "add-tx-to-queue", payload: { @@ -141,12 +143,14 @@ export const TxProvider: FC = ({ children }) => { if (lastTx.type === AccountType.WASM) { processWasmTx(lastTx); + } else { + processEVMTx(lastTx); } } }, [state.queue]); useEffect(() => { - if (selectedAccount.key && api) { + if (selectedAccount.key && selectedChain?.name && api) { (async () => { const records = await Extension.getActivity(); dispatch({ @@ -157,14 +161,18 @@ export const TxProvider: FC = ({ children }) => { }); })(); } - }, [selectedAccount.key, api]); + }, [selectedAccount.key, api, selectedChain?.name]); useEffect(() => { if (state.activity.length > 0 && !isSearching) { setisSearching(true); for (const activity of state.activity) { if (activity.status === RecordStatus.PENDING) { - searchTx(Number(activity?.fromBlock), activity.hash); + if (activity.reference === "wasm") { + searchTx(Number(activity?.fromBlock), activity.hash); + } else { + searchEvmTx(activity.hash); + } } } } @@ -179,8 +187,9 @@ export const TxProvider: FC = ({ children }) => { }: newTx) => { const a = await (api as ApiPromise).rpc.chain.getBlock(); - const unsub = await extrinsic?.signAndSend( - sender, + const unsub = await (extrinsic as polkadotExtrinsic)?.signAndSend( + sender as AddressOrPair, + { tip: 0 }, async ({ events, txHash, status }: ISubmittableResult) => { if (String(status.type) === "Ready") { const hash = txHash.toString(); @@ -214,17 +223,14 @@ export const TxProvider: FC = ({ children }) => { }, }); await Extension.addActivity(hash, activity as Record); - console.log("save record"); } if (status.isFinalized) { const failedEvents = events.filter(({ event }) => api.events.system.ExtrinsicFailed.is(event) ); - console.log("failed events", failedEvents); let status = RecordStatus.PENDING; let error = undefined; if (failedEvents.length > 0) { - console.log("update to failed"); failedEvents.forEach( ({ event: { @@ -232,15 +238,11 @@ export const TxProvider: FC = ({ children }) => { }, }) => { if (_error.isModule) { - // for module errors, we have the section indexed, lookup const decoded = api.registry.findMetaError(_error.asModule); const { docs, method, section } = decoded; error = `${section}.${method}: ${docs.join(" ")}`; - console.log(error); } else { - // Other, CannotLookup, BadOrigin, no extra info error = _error.toString(); - console.log(error); } } ); @@ -249,10 +251,6 @@ export const TxProvider: FC = ({ children }) => { status = RecordStatus.SUCCESS; } const hash = txHash.toString(); - console.log("should update:", { - hash, - status, - }); dispatch({ type: "update-activity-status", payload: { @@ -285,17 +283,13 @@ export const TxProvider: FC = ({ children }) => { hash, }, ] of block.extrinsics.entries()) { - // console.log(); if (hash.toString() === extHash) { - console.log("found at: ", number); allRecords - // filter the specific events based on the phase and then the - // index of our extrinsic in the block + .filter( ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.eq(index) ) - // test the events against the specific types we are looking for .forEach(async ({ event }) => { let status = RecordStatus.PENDING; let error = undefined; @@ -318,14 +312,14 @@ export const TxProvider: FC = ({ children }) => { await Extension.updateActivity(hash.toString(), status, error); - // dispatch({ - // type: "update-activity-status", - // payload: { - // hash, - // status, - // error, - // }, - // }); + dispatch({ + type: "update-activity-status", + payload: { + hash, + status, + error, + }, + }); }); finish = true; break; @@ -339,6 +333,91 @@ export const TxProvider: FC = ({ children }) => { } }; + const processEVMTx = async ({ + amount, + destinationAccount, + sender, + tx: evmTx, + type, + }: newTx) => { + try { + const tx = evmTx as ethers.providers.TransactionResponse; + + const date = Date.now(); + + const hash = tx.hash; + + const activity = { + address: destinationAccount, + type: RecordType.TRANSFER, + reference: "evm", + hash: hash, + status: RecordStatus.PENDING, + createdAt: date, + lastUpdated: date, + error: undefined, + network: selectedChain?.name || "", + recipientNetwork: selectedChain?.name || "", + data: { + symbol: String(selectedChain?.nativeCurrency.symbol), + from: selectedAccount.value.address, + to: destinationAccount, + gas: "0", + gasPrice: "0", + value: String(amount), + } as TransferData, + }; + dispatch({ + type: "add-activity", + payload: { + tx: activity, + }, + }); + await Extension.addActivity(tx.hash, activity as Record); + + const result = await tx.wait(); + + const status = + result.status === 1 ? RecordStatus.SUCCESS : RecordStatus.FAIL; + const error = ""; + + dispatch({ + type: "update-activity-status", + payload: { + hash: hash, + status, + error, + }, + }); + await Extension.updateActivity(hash, status, error); + } catch (err) { + const code = err.data.replace("Reverted ", ""); + let reason = ethers.utils.toUtf8String("0x" + code.substr(138)); + showErrorToast(reason); + } + }; + + const searchEvmTx = async (hash: string) => { + const txReceipt = await ( + api as ethers.providers.JsonRpcProvider + ).getTransaction(hash); + const result = await txReceipt.wait(); + + const status = + result.status === 1 ? RecordStatus.SUCCESS : RecordStatus.FAIL; + const error = ""; + + dispatch({ + type: "update-activity-status", + payload: { + hash: hash, + status, + error, + }, + }); + await Extension.updateActivity(hash, status, error); + }; + return ( Date: Wed, 15 Mar 2023 22:16:52 +0900 Subject: [PATCH 008/251] manage networks --- src/Extension.ts | 12 +- src/i18n/en.json | 6 +- src/i18n/es.json | 6 +- src/i18n/jp.json | 6 +- src/pages/balance/components/Settings.tsx | 30 +- src/pages/settings/Advanced.tsx | 27 +- src/pages/settings/General.tsx | 18 ++ .../advanced_settings/ManageNetworks.tsx | 291 +++++++++++++++++- src/storage/entities/Chains.ts | 106 ++----- src/storage/entities/settings/Settings.ts | 2 +- 10 files changed, 352 insertions(+), 152 deletions(-) diff --git a/src/Extension.ts b/src/Extension.ts index 188d8d39..fb7e93f4 100644 --- a/src/Extension.ts +++ b/src/Extension.ts @@ -251,10 +251,18 @@ export default class Extension { return Activity.getRecords(); } - static async getAllChains(): Promise { + static async getAllChains(): Promise { const chains = await Chains.get(); if (!chains) throw new Error("failed_to_get_chains"); - return chains.getAll(); + return chains; + } + + static async saveCustomChain(chain: Chain) { + await Chains.saveCustomChain(chain); + } + + static async removeCustomChain(chainName: string) { + await Chains.removeCustomChain(chainName); } static async getXCMChains(chainName: string): Promise { diff --git a/src/i18n/en.json b/src/i18n/en.json index d849442e..d79ff973 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -143,11 +143,11 @@ }, "general_settings": { "title": "General", - "languages": "Languages" + "languages": "Languages", + "manage_networks": "Manage networks" }, "advanced_settings": { - "title": "Advanced", - "manage_networks": "Manage networks" + "title": "Advanced" }, "manage_networks": { "title": "Manage networks" diff --git a/src/i18n/es.json b/src/i18n/es.json index e24c9218..0f36fb67 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -143,11 +143,11 @@ }, "general_settings": { "title": "General", - "languages": "Idiomas" + "languages": "Idiomas", + "manage_networks": "Adminsitrar redes" }, "advanced_settings": { - "title": "Avanzado", - "manage_networks": "Adminsitrar redes" + "title": "Avanzado" }, "manage_networks": { "title": "Adminsitrar redes" diff --git a/src/i18n/jp.json b/src/i18n/jp.json index a776d657..67ecb0ca 100644 --- a/src/i18n/jp.json +++ b/src/i18n/jp.json @@ -143,11 +143,11 @@ }, "general_settings": { "title": "一般", - "languages": "言語" + "languages": "言語", + "manage_networks": "ネットワークを追加" }, "advanced_settings": { - "title": "高度な", - "manage_networks": "ネットワークを追加" + "title": "高度な" }, "manage_networks": { "title": "ネットワークを追加" diff --git a/src/pages/balance/components/Settings.tsx b/src/pages/balance/components/Settings.tsx index 0d727a75..de411f81 100644 --- a/src/pages/balance/components/Settings.tsx +++ b/src/pages/balance/components/Settings.tsx @@ -6,10 +6,10 @@ import Extension from "@src/Extension"; import { useTranslation } from "react-i18next"; import { SETTINGS_GENERAL, - SETTINGS_ADVANCED, + //SETTINGS_ADVANCED, SETTINGS_CONTACTS, - SETTINGS_SECURITY, - SETTINGS_BUG, + //SETTINGS_SECURITY, + //SETTINGS_BUG, SIGNIN, } from "@src/routes/paths"; import { ICON_SIZE } from "@src/constants/icons"; @@ -19,22 +19,22 @@ const OPTIONS = [ text: "general", href: SETTINGS_GENERAL, }, - { - text: "advanced", - href: SETTINGS_ADVANCED, - }, + //{ + // text: "advanced", + // href: SETTINGS_ADVANCED, + //}, { text: "contacts", href: SETTINGS_CONTACTS, }, - { - text: "security", - href: SETTINGS_SECURITY, - }, - { - text: "bug_report", - href: SETTINGS_BUG, - }, + //{ + // text: "security", + // href: SETTINGS_SECURITY, + //}, + //{ + // text: "bug_report", + // href: SETTINGS_BUG, + //}, ]; export const Settings = () => { diff --git a/src/pages/settings/Advanced.tsx b/src/pages/settings/Advanced.tsx index 83758a34..14bb010f 100644 --- a/src/pages/settings/Advanced.tsx +++ b/src/pages/settings/Advanced.tsx @@ -8,9 +8,6 @@ import Extension from "@src/Extension"; import { useToast } from "@src/hooks"; import Setting from "@src/storage/entities/settings/Setting"; import { Loading } from "@src/components/common"; -import { SettingKey } from "@src/storage/entities/settings/types"; -import { BsPlus } from "react-icons/bs"; -import { SETTINGS_MANAGE_NETWORKS } from "@src/routes/paths"; export const Advanced = () => { const { t } = useTranslation("advanced_settings"); @@ -52,25 +49,11 @@ export const Advanced = () => {

{t("title")}

{settings.map((setting, index) => { - switch (setting.name) { - case SettingKey.MANAGE_NETWORKS: - return ( -
-

{t(setting.name)}

- -
- ); - } + return ( +
+

{setting.name}

+
+ ); })} ); diff --git a/src/pages/settings/General.tsx b/src/pages/settings/General.tsx index 54e0c2d9..cc2070d5 100644 --- a/src/pages/settings/General.tsx +++ b/src/pages/settings/General.tsx @@ -9,6 +9,8 @@ import { useToast } from "@src/hooks"; import Setting from "@src/storage/entities/settings/Setting"; import { Language, SettingKey } from "@src/storage/entities/settings/types"; import { Loading } from "@src/components/common"; +import { BsGear, BsPlus } from "react-icons/bs"; +import { SETTINGS_MANAGE_NETWORKS } from "@src/routes/paths"; export const General = () => { const { t, i18n } = useTranslation("general_settings"); @@ -89,6 +91,22 @@ export const General = () => {
); + case SettingKey.MANAGE_NETWORKS: + return ( +
+

{t(setting.name)}

+ +
+ ); } })} diff --git a/src/pages/settings/advanced_settings/ManageNetworks.tsx b/src/pages/settings/advanced_settings/ManageNetworks.tsx index ac9847c0..c953b07c 100644 --- a/src/pages/settings/advanced_settings/ManageNetworks.tsx +++ b/src/pages/settings/advanced_settings/ManageNetworks.tsx @@ -6,15 +6,21 @@ import { useTranslation } from "react-i18next"; import { useEffect, useState } from "react"; import { useToast } from "@src/hooks"; import { Loading } from "@src/components/common"; -import { Chain } from "@src/constants/chains"; import Extension from "@src/Extension"; +import Chains from "@src/storage/entities/Chains"; +import { Chain } from "@src/constants/chains"; +import { BsPlus } from "react-icons/bs"; export const ManageNetworks = () => { const { t } = useTranslation("manage_networks"); const { t: tCommon } = useTranslation("common"); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); - const [networks, setNetworks] = useState([] as Chain[]); + const [networks, setNetworks] = useState({} as Chains); + const [isCreating, setIsCreating] = useState(false); + const [selectedNetwork, setSelectedNetwork] = useState( + undefined as Chain | undefined + ); const { showErrorToast } = useToast(); useEffect(() => { @@ -26,23 +32,132 @@ export const ManageNetworks = () => { try { const networks = await Extension.getAllChains(); setNetworks(networks); + setSelectedNetwork(networks.getAll()[0]); } catch (error) { - setNetworks([]); + setNetworks({} as Chains); showErrorToast(tCommon(error as string)); } finally { setIsLoading(false); } }; + const changeNetwork = (chainName: string) => { + const network = networks + .getAll() + .find((network) => network.name === chainName); + setSelectedNetwork(network); + }; + + const isCustom = (chainName: string) => { + return networks.custom.find((network) => network.name === chainName) + ? true + : false; + }; + + const updateName = (value: string) => { + if (!selectedNetwork) return; + const newSelectedNetwork = { ...selectedNetwork }; + newSelectedNetwork.name = value; + setSelectedNetwork(newSelectedNetwork); + }; + + const updateExplorer = async (newExplorer: string, index: number) => { + if (!selectedNetwork) return; + const newSelectedNetwork = { ...selectedNetwork }; + if (!newSelectedNetwork.explorers) { + newSelectedNetwork.explorers = [{ url: "", name: "explorer" }]; + } + newSelectedNetwork.explorers[index].url = newExplorer; + setSelectedNetwork(newSelectedNetwork as Chain); + }; + + const updateNativeCurrency = async (key: string, value: string) => { + if (!selectedNetwork) return; + const newSelectedNetwork = { ...selectedNetwork }; + if (!newSelectedNetwork.nativeCurrency) { + newSelectedNetwork.nativeCurrency = { + name: "", + symbol: "", + decimals: 0, + }; + } + newSelectedNetwork.nativeCurrency[key] = + key === "decimals" ? parseInt(value) : value; + setSelectedNetwork(newSelectedNetwork as Chain); + }; + + const updateAddressPrefix = async (value: string) => { + if (!selectedNetwork) return; + const newSelectedNetwork = { ...selectedNetwork }; + if (!newSelectedNetwork.addressPrefix) { + newSelectedNetwork.addressPrefix = 0; + } + newSelectedNetwork.addressPrefix = parseInt(value); + setSelectedNetwork(newSelectedNetwork as Chain); + }; + + const updateChain = async (value: string) => { + if (!selectedNetwork) return; + const newSelectedNetwork = { ...selectedNetwork }; + if (!newSelectedNetwork.chain) { + newSelectedNetwork.chain = ""; + } + newSelectedNetwork.chain = value; + setSelectedNetwork(newSelectedNetwork as Chain); + }; + + const updateRPC = async (key: string, value: string) => { + if (!selectedNetwork) return; + const newSelectedNetwork = { ...selectedNetwork }; + if (!newSelectedNetwork.rpc) { + newSelectedNetwork.rpc = { evm: "", wasm: "" }; + } + newSelectedNetwork.rpc[key] = value; + setSelectedNetwork(newSelectedNetwork as Chain); + }; + + const saveNetwork = async () => { + if (!selectedNetwork) return; + try { + await Extension.saveCustomChain(selectedNetwork); + getNetworks(); + setIsCreating(false); + } catch (error) { + showErrorToast(tCommon(error as string)); + } + }; + + const cancel = () => { + changeNetwork(networks.mainnets[0].name); + setIsCreating(false); + }; + const deleteNetwork = async (chainName: string) => { try { - //await Extension.removeChain(chainName); + await Extension.removeCustomChain(chainName); getNetworks(); } catch (error) { showErrorToast(tCommon(error as string)); } }; + const newNetwork = async () => { + setIsCreating(true); + const newCustomNetwork: Chain = { + name: "New Network", + chain: "", + rpc: { evm: "", wasm: "" }, + addressPrefix: 0, + nativeCurrency: { + name: "", + symbol: "", + decimals: 0, + }, + explorers: [{ url: "", name: "explorer" }], + } as Chain; + setSelectedNetwork(newCustomNetwork); + }; + if (isLoading) { return ( @@ -62,24 +177,166 @@ export const ManageNetworks = () => { onClick={() => navigate(-1)} />

{t("title")}

-
- {networks && - networks.map((network, index) => { - return ( -
+ +
+ )} + + + {isCreating ? ( + <> + +
+ updateName(e.target.value)} + /> +
+ + ) : ( + + )} + {selectedNetwork && ( + <> + +
+ updateChain(e.target.value)} + readOnly={!isCustom(selectedNetwork.name) && !isCreating} + /> +
+ +
+ updateAddressPrefix(e.target.value)} + readOnly={!isCustom(selectedNetwork.name) && !isCreating} + /> +
+ + {selectedNetwork.nativeCurrency && ( + <> + + {Object.keys(selectedNetwork.nativeCurrency).map((key, index) => { + const value = selectedNetwork.nativeCurrency[key]; + return ( +
+
+ {key} +
+ + updateNativeCurrency(key, e.target.value) + } + readOnly={!isCustom(selectedNetwork.name) && !isCreating} + /> +
+ ); + })} + + )} + + {selectedNetwork.rpc && ( + <> + + {Object.keys(selectedNetwork.rpc).map((key, index) => { + const rpc = selectedNetwork.rpc[key]; + return ( +
+ updateRPC(key, e.target.value)} + readOnly={!isCustom(selectedNetwork.name) && !isCreating} + /> +
+ ); + })} + + )} + + {selectedNetwork.explorers && ( + <> + + {selectedNetwork.explorers.map((key, index) => { + return ( +
+ updateExplorer(e.target.value, index)} + readOnly={!isCustom(selectedNetwork.name) && !isCreating} + /> +
+ ); + })} + + )} + + {(isCustom(selectedNetwork.name) || isCreating) && ( +
+
- ); - })} + )} + + )}
); }; diff --git a/src/storage/entities/Chains.ts b/src/storage/entities/Chains.ts index 39ac6d25..a9a9533d 100644 --- a/src/storage/entities/Chains.ts +++ b/src/storage/entities/Chains.ts @@ -1,16 +1,11 @@ import { Chain, MAINNETS, PARACHAINS, TESTNETS } from "@src/constants/chains"; import BaseEntity from "./BaseEntity"; -export enum ChainType { - MAINNET = "mainnets", - PARACHAIN = "parachains", - TESTNET = "testnets", -} - export default class Chains extends BaseEntity { mainnets: Chain[]; parachains: Chain[]; testnets: Chain[]; + custom: Chain[]; private static instance: Chains; @@ -19,6 +14,7 @@ export default class Chains extends BaseEntity { this.mainnets = MAINNETS; this.parachains = PARACHAINS; this.testnets = TESTNETS; + this.custom = []; } public static getInstance() { @@ -47,44 +43,24 @@ export default class Chains extends BaseEntity { chains.mainnets = stored.mainnets; chains.parachains = stored.parachains; chains.testnets = stored.testnets; + chains.custom = stored.custom; } - static async saveCustomChain(chain: Chain, chainType: ChainType) { + static async saveCustomChain(chain: Chain) { const chains = await Chains.get(); if (!chains) throw new Error("failed_to_save_custom_chain"); + console.log("chains", chains); + console.log("chain", chain); if (chains.isAlreadyAdded(chain)) throw new Error("chain_already_added"); - switch (chainType) { - case ChainType.MAINNET: - chains.addMainnet(chain); - break; - case ChainType.PARACHAIN: - chains.addParachain(chain); - break; - case ChainType.TESTNET: - chains.addTestnet(chain); - break; - default: - throw new Error("invalid_chain_type"); - } + chains.custom = [...chains.custom, chain]; + console.log("chains updated", chains) await Chains.set(chains); } - static async removeCustomChain(chain: Chain, chainType: ChainType) { + static async removeCustomChain(chainName: string) { const chains = await Chains.get(); if (!chains) throw new Error("failed_to_remove_custom_chain"); - switch (chainType) { - case ChainType.MAINNET: - chains.removeMainnet(chain); - break; - case ChainType.PARACHAIN: - chains.removeParachain(chain); - break; - case ChainType.TESTNET: - chains.removeTestnet(chain); - break; - default: - throw new Error("invalid_chain_type"); - } + chains.custom = chains.custom.filter((c) => c.name !== chainName); await Chains.set(chains); } @@ -97,68 +73,26 @@ export default class Chains extends BaseEntity { if (parachain) return parachain; const testnet = chains.testnets.find((c) => c.name === chainName); if (testnet) return testnet; + const custom = chains.custom.find((c) => c.name === chainName); + if (custom) return custom; return undefined; } - get() { - return { - mainnets: this.mainnets, - parachains: this.parachains, - testnets: this.testnets, - }; - } - getAll() { - return [...this.mainnets, ...this.parachains, ...this.testnets]; - } - - set(mainnets: Chain[], parachains: Chain[], testnets: Chain[]) { - this.mainnets = mainnets; - this.parachains = parachains; - this.testnets = testnets; - } - - addMainnet(chain: Chain) { - this.mainnets.push(chain); - } - - addParachain(chain: Chain) { - this.parachains.push(chain); - } - - addTestnet(chain: Chain) { - this.testnets.push(chain); - } - - removeMainnet(chain: Chain) { - this.mainnets = this.mainnets.filter((c) => c !== chain); - } - - removeParachain(chain: Chain) { - this.parachains = this.parachains.filter((c) => c !== chain); - } - - removeTestnet(chain: Chain) { - this.testnets = this.testnets.filter((c) => c !== chain); - } - - getMainnets() { - return this.mainnets; - } - - getParachains() { - return this.parachains; - } - - getTestnets() { - return this.testnets; + return [ + ...this.mainnets, + ...this.parachains, + ...this.testnets, + ...this.custom, + ]; } isAlreadyAdded(chain: Chain) { return ( this.mainnets.some((c) => c.name === chain.name) || this.parachains.some((c) => c.name === chain.name) || - this.testnets.some((c) => c.name === chain.name) + this.testnets.some((c) => c.name === chain.name) || + this.custom.some((c) => c.name === chain.name) ); } } diff --git a/src/storage/entities/settings/Settings.ts b/src/storage/entities/settings/Settings.ts index 011c24db..030d3b27 100644 --- a/src/storage/entities/settings/Settings.ts +++ b/src/storage/entities/settings/Settings.ts @@ -26,7 +26,7 @@ export default class Settings extends BaseEntity { LanguageSetting.getSupportedLanguages() ); // this setting does not have a value (the true is just a placeholder) - settings.addToAdvanced(SettingKey.MANAGE_NETWORKS, true); + settings.addToGeneral(SettingKey.MANAGE_NETWORKS, true); await Settings.set(settings); } From 64c28666e5fd0dea453d1fdc230c917515aa5d9f Mon Sep 17 00:00:00 2001 From: Fernando Sirni Date: Thu, 16 Mar 2023 02:13:10 +0900 Subject: [PATCH 009/251] using form (not working) --- src/constants/chains.ts | 129 ++++---- src/i18n/en.json | 7 +- src/i18n/es.json | 7 +- src/i18n/jp.json | 7 +- src/pages/balance/components/Activity.tsx | 8 +- src/pages/settings/Contacts.tsx | 18 +- .../advanced_settings/ManageNetworks.tsx | 288 ++++++++---------- src/storage/entities/Chains.ts | 32 +- 8 files changed, 261 insertions(+), 235 deletions(-) diff --git a/src/constants/chains.ts b/src/constants/chains.ts index 73c1f770..20d15cf8 100644 --- a/src/constants/chains.ts +++ b/src/constants/chains.ts @@ -1,32 +1,12 @@ import { AccountType } from "@src/accounts/types"; +import { Chain } from "@src/storage/entities/Chains"; const WASM = "WASM" as AccountType.WASM; const EVM = "EVM" as AccountType.EVM; -export interface Chain { - name: string; - chain?: string; - addressPrefix?: number; - rpc: { - wasm?: string; - evm?: string; - }; - nativeCurrency: { - name: string; - symbol: string; - decimals: number; - }; - explorers: { - name: string; - url: string; - }[]; - supportedAccounts: AccountType[]; - xcm?: string[]; -} export const MAINNETS: Chain[] = [ { name: "Polkadot", - chain: "substrate", rpc: { wasm: "wss://rpc.polkadot.io" }, addressPrefix: 0, nativeCurrency: { @@ -34,17 +14,17 @@ export const MAINNETS: Chain[] = [ symbol: "DOT", decimals: 10, }, - explorers: [ - { + logo: "polkadot", + explorer: { + wasm: { name: "subscan", url: "https://polkadot.subscan.io/", }, - ], + }, supportedAccounts: [WASM], }, { name: "Kusama", - chain: "substrate", rpc: { wasm: "wss://kusama-rpc.polkadot.io" }, addressPrefix: 2, nativeCurrency: { @@ -52,17 +32,17 @@ export const MAINNETS: Chain[] = [ symbol: "KSM", decimals: 12, }, - explorers: [ - { + logo: "kusama", + explorer: { + wasm: { name: "subscanß", url: "https://kusama.subscan.io/", }, - ], + }, supportedAccounts: [WASM], }, { name: "Ethereum", - chain: "ethereum", rpc: { evm: "https://eth.llamarpc.com" }, addressPrefix: 1, nativeCurrency: { @@ -70,12 +50,13 @@ export const MAINNETS: Chain[] = [ symbol: "ETH", decimals: 18, }, - explorers: [ - { + explorer: { + evm: { name: "etherscan", url: "https://etherscan.io/", }, - ], + }, + logo: "ethereum", supportedAccounts: [EVM], }, ]; @@ -83,7 +64,6 @@ export const MAINNETS: Chain[] = [ export const PARACHAINS: Chain[] = [ { name: "Astar", - chain: "ASTR", rpc: { evm: "https://evm.astar.network", wasm: "wss://rpc.astar.network", @@ -94,17 +74,21 @@ export const PARACHAINS: Chain[] = [ symbol: "ASTR", decimals: 18, }, - explorers: [ - { + explorer: { + evm: { name: "subscan", url: "https://astar.subscan.io/", }, - ], + wasm: { + name: "subscan", + url: "https://astar.subscan.io/", + }, + }, + logo: "astar", supportedAccounts: [WASM, EVM], }, { name: "Moonbeam", - chain: "MOON", rpc: { evm: "https://rpc.api.moonbeam.network", wasm: "wss://wss.api.moonbeam.network", @@ -115,18 +99,22 @@ export const PARACHAINS: Chain[] = [ symbol: "GLMR", decimals: 18, }, - explorers: [ - { + explorer: { + evm: { name: "moonscan", url: "https://moonbeam.moonscan.io/", }, - ], + wasm: { + name: "moonscan", + url: "https://moonbeam.moonscan.io/", + }, + }, + logo: "moonbeam", supportedAccounts: [EVM, WASM], }, { name: "Moonriver", - chain: "MOON", rpc: { evm: "https://rpc.api.moonriver.moonbeam.network", wasm: "wss://wss.api.moonriver.moonbeam.network", @@ -138,17 +126,17 @@ export const PARACHAINS: Chain[] = [ decimals: 18, }, - explorers: [ - { + explorer: { + evm: { name: "moonscan", url: "https://moonriver.moonscan.io/", }, - ], + }, + logo: "moonriver", supportedAccounts: [EVM], }, { name: "Shiden", - chain: "SDN", rpc: { evm: "https://evm.shiden.astar.network", wasm: "wss://shiden.api.onfinality.io/public-ws", @@ -159,12 +147,17 @@ export const PARACHAINS: Chain[] = [ symbol: "SDN", decimals: 18, }, - explorers: [ - { + explorer: { + evm: { + name: "subscan", + url: "https://shiden.subscan.io/", + }, + wasm: { name: "subscan", url: "https://shiden.subscan.io/", }, - ], + }, + logo: "shiden", supportedAccounts: [EVM, WASM], }, ]; @@ -172,7 +165,6 @@ export const PARACHAINS: Chain[] = [ export const TESTNETS: Chain[] = [ { name: "Moonbase Alpha", - chain: "MOON", rpc: { evm: "https://rpc.api.moonbase.moonbeam.network", wasm: "wss://wss.api.moonbase.moonbeam.network", @@ -183,17 +175,21 @@ export const TESTNETS: Chain[] = [ symbol: "DEV", decimals: 18, }, - explorers: [ - { + explorer: { + evm: { name: "moonscan", url: "https://moonbase.moonscan.io/", }, - ], + wasm: { + name: "moonscan", + url: "https://moonbase.moonscan.io/", + }, + }, + logo: "moonbase", supportedAccounts: [EVM, WASM], }, { name: "Shibuya", - chain: "substrate", rpc: { evm: "https://evm.shibuya.astar.network", wasm: "wss://shibuya-rpc.dwellir.com", @@ -205,17 +201,21 @@ export const TESTNETS: Chain[] = [ }, addressPrefix: 5, - explorers: [ - { + explorer: { + evm: { name: "subscan", url: "https://shibuya.subscan.io/", }, - ], + wasm: { + name: "subscan", + url: "https://shibuya.subscan.io/", + }, + }, + logo: "shibuya", supportedAccounts: [EVM, WASM], }, { name: "Contracts Testnet", - chain: "Rococo", rpc: { wasm: "wss://rococo-rockmine-rpc.polkadot.io", }, @@ -226,17 +226,17 @@ export const TESTNETS: Chain[] = [ symbol: "ROC", decimals: 12, }, - explorers: [ - { + explorer: { + wasm: { name: "", url: "https://rockmine.subscan.io/", }, - ], + }, + logo: "rococo", supportedAccounts: [WASM], }, { name: "Goerli", - chain: "goerli", rpc: { evm: "https://goerli.infura.io/v3/" }, addressPrefix: 5, nativeCurrency: { @@ -244,12 +244,13 @@ export const TESTNETS: Chain[] = [ symbol: "GoerliETH", decimals: 18, }, - explorers: [ - { + explorer: { + evm: { name: "etherscan", url: "https://goerli.etherscan.io/", }, - ], + }, + logo: "goerli", supportedAccounts: [EVM], }, ]; diff --git a/src/i18n/en.json b/src/i18n/en.json index d79ff973..81fd6a5a 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -47,7 +47,9 @@ "address_required": "Address is required", "view_in_scanner": "Explore", "add": "Add", - "manage": "Manage" + "manage": "Manage", + "cancel": "Cancel", + "save": "Save" }, "welcome": { "welcome_message": "Welcome to Kuma Wallet", @@ -159,7 +161,8 @@ "save_contact": "Save contact", "insert_name": "Insert contact name", "insert_address": "Insert address", - "search": "Search contacts by name or address" + "search": "Search contacts by name or address", + "new_contact": "New contact" }, "activity": { "title": "Activity", diff --git a/src/i18n/es.json b/src/i18n/es.json index 0f36fb67..64921817 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -47,7 +47,9 @@ "address_required": "Dirección requerida", "view_in_scanner": "Ver en el escáner", "add": "Añadir", - "manage": "Administrar" + "manage": "Administrar", + "cancel": "Cancelar", + "save": "Guardar" }, "welcome": { "welcome_message": "Bienvenido a Kuma Wallet", @@ -159,7 +161,8 @@ "save_contact": "Guardar contacto", "insert_name": "Ingrese el nombre del contacto", "insert_address": "Ingrese la dirección del contacto", - "search": "Buscar contactos por nombre o dirección" + "search": "Buscar contactos por nombre o dirección", + "new_contact": "Nuevo contacto" }, "activity": { "title": "Actividad", diff --git a/src/i18n/jp.json b/src/i18n/jp.json index 67ecb0ca..e078847d 100644 --- a/src/i18n/jp.json +++ b/src/i18n/jp.json @@ -47,7 +47,9 @@ "address_required": "住所が必要です", "view_in_scanner": "スキャナーで表示", "add": "追加", - "manage": "管理" + "manage": "管理", + "cancel": "キャンセル", + "save": "保存" }, "welcome": { "welcome_message": "Kuma Walletへようこそ", @@ -159,7 +161,8 @@ "save_contact": "連絡先を保存する", "insert_name": "連絡先の名前を入力してください", "insert_address": "連絡先の住所を入力してください", - "search": "名前または住所で連絡先を検索する" + "search": "名前または住所で連絡先を検索する", + "new_contact": "新しい連絡先" }, "activity": { "title": "アクティビティ", diff --git a/src/pages/balance/components/Activity.tsx b/src/pages/balance/components/Activity.tsx index e5578ae2..60030a54 100644 --- a/src/pages/balance/components/Activity.tsx +++ b/src/pages/balance/components/Activity.tsx @@ -58,15 +58,15 @@ export const Activity = () => { }; const getLink = (network: string, hash: string) => { - const { explorers } = + const { explorer } = CHAINS.flatMap((chainType) => chainType.chains).find( (chain) => chain.name.toLowerCase() === network.toLowerCase() ) || {}; - const { url } = explorers?.[0] || {}; + const { evm, wasm } = explorer || {}; if (type.toLowerCase() === "wasm") { - return `${url}extrinsic/${hash}`; + return `${wasm?.url}extrinsic/${hash}`; } else { - return `${url}tx/${hash}`; + return `${evm?.url}tx/${hash}`; } }; diff --git a/src/pages/settings/Contacts.tsx b/src/pages/settings/Contacts.tsx index 2ab0ac62..b9c40f64 100644 --- a/src/pages/settings/Contacts.tsx +++ b/src/pages/settings/Contacts.tsx @@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"; import { useToast } from "@src/hooks"; import Extension from "@src/Extension"; import { Loading } from "@src/components/common"; -import { BsPlus, BsTrash } from "react-icons/bs"; +import { BsTrash } from "react-icons/bs"; import { useForm } from "react-hook-form"; import { object, string } from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; @@ -106,11 +106,10 @@ export const Contacts = () => {
)} @@ -140,10 +139,17 @@ export const Contacts = () => {
+
diff --git a/src/pages/settings/advanced_settings/ManageNetworks.tsx b/src/pages/settings/advanced_settings/ManageNetworks.tsx index c953b07c..b109a827 100644 --- a/src/pages/settings/advanced_settings/ManageNetworks.tsx +++ b/src/pages/settings/advanced_settings/ManageNetworks.tsx @@ -7,9 +7,10 @@ import { useEffect, useState } from "react"; import { useToast } from "@src/hooks"; import { Loading } from "@src/components/common"; import Extension from "@src/Extension"; -import Chains from "@src/storage/entities/Chains"; -import { Chain } from "@src/constants/chains"; -import { BsPlus } from "react-icons/bs"; +import Chains, { Chain } from "@src/storage/entities/Chains"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { object, string, number } from "yup"; export const ManageNetworks = () => { const { t } = useTranslation("manage_networks"); @@ -46,6 +47,15 @@ export const ManageNetworks = () => { .getAll() .find((network) => network.name === chainName); setSelectedNetwork(network); + setValue("name", network?.name || ""); + setValue("addressPrefix", network?.addressPrefix); + setValue("explorer", network?.explorer || {}); + setValue("logo", network?.logo || ""); + setValue( + "nativeCurrency", + network?.nativeCurrency || { name: "", symbol: "", decimals: 0 } + ); + setValue("rpc", network?.rpc || { evm: "", wasm: "" }); }; const isCustom = (chainName: string) => { @@ -54,78 +64,54 @@ export const ManageNetworks = () => { : false; }; - const updateName = (value: string) => { - if (!selectedNetwork) return; - const newSelectedNetwork = { ...selectedNetwork }; - newSelectedNetwork.name = value; - setSelectedNetwork(newSelectedNetwork); - }; - - const updateExplorer = async (newExplorer: string, index: number) => { - if (!selectedNetwork) return; - const newSelectedNetwork = { ...selectedNetwork }; - if (!newSelectedNetwork.explorers) { - newSelectedNetwork.explorers = [{ url: "", name: "explorer" }]; - } - newSelectedNetwork.explorers[index].url = newExplorer; - setSelectedNetwork(newSelectedNetwork as Chain); - }; + const schema = object({ + name: string().required(), + chain: string().optional(), + addressPrefix: number().optional(), + rpc: object().shape({ + wasm: string().optional(), + evm: string().optional(), + }), + nativeCurrency: object().shape({ + name: string().required(), + symbol: string().required(), + decimals: number().required(), + }), + explorer: object().shape({ + name: string().required(), + url: string().required(), + }), + }).required(); - const updateNativeCurrency = async (key: string, value: string) => { - if (!selectedNetwork) return; - const newSelectedNetwork = { ...selectedNetwork }; - if (!newSelectedNetwork.nativeCurrency) { - newSelectedNetwork.nativeCurrency = { + const { + register, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ + defaultValues: { + name: "New Network", + rpc: { evm: "", wasm: "" }, + addressPrefix: 0, + nativeCurrency: { name: "", symbol: "", decimals: 0, - }; - } - newSelectedNetwork.nativeCurrency[key] = - key === "decimals" ? parseInt(value) : value; - setSelectedNetwork(newSelectedNetwork as Chain); - }; - - const updateAddressPrefix = async (value: string) => { - if (!selectedNetwork) return; - const newSelectedNetwork = { ...selectedNetwork }; - if (!newSelectedNetwork.addressPrefix) { - newSelectedNetwork.addressPrefix = 0; - } - newSelectedNetwork.addressPrefix = parseInt(value); - setSelectedNetwork(newSelectedNetwork as Chain); - }; - - const updateChain = async (value: string) => { - if (!selectedNetwork) return; - const newSelectedNetwork = { ...selectedNetwork }; - if (!newSelectedNetwork.chain) { - newSelectedNetwork.chain = ""; - } - newSelectedNetwork.chain = value; - setSelectedNetwork(newSelectedNetwork as Chain); - }; - - const updateRPC = async (key: string, value: string) => { - if (!selectedNetwork) return; - const newSelectedNetwork = { ...selectedNetwork }; - if (!newSelectedNetwork.rpc) { - newSelectedNetwork.rpc = { evm: "", wasm: "" }; - } - newSelectedNetwork.rpc[key] = value; - setSelectedNetwork(newSelectedNetwork as Chain); - }; + }, + explorer: {}, + }, + resolver: yupResolver(schema), + }); - const saveNetwork = async () => { - if (!selectedNetwork) return; + const _onSubmit = handleSubmit(async (data) => { try { - await Extension.saveCustomChain(selectedNetwork); + await Extension.saveCustomChain(data); getNetworks(); setIsCreating(false); } catch (error) { showErrorToast(tCommon(error as string)); } - }; + }); const cancel = () => { changeNetwork(networks.mainnets[0].name); @@ -141,23 +127,6 @@ export const ManageNetworks = () => { } }; - const newNetwork = async () => { - setIsCreating(true); - const newCustomNetwork: Chain = { - name: "New Network", - chain: "", - rpc: { evm: "", wasm: "" }, - addressPrefix: 0, - nativeCurrency: { - name: "", - symbol: "", - decimals: 0, - }, - explorers: [{ url: "", name: "explorer" }], - } as Chain; - setSelectedNetwork(newCustomNetwork); - }; - if (isLoading) { return ( @@ -182,7 +151,7 @@ export const ManageNetworks = () => { @@ -198,19 +167,14 @@ export const ManageNetworks = () => {
updateName(e.target.value)} + {...register("name")} />
) : ( updateChain(e.target.value)} - readOnly={!isCustom(selectedNetwork.name) && !isCreating} - /> - -