@sidhujag/sysweb3-keyring 1.0.544 → 1.0.547
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/coverage/clover.xml +2875 -0
- package/coverage/coverage-final.json +29468 -0
- package/coverage/lcov-report/base.css +354 -0
- package/coverage/lcov-report/block-navigation.js +85 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +320 -0
- package/coverage/lcov-report/prettify.css +101 -0
- package/coverage/lcov-report/prettify.js +1008 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +191 -0
- package/coverage/lcov-report/src/index.html +276 -0
- package/coverage/lcov-report/src/index.ts.html +114 -0
- package/coverage/lcov-report/src/initial-state.ts.html +558 -0
- package/coverage/lcov-report/src/keyring-manager.ts.html +6279 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.html +178 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.ts.html +144 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/appClient.ts.html +1560 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/bip32.ts.html +276 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/buffertools.ts.html +495 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/clientCommands.ts.html +1138 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/index.html +363 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts.html +289 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkle.ts.html +486 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkleMap.ts.html +240 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/policy.ts.html +342 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/psbtv2.ts.html +2388 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/varint.ts.html +453 -0
- package/coverage/lcov-report/src/ledger/consts.ts.html +177 -0
- package/coverage/lcov-report/src/ledger/index.html +216 -0
- package/coverage/lcov-report/src/ledger/index.ts.html +1371 -0
- package/coverage/lcov-report/src/ledger/utils.ts.html +102 -0
- package/coverage/lcov-report/src/signers.ts.html +591 -0
- package/coverage/lcov-report/src/storage.ts.html +198 -0
- package/coverage/lcov-report/src/transactions/ethereum.ts.html +5826 -0
- package/coverage/lcov-report/src/transactions/index.html +216 -0
- package/coverage/lcov-report/src/transactions/index.ts.html +93 -0
- package/coverage/lcov-report/src/transactions/syscoin.ts.html +1521 -0
- package/coverage/lcov-report/src/trezor/index.html +176 -0
- package/coverage/lcov-report/src/trezor/index.ts.html +2655 -0
- package/coverage/lcov-report/src/types.ts.html +1443 -0
- package/coverage/lcov-report/src/utils/derivation-paths.ts.html +486 -0
- package/coverage/lcov-report/src/utils/index.html +196 -0
- package/coverage/lcov-report/src/utils/psbt.ts.html +159 -0
- package/coverage/lcov-report/test/helpers/constants.ts.html +627 -0
- package/coverage/lcov-report/test/helpers/index.html +176 -0
- package/coverage/lcov.info +4832 -0
- package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/appClient.js +1 -124
- package/dist/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
- package/{cjs → dist/cjs}/transactions/ethereum.js +24 -11
- package/dist/cjs/transactions/ethereum.js.map +1 -0
- package/dist/package.json +50 -0
- package/{types → dist/types}/ledger/bitcoin_client/lib/appClient.d.ts +0 -6
- package/examples/basic-usage.js +140 -0
- package/jest.config.js +32 -0
- package/package.json +31 -13
- package/readme.md +201 -0
- package/src/declare.d.ts +7 -0
- package/src/errorUtils.ts +83 -0
- package/src/hardware-wallet-manager.ts +655 -0
- package/src/index.ts +12 -0
- package/src/initial-state.ts +108 -0
- package/src/keyring-manager.ts +2698 -0
- package/src/ledger/bitcoin_client/index.ts +19 -0
- package/src/ledger/bitcoin_client/lib/appClient.ts +405 -0
- package/src/ledger/bitcoin_client/lib/bip32.ts +61 -0
- package/src/ledger/bitcoin_client/lib/buffertools.ts +134 -0
- package/src/ledger/bitcoin_client/lib/clientCommands.ts +356 -0
- package/src/ledger/bitcoin_client/lib/constants.ts +12 -0
- package/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts +65 -0
- package/src/ledger/bitcoin_client/lib/merkle.ts +136 -0
- package/src/ledger/bitcoin_client/lib/merkleMap.ts +49 -0
- package/src/ledger/bitcoin_client/lib/policy.ts +91 -0
- package/src/ledger/bitcoin_client/lib/psbtv2.ts +768 -0
- package/src/ledger/bitcoin_client/lib/varint.ts +120 -0
- package/src/ledger/consts.ts +3 -0
- package/src/ledger/index.ts +685 -0
- package/src/ledger/types.ts +74 -0
- package/src/network-utils.ts +99 -0
- package/src/providers.ts +345 -0
- package/src/signers.ts +158 -0
- package/src/storage.ts +63 -0
- package/src/transactions/__tests__/integration.test.ts +303 -0
- package/src/transactions/__tests__/syscoin.test.ts +409 -0
- package/src/transactions/ethereum.ts +2503 -0
- package/src/transactions/index.ts +2 -0
- package/src/transactions/syscoin.ts +542 -0
- package/src/trezor/index.ts +1050 -0
- package/src/types.ts +366 -0
- package/src/utils/derivation-paths.ts +133 -0
- package/src/utils/psbt.ts +24 -0
- package/src/utils.ts +191 -0
- package/test/README.md +158 -0
- package/test/__mocks__/ledger-mock.js +20 -0
- package/test/__mocks__/trezor-mock.js +75 -0
- package/test/cleanup-summary.md +167 -0
- package/test/helpers/README.md +78 -0
- package/test/helpers/constants.ts +79 -0
- package/test/helpers/setup.ts +714 -0
- package/test/integration/import-validation.spec.ts +588 -0
- package/test/unit/hardware/ledger.spec.ts +869 -0
- package/test/unit/hardware/trezor.spec.ts +828 -0
- package/test/unit/keyring-manager/account-management.spec.ts +970 -0
- package/test/unit/keyring-manager/import-watchonly.spec.ts +181 -0
- package/test/unit/keyring-manager/import-wif.spec.ts +126 -0
- package/test/unit/keyring-manager/initialization.spec.ts +782 -0
- package/test/unit/keyring-manager/key-derivation.spec.ts +996 -0
- package/test/unit/keyring-manager/security.spec.ts +505 -0
- package/test/unit/keyring-manager/state-management.spec.ts +375 -0
- package/test/unit/network/network-management.spec.ts +372 -0
- package/test/unit/transactions/ethereum-transactions.spec.ts +382 -0
- package/test/unit/transactions/syscoin-transactions.spec.ts +615 -0
- package/tsconfig.json +14 -0
- package/cjs/ledger/bitcoin_client/lib/appClient.js.map +0 -1
- package/cjs/transactions/ethereum.js.map +0 -1
- /package/{README.md → dist/README.md} +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js.map +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/index.js +0 -0
- /package/{cjs → dist/cjs}/index.js.map +0 -0
- /package/{cjs → dist/cjs}/initial-state.js +0 -0
- /package/{cjs → dist/cjs}/initial-state.js.map +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js.map +0 -0
- /package/{cjs → dist/cjs}/network-utils.js +0 -0
- /package/{cjs → dist/cjs}/network-utils.js.map +0 -0
- /package/{cjs → dist/cjs}/providers.js +0 -0
- /package/{cjs → dist/cjs}/providers.js.map +0 -0
- /package/{cjs → dist/cjs}/signers.js +0 -0
- /package/{cjs → dist/cjs}/signers.js.map +0 -0
- /package/{cjs → dist/cjs}/storage.js +0 -0
- /package/{cjs → dist/cjs}/storage.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js.map +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js.map +0 -0
- /package/{cjs → dist/cjs}/types.js +0 -0
- /package/{cjs → dist/cjs}/types.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js.map +0 -0
- /package/{cjs → dist/cjs}/utils.js +0 -0
- /package/{cjs → dist/cjs}/utils.js.map +0 -0
- /package/{types → dist/types}/errorUtils.d.ts +0 -0
- /package/{types → dist/types}/hardware-wallet-manager.d.ts +0 -0
- /package/{types → dist/types}/index.d.ts +0 -0
- /package/{types → dist/types}/initial-state.d.ts +0 -0
- /package/{types → dist/types}/keyring-manager.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/bip32.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/buffertools.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/clientCommands.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/constants.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkle.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkleMap.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/policy.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/psbtv2.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/varint.d.ts +0 -0
- /package/{types → dist/types}/ledger/consts.d.ts +0 -0
- /package/{types → dist/types}/ledger/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/types.d.ts +0 -0
- /package/{types → dist/types}/network-utils.d.ts +0 -0
- /package/{types → dist/types}/providers.d.ts +0 -0
- /package/{types → dist/types}/signers.d.ts +0 -0
- /package/{types → dist/types}/storage.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/integration.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/syscoin.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/ethereum.d.ts +0 -0
- /package/{types → dist/types}/transactions/index.d.ts +0 -0
- /package/{types → dist/types}/transactions/syscoin.d.ts +0 -0
- /package/{types → dist/types}/trezor/index.d.ts +0 -0
- /package/{types → dist/types}/types.d.ts +0 -0
- /package/{types → dist/types}/utils/derivation-paths.d.ts +0 -0
- /package/{types → dist/types}/utils/psbt.d.ts +0 -0
- /package/{types → dist/types}/utils.d.ts +0 -0
|
@@ -0,0 +1,2503 @@
|
|
|
1
|
+
import { TransactionResponse } from '@ethersproject/abstract-provider';
|
|
2
|
+
import { BigNumber } from '@ethersproject/bignumber';
|
|
3
|
+
import { isHexString } from '@ethersproject/bytes';
|
|
4
|
+
import { Zero } from '@ethersproject/constants';
|
|
5
|
+
import { Contract } from '@ethersproject/contracts';
|
|
6
|
+
import { Deferrable, resolveProperties } from '@ethersproject/properties';
|
|
7
|
+
import {
|
|
8
|
+
TransactionRequest,
|
|
9
|
+
TransactionResponse as EthersTransactionResponse,
|
|
10
|
+
} from '@ethersproject/providers';
|
|
11
|
+
import { serialize as serializeTransaction } from '@ethersproject/transactions';
|
|
12
|
+
import { parseUnits, formatEther, formatUnits } from '@ethersproject/units';
|
|
13
|
+
import { Wallet } from '@ethersproject/wallet';
|
|
14
|
+
import {
|
|
15
|
+
concatSig,
|
|
16
|
+
decrypt,
|
|
17
|
+
signTypedData as signTypedDataUtil,
|
|
18
|
+
TypedMessage,
|
|
19
|
+
SignTypedDataVersion,
|
|
20
|
+
TypedDataV1,
|
|
21
|
+
getEncryptionPublicKey,
|
|
22
|
+
recoverPersonalSignature,
|
|
23
|
+
recoverTypedSignature,
|
|
24
|
+
EthEncryptedData,
|
|
25
|
+
} from '@metamask/eth-sig-util';
|
|
26
|
+
import { INetwork, INetworkType } from '@sidhujag/sysweb3-network';
|
|
27
|
+
import {
|
|
28
|
+
createContractUsingAbi,
|
|
29
|
+
getErc20Abi,
|
|
30
|
+
getErc21Abi,
|
|
31
|
+
getErc55Abi,
|
|
32
|
+
} from '@sidhujag/sysweb3-utils';
|
|
33
|
+
import { EthereumTransactionEIP1559 } from '@trezor/connect-web';
|
|
34
|
+
import {
|
|
35
|
+
ecsign,
|
|
36
|
+
toBuffer,
|
|
37
|
+
stripHexPrefix,
|
|
38
|
+
hashPersonalMessage,
|
|
39
|
+
toAscii,
|
|
40
|
+
} from 'ethereumjs-util';
|
|
41
|
+
import omit from 'lodash/omit';
|
|
42
|
+
|
|
43
|
+
import { LedgerKeyring } from '../ledger';
|
|
44
|
+
import { CustomJsonRpcProvider, CustomL2JsonRpcProvider } from '../providers';
|
|
45
|
+
import { TrezorKeyring } from '../trezor';
|
|
46
|
+
import {
|
|
47
|
+
IResponseFromSendErcSignedTransaction,
|
|
48
|
+
ISendSignedErcTransactionProps,
|
|
49
|
+
IEthereumTransactions,
|
|
50
|
+
SimpleTransactionRequest,
|
|
51
|
+
KeyringAccountType,
|
|
52
|
+
accountType,
|
|
53
|
+
IGasParams,
|
|
54
|
+
} from '../types';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Chain IDs for zkSync Era networks that require specialized L2 provider functionality.
|
|
58
|
+
* These networks use CustomL2JsonRpcProvider (which extends zksync-ethers.Provider)
|
|
59
|
+
* instead of CustomJsonRpcProvider.
|
|
60
|
+
*
|
|
61
|
+
* zkSync Era networks:
|
|
62
|
+
* - 324: zkSync Era Mainnet
|
|
63
|
+
* - 300: zkSync Era Sepolia Testnet
|
|
64
|
+
*/
|
|
65
|
+
const L2_NETWORK_CHAIN_IDS = [324, 300];
|
|
66
|
+
|
|
67
|
+
export class EthereumTransactions implements IEthereumTransactions {
|
|
68
|
+
private _web3Provider: CustomJsonRpcProvider | CustomL2JsonRpcProvider;
|
|
69
|
+
public trezorSigner: TrezorKeyring;
|
|
70
|
+
public ledgerSigner: LedgerKeyring;
|
|
71
|
+
private getNetwork: () => INetwork;
|
|
72
|
+
private abortController: AbortController;
|
|
73
|
+
private getDecryptedPrivateKey: () => {
|
|
74
|
+
address: string;
|
|
75
|
+
decryptedPrivateKey: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
private getState: () => {
|
|
79
|
+
accounts: {
|
|
80
|
+
HDAccount: accountType;
|
|
81
|
+
Imported: accountType;
|
|
82
|
+
Ledger: accountType;
|
|
83
|
+
Trezor: accountType;
|
|
84
|
+
};
|
|
85
|
+
activeAccountId: number;
|
|
86
|
+
activeAccountType: KeyringAccountType;
|
|
87
|
+
activeNetwork: INetwork;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
constructor(
|
|
91
|
+
getNetwork: () => INetwork,
|
|
92
|
+
getDecryptedPrivateKey: () => {
|
|
93
|
+
address: string;
|
|
94
|
+
decryptedPrivateKey: string;
|
|
95
|
+
},
|
|
96
|
+
getState: () => {
|
|
97
|
+
accounts: {
|
|
98
|
+
HDAccount: accountType;
|
|
99
|
+
Imported: accountType;
|
|
100
|
+
Ledger: accountType;
|
|
101
|
+
Trezor: accountType;
|
|
102
|
+
};
|
|
103
|
+
activeAccountId: number;
|
|
104
|
+
activeAccountType: KeyringAccountType;
|
|
105
|
+
activeNetwork: INetwork;
|
|
106
|
+
},
|
|
107
|
+
ledgerSigner: LedgerKeyring,
|
|
108
|
+
trezorSigner: TrezorKeyring
|
|
109
|
+
) {
|
|
110
|
+
this.getNetwork = getNetwork;
|
|
111
|
+
this.getDecryptedPrivateKey = getDecryptedPrivateKey;
|
|
112
|
+
this.abortController = new AbortController();
|
|
113
|
+
|
|
114
|
+
// NOTE: Defer network access until vault state getter is initialized
|
|
115
|
+
// The web3Provider will be created lazily when first accessed via getters
|
|
116
|
+
|
|
117
|
+
this.getState = getState;
|
|
118
|
+
this.trezorSigner = trezorSigner;
|
|
119
|
+
this.ledgerSigner = ledgerSigner;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Getter that automatically ensures providers are initialized when accessed
|
|
123
|
+
public get web3Provider(): CustomJsonRpcProvider | CustomL2JsonRpcProvider {
|
|
124
|
+
this.ensureProvidersInitialized();
|
|
125
|
+
return this._web3Provider;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Helper method to ensure providers are initialized when first needed
|
|
129
|
+
private ensureProvidersInitialized() {
|
|
130
|
+
if (!this._web3Provider) {
|
|
131
|
+
// Providers not initialized yet, initialize them now
|
|
132
|
+
try {
|
|
133
|
+
const currentNetwork = this.getNetwork();
|
|
134
|
+
this.setWeb3Provider(currentNetwork);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// If vault state not available yet, providers will be initialized later
|
|
137
|
+
// when setWeb3Provider is called explicitly
|
|
138
|
+
console.log(
|
|
139
|
+
'[EthereumTransactions] Deferring provider initialization:',
|
|
140
|
+
error.message
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Helper method to detect UTXO networks
|
|
147
|
+
private isUtxoNetwork(network: INetwork): boolean {
|
|
148
|
+
// Generic UTXO network detection patterns:
|
|
149
|
+
// 1. URL contains blockbook or trezor (most reliable)
|
|
150
|
+
// 2. Network kind is explicitly set to 'syscoin'
|
|
151
|
+
const hasBlockbookUrl = !!(
|
|
152
|
+
network.url?.includes('blockbook') || network.url?.includes('trezor')
|
|
153
|
+
);
|
|
154
|
+
const hasUtxoKind = (network as any).kind === INetworkType.Syscoin;
|
|
155
|
+
|
|
156
|
+
return hasBlockbookUrl || hasUtxoKind;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
signTypedData = async (
|
|
160
|
+
addr: string,
|
|
161
|
+
typedData: TypedDataV1 | TypedMessage<any>,
|
|
162
|
+
version: SignTypedDataVersion
|
|
163
|
+
) => {
|
|
164
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
165
|
+
const { activeAccountType, accounts, activeAccountId } = this.getState();
|
|
166
|
+
const activeAccount = accounts[activeAccountType][activeAccountId];
|
|
167
|
+
|
|
168
|
+
// Validate that the derived address matches the active account to prevent race conditions
|
|
169
|
+
if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
170
|
+
throw {
|
|
171
|
+
message: `Account state mismatch detected. Expected ${activeAccount.address} but got ${address}. Please try again after account switching completes.`,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const signTypedDataLocal = () => {
|
|
176
|
+
if (addr.toLowerCase() !== address.toLowerCase())
|
|
177
|
+
throw {
|
|
178
|
+
message: 'Decrypting for wrong address, change activeAccount maybe',
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const privKey = Buffer.from(stripHexPrefix(decryptedPrivateKey), 'hex');
|
|
182
|
+
return signTypedDataUtil({
|
|
183
|
+
privateKey: privKey,
|
|
184
|
+
data: typedData as any,
|
|
185
|
+
version,
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const signTypedDataWithLedger = async () => {
|
|
190
|
+
if (addr.toLowerCase() !== activeAccount.address.toLowerCase())
|
|
191
|
+
throw {
|
|
192
|
+
message: 'Decrypting for wrong address, change activeAccount maybe',
|
|
193
|
+
};
|
|
194
|
+
return await this.ledgerSigner.evm.signTypedData({
|
|
195
|
+
version,
|
|
196
|
+
accountIndex: activeAccountId,
|
|
197
|
+
data: typedData,
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const signTypedDataWithTrezor = async () => {
|
|
202
|
+
if (addr.toLowerCase() !== activeAccount.address.toLowerCase())
|
|
203
|
+
throw {
|
|
204
|
+
message: 'Decrypting for wrong address, change activeAccount maybe',
|
|
205
|
+
};
|
|
206
|
+
return await this.trezorSigner.signTypedData({
|
|
207
|
+
version,
|
|
208
|
+
address: addr,
|
|
209
|
+
data: typedData,
|
|
210
|
+
index: activeAccountId,
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
switch (activeAccountType) {
|
|
215
|
+
case KeyringAccountType.Trezor:
|
|
216
|
+
return await signTypedDataWithTrezor();
|
|
217
|
+
case KeyringAccountType.Ledger:
|
|
218
|
+
return await signTypedDataWithLedger();
|
|
219
|
+
default:
|
|
220
|
+
return signTypedDataLocal();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
verifyTypedSignature = (
|
|
225
|
+
data: TypedDataV1 | TypedMessage<any>,
|
|
226
|
+
signature: string,
|
|
227
|
+
version: SignTypedDataVersion
|
|
228
|
+
) => {
|
|
229
|
+
try {
|
|
230
|
+
return recoverTypedSignature({ data: data as any, signature, version });
|
|
231
|
+
} catch (error) {
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
ethSign = async (params: string[]) => {
|
|
237
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
238
|
+
const { accounts, activeAccountId, activeAccountType, activeNetwork } =
|
|
239
|
+
this.getState();
|
|
240
|
+
const activeAccount = accounts[activeAccountType][activeAccountId];
|
|
241
|
+
|
|
242
|
+
// Validate that the derived address matches the active account to prevent race conditions
|
|
243
|
+
if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
244
|
+
throw {
|
|
245
|
+
message: `Account state mismatch detected. Expected ${activeAccount.address} but got ${address}. Please try again after account switching completes.`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let msg = '';
|
|
250
|
+
//Comparisions do not need to care for checksum address
|
|
251
|
+
if (params[0].toLowerCase() === address.toLowerCase()) {
|
|
252
|
+
msg = stripHexPrefix(params[1]);
|
|
253
|
+
} else if (params[1].toLowerCase() === address.toLowerCase()) {
|
|
254
|
+
msg = stripHexPrefix(params[0]);
|
|
255
|
+
} else {
|
|
256
|
+
throw new Error('Signing for wrong address');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const sign = () => {
|
|
260
|
+
try {
|
|
261
|
+
const bufPriv = toBuffer(decryptedPrivateKey);
|
|
262
|
+
|
|
263
|
+
// Validate and prepare the message for eth_sign
|
|
264
|
+
let msgHash: Buffer;
|
|
265
|
+
|
|
266
|
+
// Check if message is a valid 32-byte hex string
|
|
267
|
+
if (msg.length === 64 && /^[0-9a-fA-F]+$/.test(msg)) {
|
|
268
|
+
// Message is already a 32-byte hex string
|
|
269
|
+
msgHash = Buffer.from(msg, 'hex');
|
|
270
|
+
} else {
|
|
271
|
+
// Message is not a proper hash - provide helpful error
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Expected message to be an Uint8Array with length 32. ` +
|
|
274
|
+
`Got message of length ${msg.length}: "${msg.substring(0, 50)}${
|
|
275
|
+
msg.length > 50 ? '...' : ''
|
|
276
|
+
}". ` +
|
|
277
|
+
`For signing arbitrary text, use personal_sign instead of eth_sign.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const sig = ecsign(msgHash, bufPriv);
|
|
282
|
+
const resp = concatSig(toBuffer(sig.v), sig.r, sig.s);
|
|
283
|
+
return resp;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const signWithLedger = async () => {
|
|
290
|
+
try {
|
|
291
|
+
const response = await this.ledgerSigner.evm.signPersonalMessage({
|
|
292
|
+
accountIndex: activeAccountId,
|
|
293
|
+
message: msg,
|
|
294
|
+
});
|
|
295
|
+
return response;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const signWithTrezor = async () => {
|
|
302
|
+
try {
|
|
303
|
+
// For EVM networks, Trezor expects 'eth' regardless of the network's currency
|
|
304
|
+
const trezorCoin =
|
|
305
|
+
activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
306
|
+
const response: any = await this.trezorSigner.signMessage({
|
|
307
|
+
coin: trezorCoin,
|
|
308
|
+
address: activeAccount.address,
|
|
309
|
+
index: activeAccountId,
|
|
310
|
+
message: msg,
|
|
311
|
+
slip44: activeNetwork.slip44,
|
|
312
|
+
});
|
|
313
|
+
return response.signature as string;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
switch (activeAccountType) {
|
|
320
|
+
case KeyringAccountType.Trezor:
|
|
321
|
+
return await signWithTrezor();
|
|
322
|
+
case KeyringAccountType.Ledger:
|
|
323
|
+
return await signWithLedger();
|
|
324
|
+
default:
|
|
325
|
+
return sign();
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
signPersonalMessage = async (params: string[]) => {
|
|
330
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
331
|
+
const { accounts, activeAccountId, activeAccountType, activeNetwork } =
|
|
332
|
+
this.getState();
|
|
333
|
+
const activeAccount = accounts[activeAccountType][activeAccountId];
|
|
334
|
+
|
|
335
|
+
// Validate that the derived address matches the active account to prevent race conditions
|
|
336
|
+
if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
337
|
+
throw {
|
|
338
|
+
message: `Account state mismatch detected. Expected ${activeAccount.address} but got ${address}. Please try again after account switching completes.`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let msg = '';
|
|
343
|
+
|
|
344
|
+
if (params[0].toLowerCase() === address.toLowerCase()) {
|
|
345
|
+
msg = params[1];
|
|
346
|
+
} else if (params[1].toLowerCase() === address.toLowerCase()) {
|
|
347
|
+
msg = params[0];
|
|
348
|
+
} else {
|
|
349
|
+
throw new Error('Signing for wrong address');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const signPersonalMessageWithDefaultWallet = () => {
|
|
353
|
+
try {
|
|
354
|
+
const privateKey = toBuffer(decryptedPrivateKey);
|
|
355
|
+
|
|
356
|
+
// Handle both hex-encoded and plain text messages for personal_sign
|
|
357
|
+
let message: Buffer;
|
|
358
|
+
if (msg.startsWith('0x')) {
|
|
359
|
+
// Message is hex-encoded
|
|
360
|
+
try {
|
|
361
|
+
message = toBuffer(msg);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
// If hex parsing fails, treat as plain text
|
|
364
|
+
message = Buffer.from(msg, 'utf8');
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
// Message is plain text
|
|
368
|
+
message = Buffer.from(msg, 'utf8');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const msgHash = hashPersonalMessage(message);
|
|
372
|
+
const sig = ecsign(msgHash, privateKey);
|
|
373
|
+
const serialized = concatSig(toBuffer(sig.v), sig.r, sig.s);
|
|
374
|
+
return serialized;
|
|
375
|
+
} catch (error) {
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const signPersonalMessageWithLedger = async () => {
|
|
381
|
+
try {
|
|
382
|
+
// Handle both hex-encoded and plain text messages for personal_sign
|
|
383
|
+
let messageForLedger: string;
|
|
384
|
+
if (msg.startsWith('0x')) {
|
|
385
|
+
// Message is hex-encoded, remove 0x prefix
|
|
386
|
+
messageForLedger = msg.replace('0x', '');
|
|
387
|
+
} else {
|
|
388
|
+
// Message is plain text, convert to hex
|
|
389
|
+
messageForLedger = Buffer.from(msg, 'utf8').toString('hex');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const response = await this.ledgerSigner.evm.signPersonalMessage({
|
|
393
|
+
accountIndex: activeAccountId,
|
|
394
|
+
message: messageForLedger,
|
|
395
|
+
});
|
|
396
|
+
return response;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const signPersonalMessageWithTrezor = async () => {
|
|
403
|
+
try {
|
|
404
|
+
// Handle both hex-encoded and plain text messages for personal_sign
|
|
405
|
+
let messageForTrezor: string;
|
|
406
|
+
if (msg.startsWith('0x')) {
|
|
407
|
+
// Message is hex-encoded, keep as is
|
|
408
|
+
messageForTrezor = msg;
|
|
409
|
+
} else {
|
|
410
|
+
// Message is plain text, convert to hex with 0x prefix
|
|
411
|
+
messageForTrezor = '0x' + Buffer.from(msg, 'utf8').toString('hex');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// For EVM networks, Trezor expects 'eth' regardless of the network's currency
|
|
415
|
+
const trezorCoin =
|
|
416
|
+
activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
417
|
+
const response: any = await this.trezorSigner.signMessage({
|
|
418
|
+
coin: trezorCoin,
|
|
419
|
+
address: activeAccount.address,
|
|
420
|
+
index: activeAccountId,
|
|
421
|
+
message: messageForTrezor,
|
|
422
|
+
slip44: activeNetwork.slip44,
|
|
423
|
+
});
|
|
424
|
+
return response.signature as string;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
switch (activeAccountType) {
|
|
431
|
+
case KeyringAccountType.Trezor:
|
|
432
|
+
return await signPersonalMessageWithTrezor();
|
|
433
|
+
case KeyringAccountType.Ledger:
|
|
434
|
+
return await signPersonalMessageWithLedger();
|
|
435
|
+
default:
|
|
436
|
+
return signPersonalMessageWithDefaultWallet();
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
parsePersonalMessage = (hexMsg: string) => {
|
|
441
|
+
try {
|
|
442
|
+
return toAscii(hexMsg);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
verifyPersonalMessage = (message: string, sign: string) => {
|
|
449
|
+
try {
|
|
450
|
+
const msgParams = {
|
|
451
|
+
data: message,
|
|
452
|
+
signature: sign,
|
|
453
|
+
};
|
|
454
|
+
return recoverPersonalSignature(msgParams as any);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
getEncryptedPubKey = () => {
|
|
461
|
+
const { activeAccountType } = this.getState();
|
|
462
|
+
|
|
463
|
+
// Hardware wallets don't support encryption public key generation
|
|
464
|
+
if (
|
|
465
|
+
activeAccountType === KeyringAccountType.Trezor ||
|
|
466
|
+
activeAccountType === KeyringAccountType.Ledger
|
|
467
|
+
) {
|
|
468
|
+
throw new Error(
|
|
469
|
+
'Hardware wallets do not support eth_getEncryptionPublicKey'
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
return getEncryptionPublicKey(stripHexPrefix(decryptedPrivateKey));
|
|
477
|
+
} catch (error) {
|
|
478
|
+
throw error;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// eth_decryptMessage
|
|
483
|
+
decryptMessage = (msgParams: string[]) => {
|
|
484
|
+
const { activeAccountType } = this.getState();
|
|
485
|
+
|
|
486
|
+
// Hardware wallets don't support message decryption
|
|
487
|
+
if (
|
|
488
|
+
activeAccountType === KeyringAccountType.Trezor ||
|
|
489
|
+
activeAccountType === KeyringAccountType.Ledger
|
|
490
|
+
) {
|
|
491
|
+
throw new Error('Hardware wallets do not support eth_decrypt');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
495
|
+
|
|
496
|
+
let encryptedData = '';
|
|
497
|
+
|
|
498
|
+
if (msgParams[0].toLowerCase() === address.toLowerCase()) {
|
|
499
|
+
encryptedData = msgParams[1];
|
|
500
|
+
} else if (msgParams[1].toLowerCase() === address.toLowerCase()) {
|
|
501
|
+
encryptedData = msgParams[0];
|
|
502
|
+
} else {
|
|
503
|
+
throw new Error('Decrypting for wrong receiver');
|
|
504
|
+
}
|
|
505
|
+
encryptedData = stripHexPrefix(encryptedData);
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
const buff = Buffer.from(encryptedData, 'hex');
|
|
509
|
+
const cleanData: EthEncryptedData = JSON.parse(buff.toString('utf8'));
|
|
510
|
+
const sig = decrypt({
|
|
511
|
+
encryptedData: cleanData,
|
|
512
|
+
privateKey: stripHexPrefix(decryptedPrivateKey),
|
|
513
|
+
});
|
|
514
|
+
return sig;
|
|
515
|
+
} catch (error) {
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
toBigNumber = (aBigNumberish: string | number) =>
|
|
521
|
+
BigNumber.from(String(aBigNumberish));
|
|
522
|
+
|
|
523
|
+
getData = ({
|
|
524
|
+
contractAddress,
|
|
525
|
+
receivingAddress,
|
|
526
|
+
value,
|
|
527
|
+
}: {
|
|
528
|
+
contractAddress: string;
|
|
529
|
+
receivingAddress: string;
|
|
530
|
+
value: any;
|
|
531
|
+
}) => {
|
|
532
|
+
const abi = getErc20Abi() as any;
|
|
533
|
+
try {
|
|
534
|
+
const contract = createContractUsingAbi(
|
|
535
|
+
abi,
|
|
536
|
+
contractAddress,
|
|
537
|
+
this.web3Provider
|
|
538
|
+
);
|
|
539
|
+
const data = contract.methods
|
|
540
|
+
.transfer(receivingAddress, value)
|
|
541
|
+
.encodeABI();
|
|
542
|
+
|
|
543
|
+
return data;
|
|
544
|
+
} catch (error) {
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
getFeeDataWithDynamicMaxPriorityFeePerGas = async () => {
|
|
550
|
+
let maxFeePerGas = this.toBigNumber(0);
|
|
551
|
+
let maxPriorityFeePerGas = this.toBigNumber(0);
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
const block = await this.web3Provider.getBlock('latest');
|
|
555
|
+
if (block && block.baseFeePerGas) {
|
|
556
|
+
try {
|
|
557
|
+
const ethMaxPriorityFee = await this.web3Provider.send(
|
|
558
|
+
'eth_maxPriorityFeePerGas',
|
|
559
|
+
[]
|
|
560
|
+
);
|
|
561
|
+
maxPriorityFeePerGas = BigNumber.from(ethMaxPriorityFee);
|
|
562
|
+
maxFeePerGas = block.baseFeePerGas.mul(2).add(maxPriorityFeePerGas);
|
|
563
|
+
} catch (e) {
|
|
564
|
+
maxPriorityFeePerGas = BigNumber.from('1500000000');
|
|
565
|
+
maxFeePerGas = block.baseFeePerGas.mul(2).add(maxPriorityFeePerGas);
|
|
566
|
+
}
|
|
567
|
+
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
568
|
+
} else if (block && !block.baseFeePerGas) {
|
|
569
|
+
console.error('Chain doesnt support EIP1559');
|
|
570
|
+
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
571
|
+
} else if (!block) throw new Error('Block not found');
|
|
572
|
+
|
|
573
|
+
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
574
|
+
} catch (error) {
|
|
575
|
+
console.error(error);
|
|
576
|
+
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
calculateNewGasValues = (
|
|
580
|
+
oldTxsParams: IGasParams,
|
|
581
|
+
isForCancel: boolean,
|
|
582
|
+
isLegacy: boolean
|
|
583
|
+
): IGasParams => {
|
|
584
|
+
const newGasValues: IGasParams = {
|
|
585
|
+
maxFeePerGas: undefined,
|
|
586
|
+
maxPriorityFeePerGas: undefined,
|
|
587
|
+
gasPrice: undefined,
|
|
588
|
+
gasLimit: undefined,
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const { maxFeePerGas, maxPriorityFeePerGas, gasLimit, gasPrice } =
|
|
592
|
+
oldTxsParams;
|
|
593
|
+
|
|
594
|
+
const calculateAndConvertNewValue = (feeValue: number) => {
|
|
595
|
+
// Apply multiplier for replacement transaction (cancel or speedup)
|
|
596
|
+
const newValue = feeValue * multiplierToUse;
|
|
597
|
+
|
|
598
|
+
const calculateValue = String(newValue);
|
|
599
|
+
|
|
600
|
+
const convertValueToHex =
|
|
601
|
+
'0x' + parseInt(calculateValue, 10).toString(16);
|
|
602
|
+
|
|
603
|
+
return BigNumber.from(convertValueToHex);
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const maxFeePerGasToNumber = maxFeePerGas?.toNumber();
|
|
607
|
+
const maxPriorityFeePerGasToNumber = maxPriorityFeePerGas?.toNumber();
|
|
608
|
+
const gasLimitToNumber = gasLimit?.toNumber();
|
|
609
|
+
const gasPriceToNumber = gasPrice?.toNumber();
|
|
610
|
+
|
|
611
|
+
const multiplierToUse = 1.2; //The same calculation we used in the edit fee modal, always using the 0.2 multiplier
|
|
612
|
+
|
|
613
|
+
if (!isLegacy) {
|
|
614
|
+
// For EIP-1559 transactions
|
|
615
|
+
newGasValues.maxFeePerGas = calculateAndConvertNewValue(
|
|
616
|
+
maxFeePerGasToNumber as number
|
|
617
|
+
);
|
|
618
|
+
newGasValues.maxPriorityFeePerGas = calculateAndConvertNewValue(
|
|
619
|
+
maxPriorityFeePerGasToNumber as number
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (isLegacy) {
|
|
624
|
+
newGasValues.gasPrice = calculateAndConvertNewValue(
|
|
625
|
+
gasPriceToNumber as number
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (isForCancel) {
|
|
630
|
+
const DEFAULT_GAS_LIMIT_VALUE = '42000';
|
|
631
|
+
|
|
632
|
+
const convertToHex =
|
|
633
|
+
'0x' + parseInt(DEFAULT_GAS_LIMIT_VALUE, 10).toString(16);
|
|
634
|
+
|
|
635
|
+
newGasValues.gasLimit = BigNumber.from(convertToHex);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (!isForCancel) {
|
|
639
|
+
newGasValues.gasLimit = calculateAndConvertNewValue(
|
|
640
|
+
gasLimitToNumber as number
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return newGasValues;
|
|
645
|
+
};
|
|
646
|
+
cancelSentTransaction = async (
|
|
647
|
+
txHash: string,
|
|
648
|
+
isLegacy?: boolean,
|
|
649
|
+
fallbackNonce?: number
|
|
650
|
+
): Promise<{
|
|
651
|
+
error?: boolean;
|
|
652
|
+
isCanceled: boolean;
|
|
653
|
+
transaction?: TransactionResponse;
|
|
654
|
+
}> => {
|
|
655
|
+
const { activeAccountType, activeAccountId, accounts, activeNetwork } =
|
|
656
|
+
this.getState();
|
|
657
|
+
const activeAccount = accounts[activeAccountType][activeAccountId];
|
|
658
|
+
|
|
659
|
+
let tx = (await this.web3Provider.getTransaction(
|
|
660
|
+
txHash
|
|
661
|
+
)) as Deferrable<EthersTransactionResponse>;
|
|
662
|
+
|
|
663
|
+
// If transaction not found, create a minimal tx object with current gas prices
|
|
664
|
+
// This handles cases where tx with 0 gas never made it to the mempool
|
|
665
|
+
if (!tx && fallbackNonce !== undefined) {
|
|
666
|
+
// Fetch current network gas prices for the cancellation
|
|
667
|
+
if (isLegacy) {
|
|
668
|
+
const currentGasPrice = await this.web3Provider.getGasPrice();
|
|
669
|
+
tx = {
|
|
670
|
+
from: activeAccount.address,
|
|
671
|
+
to: activeAccount.address,
|
|
672
|
+
value: Zero,
|
|
673
|
+
nonce: fallbackNonce,
|
|
674
|
+
gasPrice: currentGasPrice,
|
|
675
|
+
gasLimit: BigNumber.from(42000),
|
|
676
|
+
data: '0x',
|
|
677
|
+
} as any;
|
|
678
|
+
} else {
|
|
679
|
+
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas();
|
|
680
|
+
tx = {
|
|
681
|
+
from: activeAccount.address,
|
|
682
|
+
to: activeAccount.address,
|
|
683
|
+
value: Zero,
|
|
684
|
+
nonce: fallbackNonce,
|
|
685
|
+
maxFeePerGas: BigNumber.from(feeData.maxFeePerGas || 0),
|
|
686
|
+
maxPriorityFeePerGas: BigNumber.from(
|
|
687
|
+
feeData.maxPriorityFeePerGas || 0
|
|
688
|
+
),
|
|
689
|
+
gasLimit: BigNumber.from(42000),
|
|
690
|
+
data: '0x',
|
|
691
|
+
} as any;
|
|
692
|
+
}
|
|
693
|
+
} else if (!tx) {
|
|
694
|
+
// No fallback nonce provided and tx not found
|
|
695
|
+
return {
|
|
696
|
+
isCanceled: false,
|
|
697
|
+
error: true,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// If the original tx has 0 or very low gas price, fetch current network gas prices
|
|
702
|
+
const oldTxsGasValues: IGasParams = {
|
|
703
|
+
maxFeePerGas: tx.maxFeePerGas as BigNumber,
|
|
704
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas as BigNumber,
|
|
705
|
+
gasPrice: tx.gasPrice as BigNumber,
|
|
706
|
+
gasLimit: tx.gasLimit as BigNumber,
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// Only fetch current gas prices if the original tx has exactly 0 or undefined gas
|
|
710
|
+
// This avoids unnecessary backend calls for legitimate low-gas networks (L2s, testnets)
|
|
711
|
+
if (isLegacy) {
|
|
712
|
+
if (!oldTxsGasValues.gasPrice || oldTxsGasValues.gasPrice.isZero()) {
|
|
713
|
+
// Fetch current network gas price for replacement
|
|
714
|
+
const currentGasPrice = await this.web3Provider.getGasPrice();
|
|
715
|
+
oldTxsGasValues.gasPrice = currentGasPrice;
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
// For EIP-1559 transactions
|
|
719
|
+
if (
|
|
720
|
+
!oldTxsGasValues.maxFeePerGas ||
|
|
721
|
+
oldTxsGasValues.maxFeePerGas.isZero()
|
|
722
|
+
) {
|
|
723
|
+
// Fetch current network fee data for replacement
|
|
724
|
+
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas();
|
|
725
|
+
oldTxsGasValues.maxFeePerGas = BigNumber.from(
|
|
726
|
+
feeData.maxFeePerGas || 0
|
|
727
|
+
);
|
|
728
|
+
oldTxsGasValues.maxPriorityFeePerGas = BigNumber.from(
|
|
729
|
+
feeData.maxPriorityFeePerGas || 0
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const newGasValues = this.calculateNewGasValues(
|
|
735
|
+
oldTxsGasValues,
|
|
736
|
+
true,
|
|
737
|
+
isLegacy || false
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
// Base cancel transaction parameters (same for all wallet types)
|
|
741
|
+
const baseCancelTx = {
|
|
742
|
+
nonce: tx.nonce,
|
|
743
|
+
from: activeAccount.address,
|
|
744
|
+
to: activeAccount.address,
|
|
745
|
+
value: Zero,
|
|
746
|
+
gasLimit: newGasValues.gasLimit,
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const changedTxToCancel: Deferrable<TransactionRequest> = isLegacy
|
|
750
|
+
? {
|
|
751
|
+
...baseCancelTx,
|
|
752
|
+
gasPrice: newGasValues.gasPrice,
|
|
753
|
+
type: 0, // Force Type 0 for legacy cancel
|
|
754
|
+
}
|
|
755
|
+
: {
|
|
756
|
+
...baseCancelTx,
|
|
757
|
+
maxFeePerGas: newGasValues.maxFeePerGas,
|
|
758
|
+
maxPriorityFeePerGas: newGasValues.maxPriorityFeePerGas,
|
|
759
|
+
// Don't set type - let ethers auto-detect
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// Ledger cancel handler
|
|
763
|
+
const cancelWithLedger = async () => {
|
|
764
|
+
try {
|
|
765
|
+
const resolvedParams = await resolveProperties(
|
|
766
|
+
omit(changedTxToCancel, 'from')
|
|
767
|
+
);
|
|
768
|
+
const formatParams = {
|
|
769
|
+
...resolvedParams,
|
|
770
|
+
nonce: resolvedParams.nonce
|
|
771
|
+
? Number(resolvedParams.nonce.toString())
|
|
772
|
+
: undefined,
|
|
773
|
+
};
|
|
774
|
+
const txFormattedForEthers = isLegacy
|
|
775
|
+
? {
|
|
776
|
+
...formatParams,
|
|
777
|
+
chainId: activeNetwork.chainId,
|
|
778
|
+
type: 0, // Need explicit type for hardware wallet serialization
|
|
779
|
+
}
|
|
780
|
+
: {
|
|
781
|
+
...formatParams,
|
|
782
|
+
chainId: activeNetwork.chainId,
|
|
783
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
787
|
+
const signature = await this.ledgerSigner.evm.signEVMTransaction({
|
|
788
|
+
rawTx: rawTx.replace('0x', ''),
|
|
789
|
+
accountIndex: activeAccountId,
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const formattedSignature = {
|
|
793
|
+
r: `0x${signature.r}`,
|
|
794
|
+
s: `0x${signature.s}`,
|
|
795
|
+
v: parseInt(signature.v, 16),
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
if (signature) {
|
|
799
|
+
const signedTx = serializeTransaction(
|
|
800
|
+
txFormattedForEthers,
|
|
801
|
+
formattedSignature
|
|
802
|
+
);
|
|
803
|
+
const transactionResponse = await this.web3Provider.sendTransaction(
|
|
804
|
+
signedTx
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
return {
|
|
808
|
+
isCanceled: true,
|
|
809
|
+
transaction: transactionResponse,
|
|
810
|
+
};
|
|
811
|
+
} else {
|
|
812
|
+
return {
|
|
813
|
+
isCanceled: false,
|
|
814
|
+
error: true,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
} catch (error) {
|
|
818
|
+
return {
|
|
819
|
+
isCanceled: false,
|
|
820
|
+
error: true,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
// Trezor cancel handler
|
|
826
|
+
const cancelWithTrezor = async () => {
|
|
827
|
+
try {
|
|
828
|
+
const trezorCoin =
|
|
829
|
+
activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
830
|
+
const formattedTx = await resolveProperties(
|
|
831
|
+
omit(changedTxToCancel, 'from')
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
const txFormattedForTrezor: any = {
|
|
835
|
+
...formattedTx,
|
|
836
|
+
gasLimit:
|
|
837
|
+
typeof formattedTx.gasLimit === 'string'
|
|
838
|
+
? formattedTx.gasLimit
|
|
839
|
+
: this.toBigNumber(String(formattedTx.gasLimit || 0))._hex,
|
|
840
|
+
value: '0x0',
|
|
841
|
+
nonce: this.toBigNumber(String(formattedTx.nonce || 0))._hex,
|
|
842
|
+
chainId: activeNetwork.chainId,
|
|
843
|
+
type: isLegacy ? 0 : 2, // Need explicit type for hardware wallet serialization
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
if (isLegacy) {
|
|
847
|
+
txFormattedForTrezor.gasPrice =
|
|
848
|
+
typeof formattedTx.gasPrice === 'string'
|
|
849
|
+
? formattedTx.gasPrice
|
|
850
|
+
: this.toBigNumber(String(formattedTx.gasPrice || 0))._hex;
|
|
851
|
+
} else {
|
|
852
|
+
txFormattedForTrezor.maxFeePerGas =
|
|
853
|
+
typeof (formattedTx as any).maxFeePerGas === 'string'
|
|
854
|
+
? (formattedTx as any).maxFeePerGas
|
|
855
|
+
: `${
|
|
856
|
+
(formattedTx as any).maxFeePerGas?._hex ||
|
|
857
|
+
(formattedTx as any).maxFeePerGas
|
|
858
|
+
}`;
|
|
859
|
+
txFormattedForTrezor.maxPriorityFeePerGas =
|
|
860
|
+
typeof (formattedTx as any).maxPriorityFeePerGas === 'string'
|
|
861
|
+
? (formattedTx as any).maxPriorityFeePerGas
|
|
862
|
+
: `${
|
|
863
|
+
(formattedTx as any).maxPriorityFeePerGas?._hex ||
|
|
864
|
+
(formattedTx as any).maxPriorityFeePerGas
|
|
865
|
+
}`;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const signature = await this.trezorSigner.signEthTransaction({
|
|
869
|
+
coin: trezorCoin,
|
|
870
|
+
tx: txFormattedForTrezor,
|
|
871
|
+
index: activeAccountId.toString(),
|
|
872
|
+
slip44: activeNetwork.slip44,
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
if (signature.success) {
|
|
876
|
+
const signedTx = serializeTransaction(
|
|
877
|
+
txFormattedForTrezor,
|
|
878
|
+
signature.payload
|
|
879
|
+
);
|
|
880
|
+
const transactionResponse = await this.web3Provider.sendTransaction(
|
|
881
|
+
signedTx
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
return {
|
|
885
|
+
isCanceled: true,
|
|
886
|
+
transaction: transactionResponse,
|
|
887
|
+
};
|
|
888
|
+
} else {
|
|
889
|
+
return {
|
|
890
|
+
isCanceled: false,
|
|
891
|
+
error: true,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
} catch (error) {
|
|
895
|
+
return {
|
|
896
|
+
isCanceled: false,
|
|
897
|
+
error: true,
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// Regular wallet cancel handler
|
|
903
|
+
const cancelWithPrivateKey = async () => {
|
|
904
|
+
try {
|
|
905
|
+
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
906
|
+
const wallet = new Wallet(decryptedPrivateKey, this.web3Provider);
|
|
907
|
+
|
|
908
|
+
const transactionResponse = await wallet.sendTransaction(
|
|
909
|
+
changedTxToCancel
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
if (transactionResponse) {
|
|
913
|
+
return {
|
|
914
|
+
isCanceled: true,
|
|
915
|
+
transaction: transactionResponse,
|
|
916
|
+
};
|
|
917
|
+
} else {
|
|
918
|
+
return {
|
|
919
|
+
isCanceled: false,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
} catch (error) {
|
|
923
|
+
return {
|
|
924
|
+
isCanceled: false,
|
|
925
|
+
error: true,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
// Route based on account type
|
|
931
|
+
switch (activeAccountType) {
|
|
932
|
+
case KeyringAccountType.Trezor:
|
|
933
|
+
return await cancelWithTrezor();
|
|
934
|
+
case KeyringAccountType.Ledger:
|
|
935
|
+
return await cancelWithLedger();
|
|
936
|
+
default:
|
|
937
|
+
return await cancelWithPrivateKey();
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
//TODO: This function needs to be refactored
|
|
941
|
+
sendFormattedTransaction = async (
|
|
942
|
+
params: SimpleTransactionRequest,
|
|
943
|
+
isLegacy?: boolean
|
|
944
|
+
) => {
|
|
945
|
+
const { activeAccountType, activeAccountId, accounts, activeNetwork } =
|
|
946
|
+
this.getState();
|
|
947
|
+
const activeAccount = accounts[activeAccountType][activeAccountId];
|
|
948
|
+
|
|
949
|
+
const sendEVMLedgerTransaction = async () => {
|
|
950
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
951
|
+
activeAccount.address
|
|
952
|
+
);
|
|
953
|
+
const formatParams = omit(params, 'from'); //From is not needed we're already passing in the HD derivation path so it can be inferred
|
|
954
|
+
const txFormattedForEthers = isLegacy
|
|
955
|
+
? {
|
|
956
|
+
...formatParams,
|
|
957
|
+
nonce: transactionNonce,
|
|
958
|
+
chainId: activeNetwork.chainId,
|
|
959
|
+
type: 0, // Force Type 0 for legacy
|
|
960
|
+
}
|
|
961
|
+
: {
|
|
962
|
+
...formatParams,
|
|
963
|
+
nonce: transactionNonce,
|
|
964
|
+
chainId: activeNetwork.chainId,
|
|
965
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
966
|
+
};
|
|
967
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
968
|
+
|
|
969
|
+
const signature = await this.ledgerSigner.evm.signEVMTransaction({
|
|
970
|
+
rawTx: rawTx.replace('0x', ''),
|
|
971
|
+
accountIndex: activeAccountId,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
const formattedSignature = {
|
|
975
|
+
r: `0x${signature.r}`,
|
|
976
|
+
s: `0x${signature.s}`,
|
|
977
|
+
v: parseInt(signature.v, 16),
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
if (signature) {
|
|
981
|
+
try {
|
|
982
|
+
const signedTx = serializeTransaction(
|
|
983
|
+
txFormattedForEthers,
|
|
984
|
+
formattedSignature
|
|
985
|
+
);
|
|
986
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
987
|
+
|
|
988
|
+
return finalTx;
|
|
989
|
+
} catch (error) {
|
|
990
|
+
throw error;
|
|
991
|
+
}
|
|
992
|
+
} else {
|
|
993
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
const sendEVMTrezorTransaction = async () => {
|
|
998
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
999
|
+
activeAccount.address
|
|
1000
|
+
);
|
|
1001
|
+
let txFormattedForTrezor = {};
|
|
1002
|
+
const formatParams = omit(params, 'from'); //From is not needed we're already passing in the HD derivation path so it can be inferred
|
|
1003
|
+
switch (isLegacy) {
|
|
1004
|
+
case true:
|
|
1005
|
+
txFormattedForTrezor = {
|
|
1006
|
+
...formatParams,
|
|
1007
|
+
gasLimit:
|
|
1008
|
+
typeof formatParams.gasLimit === 'string'
|
|
1009
|
+
? formatParams.gasLimit
|
|
1010
|
+
: // @ts-ignore
|
|
1011
|
+
`${params.gasLimit.hex}`,
|
|
1012
|
+
value:
|
|
1013
|
+
typeof formatParams.value === 'string' ||
|
|
1014
|
+
typeof formatParams.value === 'number'
|
|
1015
|
+
? `${formatParams.value}`
|
|
1016
|
+
: // @ts-ignore
|
|
1017
|
+
`${params.value.hex}`,
|
|
1018
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
1019
|
+
chainId: activeNetwork.chainId,
|
|
1020
|
+
};
|
|
1021
|
+
break;
|
|
1022
|
+
case false:
|
|
1023
|
+
txFormattedForTrezor = {
|
|
1024
|
+
...formatParams,
|
|
1025
|
+
gasLimit:
|
|
1026
|
+
typeof formatParams.gasLimit === 'string'
|
|
1027
|
+
? formatParams.gasLimit
|
|
1028
|
+
: // @ts-ignore
|
|
1029
|
+
`${params.gasLimit.hex}`,
|
|
1030
|
+
maxFeePerGas:
|
|
1031
|
+
typeof formatParams.maxFeePerGas === 'string'
|
|
1032
|
+
? formatParams.maxFeePerGas
|
|
1033
|
+
: // @ts-ignore
|
|
1034
|
+
`${params.maxFeePerGas.hex}`,
|
|
1035
|
+
maxPriorityFeePerGas:
|
|
1036
|
+
typeof formatParams.maxPriorityFeePerGas === 'string'
|
|
1037
|
+
? formatParams.maxPriorityFeePerGas
|
|
1038
|
+
: // @ts-ignore
|
|
1039
|
+
`${params.maxPriorityFeePerGas.hex}`,
|
|
1040
|
+
value:
|
|
1041
|
+
typeof formatParams.value === 'string' ||
|
|
1042
|
+
typeof formatParams.value === 'number'
|
|
1043
|
+
? `${formatParams.value}`
|
|
1044
|
+
: // @ts-ignore
|
|
1045
|
+
`${params.value.hex}`,
|
|
1046
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
1047
|
+
chainId: activeNetwork.chainId,
|
|
1048
|
+
};
|
|
1049
|
+
break;
|
|
1050
|
+
default:
|
|
1051
|
+
txFormattedForTrezor = {
|
|
1052
|
+
...formatParams,
|
|
1053
|
+
gasLimit:
|
|
1054
|
+
typeof formatParams.gasLimit === 'string'
|
|
1055
|
+
? formatParams.gasLimit
|
|
1056
|
+
: // @ts-ignore
|
|
1057
|
+
`${params.gasLimit.hex}`,
|
|
1058
|
+
maxFeePerGas:
|
|
1059
|
+
typeof formatParams.maxFeePerGas === 'string'
|
|
1060
|
+
? formatParams.maxFeePerGas
|
|
1061
|
+
: // @ts-ignore
|
|
1062
|
+
`${params.maxFeePerGas.hex}`,
|
|
1063
|
+
maxPriorityFeePerGas:
|
|
1064
|
+
typeof formatParams.maxPriorityFeePerGas === 'string'
|
|
1065
|
+
? formatParams.maxPriorityFeePerGas
|
|
1066
|
+
: // @ts-ignore
|
|
1067
|
+
`${params.maxPriorityFeePerGas.hex}`,
|
|
1068
|
+
value:
|
|
1069
|
+
typeof formatParams.value === 'string' ||
|
|
1070
|
+
typeof formatParams.value === 'number'
|
|
1071
|
+
? `${formatParams.value}`
|
|
1072
|
+
: // @ts-ignore
|
|
1073
|
+
`${params.value.hex}`,
|
|
1074
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
1075
|
+
chainId: activeNetwork.chainId,
|
|
1076
|
+
};
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const signature = await this.trezorSigner.signEthTransaction({
|
|
1081
|
+
index: `${activeAccountId}`,
|
|
1082
|
+
tx: txFormattedForTrezor as EthereumTransactionEIP1559,
|
|
1083
|
+
coin: activeNetwork.currency,
|
|
1084
|
+
slip44: activeNetwork.slip44,
|
|
1085
|
+
});
|
|
1086
|
+
if (signature.success) {
|
|
1087
|
+
try {
|
|
1088
|
+
const txFormattedForEthers = isLegacy
|
|
1089
|
+
? {
|
|
1090
|
+
...formatParams,
|
|
1091
|
+
nonce: transactionNonce,
|
|
1092
|
+
chainId: activeNetwork.chainId,
|
|
1093
|
+
type: 0, // Force Type 0 for legacy
|
|
1094
|
+
}
|
|
1095
|
+
: {
|
|
1096
|
+
...formatParams,
|
|
1097
|
+
nonce: transactionNonce,
|
|
1098
|
+
chainId: activeNetwork.chainId,
|
|
1099
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
1100
|
+
};
|
|
1101
|
+
signature.payload.v = parseInt(signature.payload.v, 16); //v parameter must be a number by ethers standards
|
|
1102
|
+
const signedTx = serializeTransaction(
|
|
1103
|
+
txFormattedForEthers,
|
|
1104
|
+
signature.payload
|
|
1105
|
+
);
|
|
1106
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
1107
|
+
|
|
1108
|
+
return finalTx;
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
throw error;
|
|
1111
|
+
}
|
|
1112
|
+
} else {
|
|
1113
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
const sendEVMTransaction = async () => {
|
|
1118
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1119
|
+
|
|
1120
|
+
// Validate that we have the correct private key for the active account to prevent race conditions
|
|
1121
|
+
// This is critical for transaction security during account switches
|
|
1122
|
+
if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
`Account state mismatch detected during transaction. Expected ${activeAccount.address} but got ${address}. Please wait for account switching to complete and try again.`
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Explicitly set transaction type based on isLegacy flag
|
|
1129
|
+
const tx: Deferrable<TransactionRequest> = isLegacy
|
|
1130
|
+
? { ...params, type: 0 } // Force Type 0 for legacy transactions
|
|
1131
|
+
: params; // Let ethers auto-detect for EIP-1559
|
|
1132
|
+
|
|
1133
|
+
const wallet = new Wallet(decryptedPrivateKey, this.web3Provider);
|
|
1134
|
+
try {
|
|
1135
|
+
const transaction = await wallet.sendTransaction(tx);
|
|
1136
|
+
const response = await this.web3Provider.getTransaction(
|
|
1137
|
+
transaction.hash
|
|
1138
|
+
);
|
|
1139
|
+
//TODO: more precisely on this lines
|
|
1140
|
+
if (!response) {
|
|
1141
|
+
return await this.getTransactionTimestamp(transaction);
|
|
1142
|
+
} else {
|
|
1143
|
+
return await this.getTransactionTimestamp(response);
|
|
1144
|
+
}
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
throw error;
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
switch (activeAccountType) {
|
|
1150
|
+
case KeyringAccountType.Trezor:
|
|
1151
|
+
return await sendEVMTrezorTransaction();
|
|
1152
|
+
case KeyringAccountType.Ledger:
|
|
1153
|
+
return await sendEVMLedgerTransaction();
|
|
1154
|
+
default:
|
|
1155
|
+
return await sendEVMTransaction();
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
sendTransactionWithEditedFee = async (
|
|
1159
|
+
txHash: string,
|
|
1160
|
+
isLegacy?: boolean
|
|
1161
|
+
): Promise<{
|
|
1162
|
+
error?: boolean;
|
|
1163
|
+
isSpeedUp: boolean;
|
|
1164
|
+
transaction?: TransactionResponse;
|
|
1165
|
+
}> => {
|
|
1166
|
+
let tx = (await this.web3Provider.getTransaction(
|
|
1167
|
+
txHash
|
|
1168
|
+
)) as Deferrable<EthersTransactionResponse>;
|
|
1169
|
+
|
|
1170
|
+
if (!tx) {
|
|
1171
|
+
// Retry a couple of times in case the node hasn't indexed the pending tx yet
|
|
1172
|
+
for (let attempt = 0; attempt < 2 && !tx; attempt++) {
|
|
1173
|
+
await new Promise((resolve) =>
|
|
1174
|
+
setTimeout(resolve, 500 * (attempt + 1))
|
|
1175
|
+
);
|
|
1176
|
+
tx = (await this.web3Provider.getTransaction(
|
|
1177
|
+
txHash
|
|
1178
|
+
)) as Deferrable<EthersTransactionResponse>;
|
|
1179
|
+
}
|
|
1180
|
+
if (!tx) {
|
|
1181
|
+
return {
|
|
1182
|
+
isSpeedUp: false,
|
|
1183
|
+
error: true,
|
|
1184
|
+
code: 'TX_NOT_FOUND',
|
|
1185
|
+
message: 'Original transaction not yet available from RPC provider',
|
|
1186
|
+
} as any;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const { activeAccountType, activeAccountId, accounts, activeNetwork } =
|
|
1191
|
+
this.getState();
|
|
1192
|
+
const activeAccount = accounts[activeAccountType][activeAccountId];
|
|
1193
|
+
|
|
1194
|
+
// Check if this might be a max send transaction by comparing total cost to balance
|
|
1195
|
+
const currentBalance = await this.web3Provider.getBalance(
|
|
1196
|
+
activeAccount.address
|
|
1197
|
+
);
|
|
1198
|
+
|
|
1199
|
+
// Ensure all transaction values are resolved from promises
|
|
1200
|
+
const gasLimit = await Promise.resolve(tx.gasLimit);
|
|
1201
|
+
const gasPrice = await Promise.resolve(tx.gasPrice || 0);
|
|
1202
|
+
const maxFeePerGas = await Promise.resolve(tx.maxFeePerGas || 0);
|
|
1203
|
+
const maxPriorityFeePerGas = await Promise.resolve(
|
|
1204
|
+
tx.maxPriorityFeePerGas || 0
|
|
1205
|
+
);
|
|
1206
|
+
const txValue = await Promise.resolve(tx.value);
|
|
1207
|
+
const txData = await Promise.resolve(tx.data || '0x');
|
|
1208
|
+
|
|
1209
|
+
// Check if this is a contract call (has data)
|
|
1210
|
+
const isContractCall = txData && txData !== '0x' && txData.length > 2;
|
|
1211
|
+
|
|
1212
|
+
const originalGasCost = isLegacy
|
|
1213
|
+
? gasLimit.mul(gasPrice || 0)
|
|
1214
|
+
: gasLimit.mul(maxFeePerGas || 0);
|
|
1215
|
+
const originalTotalCost = txValue.add(originalGasCost);
|
|
1216
|
+
|
|
1217
|
+
// If original transaction used >95% of balance, it's likely a max send
|
|
1218
|
+
const balanceThreshold = currentBalance.mul(95).div(100);
|
|
1219
|
+
const isLikelyMaxSend = originalTotalCost.gt(balanceThreshold);
|
|
1220
|
+
|
|
1221
|
+
let txWithEditedFee: Deferrable<TransactionRequest>;
|
|
1222
|
+
|
|
1223
|
+
// If the original tx has 0 or very low gas price, fetch current network gas prices
|
|
1224
|
+
const oldTxsGasValues: IGasParams = {
|
|
1225
|
+
maxFeePerGas: maxFeePerGas as BigNumber,
|
|
1226
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas as BigNumber,
|
|
1227
|
+
gasPrice: gasPrice as BigNumber,
|
|
1228
|
+
gasLimit: gasLimit as BigNumber,
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
// Only fetch current gas prices if the original tx has exactly 0 or undefined gas
|
|
1232
|
+
// This avoids unnecessary backend calls for legitimate low-gas networks (L2s, testnets)
|
|
1233
|
+
if (isLegacy) {
|
|
1234
|
+
if (!oldTxsGasValues.gasPrice || oldTxsGasValues.gasPrice.isZero()) {
|
|
1235
|
+
// Fetch current network gas price for replacement
|
|
1236
|
+
const currentGasPrice = await this.web3Provider.getGasPrice();
|
|
1237
|
+
oldTxsGasValues.gasPrice = currentGasPrice;
|
|
1238
|
+
}
|
|
1239
|
+
} else {
|
|
1240
|
+
// For EIP-1559 transactions
|
|
1241
|
+
if (
|
|
1242
|
+
!oldTxsGasValues.maxFeePerGas ||
|
|
1243
|
+
oldTxsGasValues.maxFeePerGas.isZero()
|
|
1244
|
+
) {
|
|
1245
|
+
// Fetch current network fee data for replacement
|
|
1246
|
+
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas();
|
|
1247
|
+
oldTxsGasValues.maxFeePerGas = BigNumber.from(
|
|
1248
|
+
feeData.maxFeePerGas || 0
|
|
1249
|
+
);
|
|
1250
|
+
oldTxsGasValues.maxPriorityFeePerGas = BigNumber.from(
|
|
1251
|
+
feeData.maxPriorityFeePerGas || 0
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (!isLegacy) {
|
|
1257
|
+
const newGasValues = this.calculateNewGasValues(
|
|
1258
|
+
oldTxsGasValues,
|
|
1259
|
+
false,
|
|
1260
|
+
false
|
|
1261
|
+
);
|
|
1262
|
+
|
|
1263
|
+
let adjustedValue = txValue;
|
|
1264
|
+
|
|
1265
|
+
// For likely max sends, check if we need to adjust value
|
|
1266
|
+
if (
|
|
1267
|
+
isLikelyMaxSend &&
|
|
1268
|
+
newGasValues.gasLimit &&
|
|
1269
|
+
newGasValues.maxFeePerGas
|
|
1270
|
+
) {
|
|
1271
|
+
const newGasCost = newGasValues.gasLimit.mul(newGasValues.maxFeePerGas);
|
|
1272
|
+
const newTotalCost = txValue.add(newGasCost);
|
|
1273
|
+
|
|
1274
|
+
if (newTotalCost.gt(currentBalance)) {
|
|
1275
|
+
// If this is a contract call, we cannot adjust the value
|
|
1276
|
+
if (isContractCall) {
|
|
1277
|
+
console.error(
|
|
1278
|
+
'[SpeedUp] Cannot adjust value for contract call - rejecting speedup'
|
|
1279
|
+
);
|
|
1280
|
+
return {
|
|
1281
|
+
isSpeedUp: false,
|
|
1282
|
+
error: true,
|
|
1283
|
+
code: 'CONTRACT_CALL_MAX_SEND',
|
|
1284
|
+
message:
|
|
1285
|
+
'Cannot speed up a likely max-send contract call; value cannot be adjusted to fit new gas',
|
|
1286
|
+
} as any;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// For non-contract calls, reduce value to fit within balance (clamp at zero)
|
|
1290
|
+
adjustedValue = currentBalance.gt(newGasCost)
|
|
1291
|
+
? currentBalance.sub(newGasCost)
|
|
1292
|
+
: Zero;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
txWithEditedFee = {
|
|
1297
|
+
from: tx.from,
|
|
1298
|
+
to: tx.to,
|
|
1299
|
+
nonce: tx.nonce,
|
|
1300
|
+
value: adjustedValue,
|
|
1301
|
+
data: txData,
|
|
1302
|
+
maxFeePerGas: newGasValues.maxFeePerGas,
|
|
1303
|
+
maxPriorityFeePerGas: newGasValues.maxPriorityFeePerGas,
|
|
1304
|
+
gasLimit: newGasValues.gasLimit,
|
|
1305
|
+
// Don't set type - let ethers auto-detect for EIP-1559
|
|
1306
|
+
};
|
|
1307
|
+
} else {
|
|
1308
|
+
const newGasValues = this.calculateNewGasValues(
|
|
1309
|
+
oldTxsGasValues,
|
|
1310
|
+
false,
|
|
1311
|
+
true
|
|
1312
|
+
);
|
|
1313
|
+
|
|
1314
|
+
let adjustedValue = txValue;
|
|
1315
|
+
|
|
1316
|
+
// For likely max sends, check if we need to adjust value
|
|
1317
|
+
if (isLikelyMaxSend && newGasValues.gasLimit && newGasValues.gasPrice) {
|
|
1318
|
+
const newGasCost = newGasValues.gasLimit.mul(newGasValues.gasPrice);
|
|
1319
|
+
const newTotalCost = txValue.add(newGasCost);
|
|
1320
|
+
|
|
1321
|
+
if (newTotalCost.gt(currentBalance)) {
|
|
1322
|
+
// If this is a contract call, we cannot adjust the value
|
|
1323
|
+
if (isContractCall) {
|
|
1324
|
+
console.error(
|
|
1325
|
+
'[SpeedUp] Cannot adjust value for contract call - rejecting speedup'
|
|
1326
|
+
);
|
|
1327
|
+
return {
|
|
1328
|
+
isSpeedUp: false,
|
|
1329
|
+
error: true,
|
|
1330
|
+
code: 'CONTRACT_CALL_MAX_SEND',
|
|
1331
|
+
message:
|
|
1332
|
+
'Cannot speed up a likely max-send contract call; value cannot be adjusted to fit new gas',
|
|
1333
|
+
} as any;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// For non-contract calls, reduce value to fit within balance (clamp at zero)
|
|
1337
|
+
adjustedValue = currentBalance.gt(newGasCost)
|
|
1338
|
+
? currentBalance.sub(newGasCost)
|
|
1339
|
+
: Zero;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
txWithEditedFee = {
|
|
1344
|
+
from: tx.from,
|
|
1345
|
+
to: tx.to,
|
|
1346
|
+
nonce: tx.nonce,
|
|
1347
|
+
value: adjustedValue,
|
|
1348
|
+
data: txData,
|
|
1349
|
+
gasLimit: newGasValues.gasLimit,
|
|
1350
|
+
gasPrice: newGasValues.gasPrice,
|
|
1351
|
+
type: 0, // Force Type 0 for legacy speedup
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Ledger speedup handler
|
|
1356
|
+
const speedUpWithLedger = async () => {
|
|
1357
|
+
try {
|
|
1358
|
+
const resolvedParams = await resolveProperties(
|
|
1359
|
+
omit(txWithEditedFee, 'from')
|
|
1360
|
+
);
|
|
1361
|
+
const formatParams = {
|
|
1362
|
+
...resolvedParams,
|
|
1363
|
+
nonce: resolvedParams.nonce
|
|
1364
|
+
? Number(resolvedParams.nonce.toString())
|
|
1365
|
+
: undefined,
|
|
1366
|
+
};
|
|
1367
|
+
const txFormattedForEthers = isLegacy
|
|
1368
|
+
? {
|
|
1369
|
+
...formatParams,
|
|
1370
|
+
chainId: activeNetwork.chainId,
|
|
1371
|
+
type: 0, // Need explicit type for hardware wallet serialization
|
|
1372
|
+
}
|
|
1373
|
+
: {
|
|
1374
|
+
...formatParams,
|
|
1375
|
+
chainId: activeNetwork.chainId,
|
|
1376
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1380
|
+
const signature = await this.ledgerSigner.evm.signEVMTransaction({
|
|
1381
|
+
rawTx: rawTx.replace('0x', ''),
|
|
1382
|
+
accountIndex: activeAccountId,
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
const formattedSignature = {
|
|
1386
|
+
r: `0x${signature.r}`,
|
|
1387
|
+
s: `0x${signature.s}`,
|
|
1388
|
+
v: parseInt(signature.v, 16),
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
if (signature) {
|
|
1392
|
+
const signedTx = serializeTransaction(
|
|
1393
|
+
txFormattedForEthers,
|
|
1394
|
+
formattedSignature
|
|
1395
|
+
);
|
|
1396
|
+
const transactionResponse = await this.web3Provider.sendTransaction(
|
|
1397
|
+
signedTx
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
return {
|
|
1401
|
+
isSpeedUp: true,
|
|
1402
|
+
transaction: transactionResponse,
|
|
1403
|
+
};
|
|
1404
|
+
} else {
|
|
1405
|
+
return {
|
|
1406
|
+
isSpeedUp: false,
|
|
1407
|
+
error: true,
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
console.error(
|
|
1412
|
+
'[SpeedUp] Failed to send replacement transaction with Ledger:',
|
|
1413
|
+
error
|
|
1414
|
+
);
|
|
1415
|
+
const message = (error as any)?.message || String(error);
|
|
1416
|
+
const lower = message.toLowerCase();
|
|
1417
|
+
const code = lower.includes('underpriced')
|
|
1418
|
+
? 'REPLACEMENT_UNDERPRICED'
|
|
1419
|
+
: lower.includes('known transaction') ||
|
|
1420
|
+
lower.includes('already known')
|
|
1421
|
+
? 'REPLACEMENT_ALREADY_KNOWN'
|
|
1422
|
+
: 'REPLACEMENT_SEND_FAILED';
|
|
1423
|
+
return {
|
|
1424
|
+
isSpeedUp: false,
|
|
1425
|
+
error: true,
|
|
1426
|
+
code,
|
|
1427
|
+
message,
|
|
1428
|
+
} as any;
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
// Trezor speedup handler
|
|
1433
|
+
const speedUpWithTrezor = async () => {
|
|
1434
|
+
try {
|
|
1435
|
+
const trezorCoin =
|
|
1436
|
+
activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
1437
|
+
const formattedTx = await resolveProperties(
|
|
1438
|
+
omit(txWithEditedFee, 'from')
|
|
1439
|
+
);
|
|
1440
|
+
|
|
1441
|
+
const txFormattedForTrezor: any = {
|
|
1442
|
+
...formattedTx,
|
|
1443
|
+
gasLimit:
|
|
1444
|
+
typeof formattedTx.gasLimit === 'string'
|
|
1445
|
+
? formattedTx.gasLimit
|
|
1446
|
+
: this.toBigNumber(String(formattedTx.gasLimit || 0))._hex,
|
|
1447
|
+
value:
|
|
1448
|
+
typeof formattedTx.value === 'string'
|
|
1449
|
+
? formattedTx.value
|
|
1450
|
+
: this.toBigNumber(String(formattedTx.value || 0))._hex,
|
|
1451
|
+
nonce: this.toBigNumber(String(formattedTx.nonce || 0))._hex,
|
|
1452
|
+
chainId: activeNetwork.chainId,
|
|
1453
|
+
type: isLegacy ? 0 : 2, // Need explicit type for hardware wallet serialization
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
if (formattedTx.data && formattedTx.data !== '0x') {
|
|
1457
|
+
txFormattedForTrezor.data = formattedTx.data;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (isLegacy) {
|
|
1461
|
+
txFormattedForTrezor.gasPrice =
|
|
1462
|
+
typeof formattedTx.gasPrice === 'string'
|
|
1463
|
+
? formattedTx.gasPrice
|
|
1464
|
+
: this.toBigNumber(String(formattedTx.gasPrice || 0))._hex;
|
|
1465
|
+
} else {
|
|
1466
|
+
txFormattedForTrezor.maxFeePerGas =
|
|
1467
|
+
typeof (formattedTx as any).maxFeePerGas === 'string'
|
|
1468
|
+
? (formattedTx as any).maxFeePerGas
|
|
1469
|
+
: `${
|
|
1470
|
+
(formattedTx as any).maxFeePerGas?._hex ||
|
|
1471
|
+
(formattedTx as any).maxFeePerGas
|
|
1472
|
+
}`;
|
|
1473
|
+
txFormattedForTrezor.maxPriorityFeePerGas =
|
|
1474
|
+
typeof (formattedTx as any).maxPriorityFeePerGas === 'string'
|
|
1475
|
+
? (formattedTx as any).maxPriorityFeePerGas
|
|
1476
|
+
: `${
|
|
1477
|
+
(formattedTx as any).maxPriorityFeePerGas?._hex ||
|
|
1478
|
+
(formattedTx as any).maxPriorityFeePerGas
|
|
1479
|
+
}`;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const signature = await this.trezorSigner.signEthTransaction({
|
|
1483
|
+
coin: trezorCoin,
|
|
1484
|
+
tx: txFormattedForTrezor,
|
|
1485
|
+
index: activeAccountId.toString(),
|
|
1486
|
+
slip44: activeNetwork.slip44,
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
if (signature.success) {
|
|
1490
|
+
const signedTx = serializeTransaction(
|
|
1491
|
+
txFormattedForTrezor,
|
|
1492
|
+
signature.payload
|
|
1493
|
+
);
|
|
1494
|
+
const transactionResponse = await this.web3Provider.sendTransaction(
|
|
1495
|
+
signedTx
|
|
1496
|
+
);
|
|
1497
|
+
|
|
1498
|
+
return {
|
|
1499
|
+
isSpeedUp: true,
|
|
1500
|
+
transaction: transactionResponse,
|
|
1501
|
+
};
|
|
1502
|
+
} else {
|
|
1503
|
+
return {
|
|
1504
|
+
isSpeedUp: false,
|
|
1505
|
+
error: true,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
console.error(
|
|
1510
|
+
'[SpeedUp] Failed to send replacement transaction with Trezor:',
|
|
1511
|
+
error
|
|
1512
|
+
);
|
|
1513
|
+
const message = (error as any)?.message || String(error);
|
|
1514
|
+
const lower = message.toLowerCase();
|
|
1515
|
+
const code = lower.includes('underpriced')
|
|
1516
|
+
? 'REPLACEMENT_UNDERPRICED'
|
|
1517
|
+
: lower.includes('known transaction') ||
|
|
1518
|
+
lower.includes('already known')
|
|
1519
|
+
? 'REPLACEMENT_ALREADY_KNOWN'
|
|
1520
|
+
: 'REPLACEMENT_SEND_FAILED';
|
|
1521
|
+
return {
|
|
1522
|
+
isSpeedUp: false,
|
|
1523
|
+
error: true,
|
|
1524
|
+
code,
|
|
1525
|
+
message,
|
|
1526
|
+
} as any;
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
// Regular wallet speedup handler
|
|
1531
|
+
const speedUpWithPrivateKey = async () => {
|
|
1532
|
+
try {
|
|
1533
|
+
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1534
|
+
const wallet = new Wallet(decryptedPrivateKey, this.web3Provider);
|
|
1535
|
+
|
|
1536
|
+
// Type already set in txWithEditedFee
|
|
1537
|
+
const transactionResponse = await wallet.sendTransaction(
|
|
1538
|
+
txWithEditedFee
|
|
1539
|
+
);
|
|
1540
|
+
|
|
1541
|
+
if (transactionResponse) {
|
|
1542
|
+
return {
|
|
1543
|
+
isSpeedUp: true,
|
|
1544
|
+
transaction: transactionResponse,
|
|
1545
|
+
};
|
|
1546
|
+
} else {
|
|
1547
|
+
return {
|
|
1548
|
+
isSpeedUp: false,
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
console.error(
|
|
1553
|
+
'[SpeedUp] Failed to send replacement transaction:',
|
|
1554
|
+
error
|
|
1555
|
+
);
|
|
1556
|
+
const message = (error as any)?.message || String(error);
|
|
1557
|
+
const lower = message.toLowerCase();
|
|
1558
|
+
const code = lower.includes('underpriced')
|
|
1559
|
+
? 'REPLACEMENT_UNDERPRICED'
|
|
1560
|
+
: lower.includes('known transaction') ||
|
|
1561
|
+
lower.includes('already known')
|
|
1562
|
+
? 'REPLACEMENT_ALREADY_KNOWN'
|
|
1563
|
+
: lower.includes('insufficient funds')
|
|
1564
|
+
? 'INSUFFICIENT_FUNDS_REPLACEMENT'
|
|
1565
|
+
: 'REPLACEMENT_SEND_FAILED';
|
|
1566
|
+
return {
|
|
1567
|
+
isSpeedUp: false,
|
|
1568
|
+
error: true,
|
|
1569
|
+
code,
|
|
1570
|
+
message,
|
|
1571
|
+
} as any;
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
// Route based on account type
|
|
1576
|
+
switch (activeAccountType) {
|
|
1577
|
+
case KeyringAccountType.Trezor:
|
|
1578
|
+
return await speedUpWithTrezor();
|
|
1579
|
+
case KeyringAccountType.Ledger:
|
|
1580
|
+
return await speedUpWithLedger();
|
|
1581
|
+
default:
|
|
1582
|
+
return await speedUpWithPrivateKey();
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
sendSignedErc20Transaction = async ({
|
|
1586
|
+
receiver,
|
|
1587
|
+
tokenAddress,
|
|
1588
|
+
tokenAmount,
|
|
1589
|
+
isLegacy = false,
|
|
1590
|
+
maxPriorityFeePerGas,
|
|
1591
|
+
maxFeePerGas,
|
|
1592
|
+
gasPrice,
|
|
1593
|
+
decimals,
|
|
1594
|
+
gasLimit,
|
|
1595
|
+
saveTrezorTx,
|
|
1596
|
+
}: ISendSignedErcTransactionProps): Promise<IResponseFromSendErcSignedTransaction> => {
|
|
1597
|
+
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1598
|
+
const { accounts, activeAccountType, activeAccountId, activeNetwork } =
|
|
1599
|
+
this.getState();
|
|
1600
|
+
const { address: activeAccountAddress } =
|
|
1601
|
+
accounts[activeAccountType][activeAccountId];
|
|
1602
|
+
|
|
1603
|
+
const sendERC20Token = async () => {
|
|
1604
|
+
const currentWallet = new Wallet(decryptedPrivateKey);
|
|
1605
|
+
|
|
1606
|
+
const walletSigned = currentWallet.connect(this.web3Provider);
|
|
1607
|
+
|
|
1608
|
+
try {
|
|
1609
|
+
const _contract = new Contract(
|
|
1610
|
+
tokenAddress,
|
|
1611
|
+
getErc20Abi(),
|
|
1612
|
+
walletSigned
|
|
1613
|
+
);
|
|
1614
|
+
// Preserve zero-decimal tokens: use provided decimals when defined (including 0).
|
|
1615
|
+
const resolvedDecimals =
|
|
1616
|
+
decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1617
|
+
const calculatedTokenAmount = parseUnits(
|
|
1618
|
+
tokenAmount as string,
|
|
1619
|
+
resolvedDecimals
|
|
1620
|
+
);
|
|
1621
|
+
let transferMethod;
|
|
1622
|
+
if (isLegacy) {
|
|
1623
|
+
const overrides = {
|
|
1624
|
+
nonce: await this.web3Provider.getTransactionCount(
|
|
1625
|
+
walletSigned.address,
|
|
1626
|
+
'pending'
|
|
1627
|
+
),
|
|
1628
|
+
gasPrice,
|
|
1629
|
+
...(gasLimit && { gasLimit }),
|
|
1630
|
+
type: 0, // Explicitly set Type 0 for legacy token transfers
|
|
1631
|
+
};
|
|
1632
|
+
transferMethod = await _contract.transfer(
|
|
1633
|
+
receiver,
|
|
1634
|
+
calculatedTokenAmount,
|
|
1635
|
+
overrides
|
|
1636
|
+
);
|
|
1637
|
+
} else {
|
|
1638
|
+
const overrides = {
|
|
1639
|
+
nonce: await this.web3Provider.getTransactionCount(
|
|
1640
|
+
walletSigned.address,
|
|
1641
|
+
'pending'
|
|
1642
|
+
),
|
|
1643
|
+
maxPriorityFeePerGas,
|
|
1644
|
+
maxFeePerGas,
|
|
1645
|
+
...(gasLimit && { gasLimit }),
|
|
1646
|
+
};
|
|
1647
|
+
|
|
1648
|
+
transferMethod = await _contract.transfer(
|
|
1649
|
+
receiver,
|
|
1650
|
+
calculatedTokenAmount,
|
|
1651
|
+
overrides
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
return transferMethod;
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
throw error;
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1661
|
+
const sendERC20TokenOnLedger = async () => {
|
|
1662
|
+
const signer = this.web3Provider.getSigner(activeAccountAddress);
|
|
1663
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
1664
|
+
activeAccountAddress
|
|
1665
|
+
);
|
|
1666
|
+
try {
|
|
1667
|
+
const _contract = new Contract(tokenAddress, getErc20Abi(), signer);
|
|
1668
|
+
|
|
1669
|
+
const resolvedDecimals =
|
|
1670
|
+
decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1671
|
+
const calculatedTokenAmount = parseUnits(
|
|
1672
|
+
tokenAmount as string,
|
|
1673
|
+
resolvedDecimals
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
const txData = _contract.interface.encodeFunctionData('transfer', [
|
|
1677
|
+
receiver,
|
|
1678
|
+
calculatedTokenAmount,
|
|
1679
|
+
]);
|
|
1680
|
+
|
|
1681
|
+
// Use fallback gas limit if not provided (for auto-estimation)
|
|
1682
|
+
const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
|
|
1683
|
+
|
|
1684
|
+
let txFormattedForEthers;
|
|
1685
|
+
if (isLegacy) {
|
|
1686
|
+
txFormattedForEthers = {
|
|
1687
|
+
to: tokenAddress,
|
|
1688
|
+
value: '0x0',
|
|
1689
|
+
gasLimit: effectiveGasLimit,
|
|
1690
|
+
gasPrice,
|
|
1691
|
+
data: txData,
|
|
1692
|
+
nonce: transactionNonce,
|
|
1693
|
+
chainId: activeNetwork.chainId,
|
|
1694
|
+
type: 0,
|
|
1695
|
+
};
|
|
1696
|
+
} else {
|
|
1697
|
+
txFormattedForEthers = {
|
|
1698
|
+
to: tokenAddress,
|
|
1699
|
+
value: '0x0',
|
|
1700
|
+
gasLimit: effectiveGasLimit,
|
|
1701
|
+
maxFeePerGas,
|
|
1702
|
+
maxPriorityFeePerGas,
|
|
1703
|
+
data: txData,
|
|
1704
|
+
nonce: transactionNonce,
|
|
1705
|
+
chainId: activeNetwork.chainId,
|
|
1706
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1711
|
+
|
|
1712
|
+
const signature = await this.ledgerSigner.evm.signEVMTransaction({
|
|
1713
|
+
rawTx: rawTx.replace('0x', ''),
|
|
1714
|
+
accountIndex: activeAccountId,
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
const formattedSignature = {
|
|
1718
|
+
r: `0x${signature.r}`,
|
|
1719
|
+
s: `0x${signature.s}`,
|
|
1720
|
+
v: parseInt(signature.v, 16),
|
|
1721
|
+
};
|
|
1722
|
+
if (signature) {
|
|
1723
|
+
try {
|
|
1724
|
+
const signedTx = serializeTransaction(
|
|
1725
|
+
txFormattedForEthers,
|
|
1726
|
+
formattedSignature
|
|
1727
|
+
);
|
|
1728
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
1729
|
+
|
|
1730
|
+
saveTrezorTx && saveTrezorTx(finalTx);
|
|
1731
|
+
|
|
1732
|
+
return finalTx as any;
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
throw error;
|
|
1735
|
+
}
|
|
1736
|
+
} else {
|
|
1737
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
1738
|
+
}
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
throw error;
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
const sendERC20TokenOnTrezor = async () => {
|
|
1745
|
+
const signer = this.web3Provider.getSigner(activeAccountAddress);
|
|
1746
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
1747
|
+
activeAccountAddress
|
|
1748
|
+
);
|
|
1749
|
+
try {
|
|
1750
|
+
const _contract = new Contract(tokenAddress, getErc20Abi(), signer);
|
|
1751
|
+
|
|
1752
|
+
const resolvedDecimals =
|
|
1753
|
+
decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1754
|
+
const calculatedTokenAmount = parseUnits(
|
|
1755
|
+
tokenAmount as string,
|
|
1756
|
+
resolvedDecimals
|
|
1757
|
+
);
|
|
1758
|
+
|
|
1759
|
+
const txData = _contract.interface.encodeFunctionData('transfer', [
|
|
1760
|
+
receiver,
|
|
1761
|
+
calculatedTokenAmount,
|
|
1762
|
+
]);
|
|
1763
|
+
|
|
1764
|
+
// Use fallback gas limit if not provided (for auto-estimation)
|
|
1765
|
+
const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
|
|
1766
|
+
|
|
1767
|
+
let txToBeSignedByTrezor;
|
|
1768
|
+
if (isLegacy) {
|
|
1769
|
+
txToBeSignedByTrezor = {
|
|
1770
|
+
to: tokenAddress,
|
|
1771
|
+
value: '0x0',
|
|
1772
|
+
// @ts-ignore
|
|
1773
|
+
gasLimit: `${effectiveGasLimit.hex}`,
|
|
1774
|
+
// @ts-ignore
|
|
1775
|
+
gasPrice: `${gasPrice}`,
|
|
1776
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
1777
|
+
chainId: activeNetwork.chainId,
|
|
1778
|
+
data: txData,
|
|
1779
|
+
};
|
|
1780
|
+
} else {
|
|
1781
|
+
txToBeSignedByTrezor = {
|
|
1782
|
+
to: tokenAddress,
|
|
1783
|
+
value: '0x0',
|
|
1784
|
+
// @ts-ignore
|
|
1785
|
+
gasLimit: `${effectiveGasLimit.hex}`,
|
|
1786
|
+
// @ts-ignore
|
|
1787
|
+
maxFeePerGas: `${maxFeePerGas.hex}`,
|
|
1788
|
+
// @ts-ignore
|
|
1789
|
+
maxPriorityFeePerGas: `${maxPriorityFeePerGas.hex}`,
|
|
1790
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
1791
|
+
chainId: activeNetwork.chainId,
|
|
1792
|
+
data: txData,
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
const signature = await this.trezorSigner.signEthTransaction({
|
|
1797
|
+
index: `${activeAccountId}`,
|
|
1798
|
+
tx: txToBeSignedByTrezor,
|
|
1799
|
+
coin: activeNetwork.currency,
|
|
1800
|
+
slip44: activeNetwork.slip44,
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
if (signature.success) {
|
|
1804
|
+
try {
|
|
1805
|
+
let txFormattedForEthers;
|
|
1806
|
+
if (isLegacy) {
|
|
1807
|
+
txFormattedForEthers = {
|
|
1808
|
+
to: tokenAddress,
|
|
1809
|
+
value: '0x0',
|
|
1810
|
+
gasLimit: effectiveGasLimit,
|
|
1811
|
+
gasPrice,
|
|
1812
|
+
data: txData,
|
|
1813
|
+
nonce: transactionNonce,
|
|
1814
|
+
chainId: activeNetwork.chainId,
|
|
1815
|
+
type: 0,
|
|
1816
|
+
};
|
|
1817
|
+
} else {
|
|
1818
|
+
txFormattedForEthers = {
|
|
1819
|
+
to: tokenAddress,
|
|
1820
|
+
value: '0x0',
|
|
1821
|
+
gasLimit: effectiveGasLimit,
|
|
1822
|
+
maxFeePerGas,
|
|
1823
|
+
maxPriorityFeePerGas,
|
|
1824
|
+
data: txData,
|
|
1825
|
+
nonce: transactionNonce,
|
|
1826
|
+
chainId: activeNetwork.chainId,
|
|
1827
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
signature.payload.v = parseInt(signature.payload.v, 16); //v parameter must be a number by ethers standards
|
|
1831
|
+
const signedTx = serializeTransaction(
|
|
1832
|
+
txFormattedForEthers,
|
|
1833
|
+
signature.payload
|
|
1834
|
+
);
|
|
1835
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
1836
|
+
|
|
1837
|
+
saveTrezorTx && saveTrezorTx(finalTx);
|
|
1838
|
+
|
|
1839
|
+
return finalTx as any;
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
throw error;
|
|
1842
|
+
}
|
|
1843
|
+
} else {
|
|
1844
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
1845
|
+
}
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
throw error;
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
switch (activeAccountType) {
|
|
1852
|
+
case KeyringAccountType.Trezor:
|
|
1853
|
+
return await sendERC20TokenOnTrezor();
|
|
1854
|
+
case KeyringAccountType.Ledger:
|
|
1855
|
+
return await sendERC20TokenOnLedger();
|
|
1856
|
+
default:
|
|
1857
|
+
return await sendERC20Token();
|
|
1858
|
+
}
|
|
1859
|
+
};
|
|
1860
|
+
|
|
1861
|
+
sendSignedErc721Transaction = async ({
|
|
1862
|
+
receiver,
|
|
1863
|
+
tokenAddress,
|
|
1864
|
+
tokenId,
|
|
1865
|
+
isLegacy,
|
|
1866
|
+
maxPriorityFeePerGas,
|
|
1867
|
+
maxFeePerGas,
|
|
1868
|
+
gasPrice,
|
|
1869
|
+
gasLimit,
|
|
1870
|
+
}: ISendSignedErcTransactionProps): Promise<IResponseFromSendErcSignedTransaction> => {
|
|
1871
|
+
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1872
|
+
const { accounts, activeAccountType, activeAccountId, activeNetwork } =
|
|
1873
|
+
this.getState();
|
|
1874
|
+
const { address: activeAccountAddress } =
|
|
1875
|
+
accounts[activeAccountType][activeAccountId];
|
|
1876
|
+
|
|
1877
|
+
const sendERC721Token = async () => {
|
|
1878
|
+
const currentWallet = new Wallet(decryptedPrivateKey);
|
|
1879
|
+
const walletSigned = currentWallet.connect(this.web3Provider);
|
|
1880
|
+
let transferMethod;
|
|
1881
|
+
try {
|
|
1882
|
+
const _contract = new Contract(
|
|
1883
|
+
tokenAddress,
|
|
1884
|
+
getErc21Abi(),
|
|
1885
|
+
walletSigned
|
|
1886
|
+
);
|
|
1887
|
+
|
|
1888
|
+
if (isLegacy) {
|
|
1889
|
+
const overrides = {
|
|
1890
|
+
nonce: await this.web3Provider.getTransactionCount(
|
|
1891
|
+
walletSigned.address,
|
|
1892
|
+
'pending'
|
|
1893
|
+
),
|
|
1894
|
+
gasPrice,
|
|
1895
|
+
...(gasLimit && { gasLimit }),
|
|
1896
|
+
type: 0, // Explicitly set Type 0 for legacy NFT transfers
|
|
1897
|
+
};
|
|
1898
|
+
transferMethod = await _contract.transferFrom(
|
|
1899
|
+
walletSigned.address,
|
|
1900
|
+
receiver,
|
|
1901
|
+
tokenId as number,
|
|
1902
|
+
overrides
|
|
1903
|
+
);
|
|
1904
|
+
} else {
|
|
1905
|
+
const overrides = {
|
|
1906
|
+
nonce: await this.web3Provider.getTransactionCount(
|
|
1907
|
+
walletSigned.address,
|
|
1908
|
+
'pending'
|
|
1909
|
+
),
|
|
1910
|
+
maxPriorityFeePerGas,
|
|
1911
|
+
maxFeePerGas,
|
|
1912
|
+
...(gasLimit && { gasLimit }),
|
|
1913
|
+
};
|
|
1914
|
+
transferMethod = await _contract.transferFrom(
|
|
1915
|
+
walletSigned.address,
|
|
1916
|
+
receiver,
|
|
1917
|
+
tokenId as number,
|
|
1918
|
+
overrides
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
return transferMethod;
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
throw error;
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
const sendERC721TokenOnLedger = async () => {
|
|
1929
|
+
const signer = this.web3Provider.getSigner(activeAccountAddress);
|
|
1930
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
1931
|
+
activeAccountAddress
|
|
1932
|
+
);
|
|
1933
|
+
try {
|
|
1934
|
+
const _contract = new Contract(tokenAddress, getErc21Abi(), signer);
|
|
1935
|
+
const txData = _contract.interface.encodeFunctionData('transferFrom', [
|
|
1936
|
+
activeAccountAddress,
|
|
1937
|
+
receiver,
|
|
1938
|
+
tokenId,
|
|
1939
|
+
]);
|
|
1940
|
+
|
|
1941
|
+
// Use fallback gas limit if not provided (for auto-estimation)
|
|
1942
|
+
const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
|
|
1943
|
+
|
|
1944
|
+
let txFormattedForEthers;
|
|
1945
|
+
if (isLegacy) {
|
|
1946
|
+
txFormattedForEthers = {
|
|
1947
|
+
to: tokenAddress,
|
|
1948
|
+
value: '0x0',
|
|
1949
|
+
gasLimit: effectiveGasLimit,
|
|
1950
|
+
gasPrice,
|
|
1951
|
+
data: txData,
|
|
1952
|
+
nonce: transactionNonce,
|
|
1953
|
+
chainId: activeNetwork.chainId,
|
|
1954
|
+
type: 0,
|
|
1955
|
+
};
|
|
1956
|
+
} else {
|
|
1957
|
+
txFormattedForEthers = {
|
|
1958
|
+
to: tokenAddress,
|
|
1959
|
+
value: '0x0',
|
|
1960
|
+
gasLimit: effectiveGasLimit,
|
|
1961
|
+
maxFeePerGas,
|
|
1962
|
+
maxPriorityFeePerGas,
|
|
1963
|
+
data: txData,
|
|
1964
|
+
nonce: transactionNonce,
|
|
1965
|
+
chainId: activeNetwork.chainId,
|
|
1966
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1971
|
+
|
|
1972
|
+
const signature = await this.ledgerSigner.evm.signEVMTransaction({
|
|
1973
|
+
rawTx: rawTx.replace('0x', ''),
|
|
1974
|
+
accountIndex: activeAccountId,
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
const formattedSignature = {
|
|
1978
|
+
r: `0x${signature.r}`,
|
|
1979
|
+
s: `0x${signature.s}`,
|
|
1980
|
+
v: parseInt(signature.v, 16),
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
if (signature) {
|
|
1984
|
+
try {
|
|
1985
|
+
const signedTx = serializeTransaction(
|
|
1986
|
+
txFormattedForEthers,
|
|
1987
|
+
formattedSignature
|
|
1988
|
+
);
|
|
1989
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
1990
|
+
|
|
1991
|
+
return finalTx as any;
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
throw error;
|
|
1994
|
+
}
|
|
1995
|
+
} else {
|
|
1996
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
1997
|
+
}
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
throw error;
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
|
|
2003
|
+
const sendERC721TokenOnTrezor = async () => {
|
|
2004
|
+
const signer = this.web3Provider.getSigner(activeAccountAddress);
|
|
2005
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
2006
|
+
activeAccountAddress
|
|
2007
|
+
);
|
|
2008
|
+
try {
|
|
2009
|
+
const _contract = new Contract(tokenAddress, getErc21Abi(), signer);
|
|
2010
|
+
const txData = _contract.interface.encodeFunctionData('transferFrom', [
|
|
2011
|
+
activeAccountAddress,
|
|
2012
|
+
receiver,
|
|
2013
|
+
tokenId,
|
|
2014
|
+
]);
|
|
2015
|
+
|
|
2016
|
+
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2017
|
+
const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
|
|
2018
|
+
|
|
2019
|
+
let txToBeSignedByTrezor;
|
|
2020
|
+
if (isLegacy) {
|
|
2021
|
+
txToBeSignedByTrezor = {
|
|
2022
|
+
to: tokenAddress,
|
|
2023
|
+
value: '0x0',
|
|
2024
|
+
// @ts-ignore
|
|
2025
|
+
gasLimit: `${effectiveGasLimit.hex}`,
|
|
2026
|
+
// @ts-ignore
|
|
2027
|
+
gasPrice: `${gasPrice}`,
|
|
2028
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2029
|
+
chainId: activeNetwork.chainId,
|
|
2030
|
+
data: txData,
|
|
2031
|
+
};
|
|
2032
|
+
console.log({ txToBeSignedByTrezor });
|
|
2033
|
+
} else {
|
|
2034
|
+
txToBeSignedByTrezor = {
|
|
2035
|
+
to: tokenAddress,
|
|
2036
|
+
value: '0x0',
|
|
2037
|
+
// @ts-ignore
|
|
2038
|
+
gasLimit: `${effectiveGasLimit.hex}`,
|
|
2039
|
+
// @ts-ignore
|
|
2040
|
+
maxFeePerGas: `${maxFeePerGas.hex}`,
|
|
2041
|
+
// @ts-ignore
|
|
2042
|
+
maxPriorityFeePerGas: `${maxPriorityFeePerGas.hex}`,
|
|
2043
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2044
|
+
chainId: activeNetwork.chainId,
|
|
2045
|
+
data: txData,
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// For EVM networks, Trezor expects 'eth' regardless of the network's currency
|
|
2050
|
+
const trezorCoin =
|
|
2051
|
+
activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
2052
|
+
const signature = await this.trezorSigner.signEthTransaction({
|
|
2053
|
+
index: `${activeAccountId}`,
|
|
2054
|
+
tx: txToBeSignedByTrezor,
|
|
2055
|
+
coin: trezorCoin,
|
|
2056
|
+
slip44: activeNetwork.slip44,
|
|
2057
|
+
});
|
|
2058
|
+
|
|
2059
|
+
if (signature.success) {
|
|
2060
|
+
try {
|
|
2061
|
+
let txFormattedForEthers;
|
|
2062
|
+
if (isLegacy) {
|
|
2063
|
+
txFormattedForEthers = {
|
|
2064
|
+
to: tokenAddress,
|
|
2065
|
+
value: '0x0',
|
|
2066
|
+
gasLimit: effectiveGasLimit,
|
|
2067
|
+
gasPrice,
|
|
2068
|
+
data: txData,
|
|
2069
|
+
nonce: transactionNonce,
|
|
2070
|
+
chainId: activeNetwork.chainId,
|
|
2071
|
+
type: 0,
|
|
2072
|
+
};
|
|
2073
|
+
} else {
|
|
2074
|
+
txFormattedForEthers = {
|
|
2075
|
+
to: tokenAddress,
|
|
2076
|
+
value: '0x0',
|
|
2077
|
+
gasLimit: effectiveGasLimit,
|
|
2078
|
+
maxFeePerGas,
|
|
2079
|
+
maxPriorityFeePerGas,
|
|
2080
|
+
data: txData,
|
|
2081
|
+
nonce: transactionNonce,
|
|
2082
|
+
chainId: activeNetwork.chainId,
|
|
2083
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
signature.payload.v = parseInt(signature.payload.v, 16); //v parameter must be a number by ethers standards
|
|
2087
|
+
const signedTx = serializeTransaction(
|
|
2088
|
+
txFormattedForEthers,
|
|
2089
|
+
signature.payload
|
|
2090
|
+
);
|
|
2091
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
2092
|
+
|
|
2093
|
+
return finalTx as any;
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
console.log({ error });
|
|
2096
|
+
throw error;
|
|
2097
|
+
}
|
|
2098
|
+
} else {
|
|
2099
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
2100
|
+
}
|
|
2101
|
+
} catch (error) {
|
|
2102
|
+
console.log({ errorDois: error });
|
|
2103
|
+
throw error;
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
|
|
2107
|
+
switch (activeAccountType) {
|
|
2108
|
+
case KeyringAccountType.Trezor:
|
|
2109
|
+
return await sendERC721TokenOnTrezor();
|
|
2110
|
+
case KeyringAccountType.Ledger:
|
|
2111
|
+
return await sendERC721TokenOnLedger();
|
|
2112
|
+
default:
|
|
2113
|
+
return await sendERC721Token();
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
|
|
2117
|
+
sendSignedErc1155Transaction = async ({
|
|
2118
|
+
receiver,
|
|
2119
|
+
tokenAddress,
|
|
2120
|
+
tokenId,
|
|
2121
|
+
tokenAmount,
|
|
2122
|
+
isLegacy,
|
|
2123
|
+
maxPriorityFeePerGas,
|
|
2124
|
+
maxFeePerGas,
|
|
2125
|
+
gasPrice,
|
|
2126
|
+
gasLimit,
|
|
2127
|
+
}: ISendSignedErcTransactionProps): Promise<IResponseFromSendErcSignedTransaction> => {
|
|
2128
|
+
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
2129
|
+
const { accounts, activeAccountType, activeAccountId, activeNetwork } =
|
|
2130
|
+
this.getState();
|
|
2131
|
+
const { address: activeAccountAddress } =
|
|
2132
|
+
accounts[activeAccountType][activeAccountId];
|
|
2133
|
+
|
|
2134
|
+
const sendERC1155Token = async () => {
|
|
2135
|
+
const currentWallet = new Wallet(decryptedPrivateKey);
|
|
2136
|
+
const walletSigned = currentWallet.connect(this.web3Provider);
|
|
2137
|
+
let transferMethod;
|
|
2138
|
+
try {
|
|
2139
|
+
const _contract = new Contract(
|
|
2140
|
+
tokenAddress,
|
|
2141
|
+
getErc55Abi(),
|
|
2142
|
+
walletSigned
|
|
2143
|
+
);
|
|
2144
|
+
|
|
2145
|
+
// Use BigNumber to avoid JS number overflow/precision loss
|
|
2146
|
+
const amount = BigNumber.from(tokenAmount ?? '1');
|
|
2147
|
+
|
|
2148
|
+
let overrides;
|
|
2149
|
+
if (isLegacy) {
|
|
2150
|
+
overrides = {
|
|
2151
|
+
nonce: await this.web3Provider.getTransactionCount(
|
|
2152
|
+
walletSigned.address,
|
|
2153
|
+
'pending'
|
|
2154
|
+
),
|
|
2155
|
+
gasPrice,
|
|
2156
|
+
...(gasLimit && { gasLimit }),
|
|
2157
|
+
type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
|
|
2158
|
+
};
|
|
2159
|
+
} else {
|
|
2160
|
+
overrides = {
|
|
2161
|
+
nonce: await this.web3Provider.getTransactionCount(
|
|
2162
|
+
walletSigned.address,
|
|
2163
|
+
'pending'
|
|
2164
|
+
),
|
|
2165
|
+
maxPriorityFeePerGas,
|
|
2166
|
+
maxFeePerGas,
|
|
2167
|
+
...(gasLimit && { gasLimit }),
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
transferMethod = await _contract.safeTransferFrom(
|
|
2172
|
+
walletSigned.address,
|
|
2173
|
+
receiver,
|
|
2174
|
+
tokenId as number,
|
|
2175
|
+
amount,
|
|
2176
|
+
[],
|
|
2177
|
+
overrides
|
|
2178
|
+
);
|
|
2179
|
+
return transferMethod;
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
throw error;
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
const sendERC1155TokenOnLedger = async () => {
|
|
2186
|
+
const signer = this.web3Provider.getSigner(activeAccountAddress);
|
|
2187
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
2188
|
+
activeAccountAddress
|
|
2189
|
+
);
|
|
2190
|
+
try {
|
|
2191
|
+
const _contract = new Contract(tokenAddress, getErc55Abi(), signer);
|
|
2192
|
+
|
|
2193
|
+
const amount = BigNumber.from(tokenAmount ?? '1');
|
|
2194
|
+
|
|
2195
|
+
const txData = _contract.interface.encodeFunctionData(
|
|
2196
|
+
'safeTransferFrom',
|
|
2197
|
+
[activeAccountAddress, receiver, tokenId, amount, []]
|
|
2198
|
+
);
|
|
2199
|
+
|
|
2200
|
+
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2201
|
+
const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
|
|
2202
|
+
|
|
2203
|
+
let txFormattedForEthers;
|
|
2204
|
+
if (isLegacy) {
|
|
2205
|
+
txFormattedForEthers = {
|
|
2206
|
+
to: tokenAddress,
|
|
2207
|
+
value: '0x0',
|
|
2208
|
+
gasLimit: effectiveGasLimit,
|
|
2209
|
+
gasPrice,
|
|
2210
|
+
data: txData,
|
|
2211
|
+
nonce: transactionNonce,
|
|
2212
|
+
chainId: activeNetwork.chainId,
|
|
2213
|
+
type: 0,
|
|
2214
|
+
};
|
|
2215
|
+
} else {
|
|
2216
|
+
txFormattedForEthers = {
|
|
2217
|
+
to: tokenAddress,
|
|
2218
|
+
value: '0x0',
|
|
2219
|
+
gasLimit: effectiveGasLimit,
|
|
2220
|
+
maxFeePerGas,
|
|
2221
|
+
maxPriorityFeePerGas,
|
|
2222
|
+
data: txData,
|
|
2223
|
+
nonce: transactionNonce,
|
|
2224
|
+
chainId: activeNetwork.chainId,
|
|
2225
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2230
|
+
|
|
2231
|
+
const signature = await this.ledgerSigner.evm.signEVMTransaction({
|
|
2232
|
+
rawTx: rawTx.replace('0x', ''),
|
|
2233
|
+
accountIndex: activeAccountId,
|
|
2234
|
+
});
|
|
2235
|
+
|
|
2236
|
+
const formattedSignature = {
|
|
2237
|
+
r: `0x${signature.r}`,
|
|
2238
|
+
s: `0x${signature.s}`,
|
|
2239
|
+
v: parseInt(signature.v, 16),
|
|
2240
|
+
};
|
|
2241
|
+
|
|
2242
|
+
if (signature) {
|
|
2243
|
+
try {
|
|
2244
|
+
const signedTx = serializeTransaction(
|
|
2245
|
+
txFormattedForEthers,
|
|
2246
|
+
formattedSignature
|
|
2247
|
+
);
|
|
2248
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
2249
|
+
|
|
2250
|
+
return finalTx as any;
|
|
2251
|
+
} catch (error) {
|
|
2252
|
+
throw error;
|
|
2253
|
+
}
|
|
2254
|
+
} else {
|
|
2255
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
2256
|
+
}
|
|
2257
|
+
} catch (error) {
|
|
2258
|
+
throw error;
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
const sendERC1155TokenOnTrezor = async () => {
|
|
2263
|
+
const signer = this.web3Provider.getSigner(activeAccountAddress);
|
|
2264
|
+
const transactionNonce = await this.getRecommendedNonce(
|
|
2265
|
+
activeAccountAddress
|
|
2266
|
+
);
|
|
2267
|
+
try {
|
|
2268
|
+
const _contract = new Contract(tokenAddress, getErc55Abi(), signer);
|
|
2269
|
+
|
|
2270
|
+
const amount = BigNumber.from(tokenAmount ?? '1');
|
|
2271
|
+
|
|
2272
|
+
const txData = _contract.interface.encodeFunctionData(
|
|
2273
|
+
'safeTransferFrom',
|
|
2274
|
+
[activeAccountAddress, receiver, tokenId, amount, []]
|
|
2275
|
+
);
|
|
2276
|
+
|
|
2277
|
+
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2278
|
+
const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
|
|
2279
|
+
|
|
2280
|
+
let txToBeSignedByTrezor;
|
|
2281
|
+
if (isLegacy) {
|
|
2282
|
+
txToBeSignedByTrezor = {
|
|
2283
|
+
to: tokenAddress,
|
|
2284
|
+
value: '0x0',
|
|
2285
|
+
// @ts-ignore
|
|
2286
|
+
gasLimit: `${effectiveGasLimit.hex}`,
|
|
2287
|
+
// @ts-ignore
|
|
2288
|
+
gasPrice: `${gasPrice}`,
|
|
2289
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2290
|
+
chainId: activeNetwork.chainId,
|
|
2291
|
+
data: txData,
|
|
2292
|
+
};
|
|
2293
|
+
} else {
|
|
2294
|
+
txToBeSignedByTrezor = {
|
|
2295
|
+
to: tokenAddress,
|
|
2296
|
+
value: '0x0',
|
|
2297
|
+
// @ts-ignore
|
|
2298
|
+
gasLimit: `${effectiveGasLimit.hex}`,
|
|
2299
|
+
// @ts-ignore
|
|
2300
|
+
maxFeePerGas: `${maxFeePerGas.hex}`,
|
|
2301
|
+
// @ts-ignore
|
|
2302
|
+
maxPriorityFeePerGas: `${maxPriorityFeePerGas.hex}`,
|
|
2303
|
+
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2304
|
+
chainId: activeNetwork.chainId,
|
|
2305
|
+
data: txData,
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
const signature = await this.trezorSigner.signEthTransaction({
|
|
2310
|
+
index: `${activeAccountId}`,
|
|
2311
|
+
tx: txToBeSignedByTrezor,
|
|
2312
|
+
coin: activeNetwork.currency,
|
|
2313
|
+
slip44: activeNetwork.slip44,
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
if (signature.success) {
|
|
2317
|
+
try {
|
|
2318
|
+
let txFormattedForEthers;
|
|
2319
|
+
if (isLegacy) {
|
|
2320
|
+
txFormattedForEthers = {
|
|
2321
|
+
to: tokenAddress,
|
|
2322
|
+
value: '0x0',
|
|
2323
|
+
gasLimit: effectiveGasLimit,
|
|
2324
|
+
gasPrice,
|
|
2325
|
+
data: txData,
|
|
2326
|
+
nonce: transactionNonce,
|
|
2327
|
+
chainId: activeNetwork.chainId,
|
|
2328
|
+
type: 0,
|
|
2329
|
+
};
|
|
2330
|
+
} else {
|
|
2331
|
+
txFormattedForEthers = {
|
|
2332
|
+
to: tokenAddress,
|
|
2333
|
+
value: '0x0',
|
|
2334
|
+
gasLimit: effectiveGasLimit,
|
|
2335
|
+
maxFeePerGas,
|
|
2336
|
+
maxPriorityFeePerGas,
|
|
2337
|
+
data: txData,
|
|
2338
|
+
nonce: transactionNonce,
|
|
2339
|
+
chainId: activeNetwork.chainId,
|
|
2340
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
signature.payload.v = parseInt(signature.payload.v, 16); //v parameter must be a number by ethers standards
|
|
2344
|
+
const signedTx = serializeTransaction(
|
|
2345
|
+
txFormattedForEthers,
|
|
2346
|
+
signature.payload
|
|
2347
|
+
);
|
|
2348
|
+
const finalTx = await this.web3Provider.sendTransaction(signedTx);
|
|
2349
|
+
|
|
2350
|
+
return finalTx as any;
|
|
2351
|
+
} catch (error) {
|
|
2352
|
+
console.log({ error });
|
|
2353
|
+
throw error;
|
|
2354
|
+
}
|
|
2355
|
+
} else {
|
|
2356
|
+
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
2357
|
+
}
|
|
2358
|
+
} catch (error) {
|
|
2359
|
+
console.log({ error });
|
|
2360
|
+
throw error;
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
|
|
2364
|
+
switch (activeAccountType) {
|
|
2365
|
+
case KeyringAccountType.Trezor:
|
|
2366
|
+
return await sendERC1155TokenOnTrezor();
|
|
2367
|
+
case KeyringAccountType.Ledger:
|
|
2368
|
+
return await sendERC1155TokenOnLedger();
|
|
2369
|
+
default:
|
|
2370
|
+
return await sendERC1155Token();
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2373
|
+
|
|
2374
|
+
getRecommendedNonce = async (address: string) => {
|
|
2375
|
+
try {
|
|
2376
|
+
return await this.web3Provider.getTransactionCount(address, 'pending');
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
throw error;
|
|
2379
|
+
}
|
|
2380
|
+
};
|
|
2381
|
+
|
|
2382
|
+
getFeeByType = async (type: string) => {
|
|
2383
|
+
const gasPrice = (await this.getRecommendedGasPrice(false)) as string;
|
|
2384
|
+
|
|
2385
|
+
const low = this.toBigNumber(gasPrice)
|
|
2386
|
+
.mul(BigNumber.from('8'))
|
|
2387
|
+
.div(BigNumber.from('10'))
|
|
2388
|
+
.toString();
|
|
2389
|
+
|
|
2390
|
+
const high = this.toBigNumber(gasPrice)
|
|
2391
|
+
.mul(BigNumber.from('11'))
|
|
2392
|
+
.div(BigNumber.from('10'))
|
|
2393
|
+
.toString();
|
|
2394
|
+
|
|
2395
|
+
if (type === 'low') return low;
|
|
2396
|
+
if (type === 'high') return high;
|
|
2397
|
+
|
|
2398
|
+
return gasPrice;
|
|
2399
|
+
};
|
|
2400
|
+
|
|
2401
|
+
getGasLimit = async (toAddress: string) => {
|
|
2402
|
+
try {
|
|
2403
|
+
const estimated = await this.web3Provider.estimateGas({
|
|
2404
|
+
to: toAddress,
|
|
2405
|
+
});
|
|
2406
|
+
|
|
2407
|
+
return Number(formatUnits(estimated, 'gwei'));
|
|
2408
|
+
} catch (error) {
|
|
2409
|
+
throw error;
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
|
|
2413
|
+
getTxGasLimit = async (tx: SimpleTransactionRequest) => {
|
|
2414
|
+
try {
|
|
2415
|
+
return this.web3Provider.estimateGas(tx);
|
|
2416
|
+
} catch (error) {
|
|
2417
|
+
throw error;
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
getRecommendedGasPrice = async (formatted?: boolean) => {
|
|
2422
|
+
try {
|
|
2423
|
+
const gasPriceBN = await this.web3Provider.getGasPrice();
|
|
2424
|
+
|
|
2425
|
+
if (formatted) {
|
|
2426
|
+
return {
|
|
2427
|
+
gwei: Number(formatUnits(gasPriceBN, 'gwei')).toFixed(2),
|
|
2428
|
+
ethers: formatEther(gasPriceBN),
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
return gasPriceBN.toString();
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
throw error;
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
|
|
2438
|
+
getBalance = async (address: string) => {
|
|
2439
|
+
try {
|
|
2440
|
+
const balance = await this.web3Provider.getBalance(address);
|
|
2441
|
+
const formattedBalance = formatEther(balance);
|
|
2442
|
+
|
|
2443
|
+
return parseFloat(formattedBalance); // Return full precision
|
|
2444
|
+
} catch (error) {
|
|
2445
|
+
throw error;
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
|
|
2449
|
+
private getTransactionTimestamp = async (
|
|
2450
|
+
transaction: TransactionResponse
|
|
2451
|
+
) => {
|
|
2452
|
+
const { timestamp } = await this.web3Provider.getBlock(
|
|
2453
|
+
Number(transaction.blockNumber)
|
|
2454
|
+
);
|
|
2455
|
+
|
|
2456
|
+
return {
|
|
2457
|
+
...transaction,
|
|
2458
|
+
timestamp,
|
|
2459
|
+
} as TransactionResponse;
|
|
2460
|
+
};
|
|
2461
|
+
|
|
2462
|
+
public setWeb3Provider(network: INetwork) {
|
|
2463
|
+
this.abortController.abort();
|
|
2464
|
+
this.abortController = new AbortController();
|
|
2465
|
+
|
|
2466
|
+
// Check if network is a UTXO network to avoid creating web3 providers for blockbook URLs
|
|
2467
|
+
const isUtxoNetwork = this.isUtxoNetwork(network);
|
|
2468
|
+
|
|
2469
|
+
if (isUtxoNetwork) {
|
|
2470
|
+
// For UTXO networks, don't create web3 providers at all since they won't be used
|
|
2471
|
+
console.log(
|
|
2472
|
+
'[EthereumTransactions] setWeb3Provider: Skipping web3Provider creation for UTXO network:',
|
|
2473
|
+
network.url
|
|
2474
|
+
);
|
|
2475
|
+
// Clear any existing providers for UTXO networks
|
|
2476
|
+
this._web3Provider = undefined as any;
|
|
2477
|
+
} else {
|
|
2478
|
+
// For EVM networks, create normal providers
|
|
2479
|
+
const isL2Network = L2_NETWORK_CHAIN_IDS.includes(network.chainId);
|
|
2480
|
+
|
|
2481
|
+
const CurrentProvider = isL2Network
|
|
2482
|
+
? CustomL2JsonRpcProvider
|
|
2483
|
+
: CustomJsonRpcProvider;
|
|
2484
|
+
|
|
2485
|
+
this._web3Provider = new CurrentProvider(
|
|
2486
|
+
this.abortController.signal,
|
|
2487
|
+
network.url
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
public importAccount = (mnemonicOrPrivKey: string) => {
|
|
2493
|
+
if (isHexString(mnemonicOrPrivKey)) {
|
|
2494
|
+
return new Wallet(mnemonicOrPrivKey);
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
const { privateKey } = Wallet.fromMnemonic(mnemonicOrPrivKey);
|
|
2498
|
+
|
|
2499
|
+
const account = new Wallet(privateKey);
|
|
2500
|
+
|
|
2501
|
+
return account;
|
|
2502
|
+
};
|
|
2503
|
+
}
|