@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.
package/src/account.ts ADDED
@@ -0,0 +1,678 @@
1
+
2
+ import { tracker } from '@0xsequence/sessions'
3
+ import { migrator, defaults, version } from '@0xsequence/migration'
4
+ import { Orchestrator } from '@0xsequence/signhub'
5
+ import { NetworkConfig } from '@0xsequence/network'
6
+ import { ethers, TypedDataDomain, TypedDataField } from 'ethers'
7
+ import { commons, universal } from '@0xsequence/core'
8
+ import { PresignedConfigLink } from '@0xsequence/sessions/src/tracker'
9
+ import { Wallet } from '@0xsequence/wallet'
10
+ import { FeeQuote, isRelayer, Relayer, RpcRelayer } from '@0xsequence/relayer'
11
+ import { encodeTypedDataDigest } from '@0xsequence/utils'
12
+
13
+ export type AccountStatus = {
14
+ original: {
15
+ version: number,
16
+ imageHash: string,
17
+ context: commons.context.WalletContext
18
+ }
19
+ onChain: {
20
+ imageHash: string,
21
+ config: commons.config.Config,
22
+ version: number,
23
+ deployed: boolean
24
+ },
25
+ fullyMigrated: boolean,
26
+ signedMigrations: migrator.SignedMigration[],
27
+ version: number,
28
+ presignedConfigurations: PresignedConfigLink[],
29
+ imageHash: string,
30
+ config: commons.config.Config,
31
+ checkpoint: ethers.BigNumberish,
32
+ canOnchainValidate: boolean,
33
+ }
34
+
35
+ export type AccountOptions = {
36
+ // The only unique identifier for a wallet is the address
37
+ address: string,
38
+
39
+ // The config tracker keeps track of chained configs,
40
+ // counterfactual addresses and reverse lookups for configurations
41
+ // it must implement both the ConfigTracker and MigrationTracker
42
+ tracker: tracker.ConfigTracker & migrator.PresignedMigrationTracker,
43
+
44
+ // Versioned contexts contains the context information for each Sequence version
45
+ contexts: commons.context.VersionedContext
46
+
47
+ // Optional list of migrations, if not provided, the default migrations will be used
48
+ // NOTICE: the last vestion is considered the "current" version for the account
49
+ migrations?: migrator.Migrations
50
+
51
+ // Orchestrator manages signing messages and transactions
52
+ orchestrator: Orchestrator
53
+
54
+ // Networks information and providers
55
+ networks: NetworkConfig[]
56
+ }
57
+
58
+ class Chain0Reader implements commons.reader.Reader {
59
+ async isDeployed(_wallet: string): Promise<boolean> {
60
+ return false
61
+ }
62
+
63
+ async implementation(_wallet: string): Promise<string | undefined> {
64
+ return undefined
65
+ }
66
+
67
+ async imageHash(_wallet: string): Promise<string | undefined> {
68
+ return undefined
69
+ }
70
+
71
+ async nonce(_wallet: string, _space: ethers.BigNumberish): Promise<ethers.BigNumberish> {
72
+ return ethers.constants.Zero
73
+ }
74
+
75
+ async isValidSignature(_wallet: string, _digest: ethers.utils.BytesLike, _signature: ethers.utils.BytesLike): Promise<boolean> {
76
+ throw new Error('Method not supported.')
77
+ }
78
+ }
79
+
80
+ export class Account {
81
+ public readonly address: string
82
+
83
+ public readonly networks: NetworkConfig[]
84
+ public readonly tracker: tracker.ConfigTracker & migrator.PresignedMigrationTracker
85
+ public readonly contexts: commons.context.VersionedContext
86
+
87
+ public readonly migrator: migrator.Migrator
88
+ public readonly migrations: migrator.Migrations
89
+
90
+ private orchestrator: Orchestrator
91
+
92
+ constructor(options: AccountOptions) {
93
+ this.address = ethers.utils.getAddress(options.address)
94
+
95
+ this.contexts = options.contexts
96
+ this.tracker = options.tracker
97
+ this.networks = options.networks
98
+ this.orchestrator = options.orchestrator
99
+
100
+ this.migrations = options.migrations || defaults.DefaultMigrations
101
+ this.migrator = new migrator.Migrator(options.tracker, this.migrations, this.contexts)
102
+ }
103
+
104
+ static async new(options: {
105
+ config: commons.config.SimpleConfig,
106
+ tracker: tracker.ConfigTracker & migrator.PresignedMigrationTracker,
107
+ contexts: commons.context.VersionedContext,
108
+ orchestrator: Orchestrator,
109
+ networks: NetworkConfig[],
110
+ migrations?: migrator.Migrations
111
+ }): Promise<Account> {
112
+ const mig = new migrator.Migrator(
113
+ options.tracker,
114
+ options.migrations ?? defaults.DefaultMigrations,
115
+ options.contexts
116
+ )
117
+
118
+ const lastMigration = mig.lastMigration()
119
+ const lastCoder = lastMigration.configCoder
120
+
121
+ const config = lastCoder.fromSimple(options.config)
122
+ const imageHash = lastCoder.imageHashOf(config)
123
+ const context = options.contexts[lastMigration.version]
124
+ const address = commons.context.addressOf(context, imageHash)
125
+
126
+ await options.tracker.saveCounterfactualWallet({ config, context: Object.values(options.contexts) })
127
+
128
+ return new Account({
129
+ address,
130
+ tracker: options.tracker,
131
+ contexts: options.contexts,
132
+ networks: options.networks,
133
+ orchestrator: options.orchestrator,
134
+ migrations: options.migrations
135
+ })
136
+ }
137
+
138
+ get version(): number {
139
+ return this.migrator.lastMigration().version
140
+ }
141
+
142
+ get coders(): {
143
+ signature: commons.signature.SignatureCoder,
144
+ config: commons.config.ConfigCoder,
145
+ } {
146
+ const lastMigration = this.migrator.lastMigration()
147
+
148
+ return {
149
+ signature: lastMigration.signatureCoder,
150
+ config: lastMigration.configCoder
151
+ }
152
+ }
153
+
154
+ network(chainId: ethers.BigNumberish): NetworkConfig {
155
+ const tcid = ethers.BigNumber.from(chainId)
156
+ const found = this.networks.find((n) => tcid.eq(n.chainId))
157
+ if (!found) throw new Error(`Network not found for chainId ${chainId}`)
158
+ return found
159
+ }
160
+
161
+ provider(chainId: ethers.BigNumberish): ethers.providers.Provider {
162
+ const found = this.network(chainId)
163
+ if (!found.provider && !found.rpcUrl) throw new Error(`Provider not found for chainId ${chainId}`)
164
+ return found.provider || new ethers.providers.JsonRpcProvider(found.rpcUrl)
165
+ }
166
+
167
+ reader(chainId: ethers.BigNumberish): commons.reader.Reader {
168
+ if (ethers.constants.Zero.eq(chainId)) return new Chain0Reader()
169
+
170
+ // TODO: Networks should be able to provide a reader directly
171
+ // and we should default to the on-chain reader
172
+ return new commons.reader.OnChainReader(this.provider(chainId))
173
+ }
174
+
175
+ relayer(chainId: ethers.BigNumberish): Relayer {
176
+ const found = this.network(chainId)
177
+ if (!found.relayer) throw new Error(`Relayer not found for chainId ${chainId}`)
178
+ if (isRelayer(found.relayer)) return found.relayer
179
+ return new RpcRelayer(found.relayer)
180
+ }
181
+
182
+ setOrchestrator(orchestrator: Orchestrator) {
183
+ this.orchestrator = orchestrator
184
+ }
185
+
186
+ contextFor(version: number): commons.context.WalletContext {
187
+ const ctx = this.contexts[version]
188
+ if (!ctx) throw new Error(`Context not found for version ${version}`)
189
+ return ctx
190
+ }
191
+
192
+ walletForStatus(
193
+ chainId: ethers.BigNumberish,
194
+ status: AccountStatus
195
+ ): Wallet {
196
+ const coder = universal.coderFor(status.version)
197
+ return this.walletFor(
198
+ chainId,
199
+ this.contextFor(status.version),
200
+ status.config,
201
+ coder
202
+ )
203
+ }
204
+
205
+ walletFor(
206
+ chainId: ethers.BigNumberish,
207
+ context: commons.context.WalletContext,
208
+ config: commons.config.Config,
209
+ coders: typeof this.coders
210
+ ): Wallet {
211
+ return new Wallet({
212
+ config,
213
+ context,
214
+ chainId,
215
+ coders,
216
+ address: this.address,
217
+ orchestrator: this.orchestrator,
218
+ reader: this.reader(chainId),
219
+ })
220
+ }
221
+
222
+ // Gets the current on-chain version of the wallet
223
+ // on a given network
224
+ async onchainVersionInfo(chainId: ethers.BigNumberish): Promise<{
225
+ first: {
226
+ imageHash: string,
227
+ context: commons.context.WalletContext,
228
+ version: number
229
+ }
230
+ current: number
231
+ }> {
232
+ // First we need to use the tracker to get the counterfactual imageHash
233
+ const firstImageHash = await this.tracker.imageHashOfCounterfactualWallet({
234
+ wallet: this.address
235
+ })
236
+
237
+ if (!firstImageHash) {
238
+ throw new Error(`Counterfactual imageHash not found for wallet ${this.address}`)
239
+ }
240
+
241
+ const current = await version.versionOf(
242
+ this.address,
243
+ firstImageHash.imageHash,
244
+ this.contexts,
245
+ this.reader(chainId)
246
+ )
247
+
248
+ // To find the first version, we need to try the firstImageHash
249
+ // with every context, and find the first one that matches
250
+ const first = version.counterfactualVersion(
251
+ this.address,
252
+ firstImageHash.imageHash,
253
+ Object.values(this.contexts),
254
+ )
255
+
256
+ return { first: { ...firstImageHash, version: first }, current }
257
+ }
258
+
259
+ // Get the status of the account on a given network
260
+ // this does the following process:
261
+ // 1. Get the current on-chain status of the wallet (version + imageHash)
262
+ // 2. Get any pending migrations that have been signed by the wallet
263
+ // 3. Get any pending configuration updates that have been signed by the wallet
264
+ // 4. Fetch reverse lookups for both on-chain and pending configurations
265
+ async status(chainId: ethers.BigNumberish, longestPath: boolean = false): Promise<AccountStatus> {
266
+ const isDeployedPromise = this.reader(chainId).isDeployed(this.address)
267
+ const onChainVersionInfoPromise = this.onchainVersionInfo(chainId)
268
+
269
+ let onChainImageHash = await this.reader(chainId).imageHash(this.address)
270
+ if (!onChainImageHash) {
271
+ const counterfactualImageHash = await this.tracker.imageHashOfCounterfactualWallet({
272
+ wallet: this.address
273
+ })
274
+
275
+ onChainImageHash = counterfactualImageHash?.imageHash
276
+ }
277
+
278
+ if (!onChainImageHash) {
279
+ throw new Error(`On-chain imageHash not found for wallet ${this.address}`)
280
+ }
281
+
282
+ const onChainConfig = await this.tracker.configOfImageHash({ imageHash: onChainImageHash })
283
+ if (!onChainConfig) {
284
+ throw new Error(`On-chain config not found for imageHash ${onChainImageHash}`)
285
+ }
286
+
287
+ const { current: onChainVersion, first: onChainFirstInfo } = await onChainVersionInfoPromise
288
+
289
+ let fromImageHash = onChainImageHash
290
+ let version = onChainVersion
291
+ let signedMigrations: migrator.SignedMigration[] = []
292
+
293
+ if (onChainVersion !== this.version) {
294
+ // We either need to use the presigned configuration updates, or we haven't performed
295
+ // any updates yet, so we can only use the on-chain imageHash as-is
296
+ const presignedMigrate = await this.migrator.getAllMigratePresignedTransaction({
297
+ address: this.address,
298
+ fromImageHash: onChainImageHash,
299
+ fromVersion: onChainVersion,
300
+ chainId
301
+ })
302
+
303
+ // The migrator returns the original version and imageHash
304
+ // if no presigned migration is found, so no need to check here
305
+ fromImageHash = presignedMigrate.lastImageHash
306
+ version = presignedMigrate.lastVersion
307
+
308
+ signedMigrations = presignedMigrate.signedMigrations
309
+ }
310
+
311
+ const presigned = await this.tracker.loadPresignedConfiguration({
312
+ wallet: this.address,
313
+ fromImageHash: fromImageHash,
314
+ longestPath
315
+ })
316
+
317
+ const imageHash = presigned && presigned.length > 0 ? presigned[presigned.length - 1].nextImageHash : fromImageHash
318
+ const config = await this.tracker.configOfImageHash({ imageHash })
319
+ if (!config) {
320
+ throw new Error(`Config not found for imageHash ${imageHash}`)
321
+ }
322
+
323
+ const isDeployed = await isDeployedPromise
324
+ const checkpoint = universal.coderFor(version).config.checkpointOf(config as any)
325
+
326
+ return {
327
+ original: onChainFirstInfo,
328
+ onChain: {
329
+ imageHash: onChainImageHash,
330
+ config: onChainConfig,
331
+ version: onChainVersion,
332
+ deployed: isDeployed
333
+ },
334
+ fullyMigrated: version === this.version,
335
+ signedMigrations,
336
+ version,
337
+ presignedConfigurations: presigned,
338
+ imageHash,
339
+ config,
340
+ checkpoint,
341
+ canOnchainValidate: (
342
+ version === this.version &&
343
+ isDeployed
344
+ )
345
+ }
346
+ }
347
+
348
+ private mustBeFullyMigrated(status: AccountStatus) {
349
+ if (!status.fullyMigrated) {
350
+ throw new Error(`Wallet ${this.address} is not fully migrated`)
351
+ }
352
+ }
353
+
354
+ decorateTransactions(
355
+ bundle: commons.transaction.IntendedTransactionBundle,
356
+ status: AccountStatus,
357
+ ): commons.transaction.IntendedTransactionBundle {
358
+ const bootstrapBundle = this.buildBootstrapTransactions(status)
359
+ if (bootstrapBundle.transactions.length === 0) {
360
+ return bundle
361
+ }
362
+
363
+ return {
364
+ entrypoint: bootstrapBundle.entrypoint,
365
+ chainId: bundle.chainId,
366
+ intent: bundle.intent,
367
+ transactions: [
368
+ ...bootstrapBundle.transactions,
369
+ {
370
+ to: bundle.entrypoint,
371
+ data: commons.transaction.encodeBundleExecData(bundle),
372
+ gasLimit: 0,
373
+ delegateCall: false,
374
+ revertOnError: true,
375
+ value: 0
376
+ }
377
+ ]
378
+ }
379
+ }
380
+
381
+ decorateSignature<T extends ethers.BytesLike>(
382
+ signature: T,
383
+ status: AccountStatus,
384
+ ): T | string {
385
+ if (status.presignedConfigurations.length === 0) {
386
+ return signature
387
+ }
388
+
389
+ const coder = this.coders.signature
390
+
391
+ const chain = status.presignedConfigurations.map((c) => c.signature)
392
+ const chainedSignature = coder.chainSignatures(signature, chain)
393
+ return coder.encode(chainedSignature)
394
+ }
395
+
396
+ async publishWitness(): Promise<void> {
397
+ const digest = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`This is a Sequence account woo! ${Date.now()}`))
398
+ const signature = await this.signDigest(digest, 0, false)
399
+ const decoded = this.coders.signature.decode(signature)
400
+ const signatures = this.coders.signature.signaturesOfDecoded(decoded)
401
+ return this.tracker.saveWitnesses({ wallet: this.address, digest, chainId: 0, signatures })
402
+ }
403
+
404
+ async signDigest(
405
+ digest: ethers.BytesLike,
406
+ chainId: ethers.BigNumberish,
407
+ decorate: boolean = true
408
+ ): Promise<string> {
409
+ // If we are signing a digest for chainId zero then we can never be fully migrated
410
+ // because Sequence v1 doesn't allow for signing a message on "all chains"
411
+
412
+ // So we ignore the state on "chain zero" and instead use one of the states of the networks
413
+ // wallet-webapp should ensure the wallet is as migrated as possible, trying to mimic
414
+ // the behaviour of being migrated on all chains
415
+
416
+ const chainRef = ethers.constants.Zero.eq(chainId) ? this.networks[0].chainId : chainId
417
+ const status = await this.status(chainRef)
418
+
419
+ this.mustBeFullyMigrated(status)
420
+
421
+ const wallet = this.walletForStatus(chainId, status)
422
+ const signature = await wallet.signDigest(digest)
423
+
424
+ return decorate ? this.decorateSignature(signature, status) : signature
425
+ }
426
+
427
+ async editConfig(
428
+ changes: {
429
+ add?: commons.config.SimpleSigner[];
430
+ remove?: string[];
431
+ threshold?: ethers.BigNumberish;
432
+ }
433
+ ): Promise<void> {
434
+ const currentConfig = await this.status(0).then((s) => s.config)
435
+ const newConfig = this.coders.config.editConfig(currentConfig, {
436
+ ...changes,
437
+ checkpoint: this.coders.config.checkpointOf(currentConfig).add(1)
438
+ })
439
+
440
+ return this.updateConfig(newConfig)
441
+ }
442
+
443
+ async updateConfig(
444
+ config: commons.config.Config
445
+ ): Promise<void> {
446
+ // config should be for the current version of the wallet
447
+ if (!this.coders.config.isWalletConfig(config)) {
448
+ throw new Error(`Invalid config for wallet ${this.address}`)
449
+ }
450
+
451
+ const nextImageHash = this.coders.config.imageHashOf(config)
452
+
453
+ // sign an update config struct
454
+ const updateStruct = this.coders.signature.hashSetImageHash(nextImageHash)
455
+
456
+ // sign the update struct, using chain id 0
457
+ const signature = await this.signDigest(updateStruct, 0, false)
458
+
459
+ // save the presigned transaction to the sessions tracker
460
+ return this.tracker.savePresignedConfiguration({
461
+ wallet: this.address,
462
+ nextConfig: config,
463
+ signature
464
+ })
465
+ }
466
+
467
+ /**
468
+ * This method is used to bootstrap the wallet on a given chain.
469
+ * this deploys the wallets and executes all the necessary transactions
470
+ * for that wallet to start working with the given version.
471
+ *
472
+ * This usually involves: (a) deploying the wallet, (b) executing migrations
473
+ *
474
+ * Notice: It should NOT explicitly include chained signatures. Unless internally used
475
+ * by any of the migrations.
476
+ *
477
+ */
478
+ buildBootstrapTransactions(
479
+ status: AccountStatus
480
+ ): commons.transaction.TransactionBundle {
481
+ const transactions: commons.transaction.Transaction[] = []
482
+
483
+ // Add wallet deployment if needed
484
+ if (!status.onChain.deployed) {
485
+ // Wallet deployment will vary depending on the version
486
+ // so we need to use the context to get the correct deployment
487
+ const deployTransaction = Wallet.buildDeployTransaction(
488
+ status.original.context,
489
+ status.original.imageHash
490
+ )
491
+
492
+ transactions.push(...deployTransaction.transactions)
493
+ }
494
+
495
+ // Get pending migrations
496
+ transactions.push(...status.signedMigrations.map((m) => ({
497
+ to: m.tx.entrypoint,
498
+ data: commons.transaction.encodeBundleExecData(m.tx),
499
+ value: 0,
500
+ gasLimit: 0,
501
+ revertOnError: true,
502
+ delegateCall: false
503
+ })))
504
+
505
+ // Everything is encoded as a bundle
506
+ // using the GuestModule of the account version
507
+ const { guestModule } = this.contextFor(status.version)
508
+ return { entrypoint: guestModule, transactions }
509
+ }
510
+
511
+ async bootstrapTransactions(
512
+ chainId: ethers.BigNumberish,
513
+ ): Promise<commons.transaction.TransactionBundle> {
514
+ const status = await this.status(chainId)
515
+ return this.buildBootstrapTransactions(status)
516
+ }
517
+
518
+ async doBootstrap(
519
+ chainId: ethers.BigNumberish,
520
+ feeQuote?: FeeQuote
521
+ ) {
522
+ const bootstrapTxs = await this.bootstrapTransactions(chainId)
523
+ const intended = commons.transaction.intendTransactionBundle(
524
+ bootstrapTxs,
525
+ this.address,
526
+ chainId,
527
+ ethers.utils.hexlify(ethers.utils.randomBytes(32))
528
+ )
529
+
530
+ return this.relayer(chainId).relay(intended, feeQuote)
531
+ }
532
+
533
+ signMessage(message: ethers.BytesLike, chainId: ethers.BigNumberish): Promise<string> {
534
+ return this.signDigest(ethers.utils.keccak256(message), chainId)
535
+ }
536
+
537
+ async signTransactions(
538
+ txs: commons.transaction.Transactionish,
539
+ chainId: ethers.BigNumberish
540
+ ): Promise<commons.transaction.SignedTransactionBundle> {
541
+ const status = await this.status(chainId)
542
+ this.mustBeFullyMigrated(status)
543
+
544
+ const wallet = this.walletForStatus(chainId, status)
545
+ const signed = await wallet.signTransactions(txs)
546
+
547
+ return {
548
+ ...signed,
549
+ signature: this.decorateSignature(signed.signature, status)
550
+ }
551
+ }
552
+
553
+ async signMigrations(chainId: ethers.BigNumberish, editConfig: (prevConfig: commons.config.Config) => commons.config.Config): Promise<boolean> {
554
+ const status = await this.status(chainId)
555
+ if (status.fullyMigrated) return false
556
+
557
+ const wallet = this.walletForStatus(chainId, status)
558
+ const signed = await this.migrator.signNextMigration(this.address, status.version, wallet, editConfig(wallet.config))
559
+ if (!signed) return false
560
+
561
+ await this.tracker.saveMigration(this.address, signed, this.contexts)
562
+ return true
563
+ }
564
+
565
+ async signAllMigrations(editConfig: (prevConfig: commons.config.Config) => commons.config.Config) {
566
+ return Promise.all(this.networks.map((n) => this.signMigrations(n.chainId, editConfig)))
567
+ }
568
+
569
+ async isMigratedAllChains(): Promise<boolean> {
570
+ const statuses = await Promise.all(this.networks.map((n) => this.status(n.chainId)))
571
+ return statuses.every((s) => s.fullyMigrated)
572
+ }
573
+
574
+ async sendSignedTransactions(
575
+ signedBundle: commons.transaction.IntendedTransactionBundle,
576
+ chainId: ethers.BigNumberish,
577
+ quote?: FeeQuote
578
+ ): Promise<ethers.providers.TransactionResponse> {
579
+ const status = await this.status(signedBundle.chainId)
580
+ this.mustBeFullyMigrated(status)
581
+
582
+ const decoratedBundle = this.decorateTransactions(signedBundle, status)
583
+
584
+ return this.relayer(chainId).relay(decoratedBundle, quote)
585
+ }
586
+
587
+ async sendTransaction(
588
+ txs: commons.transaction.Transactionish,
589
+ chainId: ethers.BigNumberish,
590
+ quote?: FeeQuote,
591
+ skipLazyUpdate: boolean = false
592
+ ): Promise<ethers.providers.TransactionResponse> {
593
+ if (!skipLazyUpdate) {
594
+ // if onchain wallet config is not up to date
595
+ // then we should append an extra transaction that updates it
596
+ // to the latest "lazy" state
597
+ const status = await this.status(chainId)
598
+
599
+ if (status.onChain.imageHash !== status.imageHash) {
600
+ const wallet = this.walletForStatus(chainId, status)
601
+ const updateConfig = await wallet.buildUpdateConfigurationTransaction(status.config)
602
+ txs = [(Array.isArray(txs) ? txs : [txs]), updateConfig.transactions].flat()
603
+ }
604
+ }
605
+
606
+ const signed = await this.signTransactions(txs, chainId)
607
+ return this.sendSignedTransactions(signed, chainId, quote)
608
+ }
609
+
610
+ async signTypedData(
611
+ domain: TypedDataDomain,
612
+ types: Record<string, Array<TypedDataField>>,
613
+ message: Record<string, any>,
614
+ chainId: ethers.BigNumberish
615
+ ): Promise<string> {
616
+ const digest = encodeTypedDataDigest({ domain, types, message })
617
+ return this.signDigest(digest, chainId)
618
+ }
619
+
620
+ async getAllSigners(): Promise<{
621
+ address: string,
622
+ weight: number,
623
+ network: number,
624
+ flaggedForRemoval: boolean
625
+ }[]> {
626
+ const networks = this.networks
627
+
628
+ // Getting all status with `longestPath` set to true will give us all the possible configurations
629
+ // between the current onChain config and the latest config, including the ones "flagged for removal"
630
+ const statuses = await Promise.all(networks.map((n) => this.status(n.chainId, true)))
631
+
632
+ const allSigners: {
633
+ address: string,
634
+ weight: number,
635
+ network: number,
636
+ flaggedForRemoval: boolean
637
+ }[] = []
638
+
639
+ // We need to get the signers for each status
640
+ await Promise.all(statuses.map(async (status, inet) => {
641
+ const chainId = networks[inet].chainId
642
+ return Promise.all(status.presignedConfigurations.map(async (update, iconf) => {
643
+ const isLast = iconf === status.presignedConfigurations.length - 1
644
+ const config = await this.tracker.configOfImageHash({ imageHash: update.nextImageHash })
645
+ if (!config) {
646
+ console.warn(`AllSigners may be incomplete, config not found for imageHash ${update.nextImageHash}`)
647
+ return
648
+ }
649
+
650
+ const coder = universal.genericCoderFor(config.version)
651
+ const signers = coder.config.signersOf(config)
652
+
653
+ signers.forEach((signer) => {
654
+ const exists = allSigners.find((s) => (
655
+ s.address === signer.address &&
656
+ s.network === chainId
657
+ ))
658
+
659
+ if (exists && isLast && exists.flaggedForRemoval) {
660
+ exists.flaggedForRemoval = false
661
+ return
662
+ }
663
+
664
+ if (exists) return
665
+
666
+ allSigners.push({
667
+ address: signer.address,
668
+ weight: signer.weight,
669
+ network: chainId,
670
+ flaggedForRemoval: !isLast
671
+ })
672
+ })
673
+ }))
674
+ }))
675
+
676
+ return allSigners
677
+ }
678
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export * from './account'