@0xsequence/account 0.0.0-20230217200148

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,483 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var migration = require('@0xsequence/migration');
6
+ var ethers = require('ethers');
7
+ var core = require('@0xsequence/core');
8
+ var wallet = require('@0xsequence/wallet');
9
+ var relayer = require('@0xsequence/relayer');
10
+ var utils = require('@0xsequence/utils');
11
+
12
+ function _extends() {
13
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
14
+ for (var i = 1; i < arguments.length; i++) {
15
+ var source = arguments[i];
16
+ for (var key in source) {
17
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
18
+ target[key] = source[key];
19
+ }
20
+ }
21
+ }
22
+ return target;
23
+ };
24
+ return _extends.apply(this, arguments);
25
+ }
26
+
27
+ class Chain0Reader {
28
+ async isDeployed(_wallet) {
29
+ return false;
30
+ }
31
+ async implementation(_wallet) {
32
+ return undefined;
33
+ }
34
+ async imageHash(_wallet) {
35
+ return undefined;
36
+ }
37
+ async nonce(_wallet, _space) {
38
+ return ethers.ethers.constants.Zero;
39
+ }
40
+ async isValidSignature(_wallet, _digest, _signature) {
41
+ throw new Error('Method not supported.');
42
+ }
43
+ }
44
+ class Account {
45
+ constructor(options) {
46
+ this.address = void 0;
47
+ this.networks = void 0;
48
+ this.tracker = void 0;
49
+ this.contexts = void 0;
50
+ this.migrator = void 0;
51
+ this.migrations = void 0;
52
+ this.orchestrator = void 0;
53
+ this.address = ethers.ethers.utils.getAddress(options.address);
54
+ this.contexts = options.contexts;
55
+ this.tracker = options.tracker;
56
+ this.networks = options.networks;
57
+ this.orchestrator = options.orchestrator;
58
+ this.migrations = options.migrations || migration.defaults.DefaultMigrations;
59
+ this.migrator = new migration.migrator.Migrator(options.tracker, this.migrations, this.contexts);
60
+ }
61
+ static async new(options) {
62
+ var _options$migrations;
63
+ const mig = new migration.migrator.Migrator(options.tracker, (_options$migrations = options.migrations) != null ? _options$migrations : migration.defaults.DefaultMigrations, options.contexts);
64
+ const lastMigration = mig.lastMigration();
65
+ const lastCoder = lastMigration.configCoder;
66
+ const config = lastCoder.fromSimple(options.config);
67
+ const imageHash = lastCoder.imageHashOf(config);
68
+ const context = options.contexts[lastMigration.version];
69
+ const address = core.commons.context.addressOf(context, imageHash);
70
+ await options.tracker.saveCounterfactualWallet({
71
+ config,
72
+ context: Object.values(options.contexts)
73
+ });
74
+ return new Account({
75
+ address,
76
+ tracker: options.tracker,
77
+ contexts: options.contexts,
78
+ networks: options.networks,
79
+ orchestrator: options.orchestrator,
80
+ migrations: options.migrations
81
+ });
82
+ }
83
+ get version() {
84
+ return this.migrator.lastMigration().version;
85
+ }
86
+ get coders() {
87
+ const lastMigration = this.migrator.lastMigration();
88
+ return {
89
+ signature: lastMigration.signatureCoder,
90
+ config: lastMigration.configCoder
91
+ };
92
+ }
93
+ network(chainId) {
94
+ const tcid = ethers.ethers.BigNumber.from(chainId);
95
+ const found = this.networks.find(n => tcid.eq(n.chainId));
96
+ if (!found) throw new Error(`Network not found for chainId ${chainId}`);
97
+ return found;
98
+ }
99
+ provider(chainId) {
100
+ const found = this.network(chainId);
101
+ if (!found.provider && !found.rpcUrl) throw new Error(`Provider not found for chainId ${chainId}`);
102
+ return found.provider || new ethers.ethers.providers.JsonRpcProvider(found.rpcUrl);
103
+ }
104
+ reader(chainId) {
105
+ if (ethers.ethers.constants.Zero.eq(chainId)) return new Chain0Reader();
106
+
107
+ // TODO: Networks should be able to provide a reader directly
108
+ // and we should default to the on-chain reader
109
+ return new core.commons.reader.OnChainReader(this.provider(chainId));
110
+ }
111
+ relayer(chainId) {
112
+ const found = this.network(chainId);
113
+ if (!found.relayer) throw new Error(`Relayer not found for chainId ${chainId}`);
114
+ if (relayer.isRelayer(found.relayer)) return found.relayer;
115
+ return new relayer.RpcRelayer(found.relayer);
116
+ }
117
+ setOrchestrator(orchestrator) {
118
+ this.orchestrator = orchestrator;
119
+ }
120
+ contextFor(version) {
121
+ const ctx = this.contexts[version];
122
+ if (!ctx) throw new Error(`Context not found for version ${version}`);
123
+ return ctx;
124
+ }
125
+ walletForStatus(chainId, status) {
126
+ const coder = core.universal.coderFor(status.version);
127
+ return this.walletFor(chainId, this.contextFor(status.version), status.config, coder);
128
+ }
129
+ walletFor(chainId, context, config, coders) {
130
+ return new wallet.Wallet({
131
+ config,
132
+ context,
133
+ chainId,
134
+ coders,
135
+ address: this.address,
136
+ orchestrator: this.orchestrator,
137
+ reader: this.reader(chainId)
138
+ });
139
+ }
140
+
141
+ // Gets the current on-chain version of the wallet
142
+ // on a given network
143
+ async onchainVersionInfo(chainId) {
144
+ // First we need to use the tracker to get the counterfactual imageHash
145
+ const firstImageHash = await this.tracker.imageHashOfCounterfactualWallet({
146
+ wallet: this.address
147
+ });
148
+ if (!firstImageHash) {
149
+ throw new Error(`Counterfactual imageHash not found for wallet ${this.address}`);
150
+ }
151
+ const current = await migration.version.versionOf(this.address, firstImageHash.imageHash, this.contexts, this.reader(chainId));
152
+
153
+ // To find the first version, we need to try the firstImageHash
154
+ // with every context, and find the first one that matches
155
+ const first = migration.version.counterfactualVersion(this.address, firstImageHash.imageHash, Object.values(this.contexts));
156
+ return {
157
+ first: _extends({}, firstImageHash, {
158
+ version: first
159
+ }),
160
+ current
161
+ };
162
+ }
163
+
164
+ // Get the status of the account on a given network
165
+ // this does the following process:
166
+ // 1. Get the current on-chain status of the wallet (version + imageHash)
167
+ // 2. Get any pending migrations that have been signed by the wallet
168
+ // 3. Get any pending configuration updates that have been signed by the wallet
169
+ // 4. Fetch reverse lookups for both on-chain and pending configurations
170
+ async status(chainId, longestPath = false) {
171
+ const isDeployedPromise = this.reader(chainId).isDeployed(this.address);
172
+ const onChainVersionInfoPromise = this.onchainVersionInfo(chainId);
173
+ let onChainImageHash = await this.reader(chainId).imageHash(this.address);
174
+ if (!onChainImageHash) {
175
+ const counterfactualImageHash = await this.tracker.imageHashOfCounterfactualWallet({
176
+ wallet: this.address
177
+ });
178
+ onChainImageHash = counterfactualImageHash == null ? void 0 : counterfactualImageHash.imageHash;
179
+ }
180
+ if (!onChainImageHash) {
181
+ throw new Error(`On-chain imageHash not found for wallet ${this.address}`);
182
+ }
183
+ const onChainConfig = await this.tracker.configOfImageHash({
184
+ imageHash: onChainImageHash
185
+ });
186
+ if (!onChainConfig) {
187
+ throw new Error(`On-chain config not found for imageHash ${onChainImageHash}`);
188
+ }
189
+ const {
190
+ current: onChainVersion,
191
+ first: onChainFirstInfo
192
+ } = await onChainVersionInfoPromise;
193
+ let fromImageHash = onChainImageHash;
194
+ let version = onChainVersion;
195
+ let signedMigrations = [];
196
+ if (onChainVersion !== this.version) {
197
+ // We either need to use the presigned configuration updates, or we haven't performed
198
+ // any updates yet, so we can only use the on-chain imageHash as-is
199
+ const presignedMigrate = await this.migrator.getAllMigratePresignedTransaction({
200
+ address: this.address,
201
+ fromImageHash: onChainImageHash,
202
+ fromVersion: onChainVersion,
203
+ chainId
204
+ });
205
+
206
+ // The migrator returns the original version and imageHash
207
+ // if no presigned migration is found, so no need to check here
208
+ fromImageHash = presignedMigrate.lastImageHash;
209
+ version = presignedMigrate.lastVersion;
210
+ signedMigrations = presignedMigrate.signedMigrations;
211
+ }
212
+ const presigned = await this.tracker.loadPresignedConfiguration({
213
+ wallet: this.address,
214
+ fromImageHash: fromImageHash,
215
+ longestPath
216
+ });
217
+ const imageHash = presigned && presigned.length > 0 ? presigned[presigned.length - 1].nextImageHash : fromImageHash;
218
+ const config = await this.tracker.configOfImageHash({
219
+ imageHash
220
+ });
221
+ if (!config) {
222
+ throw new Error(`Config not found for imageHash ${imageHash}`);
223
+ }
224
+ const isDeployed = await isDeployedPromise;
225
+ const checkpoint = core.universal.coderFor(version).config.checkpointOf(config);
226
+ return {
227
+ original: onChainFirstInfo,
228
+ onChain: {
229
+ imageHash: onChainImageHash,
230
+ config: onChainConfig,
231
+ version: onChainVersion,
232
+ deployed: isDeployed
233
+ },
234
+ fullyMigrated: version === this.version,
235
+ signedMigrations,
236
+ version,
237
+ presignedConfigurations: presigned,
238
+ imageHash,
239
+ config,
240
+ checkpoint,
241
+ canOnchainValidate: version === this.version && isDeployed
242
+ };
243
+ }
244
+ mustBeFullyMigrated(status) {
245
+ if (!status.fullyMigrated) {
246
+ throw new Error(`Wallet ${this.address} is not fully migrated`);
247
+ }
248
+ }
249
+ decorateTransactions(bundle, status) {
250
+ const bootstrapBundle = this.buildBootstrapTransactions(status);
251
+ if (bootstrapBundle.transactions.length === 0) {
252
+ return bundle;
253
+ }
254
+ return {
255
+ entrypoint: bootstrapBundle.entrypoint,
256
+ chainId: bundle.chainId,
257
+ intent: bundle.intent,
258
+ transactions: [...bootstrapBundle.transactions, {
259
+ to: bundle.entrypoint,
260
+ data: core.commons.transaction.encodeBundleExecData(bundle),
261
+ gasLimit: 0,
262
+ delegateCall: false,
263
+ revertOnError: true,
264
+ value: 0
265
+ }]
266
+ };
267
+ }
268
+ decorateSignature(signature, status) {
269
+ if (status.presignedConfigurations.length === 0) {
270
+ return signature;
271
+ }
272
+ const coder = this.coders.signature;
273
+ const chain = status.presignedConfigurations.map(c => c.signature);
274
+ const chainedSignature = coder.chainSignatures(signature, chain);
275
+ return coder.encode(chainedSignature);
276
+ }
277
+ async publishWitness() {
278
+ const digest = ethers.ethers.utils.keccak256(ethers.ethers.utils.toUtf8Bytes(`This is a Sequence account woo! ${Date.now()}`));
279
+ const signature = await this.signDigest(digest, 0, false);
280
+ const decoded = this.coders.signature.decode(signature);
281
+ const signatures = this.coders.signature.signaturesOfDecoded(decoded);
282
+ return this.tracker.saveWitnesses({
283
+ wallet: this.address,
284
+ digest,
285
+ chainId: 0,
286
+ signatures
287
+ });
288
+ }
289
+ async signDigest(digest, chainId, decorate = true) {
290
+ // If we are signing a digest for chainId zero then we can never be fully migrated
291
+ // because Sequence v1 doesn't allow for signing a message on "all chains"
292
+
293
+ // So we ignore the state on "chain zero" and instead use one of the states of the networks
294
+ // wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
295
+ // the behaviour of being migrated on all chains
296
+
297
+ const chainRef = ethers.ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId;
298
+ const status = await this.status(chainRef);
299
+ this.mustBeFullyMigrated(status);
300
+ const wallet = this.walletForStatus(chainId, status);
301
+ const signature = await wallet.signDigest(digest);
302
+ return decorate ? this.decorateSignature(signature, status) : signature;
303
+ }
304
+ async editConfig(changes) {
305
+ const currentConfig = await this.status(0).then(s => s.config);
306
+ const newConfig = this.coders.config.editConfig(currentConfig, _extends({}, changes, {
307
+ checkpoint: this.coders.config.checkpointOf(currentConfig).add(1)
308
+ }));
309
+ return this.updateConfig(newConfig);
310
+ }
311
+ async updateConfig(config) {
312
+ // config should be for the current version of the wallet
313
+ if (!this.coders.config.isWalletConfig(config)) {
314
+ throw new Error(`Invalid config for wallet ${this.address}`);
315
+ }
316
+ const nextImageHash = this.coders.config.imageHashOf(config);
317
+
318
+ // sign an update config struct
319
+ const updateStruct = this.coders.signature.hashSetImageHash(nextImageHash);
320
+
321
+ // sign the update struct, using chain id 0
322
+ const signature = await this.signDigest(updateStruct, 0, false);
323
+
324
+ // save the presigned transaction to the sessions tracker
325
+ return this.tracker.savePresignedConfiguration({
326
+ wallet: this.address,
327
+ nextConfig: config,
328
+ signature
329
+ });
330
+ }
331
+
332
+ /**
333
+ * This method is used to bootstrap the wallet on a given chain.
334
+ * this deploys the wallets and executes all the necessary transactions
335
+ * for that wallet to start working with the given version.
336
+ *
337
+ * This usually involves: (a) deploying the wallet, (b) executing migrations
338
+ *
339
+ * Notice: It should NOT explicitly include chained signatures. Unless internally used
340
+ * by any of the migrations.
341
+ *
342
+ */
343
+ buildBootstrapTransactions(status) {
344
+ const transactions = [];
345
+
346
+ // Add wallet deployment if needed
347
+ if (!status.onChain.deployed) {
348
+ // Wallet deployment will vary depending on the version
349
+ // so we need to use the context to get the correct deployment
350
+ const deployTransaction = wallet.Wallet.buildDeployTransaction(status.original.context, status.original.imageHash);
351
+ transactions.push(...deployTransaction.transactions);
352
+ }
353
+
354
+ // Get pending migrations
355
+ transactions.push(...status.signedMigrations.map(m => ({
356
+ to: m.tx.entrypoint,
357
+ data: core.commons.transaction.encodeBundleExecData(m.tx),
358
+ value: 0,
359
+ gasLimit: 0,
360
+ revertOnError: true,
361
+ delegateCall: false
362
+ })));
363
+
364
+ // Everything is encoded as a bundle
365
+ // using the GuestModule of the account version
366
+ const {
367
+ guestModule
368
+ } = this.contextFor(status.version);
369
+ return {
370
+ entrypoint: guestModule,
371
+ transactions
372
+ };
373
+ }
374
+ async bootstrapTransactions(chainId) {
375
+ const status = await this.status(chainId);
376
+ return this.buildBootstrapTransactions(status);
377
+ }
378
+ async doBootstrap(chainId, feeQuote) {
379
+ const bootstrapTxs = await this.bootstrapTransactions(chainId);
380
+ const intended = core.commons.transaction.intendTransactionBundle(bootstrapTxs, this.address, chainId, ethers.ethers.utils.hexlify(ethers.ethers.utils.randomBytes(32)));
381
+ return this.relayer(chainId).relay(intended, feeQuote);
382
+ }
383
+ signMessage(message, chainId) {
384
+ return this.signDigest(ethers.ethers.utils.keccak256(message), chainId);
385
+ }
386
+ async signTransactions(txs, chainId) {
387
+ const status = await this.status(chainId);
388
+ this.mustBeFullyMigrated(status);
389
+ const wallet = this.walletForStatus(chainId, status);
390
+ const signed = await wallet.signTransactions(txs);
391
+ return _extends({}, signed, {
392
+ signature: this.decorateSignature(signed.signature, status)
393
+ });
394
+ }
395
+ async signMigrations(chainId, editConfig) {
396
+ const status = await this.status(chainId);
397
+ if (status.fullyMigrated) return false;
398
+ const wallet = this.walletForStatus(chainId, status);
399
+ const signed = await this.migrator.signNextMigration(this.address, status.version, wallet, editConfig(wallet.config));
400
+ if (!signed) return false;
401
+ await this.tracker.saveMigration(this.address, signed, this.contexts);
402
+ return true;
403
+ }
404
+ async signAllMigrations(editConfig) {
405
+ return Promise.all(this.networks.map(n => this.signMigrations(n.chainId, editConfig)));
406
+ }
407
+ async isMigratedAllChains() {
408
+ const statuses = await Promise.all(this.networks.map(n => this.status(n.chainId)));
409
+ return statuses.every(s => s.fullyMigrated);
410
+ }
411
+ async sendSignedTransactions(signedBundle, chainId, quote) {
412
+ const status = await this.status(signedBundle.chainId);
413
+ this.mustBeFullyMigrated(status);
414
+ const decoratedBundle = this.decorateTransactions(signedBundle, status);
415
+ return this.relayer(chainId).relay(decoratedBundle, quote);
416
+ }
417
+ async sendTransaction(txs, chainId, quote, skipLazyUpdate = false) {
418
+ if (!skipLazyUpdate) {
419
+ // if onchain wallet config is not up to date
420
+ // then we should append an extra transaction that updates it
421
+ // to the latest "lazy" state
422
+ const status = await this.status(chainId);
423
+ if (status.onChain.imageHash !== status.imageHash) {
424
+ const wallet = this.walletForStatus(chainId, status);
425
+ const updateConfig = await wallet.buildUpdateConfigurationTransaction(status.config);
426
+ txs = [Array.isArray(txs) ? txs : [txs], updateConfig.transactions].flat();
427
+ }
428
+ }
429
+ const signed = await this.signTransactions(txs, chainId);
430
+ return this.sendSignedTransactions(signed, chainId, quote);
431
+ }
432
+ async signTypedData(domain, types, message, chainId) {
433
+ const digest = utils.encodeTypedDataDigest({
434
+ domain,
435
+ types,
436
+ message
437
+ });
438
+ return this.signDigest(digest, chainId);
439
+ }
440
+ async getAllSigners() {
441
+ var _this = this;
442
+ const networks = this.networks;
443
+
444
+ // Getting all status with `longestPath` set to true will give us all the possible configurations
445
+ // between the current onChain config and the latest config, including the ones "flagged for removal"
446
+ const statuses = await Promise.all(networks.map(n => this.status(n.chainId, true)));
447
+ const allSigners = [];
448
+
449
+ // We need to get the signers for each status
450
+ await Promise.all(statuses.map(async function (status, inet) {
451
+ const chainId = networks[inet].chainId;
452
+ return Promise.all(status.presignedConfigurations.map(async function (update, iconf) {
453
+ const isLast = iconf === status.presignedConfigurations.length - 1;
454
+ const config = await _this.tracker.configOfImageHash({
455
+ imageHash: update.nextImageHash
456
+ });
457
+ if (!config) {
458
+ console.warn(`AllSigners may be incomplete, config not found for imageHash ${update.nextImageHash}`);
459
+ return;
460
+ }
461
+ const coder = core.universal.genericCoderFor(config.version);
462
+ const signers = coder.config.signersOf(config);
463
+ signers.forEach(signer => {
464
+ const exists = allSigners.find(s => s.address === signer.address && s.network === chainId);
465
+ if (exists && isLast && exists.flaggedForRemoval) {
466
+ exists.flaggedForRemoval = false;
467
+ return;
468
+ }
469
+ if (exists) return;
470
+ allSigners.push({
471
+ address: signer.address,
472
+ weight: signer.weight,
473
+ network: chainId,
474
+ flaggedForRemoval: !isLast
475
+ });
476
+ });
477
+ }));
478
+ }));
479
+ return allSigners;
480
+ }
481
+ }
482
+
483
+ exports.Account = Account;