@sidhujag/sysweb3-keyring 2.1.2 → 2.1.4
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/README.md +55 -47
- package/cjs/current-vault-refusal.js +6 -0
- package/cjs/current-vault-refusal.js.map +1 -0
- package/cjs/evm-fees.js +29 -1
- package/cjs/evm-fees.js.map +1 -1
- package/cjs/hardware-transaction-authority.js +36 -0
- package/cjs/hardware-transaction-authority.js.map +1 -0
- package/cjs/index.js +5 -5
- package/cjs/index.js.map +1 -1
- package/cjs/keyring-core.js +466 -250
- package/cjs/keyring-core.js.map +1 -1
- package/cjs/keyring-host.js +113 -0
- package/cjs/keyring-host.js.map +1 -0
- package/cjs/keyring-manager.js +15 -55
- package/cjs/keyring-manager.js.map +1 -1
- package/cjs/ledger/host-types.js.map +1 -1
- package/cjs/ledger/index.js +44 -19
- package/cjs/ledger/index.js.map +1 -1
- package/cjs/providers.js +3 -1
- package/cjs/providers.js.map +1 -1
- package/cjs/software.js +10 -43
- package/cjs/software.js.map +1 -1
- package/cjs/storage-core.js +44 -4
- package/cjs/storage-core.js.map +1 -1
- package/cjs/transactions/ethereum.js +358 -294
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/evm-cancellation.js +43 -0
- package/cjs/transactions/evm-cancellation.js.map +1 -0
- package/cjs/transactions/evm-local-signer.js +15 -0
- package/cjs/transactions/evm-local-signer.js.map +1 -1
- package/cjs/transactions/evm-submission.js +61 -65
- package/cjs/transactions/evm-submission.js.map +1 -1
- package/cjs/transactions/hardware-psbt.js +11 -8
- package/cjs/transactions/hardware-psbt.js.map +1 -1
- package/cjs/transactions/index.js +10 -2
- package/cjs/transactions/index.js.map +1 -1
- package/cjs/transactions/prepared-evm.js +10 -7
- package/cjs/transactions/prepared-evm.js.map +1 -1
- package/cjs/transactions/syscoin-core.js +282 -202
- package/cjs/transactions/syscoin-core.js.map +1 -1
- package/cjs/trezor/connect.js +19 -2
- package/cjs/trezor/connect.js.map +1 -1
- package/cjs/trezor/index.js +38 -16
- package/cjs/trezor/index.js.map +1 -1
- package/cjs/types.js.map +1 -1
- package/cjs/utils/owned-blockbook-account.js +45 -0
- package/cjs/utils/owned-blockbook-account.js.map +1 -0
- package/esm/current-vault-refusal.js +3 -0
- package/esm/current-vault-refusal.js.map +1 -0
- package/esm/evm-fees.js +242 -0
- package/esm/evm-fees.js.map +1 -0
- package/esm/hardware-transaction-authority.js +33 -0
- package/esm/hardware-transaction-authority.js.map +1 -0
- package/esm/initial-state.js +102 -0
- package/esm/initial-state.js.map +1 -0
- package/esm/keyring-core.js +2781 -0
- package/esm/keyring-core.js.map +1 -0
- package/esm/keyring-host.js +108 -0
- package/esm/keyring-host.js.map +1 -0
- package/esm/ledger/dmk-host.js +31 -0
- package/esm/ledger/dmk-host.js.map +1 -0
- package/esm/ledger/dmk-selected-host.js +6 -0
- package/esm/ledger/dmk-selected-host.js.map +1 -0
- package/esm/ledger/dmk-session.js +154 -34
- package/esm/ledger/dmk-session.js.map +1 -1
- package/esm/ledger/dmk-utxo.js +9 -3
- package/esm/ledger/dmk-utxo.js.map +1 -1
- package/esm/ledger/host-types.js.map +1 -1
- package/esm/ledger/index.js +44 -19
- package/esm/ledger/index.js.map +1 -1
- package/esm/ledger-host.js +3 -27
- package/esm/ledger-host.js.map +1 -1
- package/esm/network-utils.js +70 -0
- package/esm/network-utils.js.map +1 -0
- package/esm/providers.js +3 -1
- package/esm/providers.js.map +1 -1
- package/esm/software-hd-account.js +20 -0
- package/esm/software-hd-account.js.map +1 -0
- package/esm/storage-core.js +258 -0
- package/esm/storage-core.js.map +1 -0
- package/esm/transactions/ethereum.js +2753 -0
- package/esm/transactions/ethereum.js.map +1 -0
- package/esm/transactions/evm-cancellation.js +40 -0
- package/esm/transactions/evm-cancellation.js.map +1 -0
- package/esm/transactions/evm-local-signer.js +202 -0
- package/esm/transactions/evm-local-signer.js.map +1 -0
- package/esm/transactions/evm-submission.js +187 -0
- package/esm/transactions/evm-submission.js.map +1 -0
- package/esm/transactions/prepared-evm.js +194 -0
- package/esm/transactions/prepared-evm.js.map +1 -0
- package/esm/transactions/syscoin-core.js +691 -0
- package/esm/transactions/syscoin-core.js.map +1 -0
- package/esm/trezor/connect.js +19 -2
- package/esm/trezor/connect.js.map +1 -1
- package/esm/trezor/index.js +38 -16
- package/esm/trezor/index.js.map +1 -1
- package/esm/types.js.map +1 -1
- package/esm/utils/excluded-utxos.js +53 -0
- package/esm/utils/excluded-utxos.js.map +1 -0
- package/esm/utils/owned-blockbook-account.js +9 -0
- package/esm/utils/owned-blockbook-account.js.map +1 -0
- package/esm/utils/psbt.js +32 -0
- package/esm/utils/psbt.js.map +1 -0
- package/esm/utils/watch-descriptor.js +138 -0
- package/esm/utils/watch-descriptor.js.map +1 -0
- package/package.json +17 -11
- package/types/current-vault-refusal.d.ts +2 -0
- package/types/evm-fees.d.ts +25 -0
- package/types/hardware-transaction-authority.d.ts +5 -0
- package/types/index.d.ts +3 -3
- package/types/keyring-core.d.ts +48 -44
- package/types/keyring-host.d.ts +46 -0
- package/types/keyring-manager.d.ts +14 -7
- package/types/ledger/dmk-host.d.ts +7 -0
- package/types/ledger/dmk-selected-host.d.ts +7 -0
- package/types/ledger/dmk-session.d.ts +10 -2
- package/types/ledger/host-types.d.ts +4 -1
- package/types/ledger/index.d.ts +1 -1
- package/types/ledger/types.d.ts +3 -2
- package/types/ledger-host.d.ts +6 -2
- package/types/providers.d.ts +1 -1
- package/types/software.d.ts +7 -19
- package/types/storage-core.d.ts +3 -1
- package/types/transactions/ethereum.d.ts +14 -11
- package/types/transactions/evm-cancellation.d.ts +10 -0
- package/types/transactions/evm-local-signer.d.ts +2 -2
- package/types/transactions/evm-submission.d.ts +11 -4
- package/types/transactions/hardware-psbt.d.ts +1 -1
- package/types/transactions/index.d.ts +3 -2
- package/types/transactions/prepared-evm.d.ts +4 -1
- package/types/transactions/syscoin-core.d.ts +48 -21
- package/types/trezor/connect.d.ts +6 -3
- package/types/trezor/index.d.ts +4 -3
- package/types/types.d.ts +95 -63
- package/types/utils/owned-blockbook-account.d.ts +3 -0
- package/cjs/hardware-wallet-manager-singleton.js +0 -34
- package/cjs/hardware-wallet-manager-singleton.js.map +0 -1
- package/cjs/storage.js +0 -20
- package/cjs/storage.js.map +0 -1
- package/cjs/transactions/syscoin.js +0 -16
- package/cjs/transactions/syscoin.js.map +0 -1
- package/cjs/utils/blockbook-cache.js +0 -76
- package/cjs/utils/blockbook-cache.js.map +0 -1
- package/types/hardware-wallet-manager-singleton.d.ts +0 -19
- package/types/storage.d.ts +0 -5
- package/types/transactions/syscoin.d.ts +0 -9
- package/types/utils/blockbook-cache.d.ts +0 -6
|
@@ -14,8 +14,10 @@ const ethers_v6_1 = require("../ethers-v6");
|
|
|
14
14
|
const evm_fees_1 = require("../evm-fees");
|
|
15
15
|
const providers_1 = require("../providers");
|
|
16
16
|
const types_1 = require("../types");
|
|
17
|
+
const evm_cancellation_1 = require("./evm-cancellation");
|
|
17
18
|
const evm_local_signer_1 = require("./evm-local-signer");
|
|
18
19
|
const evm_submission_1 = require("./evm-submission");
|
|
20
|
+
const prepared_evm_1 = require("./prepared-evm");
|
|
19
21
|
const derivation_paths_1 = require("../utils/derivation-paths");
|
|
20
22
|
const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
|
|
21
23
|
code: 'HARDWARE_SIGNER_UNAVAILABLE',
|
|
@@ -115,14 +117,16 @@ const authenticateReplacementOriginal = (transaction, requestedHash, expectedSen
|
|
|
115
117
|
});
|
|
116
118
|
};
|
|
117
119
|
const assertSynchronousOriginalValidation = (result, message = 'Original transaction validator must be synchronous') => {
|
|
120
|
+
if (result === undefined)
|
|
121
|
+
return;
|
|
118
122
|
if (result !== null &&
|
|
119
123
|
(typeof result === 'object' || typeof result === 'function') &&
|
|
120
124
|
typeof result.then === 'function') {
|
|
121
125
|
// A void callback type also accepts async functions. Decline their pending
|
|
122
126
|
// decision and consume a later rejection without allowing signing to start.
|
|
123
127
|
void Promise.resolve(result).catch(() => undefined);
|
|
124
|
-
throw new Error(message);
|
|
125
128
|
}
|
|
129
|
+
throw new Error(message);
|
|
126
130
|
};
|
|
127
131
|
class EthereumTransactions {
|
|
128
132
|
constructor(getNetwork, getDecryptedPrivateKey, getState, ledgerSigner, trezorSigner, captureSigningContext, captureSubmissionLifecycle) {
|
|
@@ -148,6 +152,14 @@ class EthereumTransactions {
|
|
|
148
152
|
if (type === types_1.KeyringAccountType.Trezor || account?.isTrezorWallet)
|
|
149
153
|
this.requireTrezorSigner();
|
|
150
154
|
};
|
|
155
|
+
this.captureSessionGuard = () => {
|
|
156
|
+
const guard = this.captureSigningContext();
|
|
157
|
+
if (typeof guard !== 'function') {
|
|
158
|
+
void Promise.resolve(guard).catch(() => undefined);
|
|
159
|
+
throw new TypeError('Signing context must return a synchronous guard');
|
|
160
|
+
}
|
|
161
|
+
return () => assertSynchronousOriginalValidation(guard(), 'Signing context guard must be synchronous');
|
|
162
|
+
};
|
|
151
163
|
this.captureEvmSigningContext = (target) => {
|
|
152
164
|
this.assertHardwareSignerAvailable(target);
|
|
153
165
|
const state = this.getState();
|
|
@@ -161,10 +173,10 @@ class EthereumTransactions {
|
|
|
161
173
|
throw new Error('Active account not found');
|
|
162
174
|
const account = { address: selectedAccount.address };
|
|
163
175
|
const network = { ...state.activeNetwork };
|
|
164
|
-
const assertSession = this.
|
|
176
|
+
const assertSession = this.captureSessionGuard();
|
|
165
177
|
const provider = this.web3Provider;
|
|
166
178
|
const assertCurrent = () => {
|
|
167
|
-
assertSession
|
|
179
|
+
assertSession();
|
|
168
180
|
const current = this.getState();
|
|
169
181
|
const currentAccount = current.accounts[activeAccountType]?.[activeAccountId];
|
|
170
182
|
if (current.activeAccountId !== selectedId ||
|
|
@@ -190,17 +202,30 @@ class EthereumTransactions {
|
|
|
190
202
|
assertCurrent,
|
|
191
203
|
};
|
|
192
204
|
};
|
|
193
|
-
this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) =>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) => {
|
|
206
|
+
if (!this.captureSubmissionLifecycle)
|
|
207
|
+
throw new TypeError('A durable EVM submission lifecycle is required');
|
|
208
|
+
return (0, evm_submission_1.captureEvmSubmission)({
|
|
209
|
+
operation,
|
|
210
|
+
account: { address: account.address, type, id },
|
|
211
|
+
network: {
|
|
212
|
+
kind: network.kind,
|
|
213
|
+
chainId: network.chainId,
|
|
214
|
+
slip44: network.slip44,
|
|
215
|
+
url: network.url,
|
|
216
|
+
},
|
|
217
|
+
...(originalHash !== undefined ? { originalHash } : {}),
|
|
218
|
+
}, assertCurrent, this.captureSubmissionLifecycle);
|
|
219
|
+
};
|
|
220
|
+
this.authorizeHardwareTransaction = async (provider, assertCurrent, signal = this.abortController.signal) => {
|
|
221
|
+
assertCurrent();
|
|
222
|
+
await provider.verifyConfiguredChainId();
|
|
223
|
+
assertCurrent();
|
|
224
|
+
return { assertCurrent, signal };
|
|
225
|
+
};
|
|
226
|
+
this.submitSignedHardwareTransaction = (provider, raw, broadcast, submission, beforeBroadcast, expectedUnsigned) => (0, evm_submission_1.submitSignedHardwareEvmTransaction)(raw, broadcast, submission, beforeBroadcast, expectedUnsigned,
|
|
227
|
+
// A device confirmation or journal write can outlast the earlier check.
|
|
228
|
+
() => provider.verifyConfiguredChainId(true));
|
|
204
229
|
this.signTypedData = async (addr, typedData, version) => {
|
|
205
230
|
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
206
231
|
const { activeAccountType, accounts, activeAccountId } = this.getState();
|
|
@@ -554,35 +579,45 @@ class EthereumTransactions {
|
|
|
554
579
|
throw new Error(`Invalid numeric field "${fieldName}" for EVM tx: ${String(value)}`);
|
|
555
580
|
}
|
|
556
581
|
};
|
|
557
|
-
this.
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
maxPriorityFeePerGas =
|
|
570
|
-
maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas;
|
|
571
|
-
if (!maxFeePerGas || maxPriorityFeePerGas == null) {
|
|
572
|
-
return {
|
|
573
|
-
gasPrice: feeData.gasPrice || (await provider.getGasPrice()),
|
|
574
|
-
isLegacy: true,
|
|
575
|
-
maxFeePerGas: undefined,
|
|
576
|
-
maxPriorityFeePerGas: undefined,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return {
|
|
581
|
-
gasPrice: undefined,
|
|
582
|
-
isLegacy: false,
|
|
583
|
-
maxFeePerGas,
|
|
584
|
-
maxPriorityFeePerGas,
|
|
582
|
+
this.captureReviewedTokenApproval = (approval, standard) => {
|
|
583
|
+
if (!approval ||
|
|
584
|
+
typeof approval.assertCurrent !== 'function' ||
|
|
585
|
+
!(approval.signal instanceof AbortSignal))
|
|
586
|
+
throw new Error(`${standard} transaction approval required`);
|
|
587
|
+
const { assertCurrent, signal } = approval;
|
|
588
|
+
const assertApproval = () => {
|
|
589
|
+
if (signal.aborted)
|
|
590
|
+
throw Object.assign(new Error(`${standard} transaction approval expired`), {
|
|
591
|
+
transactionNotBroadcast: true,
|
|
592
|
+
});
|
|
593
|
+
assertSynchronousOriginalValidation(assertCurrent(), `${standard} transaction approval must be synchronous`);
|
|
585
594
|
};
|
|
595
|
+
assertApproval();
|
|
596
|
+
return { assertApproval, signal };
|
|
597
|
+
};
|
|
598
|
+
this.reviewedTokenFees = (request, chainId, standard) => {
|
|
599
|
+
const { isLegacy } = request;
|
|
600
|
+
if (typeof isLegacy !== 'boolean' ||
|
|
601
|
+
(isLegacy
|
|
602
|
+
? request.maxFeePerGas !== undefined ||
|
|
603
|
+
request.maxPriorityFeePerGas !== undefined
|
|
604
|
+
: request.gasPrice !== undefined))
|
|
605
|
+
throw new Error(`Invalid ${standard} transaction fee model`);
|
|
606
|
+
const gasLimit = ethers_v6_1.BigNumber.from((0, evm_fees_1.assertEvmGasLimit)((0, evm_fees_1.parseEvmFeeQuantity)(request.gasLimit, `${standard} gas limit`), BigInt(chainId)));
|
|
607
|
+
const gasPrice = isLegacy
|
|
608
|
+
? ethers_v6_1.BigNumber.from((0, evm_fees_1.parseEvmFeeQuantity)(request.gasPrice, `${standard} gas price`))
|
|
609
|
+
: undefined;
|
|
610
|
+
const maxFeePerGas = !isLegacy
|
|
611
|
+
? ethers_v6_1.BigNumber.from((0, evm_fees_1.parseEvmFeeQuantity)(request.maxFeePerGas, `${standard} maximum fee`))
|
|
612
|
+
: undefined;
|
|
613
|
+
const maxPriorityFeePerGas = !isLegacy
|
|
614
|
+
? ethers_v6_1.BigNumber.from((0, evm_fees_1.parseEvmFeeQuantity)(request.maxPriorityFeePerGas, `${standard} priority fee`))
|
|
615
|
+
: undefined;
|
|
616
|
+
if (gasPrice?.isZero() ||
|
|
617
|
+
(maxFeePerGas &&
|
|
618
|
+
(maxFeePerGas.isZero() || maxPriorityFeePerGas.gt(maxFeePerGas))))
|
|
619
|
+
throw new Error(`Invalid ${standard} fees`);
|
|
620
|
+
return { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas };
|
|
586
621
|
};
|
|
587
622
|
this.getData = ({ contractAddress, receivingAddress, value, }) => {
|
|
588
623
|
const abi = (0, sysweb3_utils_1.getErc20Abi)();
|
|
@@ -673,7 +708,8 @@ class EthereumTransactions {
|
|
|
673
708
|
!/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
|
|
674
709
|
throw new Error('Cancellation fee approval required');
|
|
675
710
|
}
|
|
676
|
-
const
|
|
711
|
+
const { assertCurrentContext, validateOriginal } = options;
|
|
712
|
+
const assertHostContext = () => assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
|
|
677
713
|
assertHostContext();
|
|
678
714
|
const state = this.getState();
|
|
679
715
|
const selectedType = state.activeAccountType;
|
|
@@ -696,9 +732,9 @@ class EthereumTransactions {
|
|
|
696
732
|
if (!previewOnly)
|
|
697
733
|
this.assertHardwareSignerAvailable(targetAccount);
|
|
698
734
|
const provider = this.web3Provider;
|
|
699
|
-
const assertSession = this.
|
|
735
|
+
const assertSession = this.captureSessionGuard();
|
|
700
736
|
const assertKeyringContext = () => {
|
|
701
|
-
assertSession
|
|
737
|
+
assertSession();
|
|
702
738
|
const current = this.getState();
|
|
703
739
|
if (current.activeAccountType !== selectedType ||
|
|
704
740
|
current.activeAccountId !== selectedId ||
|
|
@@ -712,14 +748,15 @@ class EthereumTransactions {
|
|
|
712
748
|
throw new Error('Wallet signing context changed');
|
|
713
749
|
}
|
|
714
750
|
};
|
|
715
|
-
const
|
|
751
|
+
const assertSigningCurrent = () => {
|
|
716
752
|
assertHostContext();
|
|
717
753
|
assertKeyringContext();
|
|
718
754
|
};
|
|
719
|
-
|
|
755
|
+
assertSigningCurrent();
|
|
720
756
|
const submission = previewOnly
|
|
721
757
|
? undefined
|
|
722
|
-
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork,
|
|
758
|
+
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
|
|
759
|
+
const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
|
|
723
760
|
const currentEip1559Fees = async () => {
|
|
724
761
|
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
|
|
725
762
|
assertCurrent();
|
|
@@ -738,7 +775,18 @@ class EthereumTransactions {
|
|
|
738
775
|
let tx = response === null
|
|
739
776
|
? null
|
|
740
777
|
: authenticateReplacementOriginal(response, txHash.toLowerCase(), activeAccount.address.toLowerCase(), activeNetwork.chainId);
|
|
741
|
-
|
|
778
|
+
const software = activeAccountType !== types_1.KeyringAccountType.Ledger &&
|
|
779
|
+
activeAccountType !== types_1.KeyringAccountType.Trezor;
|
|
780
|
+
const softwareNonce = software ? tx?.nonce ?? fallbackNonce : undefined;
|
|
781
|
+
// The host validator may retain the wrapped response and its fee objects.
|
|
782
|
+
const softwareOriginalFees = software && tx
|
|
783
|
+
? {
|
|
784
|
+
gasPrice: tx.gasPrice.toBigInt(),
|
|
785
|
+
maxFeePerGas: tx.maxFeePerGas.toBigInt(),
|
|
786
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toBigInt(),
|
|
787
|
+
}
|
|
788
|
+
: undefined;
|
|
789
|
+
assertSynchronousOriginalValidation(validateOriginal(tx));
|
|
742
790
|
assertCurrent();
|
|
743
791
|
// If transaction not found, create a minimal tx object with current gas prices
|
|
744
792
|
// This handles cases where tx with 0 gas never made it to the mempool
|
|
@@ -753,7 +801,6 @@ class EthereumTransactions {
|
|
|
753
801
|
value: ethers_v6_1.Zero,
|
|
754
802
|
nonce: fallbackNonce,
|
|
755
803
|
gasPrice: currentGasPrice,
|
|
756
|
-
gasLimit: ethers_v6_1.BigNumber.from(42000),
|
|
757
804
|
data: '0x',
|
|
758
805
|
};
|
|
759
806
|
}
|
|
@@ -766,7 +813,6 @@ class EthereumTransactions {
|
|
|
766
813
|
nonce: fallbackNonce,
|
|
767
814
|
maxFeePerGas: feeData.maxFeePerGas,
|
|
768
815
|
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
|
|
769
|
-
gasLimit: ethers_v6_1.BigNumber.from(42000),
|
|
770
816
|
data: '0x',
|
|
771
817
|
};
|
|
772
818
|
}
|
|
@@ -778,67 +824,36 @@ class EthereumTransactions {
|
|
|
778
824
|
error: true,
|
|
779
825
|
};
|
|
780
826
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
maxFeePerGas: tx.maxFeePerGas,
|
|
784
|
-
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
|
|
785
|
-
gasPrice: tx.gasPrice,
|
|
786
|
-
gasLimit: tx.gasLimit,
|
|
787
|
-
};
|
|
827
|
+
const { gasPrice: originalGasPrice, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, } = softwareOriginalFees ?? tx;
|
|
828
|
+
let feePlan;
|
|
788
829
|
if (isLegacy) {
|
|
789
|
-
const currentGasPrice =
|
|
830
|
+
const currentGasPrice = (0, evm_fees_1.parseEvmFeeQuantity)(await provider.getGasPrice(), 'cancellation gas price');
|
|
790
831
|
assertCurrent();
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
}
|
|
832
|
+
feePlan = (0, evm_fees_1.planEvmCancellationFees)({
|
|
833
|
+
type: 0,
|
|
834
|
+
currentGasPrice,
|
|
835
|
+
originalGasPrice: originalGasPrice == null
|
|
836
|
+
? undefined
|
|
837
|
+
: (0, evm_fees_1.parseEvmFeeQuantity)(originalGasPrice, 'original gas price'),
|
|
838
|
+
});
|
|
799
839
|
}
|
|
800
840
|
else {
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
oldTxsGasValues.maxPriorityFeePerGas = currentMaxPriorityFeePerGas;
|
|
816
|
-
}
|
|
817
|
-
if (oldTxsGasValues.maxFeePerGas.lt(oldTxsGasValues.maxPriorityFeePerGas)) {
|
|
818
|
-
oldTxsGasValues.maxFeePerGas = oldTxsGasValues.maxPriorityFeePerGas;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
// Preserve the existing 20% replacement policy with exact integer math.
|
|
822
|
-
// Nodes also require a strictly larger cap and tip than an old pending
|
|
823
|
-
// transaction; zero/small fees therefore need at least a one-wei bump.
|
|
824
|
-
// This is a replacement bump, not a fabricated network priority floor.
|
|
825
|
-
const bumpCancelFee = (fee) => {
|
|
826
|
-
const percentageBump = fee.mul(120).div(100);
|
|
827
|
-
const strictBump = fee.add(1);
|
|
828
|
-
return percentageBump.gt(strictBump) ? percentageBump : strictBump;
|
|
829
|
-
};
|
|
830
|
-
const newGasValues = {
|
|
831
|
-
gasLimit: ethers_v6_1.BigNumber.from(42000),
|
|
832
|
-
...(isLegacy
|
|
833
|
-
? { gasPrice: bumpCancelFee(oldTxsGasValues.gasPrice) }
|
|
834
|
-
: {
|
|
835
|
-
maxFeePerGas: bumpCancelFee(oldTxsGasValues.maxFeePerGas),
|
|
836
|
-
maxPriorityFeePerGas: bumpCancelFee(oldTxsGasValues.maxPriorityFeePerGas),
|
|
837
|
-
}),
|
|
838
|
-
};
|
|
839
|
-
const maximumFee = newGasValues.gasLimit.mul(isLegacy ? newGasValues.gasPrice : newGasValues.maxFeePerGas);
|
|
841
|
+
const currentFees = await currentEip1559Fees();
|
|
842
|
+
feePlan = (0, evm_fees_1.planEvmCancellationFees)({
|
|
843
|
+
type: 2,
|
|
844
|
+
currentMaxFeePerGas: currentFees.maxFeePerGas.toBigInt(),
|
|
845
|
+
currentMaxPriorityFeePerGas: currentFees.maxPriorityFeePerGas.toBigInt(),
|
|
846
|
+
originalMaxFeePerGas: originalMaxFeePerGas == null
|
|
847
|
+
? undefined
|
|
848
|
+
: (0, evm_fees_1.parseEvmFeeQuantity)(originalMaxFeePerGas, 'original maximum fee'),
|
|
849
|
+
originalMaxPriorityFeePerGas: originalMaxPriorityFeePerGas == null
|
|
850
|
+
? undefined
|
|
851
|
+
: (0, evm_fees_1.parseEvmFeeQuantity)(originalMaxPriorityFeePerGas, 'original priority fee'),
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
const maximumFee = ethers_v6_1.BigNumber.from(feePlan.maximumFee);
|
|
840
855
|
assertCurrent();
|
|
841
|
-
if (
|
|
856
|
+
if (!submission)
|
|
842
857
|
return {
|
|
843
858
|
isCanceled: false,
|
|
844
859
|
error: false,
|
|
@@ -849,27 +864,27 @@ class EthereumTransactions {
|
|
|
849
864
|
const assertApprovedEnvelope = (transaction) => {
|
|
850
865
|
const gas = nonNegativeFeeQuantity(transaction.gasLimit, 'cancellation gas limit');
|
|
851
866
|
const cap = nonNegativeFeeQuantity(isLegacy ? transaction.gasPrice : transaction.maxFeePerGas, 'cancellation maximum fee');
|
|
852
|
-
if (!gas.eq(
|
|
867
|
+
if (!gas.eq(feePlan.gasLimit) || gas.mul(cap).gt(approvedMaximumFee))
|
|
853
868
|
throw new Error('Cancellation fee exceeds approval');
|
|
854
869
|
};
|
|
855
870
|
// Base cancel transaction parameters (same for all wallet types)
|
|
856
871
|
const baseCancelTx = {
|
|
857
|
-
nonce: tx.nonce,
|
|
872
|
+
nonce: software ? softwareNonce : tx.nonce,
|
|
858
873
|
from: activeAccount.address,
|
|
859
874
|
to: activeAccount.address,
|
|
860
875
|
value: ethers_v6_1.Zero,
|
|
861
|
-
gasLimit:
|
|
876
|
+
gasLimit: ethers_v6_1.BigNumber.from(feePlan.gasLimit),
|
|
862
877
|
};
|
|
863
|
-
const changedTxToCancel =
|
|
878
|
+
const changedTxToCancel = feePlan.type === 0
|
|
864
879
|
? {
|
|
865
880
|
...baseCancelTx,
|
|
866
|
-
gasPrice:
|
|
881
|
+
gasPrice: ethers_v6_1.BigNumber.from(feePlan.gasPrice),
|
|
867
882
|
type: 0, // Force Type 0 for legacy cancel
|
|
868
883
|
}
|
|
869
884
|
: {
|
|
870
885
|
...baseCancelTx,
|
|
871
|
-
maxFeePerGas:
|
|
872
|
-
maxPriorityFeePerGas:
|
|
886
|
+
maxFeePerGas: ethers_v6_1.BigNumber.from(feePlan.maxFeePerGas),
|
|
887
|
+
maxPriorityFeePerGas: ethers_v6_1.BigNumber.from(feePlan.maxPriorityFeePerGas),
|
|
873
888
|
// Don't set type - let ethers auto-detect
|
|
874
889
|
};
|
|
875
890
|
// Ledger cancel handler
|
|
@@ -901,7 +916,7 @@ class EthereumTransactions {
|
|
|
901
916
|
rawTx: rawTx.replace('0x', ''),
|
|
902
917
|
accountIndex: activeAccountId,
|
|
903
918
|
expectedAddress: activeAccount.address,
|
|
904
|
-
});
|
|
919
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
905
920
|
assertCurrent();
|
|
906
921
|
const formattedSignature = {
|
|
907
922
|
r: `0x${signature.r}`,
|
|
@@ -912,7 +927,7 @@ class EthereumTransactions {
|
|
|
912
927
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
913
928
|
assertCurrent();
|
|
914
929
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
915
|
-
const transactionResponse = await (
|
|
930
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
916
931
|
return {
|
|
917
932
|
isCanceled: true,
|
|
918
933
|
transaction: transactionResponse,
|
|
@@ -974,13 +989,13 @@ class EthereumTransactions {
|
|
|
974
989
|
tx: txFormattedForTrezor,
|
|
975
990
|
index: activeAccountId.toString(),
|
|
976
991
|
slip44: activeNetwork.slip44,
|
|
977
|
-
});
|
|
992
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
978
993
|
assertCurrent();
|
|
979
994
|
if (signature.success) {
|
|
980
995
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
981
996
|
assertCurrent();
|
|
982
997
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
983
|
-
const transactionResponse = await (
|
|
998
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
984
999
|
return {
|
|
985
1000
|
isCanceled: true,
|
|
986
1001
|
transaction: transactionResponse,
|
|
@@ -1009,7 +1024,15 @@ class EthereumTransactions {
|
|
|
1009
1024
|
if (address?.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
1010
1025
|
throw new Error('Wallet signing context changed');
|
|
1011
1026
|
}
|
|
1012
|
-
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent,
|
|
1027
|
+
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, (transaction) => {
|
|
1028
|
+
assertApprovedEnvelope(transaction);
|
|
1029
|
+
(0, evm_cancellation_1.assertEvmCancellationEnvelope)(serializeTransaction(transaction), {
|
|
1030
|
+
payer: activeAccount.address,
|
|
1031
|
+
chainId: activeNetwork.chainId,
|
|
1032
|
+
nonce: softwareNonce,
|
|
1033
|
+
feePlan,
|
|
1034
|
+
});
|
|
1035
|
+
}, submission);
|
|
1013
1036
|
if (transactionResponse) {
|
|
1014
1037
|
return {
|
|
1015
1038
|
isCanceled: true,
|
|
@@ -1041,21 +1064,31 @@ class EthereumTransactions {
|
|
|
1041
1064
|
}
|
|
1042
1065
|
};
|
|
1043
1066
|
//TODO: This function needs to be refactored
|
|
1044
|
-
this.sendFormattedTransaction = async (
|
|
1067
|
+
this.sendFormattedTransaction = async (...args) => {
|
|
1068
|
+
const [params, isLegacy, options] = args;
|
|
1045
1069
|
let broadcastAttempted = false;
|
|
1046
1070
|
try {
|
|
1047
|
-
if (options
|
|
1048
|
-
|
|
1049
|
-
|
|
1071
|
+
if (!options ||
|
|
1072
|
+
typeof options !== 'object' ||
|
|
1073
|
+
Array.isArray(options) ||
|
|
1074
|
+
(options.mode !== 'reviewed-native' &&
|
|
1075
|
+
options.mode !== 'specialized') ||
|
|
1076
|
+
typeof options.assertCurrent !== 'function')
|
|
1077
|
+
throw new Error('Explicit transaction approval is required');
|
|
1050
1078
|
// Capture once; later caller mutations cannot replace this invocation's
|
|
1051
1079
|
// admission decision while chain checks or device approval are pending.
|
|
1052
|
-
const assertHostCurrent = options
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1080
|
+
const { assertCurrent: assertHostCurrent, mode } = options;
|
|
1081
|
+
const assertApproved = () => {
|
|
1082
|
+
const result = assertHostCurrent();
|
|
1083
|
+
if (result !== undefined) {
|
|
1084
|
+
assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
|
|
1085
|
+
throw new Error('Transaction context guard must return undefined');
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
assertApproved();
|
|
1056
1089
|
return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
|
|
1057
1090
|
broadcastAttempted = true;
|
|
1058
|
-
},
|
|
1091
|
+
}, assertApproved, mode);
|
|
1059
1092
|
}
|
|
1060
1093
|
catch (error) {
|
|
1061
1094
|
// A broadcast transport error may mean the node accepted the signed
|
|
@@ -1072,26 +1105,58 @@ class EthereumTransactions {
|
|
|
1072
1105
|
throw submissionError;
|
|
1073
1106
|
}
|
|
1074
1107
|
};
|
|
1075
|
-
this.sendFormattedTransactionInternal = async (
|
|
1108
|
+
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode) => {
|
|
1076
1109
|
// Hardware approval may outlive the selected account, network or unlocked
|
|
1077
1110
|
// session. Keep immutable identities and one provider for this invocation.
|
|
1078
1111
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
if (result !== undefined) {
|
|
1082
|
-
assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
|
|
1083
|
-
throw new Error('Transaction context guard must return undefined');
|
|
1084
|
-
}
|
|
1112
|
+
const assertSigningCurrent = () => {
|
|
1113
|
+
assertHostCurrent();
|
|
1085
1114
|
assertKeyringCurrent();
|
|
1086
1115
|
};
|
|
1087
1116
|
const assertBeforeBroadcast = () => {
|
|
1088
1117
|
assertCurrent();
|
|
1089
1118
|
beforeBroadcast();
|
|
1090
1119
|
};
|
|
1091
|
-
|
|
1092
|
-
const
|
|
1120
|
+
const submission = this.captureSubmission('send', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent);
|
|
1121
|
+
const assertCurrent = submission.assertCurrent;
|
|
1122
|
+
if (mode === 'reviewed-native') {
|
|
1123
|
+
const { transaction } = (0, prepared_evm_1.snapshotPreparedEvmRequest)({
|
|
1124
|
+
account: {
|
|
1125
|
+
id: activeAccountId,
|
|
1126
|
+
type: activeAccountType,
|
|
1127
|
+
expectedAddress: activeAccount.address,
|
|
1128
|
+
},
|
|
1129
|
+
network: {
|
|
1130
|
+
kind: activeNetwork.kind,
|
|
1131
|
+
chainId: String(activeNetwork.chainId),
|
|
1132
|
+
slip44: activeNetwork.slip44,
|
|
1133
|
+
url: activeNetwork.url,
|
|
1134
|
+
},
|
|
1135
|
+
transaction: input,
|
|
1136
|
+
});
|
|
1137
|
+
const minimum = this.gasOverrides.minGasLimit;
|
|
1138
|
+
if (transaction.data !== '0x' ||
|
|
1139
|
+
BigInt(transaction.value) === 0n ||
|
|
1140
|
+
BigInt(transaction.gasLimit) < 21000n ||
|
|
1141
|
+
(transaction.type === 2 && BigInt(transaction.maxFeePerGas) === 0n) ||
|
|
1142
|
+
(minimum && BigInt(transaction.gasLimit) < minimum.toBigInt()) ||
|
|
1143
|
+
(isLegacy !== undefined && isLegacy !== (transaction.type === 0)))
|
|
1144
|
+
throw new Error('Invalid reviewed native transfer approval');
|
|
1145
|
+
assertCurrent();
|
|
1146
|
+
await provider.verifyConfiguredChainId();
|
|
1147
|
+
assertCurrent();
|
|
1148
|
+
if (this.gasOverrides.minGasLimit !== minimum)
|
|
1149
|
+
throw new Error('Reviewed native gas policy changed');
|
|
1150
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1151
|
+
if (address.toLowerCase() !== transaction.from.toLowerCase())
|
|
1152
|
+
throw new Error('Reviewed native signer does not match the approved account');
|
|
1153
|
+
assertCurrent();
|
|
1154
|
+
const { signed, expectedUnsigned } = (0, prepared_evm_1.signPreparedEvmEnvelope)(transaction, decryptedPrivateKey);
|
|
1155
|
+
return (0, evm_submission_1.submitSignedEvmTransaction)(signed.rawTransaction, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsigned);
|
|
1156
|
+
}
|
|
1093
1157
|
// Detach supplied values before any RPC/device await. Autofill updates only
|
|
1094
1158
|
// this invocation; caller mutations cannot replace already supplied fields.
|
|
1159
|
+
let params = input;
|
|
1095
1160
|
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
1096
1161
|
throw new TypeError('Invalid EVM transaction request');
|
|
1097
1162
|
const supplied = { ...params };
|
|
@@ -1232,7 +1297,7 @@ class EthereumTransactions {
|
|
|
1232
1297
|
rawTx: rawTx.replace('0x', ''),
|
|
1233
1298
|
accountIndex: activeAccountId,
|
|
1234
1299
|
expectedAddress: activeAccount.address,
|
|
1235
|
-
});
|
|
1300
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1236
1301
|
assertCurrent();
|
|
1237
1302
|
const formattedSignature = {
|
|
1238
1303
|
r: `0x${signature.r}`,
|
|
@@ -1243,7 +1308,7 @@ class EthereumTransactions {
|
|
|
1243
1308
|
try {
|
|
1244
1309
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1245
1310
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1246
|
-
const finalTx = await (
|
|
1311
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
|
|
1247
1312
|
return finalTx;
|
|
1248
1313
|
}
|
|
1249
1314
|
catch (error) {
|
|
@@ -1314,7 +1379,7 @@ class EthereumTransactions {
|
|
|
1314
1379
|
tx: txFormattedForTrezor,
|
|
1315
1380
|
coin: activeNetwork.currency,
|
|
1316
1381
|
slip44: activeNetwork.slip44,
|
|
1317
|
-
});
|
|
1382
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1318
1383
|
assertCurrent();
|
|
1319
1384
|
if (signature.success) {
|
|
1320
1385
|
try {
|
|
@@ -1333,7 +1398,7 @@ class EthereumTransactions {
|
|
|
1333
1398
|
};
|
|
1334
1399
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1335
1400
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1336
|
-
const finalTx = await (
|
|
1401
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1337
1402
|
return finalTx;
|
|
1338
1403
|
}
|
|
1339
1404
|
catch (error) {
|
|
@@ -1377,35 +1442,37 @@ class EthereumTransactions {
|
|
|
1377
1442
|
return await sendEVMTransaction();
|
|
1378
1443
|
}
|
|
1379
1444
|
};
|
|
1380
|
-
this.sendTransactionWithEditedFee = async (txHash, isLegacy
|
|
1381
|
-
const
|
|
1382
|
-
const
|
|
1383
|
-
if (!previewOnly)
|
|
1384
|
-
this.assertHardwareSignerAvailable(options.targetAccount);
|
|
1445
|
+
this.sendTransactionWithEditedFee = async (txHash, isLegacy, options) => {
|
|
1446
|
+
const previewOnly = options?.previewOnly === true;
|
|
1447
|
+
const approvedMaximumFee = options?.approvedMaximumFee;
|
|
1385
1448
|
if (!previewOnly &&
|
|
1386
1449
|
(typeof approvedMaximumFee !== 'string' ||
|
|
1387
|
-
|
|
1450
|
+
!/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee)))
|
|
1388
1451
|
throw new Error('Replacement fee approval required');
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1452
|
+
if (typeof options?.assertCurrentContext !== 'function')
|
|
1453
|
+
throw new Error('Replacement context required');
|
|
1454
|
+
if (typeof options.validateOriginal !== 'function')
|
|
1455
|
+
throw new Error('Replacement original validator required');
|
|
1456
|
+
const { assertCurrentContext, validateOriginal, beforeBroadcast } = options;
|
|
1457
|
+
if (!previewOnly)
|
|
1458
|
+
this.assertHardwareSignerAvailable(options.targetAccount);
|
|
1459
|
+
isLegacy ?? (isLegacy = false);
|
|
1460
|
+
let assertCapturedSignerCurrent;
|
|
1461
|
+
const assertSigningCurrent = () => {
|
|
1462
|
+
assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
|
|
1463
|
+
assertCapturedSignerCurrent?.();
|
|
1397
1464
|
};
|
|
1398
1465
|
const assertBeforeBroadcast = () => {
|
|
1399
|
-
|
|
1400
|
-
beforeBroadcast?.();
|
|
1401
|
-
|
|
1466
|
+
assertSigningCurrent();
|
|
1467
|
+
assertSynchronousOriginalValidation(beforeBroadcast?.(), 'Transaction broadcast guard must be synchronous');
|
|
1468
|
+
assertSigningCurrent();
|
|
1402
1469
|
};
|
|
1403
1470
|
const assertSignedSender = (signedTx) => {
|
|
1404
1471
|
if (transaction_1.Transaction.from(signedTx).from?.toLowerCase() !== expectedSender) {
|
|
1405
1472
|
throw new Error('Replacement signature does not match account');
|
|
1406
1473
|
}
|
|
1407
1474
|
};
|
|
1408
|
-
|
|
1475
|
+
assertSigningCurrent();
|
|
1409
1476
|
const state = this.getState();
|
|
1410
1477
|
const activeAccountType = options.targetAccount?.type ?? state.activeAccountType;
|
|
1411
1478
|
const activeAccountId = options.targetAccount?.id ?? state.activeAccountId;
|
|
@@ -1421,14 +1488,16 @@ class EthereumTransactions {
|
|
|
1421
1488
|
throw new Error('Replacement signing account is unavailable');
|
|
1422
1489
|
}
|
|
1423
1490
|
if (!previewOnly)
|
|
1424
|
-
|
|
1491
|
+
assertCapturedSignerCurrent =
|
|
1492
|
+
this.captureEvmSigningContext(targetAccount).assertCurrent;
|
|
1425
1493
|
const expectedSender = activeAccount.address.toLowerCase();
|
|
1426
1494
|
const requestedHash = txHash.toLowerCase();
|
|
1427
1495
|
const provider = this.web3Provider;
|
|
1428
|
-
|
|
1496
|
+
assertSigningCurrent();
|
|
1429
1497
|
const submission = previewOnly
|
|
1430
1498
|
? undefined
|
|
1431
|
-
: this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork,
|
|
1499
|
+
: this.captureSubmission('speed-up', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
|
|
1500
|
+
const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
|
|
1432
1501
|
const validateLookup = (transaction) => {
|
|
1433
1502
|
assertCurrent();
|
|
1434
1503
|
if (!transaction)
|
|
@@ -1442,7 +1511,7 @@ class EthereumTransactions {
|
|
|
1442
1511
|
activeAccountType === types_1.KeyringAccountType.Trezor) {
|
|
1443
1512
|
throw new Error('Trezor does not support type 1 replacement signing');
|
|
1444
1513
|
}
|
|
1445
|
-
assertSynchronousOriginalValidation(validateOriginal
|
|
1514
|
+
assertSynchronousOriginalValidation(validateOriginal(authenticated));
|
|
1446
1515
|
assertCurrent();
|
|
1447
1516
|
return authenticated;
|
|
1448
1517
|
};
|
|
@@ -1541,7 +1610,7 @@ class EthereumTransactions {
|
|
|
1541
1610
|
maxPriorityFeePerGas: newGasValues.maxPriorityFeePerGas,
|
|
1542
1611
|
}),
|
|
1543
1612
|
};
|
|
1544
|
-
if (
|
|
1613
|
+
if (!submission)
|
|
1545
1614
|
return { isSpeedUp: false, maximumFee: maximumFee.toString() };
|
|
1546
1615
|
if (maximumFee.gt(approvedMaximumFee))
|
|
1547
1616
|
throw new Error('Replacement fee exceeds approval');
|
|
@@ -1572,7 +1641,7 @@ class EthereumTransactions {
|
|
|
1572
1641
|
rawTx: rawTx.replace('0x', ''),
|
|
1573
1642
|
accountIndex: activeAccountId,
|
|
1574
1643
|
expectedAddress: activeAccount.address,
|
|
1575
|
-
});
|
|
1644
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1576
1645
|
const formattedSignature = {
|
|
1577
1646
|
r: `0x${signature.r}`,
|
|
1578
1647
|
s: `0x${signature.s}`,
|
|
@@ -1582,7 +1651,7 @@ class EthereumTransactions {
|
|
|
1582
1651
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1583
1652
|
assertSignedSender(signedTx);
|
|
1584
1653
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1585
|
-
const transactionResponse = await (
|
|
1654
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
|
|
1586
1655
|
return {
|
|
1587
1656
|
isSpeedUp: true,
|
|
1588
1657
|
transaction: transactionResponse,
|
|
@@ -1658,12 +1727,12 @@ class EthereumTransactions {
|
|
|
1658
1727
|
tx: txFormattedForTrezor,
|
|
1659
1728
|
index: activeAccountId.toString(),
|
|
1660
1729
|
slip44: activeNetwork.slip44,
|
|
1661
|
-
});
|
|
1730
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1662
1731
|
if (signature.success) {
|
|
1663
1732
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
1664
1733
|
assertSignedSender(signedTx);
|
|
1665
1734
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1666
|
-
const transactionResponse = await (
|
|
1735
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1667
1736
|
return {
|
|
1668
1737
|
isSpeedUp: true,
|
|
1669
1738
|
transaction: transactionResponse,
|
|
@@ -1748,44 +1817,51 @@ class EthereumTransactions {
|
|
|
1748
1817
|
return await speedUpWithPrivateKey();
|
|
1749
1818
|
}
|
|
1750
1819
|
};
|
|
1751
|
-
this.sendSignedErc20Transaction = async (
|
|
1752
|
-
const {
|
|
1820
|
+
this.sendSignedErc20Transaction = async (request, approval) => {
|
|
1821
|
+
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC20');
|
|
1822
|
+
const { receiver, tokenAddress, tokenAmountUnits, saveTrezorTx, networkUrl, } = request;
|
|
1823
|
+
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
1753
1824
|
const activeAccountAddress = activeAccount.address;
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1825
|
+
if (networkUrl !== activeNetwork.url)
|
|
1826
|
+
throw new Error('ERC20 transaction approval network changed');
|
|
1827
|
+
if ('tokenAmount' in request ||
|
|
1828
|
+
'decimals' in request ||
|
|
1829
|
+
'tokenId' in request ||
|
|
1830
|
+
typeof tokenAmountUnits !== 'string' ||
|
|
1831
|
+
!/^(0|[1-9][0-9]{0,77})$/.test(tokenAmountUnits))
|
|
1832
|
+
throw new Error('Invalid ERC20 amount');
|
|
1833
|
+
const exactTokenAmount = (0, evm_fees_1.parseEvmFeeQuantity)(tokenAmountUnits, 'ERC20 amount');
|
|
1834
|
+
const { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = this.reviewedTokenFees(request, activeNetwork.chainId, 'ERC20');
|
|
1835
|
+
const submission = this.captureSubmission('erc20', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
1836
|
+
assertApproval();
|
|
1837
|
+
assertSigningCurrent();
|
|
1838
|
+
});
|
|
1839
|
+
const assertCurrent = submission.assertCurrent;
|
|
1763
1840
|
const sendERC20Token = async () => {
|
|
1764
1841
|
assertCurrent();
|
|
1765
1842
|
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1766
1843
|
try {
|
|
1767
1844
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
1768
|
-
// Preserve zero-decimal tokens: use provided decimals when defined (including 0).
|
|
1769
|
-
const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1770
|
-
const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
|
|
1771
1845
|
let transferMethod;
|
|
1772
1846
|
if (isLegacy) {
|
|
1773
1847
|
const overrides = {
|
|
1774
1848
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1775
1849
|
gasPrice,
|
|
1776
|
-
|
|
1850
|
+
gasLimit,
|
|
1777
1851
|
type: 0, // Explicitly set Type 0 for legacy token transfers
|
|
1778
1852
|
};
|
|
1779
|
-
|
|
1853
|
+
assertCurrent();
|
|
1854
|
+
transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
|
|
1780
1855
|
}
|
|
1781
1856
|
else {
|
|
1782
1857
|
const overrides = {
|
|
1783
1858
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1784
1859
|
maxPriorityFeePerGas,
|
|
1785
1860
|
maxFeePerGas,
|
|
1786
|
-
|
|
1861
|
+
gasLimit,
|
|
1787
1862
|
};
|
|
1788
|
-
|
|
1863
|
+
assertCurrent();
|
|
1864
|
+
transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
|
|
1789
1865
|
}
|
|
1790
1866
|
return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
|
|
1791
1867
|
...transferMethod,
|
|
@@ -1804,20 +1880,16 @@ class EthereumTransactions {
|
|
|
1804
1880
|
assertCurrent();
|
|
1805
1881
|
try {
|
|
1806
1882
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
1807
|
-
const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1808
|
-
const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
|
|
1809
1883
|
const txData = _contract.interface.encodeFunctionData('transfer', [
|
|
1810
1884
|
receiver,
|
|
1811
|
-
|
|
1885
|
+
exactTokenAmount,
|
|
1812
1886
|
]);
|
|
1813
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
1814
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
|
|
1815
1887
|
let txFormattedForEthers;
|
|
1816
1888
|
if (isLegacy) {
|
|
1817
1889
|
txFormattedForEthers = {
|
|
1818
1890
|
to: tokenAddress,
|
|
1819
1891
|
value: '0x0',
|
|
1820
|
-
gasLimit:
|
|
1892
|
+
gasLimit: gasLimit,
|
|
1821
1893
|
gasPrice,
|
|
1822
1894
|
data: txData,
|
|
1823
1895
|
nonce: transactionNonce,
|
|
@@ -1829,7 +1901,7 @@ class EthereumTransactions {
|
|
|
1829
1901
|
txFormattedForEthers = {
|
|
1830
1902
|
to: tokenAddress,
|
|
1831
1903
|
value: '0x0',
|
|
1832
|
-
gasLimit:
|
|
1904
|
+
gasLimit: gasLimit,
|
|
1833
1905
|
maxFeePerGas,
|
|
1834
1906
|
maxPriorityFeePerGas,
|
|
1835
1907
|
data: txData,
|
|
@@ -1843,7 +1915,7 @@ class EthereumTransactions {
|
|
|
1843
1915
|
rawTx: rawTx.replace('0x', ''),
|
|
1844
1916
|
accountIndex: activeAccountId,
|
|
1845
1917
|
expectedAddress: activeAccount.address,
|
|
1846
|
-
});
|
|
1918
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
1847
1919
|
assertCurrent();
|
|
1848
1920
|
const formattedSignature = {
|
|
1849
1921
|
r: `0x${signature.r}`,
|
|
@@ -1855,7 +1927,7 @@ class EthereumTransactions {
|
|
|
1855
1927
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1856
1928
|
assertCurrent();
|
|
1857
1929
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1858
|
-
const finalTx = await (
|
|
1930
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
|
|
1859
1931
|
const response = await provider.sendTransaction(raw);
|
|
1860
1932
|
saveTrezorTx && saveTrezorTx(response);
|
|
1861
1933
|
return response;
|
|
@@ -1880,20 +1952,16 @@ class EthereumTransactions {
|
|
|
1880
1952
|
assertCurrent();
|
|
1881
1953
|
try {
|
|
1882
1954
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
1883
|
-
const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1884
|
-
const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
|
|
1885
1955
|
const txData = _contract.interface.encodeFunctionData('transfer', [
|
|
1886
1956
|
receiver,
|
|
1887
|
-
|
|
1957
|
+
exactTokenAmount,
|
|
1888
1958
|
]);
|
|
1889
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
1890
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
|
|
1891
1959
|
let txToBeSignedByTrezor;
|
|
1892
1960
|
if (isLegacy) {
|
|
1893
1961
|
txToBeSignedByTrezor = {
|
|
1894
1962
|
to: tokenAddress,
|
|
1895
1963
|
value: '0x0',
|
|
1896
|
-
gasLimit: this.toHex0x(
|
|
1964
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1897
1965
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
1898
1966
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
1899
1967
|
chainId: activeNetwork.chainId,
|
|
@@ -1904,7 +1972,7 @@ class EthereumTransactions {
|
|
|
1904
1972
|
txToBeSignedByTrezor = {
|
|
1905
1973
|
to: tokenAddress,
|
|
1906
1974
|
value: '0x0',
|
|
1907
|
-
gasLimit: this.toHex0x(
|
|
1975
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1908
1976
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
1909
1977
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
1910
1978
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
@@ -1922,7 +1990,7 @@ class EthereumTransactions {
|
|
|
1922
1990
|
tx: txToBeSignedByTrezor,
|
|
1923
1991
|
coin: activeNetwork.currency,
|
|
1924
1992
|
slip44: activeNetwork.slip44,
|
|
1925
|
-
});
|
|
1993
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
1926
1994
|
assertCurrent();
|
|
1927
1995
|
if (signature.success) {
|
|
1928
1996
|
try {
|
|
@@ -1931,7 +1999,7 @@ class EthereumTransactions {
|
|
|
1931
1999
|
txFormattedForEthers = {
|
|
1932
2000
|
to: tokenAddress,
|
|
1933
2001
|
value: '0x0',
|
|
1934
|
-
gasLimit:
|
|
2002
|
+
gasLimit: gasLimit,
|
|
1935
2003
|
gasPrice,
|
|
1936
2004
|
data: txData,
|
|
1937
2005
|
nonce: transactionNonce,
|
|
@@ -1943,7 +2011,7 @@ class EthereumTransactions {
|
|
|
1943
2011
|
txFormattedForEthers = {
|
|
1944
2012
|
to: tokenAddress,
|
|
1945
2013
|
value: '0x0',
|
|
1946
|
-
gasLimit:
|
|
2014
|
+
gasLimit: gasLimit,
|
|
1947
2015
|
maxFeePerGas,
|
|
1948
2016
|
maxPriorityFeePerGas,
|
|
1949
2017
|
data: txData,
|
|
@@ -1955,7 +2023,7 @@ class EthereumTransactions {
|
|
|
1955
2023
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1956
2024
|
assertCurrent();
|
|
1957
2025
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1958
|
-
const finalTx = await (
|
|
2026
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
|
|
1959
2027
|
const response = await provider.sendTransaction(raw);
|
|
1960
2028
|
saveTrezorTx && saveTrezorTx(response);
|
|
1961
2029
|
return response;
|
|
@@ -1983,22 +2051,26 @@ class EthereumTransactions {
|
|
|
1983
2051
|
return (await sendERC20Token());
|
|
1984
2052
|
}
|
|
1985
2053
|
};
|
|
1986
|
-
this.sendSignedErc721Transaction = async (
|
|
1987
|
-
const {
|
|
2054
|
+
this.sendSignedErc721Transaction = async (request, approval) => {
|
|
2055
|
+
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC721');
|
|
2056
|
+
const { receiver, tokenAddress, tokenId, networkUrl } = request;
|
|
2057
|
+
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
2058
|
+
if (networkUrl !== activeNetwork.url)
|
|
2059
|
+
throw new Error('ERC721 transaction approval network changed');
|
|
2060
|
+
if ('tokenAmountUnits' in request || 'decimals' in request)
|
|
2061
|
+
throw new Error('ERC20 amount is not allowed for NFT transfers');
|
|
2062
|
+
if (typeof tokenId !== 'string' || !/^(0|[1-9][0-9]{0,77})$/.test(tokenId))
|
|
2063
|
+
throw new Error('Invalid ERC721 token ID');
|
|
2064
|
+
const normalizedTokenId = (0, evm_fees_1.parseEvmFeeQuantity)(tokenId, 'ERC721 token ID');
|
|
2065
|
+
if ('tokenAmount' in request)
|
|
2066
|
+
throw new Error('ERC721 transfer must contain one token');
|
|
2067
|
+
const { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = this.reviewedTokenFees(request, activeNetwork.chainId, 'ERC721');
|
|
1988
2068
|
const activeAccountAddress = activeAccount.address;
|
|
1989
|
-
const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork,
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
await this.resolveEvmFeeParams({
|
|
1995
|
-
gasPrice,
|
|
1996
|
-
isLegacy,
|
|
1997
|
-
maxFeePerGas,
|
|
1998
|
-
maxPriorityFeePerGas,
|
|
1999
|
-
}, provider));
|
|
2000
|
-
assertCurrent();
|
|
2001
|
-
const normalizedTokenId = ethers_v6_1.BigNumber.from(tokenId).toBigInt();
|
|
2069
|
+
const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
2070
|
+
assertApproval();
|
|
2071
|
+
assertSigningCurrent();
|
|
2072
|
+
});
|
|
2073
|
+
const assertCurrent = submission.assertCurrent;
|
|
2002
2074
|
const sendERC721Token = async () => {
|
|
2003
2075
|
assertCurrent();
|
|
2004
2076
|
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
@@ -2009,7 +2081,7 @@ class EthereumTransactions {
|
|
|
2009
2081
|
const overrides = {
|
|
2010
2082
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2011
2083
|
gasPrice,
|
|
2012
|
-
|
|
2084
|
+
gasLimit,
|
|
2013
2085
|
type: 0, // Explicitly set Type 0 for legacy NFT transfers
|
|
2014
2086
|
};
|
|
2015
2087
|
transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
|
|
@@ -2019,7 +2091,7 @@ class EthereumTransactions {
|
|
|
2019
2091
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2020
2092
|
maxPriorityFeePerGas,
|
|
2021
2093
|
maxFeePerGas,
|
|
2022
|
-
|
|
2094
|
+
gasLimit,
|
|
2023
2095
|
};
|
|
2024
2096
|
transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
|
|
2025
2097
|
}
|
|
@@ -2045,14 +2117,12 @@ class EthereumTransactions {
|
|
|
2045
2117
|
receiver,
|
|
2046
2118
|
normalizedTokenId,
|
|
2047
2119
|
]);
|
|
2048
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2049
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
|
|
2050
2120
|
let txFormattedForEthers;
|
|
2051
2121
|
if (isLegacy) {
|
|
2052
2122
|
txFormattedForEthers = {
|
|
2053
2123
|
to: tokenAddress,
|
|
2054
2124
|
value: '0x0',
|
|
2055
|
-
gasLimit
|
|
2125
|
+
gasLimit,
|
|
2056
2126
|
gasPrice,
|
|
2057
2127
|
data: txData,
|
|
2058
2128
|
nonce: transactionNonce,
|
|
@@ -2064,7 +2134,7 @@ class EthereumTransactions {
|
|
|
2064
2134
|
txFormattedForEthers = {
|
|
2065
2135
|
to: tokenAddress,
|
|
2066
2136
|
value: '0x0',
|
|
2067
|
-
gasLimit
|
|
2137
|
+
gasLimit,
|
|
2068
2138
|
maxFeePerGas,
|
|
2069
2139
|
maxPriorityFeePerGas,
|
|
2070
2140
|
data: txData,
|
|
@@ -2078,7 +2148,7 @@ class EthereumTransactions {
|
|
|
2078
2148
|
rawTx: rawTx.replace('0x', ''),
|
|
2079
2149
|
accountIndex: activeAccountId,
|
|
2080
2150
|
expectedAddress: activeAccount.address,
|
|
2081
|
-
});
|
|
2151
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2082
2152
|
assertCurrent();
|
|
2083
2153
|
const formattedSignature = {
|
|
2084
2154
|
r: `0x${signature.r}`,
|
|
@@ -2090,7 +2160,7 @@ class EthereumTransactions {
|
|
|
2090
2160
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2091
2161
|
assertCurrent();
|
|
2092
2162
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2093
|
-
const finalTx = await (
|
|
2163
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
2094
2164
|
return finalTx;
|
|
2095
2165
|
}
|
|
2096
2166
|
catch (error) {
|
|
@@ -2116,14 +2186,12 @@ class EthereumTransactions {
|
|
|
2116
2186
|
receiver,
|
|
2117
2187
|
normalizedTokenId,
|
|
2118
2188
|
]);
|
|
2119
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2120
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
|
|
2121
2189
|
let txToBeSignedByTrezor;
|
|
2122
2190
|
if (isLegacy) {
|
|
2123
2191
|
txToBeSignedByTrezor = {
|
|
2124
2192
|
to: tokenAddress,
|
|
2125
2193
|
value: '0x0',
|
|
2126
|
-
gasLimit: this.toHex0x(
|
|
2194
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2127
2195
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2128
2196
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2129
2197
|
chainId: activeNetwork.chainId,
|
|
@@ -2135,7 +2203,7 @@ class EthereumTransactions {
|
|
|
2135
2203
|
txToBeSignedByTrezor = {
|
|
2136
2204
|
to: tokenAddress,
|
|
2137
2205
|
value: '0x0',
|
|
2138
|
-
gasLimit: this.toHex0x(
|
|
2206
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2139
2207
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2140
2208
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2141
2209
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
@@ -2155,7 +2223,7 @@ class EthereumTransactions {
|
|
|
2155
2223
|
tx: txToBeSignedByTrezor,
|
|
2156
2224
|
coin: trezorCoin,
|
|
2157
2225
|
slip44: activeNetwork.slip44,
|
|
2158
|
-
});
|
|
2226
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2159
2227
|
assertCurrent();
|
|
2160
2228
|
if (signature.success) {
|
|
2161
2229
|
try {
|
|
@@ -2164,7 +2232,7 @@ class EthereumTransactions {
|
|
|
2164
2232
|
txFormattedForEthers = {
|
|
2165
2233
|
to: tokenAddress,
|
|
2166
2234
|
value: '0x0',
|
|
2167
|
-
gasLimit
|
|
2235
|
+
gasLimit,
|
|
2168
2236
|
gasPrice,
|
|
2169
2237
|
data: txData,
|
|
2170
2238
|
nonce: transactionNonce,
|
|
@@ -2176,7 +2244,7 @@ class EthereumTransactions {
|
|
|
2176
2244
|
txFormattedForEthers = {
|
|
2177
2245
|
to: tokenAddress,
|
|
2178
2246
|
value: '0x0',
|
|
2179
|
-
gasLimit
|
|
2247
|
+
gasLimit,
|
|
2180
2248
|
maxFeePerGas,
|
|
2181
2249
|
maxPriorityFeePerGas,
|
|
2182
2250
|
data: txData,
|
|
@@ -2188,7 +2256,7 @@ class EthereumTransactions {
|
|
|
2188
2256
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2189
2257
|
assertCurrent();
|
|
2190
2258
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2191
|
-
const finalTx = await (
|
|
2259
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
2192
2260
|
return finalTx;
|
|
2193
2261
|
}
|
|
2194
2262
|
catch (error) {
|
|
@@ -2214,18 +2282,29 @@ class EthereumTransactions {
|
|
|
2214
2282
|
return (await sendERC721Token());
|
|
2215
2283
|
}
|
|
2216
2284
|
};
|
|
2217
|
-
this.sendSignedErc1155Transaction = async (
|
|
2218
|
-
const {
|
|
2285
|
+
this.sendSignedErc1155Transaction = async (request, approval) => {
|
|
2286
|
+
const { assertApproval, signal } = this.captureReviewedTokenApproval(approval, 'ERC1155');
|
|
2287
|
+
const { receiver, tokenAddress, tokenId, networkUrl } = request;
|
|
2288
|
+
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertSigningCurrent, } = this.captureEvmSigningContext();
|
|
2289
|
+
if (networkUrl !== activeNetwork.url)
|
|
2290
|
+
throw new Error('ERC1155 transaction approval network changed');
|
|
2291
|
+
if ('tokenAmountUnits' in request || 'decimals' in request)
|
|
2292
|
+
throw new Error('ERC20 amount is not allowed for NFT transfers');
|
|
2293
|
+
if (typeof tokenId !== 'string' || !/^(0|[1-9][0-9]{0,77})$/.test(tokenId))
|
|
2294
|
+
throw new Error('Invalid ERC1155 token ID');
|
|
2295
|
+
const normalizedTokenId = (0, evm_fees_1.parseEvmFeeQuantity)(tokenId, 'ERC1155 token ID');
|
|
2296
|
+
const { tokenAmount } = request;
|
|
2297
|
+
if (typeof tokenAmount !== 'string' ||
|
|
2298
|
+
!/^[1-9][0-9]{0,77}$/.test(tokenAmount))
|
|
2299
|
+
throw new Error('Invalid ERC1155 amount');
|
|
2300
|
+
const amount = (0, evm_fees_1.parseEvmFeeQuantity)(tokenAmount, 'ERC1155 amount');
|
|
2301
|
+
const { gasLimit, isLegacy, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = this.reviewedTokenFees(request, activeNetwork.chainId, 'ERC1155');
|
|
2219
2302
|
const activeAccountAddress = activeAccount.address;
|
|
2220
|
-
const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork,
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
maxFeePerGas,
|
|
2226
|
-
maxPriorityFeePerGas,
|
|
2227
|
-
}, provider));
|
|
2228
|
-
assertCurrent();
|
|
2303
|
+
const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
2304
|
+
assertApproval();
|
|
2305
|
+
assertSigningCurrent();
|
|
2306
|
+
});
|
|
2307
|
+
const assertCurrent = submission.assertCurrent;
|
|
2229
2308
|
const sendERC1155Token = async () => {
|
|
2230
2309
|
assertCurrent();
|
|
2231
2310
|
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
@@ -2233,13 +2312,12 @@ class EthereumTransactions {
|
|
|
2233
2312
|
try {
|
|
2234
2313
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
2235
2314
|
// Use BigNumber to avoid JS number overflow/precision loss
|
|
2236
|
-
const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
|
|
2237
2315
|
let overrides;
|
|
2238
2316
|
if (isLegacy) {
|
|
2239
2317
|
overrides = {
|
|
2240
2318
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2241
2319
|
gasPrice,
|
|
2242
|
-
|
|
2320
|
+
gasLimit,
|
|
2243
2321
|
type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
|
|
2244
2322
|
};
|
|
2245
2323
|
}
|
|
@@ -2248,10 +2326,10 @@ class EthereumTransactions {
|
|
|
2248
2326
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2249
2327
|
maxPriorityFeePerGas,
|
|
2250
2328
|
maxFeePerGas,
|
|
2251
|
-
|
|
2329
|
+
gasLimit,
|
|
2252
2330
|
};
|
|
2253
2331
|
}
|
|
2254
|
-
transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver,
|
|
2332
|
+
transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, amount, '0x', normalizeEthersOverrides(overrides));
|
|
2255
2333
|
return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
|
|
2256
2334
|
...transferMethod,
|
|
2257
2335
|
chainId: activeNetwork.chainId,
|
|
@@ -2269,16 +2347,13 @@ class EthereumTransactions {
|
|
|
2269
2347
|
assertCurrent();
|
|
2270
2348
|
try {
|
|
2271
2349
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
2272
|
-
const
|
|
2273
|
-
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
|
|
2274
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2275
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
|
|
2350
|
+
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
|
|
2276
2351
|
let txFormattedForEthers;
|
|
2277
2352
|
if (isLegacy) {
|
|
2278
2353
|
txFormattedForEthers = {
|
|
2279
2354
|
to: tokenAddress,
|
|
2280
2355
|
value: '0x0',
|
|
2281
|
-
gasLimit
|
|
2356
|
+
gasLimit,
|
|
2282
2357
|
gasPrice,
|
|
2283
2358
|
data: txData,
|
|
2284
2359
|
nonce: transactionNonce,
|
|
@@ -2290,7 +2365,7 @@ class EthereumTransactions {
|
|
|
2290
2365
|
txFormattedForEthers = {
|
|
2291
2366
|
to: tokenAddress,
|
|
2292
2367
|
value: '0x0',
|
|
2293
|
-
gasLimit
|
|
2368
|
+
gasLimit,
|
|
2294
2369
|
maxFeePerGas,
|
|
2295
2370
|
maxPriorityFeePerGas,
|
|
2296
2371
|
data: txData,
|
|
@@ -2304,7 +2379,7 @@ class EthereumTransactions {
|
|
|
2304
2379
|
rawTx: rawTx.replace('0x', ''),
|
|
2305
2380
|
accountIndex: activeAccountId,
|
|
2306
2381
|
expectedAddress: activeAccount.address,
|
|
2307
|
-
});
|
|
2382
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2308
2383
|
assertCurrent();
|
|
2309
2384
|
const formattedSignature = {
|
|
2310
2385
|
r: `0x${signature.r}`,
|
|
@@ -2316,7 +2391,7 @@ class EthereumTransactions {
|
|
|
2316
2391
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2317
2392
|
assertCurrent();
|
|
2318
2393
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2319
|
-
const finalTx = await (
|
|
2394
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
2320
2395
|
return finalTx;
|
|
2321
2396
|
}
|
|
2322
2397
|
catch (error) {
|
|
@@ -2337,16 +2412,13 @@ class EthereumTransactions {
|
|
|
2337
2412
|
assertCurrent();
|
|
2338
2413
|
try {
|
|
2339
2414
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
2340
|
-
const
|
|
2341
|
-
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
|
|
2342
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2343
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
|
|
2415
|
+
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
|
|
2344
2416
|
let txToBeSignedByTrezor;
|
|
2345
2417
|
if (isLegacy) {
|
|
2346
2418
|
txToBeSignedByTrezor = {
|
|
2347
2419
|
to: tokenAddress,
|
|
2348
2420
|
value: '0x0',
|
|
2349
|
-
gasLimit: this.toHex0x(
|
|
2421
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2350
2422
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2351
2423
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2352
2424
|
chainId: activeNetwork.chainId,
|
|
@@ -2357,7 +2429,7 @@ class EthereumTransactions {
|
|
|
2357
2429
|
txToBeSignedByTrezor = {
|
|
2358
2430
|
to: tokenAddress,
|
|
2359
2431
|
value: '0x0',
|
|
2360
|
-
gasLimit: this.toHex0x(
|
|
2432
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2361
2433
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2362
2434
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2363
2435
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
@@ -2375,7 +2447,7 @@ class EthereumTransactions {
|
|
|
2375
2447
|
tx: txToBeSignedByTrezor,
|
|
2376
2448
|
coin: activeNetwork.currency,
|
|
2377
2449
|
slip44: activeNetwork.slip44,
|
|
2378
|
-
});
|
|
2450
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2379
2451
|
assertCurrent();
|
|
2380
2452
|
if (signature.success) {
|
|
2381
2453
|
try {
|
|
@@ -2384,7 +2456,7 @@ class EthereumTransactions {
|
|
|
2384
2456
|
txFormattedForEthers = {
|
|
2385
2457
|
to: tokenAddress,
|
|
2386
2458
|
value: '0x0',
|
|
2387
|
-
gasLimit
|
|
2459
|
+
gasLimit,
|
|
2388
2460
|
gasPrice,
|
|
2389
2461
|
data: txData,
|
|
2390
2462
|
nonce: transactionNonce,
|
|
@@ -2396,7 +2468,7 @@ class EthereumTransactions {
|
|
|
2396
2468
|
txFormattedForEthers = {
|
|
2397
2469
|
to: tokenAddress,
|
|
2398
2470
|
value: '0x0',
|
|
2399
|
-
gasLimit
|
|
2471
|
+
gasLimit,
|
|
2400
2472
|
maxFeePerGas,
|
|
2401
2473
|
maxPriorityFeePerGas,
|
|
2402
2474
|
data: txData,
|
|
@@ -2408,7 +2480,7 @@ class EthereumTransactions {
|
|
|
2408
2480
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2409
2481
|
assertCurrent();
|
|
2410
2482
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2411
|
-
const finalTx = await (
|
|
2483
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
2412
2484
|
return finalTx;
|
|
2413
2485
|
}
|
|
2414
2486
|
catch (error) {
|
|
@@ -2608,6 +2680,11 @@ class EthereumTransactions {
|
|
|
2608
2680
|
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
|
|
2609
2681
|
};
|
|
2610
2682
|
};
|
|
2683
|
+
if (typeof captureSigningContext !== 'function')
|
|
2684
|
+
throw new TypeError('Ethereum transactions require a signing context');
|
|
2685
|
+
if (captureSubmissionLifecycle !== null &&
|
|
2686
|
+
typeof captureSubmissionLifecycle !== 'function')
|
|
2687
|
+
throw new TypeError('Ethereum transactions require an explicit submission capability');
|
|
2611
2688
|
this.getNetwork = getNetwork;
|
|
2612
2689
|
this.getDecryptedPrivateKey = (targetAccount) => {
|
|
2613
2690
|
this.assertHardwareSignerAvailable(targetAccount);
|
|
@@ -2634,11 +2711,13 @@ class EthereumTransactions {
|
|
|
2634
2711
|
// RPC/network than `activeNetwork`, nodes will reject broadcasts with chain-id mismatch errors.
|
|
2635
2712
|
try {
|
|
2636
2713
|
const currentNetwork = this.getNetwork();
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2714
|
+
if (currentNetwork?.kind !== sysweb3_network_1.INetworkType.Ethereum) {
|
|
2715
|
+
if (this._web3Provider)
|
|
2716
|
+
this.setWeb3Provider(currentNetwork);
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
const currentKey = `${currentNetwork.chainId}|${currentNetwork.url ?? ''}`;
|
|
2720
|
+
const needsInit = !this._web3Provider || this._web3ProviderKey !== currentKey;
|
|
2642
2721
|
if (needsInit) {
|
|
2643
2722
|
this.setWeb3Provider(currentNetwork);
|
|
2644
2723
|
}
|
|
@@ -2649,31 +2728,16 @@ class EthereumTransactions {
|
|
|
2649
2728
|
console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
|
|
2650
2729
|
}
|
|
2651
2730
|
}
|
|
2652
|
-
// Helper method to detect UTXO networks
|
|
2653
|
-
isUtxoNetwork(network) {
|
|
2654
|
-
// Generic UTXO network detection patterns:
|
|
2655
|
-
// 1. URL contains blockbook or trezor (most reliable)
|
|
2656
|
-
// 2. Network kind is explicitly set to 'syscoin'
|
|
2657
|
-
const hasBlockbookUrl = !!(network.url?.includes('blockbook') || network.url?.includes('trezor'));
|
|
2658
|
-
const hasUtxoKind = network.kind === sysweb3_network_1.INetworkType.Syscoin;
|
|
2659
|
-
return hasBlockbookUrl || hasUtxoKind;
|
|
2660
|
-
}
|
|
2661
2731
|
setWeb3Provider(network) {
|
|
2662
2732
|
this.abortController.abort();
|
|
2663
2733
|
this.abortController = new AbortController();
|
|
2664
|
-
|
|
2665
|
-
const isUtxoNetwork = this.isUtxoNetwork(network);
|
|
2666
|
-
if (isUtxoNetwork) {
|
|
2667
|
-
// For UTXO networks, don't create web3 providers at all since they won't be used
|
|
2668
|
-
console.log('[EthereumTransactions] setWeb3Provider: Skipping web3Provider creation for UTXO network:', network.url);
|
|
2669
|
-
// Clear any existing providers for UTXO networks
|
|
2734
|
+
if (network?.kind !== sysweb3_network_1.INetworkType.Ethereum) {
|
|
2670
2735
|
this._web3Provider = undefined;
|
|
2671
2736
|
this._web3ProviderKey = undefined;
|
|
2737
|
+
return;
|
|
2672
2738
|
}
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
|
|
2676
|
-
}
|
|
2739
|
+
this._web3Provider = new providers_1.CustomJsonRpcProvider(this.abortController.signal, network.url, network.chainId);
|
|
2740
|
+
this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
|
|
2677
2741
|
}
|
|
2678
2742
|
// Method to configure gas overrides for networks with validation issues
|
|
2679
2743
|
setGasOverrides(overrides) {
|