@sidhujag/sysweb3-keyring 2.1.3 → 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 +47 -47
- 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 +171 -220
- 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 -151
- 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 -56
- package/cjs/software.js.map +1 -1
- package/cjs/transactions/ethereum.js +306 -229
- package/cjs/transactions/ethereum.js.map +1 -1
- 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-session.js +1 -0
- 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/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 +5 -11
- package/types/hardware-transaction-authority.d.ts +5 -0
- package/types/index.d.ts +3 -3
- package/types/keyring-core.d.ts +22 -51
- package/types/keyring-host.d.ts +46 -0
- package/types/keyring-manager.d.ts +13 -18
- 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/providers.d.ts +1 -1
- package/types/software.d.ts +6 -19
- package/types/transactions/ethereum.d.ts +14 -11
- 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
|
@@ -17,6 +17,7 @@ const types_1 = require("../types");
|
|
|
17
17
|
const evm_cancellation_1 = require("./evm-cancellation");
|
|
18
18
|
const evm_local_signer_1 = require("./evm-local-signer");
|
|
19
19
|
const evm_submission_1 = require("./evm-submission");
|
|
20
|
+
const prepared_evm_1 = require("./prepared-evm");
|
|
20
21
|
const derivation_paths_1 = require("../utils/derivation-paths");
|
|
21
22
|
const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
|
|
22
23
|
code: 'HARDWARE_SIGNER_UNAVAILABLE',
|
|
@@ -116,14 +117,16 @@ const authenticateReplacementOriginal = (transaction, requestedHash, expectedSen
|
|
|
116
117
|
});
|
|
117
118
|
};
|
|
118
119
|
const assertSynchronousOriginalValidation = (result, message = 'Original transaction validator must be synchronous') => {
|
|
120
|
+
if (result === undefined)
|
|
121
|
+
return;
|
|
119
122
|
if (result !== null &&
|
|
120
123
|
(typeof result === 'object' || typeof result === 'function') &&
|
|
121
124
|
typeof result.then === 'function') {
|
|
122
125
|
// A void callback type also accepts async functions. Decline their pending
|
|
123
126
|
// decision and consume a later rejection without allowing signing to start.
|
|
124
127
|
void Promise.resolve(result).catch(() => undefined);
|
|
125
|
-
throw new Error(message);
|
|
126
128
|
}
|
|
129
|
+
throw new Error(message);
|
|
127
130
|
};
|
|
128
131
|
class EthereumTransactions {
|
|
129
132
|
constructor(getNetwork, getDecryptedPrivateKey, getState, ledgerSigner, trezorSigner, captureSigningContext, captureSubmissionLifecycle) {
|
|
@@ -149,6 +152,14 @@ class EthereumTransactions {
|
|
|
149
152
|
if (type === types_1.KeyringAccountType.Trezor || account?.isTrezorWallet)
|
|
150
153
|
this.requireTrezorSigner();
|
|
151
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
|
+
};
|
|
152
163
|
this.captureEvmSigningContext = (target) => {
|
|
153
164
|
this.assertHardwareSignerAvailable(target);
|
|
154
165
|
const state = this.getState();
|
|
@@ -162,10 +173,10 @@ class EthereumTransactions {
|
|
|
162
173
|
throw new Error('Active account not found');
|
|
163
174
|
const account = { address: selectedAccount.address };
|
|
164
175
|
const network = { ...state.activeNetwork };
|
|
165
|
-
const assertSession = this.
|
|
176
|
+
const assertSession = this.captureSessionGuard();
|
|
166
177
|
const provider = this.web3Provider;
|
|
167
178
|
const assertCurrent = () => {
|
|
168
|
-
assertSession
|
|
179
|
+
assertSession();
|
|
169
180
|
const current = this.getState();
|
|
170
181
|
const currentAccount = current.accounts[activeAccountType]?.[activeAccountId];
|
|
171
182
|
if (current.activeAccountId !== selectedId ||
|
|
@@ -191,17 +202,30 @@ class EthereumTransactions {
|
|
|
191
202
|
assertCurrent,
|
|
192
203
|
};
|
|
193
204
|
};
|
|
194
|
-
this.captureSubmission = (operation, account, type, id, network, assertCurrent, originalHash) =>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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));
|
|
205
229
|
this.signTypedData = async (addr, typedData, version) => {
|
|
206
230
|
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
207
231
|
const { activeAccountType, accounts, activeAccountId } = this.getState();
|
|
@@ -555,35 +579,45 @@ class EthereumTransactions {
|
|
|
555
579
|
throw new Error(`Invalid numeric field "${fieldName}" for EVM tx: ${String(value)}`);
|
|
556
580
|
}
|
|
557
581
|
};
|
|
558
|
-
this.
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
maxPriorityFeePerGas =
|
|
571
|
-
maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas;
|
|
572
|
-
if (!maxFeePerGas || maxPriorityFeePerGas == null) {
|
|
573
|
-
return {
|
|
574
|
-
gasPrice: feeData.gasPrice || (await provider.getGasPrice()),
|
|
575
|
-
isLegacy: true,
|
|
576
|
-
maxFeePerGas: undefined,
|
|
577
|
-
maxPriorityFeePerGas: undefined,
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
return {
|
|
582
|
-
gasPrice: undefined,
|
|
583
|
-
isLegacy: false,
|
|
584
|
-
maxFeePerGas,
|
|
585
|
-
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`);
|
|
586
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 };
|
|
587
621
|
};
|
|
588
622
|
this.getData = ({ contractAddress, receivingAddress, value, }) => {
|
|
589
623
|
const abi = (0, sysweb3_utils_1.getErc20Abi)();
|
|
@@ -674,7 +708,8 @@ class EthereumTransactions {
|
|
|
674
708
|
!/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
|
|
675
709
|
throw new Error('Cancellation fee approval required');
|
|
676
710
|
}
|
|
677
|
-
const
|
|
711
|
+
const { assertCurrentContext, validateOriginal } = options;
|
|
712
|
+
const assertHostContext = () => assertSynchronousOriginalValidation(assertCurrentContext(), 'Transaction context guard must be synchronous');
|
|
678
713
|
assertHostContext();
|
|
679
714
|
const state = this.getState();
|
|
680
715
|
const selectedType = state.activeAccountType;
|
|
@@ -697,9 +732,9 @@ class EthereumTransactions {
|
|
|
697
732
|
if (!previewOnly)
|
|
698
733
|
this.assertHardwareSignerAvailable(targetAccount);
|
|
699
734
|
const provider = this.web3Provider;
|
|
700
|
-
const assertSession = this.
|
|
735
|
+
const assertSession = this.captureSessionGuard();
|
|
701
736
|
const assertKeyringContext = () => {
|
|
702
|
-
assertSession
|
|
737
|
+
assertSession();
|
|
703
738
|
const current = this.getState();
|
|
704
739
|
if (current.activeAccountType !== selectedType ||
|
|
705
740
|
current.activeAccountId !== selectedId ||
|
|
@@ -713,14 +748,15 @@ class EthereumTransactions {
|
|
|
713
748
|
throw new Error('Wallet signing context changed');
|
|
714
749
|
}
|
|
715
750
|
};
|
|
716
|
-
const
|
|
751
|
+
const assertSigningCurrent = () => {
|
|
717
752
|
assertHostContext();
|
|
718
753
|
assertKeyringContext();
|
|
719
754
|
};
|
|
720
|
-
|
|
755
|
+
assertSigningCurrent();
|
|
721
756
|
const submission = previewOnly
|
|
722
757
|
? undefined
|
|
723
|
-
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork,
|
|
758
|
+
: this.captureSubmission('cancel', activeAccount, activeAccountType, activeAccountId, activeNetwork, assertSigningCurrent, txHash);
|
|
759
|
+
const assertCurrent = submission?.assertCurrent ?? assertSigningCurrent;
|
|
724
760
|
const currentEip1559Fees = async () => {
|
|
725
761
|
const feeData = await this.getFeeDataWithDynamicMaxPriorityFeePerGas(provider);
|
|
726
762
|
assertCurrent();
|
|
@@ -750,7 +786,7 @@ class EthereumTransactions {
|
|
|
750
786
|
maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toBigInt(),
|
|
751
787
|
}
|
|
752
788
|
: undefined;
|
|
753
|
-
assertSynchronousOriginalValidation(
|
|
789
|
+
assertSynchronousOriginalValidation(validateOriginal(tx));
|
|
754
790
|
assertCurrent();
|
|
755
791
|
// If transaction not found, create a minimal tx object with current gas prices
|
|
756
792
|
// This handles cases where tx with 0 gas never made it to the mempool
|
|
@@ -817,7 +853,7 @@ class EthereumTransactions {
|
|
|
817
853
|
}
|
|
818
854
|
const maximumFee = ethers_v6_1.BigNumber.from(feePlan.maximumFee);
|
|
819
855
|
assertCurrent();
|
|
820
|
-
if (
|
|
856
|
+
if (!submission)
|
|
821
857
|
return {
|
|
822
858
|
isCanceled: false,
|
|
823
859
|
error: false,
|
|
@@ -880,7 +916,7 @@ class EthereumTransactions {
|
|
|
880
916
|
rawTx: rawTx.replace('0x', ''),
|
|
881
917
|
accountIndex: activeAccountId,
|
|
882
918
|
expectedAddress: activeAccount.address,
|
|
883
|
-
});
|
|
919
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
884
920
|
assertCurrent();
|
|
885
921
|
const formattedSignature = {
|
|
886
922
|
r: `0x${signature.r}`,
|
|
@@ -891,7 +927,7 @@ class EthereumTransactions {
|
|
|
891
927
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
892
928
|
assertCurrent();
|
|
893
929
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
894
|
-
const transactionResponse = await (
|
|
930
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
895
931
|
return {
|
|
896
932
|
isCanceled: true,
|
|
897
933
|
transaction: transactionResponse,
|
|
@@ -953,13 +989,13 @@ class EthereumTransactions {
|
|
|
953
989
|
tx: txFormattedForTrezor,
|
|
954
990
|
index: activeAccountId.toString(),
|
|
955
991
|
slip44: activeNetwork.slip44,
|
|
956
|
-
});
|
|
992
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
957
993
|
assertCurrent();
|
|
958
994
|
if (signature.success) {
|
|
959
995
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
960
996
|
assertCurrent();
|
|
961
997
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
962
|
-
const transactionResponse = await (
|
|
998
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
963
999
|
return {
|
|
964
1000
|
isCanceled: true,
|
|
965
1001
|
transaction: transactionResponse,
|
|
@@ -1028,21 +1064,31 @@ class EthereumTransactions {
|
|
|
1028
1064
|
}
|
|
1029
1065
|
};
|
|
1030
1066
|
//TODO: This function needs to be refactored
|
|
1031
|
-
this.sendFormattedTransaction = async (
|
|
1067
|
+
this.sendFormattedTransaction = async (...args) => {
|
|
1068
|
+
const [params, isLegacy, options] = args;
|
|
1032
1069
|
let broadcastAttempted = false;
|
|
1033
1070
|
try {
|
|
1034
|
-
if (options
|
|
1035
|
-
|
|
1036
|
-
|
|
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');
|
|
1037
1078
|
// Capture once; later caller mutations cannot replace this invocation's
|
|
1038
1079
|
// admission decision while chain checks or device approval are pending.
|
|
1039
|
-
const assertHostCurrent = options
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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();
|
|
1043
1089
|
return await this.sendFormattedTransactionInternal(params, isLegacy, () => {
|
|
1044
1090
|
broadcastAttempted = true;
|
|
1045
|
-
},
|
|
1091
|
+
}, assertApproved, mode);
|
|
1046
1092
|
}
|
|
1047
1093
|
catch (error) {
|
|
1048
1094
|
// A broadcast transport error may mean the node accepted the signed
|
|
@@ -1059,26 +1105,58 @@ class EthereumTransactions {
|
|
|
1059
1105
|
throw submissionError;
|
|
1060
1106
|
}
|
|
1061
1107
|
};
|
|
1062
|
-
this.sendFormattedTransactionInternal = async (
|
|
1108
|
+
this.sendFormattedTransactionInternal = async (input, isLegacy, beforeBroadcast, assertHostCurrent, mode) => {
|
|
1063
1109
|
// Hardware approval may outlive the selected account, network or unlocked
|
|
1064
1110
|
// session. Keep immutable identities and one provider for this invocation.
|
|
1065
1111
|
const { activeAccount, activeAccountType, activeAccountId, activeNetwork, provider, assertCurrent: assertKeyringCurrent, } = this.captureEvmSigningContext();
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
if (result !== undefined) {
|
|
1069
|
-
assertSynchronousOriginalValidation(result, 'Transaction context guard must be synchronous');
|
|
1070
|
-
throw new Error('Transaction context guard must return undefined');
|
|
1071
|
-
}
|
|
1112
|
+
const assertSigningCurrent = () => {
|
|
1113
|
+
assertHostCurrent();
|
|
1072
1114
|
assertKeyringCurrent();
|
|
1073
1115
|
};
|
|
1074
1116
|
const assertBeforeBroadcast = () => {
|
|
1075
1117
|
assertCurrent();
|
|
1076
1118
|
beforeBroadcast();
|
|
1077
1119
|
};
|
|
1078
|
-
|
|
1079
|
-
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
|
+
}
|
|
1080
1157
|
// Detach supplied values before any RPC/device await. Autofill updates only
|
|
1081
1158
|
// this invocation; caller mutations cannot replace already supplied fields.
|
|
1159
|
+
let params = input;
|
|
1082
1160
|
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
1083
1161
|
throw new TypeError('Invalid EVM transaction request');
|
|
1084
1162
|
const supplied = { ...params };
|
|
@@ -1219,7 +1297,7 @@ class EthereumTransactions {
|
|
|
1219
1297
|
rawTx: rawTx.replace('0x', ''),
|
|
1220
1298
|
accountIndex: activeAccountId,
|
|
1221
1299
|
expectedAddress: activeAccount.address,
|
|
1222
|
-
});
|
|
1300
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1223
1301
|
assertCurrent();
|
|
1224
1302
|
const formattedSignature = {
|
|
1225
1303
|
r: `0x${signature.r}`,
|
|
@@ -1230,7 +1308,7 @@ class EthereumTransactions {
|
|
|
1230
1308
|
try {
|
|
1231
1309
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1232
1310
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1233
|
-
const finalTx = await (
|
|
1311
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
|
|
1234
1312
|
return finalTx;
|
|
1235
1313
|
}
|
|
1236
1314
|
catch (error) {
|
|
@@ -1301,7 +1379,7 @@ class EthereumTransactions {
|
|
|
1301
1379
|
tx: txFormattedForTrezor,
|
|
1302
1380
|
coin: activeNetwork.currency,
|
|
1303
1381
|
slip44: activeNetwork.slip44,
|
|
1304
|
-
});
|
|
1382
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1305
1383
|
assertCurrent();
|
|
1306
1384
|
if (signature.success) {
|
|
1307
1385
|
try {
|
|
@@ -1320,7 +1398,7 @@ class EthereumTransactions {
|
|
|
1320
1398
|
};
|
|
1321
1399
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1322
1400
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1323
|
-
const finalTx = await (
|
|
1401
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1324
1402
|
return finalTx;
|
|
1325
1403
|
}
|
|
1326
1404
|
catch (error) {
|
|
@@ -1364,35 +1442,37 @@ class EthereumTransactions {
|
|
|
1364
1442
|
return await sendEVMTransaction();
|
|
1365
1443
|
}
|
|
1366
1444
|
};
|
|
1367
|
-
this.sendTransactionWithEditedFee = async (txHash, isLegacy
|
|
1368
|
-
const
|
|
1369
|
-
const
|
|
1370
|
-
if (!previewOnly)
|
|
1371
|
-
this.assertHardwareSignerAvailable(options.targetAccount);
|
|
1445
|
+
this.sendTransactionWithEditedFee = async (txHash, isLegacy, options) => {
|
|
1446
|
+
const previewOnly = options?.previewOnly === true;
|
|
1447
|
+
const approvedMaximumFee = options?.approvedMaximumFee;
|
|
1372
1448
|
if (!previewOnly &&
|
|
1373
1449
|
(typeof approvedMaximumFee !== 'string' ||
|
|
1374
|
-
|
|
1450
|
+
!/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee)))
|
|
1375
1451
|
throw new Error('Replacement fee approval required');
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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?.();
|
|
1384
1464
|
};
|
|
1385
1465
|
const assertBeforeBroadcast = () => {
|
|
1386
|
-
|
|
1387
|
-
beforeBroadcast?.();
|
|
1388
|
-
|
|
1466
|
+
assertSigningCurrent();
|
|
1467
|
+
assertSynchronousOriginalValidation(beforeBroadcast?.(), 'Transaction broadcast guard must be synchronous');
|
|
1468
|
+
assertSigningCurrent();
|
|
1389
1469
|
};
|
|
1390
1470
|
const assertSignedSender = (signedTx) => {
|
|
1391
1471
|
if (transaction_1.Transaction.from(signedTx).from?.toLowerCase() !== expectedSender) {
|
|
1392
1472
|
throw new Error('Replacement signature does not match account');
|
|
1393
1473
|
}
|
|
1394
1474
|
};
|
|
1395
|
-
|
|
1475
|
+
assertSigningCurrent();
|
|
1396
1476
|
const state = this.getState();
|
|
1397
1477
|
const activeAccountType = options.targetAccount?.type ?? state.activeAccountType;
|
|
1398
1478
|
const activeAccountId = options.targetAccount?.id ?? state.activeAccountId;
|
|
@@ -1408,14 +1488,16 @@ class EthereumTransactions {
|
|
|
1408
1488
|
throw new Error('Replacement signing account is unavailable');
|
|
1409
1489
|
}
|
|
1410
1490
|
if (!previewOnly)
|
|
1411
|
-
|
|
1491
|
+
assertCapturedSignerCurrent =
|
|
1492
|
+
this.captureEvmSigningContext(targetAccount).assertCurrent;
|
|
1412
1493
|
const expectedSender = activeAccount.address.toLowerCase();
|
|
1413
1494
|
const requestedHash = txHash.toLowerCase();
|
|
1414
1495
|
const provider = this.web3Provider;
|
|
1415
|
-
|
|
1496
|
+
assertSigningCurrent();
|
|
1416
1497
|
const submission = previewOnly
|
|
1417
1498
|
? undefined
|
|
1418
|
-
: 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;
|
|
1419
1501
|
const validateLookup = (transaction) => {
|
|
1420
1502
|
assertCurrent();
|
|
1421
1503
|
if (!transaction)
|
|
@@ -1429,7 +1511,7 @@ class EthereumTransactions {
|
|
|
1429
1511
|
activeAccountType === types_1.KeyringAccountType.Trezor) {
|
|
1430
1512
|
throw new Error('Trezor does not support type 1 replacement signing');
|
|
1431
1513
|
}
|
|
1432
|
-
assertSynchronousOriginalValidation(validateOriginal
|
|
1514
|
+
assertSynchronousOriginalValidation(validateOriginal(authenticated));
|
|
1433
1515
|
assertCurrent();
|
|
1434
1516
|
return authenticated;
|
|
1435
1517
|
};
|
|
@@ -1528,7 +1610,7 @@ class EthereumTransactions {
|
|
|
1528
1610
|
maxPriorityFeePerGas: newGasValues.maxPriorityFeePerGas,
|
|
1529
1611
|
}),
|
|
1530
1612
|
};
|
|
1531
|
-
if (
|
|
1613
|
+
if (!submission)
|
|
1532
1614
|
return { isSpeedUp: false, maximumFee: maximumFee.toString() };
|
|
1533
1615
|
if (maximumFee.gt(approvedMaximumFee))
|
|
1534
1616
|
throw new Error('Replacement fee exceeds approval');
|
|
@@ -1559,7 +1641,7 @@ class EthereumTransactions {
|
|
|
1559
1641
|
rawTx: rawTx.replace('0x', ''),
|
|
1560
1642
|
accountIndex: activeAccountId,
|
|
1561
1643
|
expectedAddress: activeAccount.address,
|
|
1562
|
-
});
|
|
1644
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1563
1645
|
const formattedSignature = {
|
|
1564
1646
|
r: `0x${signature.r}`,
|
|
1565
1647
|
s: `0x${signature.s}`,
|
|
@@ -1569,7 +1651,7 @@ class EthereumTransactions {
|
|
|
1569
1651
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1570
1652
|
assertSignedSender(signedTx);
|
|
1571
1653
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1572
|
-
const transactionResponse = await (
|
|
1654
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, rawTx);
|
|
1573
1655
|
return {
|
|
1574
1656
|
isSpeedUp: true,
|
|
1575
1657
|
transaction: transactionResponse,
|
|
@@ -1645,12 +1727,12 @@ class EthereumTransactions {
|
|
|
1645
1727
|
tx: txFormattedForTrezor,
|
|
1646
1728
|
index: activeAccountId.toString(),
|
|
1647
1729
|
slip44: activeNetwork.slip44,
|
|
1648
|
-
});
|
|
1730
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent));
|
|
1649
1731
|
if (signature.success) {
|
|
1650
1732
|
const signedTx = serializeTransaction(txFormattedForTrezor, trezorSignature(signature.payload));
|
|
1651
1733
|
assertSignedSender(signedTx);
|
|
1652
1734
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1653
|
-
const transactionResponse = await (
|
|
1735
|
+
const transactionResponse = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, assertBeforeBroadcast, expectedUnsignedTransaction);
|
|
1654
1736
|
return {
|
|
1655
1737
|
isSpeedUp: true,
|
|
1656
1738
|
transaction: transactionResponse,
|
|
@@ -1735,44 +1817,51 @@ class EthereumTransactions {
|
|
|
1735
1817
|
return await speedUpWithPrivateKey();
|
|
1736
1818
|
}
|
|
1737
1819
|
};
|
|
1738
|
-
this.sendSignedErc20Transaction = async (
|
|
1739
|
-
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();
|
|
1740
1824
|
const activeAccountAddress = activeAccount.address;
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
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;
|
|
1750
1840
|
const sendERC20Token = async () => {
|
|
1751
1841
|
assertCurrent();
|
|
1752
1842
|
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1753
1843
|
try {
|
|
1754
1844
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
1755
|
-
// Preserve zero-decimal tokens: use provided decimals when defined (including 0).
|
|
1756
|
-
const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1757
|
-
const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
|
|
1758
1845
|
let transferMethod;
|
|
1759
1846
|
if (isLegacy) {
|
|
1760
1847
|
const overrides = {
|
|
1761
1848
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1762
1849
|
gasPrice,
|
|
1763
|
-
|
|
1850
|
+
gasLimit,
|
|
1764
1851
|
type: 0, // Explicitly set Type 0 for legacy token transfers
|
|
1765
1852
|
};
|
|
1766
|
-
|
|
1853
|
+
assertCurrent();
|
|
1854
|
+
transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
|
|
1767
1855
|
}
|
|
1768
1856
|
else {
|
|
1769
1857
|
const overrides = {
|
|
1770
1858
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1771
1859
|
maxPriorityFeePerGas,
|
|
1772
1860
|
maxFeePerGas,
|
|
1773
|
-
|
|
1861
|
+
gasLimit,
|
|
1774
1862
|
};
|
|
1775
|
-
|
|
1863
|
+
assertCurrent();
|
|
1864
|
+
transferMethod = await _contract.transfer.populateTransaction(receiver, exactTokenAmount, normalizeEthersOverrides(overrides));
|
|
1776
1865
|
}
|
|
1777
1866
|
return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
|
|
1778
1867
|
...transferMethod,
|
|
@@ -1791,20 +1880,16 @@ class EthereumTransactions {
|
|
|
1791
1880
|
assertCurrent();
|
|
1792
1881
|
try {
|
|
1793
1882
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
1794
|
-
const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1795
|
-
const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
|
|
1796
1883
|
const txData = _contract.interface.encodeFunctionData('transfer', [
|
|
1797
1884
|
receiver,
|
|
1798
|
-
|
|
1885
|
+
exactTokenAmount,
|
|
1799
1886
|
]);
|
|
1800
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
1801
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
|
|
1802
1887
|
let txFormattedForEthers;
|
|
1803
1888
|
if (isLegacy) {
|
|
1804
1889
|
txFormattedForEthers = {
|
|
1805
1890
|
to: tokenAddress,
|
|
1806
1891
|
value: '0x0',
|
|
1807
|
-
gasLimit:
|
|
1892
|
+
gasLimit: gasLimit,
|
|
1808
1893
|
gasPrice,
|
|
1809
1894
|
data: txData,
|
|
1810
1895
|
nonce: transactionNonce,
|
|
@@ -1816,7 +1901,7 @@ class EthereumTransactions {
|
|
|
1816
1901
|
txFormattedForEthers = {
|
|
1817
1902
|
to: tokenAddress,
|
|
1818
1903
|
value: '0x0',
|
|
1819
|
-
gasLimit:
|
|
1904
|
+
gasLimit: gasLimit,
|
|
1820
1905
|
maxFeePerGas,
|
|
1821
1906
|
maxPriorityFeePerGas,
|
|
1822
1907
|
data: txData,
|
|
@@ -1830,7 +1915,7 @@ class EthereumTransactions {
|
|
|
1830
1915
|
rawTx: rawTx.replace('0x', ''),
|
|
1831
1916
|
accountIndex: activeAccountId,
|
|
1832
1917
|
expectedAddress: activeAccount.address,
|
|
1833
|
-
});
|
|
1918
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
1834
1919
|
assertCurrent();
|
|
1835
1920
|
const formattedSignature = {
|
|
1836
1921
|
r: `0x${signature.r}`,
|
|
@@ -1842,7 +1927,7 @@ class EthereumTransactions {
|
|
|
1842
1927
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
1843
1928
|
assertCurrent();
|
|
1844
1929
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1845
|
-
const finalTx = await (
|
|
1930
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
|
|
1846
1931
|
const response = await provider.sendTransaction(raw);
|
|
1847
1932
|
saveTrezorTx && saveTrezorTx(response);
|
|
1848
1933
|
return response;
|
|
@@ -1867,20 +1952,16 @@ class EthereumTransactions {
|
|
|
1867
1952
|
assertCurrent();
|
|
1868
1953
|
try {
|
|
1869
1954
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc20Abi)(), provider);
|
|
1870
|
-
const resolvedDecimals = decimals === undefined || decimals === null ? 18 : Number(decimals);
|
|
1871
|
-
const calculatedTokenAmount = (0, ethers_v6_1.parseUnits)(tokenAmount, resolvedDecimals).toBigInt();
|
|
1872
1955
|
const txData = _contract.interface.encodeFunctionData('transfer', [
|
|
1873
1956
|
receiver,
|
|
1874
|
-
|
|
1957
|
+
exactTokenAmount,
|
|
1875
1958
|
]);
|
|
1876
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
1877
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('100000'); // ERC20 fallback
|
|
1878
1959
|
let txToBeSignedByTrezor;
|
|
1879
1960
|
if (isLegacy) {
|
|
1880
1961
|
txToBeSignedByTrezor = {
|
|
1881
1962
|
to: tokenAddress,
|
|
1882
1963
|
value: '0x0',
|
|
1883
|
-
gasLimit: this.toHex0x(
|
|
1964
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1884
1965
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
1885
1966
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
1886
1967
|
chainId: activeNetwork.chainId,
|
|
@@ -1891,7 +1972,7 @@ class EthereumTransactions {
|
|
|
1891
1972
|
txToBeSignedByTrezor = {
|
|
1892
1973
|
to: tokenAddress,
|
|
1893
1974
|
value: '0x0',
|
|
1894
|
-
gasLimit: this.toHex0x(
|
|
1975
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
1895
1976
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
1896
1977
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
1897
1978
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
@@ -1909,7 +1990,7 @@ class EthereumTransactions {
|
|
|
1909
1990
|
tx: txToBeSignedByTrezor,
|
|
1910
1991
|
coin: activeNetwork.currency,
|
|
1911
1992
|
slip44: activeNetwork.slip44,
|
|
1912
|
-
});
|
|
1993
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
1913
1994
|
assertCurrent();
|
|
1914
1995
|
if (signature.success) {
|
|
1915
1996
|
try {
|
|
@@ -1918,7 +1999,7 @@ class EthereumTransactions {
|
|
|
1918
1999
|
txFormattedForEthers = {
|
|
1919
2000
|
to: tokenAddress,
|
|
1920
2001
|
value: '0x0',
|
|
1921
|
-
gasLimit:
|
|
2002
|
+
gasLimit: gasLimit,
|
|
1922
2003
|
gasPrice,
|
|
1923
2004
|
data: txData,
|
|
1924
2005
|
nonce: transactionNonce,
|
|
@@ -1930,7 +2011,7 @@ class EthereumTransactions {
|
|
|
1930
2011
|
txFormattedForEthers = {
|
|
1931
2012
|
to: tokenAddress,
|
|
1932
2013
|
value: '0x0',
|
|
1933
|
-
gasLimit:
|
|
2014
|
+
gasLimit: gasLimit,
|
|
1934
2015
|
maxFeePerGas,
|
|
1935
2016
|
maxPriorityFeePerGas,
|
|
1936
2017
|
data: txData,
|
|
@@ -1942,7 +2023,7 @@ class EthereumTransactions {
|
|
|
1942
2023
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
1943
2024
|
assertCurrent();
|
|
1944
2025
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
1945
|
-
const finalTx = await (
|
|
2026
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, async (raw) => {
|
|
1946
2027
|
const response = await provider.sendTransaction(raw);
|
|
1947
2028
|
saveTrezorTx && saveTrezorTx(response);
|
|
1948
2029
|
return response;
|
|
@@ -1970,22 +2051,26 @@ class EthereumTransactions {
|
|
|
1970
2051
|
return (await sendERC20Token());
|
|
1971
2052
|
}
|
|
1972
2053
|
};
|
|
1973
|
-
this.sendSignedErc721Transaction = async (
|
|
1974
|
-
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');
|
|
1975
2068
|
const activeAccountAddress = activeAccount.address;
|
|
1976
|
-
const submission = this.captureSubmission('erc721', activeAccount, activeAccountType, activeAccountId, activeNetwork,
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
await this.resolveEvmFeeParams({
|
|
1982
|
-
gasPrice,
|
|
1983
|
-
isLegacy,
|
|
1984
|
-
maxFeePerGas,
|
|
1985
|
-
maxPriorityFeePerGas,
|
|
1986
|
-
}, provider));
|
|
1987
|
-
assertCurrent();
|
|
1988
|
-
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;
|
|
1989
2074
|
const sendERC721Token = async () => {
|
|
1990
2075
|
assertCurrent();
|
|
1991
2076
|
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
@@ -1996,7 +2081,7 @@ class EthereumTransactions {
|
|
|
1996
2081
|
const overrides = {
|
|
1997
2082
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
1998
2083
|
gasPrice,
|
|
1999
|
-
|
|
2084
|
+
gasLimit,
|
|
2000
2085
|
type: 0, // Explicitly set Type 0 for legacy NFT transfers
|
|
2001
2086
|
};
|
|
2002
2087
|
transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
|
|
@@ -2006,7 +2091,7 @@ class EthereumTransactions {
|
|
|
2006
2091
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2007
2092
|
maxPriorityFeePerGas,
|
|
2008
2093
|
maxFeePerGas,
|
|
2009
|
-
|
|
2094
|
+
gasLimit,
|
|
2010
2095
|
};
|
|
2011
2096
|
transferMethod = await _contract.transferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, normalizeEthersOverrides(overrides));
|
|
2012
2097
|
}
|
|
@@ -2032,14 +2117,12 @@ class EthereumTransactions {
|
|
|
2032
2117
|
receiver,
|
|
2033
2118
|
normalizedTokenId,
|
|
2034
2119
|
]);
|
|
2035
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2036
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
|
|
2037
2120
|
let txFormattedForEthers;
|
|
2038
2121
|
if (isLegacy) {
|
|
2039
2122
|
txFormattedForEthers = {
|
|
2040
2123
|
to: tokenAddress,
|
|
2041
2124
|
value: '0x0',
|
|
2042
|
-
gasLimit
|
|
2125
|
+
gasLimit,
|
|
2043
2126
|
gasPrice,
|
|
2044
2127
|
data: txData,
|
|
2045
2128
|
nonce: transactionNonce,
|
|
@@ -2051,7 +2134,7 @@ class EthereumTransactions {
|
|
|
2051
2134
|
txFormattedForEthers = {
|
|
2052
2135
|
to: tokenAddress,
|
|
2053
2136
|
value: '0x0',
|
|
2054
|
-
gasLimit
|
|
2137
|
+
gasLimit,
|
|
2055
2138
|
maxFeePerGas,
|
|
2056
2139
|
maxPriorityFeePerGas,
|
|
2057
2140
|
data: txData,
|
|
@@ -2065,7 +2148,7 @@ class EthereumTransactions {
|
|
|
2065
2148
|
rawTx: rawTx.replace('0x', ''),
|
|
2066
2149
|
accountIndex: activeAccountId,
|
|
2067
2150
|
expectedAddress: activeAccount.address,
|
|
2068
|
-
});
|
|
2151
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2069
2152
|
assertCurrent();
|
|
2070
2153
|
const formattedSignature = {
|
|
2071
2154
|
r: `0x${signature.r}`,
|
|
@@ -2077,7 +2160,7 @@ class EthereumTransactions {
|
|
|
2077
2160
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2078
2161
|
assertCurrent();
|
|
2079
2162
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2080
|
-
const finalTx = await (
|
|
2163
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
2081
2164
|
return finalTx;
|
|
2082
2165
|
}
|
|
2083
2166
|
catch (error) {
|
|
@@ -2103,14 +2186,12 @@ class EthereumTransactions {
|
|
|
2103
2186
|
receiver,
|
|
2104
2187
|
normalizedTokenId,
|
|
2105
2188
|
]);
|
|
2106
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2107
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('150000'); // ERC721 fallback
|
|
2108
2189
|
let txToBeSignedByTrezor;
|
|
2109
2190
|
if (isLegacy) {
|
|
2110
2191
|
txToBeSignedByTrezor = {
|
|
2111
2192
|
to: tokenAddress,
|
|
2112
2193
|
value: '0x0',
|
|
2113
|
-
gasLimit: this.toHex0x(
|
|
2194
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2114
2195
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2115
2196
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2116
2197
|
chainId: activeNetwork.chainId,
|
|
@@ -2122,7 +2203,7 @@ class EthereumTransactions {
|
|
|
2122
2203
|
txToBeSignedByTrezor = {
|
|
2123
2204
|
to: tokenAddress,
|
|
2124
2205
|
value: '0x0',
|
|
2125
|
-
gasLimit: this.toHex0x(
|
|
2206
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2126
2207
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2127
2208
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2128
2209
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
@@ -2142,7 +2223,7 @@ class EthereumTransactions {
|
|
|
2142
2223
|
tx: txToBeSignedByTrezor,
|
|
2143
2224
|
coin: trezorCoin,
|
|
2144
2225
|
slip44: activeNetwork.slip44,
|
|
2145
|
-
});
|
|
2226
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2146
2227
|
assertCurrent();
|
|
2147
2228
|
if (signature.success) {
|
|
2148
2229
|
try {
|
|
@@ -2151,7 +2232,7 @@ class EthereumTransactions {
|
|
|
2151
2232
|
txFormattedForEthers = {
|
|
2152
2233
|
to: tokenAddress,
|
|
2153
2234
|
value: '0x0',
|
|
2154
|
-
gasLimit
|
|
2235
|
+
gasLimit,
|
|
2155
2236
|
gasPrice,
|
|
2156
2237
|
data: txData,
|
|
2157
2238
|
nonce: transactionNonce,
|
|
@@ -2163,7 +2244,7 @@ class EthereumTransactions {
|
|
|
2163
2244
|
txFormattedForEthers = {
|
|
2164
2245
|
to: tokenAddress,
|
|
2165
2246
|
value: '0x0',
|
|
2166
|
-
gasLimit
|
|
2247
|
+
gasLimit,
|
|
2167
2248
|
maxFeePerGas,
|
|
2168
2249
|
maxPriorityFeePerGas,
|
|
2169
2250
|
data: txData,
|
|
@@ -2175,7 +2256,7 @@ class EthereumTransactions {
|
|
|
2175
2256
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2176
2257
|
assertCurrent();
|
|
2177
2258
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2178
|
-
const finalTx = await (
|
|
2259
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
2179
2260
|
return finalTx;
|
|
2180
2261
|
}
|
|
2181
2262
|
catch (error) {
|
|
@@ -2201,18 +2282,29 @@ class EthereumTransactions {
|
|
|
2201
2282
|
return (await sendERC721Token());
|
|
2202
2283
|
}
|
|
2203
2284
|
};
|
|
2204
|
-
this.sendSignedErc1155Transaction = async (
|
|
2205
|
-
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');
|
|
2206
2302
|
const activeAccountAddress = activeAccount.address;
|
|
2207
|
-
const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork,
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
maxFeePerGas,
|
|
2213
|
-
maxPriorityFeePerGas,
|
|
2214
|
-
}, provider));
|
|
2215
|
-
assertCurrent();
|
|
2303
|
+
const submission = this.captureSubmission('erc1155', activeAccount, activeAccountType, activeAccountId, activeNetwork, () => {
|
|
2304
|
+
assertApproval();
|
|
2305
|
+
assertSigningCurrent();
|
|
2306
|
+
});
|
|
2307
|
+
const assertCurrent = submission.assertCurrent;
|
|
2216
2308
|
const sendERC1155Token = async () => {
|
|
2217
2309
|
assertCurrent();
|
|
2218
2310
|
const { decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
@@ -2220,13 +2312,12 @@ class EthereumTransactions {
|
|
|
2220
2312
|
try {
|
|
2221
2313
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
2222
2314
|
// Use BigNumber to avoid JS number overflow/precision loss
|
|
2223
|
-
const amount = ethers_v6_1.BigNumber.from(tokenAmount ?? '1');
|
|
2224
2315
|
let overrides;
|
|
2225
2316
|
if (isLegacy) {
|
|
2226
2317
|
overrides = {
|
|
2227
2318
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2228
2319
|
gasPrice,
|
|
2229
|
-
|
|
2320
|
+
gasLimit,
|
|
2230
2321
|
type: 0, // Explicitly set Type 0 for legacy ERC1155 transfers
|
|
2231
2322
|
};
|
|
2232
2323
|
}
|
|
@@ -2235,10 +2326,10 @@ class EthereumTransactions {
|
|
|
2235
2326
|
nonce: await provider.getTransactionCount(activeAccountAddress, 'pending'),
|
|
2236
2327
|
maxPriorityFeePerGas,
|
|
2237
2328
|
maxFeePerGas,
|
|
2238
|
-
|
|
2329
|
+
gasLimit,
|
|
2239
2330
|
};
|
|
2240
2331
|
}
|
|
2241
|
-
transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver,
|
|
2332
|
+
transferMethod = await _contract.safeTransferFrom.populateTransaction(activeAccountAddress, receiver, normalizedTokenId, amount, '0x', normalizeEthersOverrides(overrides));
|
|
2242
2333
|
return await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, {
|
|
2243
2334
|
...transferMethod,
|
|
2244
2335
|
chainId: activeNetwork.chainId,
|
|
@@ -2256,16 +2347,13 @@ class EthereumTransactions {
|
|
|
2256
2347
|
assertCurrent();
|
|
2257
2348
|
try {
|
|
2258
2349
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
2259
|
-
const
|
|
2260
|
-
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
|
|
2261
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2262
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
|
|
2350
|
+
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
|
|
2263
2351
|
let txFormattedForEthers;
|
|
2264
2352
|
if (isLegacy) {
|
|
2265
2353
|
txFormattedForEthers = {
|
|
2266
2354
|
to: tokenAddress,
|
|
2267
2355
|
value: '0x0',
|
|
2268
|
-
gasLimit
|
|
2356
|
+
gasLimit,
|
|
2269
2357
|
gasPrice,
|
|
2270
2358
|
data: txData,
|
|
2271
2359
|
nonce: transactionNonce,
|
|
@@ -2277,7 +2365,7 @@ class EthereumTransactions {
|
|
|
2277
2365
|
txFormattedForEthers = {
|
|
2278
2366
|
to: tokenAddress,
|
|
2279
2367
|
value: '0x0',
|
|
2280
|
-
gasLimit
|
|
2368
|
+
gasLimit,
|
|
2281
2369
|
maxFeePerGas,
|
|
2282
2370
|
maxPriorityFeePerGas,
|
|
2283
2371
|
data: txData,
|
|
@@ -2291,7 +2379,7 @@ class EthereumTransactions {
|
|
|
2291
2379
|
rawTx: rawTx.replace('0x', ''),
|
|
2292
2380
|
accountIndex: activeAccountId,
|
|
2293
2381
|
expectedAddress: activeAccount.address,
|
|
2294
|
-
});
|
|
2382
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2295
2383
|
assertCurrent();
|
|
2296
2384
|
const formattedSignature = {
|
|
2297
2385
|
r: `0x${signature.r}`,
|
|
@@ -2303,7 +2391,7 @@ class EthereumTransactions {
|
|
|
2303
2391
|
const signedTx = serializeTransaction(txFormattedForEthers, formattedSignature);
|
|
2304
2392
|
assertCurrent();
|
|
2305
2393
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2306
|
-
const finalTx = await (
|
|
2394
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, rawTx);
|
|
2307
2395
|
return finalTx;
|
|
2308
2396
|
}
|
|
2309
2397
|
catch (error) {
|
|
@@ -2324,16 +2412,13 @@ class EthereumTransactions {
|
|
|
2324
2412
|
assertCurrent();
|
|
2325
2413
|
try {
|
|
2326
2414
|
const _contract = new ethers_v6_1.Contract(tokenAddress, (0, sysweb3_utils_1.getErc55Abi)(), provider);
|
|
2327
|
-
const
|
|
2328
|
-
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, tokenId, amount.toBigInt(), '0x']);
|
|
2329
|
-
// Use fallback gas limit if not provided (for auto-estimation)
|
|
2330
|
-
const effectiveGasLimit = gasLimit || this.toBigNumber('200000'); // ERC1155 fallback
|
|
2415
|
+
const txData = _contract.interface.encodeFunctionData('safeTransferFrom', [activeAccountAddress, receiver, normalizedTokenId, amount, '0x']);
|
|
2331
2416
|
let txToBeSignedByTrezor;
|
|
2332
2417
|
if (isLegacy) {
|
|
2333
2418
|
txToBeSignedByTrezor = {
|
|
2334
2419
|
to: tokenAddress,
|
|
2335
2420
|
value: '0x0',
|
|
2336
|
-
gasLimit: this.toHex0x(
|
|
2421
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2337
2422
|
gasPrice: this.toHex0x(gasPrice, 'gasPrice'),
|
|
2338
2423
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
2339
2424
|
chainId: activeNetwork.chainId,
|
|
@@ -2344,7 +2429,7 @@ class EthereumTransactions {
|
|
|
2344
2429
|
txToBeSignedByTrezor = {
|
|
2345
2430
|
to: tokenAddress,
|
|
2346
2431
|
value: '0x0',
|
|
2347
|
-
gasLimit: this.toHex0x(
|
|
2432
|
+
gasLimit: this.toHex0x(gasLimit, 'gasLimit'),
|
|
2348
2433
|
maxFeePerGas: this.toHex0x(maxFeePerGas, 'maxFeePerGas'),
|
|
2349
2434
|
maxPriorityFeePerGas: this.toHex0x(maxPriorityFeePerGas, 'maxPriorityFeePerGas'),
|
|
2350
2435
|
nonce: this.toBigNumber(transactionNonce)._hex,
|
|
@@ -2362,7 +2447,7 @@ class EthereumTransactions {
|
|
|
2362
2447
|
tx: txToBeSignedByTrezor,
|
|
2363
2448
|
coin: activeNetwork.currency,
|
|
2364
2449
|
slip44: activeNetwork.slip44,
|
|
2365
|
-
});
|
|
2450
|
+
}, await this.authorizeHardwareTransaction(provider, assertCurrent, signal));
|
|
2366
2451
|
assertCurrent();
|
|
2367
2452
|
if (signature.success) {
|
|
2368
2453
|
try {
|
|
@@ -2371,7 +2456,7 @@ class EthereumTransactions {
|
|
|
2371
2456
|
txFormattedForEthers = {
|
|
2372
2457
|
to: tokenAddress,
|
|
2373
2458
|
value: '0x0',
|
|
2374
|
-
gasLimit
|
|
2459
|
+
gasLimit,
|
|
2375
2460
|
gasPrice,
|
|
2376
2461
|
data: txData,
|
|
2377
2462
|
nonce: transactionNonce,
|
|
@@ -2383,7 +2468,7 @@ class EthereumTransactions {
|
|
|
2383
2468
|
txFormattedForEthers = {
|
|
2384
2469
|
to: tokenAddress,
|
|
2385
2470
|
value: '0x0',
|
|
2386
|
-
gasLimit
|
|
2471
|
+
gasLimit,
|
|
2387
2472
|
maxFeePerGas,
|
|
2388
2473
|
maxPriorityFeePerGas,
|
|
2389
2474
|
data: txData,
|
|
@@ -2395,7 +2480,7 @@ class EthereumTransactions {
|
|
|
2395
2480
|
const signedTx = serializeTransaction(txFormattedForEthers, trezorSignature(signature.payload));
|
|
2396
2481
|
assertCurrent();
|
|
2397
2482
|
assertHardwareTransactionSender(signedTx, activeAccount.address);
|
|
2398
|
-
const finalTx = await (
|
|
2483
|
+
const finalTx = await this.submitSignedHardwareTransaction(provider, signedTx, (raw) => provider.sendTransaction(raw), submission, undefined, expectedUnsignedTransaction);
|
|
2399
2484
|
return finalTx;
|
|
2400
2485
|
}
|
|
2401
2486
|
catch (error) {
|
|
@@ -2595,6 +2680,11 @@ class EthereumTransactions {
|
|
|
2595
2680
|
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
|
|
2596
2681
|
};
|
|
2597
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');
|
|
2598
2688
|
this.getNetwork = getNetwork;
|
|
2599
2689
|
this.getDecryptedPrivateKey = (targetAccount) => {
|
|
2600
2690
|
this.assertHardwareSignerAvailable(targetAccount);
|
|
@@ -2621,11 +2711,13 @@ class EthereumTransactions {
|
|
|
2621
2711
|
// RPC/network than `activeNetwork`, nodes will reject broadcasts with chain-id mismatch errors.
|
|
2622
2712
|
try {
|
|
2623
2713
|
const currentNetwork = this.getNetwork();
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
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;
|
|
2629
2721
|
if (needsInit) {
|
|
2630
2722
|
this.setWeb3Provider(currentNetwork);
|
|
2631
2723
|
}
|
|
@@ -2636,31 +2728,16 @@ class EthereumTransactions {
|
|
|
2636
2728
|
console.log('[EthereumTransactions] Deferring provider initialization:', error?.message || error);
|
|
2637
2729
|
}
|
|
2638
2730
|
}
|
|
2639
|
-
// Helper method to detect UTXO networks
|
|
2640
|
-
isUtxoNetwork(network) {
|
|
2641
|
-
// Generic UTXO network detection patterns:
|
|
2642
|
-
// 1. URL contains blockbook or trezor (most reliable)
|
|
2643
|
-
// 2. Network kind is explicitly set to 'syscoin'
|
|
2644
|
-
const hasBlockbookUrl = !!(network.url?.includes('blockbook') || network.url?.includes('trezor'));
|
|
2645
|
-
const hasUtxoKind = network.kind === sysweb3_network_1.INetworkType.Syscoin;
|
|
2646
|
-
return hasBlockbookUrl || hasUtxoKind;
|
|
2647
|
-
}
|
|
2648
2731
|
setWeb3Provider(network) {
|
|
2649
2732
|
this.abortController.abort();
|
|
2650
2733
|
this.abortController = new AbortController();
|
|
2651
|
-
|
|
2652
|
-
const isUtxoNetwork = this.isUtxoNetwork(network);
|
|
2653
|
-
if (isUtxoNetwork) {
|
|
2654
|
-
// For UTXO networks, don't create web3 providers at all since they won't be used
|
|
2655
|
-
console.log('[EthereumTransactions] setWeb3Provider: Skipping web3Provider creation for UTXO network:', network.url);
|
|
2656
|
-
// Clear any existing providers for UTXO networks
|
|
2734
|
+
if (network?.kind !== sysweb3_network_1.INetworkType.Ethereum) {
|
|
2657
2735
|
this._web3Provider = undefined;
|
|
2658
2736
|
this._web3ProviderKey = undefined;
|
|
2737
|
+
return;
|
|
2659
2738
|
}
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
|
|
2663
|
-
}
|
|
2739
|
+
this._web3Provider = new providers_1.CustomJsonRpcProvider(this.abortController.signal, network.url, network.chainId);
|
|
2740
|
+
this._web3ProviderKey = `${network.chainId}|${network.url ?? ''}`;
|
|
2664
2741
|
}
|
|
2665
2742
|
// Method to configure gas overrides for networks with validation issues
|
|
2666
2743
|
setGasOverrides(overrides) {
|