@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.
Files changed (212) hide show
  1. package/coverage/clover.xml +2875 -0
  2. package/coverage/coverage-final.json +29468 -0
  3. package/coverage/lcov-report/base.css +354 -0
  4. package/coverage/lcov-report/block-navigation.js +85 -0
  5. package/coverage/lcov-report/favicon.png +0 -0
  6. package/coverage/lcov-report/index.html +320 -0
  7. package/coverage/lcov-report/prettify.css +101 -0
  8. package/coverage/lcov-report/prettify.js +1008 -0
  9. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  10. package/coverage/lcov-report/sorter.js +191 -0
  11. package/coverage/lcov-report/src/index.html +276 -0
  12. package/coverage/lcov-report/src/index.ts.html +114 -0
  13. package/coverage/lcov-report/src/initial-state.ts.html +558 -0
  14. package/coverage/lcov-report/src/keyring-manager.ts.html +6279 -0
  15. package/coverage/lcov-report/src/ledger/bitcoin_client/index.html +178 -0
  16. package/coverage/lcov-report/src/ledger/bitcoin_client/index.ts.html +144 -0
  17. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/appClient.ts.html +1560 -0
  18. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/bip32.ts.html +276 -0
  19. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/buffertools.ts.html +495 -0
  20. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/clientCommands.ts.html +1138 -0
  21. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/index.html +363 -0
  22. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts.html +289 -0
  23. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkle.ts.html +486 -0
  24. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkleMap.ts.html +240 -0
  25. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/policy.ts.html +342 -0
  26. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/psbtv2.ts.html +2388 -0
  27. package/coverage/lcov-report/src/ledger/bitcoin_client/lib/varint.ts.html +453 -0
  28. package/coverage/lcov-report/src/ledger/consts.ts.html +177 -0
  29. package/coverage/lcov-report/src/ledger/index.html +216 -0
  30. package/coverage/lcov-report/src/ledger/index.ts.html +1371 -0
  31. package/coverage/lcov-report/src/ledger/utils.ts.html +102 -0
  32. package/coverage/lcov-report/src/signers.ts.html +591 -0
  33. package/coverage/lcov-report/src/storage.ts.html +198 -0
  34. package/coverage/lcov-report/src/transactions/ethereum.ts.html +5826 -0
  35. package/coverage/lcov-report/src/transactions/index.html +216 -0
  36. package/coverage/lcov-report/src/transactions/index.ts.html +93 -0
  37. package/coverage/lcov-report/src/transactions/syscoin.ts.html +1521 -0
  38. package/coverage/lcov-report/src/trezor/index.html +176 -0
  39. package/coverage/lcov-report/src/trezor/index.ts.html +2655 -0
  40. package/coverage/lcov-report/src/types.ts.html +1443 -0
  41. package/coverage/lcov-report/src/utils/derivation-paths.ts.html +486 -0
  42. package/coverage/lcov-report/src/utils/index.html +196 -0
  43. package/coverage/lcov-report/src/utils/psbt.ts.html +159 -0
  44. package/coverage/lcov-report/test/helpers/constants.ts.html +627 -0
  45. package/coverage/lcov-report/test/helpers/index.html +176 -0
  46. package/coverage/lcov.info +4832 -0
  47. package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/appClient.js +1 -124
  48. package/dist/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
  49. package/{cjs → dist/cjs}/transactions/ethereum.js +24 -11
  50. package/dist/cjs/transactions/ethereum.js.map +1 -0
  51. package/dist/package.json +50 -0
  52. package/{types → dist/types}/ledger/bitcoin_client/lib/appClient.d.ts +0 -6
  53. package/examples/basic-usage.js +140 -0
  54. package/jest.config.js +32 -0
  55. package/package.json +31 -13
  56. package/readme.md +201 -0
  57. package/src/declare.d.ts +7 -0
  58. package/src/errorUtils.ts +83 -0
  59. package/src/hardware-wallet-manager.ts +655 -0
  60. package/src/index.ts +12 -0
  61. package/src/initial-state.ts +108 -0
  62. package/src/keyring-manager.ts +2698 -0
  63. package/src/ledger/bitcoin_client/index.ts +19 -0
  64. package/src/ledger/bitcoin_client/lib/appClient.ts +405 -0
  65. package/src/ledger/bitcoin_client/lib/bip32.ts +61 -0
  66. package/src/ledger/bitcoin_client/lib/buffertools.ts +134 -0
  67. package/src/ledger/bitcoin_client/lib/clientCommands.ts +356 -0
  68. package/src/ledger/bitcoin_client/lib/constants.ts +12 -0
  69. package/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts +65 -0
  70. package/src/ledger/bitcoin_client/lib/merkle.ts +136 -0
  71. package/src/ledger/bitcoin_client/lib/merkleMap.ts +49 -0
  72. package/src/ledger/bitcoin_client/lib/policy.ts +91 -0
  73. package/src/ledger/bitcoin_client/lib/psbtv2.ts +768 -0
  74. package/src/ledger/bitcoin_client/lib/varint.ts +120 -0
  75. package/src/ledger/consts.ts +3 -0
  76. package/src/ledger/index.ts +685 -0
  77. package/src/ledger/types.ts +74 -0
  78. package/src/network-utils.ts +99 -0
  79. package/src/providers.ts +345 -0
  80. package/src/signers.ts +158 -0
  81. package/src/storage.ts +63 -0
  82. package/src/transactions/__tests__/integration.test.ts +303 -0
  83. package/src/transactions/__tests__/syscoin.test.ts +409 -0
  84. package/src/transactions/ethereum.ts +2503 -0
  85. package/src/transactions/index.ts +2 -0
  86. package/src/transactions/syscoin.ts +542 -0
  87. package/src/trezor/index.ts +1050 -0
  88. package/src/types.ts +366 -0
  89. package/src/utils/derivation-paths.ts +133 -0
  90. package/src/utils/psbt.ts +24 -0
  91. package/src/utils.ts +191 -0
  92. package/test/README.md +158 -0
  93. package/test/__mocks__/ledger-mock.js +20 -0
  94. package/test/__mocks__/trezor-mock.js +75 -0
  95. package/test/cleanup-summary.md +167 -0
  96. package/test/helpers/README.md +78 -0
  97. package/test/helpers/constants.ts +79 -0
  98. package/test/helpers/setup.ts +714 -0
  99. package/test/integration/import-validation.spec.ts +588 -0
  100. package/test/unit/hardware/ledger.spec.ts +869 -0
  101. package/test/unit/hardware/trezor.spec.ts +828 -0
  102. package/test/unit/keyring-manager/account-management.spec.ts +970 -0
  103. package/test/unit/keyring-manager/import-watchonly.spec.ts +181 -0
  104. package/test/unit/keyring-manager/import-wif.spec.ts +126 -0
  105. package/test/unit/keyring-manager/initialization.spec.ts +782 -0
  106. package/test/unit/keyring-manager/key-derivation.spec.ts +996 -0
  107. package/test/unit/keyring-manager/security.spec.ts +505 -0
  108. package/test/unit/keyring-manager/state-management.spec.ts +375 -0
  109. package/test/unit/network/network-management.spec.ts +372 -0
  110. package/test/unit/transactions/ethereum-transactions.spec.ts +382 -0
  111. package/test/unit/transactions/syscoin-transactions.spec.ts +615 -0
  112. package/tsconfig.json +14 -0
  113. package/cjs/ledger/bitcoin_client/lib/appClient.js.map +0 -1
  114. package/cjs/transactions/ethereum.js.map +0 -1
  115. /package/{README.md → dist/README.md} +0 -0
  116. /package/{cjs → dist/cjs}/errorUtils.js +0 -0
  117. /package/{cjs → dist/cjs}/errorUtils.js.map +0 -0
  118. /package/{cjs → dist/cjs}/hardware-wallet-manager.js +0 -0
  119. /package/{cjs → dist/cjs}/hardware-wallet-manager.js.map +0 -0
  120. /package/{cjs → dist/cjs}/index.js +0 -0
  121. /package/{cjs → dist/cjs}/index.js.map +0 -0
  122. /package/{cjs → dist/cjs}/initial-state.js +0 -0
  123. /package/{cjs → dist/cjs}/initial-state.js.map +0 -0
  124. /package/{cjs → dist/cjs}/keyring-manager.js +0 -0
  125. /package/{cjs → dist/cjs}/keyring-manager.js.map +0 -0
  126. /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js +0 -0
  127. /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js.map +0 -0
  128. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js +0 -0
  129. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js.map +0 -0
  130. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js +0 -0
  131. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js.map +0 -0
  132. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js +0 -0
  133. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js.map +0 -0
  134. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js +0 -0
  135. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js.map +0 -0
  136. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js +0 -0
  137. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +0 -0
  138. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js +0 -0
  139. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js.map +0 -0
  140. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js +0 -0
  141. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js.map +0 -0
  142. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js +0 -0
  143. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js.map +0 -0
  144. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js +0 -0
  145. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js.map +0 -0
  146. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js +0 -0
  147. /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js.map +0 -0
  148. /package/{cjs → dist/cjs}/ledger/consts.js +0 -0
  149. /package/{cjs → dist/cjs}/ledger/consts.js.map +0 -0
  150. /package/{cjs → dist/cjs}/ledger/index.js +0 -0
  151. /package/{cjs → dist/cjs}/ledger/index.js.map +0 -0
  152. /package/{cjs → dist/cjs}/ledger/types.js +0 -0
  153. /package/{cjs → dist/cjs}/ledger/types.js.map +0 -0
  154. /package/{cjs → dist/cjs}/network-utils.js +0 -0
  155. /package/{cjs → dist/cjs}/network-utils.js.map +0 -0
  156. /package/{cjs → dist/cjs}/providers.js +0 -0
  157. /package/{cjs → dist/cjs}/providers.js.map +0 -0
  158. /package/{cjs → dist/cjs}/signers.js +0 -0
  159. /package/{cjs → dist/cjs}/signers.js.map +0 -0
  160. /package/{cjs → dist/cjs}/storage.js +0 -0
  161. /package/{cjs → dist/cjs}/storage.js.map +0 -0
  162. /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js +0 -0
  163. /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js.map +0 -0
  164. /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js +0 -0
  165. /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js.map +0 -0
  166. /package/{cjs → dist/cjs}/transactions/index.js +0 -0
  167. /package/{cjs → dist/cjs}/transactions/index.js.map +0 -0
  168. /package/{cjs → dist/cjs}/transactions/syscoin.js +0 -0
  169. /package/{cjs → dist/cjs}/transactions/syscoin.js.map +0 -0
  170. /package/{cjs → dist/cjs}/trezor/index.js +0 -0
  171. /package/{cjs → dist/cjs}/trezor/index.js.map +0 -0
  172. /package/{cjs → dist/cjs}/types.js +0 -0
  173. /package/{cjs → dist/cjs}/types.js.map +0 -0
  174. /package/{cjs → dist/cjs}/utils/derivation-paths.js +0 -0
  175. /package/{cjs → dist/cjs}/utils/derivation-paths.js.map +0 -0
  176. /package/{cjs → dist/cjs}/utils/psbt.js +0 -0
  177. /package/{cjs → dist/cjs}/utils/psbt.js.map +0 -0
  178. /package/{cjs → dist/cjs}/utils.js +0 -0
  179. /package/{cjs → dist/cjs}/utils.js.map +0 -0
  180. /package/{types → dist/types}/errorUtils.d.ts +0 -0
  181. /package/{types → dist/types}/hardware-wallet-manager.d.ts +0 -0
  182. /package/{types → dist/types}/index.d.ts +0 -0
  183. /package/{types → dist/types}/initial-state.d.ts +0 -0
  184. /package/{types → dist/types}/keyring-manager.d.ts +0 -0
  185. /package/{types → dist/types}/ledger/bitcoin_client/index.d.ts +0 -0
  186. /package/{types → dist/types}/ledger/bitcoin_client/lib/bip32.d.ts +0 -0
  187. /package/{types → dist/types}/ledger/bitcoin_client/lib/buffertools.d.ts +0 -0
  188. /package/{types → dist/types}/ledger/bitcoin_client/lib/clientCommands.d.ts +0 -0
  189. /package/{types → dist/types}/ledger/bitcoin_client/lib/constants.d.ts +0 -0
  190. /package/{types → dist/types}/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +0 -0
  191. /package/{types → dist/types}/ledger/bitcoin_client/lib/merkle.d.ts +0 -0
  192. /package/{types → dist/types}/ledger/bitcoin_client/lib/merkleMap.d.ts +0 -0
  193. /package/{types → dist/types}/ledger/bitcoin_client/lib/policy.d.ts +0 -0
  194. /package/{types → dist/types}/ledger/bitcoin_client/lib/psbtv2.d.ts +0 -0
  195. /package/{types → dist/types}/ledger/bitcoin_client/lib/varint.d.ts +0 -0
  196. /package/{types → dist/types}/ledger/consts.d.ts +0 -0
  197. /package/{types → dist/types}/ledger/index.d.ts +0 -0
  198. /package/{types → dist/types}/ledger/types.d.ts +0 -0
  199. /package/{types → dist/types}/network-utils.d.ts +0 -0
  200. /package/{types → dist/types}/providers.d.ts +0 -0
  201. /package/{types → dist/types}/signers.d.ts +0 -0
  202. /package/{types → dist/types}/storage.d.ts +0 -0
  203. /package/{types → dist/types}/transactions/__tests__/integration.test.d.ts +0 -0
  204. /package/{types → dist/types}/transactions/__tests__/syscoin.test.d.ts +0 -0
  205. /package/{types → dist/types}/transactions/ethereum.d.ts +0 -0
  206. /package/{types → dist/types}/transactions/index.d.ts +0 -0
  207. /package/{types → dist/types}/transactions/syscoin.d.ts +0 -0
  208. /package/{types → dist/types}/trezor/index.d.ts +0 -0
  209. /package/{types → dist/types}/types.d.ts +0 -0
  210. /package/{types → dist/types}/utils/derivation-paths.d.ts +0 -0
  211. /package/{types → dist/types}/utils/psbt.d.ts +0 -0
  212. /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
+ }