From 75a1a425ab23528db0b03bd5656e5ef1ac41ced8 Mon Sep 17 00:00:00 2001 From: Jan Kraus <16012+johny@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:16:17 +0100 Subject: [PATCH] update flow for EdgeEVM withdrawal (#68) * Update flow for EVM withdrawal * Improve withdrawal step processing * Add typecheck command * Simplify 1st step of withdrawal * Improve final withdrawal step * Fix missing target="_blank" attribute --- components/common/button.tsx | 2 +- .../ecosystem/widget/ecosystem-widget.tsx | 2 +- .../pages/ecosystem/widget/web3-context.tsx | 1 + components/pages/tools/forms/evm-withdraw.tsx | 438 +++++++++++++----- .../pages/tools/tools-evm-withdrawal.tsx | 8 + lib/crypto/deposit.ts | 3 +- package.json | 3 +- tailwind.config.js | 23 +- 8 files changed, 365 insertions(+), 115 deletions(-) diff --git a/components/common/button.tsx b/components/common/button.tsx index 89850232..3e8912ce 100644 --- a/components/common/button.tsx +++ b/components/common/button.tsx @@ -17,7 +17,7 @@ export const Button = ({ children, href, colorStyle, sizing, ...restProps }: But }, colorStyles: { primary: - 'bg-primary-500 text-white hover:bg-primary-600 hover:text-white active:bg-primary-700 active:text-white', + 'bg-primary-500 text-white hover:bg-primary-600 hover:text-white active:bg-primary-700 active:text-white disabled:bg-primary-700', grey: 'bg-grey-800 text-white hover:bg-grey-700 hover:text-white active:bg-grey-700 active:text-white', black: 'bg-grey-900 text-white hover:bg-grey-700 hover:text-white active:bg-grey-600 active:text-white', diff --git a/components/pages/ecosystem/widget/ecosystem-widget.tsx b/components/pages/ecosystem/widget/ecosystem-widget.tsx index bb4ff44a..4b3609c2 100644 --- a/components/pages/ecosystem/widget/ecosystem-widget.tsx +++ b/components/pages/ecosystem/widget/ecosystem-widget.tsx @@ -3,7 +3,7 @@ import { Web3ContextProvider } from './web3-context'; export const EcosystemWidget = () => { return ( -
+

EdgeWASM and EdgeEVM conversion

diff --git a/components/pages/ecosystem/widget/web3-context.tsx b/components/pages/ecosystem/widget/web3-context.tsx index 57b50e44..539c4ce1 100644 --- a/components/pages/ecosystem/widget/web3-context.tsx +++ b/components/pages/ecosystem/widget/web3-context.tsx @@ -92,6 +92,7 @@ export const Web3ContextProvider = ({ children }) => { console.log('Disconnecting from EVM wallet'); try { const web3 = new Web3(Web3.givenProvider); + console.log('Done!'); } catch (err) { console.error(err); } diff --git a/components/pages/tools/forms/evm-withdraw.tsx b/components/pages/tools/forms/evm-withdraw.tsx index 42dfc981..fc1bceaf 100644 --- a/components/pages/tools/forms/evm-withdraw.tsx +++ b/components/pages/tools/forms/evm-withdraw.tsx @@ -20,12 +20,16 @@ if (typeof window !== 'undefined') { } interface EvmWithdrawFormState { - text?: string; - error?: boolean; + evmText?: string; + substrateText?: string; + evmError?: boolean; + substrateError?: boolean; + evmTransactionSuccess?: boolean; + substrateTransactionSuccess?: boolean; showAddNetwork?: boolean; } -type formStep = 'initial' | 'transfer' | 'complete'; +type formStep = 'initial' | 'transfer' | 'withdraw' | 'complete'; export const EvmWithdraw = () => { const addressInputEl = useRef(null); @@ -33,13 +37,14 @@ export const EvmWithdraw = () => { const evmAddressInputEl = useRef(null); const [formStep, setFormStep] = useState('initial'); + const [formState, setFormState] = useState({ + evmText: null, + evmError: false, + }); - const [formState, setFormState] = useState({ - text: null, - error: false, - } as EvmWithdrawFormState); + const [evmAddress, setEvmAddress] = useState(''); - const handleTransferButton = async (event: React.MouseEvent) => { + const handleDiscoverButton = async (event: React.MouseEvent) => { event.preventDefault(); // Reset state @@ -49,14 +54,39 @@ export const EvmWithdraw = () => { // 1. Validate form const substrateAddress = addressInputEl.current.value; if (!substrateAddress) { - setFormState({ text: 'Invalid Substrate address', error: true }); + setFormState({ evmText: 'Invalid Substrate address', evmError: true }); + addressInputEl.current.focus(); + return; + } + + // 2. Continue with transfer + try { + const addressBytes = decodeAddress(substrateAddress); + const evmAddress = Buffer.from(addressBytes.subarray(0, 20)).toString('hex'); + + setFormStep('transfer'); + setEvmAddress(Web3.utils.toChecksumAddress(evmAddress)); + } catch (err) { + console.log(err); + setFormState({ evmText: 'Transaction error', evmError: true }); + } + }; + + // Will initiate the transfer from the EVM to the intermediary address (via Metamask) + const handleTransferButton = async (event: React.MouseEvent) => { + event.preventDefault(); + + // 1. Validate form + const substrateAddress = addressInputEl.current.value; + if (!substrateAddress) { + setFormState({ evmText: 'Invalid Substrate address', evmError: true }); addressInputEl.current.focus(); return; } const amount = amountInputEl.current.value; if (amount === '' || amount === '0' || isNaN(+amount)) { - setFormState({ text: 'Invalid EDG amount', error: true }); + setFormState({ evmText: 'Invalid EDG amount', evmError: true }); amountInputEl.current.focus(); return; } @@ -67,20 +97,21 @@ export const EvmWithdraw = () => { try { await (window as any).ethereum.request({ method: 'eth_requestAccounts' }); } catch (e) { - setFormState({ text: 'Metamask or EVM compatible Web3 wallet required', error: true }); + setFormState({ evmText: 'Metamask or EVM compatible Web3 wallet required', evmError: true }); return; } const account = currentProvider?.selectedAddress; if (!account) { - setFormState({ text: 'Metamask or EVM compatible Web3 wallet required', error: true }); + setFormState({ evmText: 'Metamask or EVM compatible Web3 wallet required', evmError: true }); return; } // Are we on the right network? if (+currentProvider?.chainId !== 2021 && +currentProvider?.chainId !== 2022) { setFormState({ - text: 'Please switch to Edgeware EdgeEVM network in your web3 wallet manually or click on the `Switch to EdgeEVM` button below.', - error: true, + evmText: + 'Please switch to Edgeware EdgeEVM network in your web3 wallet manually or click on the `Switch to EdgeEVM` button below.', + evmError: true, showAddNetwork: true, }); return; @@ -91,10 +122,8 @@ export const EvmWithdraw = () => { const addressBytes = decodeAddress(substrateAddress); const evmAddress = Buffer.from(addressBytes.subarray(0, 20)).toString('hex'); - setFormStep('transfer'); - evmAddressInputEl.current.value = Web3.utils.toChecksumAddress(evmAddress); - - setFormState({ text: 'Confirm the transaction in your wallet', error: false }); + setFormStep('withdraw'); + setFormState({ evmText: 'Confirm the transaction in your wallet', evmError: false }); await web3.eth .sendTransaction({ @@ -104,49 +133,92 @@ export const EvmWithdraw = () => { gas: '55000', }) .on('transactionHash', () => { - setFormState({ text: 'Transaction sent, waiting for confirmation...', error: false }); + setFormState({ + evmText: 'Transaction sent, waiting for confirmation...', + evmError: false, + }); }); setFormState({ - text: 'Transfer to withdraw address succeeded. Please continue to step 2.', - error: false, + evmText: 'Transfer to intermediary withdrawal address succeeded.', + evmError: false, + evmTransactionSuccess: true, }); } catch (err) { console.log(err); if (err.code === 4001) { - setFormState({ text: 'Transaction canceled', error: true }); + setFormState({ evmText: 'Transaction canceled', evmError: true }); } else { - setFormState({ text: 'Transaction error', error: true }); + setFormState({ evmText: 'Transaction error', evmError: true }); } } }; + const handleManualTransfer = async (event: React.MouseEvent) => { + event.preventDefault(); + + // Validate amount + const amount = amountInputEl.current.value; + if (amount === '' || amount === '0' || isNaN(+amount)) { + setFormState({ evmText: 'Invalid EDG amount', evmError: true }); + amountInputEl.current.focus(); + return; + } + + setFormStep('withdraw'); + setFormState({ + ...formState, + evmError: false, + evmText: 'Transfer performed manually!', + evmTransactionSuccess: true, + }); + }; + + // Will sign the withdrawal extrinsic from the Substrate account (via polkadot-js) const handleWithdrawButton = async (event: React.MouseEvent) => { event.preventDefault(); - setFormState({}); + if (!formState.evmTransactionSuccess) { + return; + } const web3 = new Web3((window as any).ethereum); const currentProvider = web3?.eth?.accounts?.currentProvider as any; try { await (window as any).ethereum.request({ method: 'eth_requestAccounts' }); } catch (e) { - setFormState({ text: 'Metamask or compatible Web3 wallet required', error: true }); + setFormState({ + ...formState, + substrateText: 'Metamask or compatible Web3 wallet required!', + substrateError: true, + }); return; } if (!currentProvider) { - setFormState({ text: 'Metamask or compatible Web3 wallet required', error: true }); + setFormState({ + ...formState, + substrateText: 'Metamask or compatible Web3 wallet required!', + substrateError: true, + }); return; } const sender = addressInputEl.current.value; if (!sender) { - setFormState({ text: 'Invalid Substrate address', error: true }); + setFormState({ + ...formState, + substrateText: 'Invalid Substrate address!', + substrateError: true, + }); return; } const amount = amountInputEl.current.value; if (amount === '' || amount === '0' || isNaN(+amount)) { - setFormState({ text: 'Invalid amount', error: true }); + setFormState({ + ...formState, + substrateText: 'Invalid amount!', + substrateError: true, + }); return; } @@ -156,14 +228,20 @@ export const EvmWithdraw = () => { if (+currentProvider?.chainId !== 2021 && +currentProvider?.chainId !== 2022) { setFormState({ - text: 'Please switch to Edgeware EdgeEVM network in your web3 wallet/signer manually or click on the `Switch to EdgeEVM` button below.', - error: true, + ...formState, + substrateText: + 'Please switch to Edgeware EdgeEVM network in your web3 wallet/signer manually or click on the `Switch to EdgeEVM` button below.', + substrateError: true, showAddNetwork: true, }); return; } - setFormState({ text: 'Connecting to polkadot-js...', error: false }); + setFormState({ + ...formState, + substrateText: 'Connecting to polkadot-js...', + substrateError: false, + }); const polkadotUrl = 'wss://edgeware.jelliedowl.net'; const registry = new TypeRegistry(); @@ -183,7 +261,11 @@ export const EvmWithdraw = () => { u8aToHex(keyring.decodeAddress(address)) ); if (allAccountsHex.indexOf(addressHex) === -1) { - setFormState({ text: 'Address not found in polkadot-js', error: true }); + setFormState({ + ...formState, + substrateText: 'Address not found in polkadot-js!', + substrateError: true, + }); return; } @@ -191,8 +273,9 @@ export const EvmWithdraw = () => { const withdrawingBalance = `${amount} EDG`; setFormState({ - text: `Withdrawing ${withdrawingBalance} of ${availableBalance} available, please confirm in polkadot-js`, - error: false, + ...formState, + substrateText: `Withdrawing ${withdrawingBalance} of ${availableBalance} available, please confirm in polkadot-js...`, + substrateError: false, }); const injector = await web3FromAddress(sender); @@ -200,27 +283,39 @@ export const EvmWithdraw = () => { .withdraw(evmAddress, (+amount * 10 ** +api.registry.chainDecimals).toString()) .signAndSend(sender, { signer: injector.signer }, (result) => { if (result.isError) { - setFormState({ text: 'Transaction error', error: true }); + setFormState({ + ...formState, + substrateText: 'Transaction error!', + substrateError: true, + }); } else if (result.dispatchError && withdrawingBalance > availableBalance) { setFormState({ - text: `Transaction error. Attempted to withdraw ${withdrawingBalance} with only ${availableBalance} available`, - error: true, + ...formState, + substrateText: `Transaction error. Attempted to withdraw ${withdrawingBalance} with only ${availableBalance} available.`, + substrateError: true, }); } else if (result.dispatchError) { - setFormState({ text: 'Transaction error', error: true }); + setFormState({ ...formState, substrateText: 'Transaction error!', substrateError: true }); } else if (result.isCompleted && withdrawingBalance === availableBalance) { - setFormState({ text: `Transaction success. Withdrew ${withdrawingBalance}!` }); + setFormState({ + ...formState, + substrateTransactionSuccess: true, + substrateText: `Transaction success. Withdrew ${withdrawingBalance}!`, + }); setFormStep('complete'); } else if (result.isCompleted) { setFormState({ - text: `Transaction success. Withdrew ${withdrawingBalance} of ${availableBalance} available in the withdraw address`, + ...formState, + substrateTransactionSuccess: true, + substrateText: `Transaction success. Withdrew ${withdrawingBalance} of ${availableBalance} available in the withdraw address.`, }); + setFormStep('complete'); } }) .catch((err) => { - setFormState({ text: err.message, error: true }); + setFormState({ evmText: err.message, evmError: true }); }); }; @@ -245,7 +340,7 @@ export const EvmWithdraw = () => { ], }); } catch (err) { - setFormState({ text: err.message, error: true, showAddNetwork: true }); + setFormState({ evmText: err.message, evmError: true, showAddNetwork: true }); } }; @@ -253,7 +348,9 @@ export const EvmWithdraw = () => { event.preventDefault(); }; - const handleReset = () => { + const handleReset = (e: React.MouseEvent) => { + e.preventDefault(); + if (addressInputEl.current) { addressInputEl.current.value = ''; } @@ -266,82 +363,203 @@ export const EvmWithdraw = () => { setFormStep('initial'); }; + const stepVisible = (step: number) => { + if (step === 1) { + return true; + } + + if (step === 2) { + return ['transfer', 'withdraw', 'complete'].includes(formStep); + } + + if (step === 3) { + return ['transfer', 'withdraw', 'complete'].includes(formStep); + } + + if (step === 4) { + const isActiveStep = ['withdraw', 'complete'].includes(formStep); + return isActiveStep && formState.evmTransactionSuccess; + } + + if (step === 5) { + const isActiveStep = ['complete'].includes(formStep); + return isActiveStep && formState.substrateTransactionSuccess; + } + }; + return ( <>

- - - {formStep === 'transfer' && ( - + {stepVisible(1) && ( +
+ +

+ Based on the Substrate address, the corresponding EVM address will be discovered. +

+ {formStep === 'initial' && ( +
+ +
+ )} +
)} -
- {formState.text} -
-
- -
-
- -
- {formState.showAddNetwork && (window as any).ethereum && ( -
- +
+
+ If you transferred the funds manually, + +
+ +
+ {formState.evmText} +
+
+ )} + + {stepVisible(4) && ( +
+

4. Finalize the withdrawal from your Substrate account

+

+ Now you can withdraw the funds from the EVM to your Substrate address. This can be + done by initiating the + + evm.withdraw(address,value) + + extrinsic from your Substrate account using the{' '} + + polkadot-js + + {' or '} + + edgeware.app + {' '} + extrinsics. +
+
+ For convenience, you can use the button below to sign the transaction. +

+ +
+ {formState.substrateText} +
+
+ )} + + {stepVisible(5) && ( +
+

5. Withdrawal complete

+

+ Your withdrawal is complete. You can now check your balance in your Substrate wallet. +

)} - {formStep === 'complete' && ( +
+ +
+ + {formState.showAddNetwork && (window as any).ethereum && (
-
)} diff --git a/components/pages/tools/tools-evm-withdrawal.tsx b/components/pages/tools/tools-evm-withdrawal.tsx index 1c8c67c2..077ea90a 100644 --- a/components/pages/tools/tools-evm-withdrawal.tsx +++ b/components/pages/tools/tools-evm-withdrawal.tsx @@ -1,3 +1,4 @@ +import Link from 'next/link'; import { EvmWithdraw } from './forms/evm-withdraw'; export const ToolsEVMWithdrawal = () => { @@ -16,6 +17,13 @@ export const ToolsEVMWithdrawal = () => { Note: This requires wallets/signers compatible with EdgeEVM (e.g. Metamask) as well as substrate/EdgeWASM (e.g. polkadot-js). If you are using this tool in an EVM-only mobile dapp browser, you will need to perform step 2 manually. +
+ This tool is intended for users who are familiar with the EVM and the EdgeEVM. If you are a + beginner, please use the our{' '} + + Ecosystem Transfer Widget + {' '} + instead.

diff --git a/lib/crypto/deposit.ts b/lib/crypto/deposit.ts index 77b2057b..8a72ead1 100644 --- a/lib/crypto/deposit.ts +++ b/lib/crypto/deposit.ts @@ -65,9 +65,10 @@ export const processEVMDeposit = async ( }, }; } else { + console.error(error); return { success: false, - message: 'Failed to send transaction.', + message: error || 'Failed to send transaction.', }; } } catch (error) { diff --git a/package.json b/package.json index 0917f231..ea83a868 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "export": "next build && next export", "lint": "eslint ./ --ext .ts,.tsx", "lint:fix": "npm run lint -- --fix", - "prettier": "prettier --write '**/*.{ts,tsx}'" + "prettier": "prettier --write '**/*.{ts,tsx}'", + "typecheck": "tsc --noEmit" }, "dependencies": { "@edgeware/node-types": "3.6.2-wako", diff --git a/tailwind.config.js b/tailwind.config.js index 5ee957de..efc2ef47 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -44,7 +44,28 @@ module.exports = { }, }, fontFamily: { - sans: ['"Space Grotesk"', 'sans-serif'], + sans: [ + '"Space Grotesk"', + 'ui-sans-serif', + 'system-ui', + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + 'sans-serif', + ], + mono: [ + 'ui-monospace', + 'SFMono-Regular', + 'Menlo', + 'Monaco', + 'Consolas', + '"Liberation Mono"', + '"Courier New"', + 'monospace', + ], }, }, plugins: [require('@tailwindcss/typography')],