Skip to content

Commit

Permalink
Add network (live/testnet) switcher to transfer widget (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
johny committed Jul 4, 2023
1 parent b9173f7 commit 24582f6
Show file tree
Hide file tree
Showing 13 changed files with 617 additions and 150 deletions.
171 changes: 146 additions & 25 deletions components/pages/ecosystem/widget/useTransferWidget.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useReducer, useRef, useEffect } from 'react';
import { useWeb3Context } from './web3-context';
import { BN } from '@polkadot/util';
import { validateEVMAddress, validateSubstrateAddress, validateTokenAmount } from 'lib/crypto';
import { processEVMDeposit } from 'lib/crypto/deposit';
import { processEVMWithdrawal } from 'lib/crypto/withdrawal';
import { useCallback, useEffect, useReducer, useRef } from 'react';
import { useWeb3Context } from './web3-context';

// Shared types
export type Network = 'mainnet' | 'testnet';
type FormState = 'initial' | 'ready' | 'in-progress' | 'success' | 'error';
type TargetTransferType = 'deposit' | 'withdrawal';
type FormErrorsState = {
Expand All @@ -17,43 +19,57 @@ type FormErrorsState = {
// State
type State = {
formState: FormState;
network: Network;
ethereumConnected: boolean;
polkadotConnected: boolean;
targetTransferType: TargetTransferType;
selectedPolkadotAccount: string | undefined;
selectedEthereumAccount: string | undefined;
errors: FormErrorsState;
message: string | null;
tx: string | null;
block: string | null;
};

// Initial state
const initialState: State = {
formState: 'initial',
network: 'mainnet',
ethereumConnected: false,
polkadotConnected: false,
targetTransferType: 'deposit',
selectedPolkadotAccount: undefined,
selectedEthereumAccount: undefined,
errors: {},
message: null,
tx: null,
block: null,
};

// Action types
type Action =
| { type: 'SET_FORM_STATE'; payload: FormState }
| { type: 'SET_NETWORK'; payload: Network }
| { type: 'SET_ETHEREUM_CONNECTED'; payload: boolean }
| { type: 'SET_POLKADOT_CONNECTED'; payload: boolean }
| { type: 'SET_TARGET_TRANSFER_TYPE'; payload: TargetTransferType }
| { type: 'SET_SELECTED_POLKADOT_ACCOUNT'; payload: string | undefined }
| { type: 'SET_SELECTED_ETHEREUM_ACCOUNT'; payload: string | undefined }
| { type: 'SET_ERRORS'; payload: FormErrorsState }
| { type: 'SET_TX'; payload: string | null };
| { type: 'SET_MESSAGE'; payload: string | null }
| { type: 'SET_TX'; payload: string | null }
| { type: 'SET_BLOCK'; payload: string | null };

// Reducer
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'SET_FORM_STATE':
return { ...state, formState: action.payload };
case 'SET_NETWORK':
return {
...state,
network: action.payload,
};
case 'SET_ETHEREUM_CONNECTED':
return { ...state, ethereumConnected: action.payload };
case 'SET_POLKADOT_CONNECTED':
Expand All @@ -66,8 +82,12 @@ const reducer = (state: State, action: Action): State => {
return { ...state, selectedEthereumAccount: action.payload };
case 'SET_ERRORS':
return { ...state, errors: action.payload };
case 'SET_MESSAGE':
return { ...state, message: action.payload };
case 'SET_TX':
return { ...state, tx: action.payload };
case 'SET_BLOCK':
return { ...state, block: action.payload };
default:
return state;
}
Expand All @@ -76,12 +96,20 @@ const reducer = (state: State, action: Action): State => {
export const useTransferWidget = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const amountInputRef = useRef<HTMLInputElement>(null);
const { connectToEthereum, ethereumAccounts, connectToPolkadot, polkadotAccounts } =
useWeb3Context();
const {
connectToEthereum,
disconnectFromEthereum,
ethereumAccounts,
connectToPolkadot,
disconnectFromPolkadot,
polkadotAccounts,
updateBalances,
} = useWeb3Context();

// Actions
const setFormState = (formState: FormState) =>
dispatch({ type: 'SET_FORM_STATE', payload: formState });
const setNetwork = (network: Network) => dispatch({ type: 'SET_NETWORK', payload: network });
const setEthereumConnected = (connected: boolean) =>
dispatch({ type: 'SET_ETHEREUM_CONNECTED', payload: connected });
const setPolkadotConnected = (connected: boolean) =>
Expand All @@ -93,7 +121,10 @@ export const useTransferWidget = () => {
const setSelectedEthereumAccount = (account: string | undefined) =>
dispatch({ type: 'SET_SELECTED_ETHEREUM_ACCOUNT', payload: account });
const setErrors = (errors: FormErrorsState) => dispatch({ type: 'SET_ERRORS', payload: errors });
const setMessage = (message: string | null) =>
dispatch({ type: 'SET_MESSAGE', payload: message });
const setTx = (tx: string | null) => dispatch({ type: 'SET_TX', payload: tx });
const setBlock = (block: string | null) => dispatch({ type: 'SET_BLOCK', payload: block });

// Logic functions
const handleDepositSubmit = () => {
Expand Down Expand Up @@ -136,15 +167,35 @@ export const useTransferWidget = () => {
return;
}

// validate balance
const balance = polkadotAccounts.find((a) => a.address === substrateAddress).balance;
const transferBn = new BN(amount).mul(new BN(10).pow(new BN(18)));
const sufficientBallance = balance?.amount.gt(transferBn);

if (!sufficientBallance) {
setErrors({
amount: 'Insufficient balance',
});

return;
}

// submit form and continue with processing
setFormState('in-progress');

async function process() {
try {
const result = await processEVMDeposit(evmAddress, substrateAddress, amount);
const result = await processEVMDeposit(evmAddress, substrateAddress, amount, state.network);
if (result.success) {
setFormState('success');
setMessage(result?.message);
setTx(result?.data?.tx);
setBlock(result?.data?.block);
} else {
setErrors({
global: result.message,
});
setFormState('error');
}
} catch (error) {
setErrors({
Expand Down Expand Up @@ -198,15 +249,41 @@ export const useTransferWidget = () => {
return;
}

// validate balance
const balance = ethereumAccounts.find((a) => a.address === evmAddress).balance;
const transferBn = new BN(amount).mul(new BN(10).pow(new BN(18)));
const sufficientBallance = balance?.amount.gt(transferBn);

if (!sufficientBallance) {
setErrors({
amount: 'Insufficient balance',
});

return;
}

// submit form and continue with processing
setFormState('in-progress');

async function process() {
try {
const result = await processEVMWithdrawal(evmAddress, substrateAddress, amount);
const result = await processEVMWithdrawal(
evmAddress,
substrateAddress,
amount,
state.network
);

if (result.success) {
setFormState('success');
setTx(result?.data?.tx);
setMessage(result?.message);
setBlock(result?.data?.block);
setFormState('success');
} else {
setErrors({
global: result.message,
});
setFormState('error');
}
} catch (error) {
console.error(error);
Expand All @@ -228,6 +305,8 @@ export const useTransferWidget = () => {
} else {
handleWithdrawalSubmit();
}

handleBalanceUpdate();
};

const handleReset = (event: React.MouseEvent<HTMLButtonElement>) => {
Expand All @@ -237,21 +316,24 @@ export const useTransferWidget = () => {
} else {
setFormState('initial');
}
setTargetTransferType('deposit');
setErrors({});
setTx(null);
setBlock(null);
setMessage(null);
amountInputRef.current.value = '';
};

const handleConnect = (type: 'polkadot' | 'ethereum') => {
const isConnected = type === 'polkadot' ? state.polkadotConnected : state.ethereumConnected;

if (isConnected) {
if (
(type === 'polkadot' && state.polkadotConnected) ||
(type === 'ethereum' && state.ethereumConnected)
) {
return;
}

async function connect() {
if (type === 'polkadot') {
const result = await connectToPolkadot();
const result = await connectToPolkadot(state.network);
if (result) {
setPolkadotConnected(true);
if (state.ethereumConnected) {
Expand All @@ -260,7 +342,7 @@ export const useTransferWidget = () => {
}
} else {
try {
const result = await connectToEthereum();
const result = await connectToEthereum(state.network);
if (result) {
setEthereumConnected(true);
if (state.polkadotConnected) {
Expand All @@ -285,30 +367,67 @@ export const useTransferWidget = () => {
return;
}

if (type === 'polkadot') {
setPolkadotConnected(false);
setSelectedPolkadotAccount(undefined);
setFormState('initial');
}
async function disconnect() {
if (type === 'polkadot') {
await disconnectFromPolkadot();
setPolkadotConnected(false);
setSelectedPolkadotAccount(undefined);
setFormState('initial');
}

if (type === 'ethereum') {
setEthereumConnected(false);
setSelectedEthereumAccount(undefined);
setFormState('initial');
if (type === 'ethereum') {
await disconnectFromEthereum();
setEthereumConnected(false);
setSelectedEthereumAccount(undefined);
setFormState('initial');
}
}

disconnect();
};

const handleNetworkChange = (network: Network) => {
// reset state to ready and clear errors
setFormState('initial');
setPolkadotConnected(false);
setEthereumConnected(false);
setSelectedPolkadotAccount(undefined);
setSelectedEthereumAccount(undefined);
setErrors({});
setNetwork(network);
};

const handleBalanceUpdate = useCallback(() => {
// Balance will update by just connecting to the network again
async function update() {
console.log('Periodically updating balances');
await updateBalances(state.network);
}

window.setTimeout(update, 10_000);
}, [state.network, updateBalances]);

// useEffect hooks to synchronize hook state with useWeb3Context state
useEffect(() => {
if (state.polkadotConnected && polkadotAccounts && polkadotAccounts.length > 0) {
setSelectedPolkadotAccount(polkadotAccounts[0].address);
if (!state.selectedPolkadotAccount) {
setSelectedPolkadotAccount(polkadotAccounts[0].address);
}
}

if (state.ethereumConnected && ethereumAccounts && ethereumAccounts.length > 0) {
setSelectedEthereumAccount(ethereumAccounts[0].address);
if (!state.selectedEthereumAccount) {
setSelectedEthereumAccount(ethereumAccounts[0].address);
}
}
}, [state, polkadotAccounts, ethereumAccounts]);

useEffect(() => {
if (state.formState === 'success') {
handleBalanceUpdate();
}
}, [state.formState, handleBalanceUpdate]);

return {
state,
polkadotAccounts,
Expand All @@ -322,9 +441,11 @@ export const useTransferWidget = () => {
setSelectedEthereumAccount,
setErrors,
setTx,
setBlock,
handleSubmit,
handleReset,
handleConnect,
handleDisconnect,
handleNetworkChange,
};
};
Loading

1 comment on commit 24582f6

@vercel
Copy link

@vercel vercel bot commented on 24582f6 Jul 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.