Skip to content

Commit

Permalink
refactor for unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
qrtp committed Nov 1, 2024
1 parent b0cb600 commit 5235186
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 55 deletions.
18 changes: 11 additions & 7 deletions packages/ui-components/src/components/Wallet/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {TokenType, useTranslationContext} from '../../lib';
import {sleep} from '../../lib/sleep';
import type {AccountAsset} from '../../lib/types/fireBlocks';
import {getAsset} from '../../lib/wallet/asset';
import {getProviderUrl} from '../../lib/wallet/evm/provider';
import {createErc20TransferTx} from '../../lib/wallet/evm/token';
import {isEthAddress} from '../Chat/protocol/resolution';
import {getBlockchainDisplaySymbol} from '../Manage/common/verification/types';
Expand Down Expand Up @@ -217,13 +218,16 @@ const Send: React.FC<Props> = ({
// depending on the type of token, estimate the required gas
if (token.type === TokenType.Erc20 && token.address) {
// retrieve gas for a transaction
const transferTx = await createErc20TransferTx(
assetToSend.blockchainAsset.blockchain.networkId,
token.address,
token.walletAddress,
token.walletAddress,
0.0001,
);
const transferTx = await createErc20TransferTx({
chainId: assetToSend.blockchainAsset.blockchain.networkId,
providerUrl: getProviderUrl(
assetToSend.blockchainAsset.blockchain.networkId,
),
tokenAddress: token.address,
fromAddress: token.walletAddress,
toAddress: token.walletAddress,
amount: 0.000001,
});
const transferTxGas = await getTransactionGasEstimate(
assetToSend,
accessToken,
Expand Down
18 changes: 11 additions & 7 deletions packages/ui-components/src/hooks/useSubmitTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {isEmailValid} from '../lib/isEmailValid';
import {pollForSuccess} from '../lib/poll';
import type {AccountAsset, GetOperationResponse} from '../lib/types/fireBlocks';
import {OperationStatusType} from '../lib/types/fireBlocks';
import {getProviderUrl} from '../lib/wallet/evm/provider';
import {createErc20TransferTx} from '../lib/wallet/evm/token';
import useResolverKeys from './useResolverKeys';

Expand Down Expand Up @@ -124,13 +125,16 @@ export const useSubmitTransaction = ({
asset.blockchainAsset.blockchain.networkId &&
token.address &&
token.type === TokenType.Erc20
? await createErc20TransferTx(
asset.blockchainAsset.blockchain.networkId,
token.address,
token.walletAddress,
recipientAddress,
parseFloat(amount),
)
? await createErc20TransferTx({
chainId: asset.blockchainAsset.blockchain.networkId,
providerUrl: getProviderUrl(
asset.blockchainAsset.blockchain.networkId,
),
tokenAddress: token.address,
fromAddress: token.walletAddress,
toAddress: recipientAddress,
amount: parseFloat(amount),
})
: undefined;

// create new transfer request, depending on token type
Expand Down
13 changes: 13 additions & 0 deletions packages/ui-components/src/lib/wallet/evm/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import config from '@unstoppabledomains/config';

export const getProviderUrl = (chainId: number) => {
const providerUrl = Object.values(config.BLOCKCHAINS).find(
v => v.CHAIN_ID === chainId,
)?.JSON_RPC_API_URL;
if (providerUrl) {
return providerUrl;
}

// an RPC provider URL is required
throw new Error(`Chain ID not supported: ${chainId}`);
};
22 changes: 14 additions & 8 deletions packages/ui-components/src/lib/wallet/evm/token.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import {createErc20TransferTx} from './token';
import * as web3 from './web3';

describe('token transactions', () => {
it('should create an erc20 transfer tx', () => {
const tx = createErc20TransferTx(
80002,
'0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
'0xCD0DAdAb45bAF9a06ce1279D1342EcC3F44845af',
'0x8ee1E1d88EBE2B44eAD162777DE787Ef6A2dC2F2',
1,
);
it('should create an erc20 transfer tx', async () => {
// mock the contract decimals
jest.spyOn(web3, 'getContractDecimals').mockResolvedValue(6);

// create the transaction
const tx = await createErc20TransferTx({
chainId: 80002,
providerUrl: 'https://mock-provider-url',
tokenAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
fromAddress: '0xCD0DAdAb45bAF9a06ce1279D1342EcC3F44845af',
toAddress: '0x8ee1E1d88EBE2B44eAD162777DE787Ef6A2dC2F2',
amount: 0.000001,
});
expect(tx).toMatchObject({
chainId: 80002,
to: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
Expand Down
48 changes: 24 additions & 24 deletions packages/ui-components/src/lib/wallet/evm/token.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import {erc20Abi} from 'abitype/abis';

import type {CreateTransaction} from '../../types';
import {getWeb3} from './web3';

export const createErc20TransferTx = async (
chainId: number,
tokenAddress: string,
fromAddress: string,
toAddress: string,
amount: number,
): Promise<CreateTransaction> => {
// retrieve a web3 provider for requested chain ID
const web3 = getWeb3(chainId);
if (!web3) {
throw new Error(`Chain ID not supported: ${chainId}`);
}
import {getContract, getContractDecimals} from './web3';

export const createErc20TransferTx = async (opts: {
chainId: number;
providerUrl: string;
tokenAddress: string;
fromAddress: string;
toAddress: string;
amount: number;
}): Promise<CreateTransaction> => {
// ERC-20 contract instance for sending a specific token
const erc20Contract = new web3.eth.Contract(erc20Abi, tokenAddress, {
from: fromAddress,
});
const erc20Contract = getContract(
opts.providerUrl,
opts.tokenAddress,
opts.fromAddress,
);

// retrieve the contract decimals to represent the amount
const decimals = await erc20Contract.methods.decimals.call([]).call();
const normalizedAmt = Math.floor(amount * Math.pow(10, Number(decimals)));
const decimals = await getContractDecimals(
opts.providerUrl,
opts.tokenAddress,
);
const normalizedAmt = Math.floor(opts.amount * Math.pow(10, decimals));

// create the transaction that should be signed to execute ERC-20 transfer
return {
chainId,
to: tokenAddress,
data: erc20Contract.methods.transfer(toAddress, normalizedAmt).encodeABI(),
chainId: opts.chainId,
to: opts.tokenAddress,
data: erc20Contract.methods
.transfer(opts.toAddress, normalizedAmt)
.encodeABI(),
value: '0',
};
};
32 changes: 23 additions & 9 deletions packages/ui-components/src/lib/wallet/evm/web3.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import {erc20Abi} from 'abitype/abis';
import {Web3} from 'web3';

import config from '@unstoppabledomains/config';

export const getWeb3 = (chainId: number): Web3 | undefined => {
const providerUrl = Object.values(config.BLOCKCHAINS).find(
v => v.CHAIN_ID === chainId,
)?.JSON_RPC_API_URL;
if (!providerUrl) {
return undefined;
}
export const getWeb3 = (providerUrl: string): Web3 => {
return new Web3(providerUrl);
};

export const getContract = (
providerUrl: string,
tokenAddress: string,
fromAddress?: string,
) => {
// ERC-20 contract instance for sending a specific token
const web3 = getWeb3(providerUrl);
return new web3.eth.Contract(erc20Abi, tokenAddress, {
from: fromAddress,
});
};

export const getContractDecimals = async (
providerUrl: string,
address: string,
): Promise<number> => {
const erc20Contract = getContract(providerUrl, address);
const decimals = await erc20Contract.methods.decimals.call([]).call();
return Number(decimals);
};
1 change: 1 addition & 0 deletions packages/ui-components/src/tests/mocks/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const mockAccountAsset = (): AccountAsset => {
blockchain: {
id: 'ETH',
name: 'Ethereum',
networkId: 1,
},
},
accountId: '',
Expand Down

0 comments on commit 5235186

Please sign in to comment.