@0xsequence/account 2.3.18 → 2.3.20

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