@sidhujag/sysweb3-keyring 2.1.0 → 2.1.2
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 +14 -0
- package/README.md +46 -108
- package/cjs/evm-fees.js +223 -0
- package/cjs/evm-fees.js.map +1 -0
- package/cjs/keyring-core.js +1 -227
- package/cjs/keyring-core.js.map +1 -1
- package/cjs/keyring-manager.js +4 -3
- package/cjs/keyring-manager.js.map +1 -1
- package/cjs/software.js +12 -11
- package/cjs/software.js.map +1 -1
- package/cjs/transactions/ethereum.js +268 -195
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/evm-local-signer.js +31 -8
- package/cjs/transactions/evm-local-signer.js.map +1 -1
- package/cjs/transactions/evm-submission.js +200 -0
- package/cjs/transactions/evm-submission.js.map +1 -0
- package/cjs/transactions/index.js +1 -0
- package/cjs/transactions/index.js.map +1 -1
- package/cjs/transactions/prepared-evm.js +5 -6
- package/cjs/transactions/prepared-evm.js.map +1 -1
- package/cjs/types.js.map +1 -1
- package/cjs/utils/derivation-paths.js +4 -3
- package/cjs/utils/derivation-paths.js.map +1 -1
- package/cjs/utils/watch-descriptor.js +3 -2
- package/cjs/utils/watch-descriptor.js.map +1 -1
- package/esm/types.js.map +1 -1
- package/esm/utils/derivation-paths.js +2 -1
- package/esm/utils/derivation-paths.js.map +1 -1
- package/package.json +24 -11
- package/types/evm-fees.d.ts +42 -0
- package/types/keyring-core.d.ts +3 -8
- package/types/keyring-manager.d.ts +8 -3
- package/types/software.d.ts +6 -11
- package/types/transactions/ethereum.d.ts +10 -8
- package/types/transactions/evm-local-signer.d.ts +4 -1
- package/types/transactions/evm-submission.d.ts +51 -0
- package/types/transactions/index.d.ts +1 -0
- package/types/types.d.ts +4 -0
- package/cjs/bip84-native.js +0 -8
- package/cjs/bip84-native.js.map +0 -1
- package/cjs/public-bip84.js +0 -157
- package/cjs/public-bip84.js.map +0 -1
- package/cjs/software-utxo-account.js +0 -12
- package/cjs/software-utxo-account.js.map +0 -1
- package/cjs/transactions/bip84-native-plan.js +0 -481
- package/cjs/transactions/bip84-native-plan.js.map +0 -1
- package/cjs/transactions/prepared-bip84.js +0 -214
- package/cjs/transactions/prepared-bip84.js.map +0 -1
- package/types/bip84-native.d.ts +0 -3
- package/types/public-bip84.d.ts +0 -39
- package/types/software-utxo-account.d.ts +0 -13
- package/types/transactions/bip84-native-plan.d.ts +0 -83
- package/types/transactions/prepared-bip84.d.ts +0 -27
|
@@ -8,11 +8,14 @@ const eth_sig_util_1 = require("@metamask/eth-sig-util");
|
|
|
8
8
|
const sysweb3_network_1 = require("@sidhujag/sysweb3-network");
|
|
9
9
|
const sysweb3_utils_1 = require("@sidhujag/sysweb3-utils");
|
|
10
10
|
const transaction_1 = require("ethers/transaction");
|
|
11
|
+
const utils_1 = require("ethers/utils");
|
|
11
12
|
const omit_1 = __importDefault(require("lodash/omit"));
|
|
12
13
|
const ethers_v6_1 = require("../ethers-v6");
|
|
14
|
+
const evm_fees_1 = require("../evm-fees");
|
|
13
15
|
const providers_1 = require("../providers");
|
|
14
16
|
const types_1 = require("../types");
|
|
15
17
|
const evm_local_signer_1 = require("./evm-local-signer");
|
|
18
|
+
const evm_submission_1 = require("./evm-submission");
|
|
16
19
|
const derivation_paths_1 = require("../utils/derivation-paths");
|
|
17
20
|
const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
|
|
18
21
|
code: 'HARDWARE_SIGNER_UNAVAILABLE',
|
|
@@ -25,7 +28,11 @@ const assertHardwareTransactionSender = (signedTx, expectedAddress) => {
|
|
|
25
28
|
});
|
|
26
29
|
}
|
|
27
30
|
};
|
|
28
|
-
const stripHexPrefix = (value) =>
|
|
31
|
+
const stripHexPrefix = (value) => {
|
|
32
|
+
return value.startsWith('0x') || value.startsWith('0X')
|
|
33
|
+
? value.slice(2)
|
|
34
|
+
: value;
|
|
35
|
+
};
|
|
29
36
|
// Connect returns hexadecimal signature fields; normalize their encoding
|
|
30
37
|
// without mutating the response or reparsing an already numeric recovery v.
|
|
31
38
|
const trezorSignature = (signature) => {
|
|
@@ -47,55 +54,14 @@ const trezorSignature = (signature) => {
|
|
|
47
54
|
};
|
|
48
55
|
};
|
|
49
56
|
const normalizeEthersOverrideValue = (value) => (0, ethers_v6_1.normalizeTxValue)(value);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
value =
|
|
57
|
-
objectValue._hex ??
|
|
58
|
-
objectValue.hex ??
|
|
59
|
-
(typeof objectValue.toString === 'function'
|
|
60
|
-
? objectValue.toString()
|
|
61
|
-
: undefined);
|
|
62
|
-
if (typeof value !== 'string')
|
|
63
|
-
throw new Error(`Invalid ${label}`);
|
|
64
|
-
}
|
|
65
|
-
if (!((typeof value === 'string' && /^(?:0x[\da-f]+|\d+)$/i.test(value)) ||
|
|
66
|
-
(typeof value === 'number' &&
|
|
67
|
-
Number.isSafeInteger(value) &&
|
|
68
|
-
value >= 0) ||
|
|
69
|
-
(typeof value === 'bigint' && value >= 0n)))
|
|
70
|
-
throw new Error(`Invalid ${label}`);
|
|
71
|
-
const quantity = ethers_v6_1.BigNumber.from(value);
|
|
72
|
-
if (quantity.isNegative())
|
|
73
|
-
throw new Error(`Invalid ${label}`);
|
|
74
|
-
return quantity;
|
|
75
|
-
};
|
|
76
|
-
const isUnsupportedPriorityFeeRpc = (error) => {
|
|
77
|
-
const unsupportedCode = (code) => code === -32601 || code === 4200;
|
|
78
|
-
if (unsupportedCode(error?.code))
|
|
79
|
-
return true;
|
|
80
|
-
// Ethers' HTTP normalizer retains the matched JSON-RPC error and request
|
|
81
|
-
// under one of these two shapes. A generic outer unsupported/network error
|
|
82
|
-
// is not evidence that this particular RPC method is unavailable.
|
|
83
|
-
if (!['UNKNOWN_ERROR', 'UNSUPPORTED_OPERATION'].includes(error?.code)) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
if (error?.operation != null &&
|
|
87
|
-
error.operation !== 'eth_maxPriorityFeePerGas') {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
return [
|
|
91
|
-
{ cause: error.error, payload: error.payload },
|
|
92
|
-
{ cause: error.info?.error, payload: error.info?.payload },
|
|
93
|
-
].some(({ cause, payload }) => cause?.name !== 'AbortError' &&
|
|
94
|
-
unsupportedCode(cause?.code) &&
|
|
95
|
-
payload?.method === 'eth_maxPriorityFeePerGas' &&
|
|
96
|
-
Array.isArray(payload.params) &&
|
|
97
|
-
payload.params.length === 0);
|
|
57
|
+
// The same protocol bound applies to software and external-device envelopes.
|
|
58
|
+
// Keep this at serialization as well as before handing a request to a device.
|
|
59
|
+
const assertTransactionGasLimit = (transaction) => (0, evm_fees_1.assertEvmGasLimit)((0, evm_fees_1.parseEvmFeeQuantity)(transaction.gasLimit, 'gas limit'), (0, evm_fees_1.parseEvmFeeQuantity)(transaction.chainId ?? 0, 'chain ID'));
|
|
60
|
+
const serializeTransaction = (...args) => {
|
|
61
|
+
const snapshot = (0, evm_local_signer_1.normalizeGasLimitedTransaction)(args[0]);
|
|
62
|
+
return (0, ethers_v6_1.serializeTransaction)(snapshot, args[1]);
|
|
98
63
|
};
|
|
64
|
+
const nonNegativeFeeQuantity = (value, label) => ethers_v6_1.BigNumber.from((0, evm_fees_1.parseEvmFeeQuantity)(value, label));
|
|
99
65
|
const normalizeEthersOverrides = (overrides) => {
|
|
100
66
|
const normalized = { ...overrides };
|
|
101
67
|
for (const field of [
|
|
@@ -159,7 +125,7 @@ const assertSynchronousOriginalValidation = (result, message = 'Original transac
|
|
|
159
125
|
}
|
|
160
126
|
};
|
|
161
127
|
class EthereumTransactions {
|
|
162
|
-
constructor(getNetwork, getDecryptedPrivateKey, getState, ledgerSigner, trezorSigner, captureSigningContext) {
|
|
128
|
+
constructor(getNetwork, getDecryptedPrivateKey, getState, ledgerSigner, trezorSigner, captureSigningContext, captureSubmissionLifecycle) {
|
|
163
129
|
// Allow manual override of gas parameters when automatic estimation fails
|
|
164
130
|
this.gasOverrides = {};
|
|
165
131
|
this.requireLedgerSigner = () => {
|
|
@@ -182,22 +148,29 @@ class EthereumTransactions {
|
|
|
182
148
|
if (type === types_1.KeyringAccountType.Trezor || account?.isTrezorWallet)
|
|
183
149
|
this.requireTrezorSigner();
|
|
184
150
|
};
|
|
185
|
-
this.captureEvmSigningContext = () => {
|
|
186
|
-
this.assertHardwareSignerAvailable();
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
151
|
+
this.captureEvmSigningContext = (target) => {
|
|
152
|
+
this.assertHardwareSignerAvailable(target);
|
|
153
|
+
const state = this.getState();
|
|
154
|
+
const selectedType = state.activeAccountType;
|
|
155
|
+
const selectedId = state.activeAccountId;
|
|
156
|
+
const selectedAddress = state.accounts[selectedType]?.[selectedId]?.address;
|
|
157
|
+
const activeAccountType = target?.type ?? selectedType;
|
|
158
|
+
const activeAccountId = target?.id ?? selectedId;
|
|
159
|
+
const selectedAccount = state.accounts[activeAccountType]?.[activeAccountId];
|
|
160
|
+
if (!selectedAccount || !selectedAddress)
|
|
190
161
|
throw new Error('Active account not found');
|
|
191
162
|
const account = { address: selectedAccount.address };
|
|
192
|
-
const network = { ...activeNetwork };
|
|
163
|
+
const network = { ...state.activeNetwork };
|
|
193
164
|
const assertSession = this.captureSigningContext?.();
|
|
194
165
|
const provider = this.web3Provider;
|
|
195
166
|
const assertCurrent = () => {
|
|
196
167
|
assertSession?.();
|
|
197
168
|
const current = this.getState();
|
|
198
|
-
const currentAccount = current.accounts[
|
|
199
|
-
if (current.activeAccountId !==
|
|
200
|
-
current.activeAccountType !==
|
|
169
|
+
const currentAccount = current.accounts[activeAccountType]?.[activeAccountId];
|
|
170
|
+
if (current.activeAccountId !== selectedId ||
|
|
171
|
+
current.activeAccountType !== selectedType ||
|
|
172
|
+
current.accounts[selectedType]?.[selectedId]?.address?.toLowerCase() !==
|
|
173
|
+
selectedAddress.toLowerCase() ||
|
|
201
174
|
currentAccount?.address?.toLowerCase() !==
|
|
202
175
|
account.address.toLowerCase() ||
|
|
203
176
|
[current.activeNetwork, this.getNetwork()].some((selected) => selected.kind !== network.kind ||
|
|
@@ -217,6 +190,17 @@ class EthereumTransactions {
|
|
|
217
190
|
assertCurrent,
|
|
218
191
|
};
|
|
219
192
|
};
|
|
193
|
+
this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) => (0, evm_submission_1.captureEvmSubmission)({
|
|
194
|
+
operation,
|
|
195
|
+
account: { address: account.address, type, id },
|
|
196
|
+
network: {
|
|
197
|
+
kind: network.kind,
|
|
198
|
+
chainId: network.chainId,
|
|
199
|
+
slip44: network.slip44,
|
|
200
|
+
url: network.url,
|
|
201
|
+
},
|
|
202
|
+
...(originalHash !== undefined ? { originalHash } : {}),
|
|
203
|
+
}, assertCurrent, this.captureSubmissionLifecycle);
|
|
220
204
|
this.signTypedData = async (addr, typedData, version) => {
|
|
221
205
|
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
222
206
|
const { activeAccountType, accounts, activeAccountId } = this.getState();
|
|
@@ -615,68 +599,21 @@ class EthereumTransactions {
|
|
|
615
599
|
}
|
|
616
600
|
};
|
|
617
601
|
this.getFeeDataWithDynamicMaxPriorityFeePerGas = async (provider = this.web3Provider) => {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
try {
|
|
634
|
-
maxPriorityFeePerGas = nonNegativeFeeQuantity(await provider.send('eth_maxPriorityFeePerGas', []), 'priority fee');
|
|
635
|
-
}
|
|
636
|
-
catch (error) {
|
|
637
|
-
if (error?.code === 'SYSWEB3_RPC_RESPONSE_INVALID' ||
|
|
638
|
-
error?.name === 'AbortError' ||
|
|
639
|
-
this.abortController.signal.aborted ||
|
|
640
|
-
provider.signal?.aborted ||
|
|
641
|
-
!isUnsupportedPriorityFeeRpc(error))
|
|
642
|
-
throw error;
|
|
643
|
-
// An explicitly unsupported method may use actual observed history,
|
|
644
|
-
// including zero rewards. Missing/corrupt history never invents a tip.
|
|
645
|
-
const history = await provider.send('eth_feeHistory', [
|
|
646
|
-
'0x5',
|
|
647
|
-
'latest',
|
|
648
|
-
[25, 50, 75],
|
|
649
|
-
]);
|
|
650
|
-
if (!Array.isArray(history?.reward) || history.reward.length === 0)
|
|
651
|
-
throw new Error('Priority fee history unavailable');
|
|
652
|
-
const recentFees = history.reward
|
|
653
|
-
.map((reward) => {
|
|
654
|
-
if (!Array.isArray(reward) || reward.length !== 3)
|
|
655
|
-
throw new Error('Invalid priority fee history');
|
|
656
|
-
return nonNegativeFeeQuantity(reward[1], 'priority fee history');
|
|
657
|
-
})
|
|
658
|
-
.sort((a, b) => (a.lt(b) ? -1 : a.gt(b) ? 1 : 0));
|
|
659
|
-
maxPriorityFeePerGas = recentFees[Math.floor(recentFees.length / 2)]
|
|
660
|
-
.mul(150)
|
|
661
|
-
.div(100);
|
|
662
|
-
}
|
|
663
|
-
if (this.gasOverrides.minPriorityFee &&
|
|
664
|
-
maxPriorityFeePerGas.lt(this.gasOverrides.minPriorityFee))
|
|
665
|
-
maxPriorityFeePerGas = this.gasOverrides.minPriorityFee;
|
|
666
|
-
const baselineMaxFee = gasPrice.mul(120).div(100);
|
|
667
|
-
const calculatedMaxFee = baseFeePerGas
|
|
668
|
-
.mul(this.gasOverrides.feeMultiplier || 250)
|
|
669
|
-
.div(100)
|
|
670
|
-
.add(maxPriorityFeePerGas);
|
|
671
|
-
let maxFeePerGas = calculatedMaxFee.gt(baselineMaxFee)
|
|
672
|
-
? calculatedMaxFee
|
|
673
|
-
: baselineMaxFee;
|
|
674
|
-
const minMaxFee = maxPriorityFeePerGas.mul(120).div(100);
|
|
675
|
-
if (maxFeePerGas.lt(minMaxFee))
|
|
676
|
-
maxFeePerGas = minMaxFee;
|
|
677
|
-
if (maxFeePerGas.isZero())
|
|
678
|
-
throw new Error('Invalid EIP-1559 maximum fee');
|
|
679
|
-
return { gasPrice, baseFeePerGas, maxFeePerGas, maxPriorityFeePerGas };
|
|
602
|
+
const fee = await (0, evm_fees_1.getEvmFeeData)(provider, {
|
|
603
|
+
signal: this.abortController.signal,
|
|
604
|
+
minPriorityFee: this.gasOverrides.minPriorityFee,
|
|
605
|
+
feeMultiplier: this.gasOverrides.feeMultiplier,
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
gasPrice: ethers_v6_1.BigNumber.from(fee.gasPrice),
|
|
609
|
+
...(fee.baseFeePerGas === undefined
|
|
610
|
+
? {}
|
|
611
|
+
: {
|
|
612
|
+
baseFeePerGas: ethers_v6_1.BigNumber.from(fee.baseFeePerGas),
|
|
613
|
+
}),
|
|
614
|
+
maxFeePerGas: ethers_v6_1.BigNumber.from(fee.maxFeePerGas),
|
|
615
|
+
maxPriorityFeePerGas: ethers_v6_1.BigNumber.from(fee.maxPriorityFeePerGas),
|
|
616
|
+
};
|
|
680
617
|
};
|
|
681
618
|
this.calculateNewGasValues = (oldTxsParams, isForCancel, isLegacy) => {
|
|
682
619
|
const newGasValues = {
|
|
@@ -780,6 +717,9 @@ class EthereumTransactions {
|
|
|
780
717
|
assertKeyringContext();
|
|
781
718
|
};
|
|
782
719
|
assertCurrent();
|
|
720
|
+
const submission = previewOnly
|
|
721
|
+
? undefined
|
|
722
|
+
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent, txHash);
|
|
783
723
|
const currentEip1559Fees = async () => {
|
|
784
724
|
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
|
|
785
725
|
assertCurrent();
|
|
@@ -956,7 +896,7 @@ class EthereumTransactions {
|
|
|
956
896
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
957
897
|
};
|
|
958
898
|
assertApprovedEnvelope(txFormattedForEthers);
|
|
959
|
-
const rawTx =
|
|
899
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
960
900
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
961
901
|
rawTx: rawTx.replace('0x', ''),
|
|
962
902
|
accountIndex: activeAccountId,
|
|
@@ -969,10 +909,10 @@ class EthereumTransactions {
|
|
|
969
909
|
v: parseInt(signature.v, 16),
|
|
970
910
|
};
|
|
971
911
|
if (signature) {
|
|
972
|
-
const signedTx =
|
|
912
|
+
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
973
913
|
assertCurrent();
|
|
974
914
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
975
|
-
const transactionResponse = await provider.sendTransaction(
|
|
915
|
+
const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
976
916
|
return {
|
|
977
917
|
isCanceled: true,
|
|
978
918
|
transaction: transactionResponse,
|
|
@@ -987,6 +927,7 @@ class EthereumTransactions {
|
|
|
987
927
|
}
|
|
988
928
|
catch (error) {
|
|
989
929
|
return {
|
|
930
|
+
...(0, evm_submission_1.evmSubmissionFailure)(error),
|
|
990
931
|
isCanceled: false,
|
|
991
932
|
error: true,
|
|
992
933
|
};
|
|
@@ -1026,6 +967,8 @@ class EthereumTransactions {
|
|
|
1026
967
|
: this.toHex0x(formattedTx.maxPriorityFeePerGas, 'maxPriorityFeePerGas');
|
|
1027
968
|
}
|
|
1028
969
|
assertApprovedEnvelope(txFormattedForTrezor);
|
|
970
|
+
assertTransactionGasLimit(txFormattedForTrezor);
|
|
971
|
+
const expectedUnsignedTransaction = serializeTransaction(txFormattedForTrezor);
|
|
1029
972
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1030
973
|
coin: trezorCoin,
|
|
1031
974
|
tx: txFormattedForTrezor,
|
|
@@ -1034,10 +977,10 @@ class EthereumTransactions {
|
|
|
1034
977
|
});
|
|
1035
978
|
assertCurrent();
|
|
1036
979
|
if (signature.success) {
|
|
1037
|
-
const signedTx =
|
|
980
|
+
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
1038
981
|
assertCurrent();
|
|
1039
982
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1040
|
-
const transactionResponse = await provider.sendTransaction(
|
|
983
|
+
const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
1041
984
|
return {
|
|
1042
985
|
isCanceled: true,
|
|
1043
986
|
transaction: transactionResponse,
|
|
@@ -1052,6 +995,7 @@ class EthereumTransactions {
|
|
|
1052
995
|
}
|
|
1053
996
|
catch (error) {
|
|
1054
997
|
return {
|
|
998
|
+
...(0, evm_submission_1.evmSubmissionFailure)(error),
|
|
1055
999
|
isCanceled: false,
|
|
1056
1000
|
error: true,
|
|
1057
1001
|
};
|
|
@@ -1065,7 +1009,7 @@ class EthereumTransactions {
|
|
|
1065
1009
|
if (address?.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
1066
1010
|
throw new Error('Wallet signing context changed');
|
|
1067
1011
|
}
|
|
1068
|
-
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, assertApprovedEnvelope);
|
|
1012
|
+
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, assertApprovedEnvelope, submission);
|
|
1069
1013
|
if (transactionResponse) {
|
|
1070
1014
|
return {
|
|
1071
1015
|
isCanceled: true,
|
|
@@ -1080,6 +1024,7 @@ class EthereumTransactions {
|
|
|
1080
1024
|
}
|
|
1081
1025
|
catch (error) {
|
|
1082
1026
|
return {
|
|
1027
|
+
...(0, evm_submission_1.evmSubmissionFailure)(error),
|
|
1083
1028
|
isCanceled: false,
|
|
1084
1029
|
error: true,
|
|
1085
1030
|
};
|
|
@@ -1115,11 +1060,15 @@ class EthereumTransactions {
|
|
|
1115
1060
|
catch (error) {
|
|
1116
1061
|
// A broadcast transport error may mean the node accepted the signed
|
|
1117
1062
|
// transaction. Derive this marker from this invocation, never RPC data.
|
|
1118
|
-
const submissionError = Object.assign(new Error(error instanceof Error ? error.message : String(error)), error, {
|
|
1063
|
+
const submissionError = Object.assign(new Error(error instanceof Error ? error.message : String(error)), error, {
|
|
1064
|
+
transactionNotBroadcast: !broadcastAttempted ||
|
|
1065
|
+
(0, evm_submission_1.evmSubmissionFailure)(error).transactionNotBroadcast,
|
|
1066
|
+
});
|
|
1119
1067
|
if (error instanceof Error) {
|
|
1120
1068
|
submissionError.name = error.name;
|
|
1121
1069
|
submissionError.stack = error.stack;
|
|
1122
1070
|
}
|
|
1071
|
+
(0, evm_submission_1.copyEvmSubmissionFailure)(error, submissionError);
|
|
1123
1072
|
throw submissionError;
|
|
1124
1073
|
}
|
|
1125
1074
|
};
|
|
@@ -1140,6 +1089,54 @@ class EthereumTransactions {
|
|
|
1140
1089
|
beforeBroadcast();
|
|
1141
1090
|
};
|
|
1142
1091
|
assertCurrent();
|
|
1092
|
+
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
|
|
1093
|
+
// Detach supplied values before any RPC/device await. Autofill updates only
|
|
1094
|
+
// this invocation; caller mutations cannot replace already supplied fields.
|
|
1095
|
+
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
1096
|
+
throw new TypeError('Invalid EVM transaction request');
|
|
1097
|
+
const supplied = { ...params };
|
|
1098
|
+
// Validate original scalar spellings before conversion. Normalization's
|
|
1099
|
+
// permissive zero coercion must not bypass the bounded wire parser.
|
|
1100
|
+
for (const field of [
|
|
1101
|
+
'chainId',
|
|
1102
|
+
'nonce',
|
|
1103
|
+
'gasLimit',
|
|
1104
|
+
'gasPrice',
|
|
1105
|
+
'maxFeePerGas',
|
|
1106
|
+
'maxPriorityFeePerGas',
|
|
1107
|
+
'value',
|
|
1108
|
+
'type',
|
|
1109
|
+
]) {
|
|
1110
|
+
if (supplied[field] != null) {
|
|
1111
|
+
const value = (0, evm_fees_1.parseEvmFeeQuantity)(supplied[field], field);
|
|
1112
|
+
if (field === 'nonce' || field === 'type') {
|
|
1113
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER))
|
|
1114
|
+
throw new Error(`Invalid transaction ${field}`);
|
|
1115
|
+
supplied[field] = Number(value);
|
|
1116
|
+
}
|
|
1117
|
+
else
|
|
1118
|
+
supplied[field] = value;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
if (supplied.data != null)
|
|
1122
|
+
supplied.data = (0, utils_1.hexlify)(supplied.data);
|
|
1123
|
+
if (supplied.accessList != null)
|
|
1124
|
+
supplied.accessList = (0, transaction_1.accessListify)(supplied.accessList);
|
|
1125
|
+
params = supplied;
|
|
1126
|
+
assertCurrent();
|
|
1127
|
+
// Capture the reviewed nonce before any fee/RPC await. Hardware and the
|
|
1128
|
+
// final serializer must use the same immutable integer, including zero.
|
|
1129
|
+
let explicitHardwareNonce;
|
|
1130
|
+
if (activeAccountType === types_1.KeyringAccountType.Ledger ||
|
|
1131
|
+
activeAccountType === types_1.KeyringAccountType.Trezor) {
|
|
1132
|
+
const suppliedNonce = params.nonce;
|
|
1133
|
+
if (suppliedNonce != null) {
|
|
1134
|
+
const nonce = (0, evm_fees_1.parseEvmFeeQuantity)(suppliedNonce, 'transaction nonce');
|
|
1135
|
+
if (nonce > BigInt(Number.MAX_SAFE_INTEGER))
|
|
1136
|
+
throw new Error('Invalid transaction nonce');
|
|
1137
|
+
explicitHardwareNonce = Number(nonce);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1143
1140
|
const explicitEip1559 = params.type != null && ethers_v6_1.BigNumber.from(params.type).eq(2);
|
|
1144
1141
|
const requestedEip1559 = explicitEip1559 ||
|
|
1145
1142
|
params.maxFeePerGas != null ||
|
|
@@ -1160,7 +1157,7 @@ class EthereumTransactions {
|
|
|
1160
1157
|
validateEip1559Fees();
|
|
1161
1158
|
// Only apply a minimum gas limit when a network-specific override is set.
|
|
1162
1159
|
// Otherwise keep the user/dapp-approved gas limit unchanged.
|
|
1163
|
-
if (params.gasLimit && this.gasOverrides.minGasLimit) {
|
|
1160
|
+
if (params.gasLimit != null && this.gasOverrides.minGasLimit) {
|
|
1164
1161
|
const minGasLimit = this.gasOverrides.minGasLimit;
|
|
1165
1162
|
const currentGasLimit = ethers_v6_1.BigNumber.from(params.gasLimit);
|
|
1166
1163
|
if (currentGasLimit.lt(minGasLimit)) {
|
|
@@ -1176,12 +1173,16 @@ class EthereumTransactions {
|
|
|
1176
1173
|
...params,
|
|
1177
1174
|
from: params.from || activeAccount.address,
|
|
1178
1175
|
}, ['gasPrice']);
|
|
1179
|
-
if (
|
|
1176
|
+
if (params.gasLimit == null) {
|
|
1180
1177
|
params.gasLimit = await provider.estimateGas(txForEstimation);
|
|
1181
1178
|
assertCurrent();
|
|
1182
1179
|
}
|
|
1180
|
+
assertTransactionGasLimit({
|
|
1181
|
+
gasLimit: params.gasLimit,
|
|
1182
|
+
chainId: activeNetwork.chainId,
|
|
1183
|
+
});
|
|
1183
1184
|
if (isLegacy) {
|
|
1184
|
-
if (
|
|
1185
|
+
if (params.gasPrice == null) {
|
|
1185
1186
|
params.gasPrice = await provider.getGasPrice();
|
|
1186
1187
|
assertCurrent();
|
|
1187
1188
|
}
|
|
@@ -1207,7 +1208,8 @@ class EthereumTransactions {
|
|
|
1207
1208
|
validateEip1559Fees();
|
|
1208
1209
|
const sendEVMLedgerTransaction = async () => {
|
|
1209
1210
|
assertCurrent();
|
|
1210
|
-
const transactionNonce =
|
|
1211
|
+
const transactionNonce = explicitHardwareNonce ??
|
|
1212
|
+
(await provider.getTransactionCount(activeAccount.address, 'pending'));
|
|
1211
1213
|
assertCurrent();
|
|
1212
1214
|
const formatParams = isLegacy
|
|
1213
1215
|
? (0, omit_1.default)(params, 'from')
|
|
@@ -1225,7 +1227,7 @@ class EthereumTransactions {
|
|
|
1225
1227
|
chainId: activeNetwork.chainId,
|
|
1226
1228
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1227
1229
|
};
|
|
1228
|
-
const rawTx =
|
|
1230
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1229
1231
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1230
1232
|
rawTx: rawTx.replace('0x', ''),
|
|
1231
1233
|
accountIndex: activeAccountId,
|
|
@@ -1239,10 +1241,9 @@ class EthereumTransactions {
|
|
|
1239
1241
|
};
|
|
1240
1242
|
if (signature) {
|
|
1241
1243
|
try {
|
|
1242
|
-
const signedTx =
|
|
1244
|
+
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1243
1245
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1244
|
-
|
|
1245
|
-
const finalTx = await provider.sendTransaction(signedTx);
|
|
1246
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
|
|
1246
1247
|
return finalTx;
|
|
1247
1248
|
}
|
|
1248
1249
|
catch (error) {
|
|
@@ -1255,7 +1256,8 @@ class EthereumTransactions {
|
|
|
1255
1256
|
};
|
|
1256
1257
|
const sendEVMTrezorTransaction = async () => {
|
|
1257
1258
|
assertCurrent();
|
|
1258
|
-
const transactionNonce =
|
|
1259
|
+
const transactionNonce = explicitHardwareNonce ??
|
|
1260
|
+
(await provider.getTransactionCount(activeAccount.address, 'pending'));
|
|
1259
1261
|
assertCurrent();
|
|
1260
1262
|
let txFormattedForTrezor = {};
|
|
1261
1263
|
const formatParams = isLegacy
|
|
@@ -1265,6 +1267,7 @@ class EthereumTransactions {
|
|
|
1265
1267
|
case true:
|
|
1266
1268
|
txFormattedForTrezor = {
|
|
1267
1269
|
...formatParams,
|
|
1270
|
+
gasPrice: this.toHex0x(params.gasPrice, 'gasPrice'),
|
|
1268
1271
|
gasLimit: this.toHex0x(formatParams.gasLimit ?? params.gasLimit, 'gasLimit'),
|
|
1269
1272
|
value: this.toHex0x(formatParams.value ?? params.value, 'value'),
|
|
1270
1273
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
@@ -1301,6 +1304,11 @@ class EthereumTransactions {
|
|
|
1301
1304
|
};
|
|
1302
1305
|
break;
|
|
1303
1306
|
}
|
|
1307
|
+
assertTransactionGasLimit(txFormattedForTrezor);
|
|
1308
|
+
const expectedUnsignedTransaction = serializeTransaction({
|
|
1309
|
+
...txFormattedForTrezor,
|
|
1310
|
+
type: isLegacy ? 0 : 2,
|
|
1311
|
+
});
|
|
1304
1312
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1305
1313
|
index: `${activeAccountId}`,
|
|
1306
1314
|
tx: txFormattedForTrezor,
|
|
@@ -1323,10 +1331,9 @@ class EthereumTransactions {
|
|
|
1323
1331
|
chainId: activeNetwork.chainId,
|
|
1324
1332
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1325
1333
|
};
|
|
1326
|
-
const signedTx =
|
|
1334
|
+
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1327
1335
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1328
|
-
|
|
1329
|
-
const finalTx = await provider.sendTransaction(signedTx);
|
|
1336
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1330
1337
|
return finalTx;
|
|
1331
1338
|
}
|
|
1332
1339
|
catch (error) {
|
|
@@ -1351,7 +1358,7 @@ class EthereumTransactions {
|
|
|
1351
1358
|
? { ...params, type: 0 } // Force Type 0 for legacy transactions
|
|
1352
1359
|
: (0, omit_1.default)(params, ['gasPrice']); // Strip gasPrice for EIP-1559
|
|
1353
1360
|
try {
|
|
1354
|
-
const transaction = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, tx, assertBeforeBroadcast, assertCurrent);
|
|
1361
|
+
const transaction = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, tx, assertBeforeBroadcast, assertCurrent, undefined, submission);
|
|
1355
1362
|
// The broadcast response already has the hash. Pending transactions
|
|
1356
1363
|
// have no block number, so a block lookup can fail after a successful
|
|
1357
1364
|
// broadcast and incorrectly report the send as failed.
|
|
@@ -1380,7 +1387,14 @@ class EthereumTransactions {
|
|
|
1380
1387
|
!/^\d{1,78}$/.test(approvedMaximumFee))) {
|
|
1381
1388
|
throw new Error('Replacement fee approval required');
|
|
1382
1389
|
}
|
|
1383
|
-
|
|
1390
|
+
let signing;
|
|
1391
|
+
const assertCurrent = () => {
|
|
1392
|
+
const result = assertCurrentContext?.();
|
|
1393
|
+
assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
|
|
1394
|
+
if (result !== undefined)
|
|
1395
|
+
throw new Error('Transaction context guard must return undefined');
|
|
1396
|
+
signing?.assertCurrent();
|
|
1397
|
+
};
|
|
1384
1398
|
const assertBeforeBroadcast = () => {
|
|
1385
1399
|
assertCurrent();
|
|
1386
1400
|
beforeBroadcast?.();
|
|
@@ -1406,10 +1420,15 @@ class EthereumTransactions {
|
|
|
1406
1420
|
(targetAccount && activeAccountType === types_1.KeyringAccountType.SmartAccount)) {
|
|
1407
1421
|
throw new Error('Replacement signing account is unavailable');
|
|
1408
1422
|
}
|
|
1423
|
+
if (!previewOnly)
|
|
1424
|
+
signing = this.captureEvmSigningContext(targetAccount);
|
|
1409
1425
|
const expectedSender = activeAccount.address.toLowerCase();
|
|
1410
1426
|
const requestedHash = txHash.toLowerCase();
|
|
1411
1427
|
const provider = this.web3Provider;
|
|
1412
1428
|
assertCurrent();
|
|
1429
|
+
const submission = previewOnly
|
|
1430
|
+
? undefined
|
|
1431
|
+
: this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent, txHash);
|
|
1413
1432
|
const validateLookup = (transaction) => {
|
|
1414
1433
|
assertCurrent();
|
|
1415
1434
|
if (!transaction)
|
|
@@ -1547,7 +1566,7 @@ class EthereumTransactions {
|
|
|
1547
1566
|
chainId: activeNetwork.chainId,
|
|
1548
1567
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1549
1568
|
};
|
|
1550
|
-
const rawTx =
|
|
1569
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1551
1570
|
assertCurrent();
|
|
1552
1571
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1553
1572
|
rawTx: rawTx.replace('0x', ''),
|
|
@@ -1560,11 +1579,10 @@ class EthereumTransactions {
|
|
|
1560
1579
|
v: parseInt(signature.v, 16),
|
|
1561
1580
|
};
|
|
1562
1581
|
if (signature) {
|
|
1563
|
-
const signedTx =
|
|
1582
|
+
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1564
1583
|
assertSignedSender(signedTx);
|
|
1565
1584
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1566
|
-
|
|
1567
|
-
const transactionResponse = await provider.sendTransaction(signedTx);
|
|
1585
|
+
const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
|
|
1568
1586
|
return {
|
|
1569
1587
|
isSpeedUp: true,
|
|
1570
1588
|
transaction: transactionResponse,
|
|
@@ -1588,6 +1606,7 @@ class EthereumTransactions {
|
|
|
1588
1606
|
? 'REPLACEMENT_ALREADY_KNOWN'
|
|
1589
1607
|
: 'REPLACEMENT_SEND_FAILED';
|
|
1590
1608
|
return {
|
|
1609
|
+
...(0, evm_submission_1.evmSubmissionFailure)(error),
|
|
1591
1610
|
isSpeedUp: false,
|
|
1592
1611
|
error: true,
|
|
1593
1612
|
code,
|
|
@@ -1632,6 +1651,8 @@ class EthereumTransactions {
|
|
|
1632
1651
|
: this.toHex0x(formattedTx.maxPriorityFeePerGas, 'maxPriorityFeePerGas');
|
|
1633
1652
|
}
|
|
1634
1653
|
assertCurrent();
|
|
1654
|
+
assertTransactionGasLimit(txFormattedForTrezor);
|
|
1655
|
+
const expectedUnsignedTransaction = serializeTransaction(txFormattedForTrezor);
|
|
1635
1656
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1636
1657
|
coin: trezorCoin,
|
|
1637
1658
|
tx: txFormattedForTrezor,
|
|
@@ -1639,11 +1660,10 @@ class EthereumTransactions {
|
|
|
1639
1660
|
slip44: activeNetwork.slip44,
|
|
1640
1661
|
});
|
|
1641
1662
|
if (signature.success) {
|
|
1642
|
-
const signedTx =
|
|
1663
|
+
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
1643
1664
|
assertSignedSender(signedTx);
|
|
1644
1665
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1645
|
-
|
|
1646
|
-
const transactionResponse = await provider.sendTransaction(signedTx);
|
|
1666
|
+
const transactionResponse = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1647
1667
|
return {
|
|
1648
1668
|
isSpeedUp: true,
|
|
1649
1669
|
transaction: transactionResponse,
|
|
@@ -1667,6 +1687,7 @@ class EthereumTransactions {
|
|
|
1667
1687
|
? 'REPLACEMENT_ALREADY_KNOWN'
|
|
1668
1688
|
: 'REPLACEMENT_SEND_FAILED';
|
|
1669
1689
|
return {
|
|
1690
|
+
...(0, evm_submission_1.evmSubmissionFailure)(error),
|
|
1670
1691
|
isSpeedUp: false,
|
|
1671
1692
|
error: true,
|
|
1672
1693
|
code,
|
|
@@ -1683,7 +1704,7 @@ class EthereumTransactions {
|
|
|
1683
1704
|
if (address.toLowerCase() !== expectedSender) {
|
|
1684
1705
|
throw new Error('Replacement signing account changed');
|
|
1685
1706
|
}
|
|
1686
|
-
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, txWithEditedFee, assertBeforeBroadcast, assertCurrent);
|
|
1707
|
+
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, txWithEditedFee, assertBeforeBroadcast, assertCurrent, undefined, submission);
|
|
1687
1708
|
if (transactionResponse) {
|
|
1688
1709
|
return {
|
|
1689
1710
|
isSpeedUp: true,
|
|
@@ -1709,6 +1730,7 @@ class EthereumTransactions {
|
|
|
1709
1730
|
? 'INSUFFICIENT_FUNDS_REPLACEMENT'
|
|
1710
1731
|
: 'REPLACEMENT_SEND_FAILED';
|
|
1711
1732
|
return {
|
|
1733
|
+
...(0, evm_submission_1.evmSubmissionFailure)(error),
|
|
1712
1734
|
isSpeedUp: false,
|
|
1713
1735
|
error: true,
|
|
1714
1736
|
code,
|
|
@@ -1729,6 +1751,7 @@ class EthereumTransactions {
|
|
|
1729
1751
|
this.sendSignedErc20Transaction = async ({ receiver, tokenAddress, tokenAmount, isLegacy = false, maxPriorityFeePerGas, maxFeePerGas, gasPrice, decimals, gasLimit, saveTrezorTx, }) => {
|
|
1730
1752
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent, } = this.captureEvmSigningContext();
|
|
1731
1753
|
const activeAccountAddress = activeAccount.address;
|
|
1754
|
+
const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
|
|
1732
1755
|
({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
|
|
1733
1756
|
await this.resolveEvmFeeParams({
|
|
1734
1757
|
gasPrice,
|
|
@@ -1769,7 +1792,7 @@ class EthereumTransactions {
|
|
|
1769
1792
|
chainId: activeNetwork.chainId,
|
|
1770
1793
|
from: activeAccountAddress,
|
|
1771
1794
|
value: '0x0',
|
|
1772
|
-
}, assertCurrent, assertCurrent);
|
|
1795
|
+
}, assertCurrent, assertCurrent, undefined, submission);
|
|
1773
1796
|
}
|
|
1774
1797
|
catch (error) {
|
|
1775
1798
|
throw error;
|
|
@@ -1815,7 +1838,7 @@ class EthereumTransactions {
|
|
|
1815
1838
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1816
1839
|
};
|
|
1817
1840
|
}
|
|
1818
|
-
const rawTx =
|
|
1841
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
1819
1842
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1820
1843
|
rawTx: rawTx.replace('0x', ''),
|
|
1821
1844
|
accountIndex: activeAccountId,
|
|
@@ -1829,11 +1852,14 @@ class EthereumTransactions {
|
|
|
1829
1852
|
};
|
|
1830
1853
|
if (signature) {
|
|
1831
1854
|
try {
|
|
1832
|
-
const signedTx =
|
|
1855
|
+
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1833
1856
|
assertCurrent();
|
|
1834
1857
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1835
|
-
const finalTx = await
|
|
1836
|
-
|
|
1858
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, async (raw) => {
|
|
1859
|
+
const response = await provider.sendTransaction(raw);
|
|
1860
|
+
saveTrezorTx && saveTrezorTx(response);
|
|
1861
|
+
return response;
|
|
1862
|
+
}, submission, undefined, rawTx);
|
|
1837
1863
|
return finalTx;
|
|
1838
1864
|
}
|
|
1839
1865
|
catch (error) {
|
|
@@ -1886,6 +1912,11 @@ class EthereumTransactions {
|
|
|
1886
1912
|
data: txData,
|
|
1887
1913
|
};
|
|
1888
1914
|
}
|
|
1915
|
+
assertTransactionGasLimit(txToBeSignedByTrezor);
|
|
1916
|
+
const expectedUnsignedTransaction = serializeTransaction({
|
|
1917
|
+
...txToBeSignedByTrezor,
|
|
1918
|
+
type: isLegacy ? 0 : 2,
|
|
1919
|
+
});
|
|
1889
1920
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1890
1921
|
index: `${activeAccountId}`,
|
|
1891
1922
|
tx: txToBeSignedByTrezor,
|
|
@@ -1921,11 +1952,14 @@ class EthereumTransactions {
|
|
|
1921
1952
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1922
1953
|
};
|
|
1923
1954
|
}
|
|
1924
|
-
const signedTx =
|
|
1955
|
+
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1925
1956
|
assertCurrent();
|
|
1926
1957
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1927
|
-
const finalTx = await
|
|
1928
|
-
|
|
1958
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, async (raw) => {
|
|
1959
|
+
const response = await provider.sendTransaction(raw);
|
|
1960
|
+
saveTrezorTx && saveTrezorTx(response);
|
|
1961
|
+
return response;
|
|
1962
|
+
}, submission, undefined, expectedUnsignedTransaction);
|
|
1929
1963
|
return finalTx;
|
|
1930
1964
|
}
|
|
1931
1965
|
catch (error) {
|
|
@@ -1952,6 +1986,7 @@ class EthereumTransactions {
|
|
|
1952
1986
|
this.sendSignedErc721Transaction = async ({ receiver, tokenAddress, tokenId, isLegacy, maxPriorityFeePerGas, maxFeePerGas, gasPrice, gasLimit, }) => {
|
|
1953
1987
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent, } = this.captureEvmSigningContext();
|
|
1954
1988
|
const activeAccountAddress = activeAccount.address;
|
|
1989
|
+
const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
|
|
1955
1990
|
if (tokenId === undefined || tokenId === null) {
|
|
1956
1991
|
throw new Error('ERC721 tokenId is required');
|
|
1957
1992
|
}
|
|
@@ -1993,7 +2028,7 @@ class EthereumTransactions {
|
|
|
1993
2028
|
chainId: activeNetwork.chainId,
|
|
1994
2029
|
from: activeAccountAddress,
|
|
1995
2030
|
value: '0x0',
|
|
1996
|
-
}, assertCurrent, assertCurrent);
|
|
2031
|
+
}, assertCurrent, assertCurrent, undefined, submission);
|
|
1997
2032
|
}
|
|
1998
2033
|
catch (error) {
|
|
1999
2034
|
throw error;
|
|
@@ -2038,7 +2073,7 @@ class EthereumTransactions {
|
|
|
2038
2073
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2039
2074
|
};
|
|
2040
2075
|
}
|
|
2041
|
-
const rawTx =
|
|
2076
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2042
2077
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
2043
2078
|
rawTx: rawTx.replace('0x', ''),
|
|
2044
2079
|
accountIndex: activeAccountId,
|
|
@@ -2052,10 +2087,10 @@ class EthereumTransactions {
|
|
|
2052
2087
|
};
|
|
2053
2088
|
if (signature) {
|
|
2054
2089
|
try {
|
|
2055
|
-
const signedTx =
|
|
2090
|
+
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2056
2091
|
assertCurrent();
|
|
2057
2092
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2058
|
-
const finalTx = await provider.sendTransaction(
|
|
2093
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
2059
2094
|
return finalTx;
|
|
2060
2095
|
}
|
|
2061
2096
|
catch (error) {
|
|
@@ -2110,6 +2145,11 @@ class EthereumTransactions {
|
|
|
2110
2145
|
}
|
|
2111
2146
|
// For EVM networks, Trezor expects 'eth' regardless of the network's currency
|
|
2112
2147
|
const trezorCoin = activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
2148
|
+
assertTransactionGasLimit(txToBeSignedByTrezor);
|
|
2149
|
+
const expectedUnsignedTransaction = serializeTransaction({
|
|
2150
|
+
...txToBeSignedByTrezor,
|
|
2151
|
+
type: isLegacy ? 0 : 2,
|
|
2152
|
+
});
|
|
2113
2153
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
2114
2154
|
index: `${activeAccountId}`,
|
|
2115
2155
|
tx: txToBeSignedByTrezor,
|
|
@@ -2145,10 +2185,10 @@ class EthereumTransactions {
|
|
|
2145
2185
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2146
2186
|
};
|
|
2147
2187
|
}
|
|
2148
|
-
const signedTx =
|
|
2188
|
+
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2149
2189
|
assertCurrent();
|
|
2150
2190
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2151
|
-
const finalTx = await provider.sendTransaction(
|
|
2191
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
2152
2192
|
return finalTx;
|
|
2153
2193
|
}
|
|
2154
2194
|
catch (error) {
|
|
@@ -2177,6 +2217,7 @@ class EthereumTransactions {
|
|
|
2177
2217
|
this.sendSignedErc1155Transaction = async ({ receiver, tokenAddress, tokenId, tokenAmount, isLegacy, maxPriorityFeePerGas, maxFeePerGas, gasPrice, gasLimit, }) => {
|
|
2178
2218
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent, } = this.captureEvmSigningContext();
|
|
2179
2219
|
const activeAccountAddress = activeAccount.address;
|
|
2220
|
+
const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertCurrent);
|
|
2180
2221
|
({ gasPrice, isLegacy, maxFeePerGas, maxPriorityFeePerGas } =
|
|
2181
2222
|
await this.resolveEvmFeeParams({
|
|
2182
2223
|
gasPrice,
|
|
@@ -2216,7 +2257,7 @@ class EthereumTransactions {
|
|
|
2216
2257
|
chainId: activeNetwork.chainId,
|
|
2217
2258
|
from: activeAccountAddress,
|
|
2218
2259
|
value: '0x0',
|
|
2219
|
-
}, assertCurrent, assertCurrent);
|
|
2260
|
+
}, assertCurrent, assertCurrent, undefined, submission);
|
|
2220
2261
|
}
|
|
2221
2262
|
catch (error) {
|
|
2222
2263
|
throw error;
|
|
@@ -2258,7 +2299,7 @@ class EthereumTransactions {
|
|
|
2258
2299
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2259
2300
|
};
|
|
2260
2301
|
}
|
|
2261
|
-
const rawTx =
|
|
2302
|
+
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
2262
2303
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
2263
2304
|
rawTx: rawTx.replace('0x', ''),
|
|
2264
2305
|
accountIndex: activeAccountId,
|
|
@@ -2272,10 +2313,10 @@ class EthereumTransactions {
|
|
|
2272
2313
|
};
|
|
2273
2314
|
if (signature) {
|
|
2274
2315
|
try {
|
|
2275
|
-
const signedTx =
|
|
2316
|
+
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2276
2317
|
assertCurrent();
|
|
2277
2318
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2278
|
-
const finalTx = await provider.sendTransaction(
|
|
2319
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
2279
2320
|
return finalTx;
|
|
2280
2321
|
}
|
|
2281
2322
|
catch (error) {
|
|
@@ -2324,6 +2365,11 @@ class EthereumTransactions {
|
|
|
2324
2365
|
data: txData,
|
|
2325
2366
|
};
|
|
2326
2367
|
}
|
|
2368
|
+
assertTransactionGasLimit(txToBeSignedByTrezor);
|
|
2369
|
+
const expectedUnsignedTransaction = serializeTransaction({
|
|
2370
|
+
...txToBeSignedByTrezor,
|
|
2371
|
+
type: isLegacy ? 0 : 2,
|
|
2372
|
+
});
|
|
2327
2373
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
2328
2374
|
index: `${activeAccountId}`,
|
|
2329
2375
|
tx: txToBeSignedByTrezor,
|
|
@@ -2359,10 +2405,10 @@ class EthereumTransactions {
|
|
|
2359
2405
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2360
2406
|
};
|
|
2361
2407
|
}
|
|
2362
|
-
const signedTx =
|
|
2408
|
+
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2363
2409
|
assertCurrent();
|
|
2364
2410
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2365
|
-
const finalTx = await provider.sendTransaction(
|
|
2411
|
+
const finalTx = await (0, evm_submission_1.submitSignedEvmTransaction)(signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
2366
2412
|
return finalTx;
|
|
2367
2413
|
}
|
|
2368
2414
|
catch (error) {
|
|
@@ -2424,56 +2470,82 @@ class EthereumTransactions {
|
|
|
2424
2470
|
}
|
|
2425
2471
|
};
|
|
2426
2472
|
this.getTxGasLimit = async (tx) => {
|
|
2427
|
-
const
|
|
2473
|
+
const request = { ...tx };
|
|
2474
|
+
const network = this.getNetwork();
|
|
2475
|
+
const identity = {
|
|
2476
|
+
kind: network.kind,
|
|
2477
|
+
chainId: network.chainId,
|
|
2478
|
+
slip44: network.slip44,
|
|
2479
|
+
url: network.url,
|
|
2480
|
+
};
|
|
2481
|
+
const chainId = (0, evm_fees_1.parseEvmFeeQuantity)(identity.chainId, 'chain ID');
|
|
2482
|
+
const provider = this.web3Provider;
|
|
2483
|
+
const controller = this.abortController;
|
|
2484
|
+
const minimum = this.gasOverrides.minGasLimit;
|
|
2485
|
+
const assertCurrent = () => {
|
|
2486
|
+
const current = this.getNetwork();
|
|
2487
|
+
if (controller.signal.aborted ||
|
|
2488
|
+
controller !== this.abortController ||
|
|
2489
|
+
current.kind !== identity.kind ||
|
|
2490
|
+
current.chainId !== identity.chainId ||
|
|
2491
|
+
current.slip44 !== identity.slip44 ||
|
|
2492
|
+
current.url !== identity.url ||
|
|
2493
|
+
this.web3Provider !== provider) {
|
|
2494
|
+
const error = new Error('EVM gas estimate context changed');
|
|
2495
|
+
error.name = 'AbortError';
|
|
2496
|
+
throw error;
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
const checked = (limit) => {
|
|
2500
|
+
assertCurrent();
|
|
2501
|
+
return ethers_v6_1.BigNumber.from((0, evm_fees_1.assertEvmGasLimit)(typeof limit === 'bigint' ? limit : limit.toBigInt(), chainId));
|
|
2502
|
+
};
|
|
2503
|
+
const hasCalldata = Boolean(request.data && request.data !== '0x' && request.data !== '0x0');
|
|
2428
2504
|
try {
|
|
2429
2505
|
// First attempt: standard estimation
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
// Apply an explicit per-network floor only when configured.
|
|
2434
|
-
if (this.gasOverrides.minGasLimit &&
|
|
2435
|
-
withBuffer.lt(this.gasOverrides.minGasLimit)) {
|
|
2436
|
-
return this.gasOverrides.minGasLimit;
|
|
2437
|
-
}
|
|
2438
|
-
return withBuffer;
|
|
2506
|
+
assertCurrent();
|
|
2507
|
+
const estimated = await provider.estimateGas(request);
|
|
2508
|
+
return checked((0, evm_fees_1.bufferedEvmGasLimit)(estimated.toBigInt(), minimum?.toBigInt()));
|
|
2439
2509
|
}
|
|
2440
2510
|
catch (error) {
|
|
2441
|
-
|
|
2511
|
+
assertCurrent();
|
|
2512
|
+
if (error instanceof evm_fees_1.EvmGasLimitError ||
|
|
2513
|
+
error?.code === 'SYSWEB3_RPC_RESPONSE_INVALID' ||
|
|
2442
2514
|
error?.name === 'AbortError' ||
|
|
2443
2515
|
this.abortController.signal.aborted)
|
|
2444
2516
|
throw error;
|
|
2445
2517
|
console.warn('Gas estimation failed:', error);
|
|
2446
2518
|
if (hasCalldata) {
|
|
2447
2519
|
const calldataFallback = ethers_v6_1.BigNumber.from('100000');
|
|
2448
|
-
if (
|
|
2449
|
-
|
|
2450
|
-
return this.gasOverrides.minGasLimit;
|
|
2520
|
+
if (minimum && calldataFallback.lt(minimum)) {
|
|
2521
|
+
return checked(minimum);
|
|
2451
2522
|
}
|
|
2452
|
-
return calldataFallback;
|
|
2523
|
+
return checked(calldataFallback);
|
|
2453
2524
|
}
|
|
2454
2525
|
// Try a simpler estimation for basic transfers
|
|
2455
2526
|
try {
|
|
2456
|
-
const simpleEstimate = await
|
|
2457
|
-
to:
|
|
2458
|
-
from:
|
|
2459
|
-
value:
|
|
2527
|
+
const simpleEstimate = await provider.estimateGas({
|
|
2528
|
+
to: request.to,
|
|
2529
|
+
from: request.from,
|
|
2530
|
+
value: request.value || '0x0',
|
|
2460
2531
|
});
|
|
2461
2532
|
const withBuffer = simpleEstimate.mul(150).div(100); // 50% buffer for failed estimations
|
|
2462
|
-
if (
|
|
2463
|
-
|
|
2464
|
-
return this.gasOverrides.minGasLimit;
|
|
2533
|
+
if (minimum && withBuffer.lt(minimum)) {
|
|
2534
|
+
return checked(minimum);
|
|
2465
2535
|
}
|
|
2466
|
-
return withBuffer;
|
|
2536
|
+
return checked(withBuffer);
|
|
2467
2537
|
}
|
|
2468
2538
|
catch (secondError) {
|
|
2469
|
-
|
|
2539
|
+
assertCurrent();
|
|
2540
|
+
if (secondError instanceof evm_fees_1.EvmGasLimitError ||
|
|
2541
|
+
secondError?.code === 'SYSWEB3_RPC_RESPONSE_INVALID' ||
|
|
2470
2542
|
secondError?.name === 'AbortError' ||
|
|
2471
2543
|
this.abortController.signal.aborted) {
|
|
2472
2544
|
throw secondError;
|
|
2473
2545
|
}
|
|
2474
2546
|
// Ultimate fallback
|
|
2475
2547
|
console.warn('Simple estimation also failed, using default', secondError);
|
|
2476
|
-
return
|
|
2548
|
+
return checked(minimum || ethers_v6_1.BigNumber.from('100000'));
|
|
2477
2549
|
}
|
|
2478
2550
|
}
|
|
2479
2551
|
};
|
|
@@ -2548,6 +2620,7 @@ class EthereumTransactions {
|
|
|
2548
2620
|
this.trezorSigner = trezorSigner;
|
|
2549
2621
|
this.ledgerSigner = ledgerSigner;
|
|
2550
2622
|
this.captureSigningContext = captureSigningContext;
|
|
2623
|
+
this.captureSubmissionLifecycle = captureSubmissionLifecycle;
|
|
2551
2624
|
}
|
|
2552
2625
|
// Getter that automatically ensures providers are initialized when accessed
|
|
2553
2626
|
get web3Provider() {
|