@sidhujag/sysweb3-keyring 2.1.6 → 2.1.8
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/CHANGELOG.md +12 -0
- package/cjs/hardware-transaction-authority.js +15 -11
- package/cjs/hardware-transaction-authority.js.map +1 -1
- package/cjs/hardware-wallet-manager.js +74 -119
- package/cjs/hardware-wallet-manager.js.map +1 -1
- package/cjs/hardware-wallet-types.js +16 -0
- package/cjs/hardware-wallet-types.js.map +1 -0
- package/cjs/host-keyring.js +32 -0
- package/cjs/host-keyring.js.map +1 -0
- package/cjs/index.js +1 -0
- package/cjs/index.js.map +1 -1
- package/cjs/keyring-core.js +96 -61
- package/cjs/keyring-core.js.map +1 -1
- package/cjs/keyring-manager.js +15 -16
- package/cjs/keyring-manager.js.map +1 -1
- package/cjs/ledger/host-types.js.map +1 -1
- package/cjs/ledger/index.js +24 -4
- package/cjs/ledger/index.js.map +1 -1
- package/cjs/ledger-hardware.js +15 -0
- package/cjs/ledger-hardware.js.map +1 -0
- package/cjs/software.js +6 -25
- package/cjs/software.js.map +1 -1
- package/cjs/transactions/ethereum.js +321 -269
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/evm-envelope.js +186 -0
- package/cjs/transactions/evm-envelope.js.map +1 -0
- package/cjs/transactions/evm-submission.js +32 -4
- package/cjs/transactions/evm-submission.js.map +1 -1
- package/cjs/transactions/ledger-signing.js +44 -0
- package/cjs/transactions/ledger-signing.js.map +1 -0
- package/cjs/transactions/prepared-evm.js +44 -124
- package/cjs/transactions/prepared-evm.js.map +1 -1
- package/cjs/trezor/connect.js +238 -243
- package/cjs/trezor/connect.js.map +1 -1
- package/cjs/trezor/host-types.js +19 -0
- package/cjs/trezor/host-types.js.map +1 -0
- package/cjs/trezor/index.js +75 -23
- package/cjs/trezor/index.js.map +1 -1
- package/cjs/trezor/webextension-host.js +118 -0
- package/cjs/trezor/webextension-host.js.map +1 -0
- package/cjs/trezor-hardware.js +16 -0
- package/cjs/trezor-hardware.js.map +1 -0
- package/cjs/types.js.map +1 -1
- package/esm/hardware-transaction-authority.js +14 -11
- package/esm/hardware-transaction-authority.js.map +1 -1
- package/esm/hardware-wallet-manager.js +20 -32
- package/esm/hardware-wallet-manager.js.map +1 -1
- package/esm/hardware-wallet-types.js +13 -0
- package/esm/hardware-wallet-types.js.map +1 -0
- package/esm/keyring-core.js +98 -63
- package/esm/keyring-core.js.map +1 -1
- package/esm/ledger/dmk-host.js +2 -0
- package/esm/ledger/dmk-host.js.map +1 -1
- package/esm/ledger/dmk-session.js +73 -8
- package/esm/ledger/dmk-session.js.map +1 -1
- package/esm/ledger/host-types.js.map +1 -1
- package/esm/ledger/index.js +22 -2
- package/esm/ledger/index.js.map +1 -1
- package/esm/transactions/ethereum.js +322 -270
- package/esm/transactions/ethereum.js.map +1 -1
- package/esm/transactions/evm-envelope.js +179 -0
- package/esm/transactions/evm-envelope.js.map +1 -0
- package/esm/transactions/evm-submission.js +32 -4
- package/esm/transactions/evm-submission.js.map +1 -1
- package/esm/transactions/ledger-signing.js +41 -0
- package/esm/transactions/ledger-signing.js.map +1 -0
- package/esm/transactions/prepared-evm.js +43 -124
- package/esm/transactions/prepared-evm.js.map +1 -1
- package/esm/trezor/connect.js +236 -209
- package/esm/trezor/connect.js.map +1 -1
- package/esm/trezor/host-types.js +16 -0
- package/esm/trezor/host-types.js.map +1 -0
- package/esm/trezor/index.js +74 -22
- package/esm/trezor/index.js.map +1 -1
- package/esm/types.js.map +1 -1
- package/package.json +61 -1
- package/types/hardware-transaction-authority.d.ts +1 -0
- package/types/hardware-wallet-manager.d.ts +6 -22
- package/types/hardware-wallet-types.d.ts +21 -0
- package/types/host-keyring.d.ts +16 -0
- package/types/index.d.ts +1 -0
- package/types/keyring-core.d.ts +12 -4
- package/types/keyring-manager.d.ts +5 -3
- package/types/ledger/dmk-host.d.ts +2 -0
- package/types/ledger/dmk-session.d.ts +9 -0
- package/types/ledger/host-types.d.ts +2 -0
- package/types/ledger/index.d.ts +3 -1
- package/types/ledger-hardware.d.ts +5 -0
- package/types/software.d.ts +4 -17
- package/types/transactions/ethereum.d.ts +11 -1
- package/types/transactions/evm-envelope.d.ts +64 -0
- package/types/transactions/evm-submission.d.ts +7 -2
- package/types/transactions/ledger-signing.d.ts +13 -0
- package/types/transactions/prepared-evm.d.ts +14 -23
- package/types/trezor/connect.d.ts +11 -9
- package/types/trezor/host-types.d.ts +25 -0
- package/types/trezor/index.d.ts +7 -7
- package/types/trezor/webextension-host.d.ts +7 -0
- package/types/trezor-hardware.d.ts +6 -0
- package/types/types.d.ts +17 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { decrypt, signTypedData as signTypedDataUtil, getEncryptionPublicKey, recoverPersonalSignature, recoverTypedSignature, } from '@metamask/eth-sig-util';
|
|
2
2
|
import { INetworkType } from '@sidhujag/sysweb3-network';
|
|
3
3
|
import { createContractUsingAbi, getErc20Abi, getErc21Abi, getErc55Abi, } from '@sidhujag/sysweb3-utils';
|
|
4
|
+
import { getAddress } from 'ethers/address';
|
|
4
5
|
import { accessListify, Transaction } from 'ethers/transaction';
|
|
5
6
|
import { hexlify } from 'ethers/utils';
|
|
6
7
|
import omit from 'lodash/omit';
|
|
@@ -9,9 +10,11 @@ import { assertEvmGasLimit, bufferedEvmGasLimit, EvmGasLimitError, getEvmFeeData
|
|
|
9
10
|
import { CustomJsonRpcProvider, wrapTransactionResponse } from '../providers';
|
|
10
11
|
import { KeyringAccountType, } from '../types';
|
|
11
12
|
import { assertEvmCancellationEnvelope, authenticateRetainedCancellation, } from './evm-cancellation';
|
|
13
|
+
import { readEvmOwnData, serializePreparedEvmTransaction, snapshotPreparedEvmTransaction, } from './evm-envelope';
|
|
12
14
|
import { deriveEvmAccountFromMnemonic, parsePersonalMessage as parseLocalPersonalMessage, privateKeyToAccount, normalizeGasLimitedTransaction, sendLocalEvmTransaction, signDigestHex, signPersonalMessage as signLocalPersonalMessage, } from './evm-local-signer';
|
|
13
15
|
import { captureEvmSubmission, submitSignedEvmTransaction, submitSignedHardwareEvmTransaction, evmSubmissionFailure, copyEvmSubmissionFailure, } from './evm-submission';
|
|
14
|
-
import {
|
|
16
|
+
import { signLedgerEvmTransaction } from './ledger-signing';
|
|
17
|
+
import { signPreparedEvmEnvelope, snapshotPreparedEvmRequest, snapshotEvmSigningIdentity, preparedEvmGuard, } from './prepared-evm';
|
|
15
18
|
import { getAddressDerivationPath } from '../utils/derivation-paths';
|
|
16
19
|
const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
|
|
17
20
|
code: 'HARDWARE_SIGNER_UNAVAILABLE',
|
|
@@ -154,7 +157,7 @@ export class EthereumTransactions {
|
|
|
154
157
|
}
|
|
155
158
|
return () => assertSynchronousOriginalValidation(guard(), 'Signing context guard must be synchronous');
|
|
156
159
|
};
|
|
157
|
-
this.
|
|
160
|
+
this.captureEvmAccountContext = (target) => {
|
|
158
161
|
this.assertHardwareSignerAvailable(target);
|
|
159
162
|
const state = this.getState();
|
|
160
163
|
const selectedType = state.activeAccountType;
|
|
@@ -168,7 +171,6 @@ export class EthereumTransactions {
|
|
|
168
171
|
const account = { address: selectedAccount.address };
|
|
169
172
|
const network = { ...state.activeNetwork };
|
|
170
173
|
const assertSession = this.captureSessionGuard();
|
|
171
|
-
const provider = this.web3Provider;
|
|
172
174
|
const assertCurrent = () => {
|
|
173
175
|
assertSession();
|
|
174
176
|
const current = this.getState();
|
|
@@ -182,8 +184,7 @@ export class EthereumTransactions {
|
|
|
182
184
|
[current.activeNetwork, this.getNetwork()].some((selected) => selected.kind !== network.kind ||
|
|
183
185
|
selected.chainId !== network.chainId ||
|
|
184
186
|
selected.slip44 !== network.slip44 ||
|
|
185
|
-
selected.url !== network.url)
|
|
186
|
-
this.web3Provider !== provider)
|
|
187
|
+
selected.url !== network.url))
|
|
187
188
|
throw new Error('Wallet signing context changed');
|
|
188
189
|
};
|
|
189
190
|
assertCurrent();
|
|
@@ -192,10 +193,20 @@ export class EthereumTransactions {
|
|
|
192
193
|
activeAccountType,
|
|
193
194
|
activeAccountId,
|
|
194
195
|
activeNetwork: network,
|
|
195
|
-
provider,
|
|
196
196
|
assertCurrent,
|
|
197
197
|
};
|
|
198
198
|
};
|
|
199
|
+
this.captureEvmSigningContext = (target) => {
|
|
200
|
+
const context = this.captureEvmAccountContext(target);
|
|
201
|
+
const provider = this.web3Provider;
|
|
202
|
+
const assertCurrent = () => {
|
|
203
|
+
context.assertCurrent();
|
|
204
|
+
if (this.web3Provider !== provider)
|
|
205
|
+
throw new Error('Wallet signing context changed');
|
|
206
|
+
};
|
|
207
|
+
assertCurrent();
|
|
208
|
+
return { ...context, provider, assertCurrent };
|
|
209
|
+
};
|
|
199
210
|
this.captureSubmission = (operation, account, type, id, network, assertCurrent, details = {}) => {
|
|
200
211
|
if (!this.captureSubmissionLifecycle)
|
|
201
212
|
throw new TypeError('A durable EVM submission lifecycle is required');
|
|
@@ -296,10 +307,51 @@ export class EthereumTransactions {
|
|
|
296
307
|
throw error;
|
|
297
308
|
}
|
|
298
309
|
};
|
|
299
|
-
this.ethSign = async (params) => {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
310
|
+
this.ethSign = async (params, input) => {
|
|
311
|
+
const selected = this.captureEvmAccountContext();
|
|
312
|
+
if (!Array.isArray(params) || params.length !== 2)
|
|
313
|
+
throw new Error('Invalid digest request');
|
|
314
|
+
const first = Object.getOwnPropertyDescriptor(params, '0');
|
|
315
|
+
const second = Object.getOwnPropertyDescriptor(params, '1');
|
|
316
|
+
if (!first ||
|
|
317
|
+
!second ||
|
|
318
|
+
!('value' in first) ||
|
|
319
|
+
!('value' in second) ||
|
|
320
|
+
typeof first.value !== 'string' ||
|
|
321
|
+
typeof second.value !== 'string')
|
|
322
|
+
throw new Error('Invalid digest request');
|
|
323
|
+
params = [first.value, second.value];
|
|
324
|
+
const options = readEvmOwnData(input, [
|
|
325
|
+
'selectedAccount',
|
|
326
|
+
'targetAccount',
|
|
327
|
+
'assertCurrent',
|
|
328
|
+
]);
|
|
329
|
+
const selectedAccount = snapshotEvmSigningIdentity(options.selectedAccount);
|
|
330
|
+
const targetAccount = snapshotEvmSigningIdentity(options.targetAccount);
|
|
331
|
+
const hostGuard = preparedEvmGuard({
|
|
332
|
+
assertCurrent: options.assertCurrent,
|
|
333
|
+
});
|
|
334
|
+
const target = {
|
|
335
|
+
id: targetAccount.id,
|
|
336
|
+
type: targetAccount.type,
|
|
337
|
+
};
|
|
338
|
+
const context = this.captureEvmAccountContext(target);
|
|
339
|
+
const assertCurrent = () => {
|
|
340
|
+
hostGuard();
|
|
341
|
+
selected.assertCurrent();
|
|
342
|
+
context.assertCurrent();
|
|
343
|
+
if (selected.activeAccountId !== selectedAccount.id ||
|
|
344
|
+
selected.activeAccountType !== selectedAccount.type ||
|
|
345
|
+
getAddress(selected.activeAccount.address) !==
|
|
346
|
+
selectedAccount.expectedAddress ||
|
|
347
|
+
getAddress(context.activeAccount.address) !==
|
|
348
|
+
targetAccount.expectedAddress ||
|
|
349
|
+
targetAccount.type === 'SmartAccount')
|
|
350
|
+
throw new Error('EVM digest signing context changed');
|
|
351
|
+
};
|
|
352
|
+
assertCurrent();
|
|
353
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(target);
|
|
354
|
+
const { activeAccountId, activeAccountType, activeNetwork, activeAccount } = context;
|
|
303
355
|
// Validate that the derived address matches the active account to prevent race conditions
|
|
304
356
|
if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
305
357
|
throw {
|
|
@@ -368,14 +420,19 @@ export class EthereumTransactions {
|
|
|
368
420
|
throw error;
|
|
369
421
|
}
|
|
370
422
|
};
|
|
423
|
+
let signature;
|
|
371
424
|
switch (activeAccountType) {
|
|
372
425
|
case KeyringAccountType.Trezor:
|
|
373
|
-
|
|
426
|
+
signature = await signWithTrezor();
|
|
427
|
+
break;
|
|
374
428
|
case KeyringAccountType.Ledger:
|
|
375
|
-
|
|
429
|
+
signature = await signWithLedger();
|
|
430
|
+
break;
|
|
376
431
|
default:
|
|
377
|
-
|
|
432
|
+
signature = sign();
|
|
378
433
|
}
|
|
434
|
+
assertCurrent();
|
|
435
|
+
return signature;
|
|
379
436
|
};
|
|
380
437
|
this.signPersonalMessage = async (params) => {
|
|
381
438
|
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
@@ -945,33 +1002,18 @@ export class EthereumTransactions {
|
|
|
945
1002
|
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
946
1003
|
if (retained)
|
|
947
1004
|
assertCancellationEnvelope(rawTx);
|
|
948
|
-
const
|
|
949
|
-
|
|
1005
|
+
const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
|
|
1006
|
+
unsignedTransaction: rawTx,
|
|
950
1007
|
accountIndex: activeAccountId,
|
|
951
1008
|
expectedAddress: activeAccount.address,
|
|
952
1009
|
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
953
1010
|
assertCurrent();
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1011
|
+
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1012
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
1013
|
+
return {
|
|
1014
|
+
isCanceled: true,
|
|
1015
|
+
transaction: transactionResponse,
|
|
958
1016
|
};
|
|
959
|
-
if (signature) {
|
|
960
|
-
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
961
|
-
assertCurrent();
|
|
962
|
-
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
963
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
964
|
-
return {
|
|
965
|
-
isCanceled: true,
|
|
966
|
-
transaction: transactionResponse,
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
else {
|
|
970
|
-
return {
|
|
971
|
-
isCanceled: false,
|
|
972
|
-
error: true,
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
1017
|
}
|
|
976
1018
|
catch (error) {
|
|
977
1019
|
return {
|
|
@@ -1102,12 +1144,21 @@ export class EthereumTransactions {
|
|
|
1102
1144
|
typeof options !== 'object' ||
|
|
1103
1145
|
Array.isArray(options) ||
|
|
1104
1146
|
(options.mode !== 'reviewed-native' &&
|
|
1147
|
+
options.mode !== 'reviewed-generic' &&
|
|
1105
1148
|
options.mode !== 'specialized') ||
|
|
1106
1149
|
typeof options.assertCurrent !== 'function')
|
|
1107
1150
|
throw new Error('Explicit transaction approval is required');
|
|
1108
1151
|
// Capture once; later caller mutations cannot replace this invocation's
|
|
1109
1152
|
// admission decision while chain checks or device approval are pending.
|
|
1110
1153
|
const { assertCurrent: assertHostCurrent, mode, walletNonce } = options;
|
|
1154
|
+
const reviewId = mode === 'reviewed-generic' ? options.reviewId : undefined;
|
|
1155
|
+
if (mode === 'reviewed-generic' &&
|
|
1156
|
+
(typeof reviewId !== 'string' ||
|
|
1157
|
+
!/^[A-Za-z0-9_-]{1,128}$/.test(reviewId) ||
|
|
1158
|
+
walletNonce === undefined))
|
|
1159
|
+
throw new Error('An issued fee review and approved nonce are required');
|
|
1160
|
+
const selectedAccount = snapshotEvmSigningIdentity(options.selectedAccount);
|
|
1161
|
+
const targetAccount = snapshotEvmSigningIdentity(options.targetAccount);
|
|
1111
1162
|
const nonceApproval = walletNonce === undefined ? undefined : { ...walletNonce };
|
|
1112
1163
|
const assertApproved = () => {
|
|
1113
1164
|
const result = assertHostCurrent();
|
|
@@ -1119,14 +1170,14 @@ export class EthereumTransactions {
|
|
|
1119
1170
|
assertApproved();
|
|
1120
1171
|
return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
|
|
1121
1172
|
broadcastAttempted = true;
|
|
1122
|
-
}, assertApproved, mode, nonceApproval);
|
|
1173
|
+
}, assertApproved, mode, nonceApproval, selectedAccount, targetAccount, reviewId);
|
|
1123
1174
|
}
|
|
1124
1175
|
catch (error) {
|
|
1125
1176
|
// A broadcast transport error may mean the node accepted the signed
|
|
1126
1177
|
// transaction. Derive this marker from this invocation, never RPC data.
|
|
1127
1178
|
const submissionError = Object.assign(new Error(error instanceof Error ? error.message : String(error)), error, {
|
|
1128
1179
|
transactionNotBroadcast: !broadcastAttempted ||
|
|
1129
|
-
evmSubmissionFailure(error)
|
|
1180
|
+
evmSubmissionFailure(error)?.transactionNotBroadcast === true,
|
|
1130
1181
|
});
|
|
1131
1182
|
if (error instanceof Error) {
|
|
1132
1183
|
submissionError.name = error.name;
|
|
@@ -1136,19 +1187,60 @@ export class EthereumTransactions {
|
|
|
1136
1187
|
throw submissionError;
|
|
1137
1188
|
}
|
|
1138
1189
|
};
|
|
1139
|
-
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce) => {
|
|
1190
|
+
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce, selectedIdentity, targetIdentity, reviewId) => {
|
|
1140
1191
|
// Hardware approval may outlive the selected account, network or unlocked
|
|
1141
1192
|
// session. Keep immutable identities and one provider for this invocation.
|
|
1142
|
-
const
|
|
1193
|
+
const selectedContext = this.captureEvmAccountContext();
|
|
1194
|
+
const targetAccount = {
|
|
1195
|
+
id: targetIdentity.id,
|
|
1196
|
+
type: targetIdentity.type,
|
|
1197
|
+
};
|
|
1198
|
+
if (targetAccount.type === KeyringAccountType.SmartAccount)
|
|
1199
|
+
throw new Error('Invalid EVM payer');
|
|
1200
|
+
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext(targetAccount);
|
|
1143
1201
|
const assertSigningCurrent = () => {
|
|
1144
1202
|
assertHostCurrent();
|
|
1203
|
+
selectedContext.assertCurrent();
|
|
1145
1204
|
assertKeyringCurrent();
|
|
1205
|
+
if (selectedContext.activeAccountId !== selectedIdentity.id ||
|
|
1206
|
+
selectedContext.activeAccountType !== selectedIdentity.type ||
|
|
1207
|
+
getAddress(selectedContext.activeAccount.address) !==
|
|
1208
|
+
selectedIdentity.expectedAddress ||
|
|
1209
|
+
getAddress(activeAccount.address) !== targetIdentity.expectedAddress)
|
|
1210
|
+
throw new Error('EVM payer approval context changed');
|
|
1146
1211
|
};
|
|
1212
|
+
assertSigningCurrent();
|
|
1213
|
+
// Generic reviewed calls keep the exact prepared envelope, including
|
|
1214
|
+
// contract creation, calldata and access lists, through existing signers.
|
|
1215
|
+
const reviewed = mode === 'reviewed-generic'
|
|
1216
|
+
? snapshotPreparedEvmTransaction(input)
|
|
1217
|
+
: undefined;
|
|
1218
|
+
if (reviewed) {
|
|
1219
|
+
if (reviewed.from !== targetIdentity.expectedAddress ||
|
|
1220
|
+
reviewed.chainId !== String(activeNetwork.chainId) ||
|
|
1221
|
+
reviewed.nonce !== String(walletNonce?.nonce) ||
|
|
1222
|
+
(isLegacy !== undefined && isLegacy !== (reviewed.type === 0)) ||
|
|
1223
|
+
(this.gasOverrides.minGasLimit &&
|
|
1224
|
+
BigInt(reviewed.gasLimit) < this.gasOverrides.minGasLimit.toBigInt()))
|
|
1225
|
+
throw new Error('Reviewed EVM transaction does not match its approval');
|
|
1226
|
+
input = reviewed;
|
|
1227
|
+
isLegacy = reviewed.type === 0;
|
|
1228
|
+
}
|
|
1147
1229
|
const assertBeforeBroadcast = () => {
|
|
1148
1230
|
assertCurrent();
|
|
1149
1231
|
beforeBroadcast();
|
|
1150
1232
|
};
|
|
1151
|
-
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
|
|
1233
|
+
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
|
|
1234
|
+
walletNonce,
|
|
1235
|
+
...(reviewed
|
|
1236
|
+
? {
|
|
1237
|
+
review: {
|
|
1238
|
+
id: reviewId,
|
|
1239
|
+
unsignedHash: Transaction.from(serializePreparedEvmTransaction(reviewed)).unsignedHash,
|
|
1240
|
+
},
|
|
1241
|
+
}
|
|
1242
|
+
: {}),
|
|
1243
|
+
});
|
|
1152
1244
|
const assertCurrent = submission.assertCurrent;
|
|
1153
1245
|
const approvedNonce = submission.context.walletNonce?.nonce;
|
|
1154
1246
|
const assertApprovedNonce = (nonce) => {
|
|
@@ -1157,6 +1249,7 @@ export class EthereumTransactions {
|
|
|
1157
1249
|
};
|
|
1158
1250
|
if (mode === 'reviewed-native') {
|
|
1159
1251
|
const { transaction } = snapshotPreparedEvmRequest({
|
|
1252
|
+
selectedAccount: selectedIdentity,
|
|
1160
1253
|
account: {
|
|
1161
1254
|
id: activeAccountId,
|
|
1162
1255
|
type: activeAccountType,
|
|
@@ -1172,7 +1265,10 @@ export class EthereumTransactions {
|
|
|
1172
1265
|
});
|
|
1173
1266
|
assertApprovedNonce(Number(transaction.nonce));
|
|
1174
1267
|
const minimum = this.gasOverrides.minGasLimit;
|
|
1175
|
-
if (transaction.
|
|
1268
|
+
if (transaction.to === null ||
|
|
1269
|
+
/^0x0{40}$/i.test(transaction.to) ||
|
|
1270
|
+
(transaction.type === 2 && transaction.accessList.length !== 0) ||
|
|
1271
|
+
transaction.data !== '0x' ||
|
|
1176
1272
|
BigInt(transaction.value) === 0n ||
|
|
1177
1273
|
BigInt(transaction.gasLimit) < 21000n ||
|
|
1178
1274
|
(transaction.type === 2 && BigInt(transaction.maxFeePerGas) === 0n) ||
|
|
@@ -1184,7 +1280,7 @@ export class EthereumTransactions {
|
|
|
1184
1280
|
assertCurrent();
|
|
1185
1281
|
if (this.gasOverrides.minGasLimit !== minimum)
|
|
1186
1282
|
throw new Error('Reviewed native gas policy changed');
|
|
1187
|
-
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1283
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
|
|
1188
1284
|
if (address.toLowerCase() !== transaction.from.toLowerCase())
|
|
1189
1285
|
throw new Error('Reviewed native signer does not match the approved account');
|
|
1190
1286
|
assertCurrent();
|
|
@@ -1197,6 +1293,9 @@ export class EthereumTransactions {
|
|
|
1197
1293
|
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
1198
1294
|
throw new TypeError('Invalid EVM transaction request');
|
|
1199
1295
|
const supplied = { ...params };
|
|
1296
|
+
if (supplied.from !== undefined &&
|
|
1297
|
+
getAddress(supplied.from) !== targetIdentity.expectedAddress)
|
|
1298
|
+
throw new Error('EVM transaction sender does not match the approved payer');
|
|
1200
1299
|
// Validate original scalar spellings before conversion. Normalization's
|
|
1201
1300
|
// permissive zero coercion must not bypass the bounded wire parser.
|
|
1202
1301
|
for (const field of [
|
|
@@ -1249,7 +1348,8 @@ export class EthereumTransactions {
|
|
|
1249
1348
|
const validateEip1559Fees = () => {
|
|
1250
1349
|
const maximum = nonNegativeFeeQuantity(params.maxFeePerGas, 'maximum fee');
|
|
1251
1350
|
const priority = nonNegativeFeeQuantity(params.maxPriorityFeePerGas, 'priority fee');
|
|
1252
|
-
if (maximum.isZero() ||
|
|
1351
|
+
if ((mode !== 'reviewed-generic' && maximum.isZero()) ||
|
|
1352
|
+
priority.gt(maximum))
|
|
1253
1353
|
throw new Error('Invalid EIP-1559 fees');
|
|
1254
1354
|
params.maxFeePerGas = maximum;
|
|
1255
1355
|
params.maxPriorityFeePerGas = priority;
|
|
@@ -1260,7 +1360,9 @@ export class EthereumTransactions {
|
|
|
1260
1360
|
validateEip1559Fees();
|
|
1261
1361
|
// Only apply a minimum gas limit when a network-specific override is set.
|
|
1262
1362
|
// Otherwise keep the user/dapp-approved gas limit unchanged.
|
|
1263
|
-
if (
|
|
1363
|
+
if (mode !== 'reviewed-generic' &&
|
|
1364
|
+
params.gasLimit != null &&
|
|
1365
|
+
this.gasOverrides.minGasLimit) {
|
|
1264
1366
|
const minGasLimit = this.gasOverrides.minGasLimit;
|
|
1265
1367
|
const currentGasLimit = BigNumber.from(params.gasLimit);
|
|
1266
1368
|
if (currentGasLimit.lt(minGasLimit)) {
|
|
@@ -1331,31 +1433,15 @@ export class EthereumTransactions {
|
|
|
1331
1433
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1332
1434
|
};
|
|
1333
1435
|
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1436
|
+
const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
|
|
1437
|
+
unsignedTransaction: rawTx,
|
|
1336
1438
|
accountIndex: activeAccountId,
|
|
1337
1439
|
expectedAddress: activeAccount.address,
|
|
1338
1440
|
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1339
1441
|
assertCurrent();
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
v: parseInt(signature.v, 16),
|
|
1344
|
-
};
|
|
1345
|
-
if (signature) {
|
|
1346
|
-
try {
|
|
1347
|
-
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1348
|
-
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1349
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1350
|
-
return finalTx;
|
|
1351
|
-
}
|
|
1352
|
-
catch (error) {
|
|
1353
|
-
throw error;
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
else {
|
|
1357
|
-
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
1358
|
-
}
|
|
1442
|
+
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1443
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1444
|
+
return finalTx;
|
|
1359
1445
|
};
|
|
1360
1446
|
const sendEVMTrezorTransaction = async () => {
|
|
1361
1447
|
assertCurrent();
|
|
@@ -1449,7 +1535,7 @@ export class EthereumTransactions {
|
|
|
1449
1535
|
};
|
|
1450
1536
|
const sendEVMTransaction = async () => {
|
|
1451
1537
|
assertCurrent();
|
|
1452
|
-
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1538
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
|
|
1453
1539
|
// Validate that we have the correct private key for the active account to prevent race conditions
|
|
1454
1540
|
// This is critical for transaction security during account switches
|
|
1455
1541
|
if (address.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
@@ -1706,32 +1792,18 @@ export class EthereumTransactions {
|
|
|
1706
1792
|
};
|
|
1707
1793
|
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1708
1794
|
assertCurrent();
|
|
1709
|
-
const
|
|
1710
|
-
|
|
1795
|
+
const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
|
|
1796
|
+
unsignedTransaction: rawTx,
|
|
1711
1797
|
accountIndex: activeAccountId,
|
|
1712
1798
|
expectedAddress: activeAccount.address,
|
|
1713
1799
|
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1800
|
+
assertSignedSender(signedTx);
|
|
1801
|
+
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1802
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1803
|
+
return {
|
|
1804
|
+
isSpeedUp: true,
|
|
1805
|
+
transaction: transactionResponse,
|
|
1718
1806
|
};
|
|
1719
|
-
if (signature) {
|
|
1720
|
-
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1721
|
-
assertSignedSender(signedTx);
|
|
1722
|
-
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1723
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1724
|
-
return {
|
|
1725
|
-
isSpeedUp: true,
|
|
1726
|
-
transaction: transactionResponse,
|
|
1727
|
-
};
|
|
1728
|
-
}
|
|
1729
|
-
else {
|
|
1730
|
-
return {
|
|
1731
|
-
isSpeedUp: false,
|
|
1732
|
-
error: true,
|
|
1733
|
-
};
|
|
1734
|
-
}
|
|
1735
1807
|
}
|
|
1736
1808
|
catch (error) {
|
|
1737
1809
|
console.error('[SpeedUp] Failed to send replacement transaction with Ledger:', error);
|
|
@@ -1944,73 +2016,51 @@ export class EthereumTransactions {
|
|
|
1944
2016
|
};
|
|
1945
2017
|
const sendERC20TokenOnLedger = async () => {
|
|
1946
2018
|
assertCurrent();
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
type: 0,
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
else {
|
|
1967
|
-
txFormattedForEthers = {
|
|
1968
|
-
to: tokenAddress,
|
|
1969
|
-
value: '0x0',
|
|
1970
|
-
gasLimit: gasLimit,
|
|
1971
|
-
maxFeePerGas,
|
|
1972
|
-
maxPriorityFeePerGas,
|
|
1973
|
-
data: txData,
|
|
1974
|
-
nonce,
|
|
1975
|
-
chainId: activeNetwork.chainId,
|
|
1976
|
-
type: 2, // Need explicit type for hardware wallet serialization
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
1979
|
-
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1980
|
-
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1981
|
-
rawTx: rawTx.replace('0x', ''),
|
|
1982
|
-
accountIndex: activeAccountId,
|
|
1983
|
-
expectedAddress: activeAccount.address,
|
|
1984
|
-
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
1985
|
-
assertCurrent();
|
|
1986
|
-
const formattedSignature = {
|
|
1987
|
-
r: `0x${signature.r}`,
|
|
1988
|
-
s: `0x${signature.s}`,
|
|
1989
|
-
v: parseInt(signature.v, 16),
|
|
2019
|
+
const _contract = new Contract(tokenAddress, getErc20Abi(), provider);
|
|
2020
|
+
const txData = _contract.interface.encodeFunctionData('transfer', [
|
|
2021
|
+
receiver,
|
|
2022
|
+
exactTokenAmount,
|
|
2023
|
+
]);
|
|
2024
|
+
let txFormattedForEthers;
|
|
2025
|
+
if (isLegacy) {
|
|
2026
|
+
txFormattedForEthers = {
|
|
2027
|
+
to: tokenAddress,
|
|
2028
|
+
value: '0x0',
|
|
2029
|
+
gasLimit: gasLimit,
|
|
2030
|
+
gasPrice,
|
|
2031
|
+
data: txData,
|
|
2032
|
+
nonce,
|
|
2033
|
+
chainId: activeNetwork.chainId,
|
|
2034
|
+
type: 0,
|
|
1990
2035
|
};
|
|
1991
|
-
if (signature) {
|
|
1992
|
-
try {
|
|
1993
|
-
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1994
|
-
assertCurrent();
|
|
1995
|
-
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1996
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
|
|
1997
|
-
const response = await admit(() => provider.sendTransaction(rawTransaction));
|
|
1998
|
-
saveTrezorTx && saveTrezorTx(response);
|
|
1999
|
-
return response;
|
|
2000
|
-
}, submission, undefined, rawTx);
|
|
2001
|
-
return finalTx;
|
|
2002
|
-
}
|
|
2003
|
-
catch (error) {
|
|
2004
|
-
throw error;
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
else {
|
|
2008
|
-
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
2009
|
-
}
|
|
2010
2036
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2037
|
+
else {
|
|
2038
|
+
txFormattedForEthers = {
|
|
2039
|
+
to: tokenAddress,
|
|
2040
|
+
value: '0x0',
|
|
2041
|
+
gasLimit: gasLimit,
|
|
2042
|
+
maxFeePerGas,
|
|
2043
|
+
maxPriorityFeePerGas,
|
|
2044
|
+
data: txData,
|
|
2045
|
+
nonce,
|
|
2046
|
+
chainId: activeNetwork.chainId,
|
|
2047
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
2048
|
+
};
|
|
2013
2049
|
}
|
|
2050
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2051
|
+
const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
|
|
2052
|
+
unsignedTransaction: rawTx,
|
|
2053
|
+
accountIndex: activeAccountId,
|
|
2054
|
+
expectedAddress: activeAccount.address,
|
|
2055
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2056
|
+
assertCurrent();
|
|
2057
|
+
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2058
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
|
|
2059
|
+
const response = await admit(() => provider.sendTransaction(rawTransaction));
|
|
2060
|
+
saveTrezorTx && saveTrezorTx(response);
|
|
2061
|
+
return response;
|
|
2062
|
+
}, submission, undefined, rawTx);
|
|
2063
|
+
return finalTx;
|
|
2014
2064
|
};
|
|
2015
2065
|
const sendERC20TokenOnTrezor = async () => {
|
|
2016
2066
|
assertCurrent();
|
|
@@ -2171,70 +2221,48 @@ export class EthereumTransactions {
|
|
|
2171
2221
|
};
|
|
2172
2222
|
const sendERC721TokenOnLedger = async () => {
|
|
2173
2223
|
assertCurrent();
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
type: 0,
|
|
2192
|
-
};
|
|
2193
|
-
}
|
|
2194
|
-
else {
|
|
2195
|
-
txFormattedForEthers = {
|
|
2196
|
-
to: tokenAddress,
|
|
2197
|
-
value: '0x0',
|
|
2198
|
-
gasLimit,
|
|
2199
|
-
maxFeePerGas,
|
|
2200
|
-
maxPriorityFeePerGas,
|
|
2201
|
-
data: txData,
|
|
2202
|
-
nonce,
|
|
2203
|
-
chainId: activeNetwork.chainId,
|
|
2204
|
-
type: 2, // Need explicit type for hardware wallet serialization
|
|
2205
|
-
};
|
|
2206
|
-
}
|
|
2207
|
-
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2208
|
-
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
2209
|
-
rawTx: rawTx.replace('0x', ''),
|
|
2210
|
-
accountIndex: activeAccountId,
|
|
2211
|
-
expectedAddress: activeAccount.address,
|
|
2212
|
-
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2213
|
-
assertCurrent();
|
|
2214
|
-
const formattedSignature = {
|
|
2215
|
-
r: `0x${signature.r}`,
|
|
2216
|
-
s: `0x${signature.s}`,
|
|
2217
|
-
v: parseInt(signature.v, 16),
|
|
2224
|
+
const _contract = new Contract(tokenAddress, getErc21Abi(), provider);
|
|
2225
|
+
const txData = _contract.interface.encodeFunctionData('transferFrom', [
|
|
2226
|
+
activeAccountAddress,
|
|
2227
|
+
receiver,
|
|
2228
|
+
normalizedTokenId,
|
|
2229
|
+
]);
|
|
2230
|
+
let txFormattedForEthers;
|
|
2231
|
+
if (isLegacy) {
|
|
2232
|
+
txFormattedForEthers = {
|
|
2233
|
+
to: tokenAddress,
|
|
2234
|
+
value: '0x0',
|
|
2235
|
+
gasLimit,
|
|
2236
|
+
gasPrice,
|
|
2237
|
+
data: txData,
|
|
2238
|
+
nonce,
|
|
2239
|
+
chainId: activeNetwork.chainId,
|
|
2240
|
+
type: 0,
|
|
2218
2241
|
};
|
|
2219
|
-
if (signature) {
|
|
2220
|
-
try {
|
|
2221
|
-
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2222
|
-
assertCurrent();
|
|
2223
|
-
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2224
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2225
|
-
return finalTx;
|
|
2226
|
-
}
|
|
2227
|
-
catch (error) {
|
|
2228
|
-
throw error;
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
else {
|
|
2232
|
-
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
2233
|
-
}
|
|
2234
2242
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2243
|
+
else {
|
|
2244
|
+
txFormattedForEthers = {
|
|
2245
|
+
to: tokenAddress,
|
|
2246
|
+
value: '0x0',
|
|
2247
|
+
gasLimit,
|
|
2248
|
+
maxFeePerGas,
|
|
2249
|
+
maxPriorityFeePerGas,
|
|
2250
|
+
data: txData,
|
|
2251
|
+
nonce,
|
|
2252
|
+
chainId: activeNetwork.chainId,
|
|
2253
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
2254
|
+
};
|
|
2237
2255
|
}
|
|
2256
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2257
|
+
const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
|
|
2258
|
+
unsignedTransaction: rawTx,
|
|
2259
|
+
accountIndex: activeAccountId,
|
|
2260
|
+
expectedAddress: activeAccount.address,
|
|
2261
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2262
|
+
assertCurrent();
|
|
2263
|
+
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2264
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2265
|
+
return finalTx;
|
|
2238
2266
|
};
|
|
2239
2267
|
const sendERC721TokenOnTrezor = async () => {
|
|
2240
2268
|
assertCurrent();
|
|
@@ -2401,66 +2429,44 @@ export class EthereumTransactions {
|
|
|
2401
2429
|
};
|
|
2402
2430
|
const sendERC1155TokenOnLedger = async () => {
|
|
2403
2431
|
assertCurrent();
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
type: 0,
|
|
2418
|
-
};
|
|
2419
|
-
}
|
|
2420
|
-
else {
|
|
2421
|
-
txFormattedForEthers = {
|
|
2422
|
-
to: tokenAddress,
|
|
2423
|
-
value: '0x0',
|
|
2424
|
-
gasLimit,
|
|
2425
|
-
maxFeePerGas,
|
|
2426
|
-
maxPriorityFeePerGas,
|
|
2427
|
-
data: txData,
|
|
2428
|
-
nonce,
|
|
2429
|
-
chainId: activeNetwork.chainId,
|
|
2430
|
-
type: 2, // Need explicit type for hardware wallet serialization
|
|
2431
|
-
};
|
|
2432
|
-
}
|
|
2433
|
-
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2434
|
-
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
2435
|
-
rawTx: rawTx.replace('0x', ''),
|
|
2436
|
-
accountIndex: activeAccountId,
|
|
2437
|
-
expectedAddress: activeAccount.address,
|
|
2438
|
-
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2439
|
-
assertCurrent();
|
|
2440
|
-
const formattedSignature = {
|
|
2441
|
-
r: `0x${signature.r}`,
|
|
2442
|
-
s: `0x${signature.s}`,
|
|
2443
|
-
v: parseInt(signature.v, 16),
|
|
2432
|
+
const _contract = new Contract(tokenAddress, getErc55Abi(), provider);
|
|
2433
|
+
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
|
|
2434
|
+
let txFormattedForEthers;
|
|
2435
|
+
if (isLegacy) {
|
|
2436
|
+
txFormattedForEthers = {
|
|
2437
|
+
to: tokenAddress,
|
|
2438
|
+
value: '0x0',
|
|
2439
|
+
gasLimit,
|
|
2440
|
+
gasPrice,
|
|
2441
|
+
data: txData,
|
|
2442
|
+
nonce,
|
|
2443
|
+
chainId: activeNetwork.chainId,
|
|
2444
|
+
type: 0,
|
|
2444
2445
|
};
|
|
2445
|
-
if (signature) {
|
|
2446
|
-
try {
|
|
2447
|
-
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2448
|
-
assertCurrent();
|
|
2449
|
-
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2450
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2451
|
-
return finalTx;
|
|
2452
|
-
}
|
|
2453
|
-
catch (error) {
|
|
2454
|
-
throw error;
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
else {
|
|
2458
|
-
throw new Error(`Transaction Signature Failed. Error: ${signature}`);
|
|
2459
|
-
}
|
|
2460
2446
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2447
|
+
else {
|
|
2448
|
+
txFormattedForEthers = {
|
|
2449
|
+
to: tokenAddress,
|
|
2450
|
+
value: '0x0',
|
|
2451
|
+
gasLimit,
|
|
2452
|
+
maxFeePerGas,
|
|
2453
|
+
maxPriorityFeePerGas,
|
|
2454
|
+
data: txData,
|
|
2455
|
+
nonce,
|
|
2456
|
+
chainId: activeNetwork.chainId,
|
|
2457
|
+
type: 2, // Need explicit type for hardware wallet serialization
|
|
2458
|
+
};
|
|
2463
2459
|
}
|
|
2460
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2461
|
+
const { rawTransaction: signedTx } = await signLedgerEvmTransaction(this.requireLedgerSigner(), {
|
|
2462
|
+
unsignedTransaction: rawTx,
|
|
2463
|
+
accountIndex: activeAccountId,
|
|
2464
|
+
expectedAddress: activeAccount.address,
|
|
2465
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2466
|
+
assertCurrent();
|
|
2467
|
+
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2468
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2469
|
+
return finalTx;
|
|
2464
2470
|
};
|
|
2465
2471
|
const sendERC1155TokenOnTrezor = async () => {
|
|
2466
2472
|
assertCurrent();
|
|
@@ -2782,6 +2788,52 @@ export class EthereumTransactions {
|
|
|
2782
2788
|
console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
|
|
2783
2789
|
}
|
|
2784
2790
|
}
|
|
2791
|
+
signPreparedEvmTransaction(input, options) {
|
|
2792
|
+
const selected = this.captureEvmAccountContext();
|
|
2793
|
+
const request = snapshotPreparedEvmRequest(input);
|
|
2794
|
+
const target = {
|
|
2795
|
+
id: request.account.id,
|
|
2796
|
+
type: request.account.type,
|
|
2797
|
+
};
|
|
2798
|
+
const context = this.captureEvmAccountContext(target);
|
|
2799
|
+
const hostGuard = preparedEvmGuard(options);
|
|
2800
|
+
const targetRecord = this.getState().accounts[target.type]?.[target.id];
|
|
2801
|
+
const encryptedKey = targetRecord?.xprv;
|
|
2802
|
+
const assertCurrent = () => {
|
|
2803
|
+
selected.assertCurrent();
|
|
2804
|
+
context.assertCurrent();
|
|
2805
|
+
const live = this.getState().accounts[target.type]?.[target.id];
|
|
2806
|
+
if (!live ||
|
|
2807
|
+
live.id !== target.id ||
|
|
2808
|
+
live.isSmartAccount ||
|
|
2809
|
+
live.isLedgerWallet ||
|
|
2810
|
+
live.isTrezorWallet ||
|
|
2811
|
+
typeof encryptedKey !== 'string' ||
|
|
2812
|
+
!encryptedKey ||
|
|
2813
|
+
live.xprv !== encryptedKey ||
|
|
2814
|
+
selected.activeAccountId !== request.selectedAccount.id ||
|
|
2815
|
+
selected.activeAccountType !== request.selectedAccount.type ||
|
|
2816
|
+
getAddress(selected.activeAccount.address) !==
|
|
2817
|
+
request.selectedAccount.expectedAddress ||
|
|
2818
|
+
getAddress(context.activeAccount.address) !==
|
|
2819
|
+
request.account.expectedAddress ||
|
|
2820
|
+
context.activeNetwork.kind !== 'ethereum' ||
|
|
2821
|
+
context.activeNetwork.slip44 !== 60 ||
|
|
2822
|
+
String(context.activeNetwork.chainId) !== request.network.chainId ||
|
|
2823
|
+
context.activeNetwork.url !== request.network.url)
|
|
2824
|
+
throw new Error('Prepared EVM signing context changed or is unsupported');
|
|
2825
|
+
};
|
|
2826
|
+
assertCurrent();
|
|
2827
|
+
hostGuard();
|
|
2828
|
+
assertCurrent();
|
|
2829
|
+
const key = this.getDecryptedPrivateKey(target);
|
|
2830
|
+
if (getAddress(key.address) !== request.account.expectedAddress)
|
|
2831
|
+
throw new Error('Prepared EVM signer address mismatch');
|
|
2832
|
+
const { signed } = signPreparedEvmEnvelope(request.transaction, key.decryptedPrivateKey);
|
|
2833
|
+
hostGuard();
|
|
2834
|
+
assertCurrent();
|
|
2835
|
+
return signed;
|
|
2836
|
+
}
|
|
2785
2837
|
setWeb3Provider(network) {
|
|
2786
2838
|
this.abortController.abort();
|
|
2787
2839
|
this.abortController = new AbortController();
|