From 7f968e18c040934dcc60e40bbcbab8581b4ad4f8 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 22 Feb 2023 17:07:46 +0800 Subject: [PATCH 01/36] Added multisig account migration home page and connect wallet functionality --- packages/app-locale/src/localeKeys.ts | 3 + packages/app-locale/src/translations/enUS.ts | 5 +- packages/app-providers/src/walletProvider.tsx | 7 ++ packages/app-types/src/wallet.ts | 2 + packages/app/src/Root.tsx | 27 ++++- packages/app/src/assets/images/no-data.svg | 9 ++ packages/app/src/components/Header/index.tsx | 48 ++++---- .../MultisigMigrationProcess/index.tsx | 114 ++++++++++++++++++ .../MultisigMigrationStatus/index.tsx | 112 +++++++++++++++++ .../app/src/components/Protected/index.tsx | 3 +- packages/app/src/pages/Home.tsx | 4 + packages/app/src/pages/MultisigHome.tsx | 38 ++++++ packages/app/src/pages/MultisigMigration.tsx | 57 +++++++++ packages/app/src/routes/index.tsx | 12 ++ 14 files changed, 415 insertions(+), 26 deletions(-) create mode 100644 packages/app/src/assets/images/no-data.svg create mode 100644 packages/app/src/components/MultisigMigrationProcess/index.tsx create mode 100644 packages/app/src/components/MultisigMigrationStatus/index.tsx create mode 100644 packages/app/src/pages/MultisigHome.tsx create mode 100644 packages/app/src/pages/MultisigMigration.tsx diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index 59c4b42..45c7459 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -55,4 +55,7 @@ export const localeKeys = { evmAccountNotFree: "evmAccountNotFree", noTokensToMigrate: "noTokensToMigrate", installWalletReminder: "installWalletReminder", + multisig: "multisig", + noMultisigAccounts: "noMultisigAccounts", + addMultisigAccount: "addMultisigAccount", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index 2adc7b7..2c1102d 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -9,7 +9,7 @@ const enUs = { [localeKeys.darwiniaNetwork]: "Darwinia Network", [localeKeys.connectWallet]: "Connect wallet", [localeKeys.accountMigrationTitle]: `Darwinia 2.0 Account Migration`, - [localeKeys.accountMigrationInfo]: `Use account migration after Darwinia 2.0 upgrade to move your {{ringSymbol}}s and {{ktonSymbol}}s from the substrate account to your EVM account. Connect your wallet to view available migrations on your account.`, + [localeKeys.accountMigrationInfo]: `Use account migration after Darwinia 2.0 upgrade to move your {{ringSymbol}}s and {{ktonSymbol}}s from the substrate account to your EVM account. Connect your wallet to view available migrations on your account.Click here for the migration of multisig accounts.`, [localeKeys.bonded]: "Bonded", [localeKeys.selectAccount]: "Select account", [localeKeys.migrationSummaryInfo]: @@ -55,6 +55,9 @@ const enUs = { [localeKeys.evmAccountNotFree]: "This EVM account is not free", [localeKeys.noTokensToMigrate]: `Selected account has no tokens to migrate`, [localeKeys.installWalletReminder]: `Connection failed. Please install {{walletName}} here.`, + [localeKeys.multisig]: "Multisig", + [localeKeys.noMultisigAccounts]: "No data", + [localeKeys.addMultisigAccount]: "Add Multisig", }; export default enUs; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index 79dc7ec..38cacd9 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -37,6 +37,7 @@ const initialState: WalletCtx = { isLoadingTransaction: undefined, isAccountMigratedJustNow: undefined, walletConfig: undefined, + isMultisig: undefined, changeSelectedNetwork: () => { // do nothing }, @@ -56,6 +57,9 @@ const initialState: WalletCtx = { //do nothing return Promise.resolve(true); }, + setMultisig: (value: boolean) => { + //ignore + }, }; const WalletContext = createContext(initialState); @@ -79,6 +83,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const isKeyringInitialized = useRef(false); const [isAccountMigratedJustNow, setAccountMigratedJustNow] = useState(false); const [specName, setSpecName] = useState(); + const [isMultisig, setMultisig] = useState(false); const isWalletInstalled = () => { const injectedWallet = window.injectedWeb3; @@ -378,6 +383,8 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { selectedNetwork, forceSetAccountAddress, onInitMigration, + isMultisig, + setMultisig, }} > {children} diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index 0aeae82..118f97a 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -68,6 +68,8 @@ export interface WalletCtx { onInitMigration: (from: string, to: string, callback: (isSuccessful: boolean) => void) => void; isAccountMigratedJustNow: boolean | undefined; walletConfig: WalletConfig | undefined; + isMultisig: boolean | undefined; + setMultisig: (value: boolean) => void; } export interface SpVersionRuntimeVersion extends Struct { diff --git a/packages/app/src/Root.tsx b/packages/app/src/Root.tsx index 3da33f1..bf3c277 100644 --- a/packages/app/src/Root.tsx +++ b/packages/app/src/Root.tsx @@ -16,6 +16,7 @@ const Root = () => { selectedNetwork, isLoadingTransaction, walletConfig, + setMultisig, } = useWallet(); const [loading, setLoading] = useState(false); const navigate = useNavigate(); @@ -29,23 +30,45 @@ const Root = () => { const redirect = useCallback(() => { setStore("isConnectedToWallet", true); if (location.pathname === "/") { + if (setMultisig) { + setMultisig(false); + } navigate(`/migration${location.search}`, { replace: true }); return; } + if (location.pathname === "/multisig-home") { + if (setMultisig) { + setMultisig(true); + } + navigate(`/multisig-migration${location.search}`, { replace: true }); + return; + } + /* only navigate if the user is supposed to be redirected to another URL */ if (location.state && location.state.from) { const nextPath = location.state.from.pathname ? location.state.from.pathname : "/migration"; navigate(`${nextPath}${location.search}`, { replace: true }); } - }, [location, navigate]); + }, [location, navigate, setMultisig]); /*Monitor wallet connection and redirect to the required location */ useEffect(() => { if (isWalletConnected) { redirect(); + } else { + // the wallet isn't connected + if (location.pathname === "/") { + if (setMultisig) { + setMultisig(false); + } + } else if (location.pathname === "/multisig-home") { + if (setMultisig) { + setMultisig(true); + } + } } - }, [isWalletConnected]); + }, [isWalletConnected, location]); useEffect(() => { if (error) { diff --git a/packages/app/src/assets/images/no-data.svg b/packages/app/src/assets/images/no-data.svg new file mode 100644 index 0000000..8c13a9e --- /dev/null +++ b/packages/app/src/assets/images/no-data.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/app/src/components/Header/index.tsx b/packages/app/src/components/Header/index.tsx index f8fe740..044b331 100644 --- a/packages/app/src/components/Header/index.tsx +++ b/packages/app/src/components/Header/index.tsx @@ -14,7 +14,7 @@ import SelectAccountModal, { SelectAccountModalRef } from "../SelectAccountModal const Header = () => { const [networkOptionsTrigger, setNetworkOptionsTrigger] = useState(null); const { t } = useAppTranslation(); - const { selectedNetwork, changeSelectedNetwork, selectedAccount, connectWallet, forceSetAccountAddress } = + const { selectedNetwork, changeSelectedNetwork, selectedAccount, connectWallet, forceSetAccountAddress, isMultisig } = useWallet(); const [searchParams, setSearchParams] = useSearchParams(); const location = useLocation(); @@ -119,29 +119,33 @@ const Header = () => { ); })} - {selectedAccount ? ( -
-
- -
-
{selectedAccount.prettyName ?? toShortAddress(selectedAccount.address)}
- image + {!isMultisig && ( +
+ {selectedAccount ? ( +
+
+ +
+
{selectedAccount.prettyName ?? toShortAddress(selectedAccount.address)}
+ image +
+
-
+ ) : ( + + )}
- ) : ( - )}
{/*network switch toggle*/} diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx new file mode 100644 index 0000000..b0afeee --- /dev/null +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -0,0 +1,114 @@ +import { useStorage, useWallet } from "@darwinia/app-providers"; +import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; +import Identicon from "@polkadot/react-identicon"; +import MigrationSummary from "../MigrationSummary"; +import MigrationForm from "../MigrationForm"; +import { useEffect, useRef, useState } from "react"; +import BigNumber from "bignumber.js"; +import { Button, notification } from "@darwinia/ui"; +import { CustomInjectedAccountWithMeta } from "@darwinia/app-types"; +import noDataIcon from "../../assets/images/no-data.svg"; + +interface Props { + isCheckingMigrationStatus: boolean; +} + +const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { + const { selectedAccount } = useWallet(); + const { migrationAssetDistribution, isLoadingLedger } = useStorage(); + const { t } = useAppTranslation(); + const [showMigrationForm, setShowMigrationForm] = useState(false); + const currentAccount = useRef(); + const canShowAccountNotification = useRef(false); + + useEffect(() => { + if (currentAccount.current?.address !== selectedAccount?.address) { + currentAccount.current = selectedAccount; + canShowAccountNotification.current = true; + } + }, [selectedAccount]); + + useEffect(() => { + if (migrationAssetDistribution && !isLoadingLedger) { + const hasRingAmount = + migrationAssetDistribution.ring.transferable.gt(0) || + migrationAssetDistribution.ring.deposit?.gt(0) || + migrationAssetDistribution.ring.bonded.gt(0) || + migrationAssetDistribution.ring.unbonded.gt(0) || + migrationAssetDistribution.ring.unbonding.gt(0) || + migrationAssetDistribution.ring.vested?.gt(0); + const hasKtonAmount = + migrationAssetDistribution.kton.transferable.gt(0) || + migrationAssetDistribution.kton.bonded.gt(0) || + migrationAssetDistribution.kton.unbonded.gt(0) || + migrationAssetDistribution.kton.unbonding.gt(0); + if (hasRingAmount || hasKtonAmount) { + setShowMigrationForm(true); + } else { + // makes sure that the prompt is only shown once when the selected account changes + if (canShowAccountNotification.current) { + canShowAccountNotification.current = false; + notification.error({ + message:
{t(localeKeys.noTokensToMigrate)}
, + }); + } + setShowMigrationForm(false); + } + } + }, [migrationAssetDistribution, isLoadingLedger]); + + const footerLinks = [ + { + title: t(localeKeys.howToMigrate), + url: "https://www.baidu.com", + }, + { + title: t(localeKeys.darwiniaMergeOverview), + url: "https://medium.com/darwinianetwork/darwinia-2-0-merge-overview-96af96d668aa", + }, + { + title: t(localeKeys.darwiniaDataMigration), + url: "https://medium.com/darwinianetwork/darwinia-2-0-blockchain-data-migration-c1186338c743", + }, + ]; + + const onShowAddAccountModal = () => { + console.log("here we go======"); + }; + + return ( +
+
+
+
{t(localeKeys.multisig)}
+
+
+
+ noDataIcon +
{t(localeKeys.noMultisigAccounts)}
+ +
+
+
+
+ {footerLinks.map((item, index) => { + return ( + + {item.title} + + ); + })} +
+
+ ); +}; + +export default MultisigMigrationProcess; diff --git a/packages/app/src/components/MultisigMigrationStatus/index.tsx b/packages/app/src/components/MultisigMigrationStatus/index.tsx new file mode 100644 index 0000000..e0fc690 --- /dev/null +++ b/packages/app/src/components/MultisigMigrationStatus/index.tsx @@ -0,0 +1,112 @@ +import migrationIcon from "../../assets/images/migration.svg"; +import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; +import successIcon from "../../assets/images/success.svg"; +import copyIcon from "../../assets/images/copy.svg"; +import clockIcon from "../../assets/images/clock.svg"; +import ringIcon from "../../assets/images/ring.svg"; +import ktonIcon from "../../assets/images/kton.svg"; +import helpIcon from "../../assets/images/help.svg"; +import { Tooltip } from "@darwinia/ui"; +import { useStorage, useWallet } from "@darwinia/app-providers"; +import { AccountMigration } from "../../pages/Migration"; +import { useEffect } from "react"; +import BigNumber from "bignumber.js"; +import { + copyToClipboard, + formatTimeInUTC, + prettifyNumber, + prettifyTooltipNumber, + toTimeAgo, +} from "@darwinia/app-utils"; + +interface Props { + accountMigration?: AccountMigration; +} + +const MultisigMigrationStatus = ({ accountMigration }: Props) => { + const { t } = useAppTranslation(); + const { selectedNetwork, setTransactionStatus } = useWallet(); + const { retrieveMigratedAsset, migratedAssetDistribution, isLoadingMigratedLedger } = useStorage(); + + useEffect(() => { + setTransactionStatus(!!isLoadingMigratedLedger); + }, [isLoadingMigratedLedger]); + + useEffect(() => { + if (accountMigration) { + retrieveMigratedAsset(accountMigration.id, accountMigration.parentHash); + } + }, [accountMigration]); + + const getRingTooltipMessage = () => { + return ( +
+
+ {t(localeKeys.transferable)}:{" "} + {prettifyTooltipNumber(migratedAssetDistribution?.ring.transferable ?? BigNumber(0))} +
+
+ {t(localeKeys.deposit)}: {prettifyTooltipNumber(migratedAssetDistribution?.ring.deposit ?? BigNumber(0))} +
+
+ {t(localeKeys.bonded)}: {prettifyTooltipNumber(migratedAssetDistribution?.ring.bonded ?? BigNumber(0))} +
+
+ {t(localeKeys.unbonded)}: {prettifyTooltipNumber(migratedAssetDistribution?.ring.unbonded ?? BigNumber(0))} +
+
+ {t(localeKeys.unbonding)}: {prettifyTooltipNumber(migratedAssetDistribution?.ring.unbonding ?? BigNumber(0))} +
+
+ {t(localeKeys.vested)}: {prettifyTooltipNumber(migratedAssetDistribution?.ring.vested ?? BigNumber(0))} +
+
+ ); + }; + + const getKtonTooltipMessage = () => { + return ( +
+
+ {t(localeKeys.transferable)}:{" "} + {prettifyTooltipNumber(migratedAssetDistribution?.kton.transferable ?? BigNumber(0))} +
+
+ {t(localeKeys.bonded)}: {prettifyTooltipNumber(migratedAssetDistribution?.kton.bonded ?? BigNumber(0))} +
+
+ {t(localeKeys.unbonded)}: {prettifyTooltipNumber(migratedAssetDistribution?.kton.unbonded ?? BigNumber(0))} +
+
+ {t(localeKeys.unbonding)}: {prettifyTooltipNumber(migratedAssetDistribution?.kton.unbonding ?? BigNumber(0))} +
+
+ ); + }; + + const totalRING = + migratedAssetDistribution?.ring.bonded + .plus(migratedAssetDistribution?.ring.deposit ?? BigNumber(0)) + .plus(migratedAssetDistribution?.ring.transferable) + .plus(migratedAssetDistribution?.ring.unbonded) + .plus(migratedAssetDistribution?.ring.unbonding) + .plus(migratedAssetDistribution?.ring.vested ?? BigNumber(0)) ?? BigNumber(0); + + const totalKTON = + migratedAssetDistribution?.kton.bonded + .plus(migratedAssetDistribution?.kton.transferable) + .plus(migratedAssetDistribution?.ring.unbonded) + .plus(migratedAssetDistribution?.ring.unbonding) ?? BigNumber(0); + + const onCopyTransactionHash = () => { + copyToClipboard(accountMigration?.transactionHash ?? ""); + }; + + return ( +
+
Migration status
+
+ ); +}; + +export default MultisigMigrationStatus; diff --git a/packages/app/src/components/Protected/index.tsx b/packages/app/src/components/Protected/index.tsx index a4a719e..06abe65 100644 --- a/packages/app/src/components/Protected/index.tsx +++ b/packages/app/src/components/Protected/index.tsx @@ -6,8 +6,9 @@ const Protected = ({ children }: PropsWithChildren) => { const { isWalletConnected, isRequestingWalletConnection } = useWallet(); const location = useLocation(); //if the user isn't connected to the wallet, redirect to the homepage + const path = location.pathname.includes("/multisig") ? `/multisig-home` : `/`; if (!isWalletConnected && !isRequestingWalletConnection) { - return ; + return ; } return <>{children}; diff --git a/packages/app/src/pages/Home.tsx b/packages/app/src/pages/Home.tsx index 3cdaedb..63afc35 100644 --- a/packages/app/src/pages/Home.tsx +++ b/packages/app/src/pages/Home.tsx @@ -3,10 +3,13 @@ import { Button } from "@darwinia/ui"; import { useAppTranslation, localeKeys } from "@darwinia/app-locale"; import { useWallet } from "@darwinia/app-providers"; import migrationIcon from "../assets/images/migration.svg"; +import { useLocation } from "react-router-dom"; +import { useEffect } from "react"; const Home = () => { const { t } = useAppTranslation(); const { connectWallet, selectedNetwork } = useWallet(); + const { search: urlParams } = useLocation(); return (
@@ -21,6 +24,7 @@ const Home = () => { __html: t(localeKeys.accountMigrationInfo, { ringSymbol: selectedNetwork?.ring.symbol, ktonSymbol: selectedNetwork?.kton.symbol, + multisigLink: `/#/multisig-home${urlParams}`, }), }} /> diff --git a/packages/app/src/pages/MultisigHome.tsx b/packages/app/src/pages/MultisigHome.tsx new file mode 100644 index 0000000..9526778 --- /dev/null +++ b/packages/app/src/pages/MultisigHome.tsx @@ -0,0 +1,38 @@ +import polkadotLogo from "../assets/images/polkadot.png"; +import { Button } from "@darwinia/ui"; +import { useAppTranslation, localeKeys } from "@darwinia/app-locale"; +import { useWallet } from "@darwinia/app-providers"; +import migrationIcon from "../assets/images/migration.svg"; +import { useLocation } from "react-router-dom"; +import { useEffect } from "react"; + +const MultisigHome = () => { + const { t } = useAppTranslation(); + const { connectWallet } = useWallet(); + + return ( +
+
+
+ migration +
+
+
+
+
+ image + +
+
+
+ ); +}; + +export default MultisigHome; diff --git a/packages/app/src/pages/MultisigMigration.tsx b/packages/app/src/pages/MultisigMigration.tsx new file mode 100644 index 0000000..7567b0e --- /dev/null +++ b/packages/app/src/pages/MultisigMigration.tsx @@ -0,0 +1,57 @@ +import MigrationProcess from "../components/MigrationProcess"; +import MigrationStatus from "../components/MigrationStatus"; +import { useWallet } from "@darwinia/app-providers"; +import { useQuery } from "@apollo/client"; +import { FIND_MIGRATION_BY_SOURCE_ADDRESS } from "@darwinia/app-config"; +import { useEffect, useState } from "react"; +import MultisigMigrationStatus from "../components/MultisigMigrationStatus"; +import MultisigMigrationProcess from "../components/MultisigMigrationProcess"; + +interface MigrationQuery { + accountAddress: string; +} + +export interface AccountMigration { + id: string; + blockNumber: number; + blockTime: string; + destination: string; + parentHash: string; + transactionHash: string; +} + +interface MigrationResult { + accountMigration?: AccountMigration; +} + +const Migration = () => { + const { isAccountMigratedJustNow, selectedAccount } = useWallet(); + const [accountMigrated, setAccountMigrated] = useState(); + const [isLoading, setIsLoading] = useState(false); + + /*const { + loading: isLoading, + data: migrationResult, + error, + } = useQuery(FIND_MIGRATION_BY_SOURCE_ADDRESS, { + variables: { + accountAddress: selectedAccount?.address ?? "", + }, + }); + + useEffect(() => { + if (!migrationResult || !migrationResult.accountMigration) { + setAccountMigrated(false); + } else { + setAccountMigrated(true); + } + }, [migrationResult, isAccountMigratedJustNow]);*/ + + return accountMigrated || isAccountMigratedJustNow ? ( + + ) : ( + + ); +}; + +export default Migration; diff --git a/packages/app/src/routes/index.tsx b/packages/app/src/routes/index.tsx index f0c5398..eccadf6 100644 --- a/packages/app/src/routes/index.tsx +++ b/packages/app/src/routes/index.tsx @@ -35,6 +35,10 @@ const browserRouter = createHashRouter([ index: true, element: , }, + { + path: "multisig-home", + element: , + }, { path: "migration", element: ( @@ -43,6 +47,14 @@ const browserRouter = createHashRouter([ ), }, + { + path: "multisig-migration", + element: ( + + + + ), + }, /*{ path: "relayers-overview", element: , From a29ce34ee491ae162861efb8ec510428b5fd4a89 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Thu, 23 Feb 2023 15:38:48 +0800 Subject: [PATCH 02/36] added multisig account creation modal --- packages/app-locale/src/localeKeys.ts | 6 +++++ .../MultisigMigrationProcess/index.tsx | 24 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index 45c7459..b39801d 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -58,4 +58,10 @@ export const localeKeys = { multisig: "multisig", noMultisigAccounts: "noMultisigAccounts", addMultisigAccount: "addMultisigAccount", + createWallet: "Create wallet", + name: "Name", + threshold: "Threshold", + yourAddress: "yourAddress", + membersAddress: "membersAddress", + memberAddress: "memberAddress", }; diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index b0afeee..73f4837 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -5,7 +5,7 @@ import MigrationSummary from "../MigrationSummary"; import MigrationForm from "../MigrationForm"; import { useEffect, useRef, useState } from "react"; import BigNumber from "bignumber.js"; -import { Button, notification } from "@darwinia/ui"; +import { Button, ModalEnhanced, notification } from "@darwinia/ui"; import { CustomInjectedAccountWithMeta } from "@darwinia/app-types"; import noDataIcon from "../../assets/images/no-data.svg"; @@ -20,6 +20,7 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { const [showMigrationForm, setShowMigrationForm] = useState(false); const currentAccount = useRef(); const canShowAccountNotification = useRef(false); + const [isAddMultisigModalVisible, setAddMultisigModalVisibility] = useState(false); useEffect(() => { if (currentAccount.current?.address !== selectedAccount?.address) { @@ -73,7 +74,15 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { ]; const onShowAddAccountModal = () => { - console.log("here we go======"); + setAddMultisigModalVisibility(true); + }; + + const onCloseAddAccountModal = () => { + setAddMultisigModalVisibility(false); + }; + + const onCreateMultisigAccount = () => { + console.log("create multi sig"); }; return ( @@ -107,6 +116,17 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { ); })}
+ +
Hello
+
); }; From d92750359fee062df556ba989e4ff17458dd2100 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Fri, 24 Feb 2023 11:19:06 +0800 Subject: [PATCH 03/36] updated the add multisig modal --- packages/app-locale/src/localeKeys.ts | 4 +- packages/app-locale/src/translations/enUS.ts | 9 +++ packages/app/src/Root.tsx | 5 +- .../MultisigMigrationProcess/index.tsx | 58 +++++++++++++++++-- 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index b39801d..82eadd1 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -58,10 +58,12 @@ export const localeKeys = { multisig: "multisig", noMultisigAccounts: "noMultisigAccounts", addMultisigAccount: "addMultisigAccount", - createWallet: "Create wallet", + createWallet: "createWallet", name: "Name", threshold: "Threshold", yourAddress: "yourAddress", membersAddress: "membersAddress", memberAddress: "memberAddress", + addMembers: "addMembers", + createMultisig: "createMultisig", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index 2c1102d..ca518b2 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -58,6 +58,15 @@ const enUs = { [localeKeys.multisig]: "Multisig", [localeKeys.noMultisigAccounts]: "No data", [localeKeys.addMultisigAccount]: "Add Multisig", + [localeKeys.addMembers]: "Add Members", + [localeKeys.createWallet]: "Create wallet", + [localeKeys.name]: "Name", + [localeKeys.threshold]: "Threshold", + [localeKeys.yourAddress]: "Your address", + [localeKeys.memberAddress]: "Member address", + [localeKeys.membersAddress]: "Members addresses", + [localeKeys.addMembers]: "Add members", + [localeKeys.createMultisig]: "Create", }; export default enUs; diff --git a/packages/app/src/Root.tsx b/packages/app/src/Root.tsx index bf3c277..6068a89 100644 --- a/packages/app/src/Root.tsx +++ b/packages/app/src/Root.tsx @@ -100,12 +100,13 @@ const Root = () => { }, [error, walletConfig]); //check if it should auto connect to wallet or wait for the user to click the connect wallet button - useEffect(() => { + // no need to auto connect so as to allow + /*useEffect(() => { const shouldAutoConnect = getStore("isConnectedToWallet"); if (shouldAutoConnect) { connectWallet(); } - }, [selectedNetwork]); + }, [selectedNetwork]);*/ return ( diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 73f4837..3fe622d 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -5,22 +5,30 @@ import MigrationSummary from "../MigrationSummary"; import MigrationForm from "../MigrationForm"; import { useEffect, useRef, useState } from "react"; import BigNumber from "bignumber.js"; -import { Button, ModalEnhanced, notification } from "@darwinia/ui"; +import { Button, Input, ModalEnhanced, notification, OptionProps, Select, Tooltip } from "@darwinia/ui"; import { CustomInjectedAccountWithMeta } from "@darwinia/app-types"; import noDataIcon from "../../assets/images/no-data.svg"; +import helpIcon from "../../assets/images/help.svg"; interface Props { isCheckingMigrationStatus: boolean; } const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { - const { selectedAccount } = useWallet(); + const { selectedAccount, injectedAccounts } = useWallet(); const { migrationAssetDistribution, isLoadingLedger } = useStorage(); const { t } = useAppTranslation(); const [showMigrationForm, setShowMigrationForm] = useState(false); const currentAccount = useRef(); const canShowAccountNotification = useRef(false); const [isAddMultisigModalVisible, setAddMultisigModalVisibility] = useState(false); + const accountsOptions: OptionProps[] = (injectedAccounts?.map((item, index) => { + return { + id: index, + value: item.address, + label: item.address, + }; + }) ?? []) as unknown as OptionProps[]; useEffect(() => { if (currentAccount.current?.address !== selectedAccount?.address) { @@ -85,6 +93,8 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { console.log("create multi sig"); }; + const accountSelectionChanged = (value: string | string[]) => {}; + return (
@@ -119,13 +129,53 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { -
Hello
+
+
+
+
{t(localeKeys.name)}
+ + image + +
+ +
+ +
+
+
{t(localeKeys.threshold)}
+ + image + +
+ +
+ +
+
+
{t(localeKeys.yourAddress)}
+
+ +
+ image +
+
+
); From cb3ff58eaa70a8e7e1d25f3da59624d3f928524e Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Thu, 2 Mar 2023 10:34:09 +0800 Subject: [PATCH 04/36] Added account summary page --- packages/app-locale/src/localeKeys.ts | 10 + packages/app-locale/src/translations/enUS.ts | 12 +- packages/app-providers/src/walletProvider.tsx | 33 ++ packages/app-types/src/others.ts | 3 +- packages/app-types/src/wallet.ts | 24 ++ packages/app/src/Root.tsx | 2 +- packages/app/src/assets/images/trash-bin.svg | 3 + .../MultisigMigrationProcess/index.tsx | 321 ++++++++++++++++-- .../app/src/pages/MultisigAccountSummary.tsx | 97 ++++++ packages/app/src/routes/index.tsx | 8 + 10 files changed, 485 insertions(+), 28 deletions(-) create mode 100644 packages/app/src/assets/images/trash-bin.svg create mode 100644 packages/app/src/pages/MultisigAccountSummary.tsx diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index 82eadd1..ba34aaa 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -66,4 +66,14 @@ export const localeKeys = { memberAddress: "memberAddress", addMembers: "addMembers", createMultisig: "createMultisig", + multisigCreationFailed: "multisigCreationFailed", + multisigNameTip: "multisigNameTip", + multisigThresholdTip: "multisigThresholdTip", + address: "address", + asset: "asset", + actions: "actions", + balanceAmount: "balanceAmount", + members: "members", + member: "member", + memberYou: "memberYou", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index ca518b2..da0be1a 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -57,7 +57,7 @@ const enUs = { [localeKeys.installWalletReminder]: `Connection failed. Please install {{walletName}} here.`, [localeKeys.multisig]: "Multisig", [localeKeys.noMultisigAccounts]: "No data", - [localeKeys.addMultisigAccount]: "Add Multisig", + [localeKeys.addMultisigAccount]: "Add multisig", [localeKeys.addMembers]: "Add Members", [localeKeys.createWallet]: "Create wallet", [localeKeys.name]: "Name", @@ -67,6 +67,16 @@ const enUs = { [localeKeys.membersAddress]: "Members addresses", [localeKeys.addMembers]: "Add members", [localeKeys.createMultisig]: "Create", + [localeKeys.multisigCreationFailed]: `Creation failed, unable to find the multisig account in Darwinia 1.0, please double-check the threshold and the addresses of all members.`, + [localeKeys.multisigNameTip]: `Arbitrary wallet name to help yourself remember.`, + [localeKeys.multisigThresholdTip]: `Minimum approvals required to validate the multi-sig transaction.`, + [localeKeys.address]: "Address", + [localeKeys.asset]: "Asset", + [localeKeys.actions]: "Actions", + [localeKeys.balanceAmount]: "{{amount}} {{tokenSymbol}}", + [localeKeys.members]: "Members", + [localeKeys.member]: "Member", + [localeKeys.memberYou]: "Member(You)", }; export default enUs; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index 1b792c0..fa80a3e 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -11,6 +11,8 @@ import { SpVersionRuntimeVersion, PalletVestingVestingInfo, DarwiniaAccountMigrationAssetAccount, + CreateOptions, + MultisigAccount, } from "@darwinia/app-types"; import { ApiPromise, WsProvider, SubmittableResult } from "@polkadot/api"; import { web3Accounts, web3Enable } from "@polkadot/extension-dapp"; @@ -60,6 +62,9 @@ const initialState: WalletCtx = { setMultisig: (value: boolean) => { //ignore }, + checkDarwiniaOneMultisigAccount: (signatories: string[], threshold: number, { name, tags = [] }: CreateOptions) => { + return Promise.resolve(undefined); + }, }; const WalletContext = createContext(initialState); @@ -365,6 +370,33 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { }); }, [apiPromise]); + const checkDarwiniaOneMultisigAccount = useCallback( + (signatories: string[], threshold: number, { name, tags = [] }: CreateOptions) => { + if (!apiPromise) { + return Promise.resolve(undefined); + } + + const genesisHash = apiPromise.genesisHash.toString(); + + const accountResult = keyring.addMultisig(signatories, threshold, { genesisHash, name, tags }); + const { pair } = accountResult; + const { meta } = pair; + const account: MultisigAccount = { + address: pair.address, + type: pair.type, + meta: { + genesisHash: meta.genesisHash?.toString() ?? "", + name: (meta.name ?? "") as string, + who: (meta.who ?? []) as string[], + threshold: (meta.threshold ?? 1) as number, + }, + }; + + return Promise.resolve(account); + }, + [apiPromise] + ); + return ( { onInitMigration, isMultisig, setMultisig, + checkDarwiniaOneMultisigAccount, }} > {children} diff --git a/packages/app-types/src/others.ts b/packages/app-types/src/others.ts index 963f012..11ce54c 100644 --- a/packages/app-types/src/others.ts +++ b/packages/app-types/src/others.ts @@ -1,4 +1,4 @@ -import { SupportedWallet } from "./wallet"; +import { MultisigAccount, SupportedWallet } from "./wallet"; export interface Account { id: number; @@ -8,4 +8,5 @@ export interface Storage { isConnectedToWallet?: boolean; selectedNetwork?: boolean; selectedWallet?: SupportedWallet; + multisigAccounts?: MultisigAccount[]; } diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index 118f97a..3c79b08 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -70,8 +70,32 @@ export interface WalletCtx { walletConfig: WalletConfig | undefined; isMultisig: boolean | undefined; setMultisig: (value: boolean) => void; + checkDarwiniaOneMultisigAccount: ( + signatories: string[], + threshold: number, + { name, tags = [] }: CreateOptions + ) => Promise; } export interface SpVersionRuntimeVersion extends Struct { specName: string; } + +export interface CreateOptions { + genesisHash?: string; + name: string; + tags?: string[]; +} + +export interface MultisigAccountMeta { + who: string[]; + genesisHash: string; + name: string; + threshold: number; +} + +export interface MultisigAccount { + address: string; + type: string; + meta: MultisigAccountMeta; +} diff --git a/packages/app/src/Root.tsx b/packages/app/src/Root.tsx index 6068a89..8d11176 100644 --- a/packages/app/src/Root.tsx +++ b/packages/app/src/Root.tsx @@ -62,7 +62,7 @@ const Root = () => { if (setMultisig) { setMultisig(false); } - } else if (location.pathname === "/multisig-home") { + } else if (location.pathname.includes("multisig")) { if (setMultisig) { setMultisig(true); } diff --git a/packages/app/src/assets/images/trash-bin.svg b/packages/app/src/assets/images/trash-bin.svg new file mode 100644 index 0000000..b53ab9b --- /dev/null +++ b/packages/app/src/assets/images/trash-bin.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 3fe622d..5a7c9fb 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -3,21 +3,40 @@ import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; import Identicon from "@polkadot/react-identicon"; import MigrationSummary from "../MigrationSummary"; import MigrationForm from "../MigrationForm"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import BigNumber from "bignumber.js"; -import { Button, Input, ModalEnhanced, notification, OptionProps, Select, Tooltip } from "@darwinia/ui"; -import { CustomInjectedAccountWithMeta } from "@darwinia/app-types"; +import { Button, Column, Input, ModalEnhanced, notification, OptionProps, Select, Table, Tooltip } from "@darwinia/ui"; +import { CustomInjectedAccountWithMeta, MultisigAccount } from "@darwinia/app-types"; import noDataIcon from "../../assets/images/no-data.svg"; import helpIcon from "../../assets/images/help.svg"; +import trashIcon from "../../assets/images/trash-bin.svg"; +import { getStore, prettifyNumber, setStore } from "@darwinia/app-utils"; +import { useLocation, useNavigate } from "react-router-dom"; interface Props { isCheckingMigrationStatus: boolean; } +interface Asset { + ring: BigNumber; + kton: BigNumber; +} + +interface MultisigAccountData { + id: string; + address: string; + name: string; + who: string[]; + asset: Asset; + threshold: number; +} + const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { - const { selectedAccount, injectedAccounts } = useWallet(); + const { selectedAccount, injectedAccounts, checkDarwiniaOneMultisigAccount, selectedNetwork } = useWallet(); const { migrationAssetDistribution, isLoadingLedger } = useStorage(); const { t } = useAppTranslation(); + const navigate = useNavigate(); + const location = useLocation(); const [showMigrationForm, setShowMigrationForm] = useState(false); const currentAccount = useRef(); const canShowAccountNotification = useRef(false); @@ -30,6 +49,144 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { }; }) ?? []) as unknown as OptionProps[]; + const [memberAddresses, setMemberAddresses] = useState<{ address: string; id: number }[]>([ + { id: new Date().getTime(), address: "" }, + ]); + const [threshold, setThreshold] = useState(""); + const [name, setName] = useState(""); + const [selectedAddress, setSelectedAddress] = useState(""); + const [isCheckingAccountExistence, setCheckingAccountExistence] = useState(false); + const [multisigAccountsList, setMultisigAccountsList] = useState([]); + + const columns: Column[] = [ + { + id: "1", + title:
{t(localeKeys.name)}
, + key: "name", + width: "200px", + render: (row) => { + return ( +
+ +
{row.name}
+
+ ); + }, + }, + { + id: "2", + title:
{t(localeKeys.address)}
, + key: "address", + width: "480px", + render: (row) => { + return ( +
+
{row.address}
+
+ ); + }, + }, + { + id: "3", + title:
{t(localeKeys.asset)}
, + key: "asset", + render: (row) => { + return ( +
+
+ {t(localeKeys.balanceAmount, { + amount: prettifyNumber({ + number: row.asset.ring, + }), + tokenSymbol: selectedNetwork?.ring.symbol, + })} +
+
+ {t(localeKeys.balanceAmount, { + amount: prettifyNumber({ + number: row.asset.kton, + }), + tokenSymbol: selectedNetwork?.kton.symbol, + })} +
+
+ ); + }, + }, + { + id: "4", + title:
{t(localeKeys.actions)}
, + key: "name", + width: "200px", + render: (row) => { + return ( +
+ +
+ ); + }, + }, + ]; + + const onInitializeMigration = useCallback( + (item: MultisigAccountData) => { + //multisig-account-summary + console.log(item); + console.log(location); + const params = new URLSearchParams(location.search); + params.set("address", item.address); + params.set("name", item.name); + params.set("who", item.who.join(",")); + params.set("threshold", item.threshold.toString()); + console.log(params.toString()); + navigate(`/multisig-account-summary?${params.toString()}`); + }, + [location] + ); + + const prepareMultisigAccountData = (accountList: MultisigAccount[]) => { + const data: MultisigAccountData[] = []; + for (let i = 0; i < accountList.length; i++) { + const accountItem = accountList[i]; + const item: MultisigAccountData = { + id: accountItem.address, + address: accountItem.address, + name: accountItem.meta.name, + asset: { + ring: BigNumber(0), + kton: BigNumber(0), + }, + who: [...accountItem.meta.who], + threshold: accountItem.meta.threshold, + }; + data.push(item); + } + return data; + }; + + useEffect(() => { + const multisigAccounts: MultisigAccount[] = getStore("multisigAccounts") ?? []; + const data = prepareMultisigAccountData(multisigAccounts); + setMultisigAccountsList(data); + }, []); + + useEffect(() => { + console.log("list changed"); + }, [multisigAccountsList]); + useEffect(() => { if (currentAccount.current?.address !== selectedAccount?.address) { currentAccount.current = selectedAccount; @@ -89,27 +246,101 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { setAddMultisigModalVisibility(false); }; - const onCreateMultisigAccount = () => { - console.log("create multi sig"); + const onCreateMultisigAccount = async () => { + try { + setCheckingAccountExistence(true); + const signatories = memberAddresses.map((item) => item.address); + signatories.unshift(selectedAddress); + const thresholdNumber = Number(threshold); + const account = await checkDarwiniaOneMultisigAccount(signatories, thresholdNumber, { name }); + setCheckingAccountExistence(false); + console.log(account); + if (typeof account === "undefined") { + notification.success({ + message:
{t(localeKeys.multisigCreationFailed)}
, + duration: 10000, + }); + return; + } + const multisigAccounts: MultisigAccount[] = getStore("multisigAccounts") ?? []; + //remove the account is it was already available in the local storage + const filteredAccounts = multisigAccounts.filter((account) => account.address !== account.address); + filteredAccounts.push(account); + setStore("multisigAccounts", filteredAccounts); + const data = prepareMultisigAccountData([account]); + setMultisigAccountsList((old) => { + return [...old, ...data]; + }); + } catch (e) { + setCheckingAccountExistence(false); + //ignore + } + }; + + const accountSelectionChanged = (value: string | string[]) => { + if (!Array.isArray(value)) { + setSelectedAddress(value); + console.log(value); + } + }; + + const onMemberAddressChanged = (index: number, value: string) => { + const members = [...memberAddresses]; + members[index].address = value; + setMemberAddresses(() => members); }; - const accountSelectionChanged = (value: string | string[]) => {}; + const onThresholdChanged = (value: string) => { + setThreshold(value); + }; + + const onNameChanged = (value: string) => { + setName(value); + }; + + const onDeleteMemberAddress = (index: number) => { + const addresses = [...memberAddresses]; + addresses.splice(index, 1); + setMemberAddresses(addresses); + }; + + const onAddNewMemberAddress = () => { + setMemberAddresses((old) => { + return [ + ...old, + { + id: new Date().getTime(), + address: "", + }, + ]; + }); + }; return (
-
{t(localeKeys.multisig)}
-
-
-
- noDataIcon -
{t(localeKeys.noMultisigAccounts)}
+
+
{t(localeKeys.multisig)}
+ + {multisigAccountsList.length > 0 ? ( + + ) : ( +
+
+ noDataIcon +
{t(localeKeys.noMultisigAccounts)}
+ +
+
+ )}
{footerLinks.map((item, index) => { @@ -126,53 +357,93 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { ); })}
+ -
+
{t(localeKeys.name)}
- + image
- + { + onNameChanged(e.target.value); + }} + leftIcon={null} + />
{t(localeKeys.threshold)}
- + image
- + { + onThresholdChanged(e.target.value); + }} + leftIcon={null} + />
{t(localeKeys.yourAddress)}
-
{t(localeKeys.membersAddress)}
-
-
- -
- image +
+ {memberAddresses.map((item, index) => { + return ( +
+
+ { + onMemberAddressChanged(index, event.target.value); + }} + value={item.address} + placeholder={t(localeKeys.memberAddress)} + leftIcon={null} + /> +
+ { + onDeleteMemberAddress(index); + }} + className={"w-[21px] h-[21px] cursor-pointer"} + src={trashIcon} + alt="image" + /> +
+ ); + })} +
+
+
diff --git a/packages/app/src/pages/MultisigAccountSummary.tsx b/packages/app/src/pages/MultisigAccountSummary.tsx new file mode 100644 index 0000000..0f07112 --- /dev/null +++ b/packages/app/src/pages/MultisigAccountSummary.tsx @@ -0,0 +1,97 @@ +import Identicon from "@polkadot/react-identicon"; +import { useLocation } from "react-router-dom"; +import { Button, SlideDownUp } from "@darwinia/ui"; +import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; +import copyIcon from "../assets/images/copy.svg"; +import caretIcon from "../assets/images/caret-down.svg"; +import { useState } from "react"; +import { useWallet } from "@darwinia/app-providers"; + +const MultisigAccountSummary = () => { + const { t } = useAppTranslation(); + const { injectedAccounts } = useWallet(); + const location = useLocation(); + const params = new URLSearchParams(location.search); + const address = params.get("address"); + const members = (params.get("who") ?? "").split(","); + const name = params.get("name"); + const threshold = params.get("threshold"); + const [isMemberSectionVisible, setMemberSectionVisible] = useState(true); + + const toggleMemberSections = () => { + setMemberSectionVisible((isVisible) => !isVisible); + }; + + return ( +
+
+
+
+
+ +
+
{name}
+
+ {t(localeKeys.multisig)} +
+
+
+
{address}
+ image +
+
+ +
+
+
+
{t(localeKeys.threshold)}
+
{threshold}
+
+
+
{t(localeKeys.members)}
+
{members.length}
+
+
+ caretIcon +
+
+ +
+ {members.map((member, index) => { + const isMyAccount = !!injectedAccounts?.find( + (account) => account.address.toLowerCase() === member.toLowerCase() + ); + return ( +
+
+ {isMyAccount ? t(localeKeys.memberYou) : t(localeKeys.member)} +
+
{member}
+
+ ); + })} +
+
+
+
+
+
+
Card1
+
Card2
+
+
+
+ ); +}; + +export default MultisigAccountSummary; diff --git a/packages/app/src/routes/index.tsx b/packages/app/src/routes/index.tsx index eccadf6..cb7df43 100644 --- a/packages/app/src/routes/index.tsx +++ b/packages/app/src/routes/index.tsx @@ -55,6 +55,14 @@ const browserRouter = createHashRouter([ ), }, + { + path: "multisig-account-summary", + element: ( + + + + ), + }, /*{ path: "relayers-overview", element: , From 3b4e485886830692b86dd500543e93e4e434d7e9 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Fri, 3 Mar 2023 15:43:13 +0800 Subject: [PATCH 05/36] -added token balance summary component and code refactoring --- .../src/components/MigrationSummary/index.tsx | 177 +----------------- .../components/TokensBalanceSummary/index.tsx | 173 +++++++++++++++++ 2 files changed, 176 insertions(+), 174 deletions(-) create mode 100644 packages/app/src/components/TokensBalanceSummary/index.tsx diff --git a/packages/app/src/components/MigrationSummary/index.tsx b/packages/app/src/components/MigrationSummary/index.tsx index 36cd2fc..362ad24 100644 --- a/packages/app/src/components/MigrationSummary/index.tsx +++ b/packages/app/src/components/MigrationSummary/index.tsx @@ -7,6 +7,7 @@ import { Tooltip } from "@darwinia/ui"; import { prettifyNumber, prettifyTooltipNumber } from "@darwinia/app-utils"; import BigNumber from "bignumber.js"; import { useEffect } from "react"; +import TokensBalanceSummary from "../TokensBalanceSummary"; interface Props { isCheckingMigrationStatus: boolean; @@ -14,7 +15,7 @@ interface Props { const MigrationSummary = ({ isCheckingMigrationStatus }: Props) => { const { t } = useAppTranslation(); - const { selectedNetwork, setTransactionStatus } = useWallet(); + const { setTransactionStatus } = useWallet(); const { migrationAssetDistribution, isLoadingLedger } = useStorage(); useEffect(() => { @@ -23,179 +24,7 @@ const MigrationSummary = ({ isCheckingMigrationStatus }: Props) => { return (
-
-
-
-
-
- {"image"} -
{selectedNetwork?.ring.symbol.toUpperCase()}
-
-
-
-
-
-
{t(localeKeys.transferable)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.transferable ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.transferable ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.deposit)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.deposit ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.deposit ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.bonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.bonded ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.bonded ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.unbonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.unbonded ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.unbonded ?? BigNumber(0), - })} - -
-
-
-
-
{t(localeKeys.unbonding)}
- Ubonding Message
}> - image - -
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.unbonding ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.unbonding ?? BigNumber(0), - })} - -
-
-
-
-
{t(localeKeys.vested)}
- Vested Message
}> - image - -
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.vested ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.vested ?? BigNumber(0), - })} - - - - - -
-
-
-
- {"image"} -
{selectedNetwork?.kton.symbol.toUpperCase()}
-
-
-
-
-
-
{t(localeKeys.transferable)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.transferable ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.transferable ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.bonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.bonded ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.bonded ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.unbonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.unbonded ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.unbonded ?? BigNumber(0), - })} - -
- -
-
-
{t(localeKeys.unbonding)}
- Ubonding Message
}> - image - -
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.unbonding ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.unbonding ?? BigNumber(0), - })} - - - - - - +
{t(localeKeys.migrationSummaryInfo)}
); diff --git a/packages/app/src/components/TokensBalanceSummary/index.tsx b/packages/app/src/components/TokensBalanceSummary/index.tsx new file mode 100644 index 0000000..899322e --- /dev/null +++ b/packages/app/src/components/TokensBalanceSummary/index.tsx @@ -0,0 +1,173 @@ +import ringIcon from "../../assets/images/ring.svg"; +import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; +import { Tooltip } from "@darwinia/ui"; +import { prettifyNumber, prettifyTooltipNumber } from "@darwinia/app-utils"; +import BigNumber from "bignumber.js"; +import helpIcon from "../../assets/images/help.svg"; +import ktonIcon from "../../assets/images/kton.svg"; +import { useWallet } from "@darwinia/app-providers"; +import { AssetDistribution } from "@darwinia/app-types"; + +interface Props { + asset: AssetDistribution | undefined; +} + +const TokensBalanceSummary = ({ asset: assetDistribution }: Props) => { + const { selectedNetwork } = useWallet(); + const { t } = useAppTranslation(); + return ( + <> +
+
+
+
+
+ {"image"} +
{selectedNetwork?.ring.symbol.toUpperCase()}
+
+
+
+
+
+
{t(localeKeys.transferable)}
+
+ {prettifyTooltipNumber(assetDistribution?.ring.transferable ?? BigNumber(0))}
} + > + {prettifyNumber({ + number: assetDistribution?.ring.transferable ?? BigNumber(0), + })} + +
+
+
+
{t(localeKeys.deposit)}
+
+ {prettifyTooltipNumber(assetDistribution?.ring.deposit ?? BigNumber(0))}
}> + {prettifyNumber({ + number: assetDistribution?.ring.deposit ?? BigNumber(0), + })} + +
+
+
+
{t(localeKeys.bonded)}
+
+ {prettifyTooltipNumber(assetDistribution?.ring.bonded ?? BigNumber(0))}
}> + {prettifyNumber({ + number: assetDistribution?.ring.bonded ?? BigNumber(0), + })} + +
+
+
+
{t(localeKeys.unbonded)}
+
+ {prettifyTooltipNumber(assetDistribution?.ring.unbonded ?? BigNumber(0))}
}> + {prettifyNumber({ + number: assetDistribution?.ring.unbonded ?? BigNumber(0), + })} + +
+ +
+
+
{t(localeKeys.unbonding)}
+ Ubonding Message
}> + image + +
+
+ {prettifyTooltipNumber(assetDistribution?.ring.unbonding ?? BigNumber(0))}
} + > + {prettifyNumber({ + number: assetDistribution?.ring.unbonding ?? BigNumber(0), + })} + + + +
+
+
{t(localeKeys.vested)}
+ Vested Message
}> + image + +
+
+ {prettifyTooltipNumber(assetDistribution?.ring.vested ?? BigNumber(0))}
}> + {prettifyNumber({ + number: assetDistribution?.ring.vested ?? BigNumber(0), + })} + + + + + +
+
+
+
+ {"image"} +
{selectedNetwork?.kton.symbol.toUpperCase()}
+
+
+
+
+
+
{t(localeKeys.transferable)}
+
+ {prettifyTooltipNumber(assetDistribution?.kton.transferable ?? BigNumber(0))}
} + > + {prettifyNumber({ + number: assetDistribution?.kton.transferable ?? BigNumber(0), + })} + +
+
+
+
{t(localeKeys.bonded)}
+
+ {prettifyTooltipNumber(assetDistribution?.kton.bonded ?? BigNumber(0))}
}> + {prettifyNumber({ + number: assetDistribution?.kton.bonded ?? BigNumber(0), + })} + +
+
+
+
{t(localeKeys.unbonded)}
+
+ {prettifyTooltipNumber(assetDistribution?.kton.unbonded ?? BigNumber(0))}
}> + {prettifyNumber({ + number: assetDistribution?.kton.unbonded ?? BigNumber(0), + })} + +
+ +
+
+
{t(localeKeys.unbonding)}
+ Ubonding Message
}> + image + +
+
+ {prettifyTooltipNumber(assetDistribution?.kton.unbonding ?? BigNumber(0))}
} + > + {prettifyNumber({ + number: assetDistribution?.kton.unbonding ?? BigNumber(0), + })} + + + + + + + + ); +}; + +export default TokensBalanceSummary; From 6e0075099e7b6768843869e2e88b2f42f6e58a74 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Fri, 3 Mar 2023 17:22:11 +0800 Subject: [PATCH 06/36] Adding migration modal --- packages/app-locale/src/localeKeys.ts | 7 ++ packages/app-locale/src/translations/enUS.ts | 8 ++ .../components/MultisigAccountInfo/index.tsx | 117 ++++++++++++++++++ .../app/src/pages/MultisigAccountSummary.tsx | 87 +------------ 4 files changed, 136 insertions(+), 83 deletions(-) create mode 100644 packages/app/src/components/MultisigAccountInfo/index.tsx diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index ba34aaa..55ba782 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -76,4 +76,11 @@ export const localeKeys = { members: "members", member: "member", memberYou: "memberYou", + migration: "migration", + continue: "continue", + general: "general", + fromSubstrateMultisig: "fromSubstrateMultisig", + toEVMAccountDarwinia: "toEVMAccountDarwinia", + evmAccountFormat: "evmAccountFormat", + migrationNotice: "migrationNotice", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index da0be1a..294055d 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -77,6 +77,14 @@ const enUs = { [localeKeys.members]: "Members", [localeKeys.member]: "Member", [localeKeys.memberYou]: "Member(You)", + [localeKeys.migration]: "Migration", + [localeKeys.continue]: "Continue", + [localeKeys.general]: "General", + [localeKeys.fromSubstrateMultisig]: "From The Substrate Multisig Account (Darwinia 1.0)", + [localeKeys.toEVMAccountDarwinia]: "To The EVM Account (Darwinia 2.0)", + [localeKeys.evmAccountFormat]: "EVM Account Format e.g. 0x267be1…", + [localeKeys.migrationNotice]: + "Please Ensure That You Have The Private Key For This EVM Account. And This Account Is Not From Any Third-Party Platform.", }; export default enUs; diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx new file mode 100644 index 0000000..728d8c8 --- /dev/null +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -0,0 +1,117 @@ +import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; +import { useWallet } from "@darwinia/app-providers"; +import { useLocation } from "react-router-dom"; +import { useState } from "react"; +import Identicon from "@polkadot/react-identicon"; +import copyIcon from "../../assets/images/copy.svg"; +import { Button, ModalEnhanced, SlideDownUp } from "@darwinia/ui"; +import caretIcon from "../../assets/images/caret-down.svg"; + +const MultisigAccountInfo = () => { + const { t } = useAppTranslation(); + const { injectedAccounts } = useWallet(); + const location = useLocation(); + const params = new URLSearchParams(location.search); + const address = params.get("address"); + const members = (params.get("who") ?? "").split(","); + const name = params.get("name"); + const threshold = params.get("threshold"); + const [isMemberSectionVisible, setMemberSectionVisible] = useState(true); + const [isMigrateModalVisible, setMigrationModalVisible] = useState(false); + + const toggleMemberSections = () => { + setMemberSectionVisible((isVisible) => !isVisible); + }; + + const onShowMigrateModal = () => { + setMigrationModalVisible(true); + console.log("here==="); + }; + + const onCloseModal = () => {}; + + return ( +
+
+
+
+
+ +
+
{name}
+
+ {t(localeKeys.multisig)} +
+
+
+
{address}
+ image +
+
+ +
+
+
+
{t(localeKeys.threshold)}
+
{threshold}
+
+
+
{t(localeKeys.members)}
+
{members.length}
+
+
+ caretIcon +
+
+
+ +
+ {members.map((member, index) => { + const isMyAccount = !!injectedAccounts?.find( + (account) => account.address.toLowerCase() === member.toLowerCase() + ); + return ( +
+
+ {isMyAccount ? t(localeKeys.memberYou) : t(localeKeys.member)} +
+
{member}
+
+ ); + })} +
+
+
+
+
+ +
+
+
{t(localeKeys.fromSubstrateMultisig)}
+
+ +
{address}
+
+
+
+
+
+ ); +}; + +export default MultisigAccountInfo; diff --git a/packages/app/src/pages/MultisigAccountSummary.tsx b/packages/app/src/pages/MultisigAccountSummary.tsx index 0f07112..a3160f2 100644 --- a/packages/app/src/pages/MultisigAccountSummary.tsx +++ b/packages/app/src/pages/MultisigAccountSummary.tsx @@ -1,94 +1,15 @@ -import Identicon from "@polkadot/react-identicon"; -import { useLocation } from "react-router-dom"; -import { Button, SlideDownUp } from "@darwinia/ui"; import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; -import copyIcon from "../assets/images/copy.svg"; -import caretIcon from "../assets/images/caret-down.svg"; -import { useState } from "react"; -import { useWallet } from "@darwinia/app-providers"; +import TokensBalanceSummary from "../components/TokensBalanceSummary"; +import MultisigAccountInfo from "../components/MultisigAccountInfo"; const MultisigAccountSummary = () => { const { t } = useAppTranslation(); - const { injectedAccounts } = useWallet(); - const location = useLocation(); - const params = new URLSearchParams(location.search); - const address = params.get("address"); - const members = (params.get("who") ?? "").split(","); - const name = params.get("name"); - const threshold = params.get("threshold"); - const [isMemberSectionVisible, setMemberSectionVisible] = useState(true); - - const toggleMemberSections = () => { - setMemberSectionVisible((isVisible) => !isVisible); - }; return (
+
-
-
-
- -
-
{name}
-
- {t(localeKeys.multisig)} -
-
-
-
{address}
- image -
-
- -
-
-
-
{t(localeKeys.threshold)}
-
{threshold}
-
-
-
{t(localeKeys.members)}
-
{members.length}
-
-
- caretIcon -
-
- -
- {members.map((member, index) => { - const isMyAccount = !!injectedAccounts?.find( - (account) => account.address.toLowerCase() === member.toLowerCase() - ); - return ( -
-
- {isMyAccount ? t(localeKeys.memberYou) : t(localeKeys.member)} -
-
{member}
-
- ); - })} -
-
-
-
-
-
-
Card1
-
Card2
-
+
); From 44925f749ad60d2fe8717156cbe9afa7b2341e80 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Mon, 6 Mar 2023 11:42:07 +0800 Subject: [PATCH 07/36] Added account migration initialization page --- packages/app-utils/src/others.ts | 6 +- packages/app-utils/src/time.ts | 5 - packages/app/src/assets/styles/index.scss | 22 ++ .../src/components/MigrationForm/index.tsx | 2 +- .../components/MultisigAccountInfo/index.tsx | 343 +++++++++++++++++- .../MultisigMigrationProcess/index.tsx | 12 +- .../MultisigMigrationStatus/index.tsx | 34 +- .../pages/MultisigAccountMigrationSummary.tsx | 62 ++++ .../app/src/pages/MultisigAccountSummary.tsx | 18 - packages/app/src/pages/MultisigMigration.tsx | 43 +-- packages/app/src/routes/index.tsx | 6 +- 11 files changed, 458 insertions(+), 95 deletions(-) create mode 100644 packages/app/src/pages/MultisigAccountMigrationSummary.tsx delete mode 100644 packages/app/src/pages/MultisigAccountSummary.tsx diff --git a/packages/app-utils/src/others.ts b/packages/app-utils/src/others.ts index b0121e0..f4f878f 100644 --- a/packages/app-utils/src/others.ts +++ b/packages/app-utils/src/others.ts @@ -1,7 +1,7 @@ import { Storage } from "@darwinia/app-types"; import { STORAGE as APP_STORAGE } from "@darwinia/app-config"; import BigNumber from "bignumber.js"; -import { ethers } from "ethers"; +import { ethers, utils } from "ethers"; export const setStore = (key: keyof Storage, value: unknown) => { try { @@ -97,3 +97,7 @@ export const formatToEther = (valueInWei: string): string => { export const formatToWei = (valueInEther: string) => { return ethers.utils.parseEther(valueInEther); }; + +export const isEthereumAddress = (address: string): boolean => { + return utils.isAddress(address); +}; diff --git a/packages/app-utils/src/time.ts b/packages/app-utils/src/time.ts index 6785f31..c2f8402 100644 --- a/packages/app-utils/src/time.ts +++ b/packages/app-utils/src/time.ts @@ -1,5 +1,4 @@ import moment from "moment"; -import { utils } from "ethers"; /*Use this to format the time ago to your own needs*/ /*moment.updateLocale("en", { @@ -27,7 +26,3 @@ export const formatTimeInUTC = (time: string, inputFormat = "YYYY-MM-DDTHH:mm:ss export const getMonthsRange = (startTimestamp: number, endTimestamp: number) => { return Math.round(moment(endTimestamp).diff(moment(startTimestamp), "months", true)); }; - -export const isEthereumAddress = (address: string): boolean => { - return utils.isAddress(address); -}; diff --git a/packages/app/src/assets/styles/index.scss b/packages/app/src/assets/styles/index.scss index ea0cd83..fdbdf6e 100644 --- a/packages/app/src/assets/styles/index.scss +++ b/packages/app/src/assets/styles/index.scss @@ -153,3 +153,25 @@ a { align-items: flex-start !important; } } +.custom-input { + align-self: stretch; + display: block; + width: 100%; + flex: 1; + border: none; + background-color: transparent; + outline: none; + appearance: none; + + &.error { + color: var(--danger) !important; + } + + &:focus { + outline: none; + } + + &::placeholder { + color: var(--halfWhite); + } +} diff --git a/packages/app/src/components/MigrationForm/index.tsx b/packages/app/src/components/MigrationForm/index.tsx index 2731b59..1fe1498 100644 --- a/packages/app/src/components/MigrationForm/index.tsx +++ b/packages/app/src/components/MigrationForm/index.tsx @@ -8,7 +8,7 @@ import JazzIcon from "../JazzIcon"; import { isEthereumAddress } from "@darwinia/app-utils"; import { useQuery } from "@apollo/client"; -interface Tip { +export interface Tip { id: number; title: string; } diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index 728d8c8..906e542 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -4,8 +4,13 @@ import { useLocation } from "react-router-dom"; import { useState } from "react"; import Identicon from "@polkadot/react-identicon"; import copyIcon from "../../assets/images/copy.svg"; -import { Button, ModalEnhanced, SlideDownUp } from "@darwinia/ui"; +import { Button, CheckboxGroup, Input, ModalEnhanced, SlideDownUp, Tooltip } from "@darwinia/ui"; import caretIcon from "../../assets/images/caret-down.svg"; +import JazzIcon from "../JazzIcon"; +import { Tip } from "../MigrationForm"; +import helpIcon from "../../assets/images/help.svg"; +import trashIcon from "../../assets/images/trash-bin.svg"; +import { isValidNumber, isEthereumAddress } from "@darwinia/app-utils"; const MultisigAccountInfo = () => { const { t } = useAppTranslation(); @@ -18,6 +23,29 @@ const MultisigAccountInfo = () => { const threshold = params.get("threshold"); const [isMemberSectionVisible, setMemberSectionVisible] = useState(true); const [isMigrateModalVisible, setMigrationModalVisible] = useState(false); + const [destinationAddress, setDestinationAddress] = useState(""); + const [activeDestinationTab, setActiveDestinationTab] = useState(1); + const [isAttentionModalVisible, setAttentionModalVisibility] = useState(false); + const [isConfirmationModalVisible, setConfirmationModalVisibility] = useState(false); + const [checkedTips, setCheckedTips] = useState([]); + const { selectedNetwork, onInitMigration } = useWallet(); + const [isProcessingMigration, setProcessingMigration] = useState(false); + const [memberAddresses, setMemberAddresses] = useState<{ address: string; id: number }[]>([ + { id: new Date().getTime(), address: "" }, + ]); + const [newAccountThreshold, setNewAccountThreshold] = useState(""); + const [newMultisigAccountAddress, setNewMultisigAccountAddress] = useState(""); + + const destinationTabs = [ + { + id: 1, + label: t(localeKeys.general), + }, + { + id: 2, + label: t(localeKeys.multisig), + }, + ]; const toggleMemberSections = () => { setMemberSectionVisible((isVisible) => !isVisible); @@ -25,10 +53,118 @@ const MultisigAccountInfo = () => { const onShowMigrateModal = () => { setMigrationModalVisible(true); - console.log("here==="); }; - const onCloseModal = () => {}; + const onCloseModal = () => { + setMigrationModalVisible(false); + }; + + const onContinueMigration = () => { + setMigrationModalVisible(false); + setAttentionModalVisibility(true); + }; + + const generateMultisigAccount = () => { + const dummyAddress = "0xDeA37A59acB4F407980Ea347ab351697E7102ae0"; + setNewMultisigAccountAddress(dummyAddress); + console.log("call the smart contract"); + }; + + const onAttentionTipChecked = (checkedTip: Tip, allCheckedTips: Tip[]) => { + setCheckedTips(allCheckedTips); + }; + + const attentionTips: Tip[] = [ + { + id: 1, + title: t(localeKeys.iamMigratingFromOneDarwiniaToTwo), + }, + { + id: 2, + title: t(localeKeys.iHaveConfirmedIsNewAddress), + }, + { + id: 3, + title: t(localeKeys.iHavePrivateKeys), + }, + { + id: 4, + title: t(localeKeys.evmAccountNotExchange), + }, + { + id: 5, + title: t(localeKeys.evmAccountSafe, { link: "https://metamask.io/" }), + }, + ]; + + const onCloseAttentionModal = () => { + setAttentionModalVisibility(false); + setCheckedTips([]); + }; + + const getTipOption = (option: Tip) => { + return
; + }; + + const onTermsAgreeing = () => { + setAttentionModalVisibility(false); + setConfirmationModalVisibility(true); + }; + + const isContinueButtonDisabled = () => { + if (activeDestinationTab === 1) { + return destinationAddress.length === 0; + } else { + const isValidThreshold = isValidNumber(newAccountThreshold); + const isValidMultisigAddress = isEthereumAddress(newMultisigAccountAddress); + return !isValidThreshold || !isValidMultisigAddress; + } + }; + + const onCloseConfirmationModal = () => { + setConfirmationModalVisibility(false); + }; + + const onConfirmAndMigrate = async () => { + if (!address) { + return; + } + try { + setProcessingMigration(true); + const destination = activeDestinationTab === 1 ? destinationAddress : newMultisigAccountAddress; + onInitMigration(address, destination, (isSuccessful) => { + setProcessingMigration(isSuccessful); + }); + } catch (e) { + setProcessingMigration(false); + } + }; + + const onDeleteMemberAddress = (index: number) => { + const addresses = [...memberAddresses]; + addresses.splice(index, 1); + setMemberAddresses(addresses); + }; + + const onMemberAddressChanged = (index: number, value: string) => { + const members = [...memberAddresses]; + members[index].address = value; + setMemberAddresses(() => members); + //TODO delete this + generateMultisigAccount(); + }; + + const onAddNewMemberAddress = () => { + setMemberAddresses((old) => { + return [ + ...old, + { + id: new Date().getTime(), + address: "", + }, + ]; + }); + }; return (
@@ -55,7 +191,7 @@ const MultisigAccountInfo = () => {
-
+
{t(localeKeys.threshold)}
{threshold}
@@ -66,7 +202,6 @@ const MultisigAccountInfo = () => {
caretIcon { (account) => account.address.toLowerCase() === member.toLowerCase() ); return ( -
+
{isMyAccount ? t(localeKeys.memberYou) : t(localeKeys.member)}
@@ -94,20 +232,197 @@ const MultisigAccountInfo = () => {
- -
+ {/*Migration modal*/} + +
{t(localeKeys.fromSubstrateMultisig)}
- +
{address}
+
+
{t(localeKeys.toEVMAccountDarwinia)}
+
+
+ {destinationTabs.map((tab) => { + const tabBg = tab.id === activeDestinationTab ? `bg-primary` : ""; + return ( +
{ + setActiveDestinationTab(tab.id); + }} + className={`flex-1 text-center py-[5px] cursor-pointer ${tabBg}`} + key={tab.id} + > + {tab.label} +
+ ); + })} +
+
+
+ {/*General tab*/} +
+
+
+ +
+ { + setDestinationAddress(e.target.value); + }} + placeholder={t(localeKeys.evmAccountFormat)} + className={"custom-input h-[40px]"} + /> +
+
{t(localeKeys.migrationNotice)}
+
+ {/*Multisig tab*/} +
+
+
+
+
{t(localeKeys.threshold)}
+ + image + +
+ { + setNewAccountThreshold(e.target.value); + }} + leftIcon={null} + /> +
+ +
+
+
{t(localeKeys.membersAddress)}
+
+
+ {memberAddresses.map((item, index) => { + return ( +
+
+ { + onMemberAddressChanged(index, event.target.value); + }} + value={item.address} + placeholder={t(localeKeys.memberAddress)} + leftIcon={null} + /> +
+ { + onDeleteMemberAddress(index); + }} + className={"w-[21px] h-[21px] cursor-pointer"} + src={trashIcon} + alt="image" + /> +
+ ); + })} +
+
+ +
+
+
+
+
+ +
+
{newMultisigAccountAddress}
+
+
+
+
+
+
+ + {/*terms of service*/} + +
+ +
+
+ + {/*Migration confirmation*/} + +
+ {/*Origin*/} +
+
+ {t(localeKeys.fromTheSubstrateAccount, { name: selectedNetwork?.name })} +
+
+ +
{address}
+
+
+ {/*Destination*/} +
+
{t(localeKeys.toEVMAccount, { name: selectedNetwork?.name })}
+
+
+ +
+
{activeDestinationTab === 1 ? destinationAddress : newMultisigAccountAddress}
+
+
diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 5a7c9fb..334cf76 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -13,10 +13,6 @@ import trashIcon from "../../assets/images/trash-bin.svg"; import { getStore, prettifyNumber, setStore } from "@darwinia/app-utils"; import { useLocation, useNavigate } from "react-router-dom"; -interface Props { - isCheckingMigrationStatus: boolean; -} - interface Asset { ring: BigNumber; kton: BigNumber; @@ -31,7 +27,7 @@ interface MultisigAccountData { threshold: number; } -const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { +const MultisigMigrationProcess = () => { const { selectedAccount, injectedAccounts, checkDarwiniaOneMultisigAccount, selectedNetwork } = useWallet(); const { migrationAssetDistribution, isLoadingLedger } = useStorage(); const { t } = useAppTranslation(); @@ -143,16 +139,12 @@ const MultisigMigrationProcess = ({ isCheckingMigrationStatus }: Props) => { const onInitializeMigration = useCallback( (item: MultisigAccountData) => { - //multisig-account-summary - console.log(item); - console.log(location); const params = new URLSearchParams(location.search); params.set("address", item.address); params.set("name", item.name); params.set("who", item.who.join(",")); params.set("threshold", item.threshold.toString()); - console.log(params.toString()); - navigate(`/multisig-account-summary?${params.toString()}`); + navigate(`/multisig-account-migration-summary?${params.toString()}`); }, [location] ); diff --git a/packages/app/src/components/MultisigMigrationStatus/index.tsx b/packages/app/src/components/MultisigMigrationStatus/index.tsx index e0fc690..0849a27 100644 --- a/packages/app/src/components/MultisigMigrationStatus/index.tsx +++ b/packages/app/src/components/MultisigMigrationStatus/index.tsx @@ -28,9 +28,9 @@ const MultisigMigrationStatus = ({ accountMigration }: Props) => { const { selectedNetwork, setTransactionStatus } = useWallet(); const { retrieveMigratedAsset, migratedAssetDistribution, isLoadingMigratedLedger } = useStorage(); - useEffect(() => { + /*useEffect(() => { setTransactionStatus(!!isLoadingMigratedLedger); - }, [isLoadingMigratedLedger]); + }, [isLoadingMigratedLedger]);*/ useEffect(() => { if (accountMigration) { @@ -102,9 +102,39 @@ const MultisigMigrationStatus = ({ accountMigration }: Props) => { copyToClipboard(accountMigration?.transactionHash ?? ""); }; + const footerLinks = [ + { + title: t(localeKeys.howToMigrate), + url: "https://www.baidu.com", + }, + { + title: t(localeKeys.darwiniaMergeOverview), + url: "https://medium.com/darwinianetwork/darwinia-2-0-merge-overview-96af96d668aa", + }, + { + title: t(localeKeys.darwiniaDataMigration), + url: "https://medium.com/darwinianetwork/darwinia-2-0-blockchain-data-migration-c1186338c743", + }, + ]; + return (
Migration status
+
+ {footerLinks.map((item, index) => { + return ( + + {item.title} + + ); + })} +
); }; diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx new file mode 100644 index 0000000..96a1d4b --- /dev/null +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -0,0 +1,62 @@ +import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; +import TokensBalanceSummary from "../components/TokensBalanceSummary"; +import MultisigAccountInfo from "../components/MultisigAccountInfo"; +import { useLocation } from "react-router-dom"; +import { useState } from "react"; + +const MultisigAccountMigrationSummary = () => { + const { t } = useAppTranslation(); + const [isAccountMigrationInitialized, setAccountMigrationInitialized] = useState(true); + + const location = useLocation(); + const params = new URLSearchParams(location.search); + const address = params.get("address"); + const members = (params.get("who") ?? "").split(","); + const name = params.get("name"); + const threshold = params.get("threshold"); + + const footerLinks = [ + { + title: t(localeKeys.howToMigrate), + url: "https://www.baidu.com", + }, + { + title: t(localeKeys.darwiniaMergeOverview), + url: "https://medium.com/darwinianetwork/darwinia-2-0-merge-overview-96af96d668aa", + }, + { + title: t(localeKeys.darwiniaDataMigration), + url: "https://medium.com/darwinianetwork/darwinia-2-0-blockchain-data-migration-c1186338c743", + }, + ]; + + return ( +
+ + {isAccountMigrationInitialized ? ( +
Account migration initilized
+ ) : ( +
+ +
+ )} +
+ {footerLinks.map((item, index) => { + return ( + + {item.title} + + ); + })} +
+
+ ); +}; + +export default MultisigAccountMigrationSummary; diff --git a/packages/app/src/pages/MultisigAccountSummary.tsx b/packages/app/src/pages/MultisigAccountSummary.tsx deleted file mode 100644 index a3160f2..0000000 --- a/packages/app/src/pages/MultisigAccountSummary.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; -import TokensBalanceSummary from "../components/TokensBalanceSummary"; -import MultisigAccountInfo from "../components/MultisigAccountInfo"; - -const MultisigAccountSummary = () => { - const { t } = useAppTranslation(); - - return ( -
- -
- -
-
- ); -}; - -export default MultisigAccountSummary; diff --git a/packages/app/src/pages/MultisigMigration.tsx b/packages/app/src/pages/MultisigMigration.tsx index 7567b0e..1a9bc57 100644 --- a/packages/app/src/pages/MultisigMigration.tsx +++ b/packages/app/src/pages/MultisigMigration.tsx @@ -1,16 +1,5 @@ -import MigrationProcess from "../components/MigrationProcess"; -import MigrationStatus from "../components/MigrationStatus"; -import { useWallet } from "@darwinia/app-providers"; -import { useQuery } from "@apollo/client"; -import { FIND_MIGRATION_BY_SOURCE_ADDRESS } from "@darwinia/app-config"; -import { useEffect, useState } from "react"; -import MultisigMigrationStatus from "../components/MultisigMigrationStatus"; import MultisigMigrationProcess from "../components/MultisigMigrationProcess"; -interface MigrationQuery { - accountAddress: string; -} - export interface AccountMigration { id: string; blockNumber: number; @@ -20,38 +9,8 @@ export interface AccountMigration { transactionHash: string; } -interface MigrationResult { - accountMigration?: AccountMigration; -} - const Migration = () => { - const { isAccountMigratedJustNow, selectedAccount } = useWallet(); - const [accountMigrated, setAccountMigrated] = useState(); - const [isLoading, setIsLoading] = useState(false); - - /*const { - loading: isLoading, - data: migrationResult, - error, - } = useQuery(FIND_MIGRATION_BY_SOURCE_ADDRESS, { - variables: { - accountAddress: selectedAccount?.address ?? "", - }, - }); - - useEffect(() => { - if (!migrationResult || !migrationResult.accountMigration) { - setAccountMigrated(false); - } else { - setAccountMigrated(true); - } - }, [migrationResult, isAccountMigratedJustNow]);*/ - - return accountMigrated || isAccountMigratedJustNow ? ( - - ) : ( - - ); + return ; }; export default Migration; diff --git a/packages/app/src/routes/index.tsx b/packages/app/src/routes/index.tsx index cb7df43..957a672 100644 --- a/packages/app/src/routes/index.tsx +++ b/packages/app/src/routes/index.tsx @@ -40,6 +40,7 @@ const browserRouter = createHashRouter([ element: , }, { + //This path is used when migrating a normal account path: "migration", element: ( @@ -48,6 +49,7 @@ const browserRouter = createHashRouter([ ), }, { + //This path is used when migrating a multisig account path: "multisig-migration", element: ( @@ -56,10 +58,10 @@ const browserRouter = createHashRouter([ ), }, { - path: "multisig-account-summary", + path: "multisig-account-migration-summary", element: ( - + ), }, From e534d2a1aa5d1c2c73d269640db42ac57b7cbff1 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 8 Mar 2023 14:15:24 +0800 Subject: [PATCH 08/36] Added account migration progress tabs --- .../MultisigMigrationProgressTabs/index.tsx | 32 +++++++++++++++++++ .../pages/MultisigAccountMigrationSummary.tsx | 3 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/components/MultisigMigrationProgressTabs/index.tsx diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx new file mode 100644 index 0000000..aa727e1 --- /dev/null +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -0,0 +1,32 @@ +import { Tabs, Tab } from "@darwinia/ui"; +import { useAppTranslation } from "@darwinia/app-locale"; +import { useState } from "react"; + +const MultisigMigrationProgressTabs = () => { + const { t } = useAppTranslation(); + const tabs: Tab[] = [ + { + id: "1", + title: "aaa", + }, + { + id: "2", + title: "bbb", + }, + { + id: "3", + title: "ccc", + }, + ]; + const [selectedTab, setSelectedTab] = useState(tabs[0]); + const onTabsChange = (selectedTab: Tab) => { + setSelectedTab(selectedTab); + }; + return ( +
+ +
+ ); +}; + +export default MultisigMigrationProgressTabs; diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx index 96a1d4b..5f78e04 100644 --- a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -3,6 +3,7 @@ import TokensBalanceSummary from "../components/TokensBalanceSummary"; import MultisigAccountInfo from "../components/MultisigAccountInfo"; import { useLocation } from "react-router-dom"; import { useState } from "react"; +import MultisigMigrationProgressTabs from "../components/MultisigMigrationProgressTabs"; const MultisigAccountMigrationSummary = () => { const { t } = useAppTranslation(); @@ -34,7 +35,7 @@ const MultisigAccountMigrationSummary = () => {
{isAccountMigrationInitialized ? ( -
Account migration initilized
+ ) : (
From 1a4641fb56273dcfcd5e408861730de3d9185f27 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Thu, 9 Mar 2023 10:11:49 +0800 Subject: [PATCH 09/36] Added progress table --- packages/app-locale/src/localeKeys.ts | 11 +++ packages/app-locale/src/translations/enUS.ts | 11 +++ .../MultisigMigrationProgressTabs/index.tsx | 80 +++++++++++++++++-- packages/app/src/pages/MultisigHome.tsx | 37 +++++---- 4 files changed, 117 insertions(+), 22 deletions(-) diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index 3b82afd..0f987c6 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -84,4 +84,15 @@ export const localeKeys = { evmAccountFormat: "evmAccountFormat", migrationNotice: "migrationNotice", pending: "pending", + inProgress: "inProgress", + confirmedExtrinsic: "confirmedExtrinsic", + cancelledExtrinsics: "cancelledExtrinsics", + callHash: "callHash", + progress: "progress", + action: "action", + parameters: "parameters", + destination: "destination", + type: "type", + assets: "assets", + executed: "executed", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index b8cd4ee..f3b9ebd 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -86,6 +86,17 @@ const enUs = { [localeKeys.migrationNotice]: "Please Ensure That You Have The Private Key For This EVM Account. And This Account Is Not From Any Third-Party Platform.", [localeKeys.pending]: "Pending", + [localeKeys.inProgress]: "In Progress({{number}})", + [localeKeys.confirmedExtrinsic]: "Confirmed Extrinsics({{number}})", + [localeKeys.cancelledExtrinsics]: "Cancelled Extrinsics({{number}})", + [localeKeys.callHash]: "Call hash", + [localeKeys.progress]: "Progress", + [localeKeys.action]: "Action", + [localeKeys.parameters]: "Parameters", + [localeKeys.destination]: "Dest", + [localeKeys.type]: "Type", + [localeKeys.assets]: "Assets", + [localeKeys.executed]: "Executed", }; export default enUs; diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx index aa727e1..39da85c 100644 --- a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -1,21 +1,36 @@ import { Tabs, Tab } from "@darwinia/ui"; -import { useAppTranslation } from "@darwinia/app-locale"; -import { useState } from "react"; +import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import Identicon from "@polkadot/react-identicon"; const MultisigMigrationProgressTabs = () => { const { t } = useAppTranslation(); + const [memberAccounts, setMemberAccounts] = useState(); + const location = useLocation(); + const params = new URLSearchParams(location.search); + const address = params.get("address"); + const members = (params.get("who") ?? "").split(","); + const name = params.get("name"); + const threshold = params.get("threshold"); + + useEffect(() => { + const members = (params.get("who") ?? "").split(","); + setMemberAccounts(members); + }, [location]); + const tabs: Tab[] = [ { id: "1", - title: "aaa", + title: t(localeKeys.inProgress, { number: 3 }), }, { id: "2", - title: "bbb", + title: t(localeKeys.confirmedExtrinsic, { number: 0 }), }, { id: "3", - title: "ccc", + title: t(localeKeys.cancelledExtrinsics, { number: 0 }), }, ]; const [selectedTab, setSelectedTab] = useState(tabs[0]); @@ -23,8 +38,59 @@ const MultisigMigrationProgressTabs = () => { setSelectedTab(selectedTab); }; return ( -
- +
+
+
+ +
+
+ {/*in progress*/} +
+
+
+
+
{t(localeKeys.callHash)}
+
{t(localeKeys.status)}
+
{t(localeKeys.progress)}
+
{t(localeKeys.action)}
+
+
+
+
0x0E55c72781aCD923C4e3e7Ad9bB8363de15ef204
+
Multisig_Account_Migrate
+
3/3
+
{t(localeKeys.executed)}
+
+
+
+
{t(localeKeys.progress)}
+
+ {memberAccounts?.map((item) => { + return ( +
+
onchainmoney.com
+
+ +
{item}
+
+
Initialized
+
+ ); + })} +
+
+
+
+
+
+
+
+
); }; diff --git a/packages/app/src/pages/MultisigHome.tsx b/packages/app/src/pages/MultisigHome.tsx index 9526778..311ed63 100644 --- a/packages/app/src/pages/MultisigHome.tsx +++ b/packages/app/src/pages/MultisigHome.tsx @@ -1,14 +1,13 @@ -import polkadotLogo from "../assets/images/polkadot.png"; -import { Button } from "@darwinia/ui"; import { useAppTranslation, localeKeys } from "@darwinia/app-locale"; import { useWallet } from "@darwinia/app-providers"; import migrationIcon from "../assets/images/migration.svg"; import { useLocation } from "react-router-dom"; import { useEffect } from "react"; +import { dAppSupportedWallets } from "@darwinia/app-config"; const MultisigHome = () => { const { t } = useAppTranslation(); - const { connectWallet } = useWallet(); + const { connectWallet, walletConfig } = useWallet(); return (
@@ -18,18 +17,26 @@ const MultisigHome = () => {
-
-
- image - -
+
+ {dAppSupportedWallets.map(({ name, logo, sources }, index) => { + const selected = name === walletConfig?.name; + const injecteds = window.injectedWeb3; + const installed = injecteds && sources.some((source) => injecteds[source]); + + return ( + + ); + })}
); From 1c4ccd073b76ee5df65303e686498be90f1c452f Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Mon, 13 Mar 2023 10:20:40 +0800 Subject: [PATCH 10/36] bug fix --- packages/app/src/components/Header/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/components/Header/index.tsx b/packages/app/src/components/Header/index.tsx index 5b8a7d6..67d2cb3 100644 --- a/packages/app/src/components/Header/index.tsx +++ b/packages/app/src/components/Header/index.tsx @@ -21,6 +21,7 @@ const Header = () => { connectWallet, forceSetAccountAddress, walletConfig, + isMultisig, } = useWallet(); const [searchParams, setSearchParams] = useSearchParams(); const location = useLocation(); From a7d5040e4d7b5708e87d1d1489d8e1f6397a5bcd Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Tue, 14 Mar 2023 12:13:14 +0800 Subject: [PATCH 11/36] minor updates --- packages/app-config/src/supportedNetworks.ts | 2 +- .../MultisigMigrationProgressTabs/index.tsx | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/app-config/src/supportedNetworks.ts b/packages/app-config/src/supportedNetworks.ts index b2313b3..c0c1ab7 100644 --- a/packages/app-config/src/supportedNetworks.ts +++ b/packages/app-config/src/supportedNetworks.ts @@ -4,4 +4,4 @@ import { darwinia } from "./chains/darwinia"; import { pangolin } from "./chains/pangolin"; import { pangoro } from "./chains/pangoro"; -export const supportedNetworks: ChainConfig[] = [crab]; +export const supportedNetworks: ChainConfig[] = [crab, pangolin, pangoro]; diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx index 39da85c..5406c16 100644 --- a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -3,6 +3,7 @@ import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; import { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; import Identicon from "@polkadot/react-identicon"; +import BigNumber from "bignumber.js"; const MultisigMigrationProgressTabs = () => { const { t } = useAppTranslation(); @@ -37,6 +38,21 @@ const MultisigMigrationProgressTabs = () => { const onTabsChange = (selectedTab: Tab) => { setSelectedTab(selectedTab); }; + + const parameters = { + destination: { + account: "0xe59261f6D4088BcD69985A3D369Ff14cC54EF1E5", + status: 0, + }, + type: "Multisig Account", + threshold: threshold, + member: members, + asset: { + ring: BigNumber(20000000000000000000), + kton: BigNumber(35000000000000000000), + }, + }; + return (
@@ -85,6 +101,29 @@ const MultisigMigrationProgressTabs = () => {
+
+
+
{t(localeKeys.parameters)}
+
+
+
Dest
+
{parameters.destination.account}
+
+
+
Dest
+
{parameters.destination.account}
+
+
+
Dest
+
{parameters.destination.account}
+
+
+
Dest
+
{parameters.destination.account}
+
+
+
+
From ee2be5774f4e2f076050fc8d9f44426c75af84e2 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 15 Mar 2023 08:39:52 +0800 Subject: [PATCH 12/36] Added other UI widgets --- packages/app-locale/src/localeKeys.ts | 2 + packages/app-locale/src/translations/enUS.ts | 2 + packages/app/src/assets/images/info.svg | 3 + .../components/MultisigAccountInfo/index.tsx | 9 +- .../MultisigMigrationProgressTabs/index.tsx | 85 +++++++++++++++++-- 5 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 packages/app/src/assets/images/info.svg diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index 0f987c6..c85adc4 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -95,4 +95,6 @@ export const localeKeys = { type: "type", assets: "assets", executed: "executed", + waitingDeploy: "waitingDeploy", + waitingDeployMessage: "waitingDeployMessage", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index ffdca80..81d791b 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -97,6 +97,8 @@ const enUs = { [localeKeys.type]: "Type", [localeKeys.assets]: "Assets", [localeKeys.executed]: "Executed", + [localeKeys.waitingDeploy]: "Waiting deploy", + [localeKeys.waitingDeployMessage]: `NOTE: This multisig account will need to be manually deployed on this page after the extrinsic is executed to complete the migration of the multi-signature account.`, }; export default enUs; diff --git a/packages/app/src/assets/images/info.svg b/packages/app/src/assets/images/info.svg new file mode 100644 index 0000000..98aac90 --- /dev/null +++ b/packages/app/src/assets/images/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index 906e542..aa18e0b 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -167,7 +167,14 @@ const MultisigAccountInfo = () => { }; return ( -
+
+
+
One more step! Please click
+
+ +
+
to complete the migration of the multisig account.
+
diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx index 5406c16..a1d195b 100644 --- a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -1,14 +1,23 @@ -import { Tabs, Tab } from "@darwinia/ui"; +import { Tabs, Tab, Tooltip } from "@darwinia/ui"; import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; import { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; import Identicon from "@polkadot/react-identicon"; import BigNumber from "bignumber.js"; +import crabIcon from "../../assets/images/crab.svg"; +import ringIcon from "../../assets/images/ring.svg"; +import cktonIcon from "../../assets/images/ckton.svg"; +import ktonIcon from "../../assets/images/kton.svg"; +import helpIcon from "../../assets/images/help.svg"; +import infoIcon from "../../assets/images/info.svg"; +import { useWallet } from "@darwinia/app-providers"; +import { prettifyNumber, prettifyTooltipNumber } from "@darwinia/app-utils"; const MultisigMigrationProgressTabs = () => { const { t } = useAppTranslation(); const [memberAccounts, setMemberAccounts] = useState(); const location = useLocation(); + const { selectedNetwork } = useWallet(); const params = new URLSearchParams(location.search); const address = params.get("address"); const members = (params.get("who") ?? "").split(","); @@ -53,6 +62,17 @@ const MultisigMigrationProgressTabs = () => { }, }; + const ringTokenIcon = selectedNetwork?.name === "Crab" ? crabIcon : ringIcon; + const ktonTokenIcon = selectedNetwork?.name === "Crab" ? cktonIcon : ktonIcon; + + const getRingTooltipMessage = () => { + return
Ring Message
; + }; + + const getKtonTooltipMessage = () => { + return
KTON Message
; + }; + return (
@@ -106,20 +126,67 @@ const MultisigMigrationProgressTabs = () => {
{t(localeKeys.parameters)}
-
Dest
-
{parameters.destination.account}
+
{t(localeKeys.destination)}
+
+
{parameters.destination.account}
+
+ + icon + +
{t(localeKeys.waitingDeploy)}
+
+
-
Dest
-
{parameters.destination.account}
+
{t(localeKeys.type)}
+
{parameters.type}
-
Dest
-
{parameters.destination.account}
+
{t(localeKeys.threshold)}
+
{parameters.threshold}
-
Dest
-
{parameters.destination.account}
+
{t(localeKeys.members)}
+
+ {parameters.member.map((item, index) => { + return
{item}
; + })} +
+
+
+
{t(localeKeys.asset)}
+
+
+
+ image +
+ + {prettifyNumber({ + number: parameters.asset.ring, + })} + + {selectedNetwork?.ring.symbol.toUpperCase()} +
+ + image + +
+
+ image +
+ + {prettifyNumber({ + number: parameters.asset.kton, + })} + + {selectedNetwork?.kton.symbol.toUpperCase()} +
+ + image + +
+
+
From 0f85a0d0da43c815834d0b5f513de05a8f96f95d Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Thu, 16 Mar 2023 15:39:52 +0800 Subject: [PATCH 13/36] Added multisig account creation logic --- packages/app-config/src/abi/contract.json | 86 ++++++++ packages/app-config/src/chains/crab.ts | 7 + packages/app-config/src/chains/darwinia.ts | 7 + packages/app-config/src/chains/pangolin.ts | 7 + packages/app-config/src/chains/pangoro.ts | 7 + packages/app-config/src/supportedNetworks.ts | 2 +- packages/app-locale/src/localeKeys.ts | 8 + packages/app-locale/src/translations/enUS.ts | 8 + .../app-providers/src/storageProvider.tsx | 21 ++ packages/app-providers/src/walletProvider.tsx | 191 ++++++++++-------- packages/app-types/src/storage.ts | 2 + packages/app-types/src/wallet.ts | 27 +-- packages/app-utils/src/others.ts | 15 +- packages/app/src/components/Header/index.tsx | 47 ++--- .../components/MultisigAccountInfo/index.tsx | 15 +- .../MultisigMigrationProcess/index.tsx | 92 ++++----- .../MultisigMigrationStatus/index.tsx | 2 +- .../pages/MultisigAccountMigrationSummary.tsx | 2 +- 18 files changed, 368 insertions(+), 178 deletions(-) create mode 100644 packages/app-config/src/abi/contract.json diff --git a/packages/app-config/src/abi/contract.json b/packages/app-config/src/abi/contract.json new file mode 100644 index 0000000..a9dfb74 --- /dev/null +++ b/packages/app-config/src/abi/contract.json @@ -0,0 +1,86 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Deployed", + "type": "event" + }, + { + "inputs": [], + "name": "bytecode", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "public_key", + "type": "bytes32" + }, + { + "internalType": "address[]", + "name": "owners", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "threhold", + "type": "uint256" + } + ], + "name": "computeAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "public_key", + "type": "bytes32" + }, + { + "internalType": "address[]", + "name": "owners", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "threhold", + "type": "uint256" + } + ], + "name": "deploy", + "outputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/packages/app-config/src/chains/crab.ts b/packages/app-config/src/chains/crab.ts index 211747e..0d9e17f 100644 --- a/packages/app-config/src/chains/crab.ts +++ b/packages/app-config/src/chains/crab.ts @@ -1,4 +1,5 @@ import { ChainConfig } from "@darwinia/app-types"; +import multisigContract from "../abi/contract.json"; export const crab: ChainConfig = { name: "Crab", @@ -13,6 +14,12 @@ export const crab: ChainConfig = { symbol: "CRAB", decimals: 9, }, + contractAddresses: { + multisig: "0x227c3e01071C2429766dDec2267A613e32DD463e", + }, + contractInterface: { + multisig: multisigContract, + }, chainId: 44, prefix: 42, substrate: { diff --git a/packages/app-config/src/chains/darwinia.ts b/packages/app-config/src/chains/darwinia.ts index 6ac4d06..880669e 100644 --- a/packages/app-config/src/chains/darwinia.ts +++ b/packages/app-config/src/chains/darwinia.ts @@ -1,4 +1,5 @@ import { ChainConfig } from "@darwinia/app-types"; +import multisigContract from "../abi/contract.json"; export const darwinia: ChainConfig = { name: "Darwinia", @@ -13,6 +14,12 @@ export const darwinia: ChainConfig = { symbol: "RING", decimals: 18, }, + contractAddresses: { + multisig: "0x227c3e01071C2429766dDec2267A613e32DD463e", //TODO update this accordingly + }, + contractInterface: { + multisig: multisigContract, + }, chainId: 46, prefix: 18, substrate: { diff --git a/packages/app-config/src/chains/pangolin.ts b/packages/app-config/src/chains/pangolin.ts index 6f3fe91..15fc0ff 100644 --- a/packages/app-config/src/chains/pangolin.ts +++ b/packages/app-config/src/chains/pangolin.ts @@ -1,4 +1,5 @@ import { ChainConfig } from "@darwinia/app-types"; +import multisigContract from "../abi/contract.json"; export const pangolin: ChainConfig = { name: "Pangolin", @@ -13,6 +14,12 @@ export const pangolin: ChainConfig = { symbol: "PRING", decimals: 9, }, + contractAddresses: { + multisig: "0x227c3e01071C2429766dDec2267A613e32DD463e", + }, + contractInterface: { + multisig: multisigContract, + }, chainId: 43, prefix: 42, substrate: { diff --git a/packages/app-config/src/chains/pangoro.ts b/packages/app-config/src/chains/pangoro.ts index e87e57d..6ac8674 100644 --- a/packages/app-config/src/chains/pangoro.ts +++ b/packages/app-config/src/chains/pangoro.ts @@ -1,4 +1,5 @@ import { ChainConfig } from "@darwinia/app-types"; +import multisigContract from "../abi/contract.json"; export const pangoro: ChainConfig = { name: "Pangoro", @@ -14,6 +15,12 @@ export const pangoro: ChainConfig = { symbol: "ORING", decimals: 9, }, + contractAddresses: { + multisig: "0xF09FE18d3f4b4345b47c098C8B8Fba7743418aA4", + }, + contractInterface: { + multisig: multisigContract, + }, chainId: 45, prefix: 18, substrate: { diff --git a/packages/app-config/src/supportedNetworks.ts b/packages/app-config/src/supportedNetworks.ts index c0c1ab7..d8fc02a 100644 --- a/packages/app-config/src/supportedNetworks.ts +++ b/packages/app-config/src/supportedNetworks.ts @@ -4,4 +4,4 @@ import { darwinia } from "./chains/darwinia"; import { pangolin } from "./chains/pangolin"; import { pangoro } from "./chains/pangoro"; -export const supportedNetworks: ChainConfig[] = [crab, pangolin, pangoro]; +export const supportedNetworks: ChainConfig[] = [crab, pangoro]; diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index c85adc4..a9b8845 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -97,4 +97,12 @@ export const localeKeys = { executed: "executed", waitingDeploy: "waitingDeploy", waitingDeployMessage: "waitingDeployMessage", + oneMoreStep: "oneMoreStep", + deploy: "deploy", + toCompleteMigration: "toCompleteMigration", + multisigMigrationSuccessful: "multisigMigrationSuccessful", + invalidName: "invalidName", + invalidThreshold: "invalidThreshold", + invalidMemberAddress: "invalidMemberAddress", + selectYourAddress: "selectYourAddress", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index 81d791b..2b2e5dd 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -99,6 +99,14 @@ const enUs = { [localeKeys.executed]: "Executed", [localeKeys.waitingDeploy]: "Waiting deploy", [localeKeys.waitingDeployMessage]: `NOTE: This multisig account will need to be manually deployed on this page after the extrinsic is executed to complete the migration of the multi-signature account.`, + [localeKeys.oneMoreStep]: "One more step! Please click", + [localeKeys.deploy]: "Deploy", + [localeKeys.toCompleteMigration]: "to complete the migration of the multisig account.", + [localeKeys.multisigMigrationSuccessful]: `This Multisig account has been migrated successfully. Please note that version 2.0 still supports multisig, and you can use Gnosis Safe Multisig - IPFS to perform multisig operations on 2.0.`, + [localeKeys.invalidName]: "Invalid name", + [localeKeys.invalidThreshold]: "Invalid threshold", + [localeKeys.invalidMemberAddress]: "Invalid member address", + [localeKeys.selectYourAddress]: "Select your address", }; export default enUs; diff --git a/packages/app-providers/src/storageProvider.tsx b/packages/app-providers/src/storageProvider.tsx index b9fa082..48c9fd8 100644 --- a/packages/app-providers/src/storageProvider.tsx +++ b/packages/app-providers/src/storageProvider.tsx @@ -6,6 +6,8 @@ import useLedger from "./hooks/useLedger"; import { keyring } from "@polkadot/ui-keyring"; import { StorageKey } from "@polkadot/types"; import type { AnyTuple, Codec } from "@polkadot/types/types"; +import { Contract, ethers } from "ethers"; +import { createMultiSigAccount } from "@darwinia/app-utils"; const initialState: StorageCtx = { migrationAssetDistribution: undefined, @@ -19,6 +21,7 @@ const initialState: StorageCtx = { }, isAccountFree: undefined, migratedAssetDistribution: undefined, + multisigContract: undefined, }; export type UnSubscription = () => void; @@ -29,6 +32,7 @@ export const StorageProvider = ({ children }: PropsWithChildren) => { const { selectedNetwork, selectedAccount } = useWallet(); const [apiPromise, setApiPromise] = useState(); const [isAccountFree, setAccountFree] = useState(false); + const [multisigContract, setMultisigContract] = useState(); const { isLoadingLedger, @@ -116,6 +120,22 @@ export const StorageProvider = ({ children }: PropsWithChildren) => { initStorageNetwork(selectedNetwork.substrate.wssURL); }, [selectedNetwork]); + useEffect(() => { + if (!selectedAccount || !selectedNetwork) { + return; + } + //refresh the page with the newly selected account + const newProvider = new ethers.providers.Web3Provider(window.ethereum); + const newSigner = newProvider.getSigner(); + const newMultisigContract = new ethers.Contract( + selectedNetwork.contractAddresses.multisig, + selectedNetwork.contractInterface.multisig, + newSigner + ); + + setMultisigContract(newMultisigContract); + }, [selectedAccount, selectedNetwork]); + return ( { isLoadingMigratedLedger, retrieveMigratedAsset, migratedAssetDistribution, + multisigContract, }} > {children} diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index 68ca569..d333cb0 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -11,7 +11,6 @@ import { SpVersionRuntimeVersion, PalletVestingVestingInfo, DarwiniaAccountMigrationAssetAccount, - CreateOptions, MultisigAccount, } from "@darwinia/app-types"; import { ApiPromise, WsProvider, SubmittableResult } from "@polkadot/api"; @@ -24,7 +23,7 @@ import BigNumber from "bignumber.js"; import { FrameSystemAccountInfo } from "@darwinia/api-derive/accounts/types"; import { UnSubscription } from "./storageProvider"; import { Option, Vec } from "@polkadot/types"; -import { convertToSS58, setStore, getStore } from "@darwinia/app-utils"; +import { convertToSS58, setStore, getStore, createMultiSigAccount } from "@darwinia/app-utils"; /*This is just a blueprint, no value will be stored in here*/ const initialState: WalletCtx = { @@ -64,7 +63,7 @@ const initialState: WalletCtx = { setMultisig: (value: boolean) => { //ignore }, - checkDarwiniaOneMultisigAccount: (signatories: string[], threshold: number, { name, tags = [] }: CreateOptions) => { + checkDarwiniaOneMultisigAccount: (signatories: string[], threshold: number, name?: string) => { return Promise.resolve(undefined); }, }; @@ -95,11 +94,11 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const setSelectedWallet = useCallback((name: SupportedWallet | null | undefined) => { _setSelectedWallet(name); - setStore('selectedWallet', name); + setStore("selectedWallet", name); }, []); useEffect(() => { - _setSelectedWallet(getStore('selectedWallet')); + _setSelectedWallet(getStore("selectedWallet")); }, []); useEffect(() => { @@ -194,6 +193,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { useEffect(() => { const parseAccounts = async () => { if (!apiPromise) { + // don't query account balances if you're on multisig account migration setLoadingBalance(false); return; } @@ -202,8 +202,13 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const accounts = injectedAccountsRef.current; for (let i = 0; i < accounts.length; i++) { - const prettyName = await getPrettyName(accounts[i].address); - const balance = await getAccountBalance(accounts[i].address); + const prettyName = isMultisig ? "" : await getPrettyName(accounts[i].address); + const balance = isMultisig + ? { + ring: BigNumber(0), + kton: BigNumber(0), + } + : await getAccountBalance(accounts[i].address); customAccounts.push({ ...accounts[i], prettyName, @@ -236,86 +241,89 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { setLoadingBalance(false); //ignore }); - }, [injectedAccountsRef.current, apiPromise, selectedNetwork]); + }, [injectedAccountsRef.current, apiPromise, selectedNetwork, isMultisig]); /*Connect to MetaMask*/ - const connectWallet = useCallback(async (name: SupportedWallet) => { - if (!selectedNetwork || isRequestingWalletConnection) { - return; - } - - const walletCfg = dAppSupportedWallets.find((item) => item.name === name); - if (!walletCfg) { - return; - } - - const injecteds = window.injectedWeb3; - const source = injecteds && walletCfg.sources.find((source) => injecteds[source]); - if (!source) { - setWalletConnected(false); - setRequestingWalletConnection(false); - setLoadingTransaction(false); - setLoadingBalance(false); - setError({ - code: 1, - message: "Please Install Polkadot JS Extension", - }); - return; - } + const connectWallet = useCallback( + async (name: SupportedWallet) => { + if (!selectedNetwork || isRequestingWalletConnection) { + return; + } - try { - setWalletConnected(false); - setRequestingWalletConnection(true); - const provider = new WsProvider(selectedNetwork.substrate.wssURL); - const api = new ApiPromise({ - provider, - }); + const walletCfg = dAppSupportedWallets.find((item) => item.name === name); + if (!walletCfg) { + return; + } - api.on("connected", async () => { - const readyAPI = await api.isReady; - setApiPromise(readyAPI); + const injecteds = window.injectedWeb3; + const source = injecteds && walletCfg.sources.find((source) => injecteds[source]); + if (!source) { + setWalletConnected(false); setRequestingWalletConnection(false); - }); - api.on("disconnected", () => { - // console.log("disconnected"); - }); - api.on("error", () => { - // console.log("error"); - }); - - const wallet = injecteds[source]; - if (!wallet.enable) { + setLoadingTransaction(false); + setLoadingBalance(false); + setError({ + code: 1, + message: "Please Install Polkadot JS Extension", + }); return; } - const res = await wallet.enable(DARWINIA_APPS); - if (res) { - const enabledExtensions = [res]; - - /* this is the signer that needs to be used when we sign a transaction */ - setSigner(enabledExtensions[0].signer); - /* this will return a list of all the accounts that are in the Polkadot extension */ - const unfilteredAccounts = await res.accounts.get(); - const accounts = unfilteredAccounts - .filter((account) => !account.address.startsWith("0x")) - .map(({ address, genesisHash, name, type }) => ({ address, type, meta: { genesisHash, name, source } })); - accounts.forEach((account) => { - keyring.saveAddress(account.address, account.meta); + + try { + setWalletConnected(false); + setRequestingWalletConnection(true); + const provider = new WsProvider(selectedNetwork.substrate.wssURL); + const api = new ApiPromise({ + provider, }); - injectedAccountsRef.current = accounts; - if (accounts.length > 0) { - /* we default using the first account */ - setWalletConnected(true); + api.on("connected", async () => { + const readyAPI = await api.isReady; + setApiPromise(readyAPI); + setRequestingWalletConnection(false); + }); + api.on("disconnected", () => { + // console.log("disconnected"); + }); + api.on("error", () => { + // console.log("error"); + }); + + const wallet = injecteds[source]; + if (!wallet.enable) { + return; + } + const res = await wallet.enable(DARWINIA_APPS); + if (res) { + const enabledExtensions = [res]; + + /* this is the signer that needs to be used when we sign a transaction */ + setSigner(enabledExtensions[0].signer); + /* this will return a list of all the accounts that are in the Polkadot extension */ + const unfilteredAccounts = await res.accounts.get(); + const accounts = unfilteredAccounts + .filter((account) => !account.address.startsWith("0x")) + .map(({ address, genesisHash, name, type }) => ({ address, type, meta: { genesisHash, name, source } })); + accounts.forEach((account) => { + keyring.saveAddress(account.address, account.meta); + }); + injectedAccountsRef.current = accounts; + + if (accounts.length > 0) { + /* we default using the first account */ + setWalletConnected(true); + } + setSelectedWallet(name); } - setSelectedWallet(name); + } catch (e) { + setWalletConnected(false); + setRequestingWalletConnection(false); + setLoadingBalance(false); + //ignore } - } catch (e) { - setWalletConnected(false); - setRequestingWalletConnection(false); - setLoadingBalance(false); - //ignore - } - }, [selectedNetwork, isRequestingWalletConnection, apiPromise, getPrettyName]); + }, + [selectedNetwork, isRequestingWalletConnection, apiPromise, getPrettyName] + ); const changeSelectedNetwork = useCallback( (network: ChainConfig) => { @@ -396,30 +404,35 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { }, [apiPromise]); const checkDarwiniaOneMultisigAccount = useCallback( - (signatories: string[], threshold: number, { name, tags = [] }: CreateOptions) => { - if (!apiPromise) { + async (signatories: string[], threshold: number, name?: string) => { + if (!apiPromise || !selectedNetwork) { return Promise.resolve(undefined); } - const genesisHash = apiPromise.genesisHash.toString(); + const newAccount = createMultiSigAccount(signatories, selectedNetwork.prefix, threshold); + //check if the account really exists + const response = await apiPromise.query.accountMigration.accounts(newAccount); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const accountInfo = response as unknown as Option; + if (!accountInfo || !accountInfo.isSome) { + // the account doesn't exist on the chain + return Promise.resolve(undefined); + } - const accountResult = keyring.addMultisig(signatories, threshold, { genesisHash, name, tags }); - const { pair } = accountResult; - const { meta } = pair; + // the account exists on the chain const account: MultisigAccount = { - address: pair.address, - type: pair.type, + address: newAccount, meta: { - genesisHash: meta.genesisHash?.toString() ?? "", - name: (meta.name ?? "") as string, - who: (meta.who ?? []) as string[], - threshold: (meta.threshold ?? 1) as number, + name: name ?? "", + who: [...signatories], + threshold, }, }; return Promise.resolve(account); }, - [apiPromise] + [apiPromise, selectedNetwork] ); return ( diff --git a/packages/app-types/src/storage.ts b/packages/app-types/src/storage.ts index 38040e9..d1be3ae 100644 --- a/packages/app-types/src/storage.ts +++ b/packages/app-types/src/storage.ts @@ -1,4 +1,5 @@ import BigNumber from "bignumber.js"; +import { Contract } from "@ethersproject/contracts"; export interface AssetDetail { deposit?: BigNumber; @@ -27,4 +28,5 @@ export interface StorageCtx { migratedAssetDistribution: AssetDistribution | undefined; checkEVMAccountStatus: (accountId: string) => Promise; isAccountFree: boolean | undefined; + multisigContract: Contract | undefined; } diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index f8d471e..a5f3322 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -5,6 +5,7 @@ export type SupportedWallet = "Polkadot{.js}" | "Talisman" | "SubWallet"; export type SupportedBrowser = "Chrome" | "Firefox" | "Brave" | "Edge" | "Opera"; export type ChainName = "Crab" | "Pangolin" | "Darwinia" | "Pangoro"; import { Struct } from "@polkadot/types"; +import { ContractInterface } from "@ethersproject/contracts"; export interface Token { name?: string; @@ -21,6 +22,14 @@ export interface Substrate { graphQlURL: string; } +export interface ContractABI { + multisig: ContractInterface; +} + +export interface ContractAddress { + multisig: string; +} + export interface ChainConfig { name: ChainName; // this name is used to set the chain name in MetaMask, the user will later see this name on Metamask displayName: string; // This name is used on the dApp just for the user to see @@ -29,6 +38,8 @@ export interface ChainConfig { kton: Token; prefix: number; substrate: Substrate; + contractInterface: ContractABI; + contractAddresses: ContractAddress; } export interface WalletExtension { @@ -37,11 +48,11 @@ export interface WalletExtension { } export type WalletSource = - | 'polkadot-js' + | "polkadot-js" | '"polkadot-js"' - | 'talisman' + | "talisman" | '"talisman"' - | 'subwallet-js' + | "subwallet-js" | '"subwallet-js"'; export interface WalletConfig { @@ -85,7 +96,7 @@ export interface WalletCtx { checkDarwiniaOneMultisigAccount: ( signatories: string[], threshold: number, - { name, tags = [] }: CreateOptions + name?: string ) => Promise; } @@ -93,21 +104,13 @@ export interface SpVersionRuntimeVersion extends Struct { specName: string; } -export interface CreateOptions { - genesisHash?: string; - name: string; - tags?: string[]; -} - export interface MultisigAccountMeta { who: string[]; - genesisHash: string; name: string; threshold: number; } export interface MultisigAccount { address: string; - type: string; meta: MultisigAccountMeta; } diff --git a/packages/app-utils/src/others.ts b/packages/app-utils/src/others.ts index e16ad1f..ecada0f 100644 --- a/packages/app-utils/src/others.ts +++ b/packages/app-utils/src/others.ts @@ -2,7 +2,7 @@ import { Storage } from "@darwinia/app-types"; import { STORAGE as APP_STORAGE } from "@darwinia/app-config"; import BigNumber from "bignumber.js"; import { ethers, utils } from "ethers"; -import { encodeAddress } from "@polkadot/util-crypto"; +import { encodeAddress, createKeyMulti, sortAddresses, isAddress } from "@polkadot/util-crypto"; export const setStore = (key: keyof Storage, value: unknown) => { try { @@ -120,3 +120,16 @@ export function convertToSS58(text: string, prefix: number, isShort = false): st return ""; } } + +export const createMultiSigAccount = (addresses: string[], prefix: number, threshold = 1) => { + const multiAddress = createKeyMulti(addresses, threshold); + + // Convert byte array to SS58 encoding. pangoro-18, crab-42 + const ss58Address = encodeAddress(multiAddress, prefix); + + return ss58Address; +}; + +export const isSubstrateAddress = (address: string) => { + return isAddress(address); +}; diff --git a/packages/app/src/components/Header/index.tsx b/packages/app/src/components/Header/index.tsx index 67d2cb3..252d7c9 100644 --- a/packages/app/src/components/Header/index.tsx +++ b/packages/app/src/components/Header/index.tsx @@ -127,31 +127,32 @@ const Header = () => {
); })} - {!isMultisig && selectedAccount ? ( -
-
- ... -
-
{selectedAccount.prettyName ?? toShortAddress(selectedAccount.formattedAddress)}
- image + {!isMultisig && + (selectedAccount ? ( +
+
+ ... +
+
{selectedAccount.prettyName ?? toShortAddress(selectedAccount.formattedAddress)}
+ image +
-
- ) : ( - - )} + ) : ( + + ))}
{/*network switch toggle*/}
{ return (
-
One more step! Please click
-
- +
{t(localeKeys.oneMoreStep)}
+
+
-
to complete the migration of the multisig account.
+
{t(localeKeys.toCompleteMigration)}
+
+
+
diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 334cf76..8cb8833 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -10,7 +10,7 @@ import { CustomInjectedAccountWithMeta, MultisigAccount } from "@darwinia/app-ty import noDataIcon from "../../assets/images/no-data.svg"; import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; -import { getStore, prettifyNumber, setStore } from "@darwinia/app-utils"; +import { getStore, isSubstrateAddress, isValidNumber, prettifyNumber, setStore } from "@darwinia/app-utils"; import { useLocation, useNavigate } from "react-router-dom"; interface Asset { @@ -33,15 +33,14 @@ const MultisigMigrationProcess = () => { const { t } = useAppTranslation(); const navigate = useNavigate(); const location = useLocation(); - const [showMigrationForm, setShowMigrationForm] = useState(false); const currentAccount = useRef(); const canShowAccountNotification = useRef(false); const [isAddMultisigModalVisible, setAddMultisigModalVisibility] = useState(false); const accountsOptions: OptionProps[] = (injectedAccounts?.map((item, index) => { return { id: index, - value: item.address, - label: item.address, + value: item.formattedAddress, + label: item.formattedAddress, }; }) ?? []) as unknown as OptionProps[]; @@ -175,10 +174,6 @@ const MultisigMigrationProcess = () => { setMultisigAccountsList(data); }, []); - useEffect(() => { - console.log("list changed"); - }, [multisigAccountsList]); - useEffect(() => { if (currentAccount.current?.address !== selectedAccount?.address) { currentAccount.current = selectedAccount; @@ -186,39 +181,10 @@ const MultisigMigrationProcess = () => { } }, [selectedAccount]); - useEffect(() => { - if (migrationAssetDistribution && !isLoadingLedger) { - const hasRingAmount = - migrationAssetDistribution.ring.transferable.gt(0) || - migrationAssetDistribution.ring.deposit?.gt(0) || - migrationAssetDistribution.ring.bonded.gt(0) || - migrationAssetDistribution.ring.unbonded.gt(0) || - migrationAssetDistribution.ring.unbonding.gt(0) || - migrationAssetDistribution.ring.vested?.gt(0); - const hasKtonAmount = - migrationAssetDistribution.kton.transferable.gt(0) || - migrationAssetDistribution.kton.bonded.gt(0) || - migrationAssetDistribution.kton.unbonded.gt(0) || - migrationAssetDistribution.kton.unbonding.gt(0); - if (hasRingAmount || hasKtonAmount) { - setShowMigrationForm(true); - } else { - // makes sure that the prompt is only shown once when the selected account changes - if (canShowAccountNotification.current) { - canShowAccountNotification.current = false; - notification.error({ - message:
{t(localeKeys.noTokensToMigrate)}
, - }); - } - setShowMigrationForm(false); - } - } - }, [migrationAssetDistribution, isLoadingLedger]); - const footerLinks = [ { title: t(localeKeys.howToMigrate), - url: "https://www.baidu.com", + url: "https://www.notion.so/itering/How-to-migrate-the-account-to-Crab-2-0-9b8f835c914f44a29d9727a0a03b9f5d", }, { title: t(localeKeys.darwiniaMergeOverview), @@ -234,23 +200,52 @@ const MultisigMigrationProcess = () => { setAddMultisigModalVisibility(true); }; + const resetAddAccountForm = () => { + setThreshold(""); + setName(""); + setMemberAddresses([{ id: new Date().getTime(), address: "" }]); + setSelectedAddress(""); + setCheckingAccountExistence(false); + }; + const onCloseAddAccountModal = () => { setAddMultisigModalVisibility(false); + resetAddAccountForm(); }; const onCreateMultisigAccount = async () => { try { - setCheckingAccountExistence(true); - const signatories = memberAddresses.map((item) => item.address); + const signatories = memberAddresses + .map((item) => item.address) + .filter((item) => item.length > 0 && isSubstrateAddress(item)); signatories.unshift(selectedAddress); + if (name.trim().length === 0) { + notification.success({ + message:
{t(localeKeys.invalidName)}
, + }); + return; + } + if (!isValidNumber(threshold)) { + notification.success({ + message:
{t(localeKeys.invalidThreshold)}
, + }); + return; + } + if (selectedAddress.trim().length === 0) { + notification.success({ + message:
{t(localeKeys.selectYourAddress)}
, + }); + return; + } + setCheckingAccountExistence(true); const thresholdNumber = Number(threshold); - const account = await checkDarwiniaOneMultisigAccount(signatories, thresholdNumber, { name }); + const account = await checkDarwiniaOneMultisigAccount(signatories, thresholdNumber, name); setCheckingAccountExistence(false); - console.log(account); + if (typeof account === "undefined") { notification.success({ message:
{t(localeKeys.multisigCreationFailed)}
, - duration: 10000, + duration: 15000, }); return; } @@ -263,7 +258,10 @@ const MultisigMigrationProcess = () => { setMultisigAccountsList((old) => { return [...old, ...data]; }); + //hide modal + onCloseAddAccountModal(); } catch (e) { + console.log(e); setCheckingAccountExistence(false); //ignore } @@ -314,9 +312,11 @@ const MultisigMigrationProcess = () => {
{t(localeKeys.multisig)}
- + {multisigAccountsList.length > 0 && ( + + )}
diff --git a/packages/app/src/components/MultisigMigrationStatus/index.tsx b/packages/app/src/components/MultisigMigrationStatus/index.tsx index 0849a27..3738554 100644 --- a/packages/app/src/components/MultisigMigrationStatus/index.tsx +++ b/packages/app/src/components/MultisigMigrationStatus/index.tsx @@ -105,7 +105,7 @@ const MultisigMigrationStatus = ({ accountMigration }: Props) => { const footerLinks = [ { title: t(localeKeys.howToMigrate), - url: "https://www.baidu.com", + url: "https://www.notion.so/itering/How-to-migrate-the-account-to-Crab-2-0-9b8f835c914f44a29d9727a0a03b9f5d", }, { title: t(localeKeys.darwiniaMergeOverview), diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx index 5f78e04..ae7c0aa 100644 --- a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -19,7 +19,7 @@ const MultisigAccountMigrationSummary = () => { const footerLinks = [ { title: t(localeKeys.howToMigrate), - url: "https://www.baidu.com", + url: "https://www.notion.so/itering/How-to-migrate-the-account-to-Crab-2-0-9b8f835c914f44a29d9727a0a03b9f5d", }, { title: t(localeKeys.darwiniaMergeOverview), From aaf6f2f8abfc2d8f07695cc1a4157d5915e337bf Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Tue, 21 Mar 2023 11:18:25 +0800 Subject: [PATCH 14/36] Multisig accounts balances can be fetched --- packages/app-providers/src/hooks/useBlock.ts | 6 +- packages/app-providers/src/hooks/useLedger.ts | 82 ++++++--- .../app-providers/src/storageProvider.tsx | 32 +--- packages/app-providers/src/walletProvider.tsx | 128 +++++++------ packages/app-types/src/wallet.ts | 11 ++ packages/app/src/Root.tsx | 5 +- .../src/components/MigrationSummary/index.tsx | 173 ------------------ .../MultisigMigrationProcess/index.tsx | 56 ++++-- .../components/TokensBalanceSummary/index.tsx | 10 +- 9 files changed, 192 insertions(+), 311 deletions(-) diff --git a/packages/app-providers/src/hooks/useBlock.ts b/packages/app-providers/src/hooks/useBlock.ts index 99d4d94..d4acdb0 100644 --- a/packages/app-providers/src/hooks/useBlock.ts +++ b/packages/app-providers/src/hooks/useBlock.ts @@ -2,11 +2,7 @@ import { useEffect, useState } from "react"; import { ApiPromise } from "@polkadot/api"; import { UnSubscription } from "../storageProvider"; import { Header } from "@polkadot/types/interfaces"; - -interface CurrentBlock { - number: number; - timestamp: number; -} +import { CurrentBlock } from "@darwinia/app-types"; const useBlock = (apiPromise: ApiPromise | undefined) => { const [currentBlock, setCurrentBlock] = useState(); diff --git a/packages/app-providers/src/hooks/useLedger.ts b/packages/app-providers/src/hooks/useLedger.ts index 0269f54..a4ddec1 100644 --- a/packages/app-providers/src/hooks/useLedger.ts +++ b/packages/app-providers/src/hooks/useLedger.ts @@ -45,29 +45,38 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => }, [selectedAccount, selectedNetwork]); const getAccountAsset = useCallback( - (accountId: string, parentBlockHash?: string) => { + async ( + accountId: string, + parentBlockHash?: string, + hasReturnOutput = false, + showLoading = true + ): Promise => { const isDataAtPoint = typeof parentBlockHash !== "undefined"; - const getStakingLedgerAndDeposits = async () => { + const getStakingLedgerAndDeposits = async (): Promise => { if (!apiPromise || !currentBlock) { setLoadingMigratedLedger(false); setLoadingLedger(false); - return; + return Promise.resolve(undefined); } const api = isDataAtPoint ? await apiPromise.at(parentBlockHash ?? "") : apiPromise; - if (isInitialLoad.current && !isDataAtPoint) { - isInitialLoad.current = false; - setLoadingLedger(true); - } else { - setLoadingLedger(false); + if (showLoading) { + if (isInitialLoad.current && !isDataAtPoint) { + isInitialLoad.current = false; + setLoadingLedger(true); + } else { + setLoadingLedger(false); + } } - if (isInitialMigratedDataLoad.current && isDataAtPoint) { - isInitialMigratedDataLoad.current = false; - setLoadingMigratedLedger(true); - } else { - setLoadingMigratedLedger(false); + if (showLoading) { + if (isInitialMigratedDataLoad.current && isDataAtPoint) { + isInitialMigratedDataLoad.current = false; + setLoadingMigratedLedger(true); + } else { + setLoadingMigratedLedger(false); + } } let ktonBalance = BigNumber(0); @@ -123,9 +132,9 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => const parseData = ( ledgerOption: Option | undefined, depositsOption: Option> | undefined - ) => { + ): AssetDistribution | undefined => { if (!ledgerOption || !depositsOption) { - return; + return undefined; } let totalDepositsAmount = BigNumber(0); @@ -207,7 +216,7 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => .minus(unbondingKtonAmount); if (isDataAtPoint) { - setMigratedAssetDistribution({ + const asset = { ring: { transferable: transferableRing, deposit: totalDepositsAmount, @@ -222,9 +231,13 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => unbonded: unbondedKtonAmount, unbonding: unbondingKtonAmount, }, - }); + }; + if (!hasReturnOutput) { + setMigratedAssetDistribution(asset); + } + return asset; } else { - setStakedAssetDistribution({ + const asset = { ring: { transferable: transferableRing, deposit: totalDepositsAmount, @@ -239,7 +252,11 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => unbonded: unbondedKtonAmount, unbonding: unbondingKtonAmount, }, - }); + }; + if (!hasReturnOutput) { + setStakedAssetDistribution(asset); + } + return asset; } } else { // this user never took part in staking @@ -249,7 +266,7 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => : BigNumber(0); const transferableKTON = ktonBalance; - setMigratedAssetDistribution({ + const asset = { ring: { transferable: transferableRing, deposit: totalDepositsAmount, @@ -264,12 +281,16 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => unbonded: BigNumber(0), unbonding: BigNumber(0), }, - }); + }; + if (!hasReturnOutput) { + setMigratedAssetDistribution(asset); + } + return asset; } else { const transferableRing = totalBalance.gt(0) ? totalBalance.plus(reservedAmount).minus(totalDepositsAmount) : BigNumber(0); - setStakedAssetDistribution({ + const asset = { ring: { transferable: transferableRing, deposit: totalDepositsAmount, @@ -284,20 +305,25 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => unbonded: BigNumber(0), unbonding: BigNumber(0), }, - }); + }; + if (!hasReturnOutput) { + setStakedAssetDistribution(asset); + } + return asset; } } }; - parseData(ledgerInfo, depositsInfo); + const asset = parseData(ledgerInfo, depositsInfo); if (isDataAtPoint) { setLoadingMigratedLedger(false); } else { setLoadingLedger(false); } + return Promise.resolve(asset); }; - getStakingLedgerAndDeposits().catch((e) => { + const asset = await getStakingLedgerAndDeposits().catch((e) => { if (isDataAtPoint) { setMigratedAssetDistribution({ ring: { @@ -339,12 +365,15 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => // console.log(e); //ignore }); + + return Promise.resolve(asset); }, - [apiPromise, currentBlock, selectedAccount] + [apiPromise, currentBlock] ); /*Get staking ledger and deposits. The data that comes back from the server needs a lot of decoding */ useEffect(() => { + /* get new balance for every newly selected account */ if (selectedAccount) { getAccountAsset(selectedAccount); } @@ -358,6 +387,7 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => ); return { + getAccountAsset, isLoadingLedger, stakedAssetDistribution, isLoadingMigratedLedger, diff --git a/packages/app-providers/src/storageProvider.tsx b/packages/app-providers/src/storageProvider.tsx index 48c9fd8..a9b067b 100644 --- a/packages/app-providers/src/storageProvider.tsx +++ b/packages/app-providers/src/storageProvider.tsx @@ -29,8 +29,7 @@ export type UnSubscription = () => void; const StorageContext = createContext(initialState); export const StorageProvider = ({ children }: PropsWithChildren) => { - const { selectedNetwork, selectedAccount } = useWallet(); - const [apiPromise, setApiPromise] = useState(); + const { selectedNetwork, selectedAccount, apiPromise } = useWallet(); const [isAccountFree, setAccountFree] = useState(false); const [multisigContract, setMultisigContract] = useState(); @@ -91,35 +90,6 @@ export const StorageProvider = ({ children }: PropsWithChildren) => { [apiPromise] ); - const initStorageNetwork = async (rpcURL: string) => { - try { - const provider = new WsProvider(rpcURL); - const api = new ApiPromise({ - provider, - }); - - api.on("connected", async () => { - const readyAPI = await api.isReady; - setApiPromise(readyAPI); - }); - api.on("disconnected", () => { - // console.log("disconnected"); - }); - api.on("error", () => { - // console.log("error"); - }); - } catch (e) { - //ignore - } - }; - - useEffect(() => { - if (!selectedNetwork) { - return; - } - initStorageNetwork(selectedNetwork.substrate.wssURL); - }, [selectedNetwork]); - useEffect(() => { if (!selectedAccount || !selectedNetwork) { return; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index d333cb0..b26ce04 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -12,6 +12,11 @@ import { PalletVestingVestingInfo, DarwiniaAccountMigrationAssetAccount, MultisigAccount, + DarwiniaStakingLedgerEncoded, + DepositEncoded, + Deposit, + DarwiniaStakingLedger, + AssetDistribution, } from "@darwinia/app-types"; import { ApiPromise, WsProvider, SubmittableResult } from "@polkadot/api"; import { web3Accounts, web3Enable } from "@polkadot/extension-dapp"; @@ -24,6 +29,8 @@ import { FrameSystemAccountInfo } from "@darwinia/api-derive/accounts/types"; import { UnSubscription } from "./storageProvider"; import { Option, Vec } from "@polkadot/types"; import { convertToSS58, setStore, getStore, createMultiSigAccount } from "@darwinia/app-utils"; +import useBlock from "./hooks/useBlock"; +import useLedger from "./hooks/useLedger"; /*This is just a blueprint, no value will be stored in here*/ const initialState: WalletCtx = { @@ -41,6 +48,10 @@ const initialState: WalletCtx = { walletConfig: undefined, isMultisig: undefined, isLoadingBalance: undefined, + apiPromise: undefined, + currentBlock: undefined, + isLoadingMultisigBalance: undefined, + setLoadingMultisigBalance: (isLoading: boolean) => {}, changeSelectedNetwork: () => { // do nothing }, @@ -66,6 +77,12 @@ const initialState: WalletCtx = { checkDarwiniaOneMultisigAccount: (signatories: string[], threshold: number, name?: string) => { return Promise.resolve(undefined); }, + getAccountBalance: (account: string) => { + return Promise.resolve({ + ring: BigNumber(0), + kton: BigNumber(0), + }); + }, }; const WalletContext = createContext(initialState); @@ -83,6 +100,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [walletConfig, setWalletConfig] = useState(); const [isLoadingTransaction, setLoadingTransaction] = useState(false); const [isLoadingBalance, setLoadingBalance] = useState(false); + const isInitialLoadingBalance = useRef(true); const [apiPromise, setApiPromise] = useState(); const { getPrettyName } = useAccountPrettyName(apiPromise); const DARWINIA_APPS = "darwinia/apps"; @@ -90,8 +108,16 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [isAccountMigratedJustNow, setAccountMigratedJustNow] = useState(false); const [specName, setSpecName] = useState(); const [isMultisig, setMultisig] = useState(false); + const [isLoadingMultisigBalance, setLoadingMultisigBalance] = useState(false); const [selectedWallet, _setSelectedWallet] = useState(); + const { currentBlock } = useBlock(apiPromise); + const { getAccountAsset } = useLedger({ + apiPromise, + selectedAccount: selectedAccount?.formattedAddress, + selectedNetwork, + }); + const setSelectedWallet = useCallback((name: SupportedWallet | null | undefined) => { _setSelectedWallet(name); setStore("selectedWallet", name); @@ -101,6 +127,11 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { _setSelectedWallet(getStore("selectedWallet")); }, []); + //makes sure that the loading spinner shows once the wallet is connected + useEffect(() => { + setLoadingBalance(isWalletConnected); + }, [isWalletConnected]); + useEffect(() => { const walletConfig = dAppSupportedWallets.find((walletConfig) => walletConfig.name === selectedWallet); if (walletConfig) { @@ -130,100 +161,70 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const getAccountBalance = useCallback( async (accountAddress: string): Promise => { - if (!apiPromise) { + if (!apiPromise || !currentBlock) { return Promise.resolve({ ring: BigNumber(0), kton: BigNumber(0), }); } - - let transferableKTON = BigNumber(0); - const ktonAccountInfo: Option = - (await apiPromise.query.accountMigration.ktonAccounts( - accountAddress - )) as unknown as Option; - if (ktonAccountInfo.isSome) { - const unwrappedKTONAccount = ktonAccountInfo.unwrap(); - const decodedKTONAccount = unwrappedKTONAccount.toHuman() as unknown as DarwiniaAccountMigrationAssetAccount; - const ktonBalanceString = decodedKTONAccount.balance.toString().replaceAll(",", ""); - transferableKTON = BigNumber(ktonBalanceString); - } - - /*We don't need to listen to account changes since the chain won't be producing blocks - * by that time */ - const response = await apiPromise.query.accountMigration.accounts(accountAddress); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const accountInfoOption = response as unknown as Option; - if (accountInfoOption.isSome) { - let vestedAmountRing = BigNumber(0); - let totalBalance = BigNumber(0); - - const unwrappedAccountInfo = accountInfoOption.unwrap(); - const accountInfo = unwrappedAccountInfo.toHuman() as unknown as FrameSystemAccountInfo; - const balance = accountInfo.data.free.toString().replaceAll(",", ""); - totalBalance = BigNumber(balance); - - const vestingInfoOption = (await apiPromise.query.accountMigration.vestings( - accountAddress - )) as unknown as Option>; - if (vestingInfoOption.isSome) { - const unwrappedVestingInfo = vestingInfoOption.unwrap(); - const vestingInfoList = unwrappedVestingInfo.toHuman() as unknown as Vec; - vestingInfoList.forEach((vesting) => { - const lockedAmount = vesting.locked.toString().replaceAll(",", ""); - vestedAmountRing = vestedAmountRing.plus(lockedAmount); - }); - } - - return Promise.resolve({ - ring: totalBalance.minus(vestedAmountRing), // this is the transferable amount - kton: transferableKTON, - }); - } - + const asset = await getAccountAsset(accountAddress, undefined, true, false); return Promise.resolve({ - ring: BigNumber(0), - kton: BigNumber(0), + ring: asset?.ring.transferable ?? BigNumber(0), + kton: asset?.kton.transferable ?? BigNumber(0), }); }, - [apiPromise] + [apiPromise, currentBlock] ); useEffect(() => { const parseAccounts = async () => { - if (!apiPromise) { - // don't query account balances if you're on multisig account migration - setLoadingBalance(false); + if (!apiPromise || !currentBlock || !isInitialLoadingBalance.current) { + // this makes sure that the balance is queried only once return; } + if (isInitialLoadingBalance.current) { + isInitialLoadingBalance.current = false; + } setLoadingBalance(true); const customAccounts: CustomInjectedAccountWithMeta[] = []; const accounts = injectedAccountsRef.current; for (let i = 0; i < accounts.length; i++) { const prettyName = isMultisig ? "" : await getPrettyName(accounts[i].address); + const formattedAddress = convertToSS58(accounts[i].address, selectedNetwork?.prefix ?? 18); + /* set all the injected accounts balance to zero if we are in the multisig account mode + * since these accounts won't be shown to the user */ + + const defaultAsset = { + ring: BigNumber(0), + kton: BigNumber(0), + }; const balance = isMultisig ? { - ring: BigNumber(0), - kton: BigNumber(0), + ...defaultAsset, } - : await getAccountBalance(accounts[i].address); + : await getAccountAsset(formattedAddress, undefined, true, false); + const normalAccountAsset = balance as AssetDistribution; customAccounts.push({ ...accounts[i], prettyName, - balance: balance, - formattedAddress: convertToSS58(accounts[i].address, selectedNetwork?.prefix ?? 18), + balance: isMultisig + ? { ...defaultAsset } + : { ring: normalAccountAsset.ring.transferable, kton: normalAccountAsset.kton.transferable }, + formattedAddress: formattedAddress, }); } /* Force adding an address if there is an account address that was set from the URL */ if (forcedAccountAddress.current) { const prettyName = await getPrettyName(forcedAccountAddress.current); - const balance = await getAccountBalance(forcedAccountAddress.current); + const balance = await getAccountAsset(forcedAccountAddress.current); customAccounts.unshift({ prettyName, - balance, + balance: { + ring: balance?.ring.transferable ?? BigNumber(0), + kton: balance?.kton.transferable ?? BigNumber(0), + }, type: "sr25519", address: forcedAccountAddress.current, meta: { source: "" }, @@ -241,7 +242,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { setLoadingBalance(false); //ignore }); - }, [injectedAccountsRef.current, apiPromise, selectedNetwork, isMultisig]); + }, [injectedAccountsRef.current, apiPromise, selectedNetwork, isMultisig, currentBlock]); /*Connect to MetaMask*/ const connectWallet = useCallback( @@ -458,6 +459,11 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { setMultisig, checkDarwiniaOneMultisigAccount, isLoadingBalance, + getAccountBalance, + apiPromise, + currentBlock, + isLoadingMultisigBalance, + setLoadingMultisigBalance, }} > {children} diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index a5f3322..bf5c0f1 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -6,6 +6,7 @@ export type SupportedBrowser = "Chrome" | "Firefox" | "Brave" | "Edge" | "Opera" export type ChainName = "Crab" | "Pangolin" | "Darwinia" | "Pangoro"; import { Struct } from "@polkadot/types"; import { ContractInterface } from "@ethersproject/contracts"; +import { ApiPromise } from "@polkadot/api"; export interface Token { name?: string; @@ -98,6 +99,11 @@ export interface WalletCtx { threshold: number, name?: string ) => Promise; + getAccountBalance: (account: string) => Promise; + apiPromise: ApiPromise | undefined; + currentBlock: CurrentBlock | undefined; + isLoadingMultisigBalance: boolean | undefined; + setLoadingMultisigBalance: (isLoading: boolean) => void; } export interface SpVersionRuntimeVersion extends Struct { @@ -114,3 +120,8 @@ export interface MultisigAccount { address: string; meta: MultisigAccountMeta; } + +export interface CurrentBlock { + number: number; + timestamp: number; +} diff --git a/packages/app/src/Root.tsx b/packages/app/src/Root.tsx index 28e670d..b448d9f 100644 --- a/packages/app/src/Root.tsx +++ b/packages/app/src/Root.tsx @@ -18,6 +18,7 @@ const Root = () => { walletConfig, setMultisig, isLoadingBalance, + isLoadingMultisigBalance, } = useWallet(); const { isLoadingLedger, isLoadingMigratedLedger } = useStorage(); const [loading, setLoading] = useState(false); @@ -31,7 +32,8 @@ const Root = () => { isLoadingTransaction || isLoadingLedger || isLoadingMigratedLedger || - isLoadingBalance + isLoadingBalance || + isLoadingMultisigBalance ); }, [ isRequestingWalletConnection, @@ -40,6 +42,7 @@ const Root = () => { isLoadingLedger, isLoadingMigratedLedger, isLoadingBalance, + isLoadingMultisigBalance, ]); const redirect = useCallback(() => { diff --git a/packages/app/src/components/MigrationSummary/index.tsx b/packages/app/src/components/MigrationSummary/index.tsx index bcb5760..2f47f18 100644 --- a/packages/app/src/components/MigrationSummary/index.tsx +++ b/packages/app/src/components/MigrationSummary/index.tsx @@ -30,179 +30,6 @@ const MigrationSummary = ({ isCheckingMigrationStatus }: Props) => { return (
-
-
-
-
-
- {"image"} -
{selectedNetwork?.ring.symbol.toUpperCase()}
-
-
-
-
-
-
{t(localeKeys.transferable)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.transferable ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.transferable ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.deposit)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.deposit ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.deposit ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.bonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.bonded ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.bonded ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.unbonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.unbonded ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.unbonded ?? BigNumber(0), - })} - -
-
-
-
-
{t(localeKeys.unbonding)}
- {/*Ubonding Message
}> - image - */} -
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.unbonding ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.unbonding ?? BigNumber(0), - })} - -
-
-
-
-
{t(localeKeys.vested)}
- {/*Vested Message
}> - image - */} -
-
- {prettifyTooltipNumber(migrationAssetDistribution?.ring.vested ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.ring.vested ?? BigNumber(0), - })} - -
-
-
-
-
-
-
-
- {"image"} -
{selectedNetwork?.kton.symbol.toUpperCase()}
-
-
-
-
-
-
{t(localeKeys.transferable)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.transferable ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.transferable ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.bonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.bonded ?? BigNumber(0))}
} - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.bonded ?? BigNumber(0), - })} - -
-
-
-
{t(localeKeys.unbonded)}
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.unbonded ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.unbonded ?? BigNumber(0), - })} - -
-
-
-
-
{t(localeKeys.unbonding)}
- {/*Ubonding Message
}> - image - */} -
-
- {prettifyTooltipNumber(migrationAssetDistribution?.kton.unbonding ?? BigNumber(0))}
- } - > - {prettifyNumber({ - number: migrationAssetDistribution?.kton.unbonding ?? BigNumber(0), - })} - -
-
-
-
-
{t(localeKeys.migrationSummaryInfo)}
); diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 8cb8833..eee5499 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -10,7 +10,14 @@ import { CustomInjectedAccountWithMeta, MultisigAccount } from "@darwinia/app-ty import noDataIcon from "../../assets/images/no-data.svg"; import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; -import { getStore, isSubstrateAddress, isValidNumber, prettifyNumber, setStore } from "@darwinia/app-utils"; +import { + convertToSS58, + getStore, + isSubstrateAddress, + isValidNumber, + prettifyNumber, + setStore, +} from "@darwinia/app-utils"; import { useLocation, useNavigate } from "react-router-dom"; interface Asset { @@ -21,6 +28,7 @@ interface Asset { interface MultisigAccountData { id: string; address: string; + formattedAddress: string; name: string; who: string[]; asset: Asset; @@ -28,7 +36,16 @@ interface MultisigAccountData { } const MultisigMigrationProcess = () => { - const { selectedAccount, injectedAccounts, checkDarwiniaOneMultisigAccount, selectedNetwork } = useWallet(); + const { + selectedAccount, + injectedAccounts, + checkDarwiniaOneMultisigAccount, + selectedNetwork, + getAccountBalance, + apiPromise, + currentBlock, + setLoadingMultisigBalance, + } = useWallet(); const { migrationAssetDistribution, isLoadingLedger } = useStorage(); const { t } = useAppTranslation(); const navigate = useNavigate(); @@ -52,6 +69,7 @@ const MultisigMigrationProcess = () => { const [selectedAddress, setSelectedAddress] = useState(""); const [isCheckingAccountExistence, setCheckingAccountExistence] = useState(false); const [multisigAccountsList, setMultisigAccountsList] = useState([]); + const isInitializingLocalAccountsRef = useRef(true); const columns: Column[] = [ { @@ -81,7 +99,7 @@ const MultisigMigrationProcess = () => { render: (row) => { return (
-
{row.address}
+
{row.formattedAddress}
); }, @@ -148,31 +166,45 @@ const MultisigMigrationProcess = () => { [location] ); - const prepareMultisigAccountData = (accountList: MultisigAccount[]) => { + const prepareMultisigAccountData = async (accountList: MultisigAccount[]): Promise => { + setLoadingMultisigBalance(true); const data: MultisigAccountData[] = []; for (let i = 0; i < accountList.length; i++) { const accountItem = accountList[i]; + const formattedAddress = convertToSS58(accountItem.address, selectedNetwork?.prefix ?? 18); + const balance = await getAccountBalance(formattedAddress); const item: MultisigAccountData = { id: accountItem.address, address: accountItem.address, + formattedAddress: formattedAddress, name: accountItem.meta.name, asset: { - ring: BigNumber(0), - kton: BigNumber(0), + ring: balance.ring, + kton: balance.kton, }, who: [...accountItem.meta.who], threshold: accountItem.meta.threshold, }; data.push(item); } - return data; + setLoadingMultisigBalance(false); + return Promise.resolve(data); }; useEffect(() => { - const multisigAccounts: MultisigAccount[] = getStore("multisigAccounts") ?? []; - const data = prepareMultisigAccountData(multisigAccounts); - setMultisigAccountsList(data); - }, []); + if (!apiPromise || !currentBlock || !isInitializingLocalAccountsRef.current) { + return; + } + isInitializingLocalAccountsRef.current = false; + const initData = async () => { + const multisigAccounts: MultisigAccount[] = getStore("multisigAccounts") ?? []; + const data = await prepareMultisigAccountData(multisigAccounts); + setMultisigAccountsList(data); + }; + initData().catch((e) => { + console.log(e); + }); + }, [apiPromise, currentBlock]); useEffect(() => { if (currentAccount.current?.address !== selectedAccount?.address) { @@ -254,7 +286,7 @@ const MultisigMigrationProcess = () => { const filteredAccounts = multisigAccounts.filter((account) => account.address !== account.address); filteredAccounts.push(account); setStore("multisigAccounts", filteredAccounts); - const data = prepareMultisigAccountData([account]); + const data = await prepareMultisigAccountData([account]); setMultisigAccountsList((old) => { return [...old, ...data]; }); diff --git a/packages/app/src/components/TokensBalanceSummary/index.tsx b/packages/app/src/components/TokensBalanceSummary/index.tsx index 899322e..fb4c769 100644 --- a/packages/app/src/components/TokensBalanceSummary/index.tsx +++ b/packages/app/src/components/TokensBalanceSummary/index.tsx @@ -7,6 +7,8 @@ import helpIcon from "../../assets/images/help.svg"; import ktonIcon from "../../assets/images/kton.svg"; import { useWallet } from "@darwinia/app-providers"; import { AssetDistribution } from "@darwinia/app-types"; +import crabIcon from "../../assets/images/crab.svg"; +import cktonIcon from "../../assets/images/ckton.svg"; interface Props { asset: AssetDistribution | undefined; @@ -15,6 +17,10 @@ interface Props { const TokensBalanceSummary = ({ asset: assetDistribution }: Props) => { const { selectedNetwork } = useWallet(); const { t } = useAppTranslation(); + + const ringTokenIcon = selectedNetwork?.name === "Crab" ? crabIcon : ringIcon; + const ktonTokenIcon = selectedNetwork?.name === "Crab" ? cktonIcon : ktonIcon; + return ( <>
@@ -22,7 +28,7 @@ const TokensBalanceSummary = ({ asset: assetDistribution }: Props) => {
- {"image"} + {"image"}
{selectedNetwork?.ring.symbol.toUpperCase()}
@@ -108,7 +114,7 @@ const TokensBalanceSummary = ({ asset: assetDistribution }: Props) => {
- {"image"} + {"image"}
{selectedNetwork?.kton.symbol.toUpperCase()}
From aed3da1f7b980e8bee46666d85caddc3b001ac0d Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Tue, 21 Mar 2023 12:17:00 +0800 Subject: [PATCH 15/36] redirect issue resolved --- packages/app/src/Root.tsx | 16 ++++++++++++---- packages/app/src/components/Protected/index.tsx | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/app/src/Root.tsx b/packages/app/src/Root.tsx index b448d9f..9da2d1f 100644 --- a/packages/app/src/Root.tsx +++ b/packages/app/src/Root.tsx @@ -56,17 +56,25 @@ const Root = () => { } if (location.pathname === "/multisig-home") { + /*The user is connected to the wallet but still trying to visit the connect wallet page*/ if (setMultisig) { setMultisig(true); } - navigate(`/multisig-migration${location.search}`, { replace: true }); + const params = new URLSearchParams(location.search); + const redirectPath = params.get("redirect"); + params.delete("redirect"); + const destination = redirectPath ? redirectPath : "/multisig-migration"; + + navigate(`${destination}?${params.toString()}`, { replace: true }); return; } /* only navigate if the user is supposed to be redirected to another URL */ - if (location.state && location.state.from) { - const nextPath = location.state.from.pathname ? location.state.from.pathname : "/migration"; - navigate(`${nextPath}${location.search}`, { replace: true }); + const params = new URLSearchParams(location.search); + const redirectPath = params.get("redirect"); + if (redirectPath) { + params.delete("redirect"); + navigate(`${redirectPath}?${params.toString()}`, { replace: true }); } }, [location, navigate, setMultisig]); diff --git a/packages/app/src/components/Protected/index.tsx b/packages/app/src/components/Protected/index.tsx index 06abe65..284a31f 100644 --- a/packages/app/src/components/Protected/index.tsx +++ b/packages/app/src/components/Protected/index.tsx @@ -7,8 +7,10 @@ const Protected = ({ children }: PropsWithChildren) => { const location = useLocation(); //if the user isn't connected to the wallet, redirect to the homepage const path = location.pathname.includes("/multisig") ? `/multisig-home` : `/`; + const params = `${location.search}&redirect=${location.pathname}`; + if (!isWalletConnected && !isRequestingWalletConnection) { - return ; + return ; } return <>{children}; From 03968fc42d63859842851612c0e84aef3e5742a8 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Tue, 21 Mar 2023 16:19:28 +0800 Subject: [PATCH 16/36] migration notifications hidden --- .../components/MultisigAccountInfo/index.tsx | 40 ++++++++++++------- .../MultisigMigrationProcess/index.tsx | 2 +- .../pages/MultisigAccountMigrationSummary.tsx | 1 + 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index 61d15c2..30c13d4 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -35,6 +35,8 @@ const MultisigAccountInfo = () => { ]); const [newAccountThreshold, setNewAccountThreshold] = useState(""); const [newMultisigAccountAddress, setNewMultisigAccountAddress] = useState(""); + const [isSuccessfullyMigrated, setIsSuccessfullyMigrated] = useState(false); + const [isIsWaitingToDeploy, setIsWaitingToDeploy] = useState(false); const destinationTabs = [ { @@ -166,22 +168,32 @@ const MultisigAccountInfo = () => { }); }; + const generateShareLink = () => { + console.log("generate share link"); + }; + return (
-
-
{t(localeKeys.oneMoreStep)}
-
- + {/*one more step to deploy*/} + {isIsWaitingToDeploy && ( +
+
{t(localeKeys.oneMoreStep)}
+
+ +
+
{t(localeKeys.toCompleteMigration)}
-
{t(localeKeys.toCompleteMigration)}
-
-
-
-
+ )} + {/*account migrated successfully*/} + {isSuccessfullyMigrated && ( +
+
+
+ )}
@@ -200,7 +212,7 @@ const MultisigAccountInfo = () => {
{address}
- image + image
diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index eee5499..b207f8e 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -157,7 +157,7 @@ const MultisigMigrationProcess = () => { const onInitializeMigration = useCallback( (item: MultisigAccountData) => { const params = new URLSearchParams(location.search); - params.set("address", item.address); + params.set("address", item.formattedAddress); params.set("name", item.name); params.set("who", item.who.join(",")); params.set("threshold", item.threshold.toString()); diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx index ae7c0aa..d748866 100644 --- a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -34,6 +34,7 @@ const MultisigAccountMigrationSummary = () => { return (
+
SHOW BALANCE
{isAccountMigrationInitialized ? ( ) : ( From 9cb0e0ae1201d55d4a94e9fc000c805a39dcd5a0 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 22 Mar 2023 17:10:07 +0800 Subject: [PATCH 17/36] multisig account can be generated --- packages/app-providers/src/walletProvider.tsx | 66 ++++++++++++++++--- packages/app-types/src/wallet.ts | 6 +- packages/app-utils/package.json | 1 + packages/app-utils/src/others.ts | 8 ++- .../src/components/MigrationSummary/index.tsx | 42 +++++++++--- .../components/MultisigAccountInfo/index.tsx | 43 ++++++++---- .../MultisigMigrationProcess/index.tsx | 6 +- .../components/TokensBalanceSummary/index.tsx | 12 ++-- .../pages/MultisigAccountMigrationSummary.tsx | 3 +- yarn.lock | 49 ++++++++++++++ 10 files changed, 193 insertions(+), 43 deletions(-) diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index b26ce04..bb3b8d9 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -31,6 +31,7 @@ import { Option, Vec } from "@polkadot/types"; import { convertToSS58, setStore, getStore, createMultiSigAccount } from "@darwinia/app-utils"; import useBlock from "./hooks/useBlock"; import useLedger from "./hooks/useLedger"; +import { Contract, ethers } from "ethers"; /*This is just a blueprint, no value will be stored in here*/ const initialState: WalletCtx = { @@ -51,6 +52,7 @@ const initialState: WalletCtx = { apiPromise: undefined, currentBlock: undefined, isLoadingMultisigBalance: undefined, + multisigContract: undefined, setLoadingMultisigBalance: (isLoading: boolean) => {}, changeSelectedNetwork: () => { // do nothing @@ -78,10 +80,7 @@ const initialState: WalletCtx = { return Promise.resolve(undefined); }, getAccountBalance: (account: string) => { - return Promise.resolve({ - ring: BigNumber(0), - kton: BigNumber(0), - }); + return Promise.resolve(undefined); }, }; @@ -110,6 +109,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [isMultisig, setMultisig] = useState(false); const [isLoadingMultisigBalance, setLoadingMultisigBalance] = useState(false); const [selectedWallet, _setSelectedWallet] = useState(); + const [multisigContract, setMultisigContract] = useState(); const { currentBlock } = useBlock(apiPromise); const { getAccountAsset } = useLedger({ @@ -154,23 +154,70 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { } }, [selectedNetwork]); + /*This will be fired once the connection to the wallet is successful*/ + useEffect(() => { + if (!selectedAccount || !selectedNetwork) { + return; + } + //refresh the page with the newly selected account + const newProvider = new ethers.providers.Web3Provider(window.ethereum); + const newSigner = newProvider.getSigner(); + const multisigContract = new ethers.Contract( + selectedNetwork.contractAddresses.multisig, + selectedNetwork.contractInterface.multisig, + newSigner + ); + + setMultisigContract(multisigContract); + }, [selectedAccount, selectedNetwork]); + const disconnectWallet = useCallback(() => { setSelectedAccount(undefined); setWalletConnected(false); }, []); const getAccountBalance = useCallback( - async (accountAddress: string): Promise => { + async (accountAddress: string): Promise => { if (!apiPromise || !currentBlock) { return Promise.resolve({ - ring: BigNumber(0), - kton: BigNumber(0), + ring: { + transferable: BigNumber(0), + deposit: BigNumber(0), + bonded: BigNumber(0), + unbonded: BigNumber(0), + unbonding: BigNumber(0), + vested: BigNumber(0), + }, + kton: { + transferable: BigNumber(0), + bonded: BigNumber(0), + unbonded: BigNumber(0), + unbonding: BigNumber(0), + }, }); } const asset = await getAccountAsset(accountAddress, undefined, true, false); + if (!asset) { + return Promise.resolve({ + ring: { + transferable: BigNumber(0), + deposit: BigNumber(0), + bonded: BigNumber(0), + unbonded: BigNumber(0), + unbonding: BigNumber(0), + vested: BigNumber(0), + }, + kton: { + transferable: BigNumber(0), + bonded: BigNumber(0), + unbonded: BigNumber(0), + unbonding: BigNumber(0), + }, + }); + } + return Promise.resolve({ - ring: asset?.ring.transferable ?? BigNumber(0), - kton: asset?.kton.transferable ?? BigNumber(0), + ...asset, }); }, [apiPromise, currentBlock] @@ -464,6 +511,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { currentBlock, isLoadingMultisigBalance, setLoadingMultisigBalance, + multisigContract, }} > {children} diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index bf5c0f1..5afe01a 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -5,8 +5,9 @@ export type SupportedWallet = "Polkadot{.js}" | "Talisman" | "SubWallet"; export type SupportedBrowser = "Chrome" | "Firefox" | "Brave" | "Edge" | "Opera"; export type ChainName = "Crab" | "Pangolin" | "Darwinia" | "Pangoro"; import { Struct } from "@polkadot/types"; -import { ContractInterface } from "@ethersproject/contracts"; +import { Contract, ContractInterface } from "@ethersproject/contracts"; import { ApiPromise } from "@polkadot/api"; +import { AssetDistribution } from "../dist"; export interface Token { name?: string; @@ -99,11 +100,12 @@ export interface WalletCtx { threshold: number, name?: string ) => Promise; - getAccountBalance: (account: string) => Promise; + getAccountBalance: (account: string) => Promise; apiPromise: ApiPromise | undefined; currentBlock: CurrentBlock | undefined; isLoadingMultisigBalance: boolean | undefined; setLoadingMultisigBalance: (isLoading: boolean) => void; + multisigContract: Contract | undefined; } export interface SpVersionRuntimeVersion extends Struct { diff --git a/packages/app-utils/package.json b/packages/app-utils/package.json index 4fd05cc..5738746 100644 --- a/packages/app-utils/package.json +++ b/packages/app-utils/package.json @@ -24,6 +24,7 @@ "dependencies": { "@darwinia/app-config": "^1.0.0", "@darwinia/app-types": "^1.0.0", + "@polkadot/util": "^11.1.1", "@polkadot/util-crypto": "^10.4.2", "bignumber.js": "^9.1.1", "ethers": "^5.7.2", diff --git a/packages/app-utils/src/others.ts b/packages/app-utils/src/others.ts index ecada0f..e0788df 100644 --- a/packages/app-utils/src/others.ts +++ b/packages/app-utils/src/others.ts @@ -2,7 +2,8 @@ import { Storage } from "@darwinia/app-types"; import { STORAGE as APP_STORAGE } from "@darwinia/app-config"; import BigNumber from "bignumber.js"; import { ethers, utils } from "ethers"; -import { encodeAddress, createKeyMulti, sortAddresses, isAddress } from "@polkadot/util-crypto"; +import { encodeAddress, createKeyMulti, sortAddresses, isAddress, decodeAddress } from "@polkadot/util-crypto"; +import { u8aToHex } from "@polkadot/util"; export const setStore = (key: keyof Storage, value: unknown) => { try { @@ -133,3 +134,8 @@ export const createMultiSigAccount = (addresses: string[], prefix: number, thres export const isSubstrateAddress = (address: string) => { return isAddress(address); }; + +export const getPublicKey = (accountAddress: string) => { + const publicKeyArray = decodeAddress(accountAddress); + return u8aToHex(publicKeyArray); +}; diff --git a/packages/app/src/components/MigrationSummary/index.tsx b/packages/app/src/components/MigrationSummary/index.tsx index 2f47f18..f278038 100644 --- a/packages/app/src/components/MigrationSummary/index.tsx +++ b/packages/app/src/components/MigrationSummary/index.tsx @@ -8,29 +8,51 @@ import helpIcon from "../../assets/images/help.svg"; import { Tooltip } from "@darwinia/ui"; import { prettifyNumber, prettifyTooltipNumber } from "@darwinia/app-utils"; import BigNumber from "bignumber.js"; -import { useEffect } from "react"; +import { useEffect, useRef, useState } from "react"; import TokensBalanceSummary from "../TokensBalanceSummary"; +import { AssetDistribution } from "@darwinia/app-types"; interface Props { - isCheckingMigrationStatus: boolean; + isCheckingMigrationStatus?: boolean; + accountAddress?: string | null; } -const MigrationSummary = ({ isCheckingMigrationStatus }: Props) => { +const MigrationSummary = ({ isCheckingMigrationStatus, accountAddress }: Props) => { const { t } = useAppTranslation(); - const { selectedNetwork, setTransactionStatus } = useWallet(); + const { selectedNetwork, setTransactionStatus, getAccountBalance, apiPromise, currentBlock, isMultisig } = + useWallet(); const { migrationAssetDistribution, isLoadingLedger } = useStorage(); + const [isLoadingBalance, setLoadingBalance] = useState(false); + const [multisigBalance, setMultisigBalance] = useState(); + const isInitializingLocalAccountsRef = useRef(true); useEffect(() => { - setTransactionStatus(!!isLoadingLedger || isCheckingMigrationStatus); - }, [isLoadingLedger, isCheckingMigrationStatus]); + setTransactionStatus(!!isLoadingLedger || !!isCheckingMigrationStatus || isLoadingBalance); + }, [isLoadingLedger, isCheckingMigrationStatus, isLoadingBalance]); - const ringTokenIcon = selectedNetwork?.name === "Crab" ? crabIcon : ringIcon; - const ktonTokenIcon = selectedNetwork?.name === "Crab" ? cktonIcon : ktonIcon; + useEffect(() => { + if (!apiPromise || !currentBlock || !isInitializingLocalAccountsRef.current) { + return; + } + isInitializingLocalAccountsRef.current = false; + const initBalance = async () => { + if (accountAddress) { + setLoadingBalance(true); + const asset = await getAccountBalance(accountAddress); + setMultisigBalance(asset); + setLoadingBalance(false); + } + }; + initBalance().catch((e) => { + console.log(e); + setLoadingBalance(false); + }); + }, [accountAddress, apiPromise, currentBlock]); return (
- -
{t(localeKeys.migrationSummaryInfo)}
+ + {!isMultisig &&
{t(localeKeys.migrationSummaryInfo)}
}
); }; diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index 30c13d4..8f111fd 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -10,11 +10,17 @@ import JazzIcon from "../JazzIcon"; import { Tip } from "../MigrationForm"; import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; -import { isValidNumber, isEthereumAddress } from "@darwinia/app-utils"; +import { isValidNumber, isEthereumAddress, getPublicKey } from "@darwinia/app-utils"; +import { BigNumber as EthersBigNumber } from "ethers"; + +interface MultisigMemberAddress { + address: string; + id: number; +} const MultisigAccountInfo = () => { const { t } = useAppTranslation(); - const { injectedAccounts } = useWallet(); + const { injectedAccounts, multisigContract } = useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); const address = params.get("address"); @@ -30,11 +36,12 @@ const MultisigAccountInfo = () => { const [checkedTips, setCheckedTips] = useState([]); const { selectedNetwork, onInitMigration } = useWallet(); const [isProcessingMigration, setProcessingMigration] = useState(false); - const [memberAddresses, setMemberAddresses] = useState<{ address: string; id: number }[]>([ + const [memberAddresses, setMemberAddresses] = useState([ { id: new Date().getTime(), address: "" }, ]); const [newAccountThreshold, setNewAccountThreshold] = useState(""); const [newMultisigAccountAddress, setNewMultisigAccountAddress] = useState(""); + const [isIsGeneratingMultisigAccount, setIsGeneratingMultisigAccount] = useState(false); const [isSuccessfullyMigrated, setIsSuccessfullyMigrated] = useState(false); const [isIsWaitingToDeploy, setIsWaitingToDeploy] = useState(false); @@ -66,10 +73,23 @@ const MultisigAccountInfo = () => { setAttentionModalVisibility(true); }; - const generateMultisigAccount = () => { - const dummyAddress = "0xDeA37A59acB4F407980Ea347ab351697E7102ae0"; - setNewMultisigAccountAddress(dummyAddress); - console.log("call the smart contract"); + const generateMultisigAccount = async (members: MultisigMemberAddress[], address: string) => { + try { + setIsGeneratingMultisigAccount(true); + const publicKey = getPublicKey(address); + const memberAddresses = members + .map((item) => item.address) + .sort() + .map((item) => item); + const thresholdNumber = EthersBigNumber.from(threshold); + + const multisigAddress = await multisigContract?.computeAddress(publicKey, memberAddresses, thresholdNumber); + setNewMultisigAccountAddress(multisigAddress); + setIsGeneratingMultisigAccount(false); + } catch (e) { + setIsGeneratingMultisigAccount(false); + console.log(e); + } }; const onAttentionTipChecked = (checkedTip: Tip, allCheckedTips: Tip[]) => { @@ -115,11 +135,11 @@ const MultisigAccountInfo = () => { const isContinueButtonDisabled = () => { if (activeDestinationTab === 1) { - return destinationAddress.length === 0; + return destinationAddress.length === 0 || isIsGeneratingMultisigAccount; } else { const isValidThreshold = isValidNumber(newAccountThreshold); const isValidMultisigAddress = isEthereumAddress(newMultisigAccountAddress); - return !isValidThreshold || !isValidMultisigAddress; + return !isValidThreshold || !isValidMultisigAddress || isIsGeneratingMultisigAccount; } }; @@ -152,8 +172,9 @@ const MultisigAccountInfo = () => { const members = [...memberAddresses]; members[index].address = value; setMemberAddresses(() => members); - //TODO delete this - generateMultisigAccount(); + if (address && members.length >= Number(newAccountThreshold ?? 1)) { + generateMultisigAccount(members, address); + } }; const onAddNewMemberAddress = () => { diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index b207f8e..62e4206 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -172,15 +172,15 @@ const MultisigMigrationProcess = () => { for (let i = 0; i < accountList.length; i++) { const accountItem = accountList[i]; const formattedAddress = convertToSS58(accountItem.address, selectedNetwork?.prefix ?? 18); - const balance = await getAccountBalance(formattedAddress); + const asset = await getAccountBalance(formattedAddress); const item: MultisigAccountData = { id: accountItem.address, address: accountItem.address, formattedAddress: formattedAddress, name: accountItem.meta.name, asset: { - ring: balance.ring, - kton: balance.kton, + ring: asset?.ring.transferable ?? BigNumber(0), + kton: asset?.kton.transferable ?? BigNumber(0), }, who: [...accountItem.meta.who], threshold: accountItem.meta.threshold, diff --git a/packages/app/src/components/TokensBalanceSummary/index.tsx b/packages/app/src/components/TokensBalanceSummary/index.tsx index fb4c769..44e224b 100644 --- a/packages/app/src/components/TokensBalanceSummary/index.tsx +++ b/packages/app/src/components/TokensBalanceSummary/index.tsx @@ -79,9 +79,9 @@ const TokensBalanceSummary = ({ asset: assetDistribution }: Props) => {
{t(localeKeys.unbonding)}
- Ubonding Message
}> + {/*Ubonding Message
}> image - + */}
{
{t(localeKeys.vested)}
- Vested Message
}> + {/*Vested Message
}> image -
+ */}
{prettifyTooltipNumber(assetDistribution?.ring.vested ?? BigNumber(0))}
}> @@ -155,9 +155,9 @@ const TokensBalanceSummary = ({ asset: assetDistribution }: Props) => {
{t(localeKeys.unbonding)}
- Ubonding Message
}> + {/*Ubonding Message
}> image - + */}
{ const { t } = useAppTranslation(); @@ -34,7 +35,7 @@ const MultisigAccountMigrationSummary = () => { return (
-
SHOW BALANCE
+ {isAccountMigrationInitialized ? ( ) : ( diff --git a/yarn.lock b/yarn.lock index 9faae4c..6673aa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2168,6 +2168,19 @@ "@types/bn.js" "^5.1.1" bn.js "^5.2.1" +"@polkadot/util@^11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-11.1.1.tgz#88db2df312e57bd6a0291d8e1e271a44e113fdd5" + integrity sha512-8vlSfJhMAck2OVdk8aep3sZP17txR+p8X3bFNP0qNJ7frfF741v/eViEC7bbVIgdT0/vYNmgS6+0Dwe06dnKuA== + dependencies: + "@polkadot/x-bigint" "11.1.1" + "@polkadot/x-global" "11.1.1" + "@polkadot/x-textdecoder" "11.1.1" + "@polkadot/x-textencoder" "11.1.1" + "@types/bn.js" "^5.1.1" + bn.js "^5.2.1" + tslib "^2.5.0" + "@polkadot/wasm-bridge@6.4.1": version "6.4.1" resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-6.4.1.tgz#e97915dd67ba543ec3381299c2a5b9330686e27e" @@ -2235,6 +2248,14 @@ "@babel/runtime" "^7.20.13" "@polkadot/x-global" "10.4.2" +"@polkadot/x-bigint@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-11.1.1.tgz#f23fb17b6d1267cb7f4fbad11a04b67ec26a14c6" + integrity sha512-iLaaPSCnVuZ7LoOWZTHgs+Ebws0MdoNHmXoTriU60YLoojDJbcOInlO+1h3fNy6oPnYN3qA3Ml1mKDnP837nxg== + dependencies: + "@polkadot/x-global" "11.1.1" + tslib "^2.5.0" + "@polkadot/x-fetch@^10.2.1": version "10.2.1" resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-10.2.1.tgz#cb5b33da1d91787eb2e5207ef62806a75ef3c62f" @@ -2259,6 +2280,13 @@ dependencies: "@babel/runtime" "^7.20.13" +"@polkadot/x-global@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-11.1.1.tgz#6862c6c679e94b0fc39dff3b6954bb508ff3d7a4" + integrity sha512-++LFUT98bi2m15w8LrgOcpE5mi9bmH65YB02xbKzU0ZHe1g5l0LwFt+QFB9tZlNqfWTgwpsFshGtvdPQqrFnKw== + dependencies: + tslib "^2.5.0" + "@polkadot/x-randomvalues@10.2.1": version "10.2.1" resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-10.2.1.tgz#1c463625c0b7cf775e94594f522eb21a5229b42e" @@ -2291,6 +2319,14 @@ "@babel/runtime" "^7.20.13" "@polkadot/x-global" "10.4.2" +"@polkadot/x-textdecoder@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-11.1.1.tgz#9596fe10981fa05f7629eaa198b11c8b191b1501" + integrity sha512-YoB82pr6kYkK5yg2BQgm5wVTf6Hq+01i+A6PgV1uXr7Rm3bxmQpGR2DKZq0QNjwWP0s6e91BxXvGoPjf7S9tBA== + dependencies: + "@polkadot/x-global" "11.1.1" + tslib "^2.5.0" + "@polkadot/x-textencoder@10.2.1": version "10.2.1" resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-10.2.1.tgz#c09562c73a44659243075d43b007b5c1b39c57a8" @@ -2307,6 +2343,14 @@ "@babel/runtime" "^7.20.13" "@polkadot/x-global" "10.4.2" +"@polkadot/x-textencoder@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-11.1.1.tgz#1c91e9aa568384595cbd7bd6ad50d71e4f92f2d0" + integrity sha512-I4IygnZeSyGUPyTmu7W2IsCHakax7QTVR9kMkCywaKEjiLzZU5B/LuDB0Gxn/3Jw2X2YfoB1TQ4mZ1bte4LX0g== + dependencies: + "@polkadot/x-global" "11.1.1" + tslib "^2.5.0" + "@polkadot/x-ws@^10.2.1": version "10.2.1" resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-10.2.1.tgz#ec119c22a8cb7b9cde00e9909e37b6ba2845efd1" @@ -7827,6 +7871,11 @@ tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" From 74c7bf650668a931252bf7985d73ed6e997c6d4f Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 22 Mar 2023 17:58:04 +0800 Subject: [PATCH 18/36] added novaWallet support on multis account --- packages/app/src/pages/MultisigHome.tsx | 48 +++++++++++++++---------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/app/src/pages/MultisigHome.tsx b/packages/app/src/pages/MultisigHome.tsx index 311ed63..921075e 100644 --- a/packages/app/src/pages/MultisigHome.tsx +++ b/packages/app/src/pages/MultisigHome.tsx @@ -1,5 +1,5 @@ import { useAppTranslation, localeKeys } from "@darwinia/app-locale"; -import { useWallet } from "@darwinia/app-providers"; +import { useMobile, useWallet } from "@darwinia/app-providers"; import migrationIcon from "../assets/images/migration.svg"; import { useLocation } from "react-router-dom"; import { useEffect } from "react"; @@ -8,6 +8,7 @@ import { dAppSupportedWallets } from "@darwinia/app-config"; const MultisigHome = () => { const { t } = useAppTranslation(); const { connectWallet, walletConfig } = useWallet(); + const { isMobile } = useMobile(); return (
@@ -18,25 +19,34 @@ const MultisigHome = () => {
- {dAppSupportedWallets.map(({ name, logo, sources }, index) => { - const selected = name === walletConfig?.name; - const injecteds = window.injectedWeb3; - const installed = injecteds && sources.some((source) => injecteds[source]); + {isMobile ? ( + + ) : ( + dAppSupportedWallets.map(({ name, logo, sources }, index) => { + const selected = name === walletConfig?.name; + const injecteds = window.injectedWeb3; + const installed = injecteds && sources.some((source) => injecteds[source]); - return ( - - ); - })} + return ( + + ); + }) + )}
); From 8b267fca8ec197c2692aab691876a814417e4720 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 22 Mar 2023 18:19:38 +0800 Subject: [PATCH 19/36] fixed a buggy import --- packages/app-types/src/wallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index 88d74e1..823ce5a 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -7,7 +7,7 @@ export type ChainName = "Crab" | "Pangolin" | "Darwinia" | "Pangoro"; import { Struct } from "@polkadot/types"; import { Contract, ContractInterface } from "@ethersproject/contracts"; import { ApiPromise } from "@polkadot/api"; -import { AssetDistribution } from "../dist"; +import { AssetDistribution } from "./storage"; export interface Token { name?: string; From 8104aa41514568ca9c1517b822b8709160e3871b Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Fri, 24 Mar 2023 17:56:50 +0800 Subject: [PATCH 20/36] Added metamask connection --- packages/app-config/src/chains/crab.ts | 2 + packages/app-config/src/chains/darwinia.ts | 2 + packages/app-config/src/chains/pangolin.ts | 2 + packages/app-config/src/chains/pangoro.ts | 4 +- packages/app-providers/src/walletProvider.tsx | 343 +++++++++++++++--- packages/app-types/src/wallet.ts | 18 + packages/app-utils/src/others.ts | 8 +- .../components/MultisigAccountInfo/index.tsx | 61 +++- .../MultisigMigrationProcess/index.tsx | 7 +- .../pages/MultisigAccountMigrationSummary.tsx | 10 +- 10 files changed, 382 insertions(+), 75 deletions(-) diff --git a/packages/app-config/src/chains/crab.ts b/packages/app-config/src/chains/crab.ts index 0d9e17f..b261d5c 100644 --- a/packages/app-config/src/chains/crab.ts +++ b/packages/app-config/src/chains/crab.ts @@ -4,6 +4,8 @@ import multisigContract from "../abi/contract.json"; export const crab: ChainConfig = { name: "Crab", displayName: "Crab", + explorerURLs: ["https://crab.subscan.io/"], + httpsURLs: ["https://crab-rpc.darwinia.network"], kton: { address: "0x0000000000000000000000000000000000000402", symbol: "CKTON", diff --git a/packages/app-config/src/chains/darwinia.ts b/packages/app-config/src/chains/darwinia.ts index 880669e..40d08f9 100644 --- a/packages/app-config/src/chains/darwinia.ts +++ b/packages/app-config/src/chains/darwinia.ts @@ -4,6 +4,8 @@ import multisigContract from "../abi/contract.json"; export const darwinia: ChainConfig = { name: "Darwinia", displayName: "Darwinia", + explorerURLs: ["https://darwinia.subscan.io/"], + httpsURLs: ["https://rpc.darwinia.network"], kton: { address: "0x0000000000000000000000000000000000000402", symbol: "KTON", diff --git a/packages/app-config/src/chains/pangolin.ts b/packages/app-config/src/chains/pangolin.ts index 15fc0ff..1a85c4f 100644 --- a/packages/app-config/src/chains/pangolin.ts +++ b/packages/app-config/src/chains/pangolin.ts @@ -4,6 +4,8 @@ import multisigContract from "../abi/contract.json"; export const pangolin: ChainConfig = { name: "Pangolin", displayName: "Pangolin", + explorerURLs: ["https://pangolin.subscan.io/"], + httpsURLs: ["https://pangolin-rpc.darwinia.network"], kton: { address: "0x0000000000000000000000000000000000000402", symbol: "PKTON", diff --git a/packages/app-config/src/chains/pangoro.ts b/packages/app-config/src/chains/pangoro.ts index 6ac8674..027e1ea 100644 --- a/packages/app-config/src/chains/pangoro.ts +++ b/packages/app-config/src/chains/pangoro.ts @@ -4,6 +4,8 @@ import multisigContract from "../abi/contract.json"; export const pangoro: ChainConfig = { name: "Pangoro", displayName: "Pangoro", + explorerURLs: ["https://pangoro.subscan.io/"], + httpsURLs: ["https://pangoro-rpc.darwinia.network"], kton: { address: "0x0000000000000000000000000000000000000402", symbol: "OKTON", @@ -16,7 +18,7 @@ export const pangoro: ChainConfig = { decimals: 9, }, contractAddresses: { - multisig: "0xF09FE18d3f4b4345b47c098C8B8Fba7743418aA4", + multisig: "0x6c25E0c1f57d7E78d7eB8D350f11204137EF71bE", }, contractInterface: { multisig: multisigContract, diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index d7cc371..f1bdf4f 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -28,10 +28,11 @@ import BigNumber from "bignumber.js"; import { FrameSystemAccountInfo } from "@darwinia/api-derive/accounts/types"; import { UnSubscription } from "./storageProvider"; import { Option, Vec } from "@polkadot/types"; -import { convertToSS58, setStore, getStore, createMultiSigAccount } from "@darwinia/app-utils"; +import { convertToSS58, setStore, getStore, createMultiSigAccount, convertNumberToHex } from "@darwinia/app-utils"; import useBlock from "./hooks/useBlock"; import useLedger from "./hooks/useLedger"; import { Contract, ethers } from "ethers"; +import { HexString } from "@polkadot/util/types"; /*This is just a blueprint, no value will be stored in here*/ const initialState: WalletCtx = { @@ -53,13 +54,25 @@ const initialState: WalletCtx = { currentBlock: undefined, isLoadingMultisigBalance: undefined, multisigContract: undefined, - setLoadingMultisigBalance: (isLoading: boolean) => {}, + isMultisigMigrationInitialized: undefined, + selectedEthereumAccount: undefined, + ethereumError: undefined, + isCorrectEthereumChain: undefined, + setLoadingMultisigBalance: (isLoading: boolean) => { + //ignore + }, + setMultisigMigrationInitialized: (isLoading: boolean) => { + //ignore + }, changeSelectedNetwork: () => { // do nothing }, connectWallet: () => { //do nothing }, + connectEthereumWallet: () => { + //ignore + }, disconnectWallet: () => { //do nothing }, @@ -76,12 +89,22 @@ const initialState: WalletCtx = { setMultisig: (value: boolean) => { //ignore }, - checkDarwiniaOneMultisigAccount: (signatories: string[], threshold: number, name?: string) => { + checkDarwiniaOneMultisigAccount: (initializer: string, signatories: string[], threshold: number, name?: string) => { return Promise.resolve(undefined); }, getAccountBalance: (account: string) => { return Promise.resolve(undefined); }, + onInitMultisigMigration: ( + from: string, + to: string, + initializer: string, + otherAccounts: string[], + threshold: string, + callback: (isSuccessful: boolean) => void + ) => { + //ignore + }, }; const WalletContext = createContext(initialState); @@ -90,11 +113,15 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [signer, setSigner] = useState(); const [isRequestingWalletConnection, setRequestingWalletConnection] = useState(false); const [isWalletConnected, setWalletConnected] = useState(false); + const [isRequestingEthereumWalletConnection, setRequestingEthereumWalletConnection] = useState(false); + const [isEthereumWalletConnected, setEthereumWalletConnected] = useState(false); const [selectedAccount, setSelectedAccount] = useState(); const [injectedAccounts, setInjectedAccounts] = useState(); + const [selectedEthereumAccount, setSelectedEthereumAccount] = useState(); const injectedAccountsRef = useRef([]); const forcedAccountAddress = useRef(); const [error, setError] = useState(undefined); + const [ethereumError, setEthereumError] = useState(undefined); const [selectedNetwork, setSelectedNetwork] = useState(); const [walletConfig, setWalletConfig] = useState(); const [isLoadingTransaction, setLoadingTransaction] = useState(false); @@ -110,6 +137,8 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [isLoadingMultisigBalance, setLoadingMultisigBalance] = useState(false); const [selectedWallet, _setSelectedWallet] = useState(); const [multisigContract, setMultisigContract] = useState(); + const [isMultisigMigrationInitialized, setMultisigMigrationInitialized] = useState(false); + const [isCorrectEthereumChain, setCorrectEthereumChain] = useState(false); const { currentBlock } = useBlock(apiPromise); const { getAccountAsset } = useLedger({ @@ -291,7 +320,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { }); }, [injectedAccountsRef.current, apiPromise, selectedNetwork, isMultisig, currentBlock]); - /*Connect to MetaMask*/ + /*Connect to Polkadot*/ const connectWallet = useCallback( async (name: SupportedWallet) => { if (!selectedNetwork || isRequestingWalletConnection) { @@ -303,19 +332,19 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { return; } - const injecteds = window.injectedWeb3; - const source = injecteds && walletCfg.sources.find((source) => injecteds[source]); - if (!source && name !== 'NovaWallet') { - setWalletConnected(false); - setRequestingWalletConnection(false); - setLoadingTransaction(false); - setLoadingBalance(false); - setError({ - code: 1, - message: "Please Install Polkadot JS Extension", - }); - return; - } + const injecteds = window.injectedWeb3; + const source = injecteds && walletCfg.sources.find((source) => injecteds[source]); + if (!source && name !== "NovaWallet") { + setWalletConnected(false); + setRequestingWalletConnection(false); + setLoadingTransaction(false); + setLoadingBalance(false); + setError({ + code: 1, + message: "Please Install Polkadot JS Extension", + }); + return; + } try { setWalletConnected(false); @@ -337,50 +366,191 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { // console.log("error"); }); - let accounts: InjectedAccountWithMeta[] = []; - if (name === 'NovaWallet') { - const extensions = await web3Enable(DARWINIA_APPS); - if (extensions.length) { - setSigner(extensions[0].signer); - const unfilteredAccounts = await web3Accounts(); - accounts = unfilteredAccounts - .filter((account) => !account.address.startsWith("0x")); - } - } else if (source) { - const wallet = injecteds[source]; - if (!wallet.enable) { - return; + let accounts: InjectedAccountWithMeta[] = []; + if (name === "NovaWallet") { + const extensions = await web3Enable(DARWINIA_APPS); + if (extensions.length) { + setSigner(extensions[0].signer); + const unfilteredAccounts = await web3Accounts(); + accounts = unfilteredAccounts.filter((account) => !account.address.startsWith("0x")); + } + } else if (source) { + const wallet = injecteds[source]; + if (!wallet.enable) { + return; + } + const res = await wallet.enable(DARWINIA_APPS); + if (res) { + const enabledExtensions = [res]; + + /* this is the signer that needs to be used when we sign a transaction */ + setSigner(enabledExtensions[0].signer); + /* this will return a list of all the accounts that are in the Polkadot extension */ + const unfilteredAccounts = await res.accounts.get(); + accounts = unfilteredAccounts + .filter((account) => !account.address.startsWith("0x")) + .map(({ address, genesisHash, name, type }) => ({ address, type, meta: { genesisHash, name, source } })); + } } - const res = await wallet.enable(DARWINIA_APPS); - if (res) { - const enabledExtensions = [res]; - - /* this is the signer that needs to be used when we sign a transaction */ - setSigner(enabledExtensions[0].signer); - /* this will return a list of all the accounts that are in the Polkadot extension */ - const unfilteredAccounts = await res.accounts.get(); - accounts = unfilteredAccounts - .filter((account) => !account.address.startsWith("0x")) - .map(({ address, genesisHash, name, type }) => ({ address, type, meta: { genesisHash, name, source } })); + accounts.forEach((account) => { + keyring.saveAddress(account.address, account.meta); + }); + injectedAccountsRef.current = accounts; + + if (accounts.length > 0) { + /* we default using the first account */ + setWalletConnected(true); } + setSelectedWallet(name); + } catch (e) { + setWalletConnected(false); + setRequestingWalletConnection(false); + setLoadingBalance(false); + //ignore } - accounts.forEach((account) => { - keyring.saveAddress(account.address, account.meta); - }); - injectedAccountsRef.current = accounts; + }, + [selectedNetwork, isRequestingWalletConnection, apiPromise, getPrettyName] + ); + + const isEthereumWalletInstalled = () => { + return !!window.ethereum; + }; + /* Listen to metamask account changes */ + useEffect(() => { + if (!isEthereumWalletInstalled() || !isEthereumWalletConnected || !selectedNetwork) { + setSelectedEthereumAccount(undefined); + return; + } + + const onAccountsChanged = (accounts: string[]) => { if (accounts.length > 0) { - /* we default using the first account */ - setWalletConnected(true); + const account = accounts[0]; + setSelectedEthereumAccount(account); + } + }; + + const onChainChanged = (chainId: HexString) => { + setCorrectEthereumChain(true); + const selectedNetworkChainId = convertNumberToHex(selectedNetwork?.chainId); + setCorrectEthereumChain(chainId === selectedNetworkChainId); + /*Metamask recommends reloading the whole page ref: https://docs.metamask.io/guide/ethereum-provider.html#events */ + // window.location.reload(); + }; + + window.ethereum?.on("accountsChanged", onAccountsChanged); + window.ethereum?.on("chainChanged", onChainChanged); + + return () => { + window.ethereum?.removeListener("accountsChanged", onAccountsChanged); + window.ethereum?.removeListener("chainChanged", onChainChanged); + }; + }, [isEthereumWalletConnected, selectedNetwork]); + + /*Connect to Ethereum wallet*/ + const connectEthereumWallet = useCallback(async () => { + if (!selectedNetwork || isRequestingEthereumWalletConnection) { + return; + } + try { + setEthereumWalletConnected(false); + if (!isEthereumWalletInstalled()) { + setEthereumError({ + code: 0, + message: "Wallet is not installed", + }); + setEthereumWalletConnected(false); + return; + } + + setRequestingEthereumWalletConnection(true); + //try switching the token to the selected network token + const chainResponse = await window.ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: ethers.utils.hexlify(selectedNetwork.chainId) }], + }); + if (!chainResponse) { + setCorrectEthereumChain(true); + //The chain was switched successfully, request account permission + // request account permission + try { + const accounts = await window.ethereum.request({ + method: "eth_requestAccounts", + }); + + if (accounts && Array.isArray(accounts) && accounts.length > 0) { + const account = accounts[0]; + setSelectedEthereumAccount(account); + setRequestingEthereumWalletConnection(false); + setEthereumWalletConnected(true); + } + } catch (e) { + console.log(e); + setRequestingEthereumWalletConnection(false); + setEthereumWalletConnected(false); + setEthereumError({ + code: 4, + message: "Account access permission rejected", + }); + } } - setSelectedWallet(name); } catch (e) { - setWalletConnected(false); - setRequestingWalletConnection(false); - setLoadingBalance(false); - //ignore + if ((e as { code: number }).code === 4902) { + /*Unrecognized chain, add it first*/ + try { + const addedChainResponse = await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: ethers.utils.hexlify(selectedNetwork.chainId), + chainName: selectedNetwork.name, + nativeCurrency: { + ...selectedNetwork.ring, + }, + rpcUrls: [...selectedNetwork.httpsURLs], + blockExplorerUrls: [...selectedNetwork.explorerURLs], + }, + ], + }); + if (!addedChainResponse) { + // request account permission + try { + const accounts = await window.ethereum.request({ + method: "eth_requestAccounts", + }); + if (accounts && Array.isArray(accounts) && accounts.length > 0) { + const account = accounts[0]; + setSelectedEthereumAccount(account); + setRequestingEthereumWalletConnection(false); + setEthereumWalletConnected(true); + } + } catch (e) { + setRequestingEthereumWalletConnection(false); + setEthereumError({ + code: 4, + message: "Account access permission rejected", + }); + setEthereumWalletConnected(false); + } + } + } catch (e) { + setEthereumError({ + code: 1, + message: "User rejected adding ethereum chain", + }); + setRequestingEthereumWalletConnection(false); + setEthereumWalletConnected(false); + } + return; + } + setRequestingEthereumWalletConnection(false); + setEthereumWalletConnected(false); + setEthereumError({ + code: 1, + message: "User rejected adding ethereum chain", + }); } - }, [selectedNetwork, isRequestingWalletConnection, apiPromise, getPrettyName]); + }, [isEthereumWalletInstalled, selectedNetwork, isRequestingEthereumWalletConnection]); const changeSelectedNetwork = useCallback( (network: ChainConfig) => { @@ -444,6 +614,61 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { [apiPromise, signer, specName] ); + const onInitMultisigMigration = useCallback( + async ( + from: string, + to: string, + initializer: string, + otherAccounts: string[], + threshold: string, + callback: (isSuccessful: boolean) => void + ) => { + let unSubscription: UnSubscription; + try { + if (!apiPromise || !signer?.signRaw || !specName) { + return callback(false); + } + + /*remove a digit from the network name such as pangolin2, etc*/ + const oldChainName = specName.slice(0, -1); + + const message = `I authorize the migration to ${to.toLowerCase()}, an unused address on ${specName}. Sign this message to authorize using the Substrate key associated with the account on ${oldChainName} that you wish to migrate.`; + + const { signature } = await signer.signRaw({ + address: from, + type: "bytes", + data: message, + }); + + const extrinsic = await apiPromise.tx.accountMigration.migrateMultisig( + initializer, + otherAccounts, + threshold, + to, + signature + ); + + unSubscription = (await extrinsic.send((result: SubmittableResult) => { + console.log(result.toHuman()); + if (result.isCompleted && result.isFinalized) { + setAccountMigratedJustNow(true); + callback(true); + } + })) as unknown as UnSubscription; + } catch (e) { + console.log(e); + callback(false); + } + + return () => { + if (unSubscription) { + unSubscription(); + } + }; + }, + [apiPromise, signer, specName] + ); + useEffect(() => { if (!apiPromise) { return; @@ -461,12 +686,14 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { }, [apiPromise]); const checkDarwiniaOneMultisigAccount = useCallback( - async (signatories: string[], threshold: number, name?: string) => { + async (initializer: string, signatories: string[], threshold: number, name?: string) => { if (!apiPromise || !selectedNetwork) { return Promise.resolve(undefined); } const newAccount = createMultiSigAccount(signatories, selectedNetwork.prefix, threshold); + console.log("signatories=====", signatories); + console.log("newAccount=====", newAccount); //check if the account really exists const response = await apiPromise.query.accountMigration.accounts(newAccount); // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -484,6 +711,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { name: name ?? "", who: [...signatories], threshold, + initializer, }, }; @@ -521,6 +749,13 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { isLoadingMultisigBalance, setLoadingMultisigBalance, multisigContract, + isMultisigMigrationInitialized, + setMultisigMigrationInitialized, + onInitMultisigMigration, + connectEthereumWallet, + ethereumError, + selectedEthereumAccount, + isCorrectEthereumChain, }} > {children} diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index 823ce5a..ac66201 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -38,6 +38,8 @@ export interface ChainConfig { chainId: number; ring: Token; kton: Token; + httpsURLs: string[]; + explorerURLs: string[]; prefix: number; substrate: Substrate; contractInterface: ContractABI; @@ -79,23 +81,38 @@ export interface WalletCtx { isRequestingWalletConnection: boolean; isWalletConnected: boolean; connectWallet: (id: SupportedWallet) => void; + connectEthereumWallet: () => void; disconnectWallet: () => void; forceSetAccountAddress: (accountAddress: string) => void; setSelectedAccount: (selectedAccount: CustomInjectedAccountWithMeta) => void; changeSelectedNetwork: (network: ChainConfig) => void; selectedNetwork: ChainConfig | undefined; error: WalletError | undefined; + ethereumError: WalletError | undefined; + selectedEthereumAccount: string | undefined; + isCorrectEthereumChain: boolean | undefined; selectedAccount: CustomInjectedAccountWithMeta | undefined; injectedAccounts: CustomInjectedAccountWithMeta[] | undefined; setTransactionStatus: (value: boolean) => void; isLoadingTransaction: boolean | undefined; onInitMigration: (from: string, to: string, callback: (isSuccessful: boolean) => void) => void; + onInitMultisigMigration: ( + from: string, + to: string, + initializer: string, + otherAccounts: string[], + threshold: string, + callback: (isSuccessful: boolean) => void + ) => void; isAccountMigratedJustNow: boolean | undefined; walletConfig: WalletConfig | undefined; isLoadingBalance: boolean | undefined; isMultisig: boolean | undefined; setMultisig: (value: boolean) => void; + setMultisigMigrationInitialized: (value: boolean) => void; + isMultisigMigrationInitialized: boolean | undefined; checkDarwiniaOneMultisigAccount: ( + initializer: string, signatories: string[], threshold: number, name?: string @@ -113,6 +130,7 @@ export interface SpVersionRuntimeVersion extends Struct { } export interface MultisigAccountMeta { + initializer: string; who: string[]; name: string; threshold: number; diff --git a/packages/app-utils/src/others.ts b/packages/app-utils/src/others.ts index 4d98c7f..40d26e6 100644 --- a/packages/app-utils/src/others.ts +++ b/packages/app-utils/src/others.ts @@ -3,8 +3,8 @@ import { STORAGE as APP_STORAGE } from "@darwinia/app-config"; import BigNumber from "bignumber.js"; import { ethers, utils } from "ethers"; import { encodeAddress, createKeyMulti, sortAddresses, isAddress, decodeAddress } from "@polkadot/util-crypto"; -import { u8aToHex } from "@polkadot/util"; -export { isMobile } from 'is-mobile'; +import { u8aToHex, numberToHex } from "@polkadot/util"; +export { isMobile } from "is-mobile"; export const setStore = (key: keyof Storage, value: unknown) => { try { @@ -140,3 +140,7 @@ export const getPublicKey = (accountAddress: string) => { const publicKeyArray = decodeAddress(accountAddress); return u8aToHex(publicKeyArray); }; + +export const convertNumberToHex = (number: number) => { + return numberToHex(number); +}; diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index 8f111fd..f41d21a 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -10,7 +10,7 @@ import JazzIcon from "../JazzIcon"; import { Tip } from "../MigrationForm"; import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; -import { isValidNumber, isEthereumAddress, getPublicKey } from "@darwinia/app-utils"; +import { isValidNumber, isEthereumAddress, getPublicKey, convertToSS58 } from "@darwinia/app-utils"; import { BigNumber as EthersBigNumber } from "ethers"; interface MultisigMemberAddress { @@ -20,11 +20,23 @@ interface MultisigMemberAddress { const MultisigAccountInfo = () => { const { t } = useAppTranslation(); - const { injectedAccounts, multisigContract } = useWallet(); + const { + injectedAccounts, + multisigContract, + setMultisigMigrationInitialized, + selectedNetwork, + onInitMultisigMigration, + connectEthereumWallet, + selectedEthereumAccount, + isCorrectEthereumChain, + } = useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); const address = params.get("address"); - const members = (params.get("who") ?? "").split(","); + const initializer = params.get("initializer"); + const members = (params.get("who") ?? "") + .split(",") + .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); const name = params.get("name"); const threshold = params.get("threshold"); const [isMemberSectionVisible, setMemberSectionVisible] = useState(true); @@ -34,7 +46,6 @@ const MultisigAccountInfo = () => { const [isAttentionModalVisible, setAttentionModalVisibility] = useState(false); const [isConfirmationModalVisible, setConfirmationModalVisibility] = useState(false); const [checkedTips, setCheckedTips] = useState([]); - const { selectedNetwork, onInitMigration } = useWallet(); const [isProcessingMigration, setProcessingMigration] = useState(false); const [memberAddresses, setMemberAddresses] = useState([ { id: new Date().getTime(), address: "" }, @@ -64,8 +75,14 @@ const MultisigAccountInfo = () => { setMigrationModalVisible(true); }; - const onCloseModal = () => { + const onCloseMigrationModal = () => { setMigrationModalVisible(false); + // reset form + setMemberAddresses([{ id: new Date().getTime(), address: "" }]); + setNewAccountThreshold(""); + setNewMultisigAccountAddress(""); + setDestinationAddress(""); + setActiveDestinationTab(1); }; const onContinueMigration = () => { @@ -82,7 +99,10 @@ const MultisigAccountInfo = () => { .sort() .map((item) => item); const thresholdNumber = EthersBigNumber.from(threshold); - + console.log("multisigContract", multisigContract); + console.log("publicKey", publicKey); + console.log("memberAddresses", memberAddresses); + console.log("thresholdNumber", thresholdNumber.toString()); const multisigAddress = await multisigContract?.computeAddress(publicKey, memberAddresses, thresholdNumber); setNewMultisigAccountAddress(multisigAddress); setIsGeneratingMultisigAccount(false); @@ -148,14 +168,22 @@ const MultisigAccountInfo = () => { }; const onConfirmAndMigrate = async () => { - if (!address) { + if (!address || !initializer || !threshold) { return; } try { setProcessingMigration(true); const destination = activeDestinationTab === 1 ? destinationAddress : newMultisigAccountAddress; - onInitMigration(address, destination, (isSuccessful) => { - setProcessingMigration(isSuccessful); + + const urlParams = new URLSearchParams(location.search); + urlParams.set("destination", destination); + + window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); + + setMultisigMigrationInitialized(true); + const otherAccounts = members.filter((account) => account !== initializer); + onInitMultisigMigration(address, destination, initializer, otherAccounts, threshold, (isSuccessful) => { + console.log("isSuccessful======", isSuccessful); }); } catch (e) { setProcessingMigration(false); @@ -193,6 +221,10 @@ const MultisigAccountInfo = () => { console.log("generate share link"); }; + const initEthereumWalletConnection = () => { + connectEthereumWallet(); + }; + return (
{/*one more step to deploy*/} @@ -285,7 +317,7 @@ const MultisigAccountInfo = () => { onConfirm={onContinueMigration} confirmDisabled={isContinueButtonDisabled()} isVisible={isMigrateModalVisible} - onClose={onCloseModal} + onClose={onCloseMigrationModal} modalTitle={t(localeKeys.migration)} >
@@ -365,6 +397,15 @@ const MultisigAccountInfo = () => {
{t(localeKeys.membersAddress)}
+
+ {/*TODO: change this logic accordingly*/} +
+ {isCorrectEthereumChain ? selectedEthereumAccount : "Connect Metamask"} +
+
{memberAddresses.map((item, index) => { return (
diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 62e4206..8412664 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -33,6 +33,7 @@ interface MultisigAccountData { who: string[]; asset: Asset; threshold: number; + initializer: string; } const MultisigMigrationProcess = () => { @@ -146,7 +147,7 @@ const MultisigMigrationProcess = () => { className={"!h-[30px]"} btnType={"secondary"} > - Migrate + {t(localeKeys.migrate)}
); @@ -159,6 +160,7 @@ const MultisigMigrationProcess = () => { const params = new URLSearchParams(location.search); params.set("address", item.formattedAddress); params.set("name", item.name); + params.set("initializer", item.initializer); params.set("who", item.who.join(",")); params.set("threshold", item.threshold.toString()); navigate(`/multisig-account-migration-summary?${params.toString()}`); @@ -178,6 +180,7 @@ const MultisigMigrationProcess = () => { address: accountItem.address, formattedAddress: formattedAddress, name: accountItem.meta.name, + initializer: accountItem.meta.initializer, asset: { ring: asset?.ring.transferable ?? BigNumber(0), kton: asset?.kton.transferable ?? BigNumber(0), @@ -271,7 +274,7 @@ const MultisigMigrationProcess = () => { } setCheckingAccountExistence(true); const thresholdNumber = Number(threshold); - const account = await checkDarwiniaOneMultisigAccount(signatories, thresholdNumber, name); + const account = await checkDarwiniaOneMultisigAccount(selectedAddress, signatories, thresholdNumber, name); setCheckingAccountExistence(false); if (typeof account === "undefined") { diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx index 06bddc4..e3a737a 100644 --- a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -5,10 +5,11 @@ import { useLocation } from "react-router-dom"; import { useState } from "react"; import MultisigMigrationProgressTabs from "../components/MultisigMigrationProgressTabs"; import MigrationSummary from "../components/MigrationSummary"; +import { useWallet } from "@darwinia/app-providers"; const MultisigAccountMigrationSummary = () => { const { t } = useAppTranslation(); - const [isAccountMigrationInitialized, setAccountMigrationInitialized] = useState(true); + const { isMultisigMigrationInitialized } = useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); @@ -35,13 +36,10 @@ const MultisigAccountMigrationSummary = () => { return (
- - {isAccountMigrationInitialized ? ( + {isMultisigMigrationInitialized ? ( ) : ( -
- -
+ )}
{footerLinks.map((item, index) => { From 30caeb7be212df9b6985ec097022a2ec6be724f4 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Mon, 27 Mar 2023 20:30:40 +0800 Subject: [PATCH 21/36] account can be migrated but not ready to deploy --- packages/app-locale/src/localeKeys.ts | 2 + packages/app-locale/src/translations/enUS.ts | 2 + packages/app-providers/src/walletProvider.tsx | 66 ++++- packages/app-types/src/others.ts | 14 +- packages/app-types/src/wallet.ts | 15 +- .../src/components/MigrationSummary/index.tsx | 6 + .../components/MultisigAccountInfo/index.tsx | 106 ++++++-- .../MultisigMigrationProgressTabs/index.tsx | 234 +++++++++++------- .../pages/MultisigAccountMigrationSummary.tsx | 37 ++- 9 files changed, 345 insertions(+), 137 deletions(-) diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index a9b8845..bef07c5 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -105,4 +105,6 @@ export const localeKeys = { invalidThreshold: "invalidThreshold", invalidMemberAddress: "invalidMemberAddress", selectYourAddress: "selectYourAddress", + initialized: "initialized", + approved: "approved", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index 2b2e5dd..f5aee26 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -107,6 +107,8 @@ const enUs = { [localeKeys.invalidThreshold]: "Invalid threshold", [localeKeys.invalidMemberAddress]: "Invalid member address", [localeKeys.selectYourAddress]: "Select your address", + [localeKeys.initialized]: "Initialized", + [localeKeys.approved]: "Approved", }; export default enUs; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index f1bdf4f..e69d079 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -17,6 +17,7 @@ import { Deposit, DarwiniaStakingLedger, AssetDistribution, + DarwiniaAccountMigrationMultisig, } from "@darwinia/app-types"; import { ApiPromise, WsProvider, SubmittableResult } from "@polkadot/api"; import { web3Accounts, web3Enable } from "@polkadot/extension-dapp"; @@ -27,12 +28,13 @@ import { keyring } from "@polkadot/ui-keyring"; import BigNumber from "bignumber.js"; import { FrameSystemAccountInfo } from "@darwinia/api-derive/accounts/types"; import { UnSubscription } from "./storageProvider"; -import { Option, Vec } from "@polkadot/types"; +import { Option } from "@polkadot/types"; import { convertToSS58, setStore, getStore, createMultiSigAccount, convertNumberToHex } from "@darwinia/app-utils"; import useBlock from "./hooks/useBlock"; import useLedger from "./hooks/useLedger"; import { Contract, ethers } from "ethers"; import { HexString } from "@polkadot/util/types"; +import { Codec } from "@polkadot/types/types"; /*This is just a blueprint, no value will be stored in here*/ const initialState: WalletCtx = { @@ -54,16 +56,14 @@ const initialState: WalletCtx = { currentBlock: undefined, isLoadingMultisigBalance: undefined, multisigContract: undefined, - isMultisigMigrationInitialized: undefined, selectedEthereumAccount: undefined, ethereumError: undefined, isCorrectEthereumChain: undefined, + multisigMigrationStatus: undefined, + isMultisigAccountMigratedJustNow: undefined, setLoadingMultisigBalance: (isLoading: boolean) => { //ignore }, - setMultisigMigrationInitialized: (isLoading: boolean) => { - //ignore - }, changeSelectedNetwork: () => { // do nothing }, @@ -95,9 +95,16 @@ const initialState: WalletCtx = { getAccountBalance: (account: string) => { return Promise.resolve(undefined); }, + checkMultisigAccountMigrationStatus: (account: string) => { + return Promise.resolve(undefined); + }, + getAccountPrettyName: () => { + return Promise.resolve(undefined); + }, onInitMultisigMigration: ( from: string, to: string, + signerAddress: string, initializer: string, otherAccounts: string[], threshold: string, @@ -132,13 +139,14 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const DARWINIA_APPS = "darwinia/apps"; const isKeyringInitialized = useRef(false); const [isAccountMigratedJustNow, setAccountMigratedJustNow] = useState(false); + const [isMultisigAccountMigratedJustNow, setMultisigAccountMigratedJustNow] = useState(false); const [specName, setSpecName] = useState(); const [isMultisig, setMultisig] = useState(false); const [isLoadingMultisigBalance, setLoadingMultisigBalance] = useState(false); const [selectedWallet, _setSelectedWallet] = useState(); const [multisigContract, setMultisigContract] = useState(); - const [isMultisigMigrationInitialized, setMultisigMigrationInitialized] = useState(false); const [isCorrectEthereumChain, setCorrectEthereumChain] = useState(false); + const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); const { currentBlock } = useBlock(apiPromise); const { getAccountAsset } = useLedger({ @@ -416,6 +424,13 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { return !!window.ethereum; }; + const getAccountPrettyName = useCallback( + (address: string): Promise => { + return getPrettyName(address); + }, + [apiPromise] + ); + /* Listen to metamask account changes */ useEffect(() => { if (!isEthereumWalletInstalled() || !isEthereumWalletConnected || !selectedNetwork) { @@ -618,6 +633,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { async ( from: string, to: string, + signerAddress: string, initializer: string, otherAccounts: string[], threshold: string, @@ -625,20 +641,24 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { ) => { let unSubscription: UnSubscription; try { + console.log("imeingia====="); if (!apiPromise || !signer?.signRaw || !specName) { return callback(false); } + setMultisigAccountMigratedJustNow(false); + console.log("level=====1"); /*remove a digit from the network name such as pangolin2, etc*/ const oldChainName = specName.slice(0, -1); const message = `I authorize the migration to ${to.toLowerCase()}, an unused address on ${specName}. Sign this message to authorize using the Substrate key associated with the account on ${oldChainName} that you wish to migrate.`; - + console.log("level=====2", signerAddress); const { signature } = await signer.signRaw({ - address: from, + address: signerAddress, type: "bytes", data: message, }); + console.log("level=====3"); const extrinsic = await apiPromise.tx.accountMigration.migrateMultisig( initializer, @@ -651,11 +671,12 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { unSubscription = (await extrinsic.send((result: SubmittableResult) => { console.log(result.toHuman()); if (result.isCompleted && result.isFinalized) { - setAccountMigratedJustNow(true); + setMultisigAccountMigratedJustNow(true); callback(true); } })) as unknown as UnSubscription; } catch (e) { + setMultisigAccountMigratedJustNow(false); console.log(e); callback(false); } @@ -669,6 +690,27 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { [apiPromise, signer, specName] ); + const checkMultisigAccountMigrationStatus = useCallback( + async (accountAddress: string): Promise => { + if (!apiPromise) { + setMultisigMigrationStatus(undefined); + return Promise.resolve(undefined); + } + + const result = (await apiPromise.query.accountMigration.multisigs(accountAddress)) as unknown as Option; + if (!result.isSome) { + setMultisigMigrationStatus(undefined); + return Promise.resolve(undefined); + } + const resultString = result.unwrap().toHuman(); + const resultObj = resultString as unknown as DarwiniaAccountMigrationMultisig; + resultObj.threshold = Number(resultObj.threshold); + setMultisigMigrationStatus(resultObj); + return Promise.resolve(resultObj); + }, + [apiPromise] + ); + useEffect(() => { if (!apiPromise) { return; @@ -749,13 +791,15 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { isLoadingMultisigBalance, setLoadingMultisigBalance, multisigContract, - isMultisigMigrationInitialized, - setMultisigMigrationInitialized, onInitMultisigMigration, connectEthereumWallet, ethereumError, selectedEthereumAccount, isCorrectEthereumChain, + checkMultisigAccountMigrationStatus, + multisigMigrationStatus, + getAccountPrettyName, + isMultisigAccountMigratedJustNow, }} > {children} diff --git a/packages/app-types/src/others.ts b/packages/app-types/src/others.ts index 11ce54c..f74e254 100644 --- a/packages/app-types/src/others.ts +++ b/packages/app-types/src/others.ts @@ -1,12 +1,24 @@ -import { MultisigAccount, SupportedWallet } from "./wallet"; +import { DestinationType, MultisigAccount, SupportedWallet } from "./wallet"; export interface Account { id: number; } +export interface Destination { + address: string; + type: DestinationType; + members: string[]; + threshold: number; +} + +export interface DestinationInfo { + [accountAddress: string]: Destination; +} + export interface Storage { isConnectedToWallet?: boolean; selectedNetwork?: boolean; selectedWallet?: SupportedWallet; multisigAccounts?: MultisigAccount[]; + destinationInfo: DestinationInfo; } diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index ac66201..308c0cc 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -99,6 +99,7 @@ export interface WalletCtx { onInitMultisigMigration: ( from: string, to: string, + signerAddress: string, initializer: string, otherAccounts: string[], threshold: string, @@ -109,8 +110,6 @@ export interface WalletCtx { isLoadingBalance: boolean | undefined; isMultisig: boolean | undefined; setMultisig: (value: boolean) => void; - setMultisigMigrationInitialized: (value: boolean) => void; - isMultisigMigrationInitialized: boolean | undefined; checkDarwiniaOneMultisigAccount: ( initializer: string, signatories: string[], @@ -118,11 +117,15 @@ export interface WalletCtx { name?: string ) => Promise; getAccountBalance: (account: string) => Promise; + checkMultisigAccountMigrationStatus: (account: string) => Promise; apiPromise: ApiPromise | undefined; currentBlock: CurrentBlock | undefined; isLoadingMultisigBalance: boolean | undefined; setLoadingMultisigBalance: (isLoading: boolean) => void; multisigContract: Contract | undefined; + multisigMigrationStatus: DarwiniaAccountMigrationMultisig | undefined; + getAccountPrettyName: (address: string) => Promise; + isMultisigAccountMigratedJustNow: boolean | undefined; } export interface SpVersionRuntimeVersion extends Struct { @@ -145,3 +148,11 @@ export interface CurrentBlock { number: number; timestamp: number; } + +export interface DarwiniaAccountMigrationMultisig { + threshold: number; + migrateTo: string; + members: [string, boolean][]; +} + +export type DestinationType = "General Account" | "Multisig Account"; diff --git a/packages/app/src/components/MigrationSummary/index.tsx b/packages/app/src/components/MigrationSummary/index.tsx index f278038..8e7602b 100644 --- a/packages/app/src/components/MigrationSummary/index.tsx +++ b/packages/app/src/components/MigrationSummary/index.tsx @@ -30,6 +30,12 @@ const MigrationSummary = ({ isCheckingMigrationStatus, accountAddress }: Props) setTransactionStatus(!!isLoadingLedger || !!isCheckingMigrationStatus || isLoadingBalance); }, [isLoadingLedger, isCheckingMigrationStatus, isLoadingBalance]); + useEffect(() => { + return () => { + setTransactionStatus(false); + }; + }, []); + useEffect(() => { if (!apiPromise || !currentBlock || !isInitializingLocalAccountsRef.current) { return; diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index f41d21a..b24a737 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -1,7 +1,7 @@ import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; import { useWallet } from "@darwinia/app-providers"; import { useLocation } from "react-router-dom"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import Identicon from "@polkadot/react-identicon"; import copyIcon from "../../assets/images/copy.svg"; import { Button, CheckboxGroup, Input, ModalEnhanced, SlideDownUp, Tooltip } from "@darwinia/ui"; @@ -10,29 +10,35 @@ import JazzIcon from "../JazzIcon"; import { Tip } from "../MigrationForm"; import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; -import { isValidNumber, isEthereumAddress, getPublicKey, convertToSS58 } from "@darwinia/app-utils"; +import { isValidNumber, isEthereumAddress, getPublicKey, convertToSS58, setStore, getStore } from "@darwinia/app-utils"; import { BigNumber as EthersBigNumber } from "ethers"; +import { DestinationInfo, DestinationType } from "@darwinia/app-types"; interface MultisigMemberAddress { address: string; id: number; } -const MultisigAccountInfo = () => { +interface Props { + isIsWaitingToDeploy: boolean; + isSuccessfullyMigrated: boolean; +} + +const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Props) => { const { t } = useAppTranslation(); const { injectedAccounts, multisigContract, - setMultisigMigrationInitialized, selectedNetwork, onInitMultisigMigration, connectEthereumWallet, selectedEthereumAccount, isCorrectEthereumChain, + multisigMigrationStatus, } = useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); - const address = params.get("address"); + const address = convertToSS58(params.get("address") ?? "", selectedNetwork?.prefix ?? 18); const initializer = params.get("initializer"); const members = (params.get("who") ?? "") .split(",") @@ -47,14 +53,16 @@ const MultisigAccountInfo = () => { const [isConfirmationModalVisible, setConfirmationModalVisibility] = useState(false); const [checkedTips, setCheckedTips] = useState([]); const [isProcessingMigration, setProcessingMigration] = useState(false); + /* Member addresses will ways be one item less since the first address will always be chosen + * automatically on MetaMask */ const [memberAddresses, setMemberAddresses] = useState([ { id: new Date().getTime(), address: "" }, ]); + //stores sorted members account address + const allMembersRef = useRef([]); const [newAccountThreshold, setNewAccountThreshold] = useState(""); const [newMultisigAccountAddress, setNewMultisigAccountAddress] = useState(""); const [isIsGeneratingMultisigAccount, setIsGeneratingMultisigAccount] = useState(false); - const [isSuccessfullyMigrated, setIsSuccessfullyMigrated] = useState(false); - const [isIsWaitingToDeploy, setIsWaitingToDeploy] = useState(false); const destinationTabs = [ { @@ -90,23 +98,29 @@ const MultisigAccountInfo = () => { setAttentionModalVisibility(true); }; - const generateMultisigAccount = async (members: MultisigMemberAddress[], address: string) => { + const generateMultisigAccount = async ( + members: MultisigMemberAddress[], + selectedEthereumAccount: string, + multisigSubstrateAddress: string + ) => { try { setIsGeneratingMultisigAccount(true); - const publicKey = getPublicKey(address); - const memberAddresses = members - .map((item) => item.address) - .sort() - .map((item) => item); - const thresholdNumber = EthersBigNumber.from(threshold); + const publicKey = getPublicKey(multisigSubstrateAddress); + const memberAddresses = members.map((item) => item.address); + + memberAddresses.unshift(selectedEthereumAccount); + const sortedMembers = memberAddresses.sort(); + const thresholdNumber = EthersBigNumber.from(newAccountThreshold); console.log("multisigContract", multisigContract); console.log("publicKey", publicKey); - console.log("memberAddresses", memberAddresses); + console.log("memberAddresses", sortedMembers); console.log("thresholdNumber", thresholdNumber.toString()); - const multisigAddress = await multisigContract?.computeAddress(publicKey, memberAddresses, thresholdNumber); + const multisigAddress = await multisigContract?.computeAddress(publicKey, sortedMembers, thresholdNumber); + allMembersRef.current = [...sortedMembers]; setNewMultisigAccountAddress(multisigAddress); setIsGeneratingMultisigAccount(false); } catch (e) { + allMembersRef.current = []; setIsGeneratingMultisigAccount(false); console.log(e); } @@ -159,7 +173,7 @@ const MultisigAccountInfo = () => { } else { const isValidThreshold = isValidNumber(newAccountThreshold); const isValidMultisigAddress = isEthereumAddress(newMultisigAccountAddress); - return !isValidThreshold || !isValidMultisigAddress || isIsGeneratingMultisigAccount; + return !isValidThreshold || !isValidMultisigAddress || isIsGeneratingMultisigAccount || !isCorrectEthereumChain; } }; @@ -174,18 +188,51 @@ const MultisigAccountInfo = () => { try { setProcessingMigration(true); const destination = activeDestinationTab === 1 ? destinationAddress : newMultisigAccountAddress; + const type: DestinationType = activeDestinationTab === 1 ? "General Account" : "Multisig Account"; const urlParams = new URLSearchParams(location.search); urlParams.set("destination", destination); + urlParams.set("destinationMembers", allMembersRef.current.join(",")); + if (activeDestinationTab === 2) { + urlParams.set("destinationThreshold", newAccountThreshold); + } + urlParams.set("destinationType", type); + + const oldData = getStore("destinationInfo") ?? {}; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + oldData[destination] = { + threshold: Number(newAccountThreshold), + address: destination, + type: type, + members: allMembersRef.current, + }; + + setStore("destinationInfo", oldData); window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); - setMultisigMigrationInitialized(true); const otherAccounts = members.filter((account) => account !== initializer); - onInitMultisigMigration(address, destination, initializer, otherAccounts, threshold, (isSuccessful) => { - console.log("isSuccessful======", isSuccessful); - }); + //TODO update the signer accordingly + const signerAddress = initializer; + onInitMultisigMigration( + address, + destination, + signerAddress, + initializer, + otherAccounts, + threshold, + (isSuccessful) => { + if (isSuccessful) { + setConfirmationModalVisibility(false); + } else { + console.log("something went wrong in migration"); + } + console.log("isSuccessful======", isSuccessful); + } + ); } catch (e) { + console.log(e); setProcessingMigration(false); } }; @@ -200,8 +247,15 @@ const MultisigAccountInfo = () => { const members = [...memberAddresses]; members[index].address = value; setMemberAddresses(() => members); - if (address && members.length >= Number(newAccountThreshold ?? 1)) { - generateMultisigAccount(members, address); + /* Member addresses will ways be one item less since the first address will always be chosen + * automatically on MetaMask */ + if ( + selectedEthereumAccount && + isCorrectEthereumChain && + address && + members.length >= Number(newAccountThreshold ?? 1) - 1 + ) { + generateMultisigAccount(members, selectedEthereumAccount, address); } }; @@ -268,7 +322,7 @@ const MultisigAccountInfo = () => { image
- + {!multisigMigrationStatus && }
@@ -292,7 +346,9 @@ const MultisigAccountInfo = () => {
{members.map((member, index) => { const isMyAccount = !!injectedAccounts?.find( - (account) => account.address.toLowerCase() === member.toLowerCase() + (account) => + convertToSS58(account.address, selectedNetwork?.prefix ?? 18).toLowerCase() === + member.toLowerCase() ); return (
{ +interface Props { + migrationStatus: DarwiniaAccountMigrationMultisig | undefined; + isWaitingToDeploy: boolean; +} + +interface MemberStatus { + name: string; + address: string; + hasApproved: boolean; +} + +const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: Props) => { const { t } = useAppTranslation(); - const [memberAccounts, setMemberAccounts] = useState(); + const [memberAccounts, setMemberAccounts] = useState([]); const location = useLocation(); - const { selectedNetwork } = useWallet(); + const { selectedNetwork, getAccountPrettyName, apiPromise } = useWallet(); const params = new URLSearchParams(location.search); - const address = params.get("address"); - const members = (params.get("who") ?? "").split(","); - const name = params.get("name"); + const members = (params.get("who") ?? "") + .split(",") + .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); const threshold = params.get("threshold"); + const initializer = convertToSS58(params.get("initializer") ?? "", selectedNetwork?.prefix ?? 18); + const [haveAllMembersApproved, setHaveAllMembersApproved] = useState(false); + const [destination, setDestination] = useState(); useEffect(() => { - const members = (params.get("who") ?? "").split(","); - setMemberAccounts(members); - }, [location]); - - const tabs: Tab[] = [ - { - id: "1", - title: t(localeKeys.inProgress, { number: 3 }), - }, - { - id: "2", - title: t(localeKeys.confirmedExtrinsic, { number: 0 }), - }, - { - id: "3", - title: t(localeKeys.cancelledExtrinsics, { number: 0 }), - }, - ]; - const [selectedTab, setSelectedTab] = useState(tabs[0]); - const onTabsChange = (selectedTab: Tab) => { - setSelectedTab(selectedTab); - }; + if (!apiPromise || !migrationStatus || !location) { + return; + } - const parameters = { - destination: { - account: "0xe59261f6D4088BcD69985A3D369Ff14cC54EF1E5", - status: 0, - }, - type: "Multisig Account", - threshold: threshold, - member: members, - asset: { - ring: BigNumber(20000000000000000000), - kton: BigNumber(35000000000000000000), - }, - }; + const prepareMembers = async () => { + setMemberAccounts([]); + let approvedCounter = 0; + const tempMembers: MemberStatus[] = []; + for (let i = 0; i < members.length; i++) { + const address = members[i]; + const account = migrationStatus.members.filter((member) => member[0] === address); + if (account.length > 0) { + const hasApproved = account[0][1]; + const name = (await getAccountPrettyName(address)) ?? ""; + if (hasApproved) { + approvedCounter = approvedCounter + 1; + } + const status: MemberStatus = { + address, + hasApproved, + name, + }; + tempMembers.push(status); + } + } + setMemberAccounts((old) => { + return [...old, ...tempMembers]; + }); + if (approvedCounter < Number(threshold)) { + setHaveAllMembersApproved(false); + } else { + setHaveAllMembersApproved(true); + } + }; + + prepareMembers().catch((e) => { + console.log(e); + }); + }, [location, apiPromise, migrationStatus]); + + useEffect(() => { + if (migrationStatus && location) { + const destination = migrationStatus.migrateTo; + const destinationInfo = getStore("destinationInfo"); + + const searchParams = new URLSearchParams(location.search); + if (searchParams.get("destinationMembers")) { + // this is a shared link + + const destinationAddress = searchParams.get("destination"); + const destinationMembers = (searchParams.get("destinationMembers") ?? "").split(","); + const type = searchParams.get("destinationType") as DestinationType; + const threshold = Number(searchParams.get("destinationThreshold")); + setDestination({ + type, + threshold, + address: destinationAddress ?? "", + members: destinationMembers, + }); + } else if (destinationInfo && destinationInfo[destination]) { + const value = destinationInfo[destination]; + const urlParams = new URLSearchParams(location.search); + + urlParams.set("destination", value.address); + urlParams.set("destinationMembers", value.members.join(",")); + if (value.type === "Multisig Account") { + urlParams.set("destinationThreshold", `${value.threshold}`); + } + urlParams.set("destinationType", value.type); + + window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); + setDestination(value); + } + } + }, [migrationStatus, location]); const ringTokenIcon = selectedNetwork?.name === "Crab" ? crabIcon : ringIcon; const ktonTokenIcon = selectedNetwork?.name === "Crab" ? cktonIcon : ktonIcon; @@ -76,45 +130,41 @@ const MultisigMigrationProgressTabs = () => { return (
-
- -
{/*in progress*/}
-
-
{t(localeKeys.callHash)}
-
{t(localeKeys.status)}
-
{t(localeKeys.progress)}
-
{t(localeKeys.action)}
-
-
-
0x0E55c72781aCD923C4e3e7Ad9bB8363de15ef204
-
Multisig_Account_Migrate
-
3/3
-
{t(localeKeys.executed)}
-
-
+
{t(localeKeys.progress)}
- {memberAccounts?.map((item) => { + {memberAccounts?.map((item, index) => { + let tag = ""; + if (initializer === item.address) { + tag = t(localeKeys.initialized); + } else if (item.hasApproved) { + tag = t(localeKeys.approved); + } else { + tag = "-"; + } return ( -
-
onchainmoney.com
+
+
{item.name}
-
{item}
+
{item.address}
-
Initialized
+
{tag}
); })} @@ -128,41 +178,47 @@ const MultisigMigrationProgressTabs = () => {
{t(localeKeys.destination)}
-
{parameters.destination.account}
-
- - icon - -
{t(localeKeys.waitingDeploy)}
-
+
{destination?.address}
+ {!haveAllMembersApproved && ( +
+ + icon + +
{t(localeKeys.waitingDeploy)}
+
+ )}
{t(localeKeys.type)}
-
{parameters.type}
-
-
-
{t(localeKeys.threshold)}
-
{parameters.threshold}
+
{destination?.type}
-
-
{t(localeKeys.members)}
-
- {parameters.member.map((item, index) => { - return
{item}
; - })} + {destination?.type === "Multisig Account" && ( +
+
{t(localeKeys.threshold)}
+
{destination?.threshold}
-
-
+ )} + {destination?.type === "Multisig Account" && ( +
+
{t(localeKeys.members)}
+
+ {destination?.members.map((item, index) => { + return
{item}
; + })} +
+
+ )} + {/*
{t(localeKeys.asset)}
image
- + {prettifyNumber({ - number: parameters.asset.ring, + number: destinationParameters.asset.ring, })} {selectedNetwork?.ring.symbol.toUpperCase()} @@ -174,9 +230,9 @@ const MultisigMigrationProgressTabs = () => {
image
- + {prettifyNumber({ - number: parameters.asset.kton, + number: destinationParameters.asset.kton, })} {selectedNetwork?.kton.symbol.toUpperCase()} @@ -187,7 +243,7 @@ const MultisigMigrationProgressTabs = () => {
-
+
*/}
diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx index e3a737a..05d7e35 100644 --- a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -2,21 +2,37 @@ import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; import TokensBalanceSummary from "../components/TokensBalanceSummary"; import MultisigAccountInfo from "../components/MultisigAccountInfo"; import { useLocation } from "react-router-dom"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import MultisigMigrationProgressTabs from "../components/MultisigMigrationProgressTabs"; import MigrationSummary from "../components/MigrationSummary"; import { useWallet } from "@darwinia/app-providers"; +import { convertToSS58 } from "@darwinia/app-utils"; +import { DarwiniaAccountMigrationMultisig } from "@darwinia/app-types"; const MultisigAccountMigrationSummary = () => { const { t } = useAppTranslation(); - const { isMultisigMigrationInitialized } = useWallet(); + const { checkMultisigAccountMigrationStatus, selectedNetwork, apiPromise, isMultisigAccountMigratedJustNow } = + useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); - const address = params.get("address"); - const members = (params.get("who") ?? "").split(","); - const name = params.get("name"); - const threshold = params.get("threshold"); + const address = convertToSS58(params.get("address") ?? "", selectedNetwork?.prefix ?? 18); + const [isSuccessfullyMigrated, setIsSuccessfullyMigrated] = useState(false); + const [isIsWaitingToDeploy, setIsWaitingToDeploy] = useState(false); + const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); + + useEffect(() => { + const checkStatus = async () => { + const result = await checkMultisigAccountMigrationStatus(address); + setMultisigMigrationStatus(result); + }; + if (apiPromise || isMultisigAccountMigratedJustNow) { + checkStatus().catch((e) => { + console.log(e); + //ignore + }); + } + }, [apiPromise, isMultisigAccountMigratedJustNow]); const footerLinks = [ { @@ -35,9 +51,12 @@ const MultisigAccountMigrationSummary = () => { return (
- - {isMultisigMigrationInitialized ? ( - + + {multisigMigrationStatus ? ( + ) : ( )} From c6da4b5fd48c3955208d51a167760f88134cd477 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Tue, 28 Mar 2023 18:37:28 +0800 Subject: [PATCH 22/36] account ready to deploy --- packages/app-locale/src/localeKeys.ts | 2 + packages/app-locale/src/translations/enUS.ts | 2 + packages/app-providers/src/walletProvider.tsx | 64 ++++++++++++++-- packages/app-types/src/wallet.ts | 7 +- .../components/MultisigAccountInfo/index.tsx | 63 ++++++++++------ .../MultisigMigrationProgressTabs/index.tsx | 74 ++++++++++++++----- 6 files changed, 162 insertions(+), 50 deletions(-) diff --git a/packages/app-locale/src/localeKeys.ts b/packages/app-locale/src/localeKeys.ts index bef07c5..621e32a 100644 --- a/packages/app-locale/src/localeKeys.ts +++ b/packages/app-locale/src/localeKeys.ts @@ -107,4 +107,6 @@ export const localeKeys = { selectYourAddress: "selectYourAddress", initialized: "initialized", approved: "approved", + migrationFailed: "migrationFailed", + approve: "approve", }; diff --git a/packages/app-locale/src/translations/enUS.ts b/packages/app-locale/src/translations/enUS.ts index f5aee26..d6cf72e 100644 --- a/packages/app-locale/src/translations/enUS.ts +++ b/packages/app-locale/src/translations/enUS.ts @@ -109,6 +109,8 @@ const enUs = { [localeKeys.selectYourAddress]: "Select your address", [localeKeys.initialized]: "Initialized", [localeKeys.approved]: "Approved", + [localeKeys.migrationFailed]: "Migration failed", + [localeKeys.approve]: "Approve", }; export default enUs; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index e69d079..dcc8334 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -102,7 +102,6 @@ const initialState: WalletCtx = { return Promise.resolve(undefined); }, onInitMultisigMigration: ( - from: string, to: string, signerAddress: string, initializer: string, @@ -112,6 +111,14 @@ const initialState: WalletCtx = { ) => { //ignore }, + onApproveMultisigMigration: ( + from: string, + to: string, + signerAddress: string, + callback: (isSuccessful: boolean) => void + ) => { + //ignore + }, }; const WalletContext = createContext(initialState); @@ -631,7 +638,6 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const onInitMultisigMigration = useCallback( async ( - from: string, to: string, signerAddress: string, initializer: string, @@ -641,24 +647,20 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { ) => { let unSubscription: UnSubscription; try { - console.log("imeingia====="); if (!apiPromise || !signer?.signRaw || !specName) { return callback(false); } setMultisigAccountMigratedJustNow(false); - console.log("level=====1"); /*remove a digit from the network name such as pangolin2, etc*/ const oldChainName = specName.slice(0, -1); const message = `I authorize the migration to ${to.toLowerCase()}, an unused address on ${specName}. Sign this message to authorize using the Substrate key associated with the account on ${oldChainName} that you wish to migrate.`; - console.log("level=====2", signerAddress); const { signature } = await signer.signRaw({ address: signerAddress, type: "bytes", data: message, }); - console.log("level=====3"); const extrinsic = await apiPromise.tx.accountMigration.migrateMultisig( initializer, @@ -690,6 +692,55 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { [apiPromise, signer, specName] ); + const onApproveMultisigMigration = useCallback( + async (from: string, to: string, signerAddress: string, callback: (isSuccessful: boolean) => void) => { + let unSubscription: UnSubscription; + try { + if (!apiPromise || !signer?.signRaw || !specName) { + return callback(false); + } + setMultisigAccountMigratedJustNow(false); + + /*remove a digit from the network name such as pangolin2, etc*/ + const oldChainName = specName.slice(0, -1); + + const message = `I authorize the migration to ${to.toLowerCase()}, an unused address on ${specName}. Sign this message to authorize using the Substrate key associated with the account on ${oldChainName} that you wish to migrate.`; + const { signature } = await signer.signRaw({ + address: signerAddress, + type: "bytes", + data: message, + }); + console.log("to=====", to); + console.log("signerAddress=====", signerAddress); + + const extrinsic = await apiPromise.tx.accountMigration.completeMultisigMigration( + from, + signerAddress, + signature + ); + + unSubscription = (await extrinsic.send((result: SubmittableResult) => { + console.log(result.toHuman()); + if (result.isCompleted && result.isFinalized) { + setMultisigAccountMigratedJustNow(true); + callback(true); + } + })) as unknown as UnSubscription; + } catch (e) { + setMultisigAccountMigratedJustNow(false); + console.log(e); + callback(false); + } + + return () => { + if (unSubscription) { + unSubscription(); + } + }; + }, + [apiPromise, signer, specName] + ); + const checkMultisigAccountMigrationStatus = useCallback( async (accountAddress: string): Promise => { if (!apiPromise) { @@ -792,6 +843,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { setLoadingMultisigBalance, multisigContract, onInitMultisigMigration, + onApproveMultisigMigration, connectEthereumWallet, ethereumError, selectedEthereumAccount, diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index 308c0cc..c4f97b3 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -97,7 +97,6 @@ export interface WalletCtx { isLoadingTransaction: boolean | undefined; onInitMigration: (from: string, to: string, callback: (isSuccessful: boolean) => void) => void; onInitMultisigMigration: ( - from: string, to: string, signerAddress: string, initializer: string, @@ -105,6 +104,12 @@ export interface WalletCtx { threshold: string, callback: (isSuccessful: boolean) => void ) => void; + onApproveMultisigMigration: ( + from: string, + to: string, + signerAddress: string, + callback: (isSuccessful: boolean) => void + ) => void; isAccountMigratedJustNow: boolean | undefined; walletConfig: WalletConfig | undefined; isLoadingBalance: boolean | undefined; diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index b24a737..e61504e 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -4,7 +4,7 @@ import { useLocation } from "react-router-dom"; import { useEffect, useRef, useState } from "react"; import Identicon from "@polkadot/react-identicon"; import copyIcon from "../../assets/images/copy.svg"; -import { Button, CheckboxGroup, Input, ModalEnhanced, SlideDownUp, Tooltip } from "@darwinia/ui"; +import { Button, CheckboxGroup, Input, ModalEnhanced, notification, SlideDownUp, Tooltip } from "@darwinia/ui"; import caretIcon from "../../assets/images/caret-down.svg"; import JazzIcon from "../JazzIcon"; import { Tip } from "../MigrationForm"; @@ -35,11 +35,12 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr selectedEthereumAccount, isCorrectEthereumChain, multisigMigrationStatus, + setTransactionStatus, } = useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); const address = convertToSS58(params.get("address") ?? "", selectedNetwork?.prefix ?? 18); - const initializer = params.get("initializer"); + const initializer = convertToSS58(params.get("initializer") ?? "", selectedNetwork?.prefix ?? 18); const members = (params.get("who") ?? "") .split(",") .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); @@ -111,10 +112,6 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr memberAddresses.unshift(selectedEthereumAccount); const sortedMembers = memberAddresses.sort(); const thresholdNumber = EthersBigNumber.from(newAccountThreshold); - console.log("multisigContract", multisigContract); - console.log("publicKey", publicKey); - console.log("memberAddresses", sortedMembers); - console.log("thresholdNumber", thresholdNumber.toString()); const multisigAddress = await multisigContract?.computeAddress(publicKey, sortedMembers, thresholdNumber); allMembersRef.current = [...sortedMembers]; setNewMultisigAccountAddress(multisigAddress); @@ -213,24 +210,17 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); const otherAccounts = members.filter((account) => account !== initializer); - //TODO update the signer accordingly - const signerAddress = initializer; - onInitMultisigMigration( - address, - destination, - signerAddress, - initializer, - otherAccounts, - threshold, - (isSuccessful) => { - if (isSuccessful) { - setConfirmationModalVisibility(false); - } else { - console.log("something went wrong in migration"); - } - console.log("isSuccessful======", isSuccessful); + + onInitMultisigMigration(destination, initializer, initializer, otherAccounts, threshold, (isSuccessful) => { + setProcessingMigration(false); + if (isSuccessful) { + setConfirmationModalVisibility(false); + } else { + notification.error({ + message:
{t(localeKeys.migrationFailed)}
, + }); } - ); + }); } catch (e) { console.log(e); setProcessingMigration(false); @@ -243,6 +233,25 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr setMemberAddresses(addresses); }; + const onDeploy = async () => { + try { + setTransactionStatus(true); + const publicKey = getPublicKey(address); + const ethereumMemberAddresses = memberAddresses.map((item) => item.address); + + if (selectedEthereumAccount) { + ethereumMemberAddresses.unshift(selectedEthereumAccount); + } + const sortedMembers = ethereumMemberAddresses.sort(); + const thresholdNumber = EthersBigNumber.from(newAccountThreshold); + const deploymentResult = await multisigContract?.deploy(publicKey, sortedMembers, thresholdNumber); + setTransactionStatus(false); + } catch (e) { + setTransactionStatus(false); + console.log(e); + } + }; + const onMemberAddressChanged = (index: number, value: string) => { const members = [...memberAddresses]; members[index].address = value; @@ -286,7 +295,13 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr
{t(localeKeys.oneMoreStep)}
- +
{t(localeKeys.toCompleteMigration)}
diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx index 45c5177..418076d 100644 --- a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -1,4 +1,4 @@ -import { Tabs, Tab, Tooltip } from "@darwinia/ui"; +import { Tabs, Tab, Tooltip, Button, notification } from "@darwinia/ui"; import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; import { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; @@ -29,15 +29,28 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P const { t } = useAppTranslation(); const [memberAccounts, setMemberAccounts] = useState([]); const location = useLocation(); - const { selectedNetwork, getAccountPrettyName, apiPromise } = useWallet(); + const { + selectedNetwork, + getAccountPrettyName, + apiPromise, + injectedAccounts, + onApproveMultisigMigration, + setTransactionStatus, + } = useWallet(); const params = new URLSearchParams(location.search); + const address = convertToSS58(params.get("address") ?? "", selectedNetwork?.prefix ?? 18); const members = (params.get("who") ?? "") .split(",") .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); const threshold = params.get("threshold"); const initializer = convertToSS58(params.get("initializer") ?? "", selectedNetwork?.prefix ?? 18); + const destinationAddress = params.get("destination"); + const destinationMembers = (params.get("destinationMembers") ?? "").split(",").filter((item) => item.trim() !== ""); + const type = params.get("destinationType") as DestinationType; + const destinationThreshold = Number(params.get("destinationThreshold")); const [haveAllMembersApproved, setHaveAllMembersApproved] = useState(false); const [destination, setDestination] = useState(); + const [recentlyApprovedAddresses, setRecentlyApprovedAddresses] = useState([]); useEffect(() => { if (!apiPromise || !migrationStatus || !location) { @@ -50,9 +63,9 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P const tempMembers: MemberStatus[] = []; for (let i = 0; i < members.length; i++) { const address = members[i]; - const account = migrationStatus.members.filter((member) => member[0] === address); - if (account.length > 0) { - const hasApproved = account[0][1]; + const account = migrationStatus.members.find((member) => member[0] === address); + if (account) { + const hasApproved = account[1] || recentlyApprovedAddresses.includes(account[0]); const name = (await getAccountPrettyName(address)) ?? ""; if (hasApproved) { approvedCounter = approvedCounter + 1; @@ -65,9 +78,8 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P tempMembers.push(status); } } - setMemberAccounts((old) => { - return [...old, ...tempMembers]; - }); + setMemberAccounts([...tempMembers]); + if (approvedCounter < Number(threshold)) { setHaveAllMembersApproved(false); } else { @@ -78,24 +90,18 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P prepareMembers().catch((e) => { console.log(e); }); - }, [location, apiPromise, migrationStatus]); + }, [location, apiPromise, migrationStatus, recentlyApprovedAddresses]); useEffect(() => { if (migrationStatus && location) { const destination = migrationStatus.migrateTo; const destinationInfo = getStore("destinationInfo"); - const searchParams = new URLSearchParams(location.search); - if (searchParams.get("destinationMembers")) { + if (destinationMembers.length > 0) { // this is a shared link - - const destinationAddress = searchParams.get("destination"); - const destinationMembers = (searchParams.get("destinationMembers") ?? "").split(","); - const type = searchParams.get("destinationType") as DestinationType; - const threshold = Number(searchParams.get("destinationThreshold")); setDestination({ type, - threshold, + threshold: destinationThreshold, address: destinationAddress ?? "", members: destinationMembers, }); @@ -103,6 +109,7 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P const value = destinationInfo[destination]; const urlParams = new URLSearchParams(location.search); + //set the URL params accordingly for later use urlParams.set("destination", value.address); urlParams.set("destinationMembers", value.members.join(",")); if (value.type === "Multisig Account") { @@ -127,6 +134,26 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P return
KTON Message
; }; + const approveMigration = (signerAddress: string) => { + if (!destination?.address || typeof threshold === "undefined" || threshold === null) { + return; + } + + setTransactionStatus(true); + onApproveMultisigMigration(address, destination.address, signerAddress, (isSuccessful) => { + if (isSuccessful) { + setRecentlyApprovedAddresses((old) => { + return [...old, signerAddress]; + }); + } else { + notification.error({ + message:
{t(localeKeys.migrationFailed)}
, + }); + } + setTransactionStatus(false); + }); + }; + return (
@@ -141,18 +168,27 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P
{t(localeKeys.progress)}
{memberAccounts?.map((item, index) => { - let tag = ""; + let tag: JSX.Element | string = ""; + const isMyAccount = !!injectedAccounts?.find( + (account) => account.formattedAddress.toLowerCase() === item.address.toLowerCase() + ); if (initializer === item.address) { tag = t(localeKeys.initialized); } else if (item.hasApproved) { tag = t(localeKeys.approved); + } else if (isMyAccount) { + tag = ( + + ); } else { tag = "-"; } return (
{item.name}
From 879a8207ad20b0983e2304376b5612f36ad8e11b Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 29 Mar 2023 11:13:39 +0800 Subject: [PATCH 23/36] resolved the balance issue when switching network --- packages/app-providers/src/hooks/useLedger.ts | 19 ++++++++++++++---- packages/app-providers/src/walletProvider.tsx | 9 +++++++-- packages/app/src/Root.tsx | 20 +++++++++++-------- packages/app/src/components/Header/index.tsx | 13 +++++++++--- .../MultisigMigrationProcess/index.tsx | 6 +++++- packages/app/src/pages/MultisigHome.tsx | 2 +- 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/packages/app-providers/src/hooks/useLedger.ts b/packages/app-providers/src/hooks/useLedger.ts index a4ddec1..02f49dc 100644 --- a/packages/app-providers/src/hooks/useLedger.ts +++ b/packages/app-providers/src/hooks/useLedger.ts @@ -21,11 +21,13 @@ interface Params { apiPromise: ApiPromise | undefined; selectedAccount: string | undefined; selectedNetwork: ChainConfig | undefined; + isWalletCaller?: boolean; } -const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => { +const useLedger = ({ apiPromise, selectedAccount, selectedNetwork, isWalletCaller = false }: Params) => { const [isLoadingLedger, setLoadingLedger] = useState(false); const [isLoadingMigratedLedger, setLoadingMigratedLedger] = useState(false); + const [isLoadingWalletLedger, setLoadingWalletLedger] = useState(false); const isInitialLoad = useRef(true); const isInitialMigratedDataLoad = useRef(true); /*staking asset distribution*/ @@ -38,9 +40,13 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => if (selectedAccount) { setStakedAssetDistribution(undefined); setMigratedAssetDistribution(undefined); - isInitialLoad.current = true; - setLoadingMigratedLedger(true); - setLoadingLedger(true); + if (!isWalletCaller) { + isInitialLoad.current = true; + setLoadingMigratedLedger(true); + setLoadingLedger(true); + } else { + setLoadingWalletLedger(true); + } } }, [selectedAccount, selectedNetwork]); @@ -59,6 +65,8 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => setLoadingLedger(false); return Promise.resolve(undefined); } + + setLoadingWalletLedger(true); const api = isDataAtPoint ? await apiPromise.at(parentBlockHash ?? "") : apiPromise; if (showLoading) { @@ -321,6 +329,7 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => } else { setLoadingLedger(false); } + setLoadingWalletLedger(false); return Promise.resolve(asset); }; const asset = await getStakingLedgerAndDeposits().catch((e) => { @@ -361,6 +370,7 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => } setLoadingMigratedLedger(false); setLoadingLedger(false); + setLoadingWalletLedger(false); // console.log(e); //ignore @@ -393,6 +403,7 @@ const useLedger = ({ apiPromise, selectedAccount, selectedNetwork }: Params) => isLoadingMigratedLedger, retrieveMigratedAsset, migratedAssetDistribution, + isLoadingWalletLedger, }; }; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index dcc8334..e4d7c78 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -156,12 +156,17 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); const { currentBlock } = useBlock(apiPromise); - const { getAccountAsset } = useLedger({ + const { getAccountAsset, isLoadingWalletLedger } = useLedger({ apiPromise, selectedAccount: selectedAccount?.formattedAddress, selectedNetwork, + isWalletCaller: true, }); + useEffect(() => { + setLoadingMultisigBalance(isLoadingWalletLedger); + }, [isLoadingWalletLedger]); + const setSelectedWallet = useCallback((name: SupportedWallet | null | undefined) => { _setSelectedWallet(name); setStore("selectedWallet", name); @@ -322,7 +327,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { formattedAddress: convertToSS58(forcedAccountAddress.current, selectedNetwork?.prefix ?? 18), }); } - if (customAccounts.length > 0) { + if (customAccounts.length > 0 && !isMultisig) { setSelectedAccount(customAccounts[0]); } setInjectedAccounts(customAccounts); diff --git a/packages/app/src/Root.tsx b/packages/app/src/Root.tsx index 9da2d1f..3c4e5c5 100644 --- a/packages/app/src/Root.tsx +++ b/packages/app/src/Root.tsx @@ -19,6 +19,7 @@ const Root = () => { setMultisig, isLoadingBalance, isLoadingMultisigBalance, + isMultisig, } = useWallet(); const { isLoadingLedger, isLoadingMigratedLedger } = useStorage(); const [loading, setLoading] = useState(false); @@ -27,14 +28,17 @@ const Root = () => { const { t } = useAppTranslation(); useEffect(() => { - setLoading( - isRequestingWalletConnection || - isLoadingTransaction || - isLoadingLedger || - isLoadingMigratedLedger || - isLoadingBalance || - isLoadingMultisigBalance - ); + if (isMultisig) { + setLoading(isRequestingWalletConnection || isLoadingTransaction || isLoadingBalance || isLoadingMultisigBalance); + } else { + setLoading( + isRequestingWalletConnection || + isLoadingTransaction || + isLoadingLedger || + isLoadingMigratedLedger || + isLoadingBalance + ); + } }, [ isRequestingWalletConnection, isWalletConnected, diff --git a/packages/app/src/components/Header/index.tsx b/packages/app/src/components/Header/index.tsx index 252d7c9..7d74f0b 100644 --- a/packages/app/src/components/Header/index.tsx +++ b/packages/app/src/components/Header/index.tsx @@ -1,4 +1,4 @@ -import { Link, useLocation, useSearchParams } from "react-router-dom"; +import { Link, useLocation, useNavigate, useSearchParams } from "react-router-dom"; import logoIcon from "../../assets/images/logo.png"; import caretIcon from "../../assets/images/caret-down.svg"; import { useEffect, useRef, useState } from "react"; @@ -25,7 +25,9 @@ const Header = () => { } = useWallet(); const [searchParams, setSearchParams] = useSearchParams(); const location = useLocation(); + const navigate = useNavigate(); const selectAccountModalRef = useRef(null); + const shouldRefreshPage = useRef(false); /* set the wallet network accordingly */ useEffect(() => { @@ -38,7 +40,7 @@ const Header = () => { // the URL contains the network param const foundNetwork = supportedNetworks.find((item) => item.name.toLowerCase() === network.toLowerCase()); if (foundNetwork) { - changeConnectedNetwork(foundNetwork); + changeSelectedNetwork(foundNetwork); } } if (account) { @@ -47,7 +49,7 @@ const Header = () => { } else { /* use test network by default */ const index = supportedNetworks.findIndex((network) => network.name === "Crab"); - changeConnectedNetwork(supportedNetworks[index]); + changeSelectedNetwork(supportedNetworks[index]); } }, []); @@ -58,6 +60,11 @@ const Header = () => { searchParams.set("network", selectedNetwork.name); setSearchParams(searchParams); + if (shouldRefreshPage.current) { + window.location.reload(); + } else { + shouldRefreshPage.current = true; + } }, [selectedNetwork]); const changeConnectedNetwork = (network: ChainConfig) => { diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 8412664..a75a480 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -194,6 +194,10 @@ const MultisigMigrationProcess = () => { return Promise.resolve(data); }; + useEffect(() => { + isInitializingLocalAccountsRef.current = true; + }, [selectedNetwork]); + useEffect(() => { if (!apiPromise || !currentBlock || !isInitializingLocalAccountsRef.current) { return; @@ -207,7 +211,7 @@ const MultisigMigrationProcess = () => { initData().catch((e) => { console.log(e); }); - }, [apiPromise, currentBlock]); + }, [apiPromise, currentBlock, selectedNetwork]); useEffect(() => { if (currentAccount.current?.address !== selectedAccount?.address) { diff --git a/packages/app/src/pages/MultisigHome.tsx b/packages/app/src/pages/MultisigHome.tsx index 921075e..df30ed6 100644 --- a/packages/app/src/pages/MultisigHome.tsx +++ b/packages/app/src/pages/MultisigHome.tsx @@ -2,7 +2,7 @@ import { useAppTranslation, localeKeys } from "@darwinia/app-locale"; import { useMobile, useWallet } from "@darwinia/app-providers"; import migrationIcon from "../assets/images/migration.svg"; import { useLocation } from "react-router-dom"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { dAppSupportedWallets } from "@darwinia/app-config"; const MultisigHome = () => { From 0b5efe3eccdc987168feb9db2ee69a2a0ab21a13 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Wed, 29 Mar 2023 17:41:27 +0800 Subject: [PATCH 24/36] fixed the smart contract connection issue --- packages/app-providers/src/walletProvider.tsx | 9 +++++---- .../app/src/components/MultisigAccountInfo/index.tsx | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index e4d7c78..59c2ba2 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -205,7 +205,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { /*This will be fired once the connection to the wallet is successful*/ useEffect(() => { - if (!selectedAccount || !selectedNetwork) { + if (!selectedNetwork) { return; } //refresh the page with the newly selected account @@ -218,7 +218,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { ); setMultisigContract(multisigContract); - }, [selectedAccount, selectedNetwork]); + }, [selectedNetwork]); const disconnectWallet = useCallback(() => { setSelectedAccount(undefined); @@ -412,8 +412,9 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { .map(({ address, genesisHash, name, type }) => ({ address, type, meta: { genesisHash, name, source } })); } } + accounts.forEach((account) => { - keyring.saveAddress(account.address, account.meta); + keyring.saveAddress(convertToSS58(account.address, selectedNetwork.prefix), account.meta); }); injectedAccountsRef.current = accounts; @@ -462,7 +463,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const selectedNetworkChainId = convertNumberToHex(selectedNetwork?.chainId); setCorrectEthereumChain(chainId === selectedNetworkChainId); /*Metamask recommends reloading the whole page ref: https://docs.metamask.io/guide/ethereum-provider.html#events */ - // window.location.reload(); + window.location.reload(); }; window.ethereum?.on("accountsChanged", onAccountsChanged); diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index e61504e..31e6922 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -113,6 +113,10 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr const sortedMembers = memberAddresses.sort(); const thresholdNumber = EthersBigNumber.from(newAccountThreshold); const multisigAddress = await multisigContract?.computeAddress(publicKey, sortedMembers, thresholdNumber); + if (!multisigAddress) { + console.log("multisig account not generated"); + return; + } allMembersRef.current = [...sortedMembers]; setNewMultisigAccountAddress(multisigAddress); setIsGeneratingMultisigAccount(false); From a85cb1c3c8b47052100be40be374cb422b4f1c19 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Thu, 30 Mar 2023 20:02:22 +0800 Subject: [PATCH 25/36] Added subquery --- packages/app-config/src/gql.ts | 11 ++++++ packages/app-providers/src/walletProvider.tsx | 23 ++++++++++-- packages/app-types/src/wallet.ts | 8 +++++ .../components/MultisigAccountInfo/index.tsx | 35 +++++++++++++------ .../MultisigMigrationProgressTabs/index.tsx | 10 ++---- .../pages/MultisigAccountMigrationSummary.tsx | 26 ++++++++++++-- 6 files changed, 91 insertions(+), 22 deletions(-) diff --git a/packages/app-config/src/gql.ts b/packages/app-config/src/gql.ts index 9c0e781..75551e4 100644 --- a/packages/app-config/src/gql.ts +++ b/packages/app-config/src/gql.ts @@ -12,3 +12,14 @@ export const FIND_MIGRATION_BY_SOURCE_ADDRESS = gql` } } `; + +export const FIND_MULTISIG_MIGRATION_BY_SOURCE_ADDRESS = gql` + query migrationQuery($accountAddress: String!) { + multisigAccountMigration(id: $accountAddress) { + id + params + blockTime + blockNumber + } + } +`; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index 59c2ba2..e94f813 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -18,6 +18,7 @@ import { DarwiniaStakingLedger, AssetDistribution, DarwiniaAccountMigrationMultisig, + MultisigParams, } from "@darwinia/app-types"; import { ApiPromise, WsProvider, SubmittableResult } from "@polkadot/api"; import { web3Accounts, web3Enable } from "@polkadot/extension-dapp"; @@ -35,6 +36,7 @@ import useLedger from "./hooks/useLedger"; import { Contract, ethers } from "ethers"; import { HexString } from "@polkadot/util/types"; import { Codec } from "@polkadot/types/types"; +import { Web3Provider } from "@ethersproject/providers"; /*This is just a blueprint, no value will be stored in here*/ const initialState: WalletCtx = { @@ -107,6 +109,7 @@ const initialState: WalletCtx = { initializer: string, otherAccounts: string[], threshold: string, + multisigParams: MultisigParams, callback: (isSuccessful: boolean) => void ) => { //ignore @@ -119,6 +122,9 @@ const initialState: WalletCtx = { ) => { //ignore }, + isMultisigAccountDeployed: (accountAddress: string) => { + return Promise.resolve(false); + }, }; const WalletContext = createContext(initialState); @@ -152,6 +158,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [isLoadingMultisigBalance, setLoadingMultisigBalance] = useState(false); const [selectedWallet, _setSelectedWallet] = useState(); const [multisigContract, setMultisigContract] = useState(); + const [provider, setProvider] = useState(); const [isCorrectEthereumChain, setCorrectEthereumChain] = useState(false); const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); @@ -216,7 +223,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { selectedNetwork.contractInterface.multisig, newSigner ); - + setProvider(newProvider); setMultisigContract(multisigContract); }, [selectedNetwork]); @@ -649,6 +656,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { initializer: string, otherAccounts: string[], threshold: string, + multisigParams: MultisigParams, callback: (isSuccessful: boolean) => void ) => { let unSubscription: UnSubscription; @@ -673,7 +681,8 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { otherAccounts, threshold, to, - signature + signature, + multisigParams ); unSubscription = (await extrinsic.send((result: SubmittableResult) => { @@ -819,6 +828,15 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { [apiPromise, selectedNetwork] ); + const isMultisigAccountDeployed = useCallback( + async (accountAddress: string): Promise => { + const result = await provider?.getCode(accountAddress); + console.log("result====", result); + return result !== "0x"; + }, + [provider] + ); + return ( { multisigMigrationStatus, getAccountPrettyName, isMultisigAccountMigratedJustNow, + isMultisigAccountDeployed, }} > {children} diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index c4f97b3..4ef37f3 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -102,6 +102,7 @@ export interface WalletCtx { initializer: string, otherAccounts: string[], threshold: string, + multisigParams: MultisigParams, callback: (isSuccessful: boolean) => void ) => void; onApproveMultisigMigration: ( @@ -131,6 +132,7 @@ export interface WalletCtx { multisigMigrationStatus: DarwiniaAccountMigrationMultisig | undefined; getAccountPrettyName: (address: string) => Promise; isMultisigAccountMigratedJustNow: boolean | undefined; + isMultisigAccountDeployed: (accountAddress: string) => Promise; } export interface SpVersionRuntimeVersion extends Struct { @@ -161,3 +163,9 @@ export interface DarwiniaAccountMigrationMultisig { } export type DestinationType = "General Account" | "Multisig Account"; + +export interface MultisigParams { + address: string; + members: string[]; + threshold: number; +} diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index 31e6922..c7b7b41 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -12,7 +12,7 @@ import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; import { isValidNumber, isEthereumAddress, getPublicKey, convertToSS58, setStore, getStore } from "@darwinia/app-utils"; import { BigNumber as EthersBigNumber } from "ethers"; -import { DestinationInfo, DestinationType } from "@darwinia/app-types"; +import { DestinationInfo, DestinationType, MultisigParams } from "@darwinia/app-types"; interface MultisigMemberAddress { address: string; @@ -202,7 +202,7 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr const oldData = getStore("destinationInfo") ?? {}; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - oldData[destination] = { + oldData[address] = { threshold: Number(newAccountThreshold), address: destination, type: type, @@ -214,17 +214,30 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); const otherAccounts = members.filter((account) => account !== initializer); + const multisigParams: MultisigParams = { + address: newMultisigAccountAddress, + threshold: Number(newAccountThreshold ?? 0), + members: allMembersRef.current, + }; - onInitMultisigMigration(destination, initializer, initializer, otherAccounts, threshold, (isSuccessful) => { - setProcessingMigration(false); - if (isSuccessful) { - setConfirmationModalVisibility(false); - } else { - notification.error({ - message:
{t(localeKeys.migrationFailed)}
, - }); + onInitMultisigMigration( + destination, + initializer, + initializer, + otherAccounts, + threshold, + multisigParams, + (isSuccessful) => { + setProcessingMigration(false); + if (isSuccessful) { + setConfirmationModalVisibility(false); + } else { + notification.error({ + message:
{t(localeKeys.migrationFailed)}
, + }); + } } - }); + ); } catch (e) { console.log(e); setProcessingMigration(false); diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx index 418076d..c8732ca 100644 --- a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -43,7 +43,6 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P .split(",") .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); const threshold = params.get("threshold"); - const initializer = convertToSS58(params.get("initializer") ?? "", selectedNetwork?.prefix ?? 18); const destinationAddress = params.get("destination"); const destinationMembers = (params.get("destinationMembers") ?? "").split(",").filter((item) => item.trim() !== ""); const type = params.get("destinationType") as DestinationType; @@ -94,7 +93,6 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P useEffect(() => { if (migrationStatus && location) { - const destination = migrationStatus.migrateTo; const destinationInfo = getStore("destinationInfo"); if (destinationMembers.length > 0) { @@ -105,8 +103,8 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P address: destinationAddress ?? "", members: destinationMembers, }); - } else if (destinationInfo && destinationInfo[destination]) { - const value = destinationInfo[destination]; + } else if (destinationInfo && destinationInfo[address]) { + const value = destinationInfo[address]; const urlParams = new URLSearchParams(location.search); //set the URL params accordingly for later use @@ -172,9 +170,7 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P const isMyAccount = !!injectedAccounts?.find( (account) => account.formattedAddress.toLowerCase() === item.address.toLowerCase() ); - if (initializer === item.address) { - tag = t(localeKeys.initialized); - } else if (item.hasApproved) { + if (item.hasApproved) { tag = t(localeKeys.approved); } else if (isMyAccount) { tag = ( diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx index 05d7e35..c75c131 100644 --- a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -8,11 +8,18 @@ import MigrationSummary from "../components/MigrationSummary"; import { useWallet } from "@darwinia/app-providers"; import { convertToSS58 } from "@darwinia/app-utils"; import { DarwiniaAccountMigrationMultisig } from "@darwinia/app-types"; +import { useQuery } from "@apollo/client"; +import { FIND_MIGRATION_BY_SOURCE_ADDRESS } from "@darwinia/app-config"; const MultisigAccountMigrationSummary = () => { const { t } = useAppTranslation(); - const { checkMultisigAccountMigrationStatus, selectedNetwork, apiPromise, isMultisigAccountMigratedJustNow } = - useWallet(); + const { + checkMultisigAccountMigrationStatus, + selectedNetwork, + apiPromise, + isMultisigAccountMigratedJustNow, + isMultisigAccountDeployed, + } = useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); @@ -21,9 +28,24 @@ const MultisigAccountMigrationSummary = () => { const [isIsWaitingToDeploy, setIsWaitingToDeploy] = useState(false); const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); + /*const { + loading: isLoading, + data: migrationResult, + error, + refetch, + } = useQuery(FIND_MIGRATION_BY_SOURCE_ADDRESS, { + variables: { + accountAddress: selectedAccount?.formattedAddress ?? "", + }, + });*/ + useEffect(() => { const checkStatus = async () => { const result = await checkMultisigAccountMigrationStatus(address); + if (!result) { + //the account either doesn't exist or was already migrated + //check subquery to see if it was migrated + } setMultisigMigrationStatus(result); }; if (apiPromise || isMultisigAccountMigratedJustNow) { From 57e74e7f48c5215ac790e9cdbb2bb3367ddf2cd2 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Fri, 31 Mar 2023 19:03:02 +0800 Subject: [PATCH 26/36] -code refactor --- packages/app-config/src/chains/pangoro.ts | 2 +- packages/app-config/src/gql.ts | 13 +- packages/app-providers/src/walletProvider.tsx | 24 +-- packages/app-types/src/wallet.ts | 12 +- packages/app/src/Root.tsx | 10 +- .../components/MultisigAccountInfo/index.tsx | 48 ++++-- .../MultisigMigrationProcess/index.tsx | 11 +- .../MultisigMigrationProgressTabs/index.tsx | 42 ++--- packages/app/src/pages/Migration.tsx | 2 +- .../pages/MultisigAccountMigrationSummary.tsx | 152 +++++++++++++++--- 10 files changed, 233 insertions(+), 83 deletions(-) diff --git a/packages/app-config/src/chains/pangoro.ts b/packages/app-config/src/chains/pangoro.ts index 027e1ea..78be784 100644 --- a/packages/app-config/src/chains/pangoro.ts +++ b/packages/app-config/src/chains/pangoro.ts @@ -26,7 +26,7 @@ export const pangoro: ChainConfig = { chainId: 45, prefix: 18, substrate: { - graphQlURL: "https://subql.darwinia.network/subql-apps-pangoro", + graphQlURL: "https://api.subquery.network/sq/isunaslabs/pangoro", wssURL: "wss://pangoro-rpc.darwinia.network", }, }; diff --git a/packages/app-config/src/gql.ts b/packages/app-config/src/gql.ts index 75551e4..fbf8144 100644 --- a/packages/app-config/src/gql.ts +++ b/packages/app-config/src/gql.ts @@ -14,7 +14,7 @@ export const FIND_MIGRATION_BY_SOURCE_ADDRESS = gql` `; export const FIND_MULTISIG_MIGRATION_BY_SOURCE_ADDRESS = gql` - query migrationQuery($accountAddress: String!) { + query multisigMigrationQuery($accountAddress: String!) { multisigAccountMigration(id: $accountAddress) { id params @@ -23,3 +23,14 @@ export const FIND_MULTISIG_MIGRATION_BY_SOURCE_ADDRESS = gql` } } `; + +export const FIND_MIGRATION_DESTINATION_PARAMS_BY_SOURCE_ADDRESS = gql` + query multisigDestinationMigrationQuery($accountAddress: String!) { + multisigDestinationAccount(id: $accountAddress) { + id + params + blockTime + blockNumber + } + } +`; diff --git a/packages/app-providers/src/walletProvider.tsx b/packages/app-providers/src/walletProvider.tsx index e94f813..1c6c427 100644 --- a/packages/app-providers/src/walletProvider.tsx +++ b/packages/app-providers/src/walletProvider.tsx @@ -17,8 +17,8 @@ import { Deposit, DarwiniaStakingLedger, AssetDistribution, - DarwiniaAccountMigrationMultisig, - MultisigParams, + DarwiniaSourceAccountMigrationMultisig, + MultisigDestinationParams, } from "@darwinia/app-types"; import { ApiPromise, WsProvider, SubmittableResult } from "@polkadot/api"; import { web3Accounts, web3Enable } from "@polkadot/extension-dapp"; @@ -63,6 +63,7 @@ const initialState: WalletCtx = { isCorrectEthereumChain: undefined, multisigMigrationStatus: undefined, isMultisigAccountMigratedJustNow: undefined, + isCheckingMultisigCompleted: undefined, setLoadingMultisigBalance: (isLoading: boolean) => { //ignore }, @@ -109,7 +110,7 @@ const initialState: WalletCtx = { initializer: string, otherAccounts: string[], threshold: string, - multisigParams: MultisigParams, + multisigDestinationParams: MultisigDestinationParams | null, callback: (isSuccessful: boolean) => void ) => { //ignore @@ -125,6 +126,9 @@ const initialState: WalletCtx = { isMultisigAccountDeployed: (accountAddress: string) => { return Promise.resolve(false); }, + setIsCheckingMultisigCompleted: (isLoading: boolean) => { + //ignore + }, }; const WalletContext = createContext(initialState); @@ -160,7 +164,8 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const [multisigContract, setMultisigContract] = useState(); const [provider, setProvider] = useState(); const [isCorrectEthereumChain, setCorrectEthereumChain] = useState(false); - const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); + const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); + const [isCheckingMultisigCompleted, setIsCheckingMultisigCompleted] = useState(false); const { currentBlock } = useBlock(apiPromise); const { getAccountAsset, isLoadingWalletLedger } = useLedger({ @@ -656,7 +661,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { initializer: string, otherAccounts: string[], threshold: string, - multisigParams: MultisigParams, + multisigDestinationParams: MultisigDestinationParams | null, callback: (isSuccessful: boolean) => void ) => { let unSubscription: UnSubscription; @@ -682,7 +687,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { threshold, to, signature, - multisigParams + multisigDestinationParams ); unSubscription = (await extrinsic.send((result: SubmittableResult) => { @@ -757,7 +762,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { ); const checkMultisigAccountMigrationStatus = useCallback( - async (accountAddress: string): Promise => { + async (accountAddress: string): Promise => { if (!apiPromise) { setMultisigMigrationStatus(undefined); return Promise.resolve(undefined); @@ -769,7 +774,7 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { return Promise.resolve(undefined); } const resultString = result.unwrap().toHuman(); - const resultObj = resultString as unknown as DarwiniaAccountMigrationMultisig; + const resultObj = resultString as unknown as DarwiniaSourceAccountMigrationMultisig; resultObj.threshold = Number(resultObj.threshold); setMultisigMigrationStatus(resultObj); return Promise.resolve(resultObj); @@ -831,7 +836,6 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { const isMultisigAccountDeployed = useCallback( async (accountAddress: string): Promise => { const result = await provider?.getCode(accountAddress); - console.log("result====", result); return result !== "0x"; }, [provider] @@ -877,6 +881,8 @@ export const WalletProvider = ({ children }: PropsWithChildren) => { getAccountPrettyName, isMultisigAccountMigratedJustNow, isMultisigAccountDeployed, + isCheckingMultisigCompleted, + setIsCheckingMultisigCompleted, }} > {children} diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index 4ef37f3..bffae87 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -102,7 +102,7 @@ export interface WalletCtx { initializer: string, otherAccounts: string[], threshold: string, - multisigParams: MultisigParams, + multisigDestinationParams: MultisigDestinationParams | null, callback: (isSuccessful: boolean) => void ) => void; onApproveMultisigMigration: ( @@ -123,16 +123,18 @@ export interface WalletCtx { name?: string ) => Promise; getAccountBalance: (account: string) => Promise; - checkMultisigAccountMigrationStatus: (account: string) => Promise; + checkMultisigAccountMigrationStatus: (account: string) => Promise; apiPromise: ApiPromise | undefined; currentBlock: CurrentBlock | undefined; isLoadingMultisigBalance: boolean | undefined; setLoadingMultisigBalance: (isLoading: boolean) => void; multisigContract: Contract | undefined; - multisigMigrationStatus: DarwiniaAccountMigrationMultisig | undefined; + multisigMigrationStatus: DarwiniaSourceAccountMigrationMultisig | undefined; getAccountPrettyName: (address: string) => Promise; isMultisigAccountMigratedJustNow: boolean | undefined; isMultisigAccountDeployed: (accountAddress: string) => Promise; + isCheckingMultisigCompleted: boolean | undefined; + setIsCheckingMultisigCompleted: (isLoading: boolean) => void; } export interface SpVersionRuntimeVersion extends Struct { @@ -156,7 +158,7 @@ export interface CurrentBlock { timestamp: number; } -export interface DarwiniaAccountMigrationMultisig { +export interface DarwiniaSourceAccountMigrationMultisig { threshold: number; migrateTo: string; members: [string, boolean][]; @@ -164,7 +166,7 @@ export interface DarwiniaAccountMigrationMultisig { export type DestinationType = "General Account" | "Multisig Account"; -export interface MultisigParams { +export interface MultisigDestinationParams { address: string; members: string[]; threshold: number; diff --git a/packages/app/src/Root.tsx b/packages/app/src/Root.tsx index 3c4e5c5..009cdd2 100644 --- a/packages/app/src/Root.tsx +++ b/packages/app/src/Root.tsx @@ -20,6 +20,7 @@ const Root = () => { isLoadingBalance, isLoadingMultisigBalance, isMultisig, + isCheckingMultisigCompleted, } = useWallet(); const { isLoadingLedger, isLoadingMigratedLedger } = useStorage(); const [loading, setLoading] = useState(false); @@ -29,7 +30,13 @@ const Root = () => { useEffect(() => { if (isMultisig) { - setLoading(isRequestingWalletConnection || isLoadingTransaction || isLoadingBalance || isLoadingMultisigBalance); + setLoading( + isRequestingWalletConnection || + isLoadingTransaction || + isLoadingBalance || + isLoadingMultisigBalance || + isCheckingMultisigCompleted + ); } else { setLoading( isRequestingWalletConnection || @@ -47,6 +54,7 @@ const Root = () => { isLoadingMigratedLedger, isLoadingBalance, isLoadingMultisigBalance, + isCheckingMultisigCompleted, ]); const redirect = useCallback(() => { diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index c7b7b41..4e53e05 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -12,7 +12,7 @@ import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; import { isValidNumber, isEthereumAddress, getPublicKey, convertToSS58, setStore, getStore } from "@darwinia/app-utils"; import { BigNumber as EthersBigNumber } from "ethers"; -import { DestinationInfo, DestinationType, MultisigParams } from "@darwinia/app-types"; +import { DestinationInfo, DestinationType, MultisigDestinationParams } from "@darwinia/app-types"; interface MultisigMemberAddress { address: string; @@ -20,11 +20,11 @@ interface MultisigMemberAddress { } interface Props { - isIsWaitingToDeploy: boolean; + isWaitingToDeploy: boolean; isSuccessfullyMigrated: boolean; } -const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Props) => { +const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Props) => { const { t } = useAppTranslation(); const { injectedAccounts, @@ -34,7 +34,6 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr connectEthereumWallet, selectedEthereumAccount, isCorrectEthereumChain, - multisigMigrationStatus, setTransactionStatus, } = useWallet(); const location = useLocation(); @@ -46,6 +45,9 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); const name = params.get("name"); const threshold = params.get("threshold"); + const destinationMembers = (params.get("destinationMembers") ?? "").split(",").filter((item) => item.trim() !== ""); + const destinationType: DestinationType = destinationMembers.length > 0 ? "Multisig Account" : "General Account"; + const destinationThreshold = params.get("destinationThreshold"); const [isMemberSectionVisible, setMemberSectionVisible] = useState(true); const [isMigrateModalVisible, setMigrationModalVisible] = useState(false); const [destinationAddress, setDestinationAddress] = useState(""); @@ -61,10 +63,12 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr ]); //stores sorted members account address const allMembersRef = useRef([]); - const [newAccountThreshold, setNewAccountThreshold] = useState(""); + const [newAccountThreshold, setNewAccountThreshold] = useState(destinationThreshold ?? ""); const [newMultisigAccountAddress, setNewMultisigAccountAddress] = useState(""); const [isIsGeneratingMultisigAccount, setIsGeneratingMultisigAccount] = useState(false); + const canMigrate = !isWaitingToDeploy && !isSuccessfullyMigrated; + const destinationTabs = [ { id: 1, @@ -188,16 +192,20 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr } try { setProcessingMigration(true); - const destination = activeDestinationTab === 1 ? destinationAddress : newMultisigAccountAddress; - const type: DestinationType = activeDestinationTab === 1 ? "General Account" : "Multisig Account"; + const isMigratingToGeneralAccount = activeDestinationTab === 1; + const destination = isMigratingToGeneralAccount ? destinationAddress : newMultisigAccountAddress; + const type: DestinationType = isMigratingToGeneralAccount ? "General Account" : "Multisig Account"; const urlParams = new URLSearchParams(location.search); urlParams.set("destination", destination); - urlParams.set("destinationMembers", allMembersRef.current.join(",")); - if (activeDestinationTab === 2) { + + if (!isMigratingToGeneralAccount) { urlParams.set("destinationThreshold", newAccountThreshold); + urlParams.set("destinationMembers", allMembersRef.current.join(",")); + } else { + urlParams.delete("destinationThreshold"); + urlParams.delete("destinationMembers"); } - urlParams.set("destinationType", type); const oldData = getStore("destinationInfo") ?? {}; // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -214,19 +222,21 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); const otherAccounts = members.filter((account) => account !== initializer); - const multisigParams: MultisigParams = { + const multisigDestinationParams: MultisigDestinationParams = { address: newMultisigAccountAddress, threshold: Number(newAccountThreshold ?? 0), members: allMembersRef.current, }; + const params = type === "General Account" ? null : multisigDestinationParams; + onInitMultisigMigration( destination, initializer, initializer, otherAccounts, threshold, - multisigParams, + params, (isSuccessful) => { setProcessingMigration(false); if (isSuccessful) { @@ -254,14 +264,18 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr try { setTransactionStatus(true); const publicKey = getPublicKey(address); - const ethereumMemberAddresses = memberAddresses.map((item) => item.address); + const ethereumMemberAddresses = + destinationMembers.length > 0 ? destinationMembers : memberAddresses.map((item) => item.address); - if (selectedEthereumAccount) { + if (selectedEthereumAccount && destinationMembers.length === 0) { ethereumMemberAddresses.unshift(selectedEthereumAccount); } const sortedMembers = ethereumMemberAddresses.sort(); const thresholdNumber = EthersBigNumber.from(newAccountThreshold); - const deploymentResult = await multisigContract?.deploy(publicKey, sortedMembers, thresholdNumber); + console.log("sortedMembers", sortedMembers); + const response = await multisigContract?.deploy(publicKey, sortedMembers, thresholdNumber); + const transactionReceipt = response.wait(1); + console.log("deploymentResult", transactionReceipt); setTransactionStatus(false); } catch (e) { setTransactionStatus(false); @@ -308,7 +322,7 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr return (
{/*one more step to deploy*/} - {isIsWaitingToDeploy && ( + {isWaitingToDeploy && (
{t(localeKeys.oneMoreStep)}
@@ -354,7 +368,7 @@ const MultisigAccountInfo = ({ isIsWaitingToDeploy, isSuccessfullyMigrated }: Pr image
- {!multisigMigrationStatus && } + {canMigrate && }
diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index a75a480..95a2839 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -279,9 +279,9 @@ const MultisigMigrationProcess = () => { setCheckingAccountExistence(true); const thresholdNumber = Number(threshold); const account = await checkDarwiniaOneMultisigAccount(selectedAddress, signatories, thresholdNumber, name); - setCheckingAccountExistence(false); if (typeof account === "undefined") { + setCheckingAccountExistence(false); notification.success({ message:
{t(localeKeys.multisigCreationFailed)}
, duration: 15000, @@ -290,13 +290,16 @@ const MultisigMigrationProcess = () => { } const multisigAccounts: MultisigAccount[] = getStore("multisigAccounts") ?? []; //remove the account is it was already available in the local storage - const filteredAccounts = multisigAccounts.filter((account) => account.address !== account.address); + const filteredAccounts = multisigAccounts.filter( + (multisigAccount) => multisigAccount.address !== account.address + ); filteredAccounts.push(account); setStore("multisigAccounts", filteredAccounts); const data = await prepareMultisigAccountData([account]); setMultisigAccountsList((old) => { return [...old, ...data]; }); + setCheckingAccountExistence(false); //hide modal onCloseAddAccountModal(); } catch (e) { @@ -347,7 +350,7 @@ const MultisigMigrationProcess = () => { return (
-
+
{t(localeKeys.multisig)}
@@ -360,7 +363,7 @@ const MultisigMigrationProcess = () => {
{multisigAccountsList.length > 0 ? ( -
+
) : (
diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx index c8732ca..31edbf9 100644 --- a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -12,11 +12,17 @@ import helpIcon from "../../assets/images/help.svg"; import infoIcon from "../../assets/images/info.svg"; import { useWallet } from "@darwinia/app-providers"; import { convertToSS58, getStore, prettifyNumber, prettifyTooltipNumber } from "@darwinia/app-utils"; -import { DarwiniaAccountMigrationMultisig, Destination, DestinationInfo, DestinationType } from "@darwinia/app-types"; +import { + DarwiniaSourceAccountMigrationMultisig, + Destination, + DestinationInfo, + DestinationType, +} from "@darwinia/app-types"; interface Props { - migrationStatus: DarwiniaAccountMigrationMultisig | undefined; + migrationStatus: DarwiniaSourceAccountMigrationMultisig | undefined; isWaitingToDeploy: boolean; + isSuccessfullyMigrated: boolean; } interface MemberStatus { @@ -25,7 +31,7 @@ interface MemberStatus { hasApproved: boolean; } -const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: Props) => { +const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy, isSuccessfullyMigrated }: Props) => { const { t } = useAppTranslation(); const [memberAccounts, setMemberAccounts] = useState([]); const location = useLocation(); @@ -45,11 +51,11 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P const threshold = params.get("threshold"); const destinationAddress = params.get("destination"); const destinationMembers = (params.get("destinationMembers") ?? "").split(",").filter((item) => item.trim() !== ""); - const type = params.get("destinationType") as DestinationType; - const destinationThreshold = Number(params.get("destinationThreshold")); - const [haveAllMembersApproved, setHaveAllMembersApproved] = useState(false); + const destinationType: DestinationType = destinationMembers.length > 0 ? "Multisig Account" : "General Account"; + const destinationThreshold = Number(params.get("destinationThreshold") ?? 0); const [destination, setDestination] = useState(); const [recentlyApprovedAddresses, setRecentlyApprovedAddresses] = useState([]); + const showWaitingDeploy = !isWaitingToDeploy && !isSuccessfullyMigrated; useEffect(() => { if (!apiPromise || !migrationStatus || !location) { @@ -58,7 +64,6 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P const prepareMembers = async () => { setMemberAccounts([]); - let approvedCounter = 0; const tempMembers: MemberStatus[] = []; for (let i = 0; i < members.length; i++) { const address = members[i]; @@ -66,9 +71,6 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P if (account) { const hasApproved = account[1] || recentlyApprovedAddresses.includes(account[0]); const name = (await getAccountPrettyName(address)) ?? ""; - if (hasApproved) { - approvedCounter = approvedCounter + 1; - } const status: MemberStatus = { address, hasApproved, @@ -78,12 +80,6 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P } } setMemberAccounts([...tempMembers]); - - if (approvedCounter < Number(threshold)) { - setHaveAllMembersApproved(false); - } else { - setHaveAllMembersApproved(true); - } }; prepareMembers().catch((e) => { @@ -98,7 +94,7 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P if (destinationMembers.length > 0) { // this is a shared link setDestination({ - type, + type: destinationType, threshold: destinationThreshold, address: destinationAddress ?? "", members: destinationMembers, @@ -109,11 +105,13 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P //set the URL params accordingly for later use urlParams.set("destination", value.address); - urlParams.set("destinationMembers", value.members.join(",")); if (value.type === "Multisig Account") { urlParams.set("destinationThreshold", `${value.threshold}`); + urlParams.set("destinationMembers", value.members.join(",")); + } else { + urlParams.delete("destinationThreshold"); + urlParams.delete("destinationMembers"); } - urlParams.set("destinationType", value.type); window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); setDestination(value); @@ -186,7 +184,9 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy }: P key={`${item.address}-${index}`} className={`flex justify-between items-center py-[12px] border-b divider`} > -
{item.name}
+
+ {isMyAccount ? item.name : t(localeKeys.member).toUpperCase()} +
{t(localeKeys.destination)}
{destination?.address}
- {!haveAllMembersApproved && ( + {showWaitingDeploy && (
icon diff --git a/packages/app/src/pages/Migration.tsx b/packages/app/src/pages/Migration.tsx index 871235f..9a4f51e 100644 --- a/packages/app/src/pages/Migration.tsx +++ b/packages/app/src/pages/Migration.tsx @@ -18,7 +18,7 @@ export interface AccountMigration { transactionHash: string; } -interface MigrationResult { +export interface MigrationResult { accountMigration?: AccountMigration; } diff --git a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx index c75c131..3acdc1e 100644 --- a/packages/app/src/pages/MultisigAccountMigrationSummary.tsx +++ b/packages/app/src/pages/MultisigAccountMigrationSummary.tsx @@ -7,9 +7,31 @@ import MultisigMigrationProgressTabs from "../components/MultisigMigrationProgre import MigrationSummary from "../components/MigrationSummary"; import { useWallet } from "@darwinia/app-providers"; import { convertToSS58 } from "@darwinia/app-utils"; -import { DarwiniaAccountMigrationMultisig } from "@darwinia/app-types"; +import { DarwiniaSourceAccountMigrationMultisig } from "@darwinia/app-types"; import { useQuery } from "@apollo/client"; -import { FIND_MIGRATION_BY_SOURCE_ADDRESS } from "@darwinia/app-config"; +import { FIND_MIGRATION_BY_SOURCE_ADDRESS, FIND_MULTISIG_MIGRATION_BY_SOURCE_ADDRESS } from "@darwinia/app-config"; +import { MigrationResult } from "./Migration"; + +interface MigrationQuery { + accountAddress: string; +} + +interface MultisigSourceParams { + threshold: number; + to: string; + members: [string, boolean][]; +} + +export interface MultisigAccountMigration { + id: string; + blockNumber: number; + blockTime: string; + params: MultisigSourceParams; +} + +interface MultisigSourceMigrationResult { + multisigAccountMigration?: MultisigAccountMigration; +} const MultisigAccountMigrationSummary = () => { const { t } = useAppTranslation(); @@ -19,42 +41,121 @@ const MultisigAccountMigrationSummary = () => { apiPromise, isMultisigAccountMigratedJustNow, isMultisigAccountDeployed, + setIsCheckingMultisigCompleted, + isCheckingMultisigCompleted, } = useWallet(); const location = useLocation(); const params = new URLSearchParams(location.search); - const address = convertToSS58(params.get("address") ?? "", selectedNetwork?.prefix ?? 18); + const address = convertToSS58(params.get("address") ?? "", selectedNetwork?.prefix ?? 18).trim(); const [isSuccessfullyMigrated, setIsSuccessfullyMigrated] = useState(false); - const [isIsWaitingToDeploy, setIsWaitingToDeploy] = useState(false); - const [multisigMigrationStatus, setMultisigMigrationStatus] = useState(); + const [isWaitingToDeploy, setIsWaitingToDeploy] = useState(false); + const [sourceMultisigMigrationStatus, setSourceMultisigMigrationStatus] = + useState(); - /*const { - loading: isLoading, + const { + loading: isCheckingSubqueryMultisigMigrationCompletion, + data: multisigMigrationResult, + error: multisigError, + } = useQuery(FIND_MULTISIG_MIGRATION_BY_SOURCE_ADDRESS, { + variables: { + accountAddress: address, + }, + }); + + const { + loading: isCheckingSubqueryMigration, data: migrationResult, error, - refetch, } = useQuery(FIND_MIGRATION_BY_SOURCE_ADDRESS, { variables: { - accountAddress: selectedAccount?.formattedAddress ?? "", + accountAddress: address, }, - });*/ + }); + + // this will hide both the tabs section and the account summary when the component is mounted + useEffect(() => { + setIsCheckingMultisigCompleted(true); + }, []); useEffect(() => { const checkStatus = async () => { + setIsCheckingMultisigCompleted(true); const result = await checkMultisigAccountMigrationStatus(address); + if (!result) { //the account either doesn't exist or was already migrated - //check subquery to see if it was migrated + //check subquery to see if migration is completed + if (multisigMigrationResult && multisigMigrationResult.multisigAccountMigration) { + const destinationAddress = multisigMigrationResult.multisigAccountMigration.params.to; + //this account has completed migration, check if it was deployed or not + const isDeployed = await isMultisigAccountDeployed(destinationAddress); + if (isDeployed) { + setIsWaitingToDeploy(false); + setIsSuccessfullyMigrated(true); + } else { + setIsWaitingToDeploy(true); + setIsSuccessfullyMigrated(false); + } + + const { + params: { to, members, threshold }, + } = multisigMigrationResult.multisigAccountMigration; + setSourceMultisigMigrationStatus({ + threshold: threshold, + members: members, + migrateTo: to, + }); + } else if (migrationResult && migrationResult.accountMigration) { + /*this multisig account was migrated to a normal account */ + const destinationAddress = migrationResult.accountMigration.destination; + //this account has completed migration, check if it was deployed or not + const isDeployed = await isMultisigAccountDeployed(destinationAddress); + if (isDeployed) { + setIsWaitingToDeploy(false); + setIsSuccessfullyMigrated(true); + } else { + setIsWaitingToDeploy(true); + setIsSuccessfullyMigrated(false); + } + + const { destination } = migrationResult.accountMigration; + setSourceMultisigMigrationStatus({ + threshold: 0, + members: [], + migrateTo: destination, + }); + } else { + // this account doesn't exist or it hasn't initialized migration yet + console.log(`The account ${address} never existed on this chain or hasn't initiated migration yet`); + setSourceMultisigMigrationStatus(undefined); + } + } else { + setSourceMultisigMigrationStatus(result); } - setMultisigMigrationStatus(result); + setIsCheckingMultisigCompleted(false); }; - if (apiPromise || isMultisigAccountMigratedJustNow) { + + if ( + apiPromise || + isMultisigAccountMigratedJustNow || + !isCheckingSubqueryMultisigMigrationCompletion || + !isCheckingSubqueryMigration + ) { checkStatus().catch((e) => { + setIsCheckingMultisigCompleted(false); console.log(e); //ignore }); } - }, [apiPromise, isMultisigAccountMigratedJustNow]); + }, [ + apiPromise, + isMultisigAccountMigratedJustNow, + multisigMigrationResult, + migrationResult, + isCheckingSubqueryMultisigMigrationCompletion, + isCheckingSubqueryMigration, + ]); const footerLinks = [ { @@ -70,17 +171,22 @@ const MultisigAccountMigrationSummary = () => { url: "https://medium.com/darwinianetwork/darwinia-2-0-blockchain-data-migration-c1186338c743", }, ]; - + console.log("sourceMultisigMigrationStatus=====", sourceMultisigMigrationStatus); return (
- - {multisigMigrationStatus ? ( - - ) : ( - + + {!isCheckingMultisigCompleted && ( + <> + {sourceMultisigMigrationStatus ? ( + + ) : ( + + )} + )}
{footerLinks.map((item, index) => { From da6ed9d8e9552a2549531917e72024bfc58f66d3 Mon Sep 17 00:00:00 2001 From: "Ismail Mbarack (Nas)" Date: Mon, 3 Apr 2023 13:16:40 +0800 Subject: [PATCH 27/36] added required validations for pages state --- packages/app-types/src/wallet.ts | 2 +- .../app/src/assets/images/warning-yellow.svg | 6 + .../components/MultisigAccountInfo/index.tsx | 147 +++++------ .../MultisigMigrationProcess/index.tsx | 2 +- .../MultisigMigrationProgressTabs/index.tsx | 113 ++++----- .../pages/MultisigAccountMigrationSummary.tsx | 234 ++++++++++++++---- 6 files changed, 312 insertions(+), 192 deletions(-) create mode 100644 packages/app/src/assets/images/warning-yellow.svg diff --git a/packages/app-types/src/wallet.ts b/packages/app-types/src/wallet.ts index bffae87..f8c477a 100644 --- a/packages/app-types/src/wallet.ts +++ b/packages/app-types/src/wallet.ts @@ -160,7 +160,7 @@ export interface CurrentBlock { export interface DarwiniaSourceAccountMigrationMultisig { threshold: number; - migrateTo: string; + to: string; members: [string, boolean][]; } diff --git a/packages/app/src/assets/images/warning-yellow.svg b/packages/app/src/assets/images/warning-yellow.svg new file mode 100644 index 0000000..82f49e6 --- /dev/null +++ b/packages/app/src/assets/images/warning-yellow.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/app/src/components/MultisigAccountInfo/index.tsx b/packages/app/src/components/MultisigAccountInfo/index.tsx index 4e53e05..e219cb0 100644 --- a/packages/app/src/components/MultisigAccountInfo/index.tsx +++ b/packages/app/src/components/MultisigAccountInfo/index.tsx @@ -1,7 +1,6 @@ import { localeKeys, useAppTranslation } from "@darwinia/app-locale"; import { useWallet } from "@darwinia/app-providers"; -import { useLocation } from "react-router-dom"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import Identicon from "@polkadot/react-identicon"; import copyIcon from "../../assets/images/copy.svg"; import { Button, CheckboxGroup, Input, ModalEnhanced, notification, SlideDownUp, Tooltip } from "@darwinia/ui"; @@ -12,7 +11,8 @@ import helpIcon from "../../assets/images/help.svg"; import trashIcon from "../../assets/images/trash-bin.svg"; import { isValidNumber, isEthereumAddress, getPublicKey, convertToSS58, setStore, getStore } from "@darwinia/app-utils"; import { BigNumber as EthersBigNumber } from "ethers"; -import { DestinationInfo, DestinationType, MultisigDestinationParams } from "@darwinia/app-types"; +import { DestinationType, MultisigDestinationParams } from "@darwinia/app-types"; +import { AccountBasicInfo } from "../../pages/MultisigAccountMigrationSummary"; interface MultisigMemberAddress { address: string; @@ -22,9 +22,16 @@ interface MultisigMemberAddress { interface Props { isWaitingToDeploy: boolean; isSuccessfullyMigrated: boolean; + isMigrationInitialized: boolean; + accountBasicInfo?: AccountBasicInfo; } -const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Props) => { +const MultisigAccountInfo = ({ + isWaitingToDeploy, + isSuccessfullyMigrated, + isMigrationInitialized, + accountBasicInfo, +}: Props) => { const { t } = useAppTranslation(); const { injectedAccounts, @@ -36,18 +43,7 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop isCorrectEthereumChain, setTransactionStatus, } = useWallet(); - const location = useLocation(); - const params = new URLSearchParams(location.search); - const address = convertToSS58(params.get("address") ?? "", selectedNetwork?.prefix ?? 18); - const initializer = convertToSS58(params.get("initializer") ?? "", selectedNetwork?.prefix ?? 18); - const members = (params.get("who") ?? "") - .split(",") - .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); - const name = params.get("name"); - const threshold = params.get("threshold"); - const destinationMembers = (params.get("destinationMembers") ?? "").split(",").filter((item) => item.trim() !== ""); - const destinationType: DestinationType = destinationMembers.length > 0 ? "Multisig Account" : "General Account"; - const destinationThreshold = params.get("destinationThreshold"); + const [isMemberSectionVisible, setMemberSectionVisible] = useState(true); const [isMigrateModalVisible, setMigrationModalVisible] = useState(false); const [destinationAddress, setDestinationAddress] = useState(""); @@ -63,12 +59,12 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop ]); //stores sorted members account address const allMembersRef = useRef([]); - const [newAccountThreshold, setNewAccountThreshold] = useState(destinationThreshold ?? ""); + const [newAccountThreshold, setNewAccountThreshold] = useState( + accountBasicInfo?.destinationThreshold ? `${accountBasicInfo?.destinationThreshold}` : "" + ); const [newMultisigAccountAddress, setNewMultisigAccountAddress] = useState(""); const [isIsGeneratingMultisigAccount, setIsGeneratingMultisigAccount] = useState(false); - const canMigrate = !isWaitingToDeploy && !isSuccessfullyMigrated; - const destinationTabs = [ { id: 1, @@ -187,41 +183,24 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop }; const onConfirmAndMigrate = async () => { - if (!address || !initializer || !threshold) { + if ( + !accountBasicInfo || + !accountBasicInfo?.address || + !accountBasicInfo?.initializer || + !accountBasicInfo?.threshold + ) { return; } try { + const initializer = accountBasicInfo.initializer; + const sourceMembers = accountBasicInfo?.members ?? []; + const sourceThreshold = accountBasicInfo.threshold.toString(); setProcessingMigration(true); const isMigratingToGeneralAccount = activeDestinationTab === 1; const destination = isMigratingToGeneralAccount ? destinationAddress : newMultisigAccountAddress; const type: DestinationType = isMigratingToGeneralAccount ? "General Account" : "Multisig Account"; - const urlParams = new URLSearchParams(location.search); - urlParams.set("destination", destination); - - if (!isMigratingToGeneralAccount) { - urlParams.set("destinationThreshold", newAccountThreshold); - urlParams.set("destinationMembers", allMembersRef.current.join(",")); - } else { - urlParams.delete("destinationThreshold"); - urlParams.delete("destinationMembers"); - } - - const oldData = getStore("destinationInfo") ?? {}; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - oldData[address] = { - threshold: Number(newAccountThreshold), - address: destination, - type: type, - members: allMembersRef.current, - }; - - setStore("destinationInfo", oldData); - - window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); - - const otherAccounts = members.filter((account) => account !== initializer); + const otherAccounts = sourceMembers.filter((account) => account !== initializer); const multisigDestinationParams: MultisigDestinationParams = { address: newMultisigAccountAddress, threshold: Number(newAccountThreshold ?? 0), @@ -235,7 +214,7 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop initializer, initializer, otherAccounts, - threshold, + sourceThreshold, params, (isSuccessful) => { setProcessingMigration(false); @@ -260,21 +239,31 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop setMemberAddresses(addresses); }; + /*If the account is migrated to a general account, it won't need to be deployed*/ const onDeploy = async () => { try { setTransactionStatus(true); - const publicKey = getPublicKey(address); + const publicKey = getPublicKey(accountBasicInfo?.address ?? ""); + const destinationMembersProps = accountBasicInfo?.destinationMembers ?? []; const ethereumMemberAddresses = - destinationMembers.length > 0 ? destinationMembers : memberAddresses.map((item) => item.address); + destinationMembersProps.length > 0 + ? destinationMembersProps + : memberAddresses.map((item) => item.address).filter((item) => item.trim() !== ""); - if (selectedEthereumAccount && destinationMembers.length === 0) { + if (selectedEthereumAccount && destinationMembersProps.length === 0) { + /* the destinationMembers don't come from the shared URL, add the selectedEthereumAccount into the list + * since the user added this account when connecting to MetaMask */ ethereumMemberAddresses.unshift(selectedEthereumAccount); } - const sortedMembers = ethereumMemberAddresses.sort(); - const thresholdNumber = EthersBigNumber.from(newAccountThreshold); + const sortedMembers = [...ethereumMemberAddresses].sort(); + const destinationThreshold = + typeof accountBasicInfo?.threshold !== "undefined" ? accountBasicInfo?.threshold : newAccountThreshold; console.log("sortedMembers", sortedMembers); + console.log("destinationThreshold", destinationThreshold); + console.log("publicKey", publicKey); + const thresholdNumber = EthersBigNumber.from(destinationThreshold); const response = await multisigContract?.deploy(publicKey, sortedMembers, thresholdNumber); - const transactionReceipt = response.wait(1); + const transactionReceipt = await response.wait(1); console.log("deploymentResult", transactionReceipt); setTransactionStatus(false); } catch (e) { @@ -292,10 +281,10 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop if ( selectedEthereumAccount && isCorrectEthereumChain && - address && + accountBasicInfo?.address && members.length >= Number(newAccountThreshold ?? 1) - 1 ) { - generateMultisigAccount(members, selectedEthereumAccount, address); + generateMultisigAccount(members, selectedEthereumAccount, accountBasicInfo?.address); } }; @@ -352,32 +341,32 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop
-
{name}
+
{accountBasicInfo?.name}
{t(localeKeys.multisig)}
-
{address}
+
{accountBasicInfo?.address}
image
- {canMigrate && } + {!isMigrationInitialized && }
{t(localeKeys.threshold)}
-
{threshold}
+
{accountBasicInfo?.threshold}
{t(localeKeys.members)}
-
{members.length}
+
{accountBasicInfo?.members.length}
- {members.map((member, index) => { + {accountBasicInfo?.members.map((member, index) => { const isMyAccount = !!injectedAccounts?.find( (account) => convertToSS58(account.address, selectedNetwork?.prefix ?? 18).toLowerCase() === @@ -426,8 +415,13 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop
{t(localeKeys.fromSubstrateMultisig)}
- -
{address}
+ +
{accountBasicInfo?.address}
@@ -500,13 +494,15 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop
- {/*TODO: change this logic accordingly*/} -
- {isCorrectEthereumChain ? selectedEthereumAccount : "Connect Metamask"} -
+ {!isCorrectEthereumChain ? ( + + ) : ( +
+ {selectedEthereumAccount} +
+ )}
{memberAddresses.map((item, index) => { return ( @@ -596,8 +592,13 @@ const MultisigAccountInfo = ({ isWaitingToDeploy, isSuccessfullyMigrated }: Prop {t(localeKeys.fromTheSubstrateAccount, { name: selectedNetwork?.name })}
- -
{address}
+ +
{accountBasicInfo?.address}
{/*Destination*/} diff --git a/packages/app/src/components/MultisigMigrationProcess/index.tsx b/packages/app/src/components/MultisigMigrationProcess/index.tsx index 95a2839..490366a 100644 --- a/packages/app/src/components/MultisigMigrationProcess/index.tsx +++ b/packages/app/src/components/MultisigMigrationProcess/index.tsx @@ -176,7 +176,7 @@ const MultisigMigrationProcess = () => { const formattedAddress = convertToSS58(accountItem.address, selectedNetwork?.prefix ?? 18); const asset = await getAccountBalance(formattedAddress); const item: MultisigAccountData = { - id: accountItem.address, + id: `${accountItem.address}-${i}`, address: accountItem.address, formattedAddress: formattedAddress, name: accountItem.meta.name, diff --git a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx index 31edbf9..ceedc8f 100644 --- a/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx +++ b/packages/app/src/components/MultisigMigrationProgressTabs/index.tsx @@ -10,17 +10,14 @@ import cktonIcon from "../../assets/images/ckton.svg"; import ktonIcon from "../../assets/images/kton.svg"; import helpIcon from "../../assets/images/help.svg"; import infoIcon from "../../assets/images/info.svg"; +import warning from "../../assets/images/warning-yellow.svg"; import { useWallet } from "@darwinia/app-providers"; import { convertToSS58, getStore, prettifyNumber, prettifyTooltipNumber } from "@darwinia/app-utils"; -import { - DarwiniaSourceAccountMigrationMultisig, - Destination, - DestinationInfo, - DestinationType, -} from "@darwinia/app-types"; +import { DarwiniaSourceAccountMigrationMultisig, MultisigDestinationParams } from "@darwinia/app-types"; interface Props { - migrationStatus: DarwiniaSourceAccountMigrationMultisig | undefined; + sourceMultisigMigrationStatus: DarwiniaSourceAccountMigrationMultisig | undefined; + multisigDestinationParams: MultisigDestinationParams | undefined; isWaitingToDeploy: boolean; isSuccessfullyMigrated: boolean; } @@ -31,9 +28,14 @@ interface MemberStatus { hasApproved: boolean; } -const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy, isSuccessfullyMigrated }: Props) => { +const MultisigMigrationProgressTabs = ({ + sourceMultisigMigrationStatus, + isWaitingToDeploy, + isSuccessfullyMigrated, + multisigDestinationParams, +}: Props) => { const { t } = useAppTranslation(); - const [memberAccounts, setMemberAccounts] = useState([]); + const [sourceMemberAccounts, setSourceMemberAccounts] = useState([]); const location = useLocation(); const { selectedNetwork, @@ -48,26 +50,23 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy, isS const members = (params.get("who") ?? "") .split(",") .map((address) => convertToSS58(address, selectedNetwork?.prefix ?? 18)); - const threshold = params.get("threshold"); - const destinationAddress = params.get("destination"); - const destinationMembers = (params.get("destinationMembers") ?? "").split(",").filter((item) => item.trim() !== ""); - const destinationType: DestinationType = destinationMembers.length > 0 ? "Multisig Account" : "General Account"; - const destinationThreshold = Number(params.get("destinationThreshold") ?? 0); - const [destination, setDestination] = useState(); const [recentlyApprovedAddresses, setRecentlyApprovedAddresses] = useState([]); - const showWaitingDeploy = !isWaitingToDeploy && !isSuccessfullyMigrated; + const [isThresholdReached, setIsThresholdReached] = useState(false); + const showWaitingDeploy = + !isWaitingToDeploy && !isSuccessfullyMigrated && (multisigDestinationParams?.members ?? []).length > 0; useEffect(() => { - if (!apiPromise || !migrationStatus || !location) { + if (!apiPromise || !sourceMultisigMigrationStatus || !location) { return; } const prepareMembers = async () => { - setMemberAccounts([]); + setSourceMemberAccounts([]); + let thresholdCounter = 0; const tempMembers: MemberStatus[] = []; for (let i = 0; i < members.length; i++) { const address = members[i]; - const account = migrationStatus.members.find((member) => member[0] === address); + const account = sourceMultisigMigrationStatus.members.find((member) => member[0] === address); if (account) { const hasApproved = account[1] || recentlyApprovedAddresses.includes(account[0]); const name = (await getAccountPrettyName(address)) ?? ""; @@ -76,48 +75,20 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy, isS hasApproved, name, }; + if (hasApproved) { + thresholdCounter = thresholdCounter + 1; + } tempMembers.push(status); } } - setMemberAccounts([...tempMembers]); + setIsThresholdReached(thresholdCounter >= sourceMultisigMigrationStatus.threshold); + setSourceMemberAccounts([...tempMembers]); }; prepareMembers().catch((e) => { console.log(e); }); - }, [location, apiPromise, migrationStatus, recentlyApprovedAddresses]); - - useEffect(() => { - if (migrationStatus && location) { - const destinationInfo = getStore("destinationInfo"); - - if (destinationMembers.length > 0) { - // this is a shared link - setDestination({ - type: destinationType, - threshold: destinationThreshold, - address: destinationAddress ?? "", - members: destinationMembers, - }); - } else if (destinationInfo && destinationInfo[address]) { - const value = destinationInfo[address]; - const urlParams = new URLSearchParams(location.search); - - //set the URL params accordingly for later use - urlParams.set("destination", value.address); - if (value.type === "Multisig Account") { - urlParams.set("destinationThreshold", `${value.threshold}`); - urlParams.set("destinationMembers", value.members.join(",")); - } else { - urlParams.delete("destinationThreshold"); - urlParams.delete("destinationMembers"); - } - - window.history.pushState({}, "", `/#${location.pathname}?${urlParams.toString()}`); - setDestination(value); - } - } - }, [migrationStatus, location]); + }, [location, apiPromise, sourceMultisigMigrationStatus, recentlyApprovedAddresses]); const ringTokenIcon = selectedNetwork?.name === "Crab" ? crabIcon : ringIcon; const ktonTokenIcon = selectedNetwork?.name === "Crab" ? cktonIcon : ktonIcon; @@ -131,12 +102,12 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy, isS }; const approveMigration = (signerAddress: string) => { - if (!destination?.address || typeof threshold === "undefined" || threshold === null) { + if (!multisigDestinationParams?.address) { return; } setTransactionStatus(true); - onApproveMultisigMigration(address, destination.address, signerAddress, (isSuccessful) => { + onApproveMultisigMigration(address, multisigDestinationParams.address, signerAddress, (isSuccessful) => { if (isSuccessful) { setRecentlyApprovedAddresses((old) => { return [...old, signerAddress]; @@ -163,14 +134,15 @@ const MultisigMigrationProgressTabs = ({ migrationStatus, isWaitingToDeploy, isS
{t(localeKeys.progress)}
- {memberAccounts?.map((item, index) => { + {sourceMemberAccounts?.map((item, index) => { let tag: JSX.Element | string = ""; const isMyAccount = !!injectedAccounts?.find( (account) => account.formattedAddress.toLowerCase() === item.address.toLowerCase() ); + if (item.hasApproved) { tag = t(localeKeys.approved); - } else if (isMyAccount) { + } else if (isMyAccount && !isThresholdReached) { tag = (