1
1
/* eslint-disable prefer-destructuring */
2
- import { Contract , ethers , formatEther , JsonRpcProvider } from 'ethers' ;
2
+ import { Contract , ethers , JsonRpcProvider } from 'ethers' ;
3
3
import { AccountData } from '../types' ;
4
4
import { ConnectedWallet } from '../ConnectedWallet' ;
5
- import { EVM } from '../EVM ' ;
5
+ import { EVMWallet as EVMBase } from '@fireblocks/wallet-derivation ' ;
6
6
import { erc20Abi } from './erc20.abi' ;
7
+ import { getChainId } from './chains' ;
7
8
8
- export class ERC20 extends EVM implements ConnectedWallet {
9
+ export class ERC20 extends EVMBase implements ConnectedWallet {
9
10
protected provider : JsonRpcProvider | undefined ;
10
11
public rpcURL : string | undefined ;
11
12
public contract ! : Contract ;
12
13
public tokenAddress : string | undefined ;
13
14
public decimals : number | undefined ;
14
15
public toAddress : string | undefined ;
16
+ private normalizingFactor : bigint | undefined ;
17
+ private chainId : number | undefined ;
18
+
19
+ public getNativeAsset ( nativeAsset : string ) {
20
+ this . chainId = getChainId ( nativeAsset ) ;
21
+ if ( ! this . chainId ) {
22
+ throw new Error ( 'Unrecognaized native asset for ERC20 token withdrawal' ) ;
23
+ }
24
+ }
15
25
16
26
public setRPCUrl ( url : string ) : void {
17
27
this . rpcURL = url ;
18
- this . provider = new JsonRpcProvider ( this . rpcURL ) ;
28
+ this . provider = new JsonRpcProvider ( this . rpcURL , this . chainId , { cacheTimeout : - 1 } ) ;
19
29
}
20
30
21
31
public setTokenAddress ( address : string ) {
@@ -32,29 +42,29 @@ export class ERC20 extends EVM implements ConnectedWallet {
32
42
33
43
public setDecimals ( decimals : number ) {
34
44
this . decimals = decimals ;
45
+ this . normalizingFactor = BigInt ( 10 ** decimals ) ;
35
46
}
36
47
37
48
public setToAddress ( toAddress : string ) {
38
49
this . toAddress = toAddress ;
39
50
}
40
51
41
52
public async getBalance ( ) : Promise < number > {
42
- const weiBalance = await this . contract . balanceOf ( this . address ) ;
43
- return parseFloat ( parseFloat ( ethers . formatEther ( weiBalance ) ) . toFixed ( 2 ) ) ;
53
+ const weiBalance : bigint = await this . contract . balanceOf ( this . address ) ;
54
+ return Number ( weiBalance / this . normalizingFactor ! ) ;
44
55
}
45
56
46
57
public async prepare ( ) : Promise < AccountData > {
47
58
this . init ( ) ;
48
59
const nonce = await this . provider ! . getTransactionCount ( this . address , 'latest' ) ;
49
- const chainId = ( await this . provider ! . getNetwork ( ) ) . chainId ;
50
60
51
61
const displayBalance = await this . getBalance ( ) ;
52
62
const ethBalance = await this . getEthBalance ( ) ;
53
63
54
- let { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = await this . provider ! . getFeeData ( ) ;
64
+ const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = await this . provider ! . getFeeData ( ) ;
55
65
56
66
const iface = new ethers . Interface ( erc20Abi ) ;
57
- const data = iface . encodeFunctionData ( 'transfer' , [ this . toAddress , ethers . parseUnits ( displayBalance . toFixed ( 2 ) , 'ether' ) ] ) ;
67
+ const data = iface . encodeFunctionData ( 'transfer' , [ this . toAddress , BigInt ( displayBalance ) * this . normalizingFactor ! ] ) ;
58
68
59
69
const tx = {
60
70
to : this . tokenAddress ,
@@ -63,69 +73,44 @@ export class ERC20 extends EVM implements ConnectedWallet {
63
73
} ;
64
74
const gasLimit = await this . provider ?. estimateGas ( tx ) ;
65
75
66
- const extraParams = new Map ( ) ;
76
+ const extraParams = new Map < string , any > ( ) ;
67
77
extraParams . set ( 'tokenAddress' , this . tokenAddress ) ;
68
- extraParams . set ( 'gasLimit' , gasLimit ) ;
69
- extraParams . set ( 'maxFee' , maxFeePerGas ) ;
70
- extraParams . set ( 'priorityFee' , maxPriorityFeePerGas ) ;
78
+ extraParams . set ( 'gasLimit' , gasLimit ?. toString ( ) ) ;
79
+ extraParams . set ( 'maxFee' , maxFeePerGas ?. toString ( ) ) ;
80
+ extraParams . set ( 'priorityFee' , maxPriorityFeePerGas ?. toString ( ) ) ;
81
+ extraParams . set ( 'weiBalance' , ( BigInt ( displayBalance ) * this . normalizingFactor ! ) . toString ( ) ) ;
71
82
72
83
const preparedData : AccountData = {
73
84
balance : displayBalance ,
74
85
extraParams,
75
86
gasPrice,
76
87
nonce,
77
- chainId : Number ( chainId ) ,
88
+ chainId : this . chainId ,
78
89
insufficientBalance : displayBalance <= 0 ,
79
- insufficientBalanceForTokenTransfer : ethBalance <= gasPrice ! * gasLimit ! ,
90
+ insufficientBalanceForTokenTransfer : Number ( ethBalance ! ) <= Number ( gasPrice ! * gasLimit ! ) ,
80
91
} ;
81
- this . relayLogger . logPreparedData ( 'ERC20' , preparedData ) ;
82
92
return preparedData ;
83
93
}
84
94
85
- // public async generateTx(to: string, amount: number): Promise<TxPayload> {
86
- // const nonce = await this.provider!.getTransactionCount(this.address, 'latest');
87
-
88
- // // Should we use maxGasPrice? i.e. EIP1559.
89
- // const { gasPrice } = await this.provider!.getFeeData();
90
-
91
- // const tx = {
92
- // from: this.address,
93
- // to,
94
- // nonce,
95
- // gasLimit: 21000,
96
- // gasPrice,
97
- // value: 0,
98
- // chainId: this.path.coinType === 1 ? 5 : 1,
99
- // data: new Interface(transferAbi).encodeFunctionData('transfer', [
100
- // to,
101
- // BigInt(amount) * BigInt(await this.contract.decimals()),
102
- // ]),
103
- // };
104
-
105
- // this.relayLogger.debug(`ERC20: Generated tx: ${JSON.stringify(tx, (_, v) => (typeof v === 'bigint' ? v.toString() : v), 2)}`);
106
-
107
- // const unsignedTx = Transaction.from(tx).serialized;
108
-
109
- // const preparedData = {
110
- // derivationPath: this.pathParts,
111
- // tx: unsignedTx,
112
- // };
113
-
114
- // this.relayLogger.debug(`ERC20: Prepared data: ${JSON.stringify(preparedData, null, 2)}`);
115
- // return preparedData;
116
- // }
117
-
118
95
public async broadcastTx ( txHex : string ) : Promise < string > {
119
- return super . broadcastTx ( txHex ) ;
96
+ try {
97
+ const txRes = await this . provider ! . broadcastTransaction ( txHex ) ;
98
+ this . relayLogger . debug ( `EVM: Tx broadcasted: ${ JSON . stringify ( txRes , null , 2 ) } ` ) ;
99
+ return txRes . hash ;
100
+ } catch ( e ) {
101
+ this . relayLogger . error ( 'EVM: Error broadcasting tx:' , e ) ;
102
+ if ( ( e as Error ) . message . includes ( 'insufficient funds for intrinsic transaction cost' ) ) {
103
+ throw new Error (
104
+ 'Insufficient funds for transfer, this might be due to a spike in network fees, please wait and try again' ,
105
+ ) ;
106
+ }
107
+ throw e ;
108
+ }
120
109
}
121
110
122
111
private async getEthBalance ( ) {
123
- const weiBalance = await this . provider ?. getBalance ( this . address ) ;
124
- const balance = formatEther ( weiBalance ! ) ;
125
- const ethBalance = Number ( balance ) ;
126
-
127
- console . info ( 'Eth balance info' , { ethBalance } ) ;
128
-
129
- return ethBalance ;
112
+ const weiBalanceBN = await this . provider ?. getBalance ( this . address ) ;
113
+ console . info ( 'Eth balance info' , { weiBalanceBN } ) ;
114
+ return weiBalanceBN ;
130
115
}
131
116
}
0 commit comments