@sidhujag/sysweb3-keyring 2.1.4 → 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/CHANGELOG.md +6 -0
- 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 +155 -10
- package/cjs/transactions/syscoin-core.js.map +1 -1
- package/cjs/types.js.map +1 -1
- package/cjs/utils/excluded-utxos.js +6 -2
- package/cjs/utils/excluded-utxos.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 +156 -11
- package/esm/transactions/syscoin-core.js.map +1 -1
- package/esm/types.js.map +1 -1
- package/esm/utils/excluded-utxos.js +6 -2
- package/esm/utils/excluded-utxos.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
- package/types/utils/excluded-utxos.d.ts +1 -1
|
@@ -5,10 +5,10 @@ import { accessListify, Transaction } from 'ethers/transaction';
|
|
|
5
5
|
import { hexlify } from 'ethers/utils';
|
|
6
6
|
import omit from 'lodash/omit';
|
|
7
7
|
import { BigNumber, Contract, Zero, formatEther, formatUnits, isHexString, normalizeTransactionRequest, normalizeTxValue, resolveProperties, serializeTransaction as serializeUncheckedTransaction, } from '../ethers-v6';
|
|
8
|
-
import { assertEvmGasLimit, bufferedEvmGasLimit, EvmGasLimitError, getEvmFeeData, parseEvmFeeQuantity, planEvmCancellationFees, } from '../evm-fees';
|
|
8
|
+
import { assertEvmGasLimit, bufferedEvmGasLimit, EvmGasLimitError, getEvmFeeData, parseEvmFeeQuantity, planEvmCancellationFees, planEvmSpeedUpFees, } from '../evm-fees';
|
|
9
9
|
import { CustomJsonRpcProvider, wrapTransactionResponse } from '../providers';
|
|
10
10
|
import { KeyringAccountType, } from '../types';
|
|
11
|
-
import { assertEvmCancellationEnvelope } from './evm-cancellation';
|
|
11
|
+
import { assertEvmCancellationEnvelope, authenticateRetainedCancellation, } from './evm-cancellation';
|
|
12
12
|
import { deriveEvmAccountFromMnemonic, parsePersonalMessage as parseLocalPersonalMessage, privateKeyToAccount, normalizeGasLimitedTransaction, sendLocalEvmTransaction, signDigestHex, signPersonalMessage as signLocalPersonalMessage, } from './evm-local-signer';
|
|
13
13
|
import { captureEvmSubmission, submitSignedEvmTransaction, submitSignedHardwareEvmTransaction, evmSubmissionFailure, copyEvmSubmissionFailure, } from './evm-submission';
|
|
14
14
|
import { signPreparedEvmEnvelope, snapshotPreparedEvmRequest, } from './prepared-evm';
|
|
@@ -196,7 +196,7 @@ export class EthereumTransactions {
|
|
|
196
196
|
assertCurrent,
|
|
197
197
|
};
|
|
198
198
|
};
|
|
199
|
-
this.captureSubmission = (operation, account, type, id, network, assertCurrent,
|
|
199
|
+
this.captureSubmission = (operation, account, type, id, network, assertCurrent, details = {}) => {
|
|
200
200
|
if (!this.captureSubmissionLifecycle)
|
|
201
201
|
throw new TypeError('A durable EVM submission lifecycle is required');
|
|
202
202
|
return captureEvmSubmission({
|
|
@@ -208,7 +208,7 @@ export class EthereumTransactions {
|
|
|
208
208
|
slip44: network.slip44,
|
|
209
209
|
url: network.url,
|
|
210
210
|
},
|
|
211
|
-
...
|
|
211
|
+
...details,
|
|
212
212
|
}, assertCurrent, this.captureSubmissionLifecycle);
|
|
213
213
|
};
|
|
214
214
|
this.authorizeHardwareTransaction = async (provider, assertCurrent, signal = this.abortController.signal) => {
|
|
@@ -578,7 +578,9 @@ export class EthereumTransactions {
|
|
|
578
578
|
typeof approval.assertCurrent !== 'function' ||
|
|
579
579
|
!(approval.signal instanceof AbortSignal))
|
|
580
580
|
throw new Error(`${standard} transaction approval required`);
|
|
581
|
-
const { assertCurrent, signal } = approval;
|
|
581
|
+
const { assertCurrent, signal, nonce } = approval;
|
|
582
|
+
if (!Number.isSafeInteger(nonce) || nonce < 0)
|
|
583
|
+
throw new Error(`Invalid ${standard} transaction approval nonce`);
|
|
582
584
|
const assertApproval = () => {
|
|
583
585
|
if (signal.aborted)
|
|
584
586
|
throw Object.assign(new Error(`${standard} transaction approval expired`), {
|
|
@@ -587,7 +589,7 @@ export class EthereumTransactions {
|
|
|
587
589
|
assertSynchronousOriginalValidation(assertCurrent(), `${standard} transaction approval must be synchronous`);
|
|
588
590
|
};
|
|
589
591
|
assertApproval();
|
|
590
|
-
return { assertApproval, signal };
|
|
592
|
+
return { assertApproval, signal, nonce };
|
|
591
593
|
};
|
|
592
594
|
this.reviewedTokenFees = (request, chainId, standard) => {
|
|
593
595
|
const { isLegacy } = request;
|
|
@@ -644,7 +646,7 @@ export class EthereumTransactions {
|
|
|
644
646
|
maxPriorityFeePerGas: BigNumber.from(fee.maxPriorityFeePerGas),
|
|
645
647
|
};
|
|
646
648
|
};
|
|
647
|
-
this.calculateNewGasValues = (oldTxsParams, isForCancel, isLegacy) => {
|
|
649
|
+
this.calculateNewGasValues = (oldTxsParams, isForCancel, isLegacy, bounds) => {
|
|
648
650
|
const newGasValues = {
|
|
649
651
|
maxFeePerGas: undefined,
|
|
650
652
|
maxPriorityFeePerGas: undefined,
|
|
@@ -652,8 +654,34 @@ export class EthereumTransactions {
|
|
|
652
654
|
gasLimit: undefined,
|
|
653
655
|
};
|
|
654
656
|
const { maxFeePerGas, maxPriorityFeePerGas, gasLimit, gasPrice } = oldTxsParams;
|
|
657
|
+
if (!isForCancel) {
|
|
658
|
+
const originalGasLimit = parseEvmFeeQuantity(gasLimit, 'replacement gas limit');
|
|
659
|
+
const plan = isLegacy
|
|
660
|
+
? planEvmSpeedUpFees({
|
|
661
|
+
...bounds,
|
|
662
|
+
type: 0,
|
|
663
|
+
gasLimit: originalGasLimit,
|
|
664
|
+
gasPrice: parseEvmFeeQuantity(gasPrice, 'replacement gas price'),
|
|
665
|
+
})
|
|
666
|
+
: planEvmSpeedUpFees({
|
|
667
|
+
...bounds,
|
|
668
|
+
type: 2,
|
|
669
|
+
gasLimit: originalGasLimit,
|
|
670
|
+
maxFeePerGas: parseEvmFeeQuantity(maxFeePerGas, 'replacement fee cap'),
|
|
671
|
+
maxPriorityFeePerGas: parseEvmFeeQuantity(maxPriorityFeePerGas, 'replacement priority fee'),
|
|
672
|
+
});
|
|
673
|
+
newGasValues.gasLimit = BigNumber.from(plan.gasLimit);
|
|
674
|
+
if (plan.type === 0)
|
|
675
|
+
newGasValues.gasPrice = BigNumber.from(plan.gasPrice);
|
|
676
|
+
else {
|
|
677
|
+
newGasValues.maxFeePerGas = BigNumber.from(plan.maxFeePerGas);
|
|
678
|
+
newGasValues.maxPriorityFeePerGas = BigNumber.from(plan.maxPriorityFeePerGas);
|
|
679
|
+
}
|
|
680
|
+
return newGasValues;
|
|
681
|
+
}
|
|
682
|
+
// Keep the legacy direct cancellation helper. The guarded cancellation
|
|
683
|
+
// transaction path already uses planEvmCancellationFees.
|
|
655
684
|
const calculateAndConvertNewValue = (feeValue) => {
|
|
656
|
-
// Apply multiplier for replacement transaction (cancel or speedup)
|
|
657
685
|
const newValue = feeValue * multiplierToUse;
|
|
658
686
|
const calculateValue = String(newValue);
|
|
659
687
|
const convertValueToHex = '0x' + parseInt(calculateValue, 10).toString(16);
|
|
@@ -661,7 +689,6 @@ export class EthereumTransactions {
|
|
|
661
689
|
};
|
|
662
690
|
const maxFeePerGasToNumber = maxFeePerGas?.toNumber();
|
|
663
691
|
const maxPriorityFeePerGasToNumber = maxPriorityFeePerGas?.toNumber();
|
|
664
|
-
const gasLimitToNumber = gasLimit?.toNumber();
|
|
665
692
|
const gasPriceToNumber = gasPrice?.toNumber();
|
|
666
693
|
const multiplierToUse = 1.2; //The same calculation we used in the edit fee modal, always using the 0.2 multiplier
|
|
667
694
|
if (!isLegacy) {
|
|
@@ -672,14 +699,8 @@ export class EthereumTransactions {
|
|
|
672
699
|
if (isLegacy) {
|
|
673
700
|
newGasValues.gasPrice = calculateAndConvertNewValue(gasPriceToNumber);
|
|
674
701
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const convertToHex = '0x' + parseInt(DEFAULT_GAS_LIMIT_VALUE, 10).toString(16);
|
|
678
|
-
newGasValues.gasLimit = BigNumber.from(convertToHex);
|
|
679
|
-
}
|
|
680
|
-
if (!isForCancel) {
|
|
681
|
-
newGasValues.gasLimit = calculateAndConvertNewValue(gasLimitToNumber);
|
|
682
|
-
}
|
|
702
|
+
newGasValues.gasLimit = BigNumber.from(42000);
|
|
703
|
+
assertEvmGasLimit(newGasValues.gasLimit.toBigInt(), bounds.chainId, bounds.sampledBlockGasLimit);
|
|
683
704
|
return newGasValues;
|
|
684
705
|
};
|
|
685
706
|
this.cancelSentTransaction = async (txHash, isLegacy, fallbackNonce, options) => {
|
|
@@ -688,8 +709,13 @@ export class EthereumTransactions {
|
|
|
688
709
|
if (typeof options?.assertCurrentContext !== 'function') {
|
|
689
710
|
throw new Error('Cancellation context required');
|
|
690
711
|
}
|
|
691
|
-
|
|
692
|
-
|
|
712
|
+
const { assertCurrentContext, validateOriginal, origin } = options;
|
|
713
|
+
if (origin === undefined) {
|
|
714
|
+
if (typeof validateOriginal !== 'function')
|
|
715
|
+
throw new Error('Cancellation original validator required');
|
|
716
|
+
}
|
|
717
|
+
else if (validateOriginal !== undefined || fallbackNonce !== undefined) {
|
|
718
|
+
throw new Error('Retained cancellation cannot use RPC fallback');
|
|
693
719
|
}
|
|
694
720
|
if (fallbackNonce !== undefined &&
|
|
695
721
|
(!Number.isSafeInteger(fallbackNonce) || fallbackNonce < 0)) {
|
|
@@ -702,7 +728,6 @@ export class EthereumTransactions {
|
|
|
702
728
|
!/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
|
|
703
729
|
throw new Error('Cancellation fee approval required');
|
|
704
730
|
}
|
|
705
|
-
const { assertCurrentContext, validateOriginal } = options;
|
|
706
731
|
const assertHostContext = () => assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
|
|
707
732
|
assertHostContext();
|
|
708
733
|
const state = this.getState();
|
|
@@ -747,9 +772,22 @@ export class EthereumTransactions {
|
|
|
747
772
|
assertKeyringContext();
|
|
748
773
|
};
|
|
749
774
|
assertSigningCurrent();
|
|
775
|
+
const retained = origin === undefined
|
|
776
|
+
? undefined
|
|
777
|
+
: authenticateRetainedCancellation(origin, txHash.toLowerCase(), activeAccount.address, activeNetwork.chainId);
|
|
778
|
+
if (retained) {
|
|
779
|
+
const legacy = retained.feeFloor.type === 0;
|
|
780
|
+
if (activeNetwork.kind !== INetworkType.Ethereum ||
|
|
781
|
+
(isLegacy !== undefined && isLegacy !== legacy))
|
|
782
|
+
throw new Error('Retained cancellation network or type changed');
|
|
783
|
+
isLegacy = legacy;
|
|
784
|
+
}
|
|
750
785
|
const submission = previewOnly
|
|
751
786
|
? undefined
|
|
752
|
-
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent,
|
|
787
|
+
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, {
|
|
788
|
+
originalHash: retained ? txHash.toLowerCase() : txHash,
|
|
789
|
+
origin: retained,
|
|
790
|
+
});
|
|
753
791
|
const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
|
|
754
792
|
const currentEip1559Fees = async () => {
|
|
755
793
|
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
|
|
@@ -761,64 +799,53 @@ export class EthereumTransactions {
|
|
|
761
799
|
}
|
|
762
800
|
return { maxFeePerGas, maxPriorityFeePerGas };
|
|
763
801
|
};
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
if (
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
: undefined;
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
value: Zero,
|
|
796
|
-
nonce: fallbackNonce,
|
|
797
|
-
gasPrice: currentGasPrice,
|
|
798
|
-
data: '0x',
|
|
799
|
-
};
|
|
802
|
+
let cancellationNonce;
|
|
803
|
+
let originalFees;
|
|
804
|
+
if (retained) {
|
|
805
|
+
cancellationNonce = retained.nonce;
|
|
806
|
+
originalFees = retained.feeFloor;
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
const response = await provider.getTransaction(txHash);
|
|
810
|
+
assertCurrent();
|
|
811
|
+
if (response?.blockNumber != null || response?.blockHash != null) {
|
|
812
|
+
throw new Error('Original transaction is already mined');
|
|
813
|
+
}
|
|
814
|
+
const tx = response === null
|
|
815
|
+
? null
|
|
816
|
+
: authenticateReplacementOriginal(response, txHash.toLowerCase(), activeAccount.address.toLowerCase(), activeNetwork.chainId);
|
|
817
|
+
const software = activeAccountType !== KeyringAccountType.Ledger &&
|
|
818
|
+
activeAccountType !== KeyringAccountType.Trezor;
|
|
819
|
+
// The host validator may retain the wrapped response and its fee objects.
|
|
820
|
+
const softwareNonce = software ? tx?.nonce ?? fallbackNonce : undefined;
|
|
821
|
+
const softwareOriginalFees = software && tx
|
|
822
|
+
? {
|
|
823
|
+
gasPrice: tx.gasPrice.toBigInt(),
|
|
824
|
+
maxFeePerGas: tx.maxFeePerGas.toBigInt(),
|
|
825
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toBigInt(),
|
|
826
|
+
}
|
|
827
|
+
: undefined;
|
|
828
|
+
assertSynchronousOriginalValidation(validateOriginal(tx));
|
|
829
|
+
assertCurrent();
|
|
830
|
+
if (tx) {
|
|
831
|
+
cancellationNonce = software ? softwareNonce : tx.nonce;
|
|
832
|
+
originalFees = softwareOriginalFees ?? tx;
|
|
800
833
|
}
|
|
801
834
|
else {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
835
|
+
if (fallbackNonce === undefined)
|
|
836
|
+
return { isCanceled: false, error: true };
|
|
837
|
+
cancellationNonce = fallbackNonce;
|
|
838
|
+
// Preserve the ordinary host's explicitly admitted missing-RPC path.
|
|
839
|
+
if (isLegacy) {
|
|
840
|
+
const gasPrice = nonNegativeFeeQuantity(await provider.getGasPrice(), 'cancellation gas price');
|
|
841
|
+
assertCurrent();
|
|
842
|
+
originalFees = { gasPrice };
|
|
843
|
+
}
|
|
844
|
+
else
|
|
845
|
+
originalFees = await currentEip1559Fees();
|
|
812
846
|
}
|
|
813
847
|
}
|
|
814
|
-
|
|
815
|
-
// No fallback nonce provided and tx not found
|
|
816
|
-
return {
|
|
817
|
-
isCanceled: false,
|
|
818
|
-
error: true,
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = softwareOriginalFees ?? tx;
|
|
848
|
+
const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = originalFees;
|
|
822
849
|
let feePlan;
|
|
823
850
|
if (isLegacy) {
|
|
824
851
|
const currentGasPrice = parseEvmFeeQuantity(await provider.getGasPrice(), 'cancellation gas price');
|
|
@@ -855,6 +882,16 @@ export class EthereumTransactions {
|
|
|
855
882
|
};
|
|
856
883
|
if (maximumFee.gt(approvedMaximumFee))
|
|
857
884
|
throw new Error('Cancellation fee exceeds approval');
|
|
885
|
+
const assertCancellationEnvelope = (unsigned) => {
|
|
886
|
+
if (cancellationNonce === undefined)
|
|
887
|
+
throw new Error('Cancellation nonce required');
|
|
888
|
+
assertEvmCancellationEnvelope(unsigned, {
|
|
889
|
+
payer: activeAccount.address,
|
|
890
|
+
chainId: activeNetwork.chainId,
|
|
891
|
+
nonce: cancellationNonce,
|
|
892
|
+
feePlan,
|
|
893
|
+
});
|
|
894
|
+
};
|
|
858
895
|
const assertApprovedEnvelope = (transaction) => {
|
|
859
896
|
const gas = nonNegativeFeeQuantity(transaction.gasLimit, 'cancellation gas limit');
|
|
860
897
|
const cap = nonNegativeFeeQuantity(isLegacy ? transaction.gasPrice : transaction.maxFeePerGas, 'cancellation maximum fee');
|
|
@@ -863,7 +900,7 @@ export class EthereumTransactions {
|
|
|
863
900
|
};
|
|
864
901
|
// Base cancel transaction parameters (same for all wallet types)
|
|
865
902
|
const baseCancelTx = {
|
|
866
|
-
nonce:
|
|
903
|
+
nonce: cancellationNonce,
|
|
867
904
|
from: activeAccount.address,
|
|
868
905
|
to: activeAccount.address,
|
|
869
906
|
value: Zero,
|
|
@@ -889,7 +926,7 @@ export class EthereumTransactions {
|
|
|
889
926
|
assertCurrent();
|
|
890
927
|
const formatParams = {
|
|
891
928
|
...resolvedParams,
|
|
892
|
-
nonce: resolvedParams.nonce
|
|
929
|
+
nonce: resolvedParams.nonce != null
|
|
893
930
|
? Number(resolvedParams.nonce.toString())
|
|
894
931
|
: undefined,
|
|
895
932
|
};
|
|
@@ -906,6 +943,8 @@ export class EthereumTransactions {
|
|
|
906
943
|
};
|
|
907
944
|
assertApprovedEnvelope(txFormattedForEthers);
|
|
908
945
|
const rawTx = serializeTransaction(txFormattedForEthers);
|
|
946
|
+
if (retained)
|
|
947
|
+
assertCancellationEnvelope(rawTx);
|
|
909
948
|
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
910
949
|
rawTx: rawTx.replace('0x', ''),
|
|
911
950
|
accountIndex: activeAccountId,
|
|
@@ -921,7 +960,7 @@ export class EthereumTransactions {
|
|
|
921
960
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
922
961
|
assertCurrent();
|
|
923
962
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
924
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
963
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
925
964
|
return {
|
|
926
965
|
isCanceled: true,
|
|
927
966
|
transaction: transactionResponse,
|
|
@@ -978,6 +1017,8 @@ export class EthereumTransactions {
|
|
|
978
1017
|
assertApprovedEnvelope(txFormattedForTrezor);
|
|
979
1018
|
assertTransactionGasLimit(txFormattedForTrezor);
|
|
980
1019
|
const expectedUnsignedTransaction = serializeTransaction(txFormattedForTrezor);
|
|
1020
|
+
if (retained)
|
|
1021
|
+
assertCancellationEnvelope(expectedUnsignedTransaction);
|
|
981
1022
|
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
982
1023
|
coin: trezorCoin,
|
|
983
1024
|
tx: txFormattedForTrezor,
|
|
@@ -989,7 +1030,7 @@ export class EthereumTransactions {
|
|
|
989
1030
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
990
1031
|
assertCurrent();
|
|
991
1032
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
992
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1033
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
|
|
993
1034
|
return {
|
|
994
1035
|
isCanceled: true,
|
|
995
1036
|
transaction: transactionResponse,
|
|
@@ -1020,12 +1061,7 @@ export class EthereumTransactions {
|
|
|
1020
1061
|
}
|
|
1021
1062
|
const transactionResponse = await sendLocalEvmTransaction(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, (transaction) => {
|
|
1022
1063
|
assertApprovedEnvelope(transaction);
|
|
1023
|
-
|
|
1024
|
-
payer: activeAccount.address,
|
|
1025
|
-
chainId: activeNetwork.chainId,
|
|
1026
|
-
nonce: softwareNonce,
|
|
1027
|
-
feePlan,
|
|
1028
|
-
});
|
|
1064
|
+
assertCancellationEnvelope(serializeTransaction(transaction));
|
|
1029
1065
|
}, submission);
|
|
1030
1066
|
if (transactionResponse) {
|
|
1031
1067
|
return {
|
|
@@ -1071,7 +1107,8 @@ export class EthereumTransactions {
|
|
|
1071
1107
|
throw new Error('Explicit transaction approval is required');
|
|
1072
1108
|
// Capture once; later caller mutations cannot replace this invocation's
|
|
1073
1109
|
// admission decision while chain checks or device approval are pending.
|
|
1074
|
-
const { assertCurrent: assertHostCurrent, mode } = options;
|
|
1110
|
+
const { assertCurrent: assertHostCurrent, mode, walletNonce } = options;
|
|
1111
|
+
const nonceApproval = walletNonce === undefined ? undefined : { ...walletNonce };
|
|
1075
1112
|
const assertApproved = () => {
|
|
1076
1113
|
const result = assertHostCurrent();
|
|
1077
1114
|
if (result !== undefined) {
|
|
@@ -1082,7 +1119,7 @@ export class EthereumTransactions {
|
|
|
1082
1119
|
assertApproved();
|
|
1083
1120
|
return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
|
|
1084
1121
|
broadcastAttempted = true;
|
|
1085
|
-
}, assertApproved, mode);
|
|
1122
|
+
}, assertApproved, mode, nonceApproval);
|
|
1086
1123
|
}
|
|
1087
1124
|
catch (error) {
|
|
1088
1125
|
// A broadcast transport error may mean the node accepted the signed
|
|
@@ -1099,7 +1136,7 @@ export class EthereumTransactions {
|
|
|
1099
1136
|
throw submissionError;
|
|
1100
1137
|
}
|
|
1101
1138
|
};
|
|
1102
|
-
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode) => {
|
|
1139
|
+
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode, walletNonce) => {
|
|
1103
1140
|
// Hardware approval may outlive the selected account, network or unlocked
|
|
1104
1141
|
// session. Keep immutable identities and one provider for this invocation.
|
|
1105
1142
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
|
|
@@ -1111,8 +1148,13 @@ export class EthereumTransactions {
|
|
|
1111
1148
|
assertCurrent();
|
|
1112
1149
|
beforeBroadcast();
|
|
1113
1150
|
};
|
|
1114
|
-
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent);
|
|
1151
|
+
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { walletNonce });
|
|
1115
1152
|
const assertCurrent = submission.assertCurrent;
|
|
1153
|
+
const approvedNonce = submission.context.walletNonce?.nonce;
|
|
1154
|
+
const assertApprovedNonce = (nonce) => {
|
|
1155
|
+
if (approvedNonce !== undefined && nonce !== approvedNonce)
|
|
1156
|
+
throw new Error('EVM transaction nonce does not match wallet approval');
|
|
1157
|
+
};
|
|
1116
1158
|
if (mode === 'reviewed-native') {
|
|
1117
1159
|
const { transaction } = snapshotPreparedEvmRequest({
|
|
1118
1160
|
account: {
|
|
@@ -1128,6 +1170,7 @@ export class EthereumTransactions {
|
|
|
1128
1170
|
},
|
|
1129
1171
|
transaction: input,
|
|
1130
1172
|
});
|
|
1173
|
+
assertApprovedNonce(Number(transaction.nonce));
|
|
1131
1174
|
const minimum = this.gasOverrides.minGasLimit;
|
|
1132
1175
|
if (transaction.data !== '0x' ||
|
|
1133
1176
|
BigInt(transaction.value) === 0n ||
|
|
@@ -1146,7 +1189,7 @@ export class EthereumTransactions {
|
|
|
1146
1189
|
throw new Error('Reviewed native signer does not match the approved account');
|
|
1147
1190
|
assertCurrent();
|
|
1148
1191
|
const { signed, expectedUnsigned } = signPreparedEvmEnvelope(transaction, decryptedPrivateKey);
|
|
1149
|
-
return submitSignedEvmTransaction(signed.rawTransaction, (
|
|
1192
|
+
return submitSignedEvmTransaction(signed.rawTransaction, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsigned);
|
|
1150
1193
|
}
|
|
1151
1194
|
// Detach supplied values before any RPC/device await. Autofill updates only
|
|
1152
1195
|
// this invocation; caller mutations cannot replace already supplied fields.
|
|
@@ -1181,6 +1224,7 @@ export class EthereumTransactions {
|
|
|
1181
1224
|
supplied.data = hexlify(supplied.data);
|
|
1182
1225
|
if (supplied.accessList != null)
|
|
1183
1226
|
supplied.accessList = accessListify(supplied.accessList);
|
|
1227
|
+
assertApprovedNonce(supplied.nonce);
|
|
1184
1228
|
params = supplied;
|
|
1185
1229
|
assertCurrent();
|
|
1186
1230
|
// Capture the reviewed nonce before any fee/RPC await. Hardware and the
|
|
@@ -1302,7 +1346,7 @@ export class EthereumTransactions {
|
|
|
1302
1346
|
try {
|
|
1303
1347
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1304
1348
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1305
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1349
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1306
1350
|
return finalTx;
|
|
1307
1351
|
}
|
|
1308
1352
|
catch (error) {
|
|
@@ -1392,7 +1436,7 @@ export class EthereumTransactions {
|
|
|
1392
1436
|
};
|
|
1393
1437
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1394
1438
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1395
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1439
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1396
1440
|
return finalTx;
|
|
1397
1441
|
}
|
|
1398
1442
|
catch (error) {
|
|
@@ -1447,7 +1491,7 @@ export class EthereumTransactions {
|
|
|
1447
1491
|
throw new Error('Replacement context required');
|
|
1448
1492
|
if (typeof options.validateOriginal !== 'function')
|
|
1449
1493
|
throw new Error('Replacement original validator required');
|
|
1450
|
-
const { assertCurrentContext, validateOriginal, beforeBroadcast } = options;
|
|
1494
|
+
const { assertCurrentContext, validateOriginal, beforeBroadcast, retainedCancellation, } = options;
|
|
1451
1495
|
if (!previewOnly)
|
|
1452
1496
|
this.assertHardwareSignerAvailable(options.targetAccount);
|
|
1453
1497
|
isLegacy ?? (isLegacy = false);
|
|
@@ -1488,9 +1532,27 @@ export class EthereumTransactions {
|
|
|
1488
1532
|
const requestedHash = txHash.toLowerCase();
|
|
1489
1533
|
const provider = this.web3Provider;
|
|
1490
1534
|
assertSigningCurrent();
|
|
1535
|
+
let cancellation;
|
|
1536
|
+
if (retainedCancellation !== undefined) {
|
|
1537
|
+
if (typeof retainedCancellation !== 'string' ||
|
|
1538
|
+
retainedCancellation.length > 1024)
|
|
1539
|
+
throw new Error('Invalid retained cancellation');
|
|
1540
|
+
cancellation = Transaction.from(retainedCancellation);
|
|
1541
|
+
if (!cancellation.isSigned() ||
|
|
1542
|
+
cancellation.serialized !== retainedCancellation ||
|
|
1543
|
+
cancellation.hash !== requestedHash ||
|
|
1544
|
+
cancellation.chainId !== BigInt(activeNetwork.chainId) ||
|
|
1545
|
+
cancellation.from?.toLowerCase() !== expectedSender ||
|
|
1546
|
+
cancellation.to?.toLowerCase() !== expectedSender ||
|
|
1547
|
+
cancellation.value !== 0n ||
|
|
1548
|
+
cancellation.data !== '0x' ||
|
|
1549
|
+
cancellation.accessList?.length ||
|
|
1550
|
+
(cancellation.type !== 0 && cancellation.type !== 2))
|
|
1551
|
+
throw new Error('Invalid retained cancellation');
|
|
1552
|
+
}
|
|
1491
1553
|
const submission = previewOnly
|
|
1492
1554
|
? undefined
|
|
1493
|
-
: this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
|
|
1555
|
+
: this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, { originalHash: txHash });
|
|
1494
1556
|
const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
|
|
1495
1557
|
const validateLookup = (transaction) => {
|
|
1496
1558
|
assertCurrent();
|
|
@@ -1498,6 +1560,10 @@ export class EthereumTransactions {
|
|
|
1498
1560
|
return null;
|
|
1499
1561
|
const authenticated = authenticateReplacementOriginal(transaction, requestedHash, expectedSender, activeNetwork.chainId);
|
|
1500
1562
|
const type = authenticated.type;
|
|
1563
|
+
if (cancellation &&
|
|
1564
|
+
(type !== cancellation.type ||
|
|
1565
|
+
parseEvmFeeQuantity(authenticated.gasLimit, 'retained cancellation gas limit') !== cancellation.gasLimit))
|
|
1566
|
+
throw new Error('Retained cancellation does not match original');
|
|
1501
1567
|
if (type !== 1)
|
|
1502
1568
|
isLegacy = type === 0;
|
|
1503
1569
|
if (isLegacy &&
|
|
@@ -1526,9 +1592,20 @@ export class EthereumTransactions {
|
|
|
1526
1592
|
};
|
|
1527
1593
|
}
|
|
1528
1594
|
}
|
|
1529
|
-
// Check if this might be a max send transaction by comparing total cost to balance
|
|
1530
1595
|
const currentBalance = await provider.getBalance(activeAccount.address);
|
|
1531
1596
|
assertCurrent();
|
|
1597
|
+
const sampledBlock = await provider.getBlock('latest');
|
|
1598
|
+
assertCurrent();
|
|
1599
|
+
const bounds = Object.freeze({
|
|
1600
|
+
chainId: BigInt(activeNetwork.chainId),
|
|
1601
|
+
...(cancellation
|
|
1602
|
+
? { retainedCancellationGasLimit: cancellation.gasLimit }
|
|
1603
|
+
: {}),
|
|
1604
|
+
sampledBlockGasLimit: parseEvmFeeQuantity(sampledBlock?.gasLimit, 'replacement block gas limit'),
|
|
1605
|
+
sampledBaseFeePerGas: sampledBlock?.baseFeePerGas == null
|
|
1606
|
+
? null
|
|
1607
|
+
: parseEvmFeeQuantity(sampledBlock.baseFeePerGas, 'replacement base fee'),
|
|
1608
|
+
});
|
|
1532
1609
|
// Ensure all transaction values are resolved from promises
|
|
1533
1610
|
const gasLimit = await Promise.resolve(tx.gasLimit);
|
|
1534
1611
|
const gasPrice = await Promise.resolve(tx.gasPrice || 0);
|
|
@@ -1537,15 +1614,6 @@ export class EthereumTransactions {
|
|
|
1537
1614
|
const txValue = await Promise.resolve(tx.value);
|
|
1538
1615
|
const txData = await Promise.resolve(tx.data || '0x');
|
|
1539
1616
|
assertCurrent();
|
|
1540
|
-
// Check if this is a contract call (has data)
|
|
1541
|
-
const isContractCall = txData && txData !== '0x' && txData.length > 2;
|
|
1542
|
-
const originalGasCost = isLegacy
|
|
1543
|
-
? gasLimit.mul(gasPrice || 0)
|
|
1544
|
-
: gasLimit.mul(maxFeePerGas || 0);
|
|
1545
|
-
const originalTotalCost = txValue.add(originalGasCost);
|
|
1546
|
-
// If original transaction used >95% of balance, it's likely a max send
|
|
1547
|
-
const balanceThreshold = currentBalance.mul(95).div(100);
|
|
1548
|
-
const isLikelyMaxSend = originalTotalCost.gt(balanceThreshold);
|
|
1549
1617
|
const oldTxsGasValues = {
|
|
1550
1618
|
maxFeePerGas: maxFeePerGas,
|
|
1551
1619
|
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
|
@@ -1559,26 +1627,33 @@ export class EthereumTransactions {
|
|
|
1559
1627
|
assertCurrent();
|
|
1560
1628
|
}
|
|
1561
1629
|
else if (!isLegacy && oldTxsGasValues.maxFeePerGas.isZero()) {
|
|
1562
|
-
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(
|
|
1630
|
+
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas({
|
|
1631
|
+
getGasPrice: () => provider.getGasPrice(),
|
|
1632
|
+
getBlock: async () => sampledBlock,
|
|
1633
|
+
send: (method, params) => provider.send(method, params),
|
|
1634
|
+
});
|
|
1563
1635
|
assertCurrent();
|
|
1564
1636
|
oldTxsGasValues.maxFeePerGas = BigNumber.from(feeData.maxFeePerGas || 0);
|
|
1565
1637
|
oldTxsGasValues.maxPriorityFeePerGas = BigNumber.from(feeData.maxPriorityFeePerGas || 0);
|
|
1566
1638
|
}
|
|
1567
|
-
const newGasValues = this.calculateNewGasValues(oldTxsGasValues, false, isLegacy);
|
|
1639
|
+
const newGasValues = this.calculateNewGasValues(oldTxsGasValues, false, isLegacy, bounds);
|
|
1568
1640
|
if (!newGasValues.gasLimit.gt(0))
|
|
1569
1641
|
throw new Error('Invalid replacement gas limit');
|
|
1642
|
+
assertTransactionGasLimit({
|
|
1643
|
+
gasLimit: newGasValues.gasLimit,
|
|
1644
|
+
chainId: activeNetwork.chainId,
|
|
1645
|
+
});
|
|
1570
1646
|
if (!isLegacy &&
|
|
1571
1647
|
newGasValues.maxPriorityFeePerGas.gt(newGasValues.maxFeePerGas))
|
|
1572
1648
|
throw new Error('Invalid replacement fee');
|
|
1573
1649
|
const maximumFee = newGasValues.gasLimit.mul(isLegacy ? newGasValues.gasPrice : newGasValues.maxFeePerGas);
|
|
1574
|
-
if (
|
|
1575
|
-
if (
|
|
1576
|
-
console.error('[SpeedUp] Cannot adjust value for contract call - rejecting speedup');
|
|
1650
|
+
if (txValue.add(maximumFee).gt(currentBalance)) {
|
|
1651
|
+
if (txData.length > 2) {
|
|
1577
1652
|
return {
|
|
1578
1653
|
isSpeedUp: false,
|
|
1579
1654
|
error: true,
|
|
1580
1655
|
code: 'CONTRACT_CALL_MAX_SEND',
|
|
1581
|
-
message: '
|
|
1656
|
+
message: 'Add funds before speeding up this contract call; the original amount will not be changed',
|
|
1582
1657
|
};
|
|
1583
1658
|
}
|
|
1584
1659
|
return {
|
|
@@ -1645,7 +1720,7 @@ export class EthereumTransactions {
|
|
|
1645
1720
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1646
1721
|
assertSignedSender(signedTx);
|
|
1647
1722
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1648
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1723
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, rawTx);
|
|
1649
1724
|
return {
|
|
1650
1725
|
isSpeedUp: true,
|
|
1651
1726
|
transaction: transactionResponse,
|
|
@@ -1726,7 +1801,7 @@ export class EthereumTransactions {
|
|
|
1726
1801
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
1727
1802
|
assertSignedSender(signedTx);
|
|
1728
1803
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1729
|
-
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
1804
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1730
1805
|
return {
|
|
1731
1806
|
isSpeedUp: true,
|
|
1732
1807
|
transaction: transactionResponse,
|
|
@@ -1812,7 +1887,7 @@ export class EthereumTransactions {
|
|
|
1812
1887
|
}
|
|
1813
1888
|
};
|
|
1814
1889
|
this.sendSignedErc20Transaction = async (request, approval) => {
|
|
1815
|
-
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC20');
|
|
1890
|
+
const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC20');
|
|
1816
1891
|
const { receiver, tokenAddress, tokenAmountUnits, saveTrezorTx, networkUrl, } = request;
|
|
1817
1892
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
1818
1893
|
const activeAccountAddress = activeAccount.address;
|
|
@@ -1829,7 +1904,7 @@ export class EthereumTransactions {
|
|
|
1829
1904
|
const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
1830
1905
|
assertApproval();
|
|
1831
1906
|
assertSigningCurrent();
|
|
1832
|
-
});
|
|
1907
|
+
}, { walletNonce: { nonce, source: 'automatic' } });
|
|
1833
1908
|
const assertCurrent = submission.assertCurrent;
|
|
1834
1909
|
const sendERC20Token = async () => {
|
|
1835
1910
|
assertCurrent();
|
|
@@ -1839,7 +1914,6 @@ export class EthereumTransactions {
|
|
|
1839
1914
|
let transferMethod;
|
|
1840
1915
|
if (isLegacy) {
|
|
1841
1916
|
const overrides = {
|
|
1842
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1843
1917
|
gasPrice,
|
|
1844
1918
|
gasLimit,
|
|
1845
1919
|
type: 0, // Explicitly set Type 0 for legacy token transfers
|
|
@@ -1849,7 +1923,6 @@ export class EthereumTransactions {
|
|
|
1849
1923
|
}
|
|
1850
1924
|
else {
|
|
1851
1925
|
const overrides = {
|
|
1852
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1853
1926
|
maxPriorityFeePerGas,
|
|
1854
1927
|
maxFeePerGas,
|
|
1855
1928
|
gasLimit,
|
|
@@ -1859,6 +1932,7 @@ export class EthereumTransactions {
|
|
|
1859
1932
|
}
|
|
1860
1933
|
return await sendLocalEvmTransaction(provider, decryptedPrivateKey, {
|
|
1861
1934
|
...transferMethod,
|
|
1935
|
+
nonce,
|
|
1862
1936
|
chainId: activeNetwork.chainId,
|
|
1863
1937
|
from: activeAccountAddress,
|
|
1864
1938
|
value: '0x0',
|
|
@@ -1869,8 +1943,6 @@ export class EthereumTransactions {
|
|
|
1869
1943
|
}
|
|
1870
1944
|
};
|
|
1871
1945
|
const sendERC20TokenOnLedger = async () => {
|
|
1872
|
-
assertCurrent();
|
|
1873
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
1874
1946
|
assertCurrent();
|
|
1875
1947
|
try {
|
|
1876
1948
|
const _contract = new Contract(tokenAddress, getErc20Abi(), provider);
|
|
@@ -1886,7 +1958,7 @@ export class EthereumTransactions {
|
|
|
1886
1958
|
gasLimit: gasLimit,
|
|
1887
1959
|
gasPrice,
|
|
1888
1960
|
data: txData,
|
|
1889
|
-
nonce
|
|
1961
|
+
nonce,
|
|
1890
1962
|
chainId: activeNetwork.chainId,
|
|
1891
1963
|
type: 0,
|
|
1892
1964
|
};
|
|
@@ -1899,7 +1971,7 @@ export class EthereumTransactions {
|
|
|
1899
1971
|
maxFeePerGas,
|
|
1900
1972
|
maxPriorityFeePerGas,
|
|
1901
1973
|
data: txData,
|
|
1902
|
-
nonce
|
|
1974
|
+
nonce,
|
|
1903
1975
|
chainId: activeNetwork.chainId,
|
|
1904
1976
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1905
1977
|
};
|
|
@@ -1921,8 +1993,8 @@ export class EthereumTransactions {
|
|
|
1921
1993
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1922
1994
|
assertCurrent();
|
|
1923
1995
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1924
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (
|
|
1925
|
-
const response = await provider.sendTransaction(
|
|
1996
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
|
|
1997
|
+
const response = await admit(() => provider.sendTransaction(rawTransaction));
|
|
1926
1998
|
saveTrezorTx && saveTrezorTx(response);
|
|
1927
1999
|
return response;
|
|
1928
2000
|
}, submission, undefined, rawTx);
|
|
@@ -1941,8 +2013,6 @@ export class EthereumTransactions {
|
|
|
1941
2013
|
}
|
|
1942
2014
|
};
|
|
1943
2015
|
const sendERC20TokenOnTrezor = async () => {
|
|
1944
|
-
assertCurrent();
|
|
1945
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
1946
2016
|
assertCurrent();
|
|
1947
2017
|
try {
|
|
1948
2018
|
const _contract = new Contract(tokenAddress, getErc20Abi(), provider);
|
|
@@ -1957,7 +2027,7 @@ export class EthereumTransactions {
|
|
|
1957
2027
|
value: '0x0',
|
|
1958
2028
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1959
2029
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
1960
|
-
nonce: this.toBigNumber(
|
|
2030
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
1961
2031
|
chainId: activeNetwork.chainId,
|
|
1962
2032
|
data: txData,
|
|
1963
2033
|
};
|
|
@@ -1969,7 +2039,7 @@ export class EthereumTransactions {
|
|
|
1969
2039
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1970
2040
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
1971
2041
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
1972
|
-
nonce: this.toBigNumber(
|
|
2042
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
1973
2043
|
chainId: activeNetwork.chainId,
|
|
1974
2044
|
data: txData,
|
|
1975
2045
|
};
|
|
@@ -1996,7 +2066,7 @@ export class EthereumTransactions {
|
|
|
1996
2066
|
gasLimit: gasLimit,
|
|
1997
2067
|
gasPrice,
|
|
1998
2068
|
data: txData,
|
|
1999
|
-
nonce
|
|
2069
|
+
nonce,
|
|
2000
2070
|
chainId: activeNetwork.chainId,
|
|
2001
2071
|
type: 0,
|
|
2002
2072
|
};
|
|
@@ -2009,7 +2079,7 @@ export class EthereumTransactions {
|
|
|
2009
2079
|
maxFeePerGas,
|
|
2010
2080
|
maxPriorityFeePerGas,
|
|
2011
2081
|
data: txData,
|
|
2012
|
-
nonce
|
|
2082
|
+
nonce,
|
|
2013
2083
|
chainId: activeNetwork.chainId,
|
|
2014
2084
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2015
2085
|
};
|
|
@@ -2017,8 +2087,8 @@ export class EthereumTransactions {
|
|
|
2017
2087
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2018
2088
|
assertCurrent();
|
|
2019
2089
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2020
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (
|
|
2021
|
-
const response = await provider.sendTransaction(
|
|
2090
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async ({ rawTransaction, admit }) => {
|
|
2091
|
+
const response = await admit(() => provider.sendTransaction(rawTransaction));
|
|
2022
2092
|
saveTrezorTx && saveTrezorTx(response);
|
|
2023
2093
|
return response;
|
|
2024
2094
|
}, submission, undefined, expectedUnsignedTransaction);
|
|
@@ -2046,7 +2116,7 @@ export class EthereumTransactions {
|
|
|
2046
2116
|
}
|
|
2047
2117
|
};
|
|
2048
2118
|
this.sendSignedErc721Transaction = async (request, approval) => {
|
|
2049
|
-
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC721');
|
|
2119
|
+
const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC721');
|
|
2050
2120
|
const { receiver, tokenAddress, tokenId, networkUrl } = request;
|
|
2051
2121
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
2052
2122
|
if (networkUrl !== activeNetwork.url)
|
|
@@ -2063,7 +2133,7 @@ export class EthereumTransactions {
|
|
|
2063
2133
|
const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
2064
2134
|
assertApproval();
|
|
2065
2135
|
assertSigningCurrent();
|
|
2066
|
-
});
|
|
2136
|
+
}, { walletNonce: { nonce, source: 'automatic' } });
|
|
2067
2137
|
const assertCurrent = submission.assertCurrent;
|
|
2068
2138
|
const sendERC721Token = async () => {
|
|
2069
2139
|
assertCurrent();
|
|
@@ -2073,7 +2143,6 @@ export class EthereumTransactions {
|
|
|
2073
2143
|
const _contract = new Contract(tokenAddress, getErc21Abi(), provider);
|
|
2074
2144
|
if (isLegacy) {
|
|
2075
2145
|
const overrides = {
|
|
2076
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2077
2146
|
gasPrice,
|
|
2078
2147
|
gasLimit,
|
|
2079
2148
|
type: 0, // Explicitly set Type 0 for legacy NFT transfers
|
|
@@ -2082,7 +2151,6 @@ export class EthereumTransactions {
|
|
|
2082
2151
|
}
|
|
2083
2152
|
else {
|
|
2084
2153
|
const overrides = {
|
|
2085
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2086
2154
|
maxPriorityFeePerGas,
|
|
2087
2155
|
maxFeePerGas,
|
|
2088
2156
|
gasLimit,
|
|
@@ -2091,6 +2159,7 @@ export class EthereumTransactions {
|
|
|
2091
2159
|
}
|
|
2092
2160
|
return await sendLocalEvmTransaction(provider, decryptedPrivateKey, {
|
|
2093
2161
|
...transferMethod,
|
|
2162
|
+
nonce,
|
|
2094
2163
|
chainId: activeNetwork.chainId,
|
|
2095
2164
|
from: activeAccountAddress,
|
|
2096
2165
|
value: '0x0',
|
|
@@ -2101,8 +2170,6 @@ export class EthereumTransactions {
|
|
|
2101
2170
|
}
|
|
2102
2171
|
};
|
|
2103
2172
|
const sendERC721TokenOnLedger = async () => {
|
|
2104
|
-
assertCurrent();
|
|
2105
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2106
2173
|
assertCurrent();
|
|
2107
2174
|
try {
|
|
2108
2175
|
const _contract = new Contract(tokenAddress, getErc21Abi(), provider);
|
|
@@ -2119,7 +2186,7 @@ export class EthereumTransactions {
|
|
|
2119
2186
|
gasLimit,
|
|
2120
2187
|
gasPrice,
|
|
2121
2188
|
data: txData,
|
|
2122
|
-
nonce
|
|
2189
|
+
nonce,
|
|
2123
2190
|
chainId: activeNetwork.chainId,
|
|
2124
2191
|
type: 0,
|
|
2125
2192
|
};
|
|
@@ -2132,7 +2199,7 @@ export class EthereumTransactions {
|
|
|
2132
2199
|
maxFeePerGas,
|
|
2133
2200
|
maxPriorityFeePerGas,
|
|
2134
2201
|
data: txData,
|
|
2135
|
-
nonce
|
|
2202
|
+
nonce,
|
|
2136
2203
|
chainId: activeNetwork.chainId,
|
|
2137
2204
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2138
2205
|
};
|
|
@@ -2154,7 +2221,7 @@ export class EthereumTransactions {
|
|
|
2154
2221
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2155
2222
|
assertCurrent();
|
|
2156
2223
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2157
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2224
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2158
2225
|
return finalTx;
|
|
2159
2226
|
}
|
|
2160
2227
|
catch (error) {
|
|
@@ -2170,8 +2237,6 @@ export class EthereumTransactions {
|
|
|
2170
2237
|
}
|
|
2171
2238
|
};
|
|
2172
2239
|
const sendERC721TokenOnTrezor = async () => {
|
|
2173
|
-
assertCurrent();
|
|
2174
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2175
2240
|
assertCurrent();
|
|
2176
2241
|
try {
|
|
2177
2242
|
const _contract = new Contract(tokenAddress, getErc21Abi(), provider);
|
|
@@ -2187,7 +2252,7 @@ export class EthereumTransactions {
|
|
|
2187
2252
|
value: '0x0',
|
|
2188
2253
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2189
2254
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2190
|
-
nonce: this.toBigNumber(
|
|
2255
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2191
2256
|
chainId: activeNetwork.chainId,
|
|
2192
2257
|
data: txData,
|
|
2193
2258
|
};
|
|
@@ -2200,7 +2265,7 @@ export class EthereumTransactions {
|
|
|
2200
2265
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2201
2266
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2202
2267
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2203
|
-
nonce: this.toBigNumber(
|
|
2268
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2204
2269
|
chainId: activeNetwork.chainId,
|
|
2205
2270
|
data: txData,
|
|
2206
2271
|
};
|
|
@@ -2229,7 +2294,7 @@ export class EthereumTransactions {
|
|
|
2229
2294
|
gasLimit,
|
|
2230
2295
|
gasPrice,
|
|
2231
2296
|
data: txData,
|
|
2232
|
-
nonce
|
|
2297
|
+
nonce,
|
|
2233
2298
|
chainId: activeNetwork.chainId,
|
|
2234
2299
|
type: 0,
|
|
2235
2300
|
};
|
|
@@ -2242,7 +2307,7 @@ export class EthereumTransactions {
|
|
|
2242
2307
|
maxFeePerGas,
|
|
2243
2308
|
maxPriorityFeePerGas,
|
|
2244
2309
|
data: txData,
|
|
2245
|
-
nonce
|
|
2310
|
+
nonce,
|
|
2246
2311
|
chainId: activeNetwork.chainId,
|
|
2247
2312
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2248
2313
|
};
|
|
@@ -2250,7 +2315,7 @@ export class EthereumTransactions {
|
|
|
2250
2315
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2251
2316
|
assertCurrent();
|
|
2252
2317
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2253
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2318
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
|
|
2254
2319
|
return finalTx;
|
|
2255
2320
|
}
|
|
2256
2321
|
catch (error) {
|
|
@@ -2277,7 +2342,7 @@ export class EthereumTransactions {
|
|
|
2277
2342
|
}
|
|
2278
2343
|
};
|
|
2279
2344
|
this.sendSignedErc1155Transaction = async (request, approval) => {
|
|
2280
|
-
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC1155');
|
|
2345
|
+
const { assertApproval, signal, nonce } = this.captureReviewedTokenApproval(approval, 'ERC1155');
|
|
2281
2346
|
const { receiver, tokenAddress, tokenId, networkUrl } = request;
|
|
2282
2347
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
2283
2348
|
if (networkUrl !== activeNetwork.url)
|
|
@@ -2297,7 +2362,7 @@ export class EthereumTransactions {
|
|
|
2297
2362
|
const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
2298
2363
|
assertApproval();
|
|
2299
2364
|
assertSigningCurrent();
|
|
2300
|
-
});
|
|
2365
|
+
}, { walletNonce: { nonce, source: 'automatic' } });
|
|
2301
2366
|
const assertCurrent = submission.assertCurrent;
|
|
2302
2367
|
const sendERC1155Token = async () => {
|
|
2303
2368
|
assertCurrent();
|
|
@@ -2309,7 +2374,6 @@ export class EthereumTransactions {
|
|
|
2309
2374
|
let overrides;
|
|
2310
2375
|
if (isLegacy) {
|
|
2311
2376
|
overrides = {
|
|
2312
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2313
2377
|
gasPrice,
|
|
2314
2378
|
gasLimit,
|
|
2315
2379
|
type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
|
|
@@ -2317,7 +2381,6 @@ export class EthereumTransactions {
|
|
|
2317
2381
|
}
|
|
2318
2382
|
else {
|
|
2319
2383
|
overrides = {
|
|
2320
|
-
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2321
2384
|
maxPriorityFeePerGas,
|
|
2322
2385
|
maxFeePerGas,
|
|
2323
2386
|
gasLimit,
|
|
@@ -2326,6 +2389,7 @@ export class EthereumTransactions {
|
|
|
2326
2389
|
transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, amount, '0x', normalizeEthersOverrides(overrides));
|
|
2327
2390
|
return await sendLocalEvmTransaction(provider, decryptedPrivateKey, {
|
|
2328
2391
|
...transferMethod,
|
|
2392
|
+
nonce,
|
|
2329
2393
|
chainId: activeNetwork.chainId,
|
|
2330
2394
|
from: activeAccountAddress,
|
|
2331
2395
|
value: '0x0',
|
|
@@ -2336,8 +2400,6 @@ export class EthereumTransactions {
|
|
|
2336
2400
|
}
|
|
2337
2401
|
};
|
|
2338
2402
|
const sendERC1155TokenOnLedger = async () => {
|
|
2339
|
-
assertCurrent();
|
|
2340
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2341
2403
|
assertCurrent();
|
|
2342
2404
|
try {
|
|
2343
2405
|
const _contract = new Contract(tokenAddress, getErc55Abi(), provider);
|
|
@@ -2350,7 +2412,7 @@ export class EthereumTransactions {
|
|
|
2350
2412
|
gasLimit,
|
|
2351
2413
|
gasPrice,
|
|
2352
2414
|
data: txData,
|
|
2353
|
-
nonce
|
|
2415
|
+
nonce,
|
|
2354
2416
|
chainId: activeNetwork.chainId,
|
|
2355
2417
|
type: 0,
|
|
2356
2418
|
};
|
|
@@ -2363,7 +2425,7 @@ export class EthereumTransactions {
|
|
|
2363
2425
|
maxFeePerGas,
|
|
2364
2426
|
maxPriorityFeePerGas,
|
|
2365
2427
|
data: txData,
|
|
2366
|
-
nonce
|
|
2428
|
+
nonce,
|
|
2367
2429
|
chainId: activeNetwork.chainId,
|
|
2368
2430
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2369
2431
|
};
|
|
@@ -2385,7 +2447,7 @@ export class EthereumTransactions {
|
|
|
2385
2447
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2386
2448
|
assertCurrent();
|
|
2387
2449
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2388
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2450
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, rawTx);
|
|
2389
2451
|
return finalTx;
|
|
2390
2452
|
}
|
|
2391
2453
|
catch (error) {
|
|
@@ -2401,8 +2463,6 @@ export class EthereumTransactions {
|
|
|
2401
2463
|
}
|
|
2402
2464
|
};
|
|
2403
2465
|
const sendERC1155TokenOnTrezor = async () => {
|
|
2404
|
-
assertCurrent();
|
|
2405
|
-
const transactionNonce = await provider.getTransactionCount(activeAccountAddress, 'pending');
|
|
2406
2466
|
assertCurrent();
|
|
2407
2467
|
try {
|
|
2408
2468
|
const _contract = new Contract(tokenAddress, getErc55Abi(), provider);
|
|
@@ -2414,7 +2474,7 @@ export class EthereumTransactions {
|
|
|
2414
2474
|
value: '0x0',
|
|
2415
2475
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2416
2476
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2417
|
-
nonce: this.toBigNumber(
|
|
2477
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2418
2478
|
chainId: activeNetwork.chainId,
|
|
2419
2479
|
data: txData,
|
|
2420
2480
|
};
|
|
@@ -2426,7 +2486,7 @@ export class EthereumTransactions {
|
|
|
2426
2486
|
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2427
2487
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2428
2488
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2429
|
-
nonce: this.toBigNumber(
|
|
2489
|
+
nonce: this.toBigNumber(nonce)._hex,
|
|
2430
2490
|
chainId: activeNetwork.chainId,
|
|
2431
2491
|
data: txData,
|
|
2432
2492
|
};
|
|
@@ -2453,7 +2513,7 @@ export class EthereumTransactions {
|
|
|
2453
2513
|
gasLimit,
|
|
2454
2514
|
gasPrice,
|
|
2455
2515
|
data: txData,
|
|
2456
|
-
nonce
|
|
2516
|
+
nonce,
|
|
2457
2517
|
chainId: activeNetwork.chainId,
|
|
2458
2518
|
type: 0,
|
|
2459
2519
|
};
|
|
@@ -2466,7 +2526,7 @@ export class EthereumTransactions {
|
|
|
2466
2526
|
maxFeePerGas,
|
|
2467
2527
|
maxPriorityFeePerGas,
|
|
2468
2528
|
data: txData,
|
|
2469
|
-
nonce
|
|
2529
|
+
nonce,
|
|
2470
2530
|
chainId: activeNetwork.chainId,
|
|
2471
2531
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
2472
2532
|
};
|
|
@@ -2474,7 +2534,7 @@ export class EthereumTransactions {
|
|
|
2474
2534
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2475
2535
|
assertCurrent();
|
|
2476
2536
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2477
|
-
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (
|
|
2537
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, ({ rawTransaction, admit }) => admit(() => provider.sendTransaction(rawTransaction)), submission, undefined, expectedUnsignedTransaction);
|
|
2478
2538
|
return finalTx;
|
|
2479
2539
|
}
|
|
2480
2540
|
catch (error) {
|