Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions apps/recovery-relay/lib/wallets/ERC20/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Contract, ethers, JsonRpcProvider, parseUnits } from 'ethers';
import { Contract, ethers, formatUnits, JsonRpcProvider } from 'ethers';
import { AccountData } from '../types';
import { ConnectedWallet } from '../ConnectedWallet';
import { Input, EVMWallet as EVMBase } from '@fireblocks/wallet-derivation';
Expand All @@ -14,7 +14,6 @@ export class ERC20 extends EVMBase implements ConnectedWallet {
public tokenAddress: string | undefined;
public decimals: number | undefined;
public toAddress: string | undefined;
private normalizingFactor: string | bigint | undefined;

constructor(private input: Input) {
super(input);
Expand Down Expand Up @@ -54,38 +53,36 @@ export class ERC20 extends EVMBase implements ConnectedWallet {
this.relayLogger.error(`ERC20 Token decimals are unavailable: ${this.assetId}`);
throw new Error(`ERC20 Token decimals are unavailable: ${this.assetId}`);
}

this.normalizingFactor = (10 ** decimals).toString();
}

public setToAddress(toAddress: string) {
this.toAddress = toAddress;
}

private async getTokenBalanceInBaseUnits(): Promise<bigint> {
return BigInt(await this.contract!.balanceOf(this.address));
}

public async getBalance(): Promise<number> {
const weiBalance: bigint = await this.contract?.balanceOf(this.address);
return Number(weiBalance / BigInt(this.normalizingFactor!));
return Number(formatUnits(await this.getTokenBalanceInBaseUnits(), this.decimals!));
}

public async prepare(): Promise<AccountData> {
this.init();
const nonce = await this.provider?.getTransactionCount(this.address, 'latest');

const displayBalance = await this.getBalance();
const ethBalance = await this.backendWallet?.getBalance();
if (!ethBalance) {
const tokenBalanceInBaseUnits = await this.getTokenBalanceInBaseUnits();
const displayBalance = Number(formatUnits(tokenBalanceInBaseUnits, this.decimals!));
const ethBalanceInWei = await this.provider!.getBalance(this.address);
if (!ethBalanceInWei) {
this.relayLogger.error(`Fee asset balance not available`);
throw new Error(`Fee asset balance not available`);
}
const ethBalanceInWei = parseUnits(ethBalance.toString(), 'ether');

const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = await this.provider!.getFeeData();

const abiInterface = new ethers.Interface(erc20Abi);
const data = abiInterface.encodeFunctionData('transfer', [
this.toAddress,
BigInt(displayBalance) * BigInt(this.normalizingFactor!),
]);
const data = abiInterface.encodeFunctionData('transfer', [this.toAddress, tokenBalanceInBaseUnits]);

const tx = {
to: this.tokenAddress,
Expand All @@ -100,16 +97,16 @@ export class ERC20 extends EVMBase implements ConnectedWallet {
extraParams.set('gasPrice', gasPrice?.toString());
extraParams.set('maxFee', maxFeePerGas?.toString());
extraParams.set('priorityFee', maxPriorityFeePerGas?.toString());
extraParams.set('weiBalance', (BigInt(displayBalance) * BigInt(this.normalizingFactor!)).toString());
extraParams.set('weiBalance', tokenBalanceInBaseUnits.toString());

const preparedData: AccountData = {
balance: displayBalance,
extraParams,
nonce,
//@ts-ignore
chainId: this.backendWallet?.chainId,
insufficientBalance: displayBalance <= 0,
insufficientBalanceForTokenTransfer: Number(ethBalanceInWei!) <= Number(gasPrice! * gasLimit!),
insufficientBalance: tokenBalanceInBaseUnits <= 0n,
insufficientBalanceForTokenTransfer: ethBalanceInWei <= gasPrice! * gasLimit!,
};
return preparedData;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/recovery-relay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"author": "Fireblocks (https://www.fireblocks.com)",
"repository": "https://github.com/fireblocks/recovery/tree/main/apps/recovery-relay",
"license": "GPL-3.0-or-later",
"version": "1.8.6",
"version": "1.8.8",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 1 addition & 1 deletion apps/recovery-utility/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"author": "Fireblocks (https://www.fireblocks.com)",
"repository": "https://github.com/fireblocks/recovery/tree/main/apps/recovery-utility",
"homepage": "https://www.fireblocks.com",
"version": "1.8.7",
"version": "1.8.8",
"license": "GPL-3.0-or-later",
"private": true,
"main": "app/background.js",
Expand Down
1 change: 0 additions & 1 deletion packages/shared/lib/csv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ export const csvImport = async <T extends 'addresses' | 'balances'>(
header: true,
dynamicTyping: true,
skipEmptyLines: 'greedy',
fastMode: true,
step: ({ data, errors }) => {
const sanatizedData = sanatize(data as unknown);
logger.debug('csvImport', { sanatizedData, errors });
Expand Down
18 changes: 18 additions & 0 deletions packages/shared/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,24 @@ Test,1523,BTC,Bitcoin,abcd,Deposit,1111111111,,`;
});
});

it('Should import CSV with quoted fields (console export format)', async () => {
const quotedCsv = `"Account Name","Account ID","Asset","Asset Name","Address","Address Type","Address Description","Tag","HD Path"
"Default","0","BASECHAIN_ETH","Ethereum (Base)","abcd","Permanent","","","m / 44 / 60 / 0 / 0 / 0"`;
const rows: string[] = [];
await csvImport(Readable.from(quotedCsv), 'addresses', (row) => {
rows.push(row.address);
});
expect(rows).toEqual(['abcd']);
});

it('Should fail importing quoted CSV with macros', async () => {
const quotedMacroCsv = `"Account Name","Account ID","Asset","Asset Name","Address","Address Type","Address Description","Tag","HD Path"
"Default","0","ETH","Ethereum","=6+2","Permanent","","","m / 44 / 60 / 0 / 0 / 0"`;
await expect(csvImport(Readable.from(quotedMacroCsv), 'addresses', (_) => {})).rejects.toThrow(
'Row contains prohibited characters - please reset workspace and check your importing CSV',
);
});

it('Should fail importing CSV with too many fields error', async () => {
expect(async () => {
await csvImport(Readable.from(invalidCsvTooManyFields), 'addresses', (_) => {});
Expand Down