@0xsequence/account 2.3.17 → 2.3.19

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.
@@ -1,1105 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var abi = require('@0xsequence/abi');
6
- var core = require('@0xsequence/core');
7
- var migration = require('@0xsequence/migration');
8
- var network = require('@0xsequence/network');
9
- var relayer = require('@0xsequence/relayer');
10
- var utils = require('@0xsequence/utils');
11
- var wallet = require('@0xsequence/wallet');
12
- var ethers = require('ethers');
13
-
14
- function _extends() {
15
- return _extends = Object.assign ? Object.assign.bind() : function (n) {
16
- for (var e = 1; e < arguments.length; e++) {
17
- var t = arguments[e];
18
- for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
19
- }
20
- return n;
21
- }, _extends.apply(null, arguments);
22
- }
23
-
24
- function encodeGasRefundTransaction(option) {
25
- if (!option) return [];
26
- const value = BigInt(option.value);
27
- switch (option.token.type) {
28
- case relayer.proto.FeeTokenType.UNKNOWN:
29
- return [{
30
- delegateCall: false,
31
- revertOnError: true,
32
- gasLimit: option.gasLimit,
33
- to: option.to,
34
- value: utils.toHexString(value),
35
- data: '0x'
36
- }];
37
- case relayer.proto.FeeTokenType.ERC20_TOKEN:
38
- if (!option.token.contractAddress) {
39
- throw new Error(`No contract address for ERC-20 fee option`);
40
- }
41
- return [{
42
- delegateCall: false,
43
- revertOnError: true,
44
- gasLimit: option.gasLimit,
45
- to: option.token.contractAddress,
46
- value: 0,
47
- data: new ethers.ethers.Interface([{
48
- constant: false,
49
- inputs: [{
50
- type: 'address'
51
- }, {
52
- type: 'uint256'
53
- }],
54
- name: 'transfer',
55
- outputs: [],
56
- type: 'function'
57
- }]).encodeFunctionData('transfer', [option.to, utils.toHexString(value)])
58
- }];
59
- default:
60
- throw new Error(`Unhandled fee token type ${option.token.type}`);
61
- }
62
- }
63
- class AccountSigner {
64
- constructor(account, chainId, options) {
65
- this.account = account;
66
- this.chainId = chainId;
67
- this.options = options;
68
- }
69
- get provider() {
70
- return this.account.providerFor(this.chainId);
71
- }
72
- async getAddress() {
73
- return this.account.address;
74
- }
75
-
76
- /**
77
- * Signs a message.
78
- *
79
- * This method will sign the message using the account associated with this signer
80
- * and the specified chain ID. The message is already being prefixed with the EIP-191 prefix.
81
- *
82
- * @param message - The message to sign. Can be a string or BytesLike.
83
- * @returns A Promise that resolves to the signature as a hexadecimal string
84
- *
85
- * @example
86
- * ```typescript
87
- * const signer = account.getSigner(chainId)
88
- *
89
- * const message = "Hello, Sequence!";
90
- * const signature = await signer.signMessage(message);
91
- * console.log(signature);
92
- * // => "0x123abc..." (hexadecimal signature)
93
- */
94
- signMessage(message) {
95
- var _this$options$cantVal, _this$options;
96
- return this.account.signMessage(message, this.chainId, (_this$options$cantVal = (_this$options = this.options) == null ? void 0 : _this$options.cantValidateBehavior) != null ? _this$options$cantVal : 'throw');
97
- }
98
- signTypedData(domain, types, value) {
99
- var _this$options$cantVal2, _this$options2;
100
- return this.account.signTypedData(domain, types, value, this.chainId, (_this$options$cantVal2 = (_this$options2 = this.options) == null ? void 0 : _this$options2.cantValidateBehavior) != null ? _this$options$cantVal2 : 'throw');
101
- }
102
- async defaultSelectFee(_txs, options) {
103
- // If no options, return undefined
104
- if (options.length === 0) return undefined;
105
-
106
- // If there are multiple options, try them one by one
107
- // until we find one that satisfies the balance requirement
108
- const balanceOfAbi = [{
109
- constant: true,
110
- inputs: [{
111
- type: 'address'
112
- }],
113
- name: 'balanceOf',
114
- outputs: [{
115
- type: 'uint256'
116
- }],
117
- type: 'function'
118
- }];
119
- for (const option of options) {
120
- if (option.token.type === relayer.proto.FeeTokenType.UNKNOWN) {
121
- // Native token
122
- const balance = await this.getBalance();
123
- if (balance >= BigInt(option.value)) {
124
- return option;
125
- }
126
- } else if (option.token.contractAddress && option.token.type === relayer.proto.FeeTokenType.ERC20_TOKEN) {
127
- // ERC20 token
128
- const token = new ethers.ethers.Contract(option.token.contractAddress, balanceOfAbi, this.provider);
129
- const balance = await token.balanceOf(this.account.address);
130
- if (balance >= BigInt(option.value)) {
131
- return option;
132
- }
133
- } else ;
134
- }
135
- throw new Error('No fee option available - not enough balance');
136
- }
137
- async sendTransaction(txs, options) {
138
- var _this$options$stubSig, _this$options3, _this$options$selectF, _this$options4, _this$options5;
139
- const prepare = await this.account.prepareTransactions({
140
- txs,
141
- chainId: this.chainId,
142
- stubSignatureOverrides: (_this$options$stubSig = (_this$options3 = this.options) == null ? void 0 : _this$options3.stubSignatureOverrides) != null ? _this$options$stubSig : new Map(),
143
- simulateForFeeOptions: options == null ? void 0 : options.simulateForFeeOptions
144
- });
145
- const selectMethod = (_this$options$selectF = (_this$options4 = this.options) == null ? void 0 : _this$options4.selectFee) != null ? _this$options$selectF : this.defaultSelectFee.bind(this);
146
- const feeOption = await selectMethod(txs, prepare.feeOptions);
147
- const finalTransactions = [...prepare.transactions, ...encodeGasRefundTransaction(feeOption)];
148
- return this.account.sendTransaction(finalTransactions, this.chainId, prepare.feeQuote, undefined, undefined, ((_this$options5 = this.options) == null ? void 0 : _this$options5.nonceSpace) !== undefined ? {
149
- nonceSpace: this.options.nonceSpace
150
- } : undefined); // Will always have a transaction response
151
- }
152
- getBalance(blockTag) {
153
- return this.provider.getBalance(this.account.address, blockTag);
154
- }
155
- call(transaction, blockTag) {
156
- return this.provider.call(_extends({}, transaction, {
157
- blockTag
158
- }));
159
- }
160
- async resolveName(name) {
161
- const res = await this.provider.resolveName(name);
162
- if (!res) throw new Error(`Could not resolve name ${name}`);
163
- return res;
164
- }
165
- connect(_provider) {
166
- throw new Error('Method not implemented.');
167
- }
168
- signTransaction(transaction) {
169
- throw new Error('Method not implemented.');
170
- }
171
- getTransactionCount(blockTag) {
172
- throw new Error('Method not implemented.');
173
- }
174
- estimateGas(transaction) {
175
- throw new Error('Method not implemented.');
176
- }
177
- getChainId() {
178
- return Promise.resolve(Number(this.chainId));
179
- }
180
- getGasPrice() {
181
- throw new Error('Method not implemented.');
182
- }
183
- getFeeData() {
184
- throw new Error('Method not implemented.');
185
- }
186
- getNonce(blockTag) {
187
- throw new Error('Method not implemented.');
188
- }
189
- populateCall(tx) {
190
- throw new Error('Method not implemented.');
191
- }
192
- checkTransaction(transaction) {
193
- throw new Error('Method not implemented.');
194
- }
195
- async populateTransaction(tx) {
196
- throw new Error('Method not implemented.');
197
- }
198
- _checkProvider(operation) {
199
- throw new Error('Method not implemented.');
200
- }
201
- }
202
-
203
- class Chain0Reader {
204
- async isDeployed(_wallet) {
205
- return false;
206
- }
207
- async implementation(_wallet) {
208
- return undefined;
209
- }
210
- async imageHash(_wallet) {
211
- return undefined;
212
- }
213
- async nonce(_wallet, _space) {
214
- return 0n;
215
- }
216
- async isValidSignature(_wallet, _digest, _signature) {
217
- throw new Error('Method not supported.');
218
- }
219
- }
220
- class Account {
221
- constructor(options) {
222
- this.address = void 0;
223
- this.networks = void 0;
224
- this.tracker = void 0;
225
- this.contexts = void 0;
226
- this.migrator = void 0;
227
- this.migrations = void 0;
228
- this.orchestrator = void 0;
229
- this.jwt = void 0;
230
- this.projectAccessKey = void 0;
231
- this.address = ethers.ethers.getAddress(options.address);
232
- this.contexts = options.contexts;
233
- this.tracker = options.tracker;
234
- this.networks = options.networks;
235
- this.orchestrator = options.orchestrator;
236
- this.jwt = options.jwt;
237
- this.projectAccessKey = options.projectAccessKey;
238
- this.migrations = options.migrations || migration.defaults.DefaultMigrations;
239
- this.migrator = new migration.migrator.Migrator(options.tracker, this.migrations, this.contexts);
240
- }
241
- getSigner(chainId, options) {
242
- return new AccountSigner(this, chainId, options);
243
- }
244
- static async new(options) {
245
- var _options$migrations;
246
- const mig = new migration.migrator.Migrator(options.tracker, (_options$migrations = options.migrations) != null ? _options$migrations : migration.defaults.DefaultMigrations, options.contexts);
247
- const lastMigration = mig.lastMigration();
248
- const lastCoder = lastMigration.configCoder;
249
- const config = lastCoder.fromSimple(options.config);
250
- const imageHash = lastCoder.imageHashOf(config);
251
- const context = options.contexts[lastMigration.version];
252
- const address = core.commons.context.addressOf(context, imageHash);
253
- await options.tracker.saveCounterfactualWallet({
254
- config,
255
- context: Object.values(options.contexts)
256
- });
257
- return new Account({
258
- address,
259
- tracker: options.tracker,
260
- contexts: options.contexts,
261
- networks: options.networks,
262
- orchestrator: options.orchestrator,
263
- migrations: options.migrations,
264
- projectAccessKey: options.projectAccessKey
265
- });
266
- }
267
- getAddress() {
268
- return Promise.resolve(this.address);
269
- }
270
- get version() {
271
- return this.migrator.lastMigration().version;
272
- }
273
- get coders() {
274
- const lastMigration = this.migrator.lastMigration();
275
- return {
276
- signature: lastMigration.signatureCoder,
277
- config: lastMigration.configCoder
278
- };
279
- }
280
- network(chainId) {
281
- const tcid = BigInt(chainId);
282
- const found = this.networks.find(n => tcid === BigInt(n.chainId));
283
- if (!found) throw new Error(`Network not found for chainId ${chainId}`);
284
- return found;
285
- }
286
- providerFor(chainId) {
287
- const found = this.network(chainId);
288
- if (!found.provider && !found.rpcUrl) {
289
- throw new Error(`Provider not found for chainId ${chainId}`);
290
- }
291
- const network = new ethers.ethers.Network(found.name, found.chainId);
292
- return found.provider || new ethers.ethers.JsonRpcProvider(utils.getFetchRequest(found.rpcUrl, this.projectAccessKey, this.jwt), network, {
293
- staticNetwork: network
294
- });
295
- }
296
- reader(chainId) {
297
- if (BigInt(chainId) === 0n) {
298
- return new Chain0Reader();
299
- }
300
-
301
- // TODO: Networks should be able to provide a reader directly
302
- // and we should default to the on-chain reader
303
- return new core.commons.reader.OnChainReader(this.providerFor(chainId));
304
- }
305
- relayer(chainId) {
306
- const found = this.network(chainId);
307
- if (!found.relayer) throw new Error(`Relayer not found for chainId ${chainId}`);
308
- if (relayer.isRelayer(found.relayer)) return found.relayer;
309
- return new relayer.RpcRelayer(_extends({}, found.relayer, {
310
- projectAccessKey: this.projectAccessKey,
311
- jwtAuth: this.jwt
312
- }));
313
- }
314
- setOrchestrator(orchestrator) {
315
- this.orchestrator = orchestrator;
316
- }
317
- setJwt(jwt) {
318
- this.jwt = jwt;
319
- }
320
- contextFor(version) {
321
- const ctx = this.contexts[version];
322
- if (!ctx) throw new Error(`Context not found for version ${version}`);
323
- return ctx;
324
- }
325
- walletForStatus(chainId, status) {
326
- const coder = core.universal.coderFor(status.version);
327
- return this.walletFor(chainId, this.contextFor(status.version), status.config, coder);
328
- }
329
- walletFor(chainId, context, config, coders) {
330
- const isNetworkZero = BigInt(chainId) === 0n;
331
- return new wallet.Wallet({
332
- config,
333
- context,
334
- chainId,
335
- coders,
336
- relayer: isNetworkZero ? undefined : this.relayer(chainId),
337
- address: this.address,
338
- orchestrator: this.orchestrator,
339
- reader: this.reader(chainId)
340
- });
341
- }
342
-
343
- // Get the status of the account on a given network
344
- // this does the following process:
345
- // 1. Get the current on-chain status of the wallet (version + imageHash)
346
- // 2. Get any pending migrations that have been signed by the wallet
347
- // 3. Get any pending configuration updates that have been signed by the wallet
348
- // 4. Fetch reverse lookups for both on-chain and pending configurations
349
- async status(chainId, longestPath = false) {
350
- var _this = this;
351
- const isDeployedPromise = this.reader(chainId).isDeployed(this.address);
352
- const counterfactualImageHashPromise = this.tracker.imageHashOfCounterfactualWallet({
353
- wallet: this.address
354
- }).then(r => {
355
- if (!r) throw new Error(`Counterfactual imageHash not found for wallet ${this.address}`);
356
- return r;
357
- });
358
- const counterFactualVersionPromise = counterfactualImageHashPromise.then(r => {
359
- return migration.version.counterfactualVersion(this.address, r.imageHash, Object.values(this.contexts));
360
- });
361
- const onChainVersionPromise = async function () {
362
- const isDeployed = await isDeployedPromise;
363
- if (!isDeployed) return counterFactualVersionPromise;
364
- const implementation = await _this.reader(chainId).implementation(_this.address);
365
- if (!implementation) throw new Error(`Implementation not found for wallet ${_this.address}`);
366
- const versions = Object.values(_this.contexts);
367
- for (let i = 0; i < versions.length; i++) {
368
- if (versions[i].mainModule === implementation || versions[i].mainModuleUpgradable === implementation) {
369
- return versions[i].version;
370
- }
371
- }
372
- throw new Error(`Version not found for implementation ${implementation}`);
373
- }();
374
- const onChainImageHashPromise = async function () {
375
- const deployedImageHash = await _this.reader(chainId).imageHash(_this.address);
376
- if (deployedImageHash) return deployedImageHash;
377
- const counterfactualImageHash = await counterfactualImageHashPromise;
378
- if (counterfactualImageHash) return counterfactualImageHash.imageHash;
379
- throw new Error(`On-chain imageHash not found for wallet ${_this.address}`);
380
- }();
381
- const onChainConfigPromise = async function () {
382
- const onChainImageHash = await onChainImageHashPromise;
383
- const onChainConfig = await _this.tracker.configOfImageHash({
384
- imageHash: onChainImageHash
385
- });
386
- if (onChainConfig) return onChainConfig;
387
- throw new Error(`On-chain config not found for imageHash ${onChainImageHash}`);
388
- }();
389
- const onChainVersion = await onChainVersionPromise;
390
- const onChainImageHash = await onChainImageHashPromise;
391
- let fromImageHash = onChainImageHash;
392
- let lastVersion = onChainVersion;
393
- let signedMigrations = [];
394
- if (onChainVersion !== this.version) {
395
- // We either need to use the presigned configuration updates, or we haven't performed
396
- // any updates yet, so we can only use the on-chain imageHash as-is
397
- const presignedMigrate = await this.migrator.getAllMigratePresignedTransaction({
398
- address: this.address,
399
- fromImageHash: onChainImageHash,
400
- fromVersion: onChainVersion,
401
- chainId
402
- });
403
-
404
- // The migrator returns the original version and imageHash
405
- // if no presigned migration is found, so no need to check here
406
- fromImageHash = presignedMigrate.lastImageHash;
407
- lastVersion = presignedMigrate.lastVersion;
408
- signedMigrations = presignedMigrate.signedMigrations;
409
- }
410
- const presigned = await this.tracker.loadPresignedConfiguration({
411
- wallet: this.address,
412
- fromImageHash: fromImageHash,
413
- longestPath
414
- });
415
- const imageHash = presigned && presigned.length > 0 ? presigned[presigned.length - 1].nextImageHash : fromImageHash;
416
- const config = await this.tracker.configOfImageHash({
417
- imageHash
418
- });
419
- if (!config) {
420
- throw new Error(`Config not found for imageHash ${imageHash}`);
421
- }
422
- const isDeployed = await isDeployedPromise;
423
- const counterfactualImageHash = await counterfactualImageHashPromise;
424
- const checkpoint = core.universal.coderFor(lastVersion).config.checkpointOf(config);
425
- return {
426
- original: _extends({}, counterfactualImageHash, {
427
- version: await counterFactualVersionPromise
428
- }),
429
- onChain: {
430
- imageHash: onChainImageHash,
431
- config: await onChainConfigPromise,
432
- version: onChainVersion,
433
- deployed: isDeployed
434
- },
435
- fullyMigrated: lastVersion === this.version,
436
- signedMigrations,
437
- version: lastVersion,
438
- presignedConfigurations: presigned,
439
- imageHash,
440
- config,
441
- checkpoint,
442
- canOnchainValidate: onChainVersion === this.version && isDeployed
443
- };
444
- }
445
- mustBeFullyMigrated(status) {
446
- if (!status.fullyMigrated) {
447
- throw new Error(`Wallet ${this.address} is not fully migrated`);
448
- }
449
- }
450
- async predecorateSignedTransactions(status, chainId) {
451
- // Request signed predecorate transactions from child wallets
452
- const bundles = await this.orchestrator.predecorateSignedTransactions({
453
- chainId
454
- });
455
- // Get signed predecorate transaction
456
- const predecorated = await this.predecorateTransactions([], status, chainId);
457
- if (core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0) {
458
- // Sign it
459
- bundles.push(await this.signTransactions(predecorated, chainId));
460
- }
461
- return bundles;
462
- }
463
- async predecorateTransactions(txs, status, chainId) {
464
- txs = Array.isArray(txs) ? txs : [txs];
465
- // if onchain wallet config is not up to date
466
- // then we should append an extra transaction that updates it
467
- // to the latest "lazy" state
468
- if (status.onChain.imageHash !== status.imageHash) {
469
- const wallet = this.walletForStatus(chainId, status);
470
- const updateConfig = await wallet.buildUpdateConfigurationTransaction(status.config);
471
- txs = [...txs, ...updateConfig.transactions];
472
- }
473
-
474
- // On immutable chains, we add the WalletProxyHook
475
- const {
476
- proxyImplementationHook
477
- } = this.contexts[status.config.version];
478
- if (proxyImplementationHook && (chainId === network.ChainId.IMMUTABLE_ZKEVM || chainId === network.ChainId.IMMUTABLE_ZKEVM_TESTNET)) {
479
- const provider = this.providerFor(chainId);
480
- if (provider) {
481
- const hook = new ethers.ethers.Contract(this.address, abi.walletContracts.walletProxyHook.abi, provider);
482
- let implementation;
483
- try {
484
- implementation = await hook.PROXY_getImplementation();
485
- } catch (e) {
486
- // Handle below
487
- console.log('Error getting implementation address', e);
488
- }
489
- if (!implementation || implementation === ethers.ethers.ZeroAddress) {
490
- console.log('Adding wallet proxy hook');
491
- const hooksInterface = new ethers.ethers.Interface(abi.walletContracts.moduleHooks.abi);
492
- const tx = {
493
- to: this.address,
494
- data: hooksInterface.encodeFunctionData(hooksInterface.getFunction('addHook'), ['0x90611127', proxyImplementationHook]),
495
- gasLimit: 50000,
496
- // Expected ~28k gas. Buffer added
497
- delegateCall: false,
498
- revertOnError: false,
499
- value: 0
500
- };
501
- txs = [tx, ...txs];
502
- }
503
- }
504
- }
505
- return txs;
506
- }
507
- async decorateTransactions(bundles, status, chainId) {
508
- var _chainId, _bundles$;
509
- if (!Array.isArray(bundles)) {
510
- // Recurse with array
511
- return this.decorateTransactions([bundles], status, chainId);
512
- }
513
-
514
- // Default to chainId of first bundle when not supplied
515
- chainId = (_chainId = chainId) != null ? _chainId : bundles[0].chainId;
516
- const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
517
- const hasBootstrapTxs = bootstrapBundle.transactions.length > 0;
518
- if (!hasBootstrapTxs && bundles.length === 1) {
519
- return bundles[0];
520
- }
521
-
522
- // Intent defaults to first bundle when no bootstrap transaction
523
- const {
524
- entrypoint
525
- } = hasBootstrapTxs ? bootstrapBundle : bundles[0];
526
- const decoratedBundle = {
527
- entrypoint,
528
- chainId,
529
- // Intent of the first bundle is used
530
- intent: (_bundles$ = bundles[0]) == null ? void 0 : _bundles$.intent,
531
- transactions: [...bootstrapBundle.transactions, ...bundles.map(bundle => ({
532
- to: bundle.entrypoint,
533
- data: core.commons.transaction.encodeBundleExecData(bundle),
534
- gasLimit: 0,
535
- delegateCall: false,
536
- revertOnError: true,
537
- value: 0
538
- }))]
539
- };
540
-
541
- // Re-compute the meta-transaction id to use the guest module subdigest
542
- if (!status.onChain.deployed) {
543
- const id = core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, decoratedBundle.transactions);
544
- if (decoratedBundle.intent === undefined) {
545
- decoratedBundle.intent = {
546
- id,
547
- wallet: this.address
548
- };
549
- } else {
550
- decoratedBundle.intent.id = id;
551
- }
552
- }
553
- return decoratedBundle;
554
- }
555
- async decorateSignature(signature, status) {
556
- if (!status.presignedConfigurations || status.presignedConfigurations.length === 0) {
557
- return signature;
558
- }
559
- const coder = this.coders.signature;
560
- const chain = status.presignedConfigurations.map(c => c.signature);
561
- const chainedSignature = coder.chainSignatures(signature, chain);
562
- return coder.trim(chainedSignature);
563
- }
564
- async publishWitnessFor(signers, chainId = 0) {
565
- const digest = ethers.ethers.id(`This is a Sequence account woo! ${Date.now()}`);
566
- const status = await this.status(chainId);
567
- const allOfAll = this.coders.config.fromSimple({
568
- threshold: signers.length,
569
- checkpoint: 0,
570
- signers: signers.map(s => ({
571
- address: s,
572
- weight: 1
573
- }))
574
- });
575
- const wallet = this.walletFor(chainId, status.original.context, allOfAll, this.coders);
576
- const signature = await wallet.signDigest(digest);
577
- const decoded = this.coders.signature.decode(signature);
578
- const signatures = this.coders.signature.signaturesOfDecoded(decoded);
579
- if (signatures.length === 0) {
580
- throw new Error('No signatures found');
581
- }
582
- return this.tracker.saveWitnesses({
583
- wallet: this.address,
584
- digest,
585
- chainId,
586
- signatures
587
- });
588
- }
589
- async publishWitness() {
590
- const digest = ethers.ethers.id(`This is a Sequence account woo! ${Date.now()}`);
591
- const signature = await this.signDigest(digest, 0, false);
592
- const decoded = this.coders.signature.decode(signature);
593
- const signatures = this.coders.signature.signaturesOfDecoded(decoded);
594
- return this.tracker.saveWitnesses({
595
- wallet: this.address,
596
- digest,
597
- chainId: 0,
598
- signatures
599
- });
600
- }
601
- async signDigest(digest, chainId, decorate = true, cantValidateBehavior = 'ignore', metadata) {
602
- // If we are signing a digest for chainId zero then we can never be fully migrated
603
- // because Sequence v1 doesn't allow for signing a message on "all chains"
604
-
605
- // So we ignore the state on "chain zero" and instead use one of the states of the networks
606
- // wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
607
- // the behaviour of being migrated on all chains
608
- const chainRef = BigInt(chainId) === 0n ? this.networks[0].chainId : chainId;
609
- const status = await this.status(chainRef);
610
- this.mustBeFullyMigrated(status);
611
-
612
- // Check if we can validate onchain and what to do if we can't
613
- // revert early, since there is no point in signing a digest now
614
- if (!status.canOnchainValidate && cantValidateBehavior === 'throw') {
615
- throw new Error('Wallet cannot validate onchain');
616
- }
617
- const wallet = this.walletForStatus(chainId, status);
618
- const signature = await wallet.signDigest(digest, metadata);
619
- const decorated = decorate ? this.decorateSignature(signature, status) : signature;
620
-
621
- // If the wallet can't validate onchain then we
622
- // need to prefix the decorated signature with all deployments and migrations
623
- // aka doing a bootstrap using EIP-6492
624
- if (!status.canOnchainValidate) {
625
- switch (cantValidateBehavior) {
626
- // NOTICE: We covered this case before signing the digest
627
- // case 'throw':
628
- // throw new Error('Wallet cannot validate on-chain')
629
- case 'ignore':
630
- return decorated;
631
- case 'eip6492':
632
- return this.buildEIP6492Signature(await decorated, status, chainId);
633
- }
634
- }
635
- return decorated;
636
- }
637
- buildOnChainSignature(digest) {
638
- const subdigest = core.commons.signature.subdigestOf({
639
- digest: ethers.ethers.hexlify(digest),
640
- chainId: 0,
641
- address: this.address
642
- });
643
- const hexSubdigest = ethers.ethers.hexlify(subdigest);
644
- const config = this.coders.config.fromSimple({
645
- // Threshold *only* needs to be > 0, this is not a magic number
646
- // we only use 2 ** 15 because it may lead to lower gas costs in some chains
647
- threshold: 32768,
648
- checkpoint: 0,
649
- signers: [],
650
- subdigests: [hexSubdigest]
651
- });
652
- const walletInterface = new ethers.ethers.Interface(abi.walletContracts.mainModule.abi);
653
- const bundle = {
654
- entrypoint: this.address,
655
- transactions: [{
656
- to: this.address,
657
- data: walletInterface.encodeFunctionData(
658
- // *NEVER* use updateImageHash here, as it would effectively destroy the wallet
659
- // setExtraImageHash sets an additional imageHash, without changing the current one
660
- 'setExtraImageHash', [this.coders.config.imageHashOf(config),
661
- // 2 ** 255 instead of max uint256, to have more zeros in the calldata
662
- '57896044618658097711785492504343953926634992332820282019728792003956564819968']),
663
- // Conservative gas limit, used because the current relayer
664
- // has trouble estimating gas for this transaction
665
- gasLimit: 250000
666
- }]
667
- };
668
-
669
- // Fire and forget request to save the config
670
- this.tracker.saveWalletConfig({
671
- config
672
- });
673
-
674
- // Encode a signature proof for the given subdigest
675
- // use `chainId = 0` to make it simpler, as this signature is only a proof
676
- const signature = this.coders.signature.encodeSigners(config, new Map(), [hexSubdigest], 0).encoded;
677
- return {
678
- bundle,
679
- signature
680
- };
681
- }
682
- async buildEIP6492Signature(signature, status, chainId) {
683
- const bootstrapBundle = await this.buildBootstrapTransactions(status, chainId);
684
- if (bootstrapBundle.transactions.length === 0) {
685
- throw new Error('Cannot build EIP-6492 signature without bootstrap transactions');
686
- }
687
- const encoded = ethers.ethers.AbiCoder.defaultAbiCoder().encode(['address', 'bytes', 'bytes'], [bootstrapBundle.entrypoint, core.commons.transaction.encodeBundleExecData(bootstrapBundle), signature]);
688
- return ethers.ethers.solidityPacked(['bytes', 'bytes32'], [encoded, core.commons.EIP6492.EIP_6492_SUFFIX]);
689
- }
690
- async editConfig(changes) {
691
- const currentConfig = await this.status(0).then(s => s.config);
692
- const newConfig = this.coders.config.editConfig(currentConfig, _extends({}, changes, {
693
- checkpoint: this.coders.config.checkpointOf(currentConfig) + 1n
694
- }));
695
- return this.updateConfig(newConfig);
696
- }
697
- async updateConfig(config) {
698
- // config should be for the current version of the wallet
699
- if (!this.coders.config.isWalletConfig(config)) {
700
- throw new Error(`Invalid config for wallet ${this.address}`);
701
- }
702
- const nextImageHash = this.coders.config.imageHashOf(config);
703
-
704
- // sign an update config struct
705
- const updateStruct = this.coders.signature.hashSetImageHash(nextImageHash);
706
-
707
- // sign the update struct, using chain id 0
708
- const signature = await this.signDigest(updateStruct, 0, false);
709
-
710
- // save the presigned transaction to the sessions tracker
711
- await this.tracker.savePresignedConfiguration({
712
- wallet: this.address,
713
- nextConfig: config,
714
- signature,
715
- referenceChainId: 1
716
- });
717
-
718
- // safety check, tracker should have a reverse lookup for the imageHash
719
- // outside of the local cache
720
- const reverseConfig = await this.tracker.configOfImageHash({
721
- imageHash: nextImageHash,
722
- noCache: true
723
- });
724
- if (!reverseConfig || this.coders.config.imageHashOf(reverseConfig) !== nextImageHash) {
725
- throw Error(`Reverse lookup failed for imageHash ${nextImageHash}`);
726
- }
727
- }
728
-
729
- /**
730
- * This method is used to bootstrap the wallet on a given chain.
731
- * this deploys the wallets and executes all the necessary transactions
732
- * for that wallet to start working with the given version.
733
- *
734
- * This usually involves: (a) deploying the wallet, (b) executing migrations
735
- *
736
- * Notice: It should NOT explicitly include chained signatures. Unless internally used
737
- * by any of the migrations.
738
- *
739
- */
740
- async buildBootstrapTransactions(status, chainId) {
741
- var _bundle$transactions;
742
- const bundle = await this.orchestrator.buildDeployTransaction({
743
- chainId
744
- });
745
- const transactions = (_bundle$transactions = bundle == null ? void 0 : bundle.transactions) != null ? _bundle$transactions : [];
746
-
747
- // Add wallet deployment if needed
748
- if (!status.onChain.deployed) {
749
- let gasLimit;
750
- switch (BigInt(chainId)) {
751
- case BigInt(network.ChainId.SKALE_NEBULA):
752
- gasLimit = 10000000n;
753
- break;
754
- case BigInt(network.ChainId.SOMNIA_TESTNET):
755
- gasLimit = 10000000n;
756
- break;
757
- }
758
-
759
- // Wallet deployment will vary depending on the version
760
- // so we need to use the context to get the correct deployment
761
- const deployTransaction = wallet.Wallet.buildDeployTransaction(status.original.context, status.original.imageHash, gasLimit);
762
- transactions.push(...deployTransaction.transactions);
763
- }
764
-
765
- // Get pending migrations
766
- transactions.push(...status.signedMigrations.map(m => ({
767
- to: m.tx.entrypoint,
768
- data: core.commons.transaction.encodeBundleExecData(m.tx),
769
- value: 0,
770
- gasLimit: 0,
771
- revertOnError: true,
772
- delegateCall: false
773
- })));
774
-
775
- // Build the transaction intent, if the transaction has migrations
776
- // then we should use one of the intents of the migrations (anyone will do)
777
- // if it doesn't, then the only intent we could use if the GuestModule one
778
- // ... but this may fail if the relayer uses a different GuestModule
779
- const id = status.signedMigrations.length > 0 ? status.signedMigrations[0].tx.intent.id : core.commons.transaction.subdigestOfGuestModuleTransactions(this.contexts[this.version].guestModule, chainId, transactions);
780
-
781
- // Everything is encoded as a bundle
782
- // using the GuestModule of the account version
783
- const {
784
- guestModule
785
- } = this.contextFor(status.version);
786
- return {
787
- entrypoint: guestModule,
788
- transactions,
789
- chainId,
790
- intent: {
791
- id,
792
- wallet: this.address
793
- }
794
- };
795
- }
796
- async bootstrapTransactions(chainId, prestatus) {
797
- const status = prestatus || (await this.status(chainId));
798
- return this.buildBootstrapTransactions(status, chainId);
799
- }
800
- async doBootstrap(chainId, feeQuote, prestatus) {
801
- const bootstrapTxs = await this.bootstrapTransactions(chainId, prestatus);
802
- return this.relayer(chainId).relay(_extends({}, bootstrapTxs, {
803
- chainId
804
- }), feeQuote);
805
- }
806
-
807
- /**
808
- * Signs a message.
809
- *
810
- * This method will sign the message using the account associated with this signer
811
- * and the specified chain ID. If the message is already prefixed with the EIP-191
812
- * prefix, it will be hashed directly. Otherwise, it will be prefixed before hashing.
813
- *
814
- * @param message - The message to sign. Can be a string or BytesLike.
815
- * @param chainId - The chain ID to use for signing
816
- * @param cantValidateBehavior - Behavior when the wallet cannot validate on-chain
817
- * @returns A Promise that resolves to the signature as a hexadecimal string
818
- */
819
- signMessage(message, chainId, cantValidateBehavior = 'ignore') {
820
- const messageHex = ethers.ethers.hexlify(message);
821
- const prefixHex = ethers.ethers.hexlify(ethers.ethers.toUtf8Bytes(ethers.MessagePrefix));
822
- let digest;
823
-
824
- // We check if the message is already prefixed with EIP-191
825
- // This will avoid breaking changes for codebases where the message is already prefixed
826
- if (messageHex.substring(2).startsWith(prefixHex.substring(2))) {
827
- digest = ethers.ethers.keccak256(message);
828
- } else {
829
- digest = ethers.ethers.hashMessage(message);
830
- }
831
- return this.signDigest(digest, chainId, true, cantValidateBehavior);
832
- }
833
- async signTransactions(txs, chainId, pstatus, options) {
834
- const status = pstatus || (await this.status(chainId));
835
- this.mustBeFullyMigrated(status);
836
- const wallet = this.walletForStatus(chainId, status);
837
- const metadata = {
838
- address: this.address,
839
- digest: '',
840
- // Set in wallet.signTransactions
841
- chainId,
842
- config: {
843
- version: this.version
844
- },
845
- decorate: true,
846
- cantValidateBehavior: 'ignore'
847
- };
848
- const nonceOptions = options != null && options.serial ? {
849
- serial: true
850
- } : (options == null ? void 0 : options.nonceSpace) !== undefined ? {
851
- space: options.nonceSpace
852
- } : undefined;
853
- const signed = await wallet.signTransactions(txs, nonceOptions, metadata);
854
- return _extends({}, signed, {
855
- signature: await this.decorateSignature(signed.signature, status)
856
- });
857
- }
858
- async signMigrations(chainId, editConfig) {
859
- const status = await this.status(chainId);
860
- if (status.fullyMigrated) return false;
861
- const wallet = this.walletForStatus(chainId, status);
862
- const nextConfig = editConfig(wallet.config);
863
- const signed = await this.migrator.signNextMigration(this.address, status.version, wallet, nextConfig);
864
- if (!signed) return false;
865
-
866
- // Make sure the tracker has a copy of the config
867
- // before attempting to save the migration
868
- // otherwise if this second step fails the tracker could end up
869
- // with a migration to an unknown config
870
- await this.tracker.saveWalletConfig({
871
- config: nextConfig
872
- });
873
- const nextCoder = core.universal.coderFor(nextConfig.version).config;
874
- const nextImageHash = nextCoder.imageHashOf(nextConfig);
875
- const reverseConfig = await this.tracker.configOfImageHash({
876
- imageHash: nextImageHash,
877
- noCache: true
878
- });
879
- if (!reverseConfig || nextCoder.imageHashOf(reverseConfig) !== nextImageHash) {
880
- throw Error(`Reverse lookup failed for imageHash ${nextImageHash}`);
881
- }
882
- await this.tracker.saveMigration(this.address, signed, this.contexts);
883
- return true;
884
- }
885
- async signAllMigrations(editConfig) {
886
- var _this2 = this;
887
- const failedChains = [];
888
- const signedMigrations = await Promise.all(this.networks.map(async function (n) {
889
- try {
890
- // Signing migrations for each chain
891
- return await _this2.signMigrations(n.chainId, editConfig);
892
- } catch (error) {
893
- console.warn(`Failed to sign migrations for chain ${n.chainId}`, error);
894
-
895
- // Adding failed chainId to the failedChains array
896
- failedChains.push(n.chainId);
897
- // Using null as a placeholder for failed chains
898
- return null;
899
- }
900
- }));
901
-
902
- // Filter out null values to get only the successful signed migrations
903
- const successfulSignedMigrations = signedMigrations.filter(migration => migration !== null);
904
- return {
905
- signedMigrations: successfulSignedMigrations,
906
- failedChains
907
- };
908
- }
909
- async isMigratedAllChains() {
910
- var _this3 = this;
911
- const failedChains = [];
912
- const statuses = await Promise.all(this.networks.map(async function (n) {
913
- try {
914
- return await _this3.status(n.chainId);
915
- } catch (error) {
916
- failedChains.push(n.chainId);
917
- console.warn(`Failed to get status for chain ${n.chainId}`, error);
918
-
919
- // default to true for failed chains
920
- return {
921
- fullyMigrated: true
922
- };
923
- }
924
- }));
925
- const migratedAllChains = statuses.every(s => s.fullyMigrated);
926
- return {
927
- migratedAllChains,
928
- failedChains
929
- };
930
- }
931
- async sendSignedTransactions(signedBundle, chainId, quote, pstatus, callback, projectAccessKey) {
932
- if (!Array.isArray(signedBundle)) {
933
- return this.sendSignedTransactions([signedBundle], chainId, quote, pstatus, callback, projectAccessKey);
934
- }
935
- const status = pstatus || (await this.status(chainId));
936
- this.mustBeFullyMigrated(status);
937
- const decoratedBundle = await this.decorateTransactions(signedBundle, status, chainId);
938
- callback == null || callback(decoratedBundle);
939
- return this.relayer(chainId).relay(decoratedBundle, quote, undefined, projectAccessKey);
940
- }
941
- async fillGasLimits(txs, chainId, status) {
942
- const wallet = this.walletForStatus(chainId, status || (await this.status(chainId)));
943
- return wallet.fillGasLimits(txs);
944
- }
945
- async gasRefundQuotes(txs, chainId, stubSignatureOverrides, status, options) {
946
- const wstatus = status || (await this.status(chainId));
947
- const wallet = this.walletForStatus(chainId, wstatus);
948
- const predecorated = await this.predecorateTransactions(txs, wstatus, chainId);
949
- const transactions = core.commons.transaction.fromTransactionish(this.address, predecorated);
950
-
951
- // We can't sign the transactions (because we don't want to bother the user)
952
- // so we use the latest configuration to build a "stub" signature, the relayer
953
- // knows to ignore the wallet signatures
954
- const stubSignature = wallet.coders.config.buildStubSignature(wallet.config, stubSignatureOverrides);
955
-
956
- // Now we can decorate the transactions as always, but we need to manually build the signed bundle
957
- const intentId = ethers.ethers.hexlify(ethers.ethers.randomBytes(32));
958
- const signedBundle = {
959
- chainId,
960
- intent: {
961
- id: intentId,
962
- wallet: this.address
963
- },
964
- signature: stubSignature,
965
- transactions,
966
- entrypoint: this.address,
967
- nonce: 0 // The relayer also ignored the nonce
968
- };
969
- const decoratedBundle = await this.decorateTransactions(signedBundle, wstatus);
970
- const data = core.commons.transaction.encodeBundleExecData(decoratedBundle);
971
- const res = await this.relayer(chainId).getFeeOptionsRaw(decoratedBundle.entrypoint, data, options);
972
- return _extends({}, res, {
973
- decorated: decoratedBundle
974
- });
975
- }
976
- async prepareTransactions(args) {
977
- const status = await this.status(args.chainId);
978
- const transactions = await this.fillGasLimits(args.txs, args.chainId, status);
979
- const gasRefundQuote = await this.gasRefundQuotes(transactions, args.chainId, args.stubSignatureOverrides, status, {
980
- simulate: args.simulateForFeeOptions,
981
- projectAccessKey: args.projectAccessKey
982
- });
983
- const flatDecorated = core.commons.transaction.unwind(this.address, gasRefundQuote.decorated.transactions);
984
- return {
985
- transactions,
986
- flatDecorated,
987
- feeOptions: gasRefundQuote.options,
988
- feeQuote: gasRefundQuote.quote
989
- };
990
- }
991
- async sendTransaction(txs, chainId, quote, skipPreDecorate = false, callback, options) {
992
- const status = await this.status(chainId);
993
- const predecorated = skipPreDecorate ? txs : await this.predecorateTransactions(txs, status, chainId);
994
- const hasTxs = core.commons.transaction.fromTransactionish(this.address, predecorated).length > 0;
995
- const signed = hasTxs ? await this.signTransactions(predecorated, chainId, undefined, options) : undefined;
996
- const childBundles = await this.orchestrator.predecorateSignedTransactions({
997
- chainId
998
- });
999
- const bundles = [];
1000
- if (signed !== undefined && signed.transactions.length > 0) {
1001
- bundles.push(signed);
1002
- }
1003
- bundles.push(...childBundles.filter(b => b.transactions.length > 0));
1004
- return this.sendSignedTransactions(bundles, chainId, quote, undefined, callback, options == null ? void 0 : options.projectAccessKey);
1005
- }
1006
- async signTypedData(domain, types, message, chainId, cantValidateBehavior = 'ignore') {
1007
- const digest = utils.encodeTypedDataDigest({
1008
- domain,
1009
- types,
1010
- message
1011
- });
1012
- return this.signDigest(digest, chainId, true, cantValidateBehavior);
1013
- }
1014
- async getSigners() {
1015
- var _this4 = this;
1016
- const last = ts => ts.length ? ts[ts.length - 1] : undefined;
1017
- return (await Promise.all(this.networks.map(async function ({
1018
- chainId,
1019
- name
1020
- }) {
1021
- try {
1022
- var _last;
1023
- const status = await _this4.status(chainId);
1024
- let latestImageHash = (_last = last(status.presignedConfigurations)) == null ? void 0 : _last.nextImageHash;
1025
- if (!latestImageHash) {
1026
- if (status.onChain.version !== status.version) {
1027
- const migration = last(status.signedMigrations);
1028
- if (migration) {
1029
- const {
1030
- toVersion,
1031
- toConfig
1032
- } = migration;
1033
- const _coder = core.universal.genericCoderFor(toVersion);
1034
- latestImageHash = _coder.config.imageHashOf(toConfig);
1035
- }
1036
- }
1037
- }
1038
- if (!latestImageHash) {
1039
- latestImageHash = status.onChain.imageHash;
1040
- }
1041
- const latestConfig = await _this4.tracker.configOfImageHash({
1042
- imageHash: latestImageHash
1043
- });
1044
- if (!latestConfig) {
1045
- throw new Error(`unable to find config for image hash ${latestImageHash}`);
1046
- }
1047
- const coder = core.universal.genericCoderFor(latestConfig.version);
1048
- const signers = coder.config.signersOf(latestConfig);
1049
- return signers.map(signer => _extends({}, signer, {
1050
- network: chainId
1051
- }));
1052
- } catch (error) {
1053
- console.warn(`unable to get signers on network ${chainId} ${name}`, error);
1054
- return [];
1055
- }
1056
- }))).flat();
1057
- }
1058
- async getAllSigners() {
1059
- var _this5 = this;
1060
- const allSigners = [];
1061
-
1062
- // We need to get the signers for each status
1063
- await Promise.all(this.networks.map(async function (network) {
1064
- const chainId = network.chainId;
1065
-
1066
- // Getting the status with `longestPath` set to true will give us all the possible configurations
1067
- // between the current onChain config and the latest config, including the ones "flagged for removal"
1068
- const status = await _this5.status(chainId, true);
1069
- const fullChain = [status.onChain.imageHash, ...(status.onChain.version !== status.version ? status.signedMigrations.map(m => core.universal.coderFor(m.toVersion).config.imageHashOf(m.toConfig)) : []), ...status.presignedConfigurations.map(update => update.nextImageHash)];
1070
- return Promise.all(fullChain.map(async function (nextImageHash, iconf) {
1071
- const isLast = iconf === fullChain.length - 1;
1072
- const config = await _this5.tracker.configOfImageHash({
1073
- imageHash: nextImageHash
1074
- });
1075
- if (!config) {
1076
- console.warn(`AllSigners may be incomplete, config not found for imageHash ${nextImageHash}`);
1077
- return;
1078
- }
1079
- const coder = core.universal.genericCoderFor(config.version);
1080
- const signers = coder.config.signersOf(config);
1081
- signers.forEach(signer => {
1082
- const exists = allSigners.find(s => s.address === signer.address && s.network === chainId);
1083
- if (exists && isLast && exists.flaggedForRemoval) {
1084
- exists.flaggedForRemoval = false;
1085
- return;
1086
- }
1087
- if (exists) return;
1088
- allSigners.push({
1089
- address: signer.address,
1090
- weight: signer.weight,
1091
- network: chainId,
1092
- flaggedForRemoval: !isLast
1093
- });
1094
- });
1095
- }));
1096
- }));
1097
- return allSigners;
1098
- }
1099
- }
1100
- function isAccount(value) {
1101
- return value instanceof Account;
1102
- }
1103
-
1104
- exports.Account = Account;
1105
- exports.isAccount = isAccount;