@aztec/node-keystore 4.0.0-nightly.20250907 → 4.0.0-nightly.20260107

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/dest/config.d.ts CHANGED
@@ -4,4 +4,4 @@ export type KeyStoreConfig = {
4
4
  };
5
5
  export declare const keyStoreConfigMappings: ConfigMappingsType<KeyStoreConfig>;
6
6
  export declare function getKeyStoreConfigFromEnv(): KeyStoreConfig;
7
- //# sourceMappingURL=config.d.ts.map
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLGtCQUFrQixFQUF5QixNQUFNLDBCQUEwQixDQUFDO0FBRTFGLE1BQU0sTUFBTSxjQUFjLEdBQUc7SUFDM0IsaUJBQWlCLEVBQUUsTUFBTSxHQUFHLFNBQVMsQ0FBQztDQUN2QyxDQUFDO0FBRUYsZUFBTyxNQUFNLHNCQUFzQixFQUFFLGtCQUFrQixDQUFDLGNBQWMsQ0FLckUsQ0FBQztBQUVGLHdCQUFnQix3QkFBd0IsSUFBSSxjQUFjLENBRXpEIn0=
package/dest/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export * from './schemas.js';
4
4
  export * from './signer.js';
5
5
  export * from './keystore_manager.js';
6
6
  export * from './config.js';
7
- //# sourceMappingURL=index.d.ts.map
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLFlBQVksQ0FBQztBQUMzQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsYUFBYSxDQUFDIn0=
@@ -3,10 +3,11 @@
3
3
  *
4
4
  * Manages keystore configuration and delegates signing operations to appropriate signers.
5
5
  */
6
- import type { EthSigner } from '@aztec/ethereum';
6
+ import type { EthSigner } from '@aztec/ethereum/eth-signer';
7
7
  import { Buffer32 } from '@aztec/foundation/buffer';
8
8
  import { EthAddress } from '@aztec/foundation/eth-address';
9
9
  import type { Signature } from '@aztec/foundation/eth-signature';
10
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
10
11
  import type { TypedDataDefinition } from 'viem';
11
12
  import type { EthAccounts, EthRemoteSignerConfig, KeyStore, ProverKeyStore, ValidatorKeyStore as ValidatorKeystoreConfig } from './types.js';
12
13
  /**
@@ -27,6 +28,11 @@ export declare class KeystoreManager {
27
28
  * @param keystore Parsed keystore configuration
28
29
  */
29
30
  constructor(keystore: KeyStore);
31
+ /**
32
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
33
+ * Should be called after construction if validation is needed.
34
+ */
35
+ validateSigners(): Promise<void>;
30
36
  /**
31
37
  * Validates that attester addresses are unique across all validators
32
38
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
@@ -39,12 +45,16 @@ export declare class KeystoreManager {
39
45
  * This is used at construction time to check for obvious duplicates without throwing for invalid inputs.
40
46
  */
41
47
  private extractAddressesWithoutSensitiveOperations;
48
+ /**
49
+ * Extract addresses from EthAccounts without sensitive operations (no decryption/derivation).
50
+ */
51
+ private extractAddressesFromEthAccountsNonSensitive;
42
52
  /**
43
53
  * Create signers for validator attester accounts
44
54
  */
45
55
  createAttesterSigners(validatorIndex: number): EthSigner[];
46
56
  /**
47
- * Create signers for validator publisher accounts (falls back to attester if not specified)
57
+ * Create signers for validator publisher accounts (falls back to keystore-level publisher, then to attester if not specified)
48
58
  */
49
59
  createPublisherSigners(validatorIndex: number): EthSigner[];
50
60
  createAllValidatorPublisherSigners(): EthSigner[];
@@ -68,13 +78,13 @@ export declare class KeystoreManager {
68
78
  */
69
79
  getValidatorCount(): number;
70
80
  /**
71
- * Get coinbase address for validator (falls back to first attester address)
81
+ * Get coinbase address for validator (falls back to keystore-level coinbase, then to the specific attester address)
72
82
  */
73
- getCoinbaseAddress(validatorIndex: number): EthAddress;
83
+ getCoinbaseAddress(validatorIndex: number, attesterAddress: EthAddress): EthAddress;
74
84
  /**
75
- * Get fee recipient for validator
85
+ * Get fee recipient for validator (falls back to keystore-level feeRecipient)
76
86
  */
77
- getFeeRecipient(validatorIndex: number): string;
87
+ getFeeRecipient(validatorIndex: number): AztecAddress;
78
88
  /**
79
89
  * Get the raw slasher configuration as provided in the keystore file.
80
90
  * @returns The slasher accounts configuration or undefined if not set
@@ -123,5 +133,7 @@ export declare class KeystoreManager {
123
133
  * Precedence: account-level override > validator-level config > file-level default
124
134
  */
125
135
  getEffectiveRemoteSignerConfig(validatorIndex: number, attesterAddress: EthAddress): EthRemoteSignerConfig | undefined;
136
+ /** Extract ETH accounts from AttesterAccounts */
137
+ private extractEthAccountsFromAttester;
126
138
  }
127
- //# sourceMappingURL=keystore_manager.d.ts.map
139
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia2V5c3RvcmVfbWFuYWdlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2tleXN0b3JlX21hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUNILE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzVELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDM0QsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDakUsT0FBTyxLQUFLLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFLaEUsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFLaEQsT0FBTyxLQUFLLEVBSVYsV0FBVyxFQUVYLHFCQUFxQixFQUNyQixRQUFRLEVBRVIsY0FBYyxFQUNkLGlCQUFpQixJQUFJLHVCQUF1QixFQUM3QyxNQUFNLFlBQVksQ0FBQztBQUVwQjs7R0FFRztBQUNILHFCQUFhLGFBQWMsU0FBUSxLQUFLO0lBR3BCLEtBQUssQ0FBQztJQUZ4QixZQUNFLE9BQU8sRUFBRSxNQUFNLEVBQ0MsS0FBSyxDQUFDLG1CQUFPLEVBSTlCO0NBQ0Y7QUFFRDs7R0FFRztBQUNILHFCQUFhLGVBQWU7SUFDMUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQVc7SUFFcEM7Ozs7T0FJRztJQUNILFlBQVksUUFBUSxFQUFFLFFBQVEsRUFHN0I7SUFFRDs7O09BR0c7SUFDRyxlQUFlLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQXNFckM7SUFFRDs7Ozs7T0FLRztJQUNILE9BQU8sQ0FBQywrQkFBK0I7SUFrQnZDOzs7T0FHRztJQUNILE9BQU8sQ0FBQywwQ0FBMEM7SUFLbEQ7O09BRUc7SUFDSCxPQUFPLENBQUMsMkNBQTJDO0lBK0NuRDs7T0FFRztJQUNILHFCQUFxQixDQUFDLGNBQWMsRUFBRSxNQUFNLEdBQUcsU0FBUyxFQUFFLENBSXpEO0lBRUQ7O09BRUc7SUFDSCxzQkFBc0IsQ0FBQyxjQUFjLEVBQUUsTUFBTSxHQUFHLFNBQVMsRUFBRSxDQW9CMUQ7SUFFRCxrQ0FBa0MsSUFBSSxTQUFTLEVBQUUsQ0FTaEQ7SUFFRDs7T0FFRztJQUNILG9CQUFvQixJQUFJLFNBQVMsRUFBRSxDQU1sQztJQUVEOztPQUVHO0lBQ0gsbUJBQW1CLElBQUk7UUFBRSxFQUFFLEVBQUUsVUFBVSxHQUFHLFNBQVMsQ0FBQztRQUFDLE9BQU8sRUFBRSxTQUFTLEVBQUUsQ0FBQTtLQUFFLEdBQUcsU0FBUyxDQStCdEY7SUFFRDs7T0FFRztJQUNILFlBQVksQ0FBQyxLQUFLLEVBQUUsTUFBTSxHQUFHLHVCQUF1QixDQUtuRDtJQUVEOztPQUVHO0lBQ0gsaUJBQWlCLElBQUksTUFBTSxDQUUxQjtJQUVEOztPQUVHO0lBQ0gsa0JBQWtCLENBQUMsY0FBYyxFQUFFLE1BQU0sRUFBRSxlQUFlLEVBQUUsVUFBVSxHQUFHLFVBQVUsQ0FjbEY7SUFFRDs7T0FFRztJQUNILGVBQWUsQ0FBQyxjQUFjLEVBQUUsTUFBTSxHQUFHLFlBQVksQ0FlcEQ7SUFFRDs7O09BR0c7SUFDSCxrQkFBa0IsSUFBSSxXQUFXLEdBQUcsU0FBUyxDQUU1QztJQUVEOzs7T0FHRztJQUNILGVBQWUsSUFBSSxjQUFjLEdBQUcsU0FBUyxDQUU1QztJQUVEOzs7T0FHRztJQUNILHVDQUF1QyxJQUFJLElBQUksQ0FtQjlDO0lBRUQ7O09BRUc7SUFDSCxPQUFPLENBQUMsNEJBQTRCO0lBK0JwQzs7T0FFRztJQUNILE9BQU8sQ0FBQywwQkFBMEI7SUEyQ2xDOztPQUVHO0lBQ0gsT0FBTyxDQUFDLHNCQUFzQjtJQWtEOUI7O09BRUc7SUFDSCxPQUFPLENBQUMsZ0NBQWdDO0lBd0J4Qzs7T0FFRztJQUNILE9BQU8sQ0FBQyx5QkFBeUI7SUE4QmpDOztPQUVHO0lBQ0csV0FBVyxDQUFDLE1BQU0sRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLFFBQVEsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBRTFFO0lBRUQ7O09BRUc7SUFDRyxhQUFhLENBQUMsTUFBTSxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsbUJBQW1CLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUV6RjtJQUVEOzs7T0FHRztJQUNILDhCQUE4QixDQUM1QixjQUFjLEVBQUUsTUFBTSxFQUN0QixlQUFlLEVBQUUsVUFBVSxHQUMxQixxQkFBcUIsR0FBRyxTQUFTLENBd0duQztJQUVELGlEQUFpRDtJQUNqRCxPQUFPLENBQUMsOEJBQThCO0NBeUJ2QyJ9
@@ -1 +1 @@
1
- {"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAKjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAIhD,OAAO,KAAK,EAEV,WAAW,EAKX,qBAAqB,EACrB,QAAQ,EACR,cAAc,EACd,iBAAiB,IAAI,uBAAuB,EAC7C,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGpB,KAAK,CAAC,EAAE,KAAK;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;;;OAIG;gBACS,QAAQ,EAAE,QAAQ;IAK9B;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;IAkBvC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IAiElD;;OAEG;IACH,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAK1D;;OAEG;IACH,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAc3D,kCAAkC,IAAI,SAAS,EAAE;IAWjD;;OAEG;IACH,oBAAoB,IAAI,SAAS,EAAE;IAQnC;;OAEG;IACH,mBAAmB,IAAI;QAAE,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,SAAS;IAwBvF;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAOpD;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU;IAgBtD;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM;IAK/C;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,SAAS;IAI7C;;;OAGG;IACH,eAAe,IAAI,cAAc,GAAG,SAAS;IAI7C;;;OAGG;IACH,uCAAuC,IAAI,IAAI;IAqB/C;;OAEG;IACH,OAAO,CAAC,4BAA4B;IA+BpC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA+ClC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkD9B;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAwBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAI3E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAI1F;;;OAGG;IACH,8BAA8B,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,UAAU,GAC1B,qBAAqB,GAAG,SAAS;CAiIrC"}
1
+ {"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAKhD,OAAO,KAAK,EAIV,WAAW,EAEX,qBAAqB,EACrB,QAAQ,EAER,cAAc,EACd,iBAAiB,IAAI,uBAAuB,EAC7C,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGpB,KAAK,CAAC;IAFxB,YACE,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,mBAAO,EAI9B;CACF;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;;;OAIG;IACH,YAAY,QAAQ,EAAE,QAAQ,EAG7B;IAED;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAsErC;IAED;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;IAkBvC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IAKlD;;OAEG;IACH,OAAO,CAAC,2CAA2C;IA+CnD;;OAEG;IACH,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE,CAIzD;IAED;;OAEG;IACH,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE,CAoB1D;IAED,kCAAkC,IAAI,SAAS,EAAE,CAShD;IAED;;OAEG;IACH,oBAAoB,IAAI,SAAS,EAAE,CAMlC;IAED;;OAEG;IACH,mBAAmB,IAAI;QAAE,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,SAAS,CA+BtF;IAED;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB,CAKnD;IAED;;OAEG;IACH,iBAAiB,IAAI,MAAM,CAE1B;IAED;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,UAAU,GAAG,UAAU,CAclF;IAED;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY,CAepD;IAED;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,SAAS,CAE5C;IAED;;;OAGG;IACH,eAAe,IAAI,cAAc,GAAG,SAAS,CAE5C;IAED;;;OAGG;IACH,uCAAuC,IAAI,IAAI,CAmB9C;IAED;;OAEG;IACH,OAAO,CAAC,4BAA4B;IA+BpC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA2ClC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkD9B;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAwBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAE1E;IAED;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAEzF;IAED;;;OAGG;IACH,8BAA8B,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,UAAU,GAC1B,qBAAqB,GAAG,SAAS,CAwGnC;IAED,iDAAiD;IACjD,OAAO,CAAC,8BAA8B;CAyBvC"}
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * Manages keystore configuration and delegates signing operations to appropriate signers.
5
5
  */ import { Buffer32 } from '@aztec/foundation/buffer';
6
- import { EthAddress } from '@aztec/foundation/eth-address';
7
6
  import { Wallet } from '@ethersproject/wallet';
8
7
  import { readFileSync, readdirSync, statSync } from 'fs';
9
8
  import { extname, join } from 'path';
10
9
  import { mnemonicToAccount } from 'viem/accounts';
10
+ import { ethPrivateKeySchema } from './schemas.js';
11
11
  import { LocalSigner, RemoteSigner } from './signer.js';
12
12
  /**
13
13
  * Error thrown when keystore operations fail
@@ -31,6 +31,70 @@ import { LocalSigner, RemoteSigner } from './signer.js';
31
31
  this.validateUniqueAttesterAddresses();
32
32
  }
33
33
  /**
34
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
35
+ * Should be called after construction if validation is needed.
36
+ */ async validateSigners() {
37
+ // Collect all remote signers with their addresses grouped by URL
38
+ const remoteSignersByUrl = new Map();
39
+ // Helper to extract remote signer URL from config
40
+ const getUrl = (config)=>{
41
+ return typeof config === 'string' ? config : config.remoteSignerUrl;
42
+ };
43
+ // Helper to collect remote signers from accounts
44
+ const collectRemoteSigners = (accounts, defaultRemoteSigner)=>{
45
+ const processAccount = (account)=>{
46
+ if (typeof account === 'object' && !('path' in account) && !('mnemonic' in account)) {
47
+ // This is a remote signer account
48
+ const remoteSigner = account;
49
+ const address = 'address' in remoteSigner ? remoteSigner.address : remoteSigner;
50
+ let url;
51
+ if ('remoteSignerUrl' in remoteSigner && remoteSigner.remoteSignerUrl) {
52
+ url = remoteSigner.remoteSignerUrl;
53
+ } else if (defaultRemoteSigner) {
54
+ url = getUrl(defaultRemoteSigner);
55
+ } else {
56
+ return; // No remote signer URL available
57
+ }
58
+ if (!remoteSignersByUrl.has(url)) {
59
+ remoteSignersByUrl.set(url, new Set());
60
+ }
61
+ remoteSignersByUrl.get(url).add(address.toString());
62
+ }
63
+ };
64
+ if (Array.isArray(accounts)) {
65
+ accounts.forEach((account)=>collectRemoteSigners(account, defaultRemoteSigner));
66
+ } else if (typeof accounts === 'object' && 'mnemonic' in accounts) {
67
+ // Skip mnemonic configs
68
+ } else {
69
+ processAccount(accounts);
70
+ }
71
+ };
72
+ // Collect from validators
73
+ const validatorCount = this.getValidatorCount();
74
+ for(let i = 0; i < validatorCount; i++){
75
+ const validator = this.getValidator(i);
76
+ const remoteSigner = validator.remoteSigner || this.keystore.remoteSigner;
77
+ collectRemoteSigners(this.extractEthAccountsFromAttester(validator.attester), remoteSigner);
78
+ if (validator.publisher) {
79
+ collectRemoteSigners(validator.publisher, remoteSigner);
80
+ }
81
+ }
82
+ // Collect from slasher
83
+ if (this.keystore.slasher) {
84
+ collectRemoteSigners(this.keystore.slasher, this.keystore.remoteSigner);
85
+ }
86
+ // Collect from prover
87
+ if (this.keystore.prover && typeof this.keystore.prover === 'object' && 'publisher' in this.keystore.prover) {
88
+ collectRemoteSigners(this.keystore.prover.publisher, this.keystore.remoteSigner);
89
+ }
90
+ // Validate each remote signer URL with all its addresses
91
+ for (const [url, addresses] of remoteSignersByUrl.entries()){
92
+ if (addresses.size > 0) {
93
+ await RemoteSigner.validateAccess(url, Array.from(addresses));
94
+ }
95
+ }
96
+ }
97
+ /**
34
98
  * Validates that attester addresses are unique across all validators
35
99
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
36
100
  * these are validated when decrypting the JSON-V3 keystore files
@@ -54,58 +118,47 @@ import { LocalSigner, RemoteSigner } from './signer.js';
54
118
  * Best-effort address extraction that avoids decryption/derivation (no JSON-V3 or mnemonic processing).
55
119
  * This is used at construction time to check for obvious duplicates without throwing for invalid inputs.
56
120
  */ extractAddressesWithoutSensitiveOperations(accounts) {
121
+ const ethAccounts = this.extractEthAccountsFromAttester(accounts);
122
+ return this.extractAddressesFromEthAccountsNonSensitive(ethAccounts);
123
+ }
124
+ /**
125
+ * Extract addresses from EthAccounts without sensitive operations (no decryption/derivation).
126
+ */ extractAddressesFromEthAccountsNonSensitive(accounts) {
57
127
  const results = [];
58
128
  const handleAccount = (account)=>{
59
- // String cases: private key or address or remote signer address
60
129
  if (typeof account === 'string') {
61
130
  if (account.startsWith('0x') && account.length === 66) {
62
- // Private key -> derive address locally without external deps
63
131
  try {
64
- const signer = new LocalSigner(Buffer32.fromString(account));
132
+ const signer = new LocalSigner(Buffer32.fromString(ethPrivateKeySchema.parse(account)));
65
133
  results.push(signer.address);
66
134
  } catch {
67
- // Ignore invalid private key at construction time
135
+ // ignore invalid private key at construction time
68
136
  }
69
- return;
70
137
  }
71
- if (account.startsWith('0x') && account.length === 42) {
72
- // Address string
73
- try {
74
- results.push(EthAddress.fromString(account));
75
- } catch {
76
- // Ignore invalid address format at construction time
77
- }
78
- return;
79
- }
80
- // Any other string cannot be confidently resolved here
81
138
  return;
82
139
  }
83
- // JSON V3 keystore: skip (requires decryption)
84
140
  if ('path' in account) {
85
141
  return;
86
142
  }
87
- // Mnemonic: skip (requires derivation and may throw on invalid mnemonics)
88
143
  if ('mnemonic' in account) {
89
144
  return;
90
145
  }
91
- // Remote signer account (object form)
92
146
  const remoteSigner = account;
93
- const address = typeof remoteSigner === 'string' ? remoteSigner : remoteSigner.address;
94
- if (address) {
95
- try {
96
- results.push(EthAddress.fromString(address));
97
- } catch {
98
- // Ignore invalid address format at construction time
99
- }
147
+ if ('address' in remoteSigner) {
148
+ results.push(remoteSigner.address);
149
+ return;
100
150
  }
151
+ results.push(remoteSigner);
101
152
  };
102
153
  if (Array.isArray(accounts)) {
103
154
  for (const account of accounts){
104
- const subResults = this.extractAddressesWithoutSensitiveOperations(account);
105
- results.push(...subResults);
155
+ handleAccount(account);
106
156
  }
107
157
  return results;
108
158
  }
159
+ if (typeof accounts === 'object' && accounts !== null && 'mnemonic' in accounts) {
160
+ return results;
161
+ }
109
162
  handleAccount(accounts);
110
163
  return results;
111
164
  }
@@ -113,15 +166,20 @@ import { LocalSigner, RemoteSigner } from './signer.js';
113
166
  * Create signers for validator attester accounts
114
167
  */ createAttesterSigners(validatorIndex) {
115
168
  const validator = this.getValidator(validatorIndex);
116
- return this.createSignersFromEthAccounts(validator.attester, validator.remoteSigner || this.keystore.remoteSigner);
169
+ const ethAccounts = this.extractEthAccountsFromAttester(validator.attester);
170
+ return this.createSignersFromEthAccounts(ethAccounts, validator.remoteSigner || this.keystore.remoteSigner);
117
171
  }
118
172
  /**
119
- * Create signers for validator publisher accounts (falls back to attester if not specified)
173
+ * Create signers for validator publisher accounts (falls back to keystore-level publisher, then to attester if not specified)
120
174
  */ createPublisherSigners(validatorIndex) {
121
175
  const validator = this.getValidator(validatorIndex);
122
176
  if (validator.publisher) {
123
177
  return this.createSignersFromEthAccounts(validator.publisher, validator.remoteSigner || this.keystore.remoteSigner);
124
178
  }
179
+ // Fall back to keystore-level publisher
180
+ if (this.keystore.publisher) {
181
+ return this.createSignersFromEthAccounts(this.keystore.publisher, validator.remoteSigner || this.keystore.remoteSigner);
182
+ }
125
183
  // Fall back to attester signers
126
184
  return this.createAttesterSigners(validatorIndex);
127
185
  }
@@ -147,7 +205,7 @@ import { LocalSigner, RemoteSigner } from './signer.js';
147
205
  if (!this.keystore.prover) {
148
206
  return undefined;
149
207
  }
150
- // Handle simple prover case (just a private key)
208
+ // Handle prover being a private key, JSON key store or remote signer with nested address
151
209
  if (typeof this.keystore.prover === 'string' || 'path' in this.keystore.prover || 'address' in this.keystore.prover) {
152
210
  const signers = this.createSignersFromEthAccounts(this.keystore.prover, this.keystore.remoteSigner);
153
211
  return {
@@ -155,10 +213,19 @@ import { LocalSigner, RemoteSigner } from './signer.js';
155
213
  signers
156
214
  };
157
215
  }
158
- const id = EthAddress.fromString(this.keystore.prover.id);
159
- const signers = this.createSignersFromEthAccounts(this.keystore.prover.publisher, this.keystore.remoteSigner);
216
+ // Handle prover as Id and specified publishers
217
+ if ('id' in this.keystore.prover) {
218
+ const id = this.keystore.prover.id;
219
+ const signers = this.createSignersFromEthAccounts(this.keystore.prover.publisher, this.keystore.remoteSigner);
220
+ return {
221
+ id,
222
+ signers
223
+ };
224
+ }
225
+ // Here, prover is just an EthAddress for a remote signer
226
+ const signers = this.createSignersFromEthAccounts(this.keystore.prover, this.keystore.remoteSigner);
160
227
  return {
161
- id,
228
+ id: undefined,
162
229
  signers
163
230
  };
164
231
  }
@@ -176,24 +243,31 @@ import { LocalSigner, RemoteSigner } from './signer.js';
176
243
  return this.keystore.validators?.length || 0;
177
244
  }
178
245
  /**
179
- * Get coinbase address for validator (falls back to first attester address)
180
- */ getCoinbaseAddress(validatorIndex) {
246
+ * Get coinbase address for validator (falls back to keystore-level coinbase, then to the specific attester address)
247
+ */ getCoinbaseAddress(validatorIndex, attesterAddress) {
181
248
  const validator = this.getValidator(validatorIndex);
182
249
  if (validator.coinbase) {
183
- return EthAddress.fromString(validator.coinbase);
250
+ return validator.coinbase;
184
251
  }
185
- // Fall back to first attester address
186
- const attesterSigners = this.createAttesterSigners(validatorIndex);
187
- if (attesterSigners.length === 0) {
188
- throw new KeystoreError(`No attester signers found for validator ${validatorIndex}`);
252
+ // Fall back to keystore-level coinbase
253
+ if (this.keystore.coinbase) {
254
+ return this.keystore.coinbase;
189
255
  }
190
- return attesterSigners[0].address;
256
+ // Fall back to the specific attester address
257
+ return attesterAddress;
191
258
  }
192
259
  /**
193
- * Get fee recipient for validator
260
+ * Get fee recipient for validator (falls back to keystore-level feeRecipient)
194
261
  */ getFeeRecipient(validatorIndex) {
195
262
  const validator = this.getValidator(validatorIndex);
196
- return validator.feeRecipient;
263
+ if (validator.feeRecipient) {
264
+ return validator.feeRecipient;
265
+ }
266
+ // Fall back to keystore-level feeRecipient
267
+ if (this.keystore.feeRecipient) {
268
+ return this.keystore.feeRecipient;
269
+ }
270
+ throw new KeystoreError(`No feeRecipient configured for validator ${validatorIndex}. You can set it at validator or keystore level.`);
197
271
  }
198
272
  /**
199
273
  * Get the raw slasher configuration as provided in the keystore file.
@@ -215,7 +289,7 @@ import { LocalSigner, RemoteSigner } from './signer.js';
215
289
  const validatorCount = this.getValidatorCount();
216
290
  for(let validatorIndex = 0; validatorIndex < validatorCount; validatorIndex++){
217
291
  const validator = this.getValidator(validatorIndex);
218
- const signers = this.createSignersFromEthAccounts(validator.attester, validator.remoteSigner || this.keystore.remoteSigner);
292
+ const signers = this.createSignersFromEthAccounts(this.extractEthAccountsFromAttester(validator.attester), validator.remoteSigner || this.keystore.remoteSigner);
219
293
  for (const signer of signers){
220
294
  const address = signer.address.toString().toLowerCase();
221
295
  if (seenAddresses.has(address)) {
@@ -261,13 +335,9 @@ import { LocalSigner, RemoteSigner } from './signer.js';
261
335
  if (typeof account === 'string') {
262
336
  if (account.startsWith('0x') && account.length === 66) {
263
337
  // Private key
264
- return new LocalSigner(Buffer32.fromString(account));
338
+ return new LocalSigner(Buffer32.fromString(ethPrivateKeySchema.parse(account)));
265
339
  } else {
266
- // Remote signer address only - use default remote signer config
267
- if (!defaultRemoteSigner) {
268
- throw new KeystoreError(`No remote signer configuration found for address ${account}`);
269
- }
270
- return new RemoteSigner(EthAddress.fromString(account), defaultRemoteSigner);
340
+ throw new Error(`Invalid private key`);
271
341
  }
272
342
  }
273
343
  // JSON V3 keystore
@@ -277,23 +347,23 @@ import { LocalSigner, RemoteSigner } from './signer.js';
277
347
  }
278
348
  // Remote signer account
279
349
  const remoteSigner = account;
280
- if (typeof remoteSigner === 'string') {
281
- // Just an address - use default config
282
- if (!defaultRemoteSigner) {
283
- throw new KeystoreError(`No remote signer configuration found for address ${remoteSigner}`);
350
+ if ('address' in remoteSigner) {
351
+ // Remote signer with config
352
+ const config = remoteSigner.remoteSignerUrl ? {
353
+ remoteSignerUrl: remoteSigner.remoteSignerUrl,
354
+ certPath: remoteSigner.certPath,
355
+ certPass: remoteSigner.certPass
356
+ } : defaultRemoteSigner;
357
+ if (!config) {
358
+ throw new KeystoreError(`No remote signer configuration found for address ${remoteSigner.address}`);
284
359
  }
285
- return new RemoteSigner(EthAddress.fromString(remoteSigner), defaultRemoteSigner);
286
- }
287
- // Remote signer with config
288
- const config = remoteSigner.remoteSignerUrl ? {
289
- remoteSignerUrl: remoteSigner.remoteSignerUrl,
290
- certPath: remoteSigner.certPath,
291
- certPass: remoteSigner.certPass
292
- } : defaultRemoteSigner;
293
- if (!config) {
294
- throw new KeystoreError(`No remote signer configuration found for address ${remoteSigner.address}`);
295
- }
296
- return new RemoteSigner(EthAddress.fromString(remoteSigner.address), config);
360
+ return new RemoteSigner(remoteSigner.address, config);
361
+ }
362
+ // Just an address - use default config
363
+ if (!defaultRemoteSigner) {
364
+ throw new KeystoreError(`No remote signer configuration found for address ${remoteSigner}`);
365
+ }
366
+ return new RemoteSigner(remoteSigner, defaultRemoteSigner);
297
367
  }
298
368
  /**
299
369
  * Create signer from JSON V3 keystore file or directory
@@ -410,20 +480,13 @@ import { LocalSigner, RemoteSigner } from './signer.js';
410
480
  if (account.startsWith('0x') && account.length === 66) {
411
481
  // This is a private key - derive the address
412
482
  try {
413
- const signer = new LocalSigner(Buffer32.fromString(account));
483
+ const signer = new LocalSigner(Buffer32.fromString(ethPrivateKeySchema.parse(account)));
414
484
  return signer.address;
415
485
  } catch {
416
- return null;
417
- }
418
- } else if (account.startsWith('0x') && account.length === 42) {
419
- // This is an address
420
- try {
421
- return EthAddress.fromString(account);
422
- } catch {
423
- return null;
486
+ return undefined;
424
487
  }
425
488
  }
426
- return null;
489
+ return undefined;
427
490
  }
428
491
  // JSON V3 keystore
429
492
  if ('path' in account) {
@@ -431,17 +494,15 @@ import { LocalSigner, RemoteSigner } from './signer.js';
431
494
  const signers = this.createSignerFromJsonV3(account);
432
495
  return signers.map((s)=>s.address);
433
496
  } catch {
434
- return null;
497
+ return undefined;
435
498
  }
436
499
  }
437
- // Remote signer account
500
+ // Remote signer account, either it is an address or the address is nested
438
501
  const remoteSigner = account;
439
- const address = typeof remoteSigner === 'string' ? remoteSigner : remoteSigner.address;
440
- try {
441
- return EthAddress.fromString(address);
442
- } catch {
443
- return null;
502
+ if ('address' in remoteSigner) {
503
+ return remoteSigner.address;
444
504
  }
505
+ return remoteSigner;
445
506
  };
446
507
  // Helper to check if account matches and get its remote signer config
447
508
  const checkAccount = (account)=>{
@@ -458,13 +519,7 @@ import { LocalSigner, RemoteSigner } from './signer.js';
458
519
  }
459
520
  // Found a match - determine the config to return
460
521
  if (typeof account === 'string') {
461
- if (account.startsWith('0x') && account.length === 66) {
462
- // Private key - local signer, no remote config
463
- return undefined;
464
- } else {
465
- // Address only - use defaults
466
- return validator.remoteSigner || this.keystore.remoteSigner;
467
- }
522
+ return undefined;
468
523
  }
469
524
  // JSON V3 - local signer, no remote config
470
525
  if ('path' in account) {
@@ -472,50 +527,68 @@ import { LocalSigner, RemoteSigner } from './signer.js';
472
527
  }
473
528
  // Remote signer account with potential override
474
529
  const remoteSigner = account;
475
- if (typeof remoteSigner === 'string') {
476
- // Just an address - use defaults
477
- return validator.remoteSigner || this.keystore.remoteSigner;
530
+ if ('address' in remoteSigner) {
531
+ // Has inline config
532
+ if (remoteSigner.remoteSignerUrl) {
533
+ return {
534
+ remoteSignerUrl: remoteSigner.remoteSignerUrl,
535
+ certPath: remoteSigner.certPath,
536
+ certPass: remoteSigner.certPass
537
+ };
538
+ } else {
539
+ // No URL specified, use defaults
540
+ return validator.remoteSigner || this.keystore.remoteSigner;
541
+ }
478
542
  }
479
- // Has inline config
480
- if (remoteSigner.remoteSignerUrl) {
481
- return {
482
- remoteSignerUrl: remoteSigner.remoteSignerUrl,
483
- certPath: remoteSigner.certPath,
484
- certPass: remoteSigner.certPass
485
- };
486
- } else {
487
- // No URL specified, use defaults
488
- return validator.remoteSigner || this.keystore.remoteSigner;
543
+ // Just an address, use defaults
544
+ return validator.remoteSigner || this.keystore.remoteSigner;
545
+ };
546
+ // Normalize attester to EthAccounts and search
547
+ const normalized = this.extractEthAccountsFromAttester(validator.attester);
548
+ const findInEthAccounts = (accs)=>{
549
+ if (typeof accs === 'string') {
550
+ return checkAccount(accs);
551
+ }
552
+ if (Array.isArray(accs)) {
553
+ for (const a of accs){
554
+ const res = checkAccount(a);
555
+ if (res !== undefined) {
556
+ return res;
557
+ }
558
+ }
559
+ return undefined;
489
560
  }
561
+ if (typeof accs === 'object' && accs !== null && 'mnemonic' in accs) {
562
+ // mnemonic-derived keys are local signers; no remote signer config
563
+ return undefined;
564
+ }
565
+ return checkAccount(accs);
490
566
  };
491
- // Check the attester configuration
492
- const { attester } = validator;
567
+ return findInEthAccounts(normalized);
568
+ }
569
+ /** Extract ETH accounts from AttesterAccounts */ extractEthAccountsFromAttester(attester) {
493
570
  if (typeof attester === 'string') {
494
- const result = checkAccount(attester);
495
- return result === undefined ? undefined : result;
571
+ return attester;
496
572
  }
497
573
  if (Array.isArray(attester)) {
498
- for (const account of attester){
499
- const result = checkAccount(account);
500
- if (result !== undefined) {
501
- return result;
574
+ const out = [];
575
+ for (const item of attester){
576
+ if (typeof item === 'string') {
577
+ out.push(item);
578
+ } else if ('eth' in item) {
579
+ out.push(item.eth);
580
+ } else if (!('mnemonic' in item)) {
581
+ out.push(item);
502
582
  }
503
583
  }
504
- return undefined;
584
+ return out;
505
585
  }
506
- // Mnemonic configuration
507
586
  if ('mnemonic' in attester) {
508
- try {
509
- const signers = this.createSignersFromMnemonic(attester);
510
- const matches = signers.some((s)=>s.address.equals(attesterAddress));
511
- // Mnemonic-derived keys are local signers
512
- return matches ? undefined : undefined;
513
- } catch {
514
- return undefined;
515
- }
587
+ return attester;
588
+ }
589
+ if ('eth' in attester) {
590
+ return attester.eth;
516
591
  }
517
- // Single account object
518
- const result = checkAccount(attester);
519
- return result === undefined ? undefined : result;
592
+ return attester;
520
593
  }
521
594
  }
package/dest/loader.d.ts CHANGED
@@ -59,4 +59,4 @@ export declare function loadMultipleKeystores(paths: string | string[]): KeyStor
59
59
  * @throws KeyStoreLoadError When duplicate attester keys are found or multiple prover configs exist.
60
60
  */
61
61
  export declare function mergeKeystores(keystores: KeyStore[]): KeyStore;
62
- //# sourceMappingURL=loader.d.ts.map
62
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZGVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbG9hZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWNBLE9BQU8sS0FBSyxFQUFlLFFBQVEsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUl4RDs7R0FFRztBQUNILHFCQUFhLGlCQUFrQixTQUFRLEtBQUs7SUFHakMsUUFBUSxFQUFFLE1BQU07SUFDUCxLQUFLLENBQUM7SUFIeEIsWUFDRSxPQUFPLEVBQUUsTUFBTSxFQUNSLFFBQVEsRUFBRSxNQUFNLEVBQ1AsS0FBSyxDQUFDLG1CQUFPLEVBSTlCO0NBQ0Y7QUFFRDs7Ozs7O0dBTUc7QUFDSCx3QkFBZ0IsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLE1BQU0sR0FBRyxRQUFRLENBdUIzRDtBQUVEOzs7Ozs7R0FNRztBQUNILHdCQUFnQixxQkFBcUIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxHQUFHLFFBQVEsRUFBRSxDQW1DakU7QUFFRDs7Ozs7Ozs7O0dBU0c7QUFDSCx3QkFBZ0IsYUFBYSxDQUFDLElBQUksRUFBRSxNQUFNLEdBQUcsUUFBUSxFQUFFLENBdUJ0RDtBQUVEOzs7Ozs7R0FNRztBQUNILHdCQUFnQixxQkFBcUIsQ0FBQyxLQUFLLEVBQUUsTUFBTSxHQUFHLE1BQU0sRUFBRSxHQUFHLFFBQVEsRUFBRSxDQThCMUU7QUFFRDs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCx3QkFBZ0IsY0FBYyxDQUFDLFNBQVMsRUFBRSxRQUFRLEVBQUUsR0FBRyxRQUFRLENBdUk5RCJ9
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIxD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAGjC,QAAQ,EAAE,MAAM;IACP,KAAK,CAAC,EAAE,KAAK;gBAF7B,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM,EACP,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAuB3D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CAmCjE;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAuBtD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,QAAQ,EAAE,CA8B1E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,QAAQ,CA+E9D"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIxD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAGjC,QAAQ,EAAE,MAAM;IACP,KAAK,CAAC;IAHxB,YACE,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM,EACP,KAAK,CAAC,mBAAO,EAI9B;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAuB3D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CAmCjE;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAuBtD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,QAAQ,EAAE,CA8B1E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAuI9D"}