@sidhujag/sysweb3-keyring 2.1.5 → 2.1.6
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/cjs/evm-fees.js +73 -0
- package/cjs/evm-fees.js.map +1 -1
- package/cjs/software.js.map +1 -1
- package/cjs/transactions/ethereum.js +220 -160
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/evm-cancellation.js +51 -0
- package/cjs/transactions/evm-cancellation.js.map +1 -1
- package/cjs/transactions/evm-local-signer.js +1 -1
- package/cjs/transactions/evm-local-signer.js.map +1 -1
- package/cjs/transactions/evm-submission.js +78 -8
- package/cjs/transactions/evm-submission.js.map +1 -1
- package/cjs/transactions/syscoin-core.js +128 -6
- package/cjs/transactions/syscoin-core.js.map +1 -1
- package/cjs/types.js.map +1 -1
- package/esm/evm-fees.js +72 -0
- package/esm/evm-fees.js.map +1 -1
- package/esm/transactions/ethereum.js +222 -162
- package/esm/transactions/ethereum.js.map +1 -1
- package/esm/transactions/evm-cancellation.js +50 -0
- package/esm/transactions/evm-cancellation.js.map +1 -1
- package/esm/transactions/evm-local-signer.js +1 -1
- package/esm/transactions/evm-local-signer.js.map +1 -1
- package/esm/transactions/evm-submission.js +78 -8
- package/esm/transactions/evm-submission.js.map +1 -1
- package/esm/transactions/syscoin-core.js +129 -7
- package/esm/transactions/syscoin-core.js.map +1 -1
- package/esm/types.js.map +1 -1
- package/package.json +3 -3
- package/types/evm-fees.d.ts +31 -0
- package/types/software.d.ts +1 -1
- package/types/transactions/ethereum.d.ts +9 -3
- package/types/transactions/evm-cancellation.d.ts +3 -0
- package/types/transactions/evm-local-signer.d.ts +1 -1
- package/types/transactions/evm-submission.d.ts +14 -2
- package/types/transactions/syscoin-core.d.ts +3 -1
- package/types/types.d.ts +49 -1
|
@@ -202,7 +202,7 @@ class EthereumTransactions {
|
|
|
202
202
|
assertCurrent,
|
|
203
203
|
};
|
|
204
204
|
};
|
|
205
|
-
this.captureSubmission = (operation, account, type, id, network, assertCurrent,
|
|
205
|
+
this.captureSubmission = (operation, account, type, id, network, assertCurrent, details = {}) => {
|
|
206
206
|
if (!this.captureSubmissionLifecycle)
|
|
207
207
|
throw new TypeError('A durable EVM submission lifecycle is required');
|
|
208
208
|
return (0, evm_submission_1.captureEvmSubmission)({
|
|
@@ -214,7 +214,7 @@ class EthereumTransactions {
|
|
|
214
214
|
slip44: network.slip44,
|
|
215
215
|
url: network.url,
|
|
216
216
|
},
|
|
217
|
-
...
|
|
217
|
+
...details,
|
|
218
218
|
}, assertCurrent, this.captureSubmissionLifecycle);
|
|
219
219
|
};
|
|
220
220
|
this.authorizeHardwareTransaction = async (provider, assertCurrent, signal = this.abortController.signal) => {
|
|
@@ -584,7 +584,9 @@ class EthereumTransactions {
|
|
|
584
584
|
typeof approval.assertCurrent !== 'function' ||
|
|
585
585
|
!(approval.signal instanceof AbortSignal))
|
|
586
586
|
throw new Error(`${standard} transaction approval required`);
|
|
587
|
-
const { assertCurrent, signal } = approval;
|
|
587
|
+
const { assertCurrent, signal, nonce } = approval;
|
|
588
|
+
if (!Number.isSafeInteger(nonce) || nonce < 0)
|
|
589
|
+
throw new Error(`Invalid ${standard} transaction approval nonce`);
|
|
588
590
|
const assertApproval = () => {
|
|
589
591
|
if (signal.aborted)
|
|
590
592
|
throw Object.assign(new Error(`${standard} transaction approval expired`), {
|
|
@@ -593,7 +595,7 @@ class EthereumTransactions {
|
|
|
593
595
|
assertSynchronousOriginalValidation(assertCurrent(), `${standard} transaction approval must be synchronous`);
|
|
594
596
|
};
|
|
595
597
|
assertApproval();
|
|
596
|
-
return { assertApproval, signal };
|
|
598
|
+
return { assertApproval, signal, nonce };
|
|
597
599
|
};
|
|
598
600
|
this.reviewedTokenFees = (request, chainId, standard) => {
|
|
599
601
|
const { isLegacy } = request;
|
|
@@ -650,7 +652,7 @@ class EthereumTransactions {
|
|
|
650
652
|
maxPriorityFeePerGas: ethers_v6_1.BigNumber.from(fee.maxPriorityFeePerGas),
|
|
651
653
|
};
|
|
652
654
|
};
|
|
653
|
-
this.calculateNewGasValues = (oldTxsParams, isForCancel, isLegacy) => {
|
|
655
|
+
this.calculateNewGasValues = (oldTxsParams, isForCancel, isLegacy, bounds) => {
|
|
654
656
|
const newGasValues = {
|
|
655
657
|
maxFeePerGas: undefined,
|
|
656
658
|
maxPriorityFeePerGas: undefined,
|
|
@@ -658,8 +660,34 @@ class EthereumTransactions {
|
|
|
658
660
|
gasLimit: undefined,
|
|
659
661
|
};
|
|
660
662
|
const { maxFeePerGas, maxPriorityFeePerGas, gasLimit, gasPrice } = oldTxsParams;
|
|
663
|
+
if (!isForCancel) {
|
|
664
|
+
const originalGasLimit = (0, evm_fees_1.parseEvmFeeQuantity)(gasLimit, 'replacement gas limit');
|
|
665
|
+
const plan = isLegacy
|
|
666
|
+
? (0, evm_fees_1.planEvmSpeedUpFees)({
|
|
667
|
+
...bounds,
|
|
668
|
+
type: 0,
|
|
669
|
+
gasLimit: originalGasLimit,
|
|
670
|
+
gasPrice: (0, evm_fees_1.parseEvmFeeQuantity)(gasPrice, 'replacement gas price'),
|
|
671
|
+
})
|
|
672
|
+
: (0, evm_fees_1.planEvmSpeedUpFees)({
|
|
673
|
+
...bounds,
|
|
674
|
+
type: 2,
|
|
675
|
+
gasLimit: originalGasLimit,
|
|
676
|
+
maxFeePerGas: (0, evm_fees_1.parseEvmFeeQuantity)(maxFeePerGas, 'replacement fee cap'),
|
|
677
|
+
maxPriorityFeePerGas: (0, evm_fees_1.parseEvmFeeQuantity)(maxPriorityFeePerGas, 'replacement priority fee'),
|
|
678
|
+
});
|
|
679
|
+
newGasValues.gasLimit = ethers_v6_1.BigNumber.from(plan.gasLimit);
|
|
680
|
+
if (plan.type === 0)
|
|
681
|
+
newGasValues.gasPrice = ethers_v6_1.BigNumber.from(plan.gasPrice);
|
|
682
|
+
else {
|
|
683
|
+
newGasValues.maxFeePerGas = ethers_v6_1.BigNumber.from(plan.maxFeePerGas);
|
|
684
|
+
newGasValues.maxPriorityFeePerGas = ethers_v6_1.BigNumber.from(plan.maxPriorityFeePerGas);
|
|
685
|
+
}
|
|
686
|
+
return newGasValues;
|
|
687
|
+
}
|
|
688
|
+
// Keep the legacy direct cancellation helper. The guarded cancellation
|
|
689
|
+
// transaction path already uses planEvmCancellationFees.
|
|
661
690
|
const calculateAndConvertNewValue = (feeValue) => {
|
|
662
|
-
// Apply multiplier for replacement transaction (cancel or speedup)
|
|
663
691
|
const newValue = feeValue * multiplierToUse;
|
|
664
692
|
const calculateValue = String(newValue);
|
|
665
693
|
const convertValueToHex = '0x' + parseInt(calculateValue, 10).toString(16);
|
|
@@ -667,7 +695,6 @@ class EthereumTransactions {
|
|
|
667
695
|
};
|
|
668
696
|
const maxFeePerGasToNumber = maxFeePerGas?.toNumber();
|
|
669
697
|
const maxPriorityFeePerGasToNumber = maxPriorityFeePerGas?.toNumber();
|
|
670
|
-
const gasLimitToNumber = gasLimit?.toNumber();
|
|
671
698
|
const gasPriceToNumber = gasPrice?.toNumber();
|
|
672
699
|
const multiplierToUse = 1.2; //The same calculation we used in the edit fee modal, always using the 0.2 multiplier
|
|
673
700
|
if (!isLegacy) {
|
|
@@ -678,14 +705,8 @@ class EthereumTransactions {
|
|
|
678
705
|
if (isLegacy) {
|
|
679
706
|
newGasValues.gasPrice = calculateAndConvertNewValue(gasPriceToNumber);
|
|
680
707
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const convertToHex = '0x' + parseInt(DEFAULT_GAS_LIMIT_VALUE, 10).toString(16);
|
|
684
|
-
newGasValues.gasLimit = ethers_v6_1.BigNumber.from(convertToHex);
|
|
685
|
-
}
|
|
686
|
-
if (!isForCancel) {
|
|
687
|
-
newGasValues.gasLimit = calculateAndConvertNewValue(gasLimitToNumber);
|
|
688
|
-
}
|
|
708
|
+
newGasValues.gasLimit = ethers_v6_1.BigNumber.from(42000);
|
|
709
|
+
(0, evm_fees_1.assertEvmGasLimit)(newGasValues.gasLimit.toBigInt(), bounds.chainId, bounds.sampledBlockGasLimit);
|
|
689
710
|
return newGasValues;
|
|
690
711
|
};
|
|
691
712
|
this.cancelSentTransaction = async (txHash, isLegacy, fallbackNonce, options) => {
|
|
@@ -694,8 +715,13 @@ class EthereumTransactions {
|
|
|
694
715
|
if (typeof options?.assertCurrentContext !== 'function') {
|
|
695
716
|
throw new Error('Cancellation context required');
|
|
696
717
|
}
|
|
697
|
-
|
|
698
|
-
|
|
718
|
+
const { assertCurrentContext, validateOriginal, origin } = options;
|
|
719
|
+
if (origin === undefined) {
|
|
720
|
+
if (typeof validateOriginal !== 'function')
|
|
721
|
+
throw new Error('Cancellation original validator required');
|
|
722
|
+
}
|
|
723
|
+
else if (validateOriginal !== undefined || fallbackNonce !== undefined) {
|
|
724
|
+
throw new Error('Retained cancellation cannot use RPC fallback');
|
|
699
725
|
}
|
|
700
726
|
if (fallbackNonce !== undefined &&
|
|
701
727
|
(!Number.isSafeInteger(fallbackNonce) || fallbackNonce < 0)) {
|
|
@@ -708,7 +734,6 @@ class EthereumTransactions {
|
|
|
708
734
|
!/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
|
|
709
735
|
throw new Error('Cancellation fee approval required');
|
|
710
736
|
}
|
|
711
|
-
const { assertCurrentContext, validateOriginal } = options;
|
|
712
737
|
const assertHostContext = () => assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
|
|
713
738
|
assertHostContext();
|
|
714
739
|
const state = this.getState();
|
|
@@ -753,9 +778,22 @@ class EthereumTransactions {
|
|
|
753
778
|
assertKeyringContext();
|
|
754
779
|
};
|
|
755
780
|
assertSigningCurrent();
|
|
781
|
+
const retained = origin === undefined
|
|
782
|
+
? undefined
|
|
783
|
+
: (0, evm_cancellation_1.authenticateRetainedCancellation)(origin, txHash.toLowerCase(), activeAccount.address, activeNetwork.chainId);
|
|
784
|
+
if (retained) {
|
|
785
|
+
const legacy = retained.feeFloor.type === 0;
|
|
786
|
+
if (activeNetwork.kind !== sysweb3_network_1.INetworkType.Ethereum ||
|
|
787
|
+
(isLegacy !== undefined && isLegacy !== legacy))
|
|
788
|
+
throw new Error('Retained cancellation network or type changed');
|
|
789
|
+
isLegacy = legacy;
|
|
790
|
+
}
|
|
756
791
|
const submission = previewOnly
|
|
757
792
|
? undefined
|
|
758
|
-
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent,
|
|
793
|
+
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
|
|
794
|
+
originalHash: retained ? txHash.toLowerCase() : txHash,
|
|
795
|
+
origin: retained,
|
|
796
|
+
});
|
|
759
797
|
const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
|
|
760
798
|
const currentEip1559Fees = async () => {
|
|
761
799
|
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
|
|
@@ -767,64 +805,53 @@ class EthereumTransactions {
|
|
|
767
805
|
}
|
|
768
806
|
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
769
807
|
};
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
: undefined;
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
value: ethers_v6_1.Zero,
|
|
802
|
-
nonce: fallbackNonce,
|
|
803
|
-
gasPrice: currentGasPrice,
|
|
804
|
-
data: '0x',
|
|
805
|
-
};
|
|
808
|
+
let cancellationNonce;
|
|
809
|
+
let originalFees;
|
|
810
|
+
if (retained) {
|
|
811
|
+
cancellationNonce = retained.nonce;
|
|
812
|
+
originalFees = retained.feeFloor;
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
const response = await provider.getTransaction(txHash);
|
|
816
|
+
assertCurrent();
|
|
817
|
+
if (response?.blockNumber != null || response?.blockHash != null) {
|
|
818
|
+
throw new Error('Original transaction is already mined');
|
|
819
|
+
}
|
|
820
|
+
const tx = response === null
|
|
821
|
+
? null
|
|
822
|
+
: authenticateReplacementOriginal(response, txHash.toLowerCase(), activeAccount.address.toLowerCase(), activeNetwork.chainId);
|
|
823
|
+
const software = activeAccountType !== types_1.KeyringAccountType.Ledger &&
|
|
824
|
+
activeAccountType !== types_1.KeyringAccountType.Trezor;
|
|
825
|
+
// The host validator may retain the wrapped response and its fee objects.
|
|
826
|
+
const softwareNonce = software ? tx?.nonce ?? fallbackNonce : undefined;
|
|
827
|
+
const softwareOriginalFees = software && tx
|
|
828
|
+
? {
|
|
829
|
+
gasPrice: tx.gasPrice.toBigInt(),
|
|
830
|
+
maxFeePerGas: tx.maxFeePerGas.toBigInt(),
|
|
831
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toBigInt(),
|
|
832
|
+
}
|
|
833
|
+
: undefined;
|
|
834
|
+
assertSynchronousOriginalValidation(validateOriginal(tx));
|
|
835
|
+
assertCurrent();
|
|
836
|
+
if (tx) {
|
|
837
|
+
cancellationNonce = software ? softwareNonce : tx.nonce;
|
|
838
|
+
originalFees = softwareOriginalFees ?? tx;
|
|
806
839
|
}
|
|
807
840
|
else {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
841
|
+
if (fallbackNonce === undefined)
|
|
842
|
+
return { isCanceled: false, error: true };
|
|
843
|
+
cancellationNonce = fallbackNonce;
|
|
844
|
+
// Preserve the ordinary host's explicitly admitted missing-RPC path.
|
|
845
|
+
if (isLegacy) {
|
|
846
|
+
const gasPrice = nonNegativeFeeQuantity(await provider.getGasPrice(), 'cancellation gas price');
|
|
847
|
+
assertCurrent();
|
|
848
|
+
originalFees = { gasPrice };
|
|
849
|
+
}
|
|
850
|
+
else
|
|
851
|
+
originalFees = await currentEip1559Fees();
|
|
818
852
|
}
|
|
819
853
|
}
|
|
820
|
-
|
|
821
|
-
// No fallback nonce provided and tx not found
|
|
822
|
-
return {
|
|
823
|
-
isCanceled: false,
|
|
824
|
-
error: true,
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = softwareOriginalFees ?? tx;
|
|
854
|
+
const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = originalFees;
|
|
828
855
|
let feePlan;
|
|
829
856
|
if (isLegacy) {
|
|
830
857
|
const currentGasPrice = (0, evm_fees_1.parseEvmFeeQuantity)(await provider.getGasPrice(), 'cancellation gas price');
|
|
@@ -861,6 +888,16 @@ class EthereumTransactions {
|
|
|
861
888
|
};
|
|
862
889
|
if (maximumFee.gt(approvedMaximumFee))
|
|
863
890
|
throw new Error('Cancellation fee exceeds approval');
|
|
891
|
+
const assertCancellationEnvelope = (unsigned) => {
|
|
892
|
+
if (cancellationNonce === undefined)
|
|
893
|
+
throw new Error('Cancellation nonce required');
|
|
894
|
+
(0, evm_cancellation_1.assertEvmCancellationEnvelope)(unsigned, {
|
|
895
|
+
payer: activeAccount.address,
|
|
896
|
+
chainId: activeNetwork.chainId,
|
|
897
|
+
nonce: cancellationNonce,
|
|
898
|
+
feePlan,
|
|
899
|
+
});
|
|
900
|
+
};
|
|
864
901
|
const assertApprovedEnvelope = (transaction) => {
|
|
865
902
|
const gas = nonNegativeFeeQuantity(transaction.gasLimit, 'cancellation gas limit');
|
|
866
903
|
const cap = nonNegativeFeeQuantity(isLegacy ? transaction.gasPrice : transaction.maxFeePerGas, 'cancellation maximum fee');
|
|
@@ -869,7 +906,7 @@ class EthereumTransactions {
|
|
|
869
906
|
};
|
|
870
907
|
// Base cancel transaction parameters (same for all wallet types)
|
|
871
908
|
const baseCancelTx = {
|
|
872
|
-
nonce:
|
|
909
|
+
nonce: cancellationNonce,
|
|
873
910
|
from: activeAccount.address,
|
|
874
911
|
to: activeAccount.address,
|
|
875
912
|
value: ethers_v6_1.Zero,
|
|
@@ -895,7 +932,7 @@ class EthereumTransactions {
|
|
|
895
932
|
assertCurrent();
|
|
896
933
|
const formatParams = {
|
|
897
934
|
...resolvedParams,
|
|
898
|
-
nonce: resolvedParams.nonce
|
|
935
|
+
nonce: resolvedParams.nonce != null
|
|
899
936
|
? Number(resolvedParams.nonce.toString())
|
|
900
937
|
: undefined,
|
|
901
938
|
};
|
|
@@ -912,6 +949,8 @@ class EthereumTransactions {
|
|
|
912
949
|
};
|
|
913
950
|
assertApprovedEnvelope(txFormattedForEthers);
|
|
914
951
|
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
952
|
+
if (retained)
|
|
953
|
+
assertCancellationEnvelope(rawTx);
|
|
915
954
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
916
955
|
rawTx: rawTx.replace('0x', ''),
|
|
917
956
|
accountIndex: activeAccountId,
|
|
@@ -927,7 +966,7 @@ class EthereumTransactions {
|
|
|
927
966
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
928
967
|
assertCurrent();
|
|
929
968
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
930
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
969
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
931
970
|
return {
|
|
932
971
|
isCanceled: true,
|
|
933
972
|
transaction: transactionResponse,
|
|
@@ -984,6 +1023,8 @@ class EthereumTransactions {
|
|
|
984
1023
|
assertApprovedEnvelope(txFormattedForTrezor);
|
|
985
1024
|
assertTransactionGasLimit(txFormattedForTrezor);
|
|
986
1025
|
const expectedUnsignedTransaction = serializeTransaction(txFormattedForTrezor);
|
|
1026
|
+
if (retained)
|
|
1027
|
+
assertCancellationEnvelope(expectedUnsignedTransaction);
|
|
987
1028
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
988
1029
|
coin: trezorCoin,
|
|
989
1030
|
tx: txFormattedForTrezor,
|
|
@@ -995,7 +1036,7 @@ class EthereumTransactions {
|
|
|
995
1036
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
996
1037
|
assertCurrent();
|
|
997
1038
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
998
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1039
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
|
|
999
1040
|
return {
|
|
1000
1041
|
isCanceled: true,
|
|
1001
1042
|
transaction: transactionResponse,
|
|
@@ -1026,12 +1067,7 @@ class EthereumTransactions {
|
|
|
1026
1067
|
}
|
|
1027
1068
|
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, (transaction) => {
|
|
1028
1069
|
assertApprovedEnvelope(transaction);
|
|
1029
|
-
(
|
|
1030
|
-
payer: activeAccount.address,
|
|
1031
|
-
chainId: activeNetwork.chainId,
|
|
1032
|
-
nonce: softwareNonce,
|
|
1033
|
-
feePlan,
|
|
1034
|
-
});
|
|
1070
|
+
assertCancellationEnvelope(serializeTransaction(transaction));
|
|
1035
1071
|
}, submission);
|
|
1036
1072
|
if (transactionResponse) {
|
|
1037
1073
|
return {
|
|
@@ -1077,7 +1113,8 @@ class EthereumTransactions {
|
|
|
1077
1113
|
throw new Error('Explicit transaction approval is required');
|
|
1078
1114
|
// Capture once; later caller mutations cannot replace this invocation's
|
|
1079
1115
|
// admission decision while chain checks or device approval are pending.
|
|
1080
|
-
const { assertCurrent: assertHostCurrent, mode } = options;
|
|
1116
|
+
const { assertCurrent: assertHostCurrent, mode, walletNonce } = options;
|
|
1117
|
+
const nonceApproval = walletNonce === undefined ? undefined : { ...walletNonce };
|
|
1081
1118
|
const assertApproved = () => {
|
|
1082
1119
|
const result = assertHostCurrent();
|
|
1083
1120
|
if (result !== undefined) {
|
|
@@ -1088,7 +1125,7 @@ class EthereumTransactions {
|
|
|
1088
1125
|
assertApproved();
|
|
1089
1126
|
return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
|
|
1090
1127
|
broadcastAttempted = true;
|
|
1091
|
-
}, assertApproved, mode);
|
|
1128
|
+
}, assertApproved, mode, nonceApproval);
|
|
1092
1129
|
}
|
|
1093
1130
|
catch (error) {
|
|
1094
1131
|
// A broadcast transport error may mean the node accepted the signed
|
|
@@ -1105,7 +1142,7 @@ class EthereumTransactions {
|
|
|
1105
1142
|
throw submissionError;
|
|
1106
1143
|
}
|
|
1107
1144
|
};
|
|
1108
|
-
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode) => {
|
|
1145
|
+
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce) => {
|
|
1109
1146
|
// Hardware approval may outlive the selected account, network or unlocked
|
|
1110
1147
|
// session. Keep immutable identities and one provider for this invocation.
|
|
1111
1148
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
|
|
@@ -1117,8 +1154,13 @@ class EthereumTransactions {
|
|
|
1117
1154
|
assertCurrent();
|
|
1118
1155
|
beforeBroadcast();
|
|
1119
1156
|
};
|
|
1120
|
-
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent);
|
|
1157
|
+
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { walletNonce });
|
|
1121
1158
|
const assertCurrent = submission.assertCurrent;
|
|
1159
|
+
const approvedNonce = submission.context.walletNonce?.nonce;
|
|
1160
|
+
const assertApprovedNonce = (nonce) => {
|
|
1161
|
+
if (approvedNonce !== undefined && nonce !== approvedNonce)
|
|
1162
|
+
throw new Error('EVM transaction nonce does not match wallet approval');
|
|
1163
|
+
};
|
|
1122
1164
|
if (mode === 'reviewed-native') {
|
|
1123
1165
|
const { transaction } = (0, prepared_evm_1.snapshotPreparedEvmRequest)({
|
|
1124
1166
|
account: {
|
|
@@ -1134,6 +1176,7 @@ class EthereumTransactions {
|
|
|
1134
1176
|
},
|
|
1135
1177
|
transaction: input,
|
|
1136
1178
|
});
|
|
1179
|
+
assertApprovedNonce(Number(transaction.nonce));
|
|
1137
1180
|
const minimum = this.gasOverrides.minGasLimit;
|
|
1138
1181
|
if (transaction.data !== '0x' ||
|
|
1139
1182
|
BigInt(transaction.value) === 0n ||
|
|
@@ -1152,7 +1195,7 @@ class EthereumTransactions {
|
|
|
1152
1195
|
throw new Error('Reviewed native signer does not match the approved account');
|
|
1153
1196
|
assertCurrent();
|
|
1154
1197
|
const { signed, expectedUnsigned } = (0, prepared_evm_1.signPreparedEvmEnvelope)(transaction, decryptedPrivateKey);
|
|
1155
|
-
return (0, evm_submission_1.submitSignedEvmTransaction)(signed.rawTransaction, (
|
|
1198
|
+
return (0, evm_submission_1.submitSignedEvmTransaction)(signed.rawTransaction, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsigned);
|
|
1156
1199
|
}
|
|
1157
1200
|
// Detach supplied values before any RPC/device await. Autofill updates only
|
|
1158
1201
|
// this invocation; caller mutations cannot replace already supplied fields.
|
|
@@ -1187,6 +1230,7 @@ class EthereumTransactions {
|
|
|
1187
1230
|
supplied.data = (0, utils_1.hexlify)(supplied.data);
|
|
1188
1231
|
if (supplied.accessList != null)
|
|
1189
1232
|
supplied.accessList = (0, transaction_1.accessListify)(supplied.accessList);
|
|
1233
|
+
assertApprovedNonce(supplied.nonce);
|
|
1190
1234
|
params = supplied;
|
|
1191
1235
|
assertCurrent();
|
|
1192
1236
|
// Capture the reviewed nonce before any fee/RPC await. Hardware and the
|
|
@@ -1308,7 +1352,7 @@ class EthereumTransactions {
|
|
|
1308
1352
|
try {
|
|
1309
1353
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1310
1354
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1311
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1355
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1312
1356
|
return finalTx;
|
|
1313
1357
|
}
|
|
1314
1358
|
catch (error) {
|
|
@@ -1398,7 +1442,7 @@ class EthereumTransactions {
|
|
|
1398
1442
|
};
|
|
1399
1443
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1400
1444
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1401
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1445
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1402
1446
|
return finalTx;
|
|
1403
1447
|
}
|
|
1404
1448
|
catch (error) {
|
|
@@ -1453,7 +1497,7 @@ class EthereumTransactions {
|
|
|
1453
1497
|
throw new Error('Replacement context required');
|
|
1454
1498
|
if (typeof options.validateOriginal !== 'function')
|
|
1455
1499
|
throw new Error('Replacement original validator required');
|
|
1456
|
-
const { assertCurrentContext, validateOriginal, beforeBroadcast } = options;
|
|
1500
|
+
const { assertCurrentContext, validateOriginal, beforeBroadcast, retainedCancellation, } = options;
|
|
1457
1501
|
if (!previewOnly)
|
|
1458
1502
|
this.assertHardwareSignerAvailable(options.targetAccount);
|
|
1459
1503
|
isLegacy ?? (isLegacy = false);
|
|
@@ -1494,9 +1538,27 @@ class EthereumTransactions {
|
|
|
1494
1538
|
const requestedHash = txHash.toLowerCase();
|
|
1495
1539
|
const provider = this.web3Provider;
|
|
1496
1540
|
assertSigningCurrent();
|
|
1541
|
+
let cancellation;
|
|
1542
|
+
if (retainedCancellation !== undefined) {
|
|
1543
|
+
if (typeof retainedCancellation !== 'string' ||
|
|
1544
|
+
retainedCancellation.length > 1024)
|
|
1545
|
+
throw new Error('Invalid retained cancellation');
|
|
1546
|
+
cancellation = transaction_1.Transaction.from(retainedCancellation);
|
|
1547
|
+
if (!cancellation.isSigned() ||
|
|
1548
|
+
cancellation.serialized !== retainedCancellation ||
|
|
1549
|
+
cancellation.hash !== requestedHash ||
|
|
1550
|
+
cancellation.chainId !== BigInt(activeNetwork.chainId) ||
|
|
1551
|
+
cancellation.from?.toLowerCase() !== expectedSender ||
|
|
1552
|
+
cancellation.to?.toLowerCase() !== expectedSender ||
|
|
1553
|
+
cancellation.value !== 0n ||
|
|
1554
|
+
cancellation.data !== '0x' ||
|
|
1555
|
+
cancellation.accessList?.length ||
|
|
1556
|
+
(cancellation.type !== 0 && cancellation.type !== 2))
|
|
1557
|
+
throw new Error('Invalid retained cancellation');
|
|
1558
|
+
}
|
|
1497
1559
|
const submission = previewOnly
|
|
1498
1560
|
? undefined
|
|
1499
|
-
: this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
|
|
1561
|
+
: this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { originalHash: txHash });
|
|
1500
1562
|
const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
|
|
1501
1563
|
const validateLookup = (transaction) => {
|
|
1502
1564
|
assertCurrent();
|
|
@@ -1504,6 +1566,10 @@ class EthereumTransactions {
|
|
|
1504
1566
|
return null;
|
|
1505
1567
|
const authenticated = authenticateReplacementOriginal(transaction, requestedHash, expectedSender, activeNetwork.chainId);
|
|
1506
1568
|
const type = authenticated.type;
|
|
1569
|
+
if (cancellation &&
|
|
1570
|
+
(type !== cancellation.type ||
|
|
1571
|
+
(0, evm_fees_1.parseEvmFeeQuantity)(authenticated.gasLimit, 'retained cancellation gas limit') !== cancellation.gasLimit))
|
|
1572
|
+
throw new Error('Retained cancellation does not match original');
|
|
1507
1573
|
if (type !== 1)
|
|
1508
1574
|
isLegacy = type === 0;
|
|
1509
1575
|
if (isLegacy &&
|
|
@@ -1532,9 +1598,20 @@ class EthereumTransactions {
|
|
|
1532
1598
|
};
|
|
1533
1599
|
}
|
|
1534
1600
|
}
|
|
1535
|
-
// Check if this might be a max send transaction by comparing total cost to balance
|
|
1536
1601
|
const currentBalance = await provider.getBalance(activeAccount.address);
|
|
1537
1602
|
assertCurrent();
|
|
1603
|
+
const sampledBlock = await provider.getBlock('latest');
|
|
1604
|
+
assertCurrent();
|
|
1605
|
+
const bounds = Object.freeze({
|
|
1606
|
+
chainId: BigInt(activeNetwork.chainId),
|
|
1607
|
+
...(cancellation
|
|
1608
|
+
? { retainedCancellationGasLimit: cancellation.gasLimit }
|
|
1609
|
+
: {}),
|
|
1610
|
+
sampledBlockGasLimit: (0, evm_fees_1.parseEvmFeeQuantity)(sampledBlock?.gasLimit, 'replacement block gas limit'),
|
|
1611
|
+
sampledBaseFeePerGas: sampledBlock?.baseFeePerGas == null
|
|
1612
|
+
? null
|
|
1613
|
+
: (0, evm_fees_1.parseEvmFeeQuantity)(sampledBlock.baseFeePerGas, 'replacement base fee'),
|
|
1614
|
+
});
|
|
1538
1615
|
// Ensure all transaction values are resolved from promises
|
|
1539
1616
|
const gasLimit = await Promise.resolve(tx.gasLimit);
|
|
1540
1617
|
const gasPrice = await Promise.resolve(tx.gasPrice || 0);
|
|
@@ -1543,15 +1620,6 @@ class EthereumTransactions {
|
|
|
1543
1620
|
const txValue = await Promise.resolve(tx.value);
|
|
1544
1621
|
const txData = await Promise.resolve(tx.data || '0x');
|
|
1545
1622
|
assertCurrent();
|
|
1546
|
-
// Check if this is a contract call (has data)
|
|
1547
|
-
const isContractCall = txData && txData !== '0x' && txData.length > 2;
|
|
1548
|
-
const originalGasCost = isLegacy
|
|
1549
|
-
? gasLimit.mul(gasPrice || 0)
|
|
1550
|
-
: gasLimit.mul(maxFeePerGas || 0);
|
|
1551
|
-
const originalTotalCost = txValue.add(originalGasCost);
|
|
1552
|
-
// If original transaction used >95% of balance, it's likely a max send
|
|
1553
|
-
const balanceThreshold = currentBalance.mul(95).div(100);
|
|
1554
|
-
const isLikelyMaxSend = originalTotalCost.gt(balanceThreshold);
|
|
1555
1623
|
const oldTxsGasValues = {
|
|
1556
1624
|
maxFeePerGas: maxFeePerGas,
|
|
1557
1625
|
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
|
@@ -1565,26 +1633,33 @@ class EthereumTransactions {
|
|
|
1565
1633
|
assertCurrent();
|
|
1566
1634
|
}
|
|
1567
1635
|
else if (!isLegacy && oldTxsGasValues.maxFeePerGas.isZero()) {
|
|
1568
|
-
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(
|
|
1636
|
+
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas({
|
|
1637
|
+
getGasPrice: () => provider.getGasPrice(),
|
|
1638
|
+
getBlock: async () => sampledBlock,
|
|
1639
|
+
send: (method, params) => provider.send(method, params),
|
|
1640
|
+
});
|
|
1569
1641
|
assertCurrent();
|
|
1570
1642
|
oldTxsGasValues.maxFeePerGas = ethers_v6_1.BigNumber.from(feeData.maxFeePerGas || 0);
|
|
1571
1643
|
oldTxsGasValues.maxPriorityFeePerGas = ethers_v6_1.BigNumber.from(feeData.maxPriorityFeePerGas || 0);
|
|
1572
1644
|
}
|
|
1573
|
-
const newGasValues = this.calculateNewGasValues(oldTxsGasValues, false, isLegacy);
|
|
1645
|
+
const newGasValues = this.calculateNewGasValues(oldTxsGasValues, false, isLegacy, bounds);
|
|
1574
1646
|
if (!newGasValues.gasLimit.gt(0))
|
|
1575
1647
|
throw new Error('Invalid replacement gas limit');
|
|
1648
|
+
assertTransactionGasLimit({
|
|
1649
|
+
gasLimit: newGasValues.gasLimit,
|
|
1650
|
+
chainId: activeNetwork.chainId,
|
|
1651
|
+
});
|
|
1576
1652
|
if (!isLegacy &&
|
|
1577
1653
|
newGasValues.maxPriorityFeePerGas.gt(newGasValues.maxFeePerGas))
|
|
1578
1654
|
throw new Error('Invalid replacement fee');
|
|
1579
1655
|
const maximumFee = newGasValues.gasLimit.mul(isLegacy ? newGasValues.gasPrice : newGasValues.maxFeePerGas);
|
|
1580
|
-
if (
|
|
1581
|
-
if (
|
|
1582
|
-
console.error('[SpeedUp] Cannot adjust value for contract call - rejecting speedup');
|
|
1656
|
+
if (txValue.add(maximumFee).gt(currentBalance)) {
|
|
1657
|
+
if (txData.length > 2) {
|
|
1583
1658
|
return {
|
|
1584
1659
|
isSpeedUp: false,
|
|
1585
1660
|
error: true,
|
|
1586
1661
|
code: 'CONTRACT_CALL_MAX_SEND',
|
|
1587
|
-
message: '
|
|
1662
|
+
message: 'Add funds before speeding up this contract call; the original amount will not be changed',
|
|
1588
1663
|
};
|
|
1589
1664
|
}
|
|
1590
1665
|
return {
|
|
@@ -1651,7 +1726,7 @@ class EthereumTransactions {
|
|
|
1651
1726
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1652
1727
|
assertSignedSender(signedTx);
|
|
1653
1728
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1654
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1729
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1655
1730
|
return {
|
|
1656
1731
|
isSpeedUp: true,
|
|
1657
1732
|
transaction: transactionResponse,
|
|
@@ -1732,7 +1807,7 @@ class EthereumTransactions {
|
|
|
1732
1807
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
1733
1808
|
assertSignedSender(signedTx);
|
|
1734
1809
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1735
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1810
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1736
1811
|
return {
|
|
1737
1812
|
isSpeedUp: true,
|
|
1738
1813
|
transaction: transactionResponse,
|
|
@@ -1818,7 +1893,7 @@ class EthereumTransactions {
|
|
|
1818
1893
|
}
|
|
1819
1894
|
};
|
|
1820
1895
|
this.sendSignedErc20Transaction = async (request, approval) => {
|
|
1821
|
-
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC20');
|
|
1896
|
+
const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC20');
|
|
1822
1897
|
const { receiver, tokenAddress, tokenAmountUnits, saveTrezorTx, networkUrl, } = request;
|
|
1823
1898
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
1824
1899
|
const activeAccountAddress = activeAccount.address;
|
|
@@ -1835,7 +1910,7 @@ class EthereumTransactions {
|
|
|
1835
1910
|
const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
1836
1911
|
assertApproval();
|
|
1837
1912
|
assertSigningCurrent();
|
|
1838
|
-
});
|
|
1913
|
+
}, { walletNonce: { nonce, source: 'automatic' } });
|
|
1839
1914
|
const assertCurrent = submission.assertCurrent;
|
|
1840
1915
|
const sendERC20Token = async () => {
|
|
1841
1916
|
assertCurrent();
|
|
@@ -1845,7 +1920,6 @@ class EthereumTransactions {
|
|
|
1845
1920
|
let transferMethod;
|
|
1846
1921
|
if (isLegacy) {
|
|
1847
1922
|
const overrides = {
|
|
1848
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1849
1923
|
gasPrice,
|
|
1850
1924
|
gasLimit,
|
|
1851
1925
|
type: 0, // Explicitly set Type 0 for legacy token transfers
|
|
@@ -1855,7 +1929,6 @@ class EthereumTransactions {
|
|
|
1855
1929
|
}
|
|
1856
1930
|
else {
|
|
1857
1931
|
const overrides = {
|
|
1858
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1859
1932
|
maxPriorityFeePerGas,
|
|
1860
1933
|
maxFeePerGas,
|
|
1861
1934
|
gasLimit,
|
|
@@ -1865,6 +1938,7 @@ class EthereumTransactions {
|
|
|
1865
1938
|
}
|
|
1866
1939
|
return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
|
|
1867
1940
|
...transferMethod,
|
|
1941
|
+
nonce,
|
|
1868
1942
|
chainId: activeNetwork.chainId,
|
|
1869
1943
|
from: activeAccountAddress,
|
|
1870
1944
|
value: '0x0',
|
|
@@ -1875,8 +1949,6 @@ class EthereumTransactions {
|
|
|
1875
1949
|
}
|
|
1876
1950
|
};
|
|
1877
1951
|
const sendERC20TokenOnLedger = async () => {
|
|
1878
|
-
assertCurrent();
|
|
1879
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
1880
1952
|
assertCurrent();
|
|
1881
1953
|
try {
|
|
1882
1954
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
@@ -1892,7 +1964,7 @@ class EthereumTransactions {
|
|
|
1892
1964
|
gasLimit: gasLimit,
|
|
1893
1965
|
gasPrice,
|
|
1894
1966
|
data: txData,
|
|
1895
|
-
nonce
|
|
1967
|
+
nonce,
|
|
1896
1968
|
chainId: activeNetwork.chainId,
|
|
1897
1969
|
type: 0,
|
|
1898
1970
|
};
|
|
@@ -1905,7 +1977,7 @@ class EthereumTransactions {
|
|
|
1905
1977
|
maxFeePerGas,
|
|
1906
1978
|
maxPriorityFeePerGas,
|
|
1907
1979
|
data: txData,
|
|
1908
|
-
nonce
|
|
1980
|
+
nonce,
|
|
1909
1981
|
chainId: activeNetwork.chainId,
|
|
1910
1982
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1911
1983
|
};
|
|
@@ -1927,8 +1999,8 @@ class EthereumTransactions {
|
|
|
1927
1999
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1928
2000
|
assertCurrent();
|
|
1929
2001
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1930
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (
|
|
1931
|
-
const response = await provider.sendTransaction(
|
|
2002
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
|
|
2003
|
+
const response = await admit(() => provider.sendTransaction(rawTransaction));
|
|
1932
2004
|
saveTrezorTx && saveTrezorTx(response);
|
|
1933
2005
|
return response;
|
|
1934
2006
|
}, submission, undefined, rawTx);
|
|
@@ -1947,8 +2019,6 @@ class EthereumTransactions {
|
|
|
1947
2019
|
}
|
|
1948
2020
|
};
|
|
1949
2021
|
const sendERC20TokenOnTrezor = async () => {
|
|
1950
|
-
assertCurrent();
|
|
1951
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
1952
2022
|
assertCurrent();
|
|
1953
2023
|
try {
|
|
1954
2024
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
@@ -1963,7 +2033,7 @@ class EthereumTransactions {
|
|
|
1963
2033
|
value: '0x0',
|
|
1964
2034
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1965
2035
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
1966
|
-
nonce: this.toBigNumber(
|
|
2036
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
1967
2037
|
chainId: activeNetwork.chainId,
|
|
1968
2038
|
data: txData,
|
|
1969
2039
|
};
|
|
@@ -1975,7 +2045,7 @@ class EthereumTransactions {
|
|
|
1975
2045
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1976
2046
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
1977
2047
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
1978
|
-
nonce: this.toBigNumber(
|
|
2048
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
1979
2049
|
chainId: activeNetwork.chainId,
|
|
1980
2050
|
data: txData,
|
|
1981
2051
|
};
|
|
@@ -2002,7 +2072,7 @@ class EthereumTransactions {
|
|
|
2002
2072
|
gasLimit: gasLimit,
|
|
2003
2073
|
gasPrice,
|
|
2004
2074
|
data: txData,
|
|
2005
|
-
nonce
|
|
2075
|
+
nonce,
|
|
2006
2076
|
chainId: activeNetwork.chainId,
|
|
2007
2077
|
type: 0,
|
|
2008
2078
|
};
|
|
@@ -2015,7 +2085,7 @@ class EthereumTransactions {
|
|
|
2015
2085
|
maxFeePerGas,
|
|
2016
2086
|
maxPriorityFeePerGas,
|
|
2017
2087
|
data: txData,
|
|
2018
|
-
nonce
|
|
2088
|
+
nonce,
|
|
2019
2089
|
chainId: activeNetwork.chainId,
|
|
2020
2090
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2021
2091
|
};
|
|
@@ -2023,8 +2093,8 @@ class EthereumTransactions {
|
|
|
2023
2093
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2024
2094
|
assertCurrent();
|
|
2025
2095
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2026
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (
|
|
2027
|
-
const response = await provider.sendTransaction(
|
|
2096
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
|
|
2097
|
+
const response = await admit(() => provider.sendTransaction(rawTransaction));
|
|
2028
2098
|
saveTrezorTx && saveTrezorTx(response);
|
|
2029
2099
|
return response;
|
|
2030
2100
|
}, submission, undefined, expectedUnsignedTransaction);
|
|
@@ -2052,7 +2122,7 @@ class EthereumTransactions {
|
|
|
2052
2122
|
}
|
|
2053
2123
|
};
|
|
2054
2124
|
this.sendSignedErc721Transaction = async (request, approval) => {
|
|
2055
|
-
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC721');
|
|
2125
|
+
const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC721');
|
|
2056
2126
|
const { receiver, tokenAddress, tokenId, networkUrl } = request;
|
|
2057
2127
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
2058
2128
|
if (networkUrl !== activeNetwork.url)
|
|
@@ -2069,7 +2139,7 @@ class EthereumTransactions {
|
|
|
2069
2139
|
const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
2070
2140
|
assertApproval();
|
|
2071
2141
|
assertSigningCurrent();
|
|
2072
|
-
});
|
|
2142
|
+
}, { walletNonce: { nonce, source: 'automatic' } });
|
|
2073
2143
|
const assertCurrent = submission.assertCurrent;
|
|
2074
2144
|
const sendERC721Token = async () => {
|
|
2075
2145
|
assertCurrent();
|
|
@@ -2079,7 +2149,6 @@ class EthereumTransactions {
|
|
|
2079
2149
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
|
|
2080
2150
|
if (isLegacy) {
|
|
2081
2151
|
const overrides = {
|
|
2082
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2083
2152
|
gasPrice,
|
|
2084
2153
|
gasLimit,
|
|
2085
2154
|
type: 0, // Explicitly set Type 0 for legacy NFT transfers
|
|
@@ -2088,7 +2157,6 @@ class EthereumTransactions {
|
|
|
2088
2157
|
}
|
|
2089
2158
|
else {
|
|
2090
2159
|
const overrides = {
|
|
2091
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2092
2160
|
maxPriorityFeePerGas,
|
|
2093
2161
|
maxFeePerGas,
|
|
2094
2162
|
gasLimit,
|
|
@@ -2097,6 +2165,7 @@ class EthereumTransactions {
|
|
|
2097
2165
|
}
|
|
2098
2166
|
return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
|
|
2099
2167
|
...transferMethod,
|
|
2168
|
+
nonce,
|
|
2100
2169
|
chainId: activeNetwork.chainId,
|
|
2101
2170
|
from: activeAccountAddress,
|
|
2102
2171
|
value: '0x0',
|
|
@@ -2107,8 +2176,6 @@ class EthereumTransactions {
|
|
|
2107
2176
|
}
|
|
2108
2177
|
};
|
|
2109
2178
|
const sendERC721TokenOnLedger = async () => {
|
|
2110
|
-
assertCurrent();
|
|
2111
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2112
2179
|
assertCurrent();
|
|
2113
2180
|
try {
|
|
2114
2181
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
|
|
@@ -2125,7 +2192,7 @@ class EthereumTransactions {
|
|
|
2125
2192
|
gasLimit,
|
|
2126
2193
|
gasPrice,
|
|
2127
2194
|
data: txData,
|
|
2128
|
-
nonce
|
|
2195
|
+
nonce,
|
|
2129
2196
|
chainId: activeNetwork.chainId,
|
|
2130
2197
|
type: 0,
|
|
2131
2198
|
};
|
|
@@ -2138,7 +2205,7 @@ class EthereumTransactions {
|
|
|
2138
2205
|
maxFeePerGas,
|
|
2139
2206
|
maxPriorityFeePerGas,
|
|
2140
2207
|
data: txData,
|
|
2141
|
-
nonce
|
|
2208
|
+
nonce,
|
|
2142
2209
|
chainId: activeNetwork.chainId,
|
|
2143
2210
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2144
2211
|
};
|
|
@@ -2160,7 +2227,7 @@ class EthereumTransactions {
|
|
|
2160
2227
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2161
2228
|
assertCurrent();
|
|
2162
2229
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2163
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2230
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2164
2231
|
return finalTx;
|
|
2165
2232
|
}
|
|
2166
2233
|
catch (error) {
|
|
@@ -2176,8 +2243,6 @@ class EthereumTransactions {
|
|
|
2176
2243
|
}
|
|
2177
2244
|
};
|
|
2178
2245
|
const sendERC721TokenOnTrezor = async () => {
|
|
2179
|
-
assertCurrent();
|
|
2180
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2181
2246
|
assertCurrent();
|
|
2182
2247
|
try {
|
|
2183
2248
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc21Abi)(), provider);
|
|
@@ -2193,7 +2258,7 @@ class EthereumTransactions {
|
|
|
2193
2258
|
value: '0x0',
|
|
2194
2259
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2195
2260
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2196
|
-
nonce: this.toBigNumber(
|
|
2261
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2197
2262
|
chainId: activeNetwork.chainId,
|
|
2198
2263
|
data: txData,
|
|
2199
2264
|
};
|
|
@@ -2206,7 +2271,7 @@ class EthereumTransactions {
|
|
|
2206
2271
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2207
2272
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2208
2273
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2209
|
-
nonce: this.toBigNumber(
|
|
2274
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2210
2275
|
chainId: activeNetwork.chainId,
|
|
2211
2276
|
data: txData,
|
|
2212
2277
|
};
|
|
@@ -2235,7 +2300,7 @@ class EthereumTransactions {
|
|
|
2235
2300
|
gasLimit,
|
|
2236
2301
|
gasPrice,
|
|
2237
2302
|
data: txData,
|
|
2238
|
-
nonce
|
|
2303
|
+
nonce,
|
|
2239
2304
|
chainId: activeNetwork.chainId,
|
|
2240
2305
|
type: 0,
|
|
2241
2306
|
};
|
|
@@ -2248,7 +2313,7 @@ class EthereumTransactions {
|
|
|
2248
2313
|
maxFeePerGas,
|
|
2249
2314
|
maxPriorityFeePerGas,
|
|
2250
2315
|
data: txData,
|
|
2251
|
-
nonce
|
|
2316
|
+
nonce,
|
|
2252
2317
|
chainId: activeNetwork.chainId,
|
|
2253
2318
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2254
2319
|
};
|
|
@@ -2256,7 +2321,7 @@ class EthereumTransactions {
|
|
|
2256
2321
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2257
2322
|
assertCurrent();
|
|
2258
2323
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2259
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2324
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
|
|
2260
2325
|
return finalTx;
|
|
2261
2326
|
}
|
|
2262
2327
|
catch (error) {
|
|
@@ -2283,7 +2348,7 @@ class EthereumTransactions {
|
|
|
2283
2348
|
}
|
|
2284
2349
|
};
|
|
2285
2350
|
this.sendSignedErc1155Transaction = async (request, approval) => {
|
|
2286
|
-
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC1155');
|
|
2351
|
+
const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC1155');
|
|
2287
2352
|
const { receiver, tokenAddress, tokenId, networkUrl } = request;
|
|
2288
2353
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
2289
2354
|
if (networkUrl !== activeNetwork.url)
|
|
@@ -2303,7 +2368,7 @@ class EthereumTransactions {
|
|
|
2303
2368
|
const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
2304
2369
|
assertApproval();
|
|
2305
2370
|
assertSigningCurrent();
|
|
2306
|
-
});
|
|
2371
|
+
}, { walletNonce: { nonce, source: 'automatic' } });
|
|
2307
2372
|
const assertCurrent = submission.assertCurrent;
|
|
2308
2373
|
const sendERC1155Token = async () => {
|
|
2309
2374
|
assertCurrent();
|
|
@@ -2315,7 +2380,6 @@ class EthereumTransactions {
|
|
|
2315
2380
|
let overrides;
|
|
2316
2381
|
if (isLegacy) {
|
|
2317
2382
|
overrides = {
|
|
2318
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2319
2383
|
gasPrice,
|
|
2320
2384
|
gasLimit,
|
|
2321
2385
|
type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
|
|
@@ -2323,7 +2387,6 @@ class EthereumTransactions {
|
|
|
2323
2387
|
}
|
|
2324
2388
|
else {
|
|
2325
2389
|
overrides = {
|
|
2326
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2327
2390
|
maxPriorityFeePerGas,
|
|
2328
2391
|
maxFeePerGas,
|
|
2329
2392
|
gasLimit,
|
|
@@ -2332,6 +2395,7 @@ class EthereumTransactions {
|
|
|
2332
2395
|
transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, amount, '0x', normalizeEthersOverrides(overrides));
|
|
2333
2396
|
return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
|
|
2334
2397
|
...transferMethod,
|
|
2398
|
+
nonce,
|
|
2335
2399
|
chainId: activeNetwork.chainId,
|
|
2336
2400
|
from: activeAccountAddress,
|
|
2337
2401
|
value: '0x0',
|
|
@@ -2342,8 +2406,6 @@ class EthereumTransactions {
|
|
|
2342
2406
|
}
|
|
2343
2407
|
};
|
|
2344
2408
|
const sendERC1155TokenOnLedger = async () => {
|
|
2345
|
-
assertCurrent();
|
|
2346
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2347
2409
|
assertCurrent();
|
|
2348
2410
|
try {
|
|
2349
2411
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
@@ -2356,7 +2418,7 @@ class EthereumTransactions {
|
|
|
2356
2418
|
gasLimit,
|
|
2357
2419
|
gasPrice,
|
|
2358
2420
|
data: txData,
|
|
2359
|
-
nonce
|
|
2421
|
+
nonce,
|
|
2360
2422
|
chainId: activeNetwork.chainId,
|
|
2361
2423
|
type: 0,
|
|
2362
2424
|
};
|
|
@@ -2369,7 +2431,7 @@ class EthereumTransactions {
|
|
|
2369
2431
|
maxFeePerGas,
|
|
2370
2432
|
maxPriorityFeePerGas,
|
|
2371
2433
|
data: txData,
|
|
2372
|
-
nonce
|
|
2434
|
+
nonce,
|
|
2373
2435
|
chainId: activeNetwork.chainId,
|
|
2374
2436
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2375
2437
|
};
|
|
@@ -2391,7 +2453,7 @@ class EthereumTransactions {
|
|
|
2391
2453
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2392
2454
|
assertCurrent();
|
|
2393
2455
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2394
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2456
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2395
2457
|
return finalTx;
|
|
2396
2458
|
}
|
|
2397
2459
|
catch (error) {
|
|
@@ -2407,8 +2469,6 @@ class EthereumTransactions {
|
|
|
2407
2469
|
}
|
|
2408
2470
|
};
|
|
2409
2471
|
const sendERC1155TokenOnTrezor = async () => {
|
|
2410
|
-
assertCurrent();
|
|
2411
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2412
2472
|
assertCurrent();
|
|
2413
2473
|
try {
|
|
2414
2474
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
@@ -2420,7 +2480,7 @@ class EthereumTransactions {
|
|
|
2420
2480
|
value: '0x0',
|
|
2421
2481
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2422
2482
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2423
|
-
nonce: this.toBigNumber(
|
|
2483
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2424
2484
|
chainId: activeNetwork.chainId,
|
|
2425
2485
|
data: txData,
|
|
2426
2486
|
};
|
|
@@ -2432,7 +2492,7 @@ class EthereumTransactions {
|
|
|
2432
2492
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2433
2493
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2434
2494
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2435
|
-
nonce: this.toBigNumber(
|
|
2495
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2436
2496
|
chainId: activeNetwork.chainId,
|
|
2437
2497
|
data: txData,
|
|
2438
2498
|
};
|
|
@@ -2459,7 +2519,7 @@ class EthereumTransactions {
|
|
|
2459
2519
|
gasLimit,
|
|
2460
2520
|
gasPrice,
|
|
2461
2521
|
data: txData,
|
|
2462
|
-
nonce
|
|
2522
|
+
nonce,
|
|
2463
2523
|
chainId: activeNetwork.chainId,
|
|
2464
2524
|
type: 0,
|
|
2465
2525
|
};
|
|
@@ -2472,7 +2532,7 @@ class EthereumTransactions {
|
|
|
2472
2532
|
maxFeePerGas,
|
|
2473
2533
|
maxPriorityFeePerGas,
|
|
2474
2534
|
data: txData,
|
|
2475
|
-
nonce
|
|
2535
|
+
nonce,
|
|
2476
2536
|
chainId: activeNetwork.chainId,
|
|
2477
2537
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2478
2538
|
};
|
|
@@ -2480,7 +2540,7 @@ class EthereumTransactions {
|
|
|
2480
2540
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2481
2541
|
assertCurrent();
|
|
2482
2542
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2483
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2543
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
|
|
2484
2544
|
return finalTx;
|
|
2485
2545
|
}
|
|
2486
2546
|
catch (error) {
|