@sidhujag/sysweb3-keyring 1.0.626 → 1.0.628
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 +20 -0
- package/cjs/keyring-core.js +2512 -0
- package/cjs/keyring-core.js.map +1 -0
- package/cjs/keyring-manager.js +23 -2437
- package/cjs/keyring-manager.js.map +1 -1
- package/cjs/software.js +58 -0
- package/cjs/software.js.map +1 -0
- package/cjs/storage-core.js +227 -0
- package/cjs/storage-core.js.map +1 -0
- package/cjs/storage.js +15 -210
- package/cjs/storage.js.map +1 -1
- package/cjs/transactions/ethereum.js +121 -29
- package/cjs/transactions/ethereum.js.map +1 -1
- package/cjs/transactions/evm-local-signer.js +11 -1
- package/cjs/transactions/evm-local-signer.js.map +1 -1
- package/cjs/transactions/hardware-psbt.js +167 -0
- package/cjs/transactions/hardware-psbt.js.map +1 -0
- package/cjs/transactions/syscoin-core.js +651 -0
- package/cjs/transactions/syscoin-core.js.map +1 -0
- package/cjs/transactions/syscoin.js +8 -768
- package/cjs/transactions/syscoin.js.map +1 -1
- package/cjs/types.js.map +1 -1
- package/package.json +38 -4
- package/types/keyring-core.d.ts +257 -0
- package/types/keyring-manager.d.ts +5 -223
- package/types/software.d.ts +23 -0
- package/types/storage-core.d.ts +22 -0
- package/types/storage.d.ts +1 -4
- package/types/transactions/ethereum.d.ts +16 -6
- package/types/transactions/evm-local-signer.d.ts +3 -1
- package/types/transactions/hardware-psbt.d.ts +7 -0
- package/types/transactions/syscoin-core.d.ts +92 -0
- package/types/transactions/syscoin.d.ts +8 -67
- package/types/types.d.ts +10 -0
- package/cjs/transactions/__tests__/integration.test.js +0 -237
- package/cjs/transactions/__tests__/integration.test.js.map +0 -1
- package/cjs/transactions/__tests__/syscoin.test.js +0 -361
- package/cjs/transactions/__tests__/syscoin.test.js.map +0 -1
- package/types/transactions/__tests__/integration.test.d.ts +0 -1
- package/types/transactions/__tests__/syscoin.test.d.ts +0 -1
package/cjs/storage.js
CHANGED
|
@@ -1,215 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
3
|
+
exports.VaultAuthenticationError = exports.getDecryptedVault = exports.setEncryptedVault = void 0;
|
|
7
4
|
const sysweb3_core_1 = require("@sidhujag/sysweb3-core");
|
|
8
|
-
const
|
|
5
|
+
const storage_core_1 = require("./storage-core");
|
|
6
|
+
// Preserve the legacy process-wide storage authority. Host-isolated consumers
|
|
7
|
+
// import storage-core directly and supply their own asynchronous storage port.
|
|
9
8
|
const storage = sysweb3_core_1.sysweb3Di.getStateStorageDb();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
async
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
await oldMutex;
|
|
22
|
-
try {
|
|
23
|
-
return await callback();
|
|
24
|
-
}
|
|
25
|
-
finally {
|
|
26
|
-
release();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const vaultMutex = new AsyncMutex();
|
|
31
|
-
const isHex = (s) => /^[0-9a-fA-F]+$/.test(s);
|
|
32
|
-
const hexToBytes = (hex) => {
|
|
33
|
-
const clean = (hex || '').trim();
|
|
34
|
-
if (!isHex(clean) || clean.length % 2 !== 0) {
|
|
35
|
-
throw new Error('Invalid hex string');
|
|
36
|
-
}
|
|
37
|
-
const out = new Uint8Array(clean.length / 2);
|
|
38
|
-
for (let i = 0; i < out.length; i++) {
|
|
39
|
-
out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
40
|
-
}
|
|
41
|
-
return out;
|
|
42
|
-
};
|
|
43
|
-
const bytesToHex = (bytes) => {
|
|
44
|
-
const u8 = new Uint8Array(bytes);
|
|
45
|
-
let hex = '';
|
|
46
|
-
for (let i = 0; i < u8.length; i++) {
|
|
47
|
-
hex += u8[i].toString(16).padStart(2, '0');
|
|
48
|
-
}
|
|
49
|
-
return hex;
|
|
50
|
-
};
|
|
51
|
-
const maybeParseGcmEnvelope = (raw) => {
|
|
52
|
-
if (!raw || typeof raw !== 'string')
|
|
53
|
-
return null;
|
|
54
|
-
if (!raw.trim().startsWith('{'))
|
|
55
|
-
return null;
|
|
56
|
-
try {
|
|
57
|
-
const parsed = JSON.parse(raw);
|
|
58
|
-
if (parsed &&
|
|
59
|
-
parsed.v === 4 &&
|
|
60
|
-
parsed.alg === 'A256GCM' &&
|
|
61
|
-
typeof parsed.iv === 'string' &&
|
|
62
|
-
typeof parsed.ct === 'string') {
|
|
63
|
-
return parsed;
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const encryptVaultWebCrypto = async (plaintextJson, keyHex) => {
|
|
72
|
-
const subtle = globalThis.crypto.subtle;
|
|
73
|
-
const iv = new Uint8Array(12);
|
|
74
|
-
globalThis.crypto.getRandomValues(iv);
|
|
75
|
-
const keyBytes = hexToBytes(keyHex);
|
|
76
|
-
if (keyBytes.length !== 32) {
|
|
77
|
-
throw new Error('Vault key must be 32 bytes (hex length 64)');
|
|
78
|
-
}
|
|
79
|
-
const key = await subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['encrypt']);
|
|
80
|
-
const pt = new TextEncoder().encode(plaintextJson);
|
|
81
|
-
const ct = await subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, pt);
|
|
82
|
-
return {
|
|
83
|
-
v: 4,
|
|
84
|
-
alg: 'A256GCM',
|
|
85
|
-
iv: bytesToHex(iv.buffer),
|
|
86
|
-
ct: bytesToHex(ct),
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
class VaultAuthenticationError extends Error {
|
|
90
|
-
constructor() {
|
|
91
|
-
super('Failed to decrypt vault - invalid password or corrupted data');
|
|
92
|
-
this.code = 'INVALID_PASSWORD';
|
|
93
|
-
this.name = 'VaultAuthenticationError';
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
exports.VaultAuthenticationError = VaultAuthenticationError;
|
|
97
|
-
const invalidPasswordError = () => new VaultAuthenticationError();
|
|
98
|
-
const decryptVaultWebCrypto = async (envelope, keyHex) => {
|
|
99
|
-
const subtle = globalThis?.crypto?.subtle;
|
|
100
|
-
if (!subtle) {
|
|
101
|
-
throw new Error('WebCrypto is required to decrypt this vault');
|
|
102
|
-
}
|
|
103
|
-
const keyBytes = hexToBytes(keyHex);
|
|
104
|
-
if (keyBytes.length !== 32) {
|
|
105
|
-
throw new Error('Vault key must be 32 bytes (hex length 64)');
|
|
106
|
-
}
|
|
107
|
-
const key = await subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']);
|
|
108
|
-
const ivBytes = hexToBytes(envelope.iv);
|
|
109
|
-
const ctBytes = hexToBytes(envelope.ct);
|
|
110
|
-
if (ivBytes.length !== 12 || ctBytes.length < 16) {
|
|
111
|
-
throw new Error('Invalid encrypted vault format');
|
|
112
|
-
}
|
|
113
|
-
let pt;
|
|
114
|
-
try {
|
|
115
|
-
pt = await subtle.decrypt({ name: 'AES-GCM', iv: ivBytes }, key, ctBytes);
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
// Only a failed authentication tag is a password/ciphertext failure.
|
|
119
|
-
// Key import and other platform exceptions must remain operational.
|
|
120
|
-
if (error?.name === 'OperationError') {
|
|
121
|
-
throw invalidPasswordError();
|
|
122
|
-
}
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
125
|
-
return new TextDecoder().decode(pt);
|
|
126
|
-
};
|
|
127
|
-
// Single vault for all networks - stores the mnemonic and can derive accounts for any slip44
|
|
128
|
-
const setEncryptedVault = async (decryptedVault, pwd, freshVaultKeys) => {
|
|
129
|
-
return vaultMutex.runExclusive(async () => {
|
|
130
|
-
const plaintext = JSON.stringify(decryptedVault);
|
|
131
|
-
// Writes never downgrade to unauthenticated passphrase AES. Legacy CBC
|
|
132
|
-
// remains readable below, but missing WebCrypto is an operational failure.
|
|
133
|
-
const canUseWebCrypto = !!globalThis?.crypto?.subtle &&
|
|
134
|
-
!!globalThis?.crypto?.getRandomValues;
|
|
135
|
-
if (!canUseWebCrypto) {
|
|
136
|
-
throw new Error('WebCrypto is required for vault encryption');
|
|
137
|
-
}
|
|
138
|
-
if (typeof pwd !== 'string' || !isHex(pwd) || pwd.length !== 64) {
|
|
139
|
-
throw new Error('Vault key must be 32 bytes (hex length 64)');
|
|
140
|
-
}
|
|
141
|
-
const envelope = await encryptVaultWebCrypto(plaintext, pwd);
|
|
142
|
-
const toStore = JSON.stringify(envelope);
|
|
143
|
-
if (freshVaultKeys) {
|
|
144
|
-
// The backend primitive (or native storage Web Lock) covers both the
|
|
145
|
-
// absence check and batch commit across independently bundled contexts.
|
|
146
|
-
const created = await storage.createManyIfAbsent({
|
|
147
|
-
'vault-keys': freshVaultKeys,
|
|
148
|
-
vault: toStore,
|
|
149
|
-
});
|
|
150
|
-
if (!created)
|
|
151
|
-
throw new Error('Cannot initialize a new vault over existing wallet storage');
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
// Existing vault updates retain their migration-specific write ordering.
|
|
155
|
-
await storage.set('vault', toStore);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
};
|
|
159
|
-
exports.setEncryptedVault = setEncryptedVault;
|
|
160
|
-
const getDecryptedVault = async (pwd, legacyPassword) => {
|
|
161
|
-
return vaultMutex.runExclusive(async () => {
|
|
162
|
-
// Always use single 'vault' key
|
|
163
|
-
const vault = await storage.get('vault');
|
|
164
|
-
if (!vault) {
|
|
165
|
-
throw new Error('Vault not found');
|
|
166
|
-
}
|
|
167
|
-
// Prefer WebCrypto AES-GCM when the stored vault is in v4 envelope format.
|
|
168
|
-
const maybeEnvelope = maybeParseGcmEnvelope(vault);
|
|
169
|
-
if (!maybeEnvelope &&
|
|
170
|
-
typeof vault === 'string' &&
|
|
171
|
-
vault.trim().startsWith('{')) {
|
|
172
|
-
// Legacy CBC vaults are base64. A JSON-shaped record must be a valid
|
|
173
|
-
// GCM envelope rather than being misreported as a password failure.
|
|
174
|
-
throw new Error('Invalid encrypted vault format');
|
|
175
|
-
}
|
|
176
|
-
let decryptedVault;
|
|
177
|
-
if (maybeEnvelope) {
|
|
178
|
-
decryptedVault = await decryptVaultWebCrypto(maybeEnvelope, pwd);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
// Legacy CryptoJS passphrase-AES vault (v3 and older v4 canary).
|
|
182
|
-
// An interrupted migration can already have written CBC with the
|
|
183
|
-
// derived key while the metadata still selects the legacy password.
|
|
184
|
-
for (const candidateKey of new Set([legacyPassword ?? pwd, pwd])) {
|
|
185
|
-
let plaintext;
|
|
186
|
-
try {
|
|
187
|
-
plaintext = crypto_js_1.default.AES.decrypt(vault, candidateKey).toString(crypto_js_1.default.enc.Utf8);
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
if (error?.message === 'Malformed UTF-8 data')
|
|
191
|
-
continue;
|
|
192
|
-
throw error;
|
|
193
|
-
}
|
|
194
|
-
if (!plaintext)
|
|
195
|
-
continue;
|
|
196
|
-
try {
|
|
197
|
-
return JSON.parse(plaintext);
|
|
198
|
-
}
|
|
199
|
-
catch (error) {
|
|
200
|
-
// CBC has no authentication tag; invalid plaintext is the only
|
|
201
|
-
// available indication of a bad password/ciphertext.
|
|
202
|
-
if (!(error instanceof SyntaxError))
|
|
203
|
-
throw error;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
throw invalidPasswordError();
|
|
207
|
-
}
|
|
208
|
-
if (!decryptedVault) {
|
|
209
|
-
throw invalidPasswordError();
|
|
210
|
-
}
|
|
211
|
-
return JSON.parse(decryptedVault);
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
exports.getDecryptedVault = getDecryptedVault;
|
|
9
|
+
const persistence = (0, storage_core_1.createVaultPersistence)({
|
|
10
|
+
get: async (key) => storage.get(key),
|
|
11
|
+
set: async (key, value) => {
|
|
12
|
+
await storage.set(key, value);
|
|
13
|
+
},
|
|
14
|
+
createManyIfAbsent: async (items) => storage.createManyIfAbsent(items),
|
|
15
|
+
});
|
|
16
|
+
exports.setEncryptedVault = persistence.setEncryptedVault;
|
|
17
|
+
exports.getDecryptedVault = persistence.getDecryptedVault;
|
|
18
|
+
var storage_core_2 = require("./storage-core");
|
|
19
|
+
Object.defineProperty(exports, "VaultAuthenticationError", { enumerable: true, get: function () { return storage_core_2.VaultAuthenticationError; } });
|
|
215
20
|
//# sourceMappingURL=storage.js.map
|
package/cjs/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/storage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/storage.ts"],"names":[],"mappings":";;;AAAA,yDAAmD;AAEnD,iDAAwD;AAExD,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,OAAO,GAAG,wBAAS,CAAC,iBAAiB,EAAE,CAAC;AAC9C,MAAM,WAAW,GAAG,IAAA,qCAAsB,EAAC;IACzC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;IACpC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QACxB,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,kBAAkB,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC;CACvE,CAAC,CAAC;AAEU,QAAA,iBAAiB,GAAG,WAAW,CAAC,iBAAiB,CAAC;AAClD,QAAA,iBAAiB,GAAG,WAAW,CAAC,iBAAiB,CAAC;AAC/D,+CAA0D;AAAjD,wHAAA,wBAAwB,OAAA"}
|
|
@@ -14,6 +14,9 @@ const providers_1 = require("../providers");
|
|
|
14
14
|
const types_1 = require("../types");
|
|
15
15
|
const evm_local_signer_1 = require("./evm-local-signer");
|
|
16
16
|
const derivation_paths_1 = require("../utils/derivation-paths");
|
|
17
|
+
const hardwareSignerUnavailable = (hardware) => Object.assign(new Error(`${hardware} signer is unavailable in this host`), {
|
|
18
|
+
code: 'HARDWARE_SIGNER_UNAVAILABLE',
|
|
19
|
+
});
|
|
17
20
|
const stripHexPrefix = (value) => value.startsWith('0x') || value.startsWith('0X') ? value.slice(2) : value;
|
|
18
21
|
const normalizeEthersOverrideValue = (value) => (0, ethers_v6_1.normalizeTxValue)(value);
|
|
19
22
|
const nonNegativeFeeQuantity = (value, label) => {
|
|
@@ -131,7 +134,28 @@ class EthereumTransactions {
|
|
|
131
134
|
constructor(getNetwork, getDecryptedPrivateKey, getState, ledgerSigner, trezorSigner, captureSigningContext) {
|
|
132
135
|
// Allow manual override of gas parameters when automatic estimation fails
|
|
133
136
|
this.gasOverrides = {};
|
|
137
|
+
this.requireLedgerSigner = () => {
|
|
138
|
+
if (!this.ledgerSigner)
|
|
139
|
+
throw hardwareSignerUnavailable('Ledger');
|
|
140
|
+
return this.ledgerSigner;
|
|
141
|
+
};
|
|
142
|
+
this.requireTrezorSigner = () => {
|
|
143
|
+
if (!this.trezorSigner)
|
|
144
|
+
throw hardwareSignerUnavailable('Trezor');
|
|
145
|
+
return this.trezorSigner;
|
|
146
|
+
};
|
|
147
|
+
this.assertHardwareSignerAvailable = (targetAccount) => {
|
|
148
|
+
const state = this.getState();
|
|
149
|
+
const type = targetAccount?.type ?? state.activeAccountType;
|
|
150
|
+
const id = targetAccount?.id ?? state.activeAccountId;
|
|
151
|
+
const account = state.accounts[type]?.[id];
|
|
152
|
+
if (type === types_1.KeyringAccountType.Ledger || account?.isLedgerWallet)
|
|
153
|
+
this.requireLedgerSigner();
|
|
154
|
+
if (type === types_1.KeyringAccountType.Trezor || account?.isTrezorWallet)
|
|
155
|
+
this.requireTrezorSigner();
|
|
156
|
+
};
|
|
134
157
|
this.captureEvmSigningContext = () => {
|
|
158
|
+
this.assertHardwareSignerAvailable();
|
|
135
159
|
const { activeAccountType, activeAccountId, accounts, activeNetwork } = this.getState();
|
|
136
160
|
const selectedAccount = accounts[activeAccountType]?.[activeAccountId];
|
|
137
161
|
if (!selectedAccount)
|
|
@@ -192,7 +216,7 @@ class EthereumTransactions {
|
|
|
192
216
|
throw {
|
|
193
217
|
message: 'Decrypting for wrong address, change activeAccount maybe',
|
|
194
218
|
};
|
|
195
|
-
return await this.
|
|
219
|
+
return await this.requireLedgerSigner().evm.signTypedData({
|
|
196
220
|
version,
|
|
197
221
|
accountIndex: activeAccountId,
|
|
198
222
|
data: typedData,
|
|
@@ -203,7 +227,7 @@ class EthereumTransactions {
|
|
|
203
227
|
throw {
|
|
204
228
|
message: 'Decrypting for wrong address, change activeAccount maybe',
|
|
205
229
|
};
|
|
206
|
-
return await this.
|
|
230
|
+
return await this.requireTrezorSigner().signTypedData({
|
|
207
231
|
version,
|
|
208
232
|
address: addr,
|
|
209
233
|
data: typedData,
|
|
@@ -225,9 +249,9 @@ class EthereumTransactions {
|
|
|
225
249
|
const { activeAccountType } = this.getState();
|
|
226
250
|
switch (activeAccountType) {
|
|
227
251
|
case types_1.KeyringAccountType.Ledger:
|
|
228
|
-
return await this.
|
|
252
|
+
return await this.requireLedgerSigner().verifyUtxoAddress(accountIndex, currency, slip44);
|
|
229
253
|
case types_1.KeyringAccountType.Trezor:
|
|
230
|
-
return await this.
|
|
254
|
+
return await this.requireTrezorSigner().verifyUtxoAddress(accountIndex, currency, slip44);
|
|
231
255
|
default:
|
|
232
256
|
throw new Error('verifyUtxoAddress is only available for hardware wallet accounts');
|
|
233
257
|
}
|
|
@@ -284,7 +308,7 @@ class EthereumTransactions {
|
|
|
284
308
|
};
|
|
285
309
|
const signWithLedger = async () => {
|
|
286
310
|
try {
|
|
287
|
-
const response = await this.
|
|
311
|
+
const response = await this.requireLedgerSigner().evm.signPersonalMessage({
|
|
288
312
|
accountIndex: activeAccountId,
|
|
289
313
|
message: msg,
|
|
290
314
|
});
|
|
@@ -298,7 +322,7 @@ class EthereumTransactions {
|
|
|
298
322
|
try {
|
|
299
323
|
// For EVM networks, Trezor expects 'eth' regardless of the network's currency
|
|
300
324
|
const trezorCoin = activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
301
|
-
const response = await this.
|
|
325
|
+
const response = await this.requireTrezorSigner().signMessage({
|
|
302
326
|
coin: trezorCoin,
|
|
303
327
|
address: activeAccount.address,
|
|
304
328
|
index: activeAccountId,
|
|
@@ -377,7 +401,7 @@ class EthereumTransactions {
|
|
|
377
401
|
// Message is plain text, convert to hex
|
|
378
402
|
messageForLedger = Buffer.from(msg, 'utf8').toString('hex');
|
|
379
403
|
}
|
|
380
|
-
const response = await this.
|
|
404
|
+
const response = await this.requireLedgerSigner().evm.signPersonalMessage({
|
|
381
405
|
accountIndex: activeAccountId,
|
|
382
406
|
message: messageForLedger,
|
|
383
407
|
});
|
|
@@ -401,7 +425,7 @@ class EthereumTransactions {
|
|
|
401
425
|
}
|
|
402
426
|
// For EVM networks, Trezor expects 'eth' regardless of the network's currency
|
|
403
427
|
const trezorCoin = activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
404
|
-
const response = await this.
|
|
428
|
+
const response = await this.requireTrezorSigner().signMessage({
|
|
405
429
|
coin: trezorCoin,
|
|
406
430
|
address: activeAccount.address,
|
|
407
431
|
index: activeAccountId,
|
|
@@ -674,9 +698,52 @@ class EthereumTransactions {
|
|
|
674
698
|
(!Number.isSafeInteger(fallbackNonce) || fallbackNonce < 0)) {
|
|
675
699
|
throw new Error('Invalid cancellation fallback nonce');
|
|
676
700
|
}
|
|
701
|
+
const previewOnly = options.previewOnly === true;
|
|
702
|
+
const approvedMaximumFee = options.approvedMaximumFee;
|
|
703
|
+
if (!previewOnly &&
|
|
704
|
+
(typeof approvedMaximumFee !== 'string' ||
|
|
705
|
+
!/^(0|[1-9][0-9]{0,77})$/.test(approvedMaximumFee))) {
|
|
706
|
+
throw new Error('Cancellation fee approval required');
|
|
707
|
+
}
|
|
677
708
|
const assertHostContext = options.assertCurrentContext;
|
|
678
709
|
assertHostContext();
|
|
679
|
-
const
|
|
710
|
+
const state = this.getState();
|
|
711
|
+
const selectedType = state.activeAccountType;
|
|
712
|
+
const selectedId = state.activeAccountId;
|
|
713
|
+
const selectedAddress = state.accounts?.[selectedType]?.[selectedId]?.address;
|
|
714
|
+
const activeAccountType = options.targetAccount?.type ?? selectedType;
|
|
715
|
+
const activeAccountId = options.targetAccount?.id ?? selectedId;
|
|
716
|
+
const targetAccount = options.targetAccount
|
|
717
|
+
? { type: activeAccountType, id: activeAccountId }
|
|
718
|
+
: undefined;
|
|
719
|
+
const activeAccount = {
|
|
720
|
+
...state.accounts?.[activeAccountType]?.[activeAccountId],
|
|
721
|
+
};
|
|
722
|
+
const activeNetwork = { ...state.activeNetwork };
|
|
723
|
+
if (!selectedAddress ||
|
|
724
|
+
!activeAccount.address ||
|
|
725
|
+
(targetAccount && activeAccountType === types_1.KeyringAccountType.SmartAccount)) {
|
|
726
|
+
throw new Error('Cancellation signing account is unavailable');
|
|
727
|
+
}
|
|
728
|
+
if (!previewOnly)
|
|
729
|
+
this.assertHardwareSignerAvailable(targetAccount);
|
|
730
|
+
const provider = this.web3Provider;
|
|
731
|
+
const assertSession = this.captureSigningContext?.();
|
|
732
|
+
const assertKeyringContext = () => {
|
|
733
|
+
assertSession?.();
|
|
734
|
+
const current = this.getState();
|
|
735
|
+
if (current.activeAccountType !== selectedType ||
|
|
736
|
+
current.activeAccountId !== selectedId ||
|
|
737
|
+
current.accounts?.[selectedType]?.[selectedId]?.address?.toLowerCase() !== selectedAddress.toLowerCase() ||
|
|
738
|
+
current.accounts?.[activeAccountType]?.[activeAccountId]?.address?.toLowerCase() !== activeAccount.address.toLowerCase() ||
|
|
739
|
+
[current.activeNetwork, this.getNetwork()].some((selected) => selected.kind !== activeNetwork.kind ||
|
|
740
|
+
selected.chainId !== activeNetwork.chainId ||
|
|
741
|
+
selected.slip44 !== activeNetwork.slip44 ||
|
|
742
|
+
selected.url !== activeNetwork.url) ||
|
|
743
|
+
this.web3Provider !== provider) {
|
|
744
|
+
throw new Error('Wallet signing context changed');
|
|
745
|
+
}
|
|
746
|
+
};
|
|
680
747
|
const assertCurrent = () => {
|
|
681
748
|
assertHostContext();
|
|
682
749
|
assertKeyringContext();
|
|
@@ -798,6 +865,22 @@ class EthereumTransactions {
|
|
|
798
865
|
maxPriorityFeePerGas: bumpCancelFee(oldTxsGasValues.maxPriorityFeePerGas),
|
|
799
866
|
}),
|
|
800
867
|
};
|
|
868
|
+
const maximumFee = newGasValues.gasLimit.mul(isLegacy ? newGasValues.gasPrice : newGasValues.maxFeePerGas);
|
|
869
|
+
assertCurrent();
|
|
870
|
+
if (previewOnly)
|
|
871
|
+
return {
|
|
872
|
+
isCanceled: false,
|
|
873
|
+
error: false,
|
|
874
|
+
maximumFee: maximumFee.toString(),
|
|
875
|
+
};
|
|
876
|
+
if (maximumFee.gt(approvedMaximumFee))
|
|
877
|
+
throw new Error('Cancellation fee exceeds approval');
|
|
878
|
+
const assertApprovedEnvelope = (transaction) => {
|
|
879
|
+
const gas = nonNegativeFeeQuantity(transaction.gasLimit, 'cancellation gas limit');
|
|
880
|
+
const cap = nonNegativeFeeQuantity(isLegacy ? transaction.gasPrice : transaction.maxFeePerGas, 'cancellation maximum fee');
|
|
881
|
+
if (!gas.eq(42000) || gas.mul(cap).gt(approvedMaximumFee))
|
|
882
|
+
throw new Error('Cancellation fee exceeds approval');
|
|
883
|
+
};
|
|
801
884
|
// Base cancel transaction parameters (same for all wallet types)
|
|
802
885
|
const baseCancelTx = {
|
|
803
886
|
nonce: tx.nonce,
|
|
@@ -841,8 +924,9 @@ class EthereumTransactions {
|
|
|
841
924
|
chainId: activeNetwork.chainId,
|
|
842
925
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
843
926
|
};
|
|
927
|
+
assertApprovedEnvelope(txFormattedForEthers);
|
|
844
928
|
const rawTx = (0, ethers_v6_1.serializeTransaction)(txFormattedForEthers);
|
|
845
|
-
const signature = await this.
|
|
929
|
+
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
846
930
|
rawTx: rawTx.replace('0x', ''),
|
|
847
931
|
accountIndex: activeAccountId,
|
|
848
932
|
});
|
|
@@ -908,7 +992,8 @@ class EthereumTransactions {
|
|
|
908
992
|
? formattedTx.maxPriorityFeePerGas
|
|
909
993
|
: this.toHex0x(formattedTx.maxPriorityFeePerGas, 'maxPriorityFeePerGas');
|
|
910
994
|
}
|
|
911
|
-
|
|
995
|
+
assertApprovedEnvelope(txFormattedForTrezor);
|
|
996
|
+
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
912
997
|
coin: trezorCoin,
|
|
913
998
|
tx: txFormattedForTrezor,
|
|
914
999
|
index: activeAccountId.toString(),
|
|
@@ -942,11 +1027,11 @@ class EthereumTransactions {
|
|
|
942
1027
|
const cancelWithPrivateKey = async () => {
|
|
943
1028
|
try {
|
|
944
1029
|
assertCurrent();
|
|
945
|
-
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey();
|
|
1030
|
+
const { address, decryptedPrivateKey } = this.getDecryptedPrivateKey(targetAccount);
|
|
946
1031
|
if (address?.toLowerCase() !== activeAccount.address.toLowerCase()) {
|
|
947
1032
|
throw new Error('Wallet signing context changed');
|
|
948
1033
|
}
|
|
949
|
-
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent);
|
|
1034
|
+
const transactionResponse = await (0, evm_local_signer_1.sendLocalEvmTransaction)(provider, decryptedPrivateKey, changedTxToCancel, assertCurrent, assertCurrent, assertApprovedEnvelope);
|
|
950
1035
|
if (transactionResponse) {
|
|
951
1036
|
return {
|
|
952
1037
|
isCanceled: true,
|
|
@@ -1107,7 +1192,7 @@ class EthereumTransactions {
|
|
|
1107
1192
|
type: 2, // Need explicit type for hardware wallet serialization
|
|
1108
1193
|
};
|
|
1109
1194
|
const rawTx = (0, ethers_v6_1.serializeTransaction)(txFormattedForEthers);
|
|
1110
|
-
const signature = await this.
|
|
1195
|
+
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1111
1196
|
rawTx: rawTx.replace('0x', ''),
|
|
1112
1197
|
accountIndex: activeAccountId,
|
|
1113
1198
|
});
|
|
@@ -1180,7 +1265,7 @@ class EthereumTransactions {
|
|
|
1180
1265
|
};
|
|
1181
1266
|
break;
|
|
1182
1267
|
}
|
|
1183
|
-
const signature = await this.
|
|
1268
|
+
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1184
1269
|
index: `${activeAccountId}`,
|
|
1185
1270
|
tx: txFormattedForTrezor,
|
|
1186
1271
|
coin: activeNetwork.currency,
|
|
@@ -1252,6 +1337,8 @@ class EthereumTransactions {
|
|
|
1252
1337
|
this.sendTransactionWithEditedFee = async (txHash, isLegacy = false, options = {}) => {
|
|
1253
1338
|
const { assertCurrentContext, validateOriginal, beforeBroadcast, approvedMaximumFee, } = options;
|
|
1254
1339
|
const previewOnly = options.previewOnly === true;
|
|
1340
|
+
if (!previewOnly)
|
|
1341
|
+
this.assertHardwareSignerAvailable(options.targetAccount);
|
|
1255
1342
|
if (!previewOnly &&
|
|
1256
1343
|
(typeof approvedMaximumFee !== 'string' ||
|
|
1257
1344
|
!/^\d{1,78}$/.test(approvedMaximumFee))) {
|
|
@@ -1366,7 +1453,6 @@ class EthereumTransactions {
|
|
|
1366
1453
|
newGasValues.maxPriorityFeePerGas.gt(newGasValues.maxFeePerGas))
|
|
1367
1454
|
throw new Error('Invalid replacement fee');
|
|
1368
1455
|
const maximumFee = newGasValues.gasLimit.mul(isLegacy ? newGasValues.gasPrice : newGasValues.maxFeePerGas);
|
|
1369
|
-
let adjustedValue = txValue;
|
|
1370
1456
|
if (isLikelyMaxSend && txValue.add(maximumFee).gt(currentBalance)) {
|
|
1371
1457
|
if (isContractCall) {
|
|
1372
1458
|
console.error('[SpeedUp] Cannot adjust value for contract call - rejecting speedup');
|
|
@@ -1377,16 +1463,19 @@ class EthereumTransactions {
|
|
|
1377
1463
|
message: 'Cannot speed up a likely max-send contract call; value cannot be adjusted to fit new gas',
|
|
1378
1464
|
};
|
|
1379
1465
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
:
|
|
1466
|
+
return {
|
|
1467
|
+
isSpeedUp: false,
|
|
1468
|
+
error: true,
|
|
1469
|
+
code: 'REPLACEMENT_VALUE_WOULD_CHANGE',
|
|
1470
|
+
message: 'Add funds before speeding up this transaction; the original amount will not be changed',
|
|
1471
|
+
};
|
|
1383
1472
|
}
|
|
1384
1473
|
const txWithEditedFee = {
|
|
1385
1474
|
chainId: activeNetwork.chainId,
|
|
1386
1475
|
from: tx.from,
|
|
1387
1476
|
to: tx.to,
|
|
1388
1477
|
nonce: tx.nonce,
|
|
1389
|
-
value:
|
|
1478
|
+
value: txValue,
|
|
1390
1479
|
data: txData,
|
|
1391
1480
|
gasLimit: newGasValues.gasLimit,
|
|
1392
1481
|
...(tx.accessList != null ? { accessList: tx.accessList } : {}),
|
|
@@ -1424,7 +1513,7 @@ class EthereumTransactions {
|
|
|
1424
1513
|
};
|
|
1425
1514
|
const rawTx = (0, ethers_v6_1.serializeTransaction)(txFormattedForEthers);
|
|
1426
1515
|
assertCurrent();
|
|
1427
|
-
const signature = await this.
|
|
1516
|
+
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1428
1517
|
rawTx: rawTx.replace('0x', ''),
|
|
1429
1518
|
accountIndex: activeAccountId,
|
|
1430
1519
|
});
|
|
@@ -1505,7 +1594,7 @@ class EthereumTransactions {
|
|
|
1505
1594
|
: this.toHex0x(formattedTx.maxPriorityFeePerGas, 'maxPriorityFeePerGas');
|
|
1506
1595
|
}
|
|
1507
1596
|
assertCurrent();
|
|
1508
|
-
const signature = await this.
|
|
1597
|
+
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1509
1598
|
coin: trezorCoin,
|
|
1510
1599
|
tx: txFormattedForTrezor,
|
|
1511
1600
|
index: activeAccountId.toString(),
|
|
@@ -1688,7 +1777,7 @@ class EthereumTransactions {
|
|
|
1688
1777
|
};
|
|
1689
1778
|
}
|
|
1690
1779
|
const rawTx = (0, ethers_v6_1.serializeTransaction)(txFormattedForEthers);
|
|
1691
|
-
const signature = await this.
|
|
1780
|
+
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1692
1781
|
rawTx: rawTx.replace('0x', ''),
|
|
1693
1782
|
accountIndex: activeAccountId,
|
|
1694
1783
|
});
|
|
@@ -1756,7 +1845,7 @@ class EthereumTransactions {
|
|
|
1756
1845
|
data: txData,
|
|
1757
1846
|
};
|
|
1758
1847
|
}
|
|
1759
|
-
const signature = await this.
|
|
1848
|
+
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1760
1849
|
index: `${activeAccountId}`,
|
|
1761
1850
|
tx: txToBeSignedByTrezor,
|
|
1762
1851
|
coin: activeNetwork.currency,
|
|
@@ -1909,7 +1998,7 @@ class EthereumTransactions {
|
|
|
1909
1998
|
};
|
|
1910
1999
|
}
|
|
1911
2000
|
const rawTx = (0, ethers_v6_1.serializeTransaction)(txFormattedForEthers);
|
|
1912
|
-
const signature = await this.
|
|
2001
|
+
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
1913
2002
|
rawTx: rawTx.replace('0x', ''),
|
|
1914
2003
|
accountIndex: activeAccountId,
|
|
1915
2004
|
});
|
|
@@ -1978,7 +2067,7 @@ class EthereumTransactions {
|
|
|
1978
2067
|
}
|
|
1979
2068
|
// For EVM networks, Trezor expects 'eth' regardless of the network's currency
|
|
1980
2069
|
const trezorCoin = activeNetwork.slip44 === 60 ? 'eth' : activeNetwork.currency;
|
|
1981
|
-
const signature = await this.
|
|
2070
|
+
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
1982
2071
|
index: `${activeAccountId}`,
|
|
1983
2072
|
tx: txToBeSignedByTrezor,
|
|
1984
2073
|
coin: trezorCoin,
|
|
@@ -2127,7 +2216,7 @@ class EthereumTransactions {
|
|
|
2127
2216
|
};
|
|
2128
2217
|
}
|
|
2129
2218
|
const rawTx = (0, ethers_v6_1.serializeTransaction)(txFormattedForEthers);
|
|
2130
|
-
const signature = await this.
|
|
2219
|
+
const signature = await this.requireLedgerSigner().evm.signEVMTransaction({
|
|
2131
2220
|
rawTx: rawTx.replace('0x', ''),
|
|
2132
2221
|
accountIndex: activeAccountId,
|
|
2133
2222
|
});
|
|
@@ -2190,7 +2279,7 @@ class EthereumTransactions {
|
|
|
2190
2279
|
data: txData,
|
|
2191
2280
|
};
|
|
2192
2281
|
}
|
|
2193
|
-
const signature = await this.
|
|
2282
|
+
const signature = await this.requireTrezorSigner().signEthTransaction({
|
|
2194
2283
|
index: `${activeAccountId}`,
|
|
2195
2284
|
tx: txToBeSignedByTrezor,
|
|
2196
2285
|
coin: activeNetwork.currency,
|
|
@@ -2403,7 +2492,10 @@ class EthereumTransactions {
|
|
|
2403
2492
|
};
|
|
2404
2493
|
};
|
|
2405
2494
|
this.getNetwork = getNetwork;
|
|
2406
|
-
this.getDecryptedPrivateKey =
|
|
2495
|
+
this.getDecryptedPrivateKey = (targetAccount) => {
|
|
2496
|
+
this.assertHardwareSignerAvailable(targetAccount);
|
|
2497
|
+
return getDecryptedPrivateKey(targetAccount);
|
|
2498
|
+
};
|
|
2407
2499
|
this.abortController = new AbortController();
|
|
2408
2500
|
// NOTE: Defer network access until vault state getter is initialized
|
|
2409
2501
|
// The web3Provider will be created lazily when first accessed via getters
|