@aztec/node-keystore 2.0.0-nightly.20250826 → 2.0.0-nightly.20250828

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,7 @@
1
+ import { type ConfigMappingsType } from '@aztec/foundation/config';
2
+ export type KeyStoreConfig = {
3
+ keyStoreDirectory: string | undefined;
4
+ };
5
+ export declare const keyStoreConfigMappings: ConfigMappingsType<KeyStoreConfig>;
6
+ export declare function getKeyStoreConfigFromEnv(): KeyStoreConfig;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAyB,MAAM,0BAA0B,CAAC;AAE1F,MAAM,MAAM,cAAc,GAAG;IAC3B,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,kBAAkB,CAAC,cAAc,CAKrE,CAAC;AAEF,wBAAgB,wBAAwB,IAAI,cAAc,CAEzD"}
package/dest/config.js ADDED
@@ -0,0 +1,10 @@
1
+ import { getConfigFromMappings } from '@aztec/foundation/config';
2
+ export const keyStoreConfigMappings = {
3
+ keyStoreDirectory: {
4
+ env: 'KEY_STORE_DIRECTORY',
5
+ description: 'Location of key store directory'
6
+ }
7
+ };
8
+ export function getKeyStoreConfigFromEnv() {
9
+ return getConfigFromMappings(keyStoreConfigMappings);
10
+ }
package/dest/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from './loader.js';
3
3
  export * from './schemas.js';
4
4
  export * from './signer.js';
5
5
  export * from './keystore_manager.js';
6
+ export * from './config.js';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC"}
package/dest/index.js CHANGED
@@ -3,3 +3,4 @@ export * from './loader.js';
3
3
  export * from './schemas.js';
4
4
  export * from './signer.js';
5
5
  export * from './keystore_manager.js';
6
+ export * from './config.js';
@@ -3,11 +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
7
  import { Buffer32 } from '@aztec/foundation/buffer';
7
8
  import { EthAddress } from '@aztec/foundation/eth-address';
8
9
  import type { Signature } from '@aztec/foundation/eth-signature';
9
10
  import type { TypedDataDefinition } from 'viem';
10
- import { type Signer } from './signer.js';
11
11
  import type { EthAccounts, EthRemoteSignerConfig, KeyStore, ProverKeyStore, ValidatorKeyStore as ValidatorKeystoreConfig } from './types.js';
12
12
  /**
13
13
  * Error thrown when keystore operations fail
@@ -42,19 +42,23 @@ export declare class KeystoreManager {
42
42
  /**
43
43
  * Create signers for validator attester accounts
44
44
  */
45
- createAttesterSigners(validatorIndex: number): Signer[];
45
+ createAttesterSigners(validatorIndex: number): EthSigner[];
46
46
  /**
47
47
  * Create signers for validator publisher accounts (falls back to attester if not specified)
48
48
  */
49
- createPublisherSigners(validatorIndex: number): Signer[];
49
+ createPublisherSigners(validatorIndex: number): EthSigner[];
50
+ createAllValidatorPublisherSigners(): EthSigner[];
50
51
  /**
51
52
  * Create signers for slasher accounts
52
53
  */
53
- createSlasherSigners(): Signer[];
54
+ createSlasherSigners(): EthSigner[];
54
55
  /**
55
56
  * Create signers for prover accounts
56
57
  */
57
- createProverSigners(): Signer[];
58
+ createProverSigners(): {
59
+ id: EthAddress | undefined;
60
+ signers: EthSigner[];
61
+ } | undefined;
58
62
  /**
59
63
  * Get validator configuration by index
60
64
  */
@@ -109,11 +113,11 @@ export declare class KeystoreManager {
109
113
  /**
110
114
  * Sign message with a specific signer
111
115
  */
112
- signMessage(signer: Signer, message: Buffer32): Promise<Signature>;
116
+ signMessage(signer: EthSigner, message: Buffer32): Promise<Signature>;
113
117
  /**
114
118
  * Sign typed data with a specific signer
115
119
  */
116
- signTypedData(signer: Signer, typedData: TypedDataDefinition): Promise<Signature>;
120
+ signTypedData(signer: EthSigner, typedData: TypedDataDefinition): Promise<Signature>;
117
121
  /**
118
122
  * Get the effective remote signer configuration for a specific attester address
119
123
  * Precedence: account-level override > validator-level config > file-level default
@@ -1 +1 @@
1
- {"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,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;AAGhD,OAAO,EAA6B,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrE,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,MAAM,EAAE;IAKvD;;OAEG;IACH,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE;IAcxD;;OAEG;IACH,oBAAoB,IAAI,MAAM,EAAE;IAQhC;;OAEG;IACH,mBAAmB,IAAI,MAAM,EAAE;IA0B/B;;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;IA4BpC;;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,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIxE;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIvF;;;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,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;IAiCvF;;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"}
@@ -125,6 +125,14 @@ import { LocalSigner, RemoteSigner } from './signer.js';
125
125
  // Fall back to attester signers
126
126
  return this.createAttesterSigners(validatorIndex);
127
127
  }
128
+ createAllValidatorPublisherSigners() {
129
+ const numValidators = this.getValidatorCount();
130
+ const allPublishers = [];
131
+ for(let i = 0; i < numValidators; i++){
132
+ allPublishers.push(...this.createPublisherSigners(i));
133
+ }
134
+ return allPublishers;
135
+ }
128
136
  /**
129
137
  * Create signers for slasher accounts
130
138
  */ createSlasherSigners() {
@@ -137,11 +145,15 @@ import { LocalSigner, RemoteSigner } from './signer.js';
137
145
  * Create signers for prover accounts
138
146
  */ createProverSigners() {
139
147
  if (!this.keystore.prover) {
140
- return [];
148
+ return undefined;
141
149
  }
142
150
  // Handle simple prover case (just a private key)
143
151
  if (typeof this.keystore.prover === 'string' || 'path' in this.keystore.prover || 'address' in this.keystore.prover) {
144
- return this.createSignersFromEthAccounts(this.keystore.prover, this.keystore.remoteSigner);
152
+ const signers = this.createSignersFromEthAccounts(this.keystore.prover, this.keystore.remoteSigner);
153
+ return {
154
+ id: undefined,
155
+ signers
156
+ };
145
157
  }
146
158
  // Handle complex prover case with id and publishers
147
159
  const proverConfig = this.keystore.prover;
@@ -150,7 +162,10 @@ import { LocalSigner, RemoteSigner } from './signer.js';
150
162
  const publisherSigners = this.createSignersFromEthAccounts(publisherAccounts, this.keystore.remoteSigner);
151
163
  signers.push(...publisherSigners);
152
164
  }
153
- return signers;
165
+ return {
166
+ id: EthAddress.fromString(proverConfig.id),
167
+ signers
168
+ };
154
169
  }
155
170
  /**
156
171
  * Get validator configuration by index
package/dest/signer.d.ts CHANGED
@@ -3,58 +3,42 @@
3
3
  *
4
4
  * Common interface for different signing backends (local, remote, encrypted)
5
5
  */
6
+ import type { EthSigner } from '@aztec/ethereum';
6
7
  import { Buffer32 } from '@aztec/foundation/buffer';
7
8
  import type { EthAddress } from '@aztec/foundation/eth-address';
8
9
  import { Signature } from '@aztec/foundation/eth-signature';
9
- import type { TypedDataDefinition } from 'viem';
10
+ import { type TransactionSerializable, type TypedDataDefinition } from 'viem';
10
11
  import type { EthRemoteSignerConfig } from './types.js';
11
12
  /**
12
13
  * Error thrown for remote signer HTTP or JSON-RPC failures
13
14
  */
14
15
  export declare class SignerError extends Error {
15
- method: 'eth_sign' | 'eth_signTypedData_v4';
16
+ method: 'eth_sign' | 'eth_signTransaction' | 'eth_signTypedData_v4';
16
17
  url: string;
17
18
  statusCode?: number | undefined;
18
19
  errorCode?: number | undefined;
19
- constructor(message: string, method: 'eth_sign' | 'eth_signTypedData_v4', url: string, statusCode?: number | undefined, errorCode?: number | undefined);
20
+ constructor(message: string, method: 'eth_sign' | 'eth_signTransaction' | 'eth_signTypedData_v4', url: string, statusCode?: number | undefined, errorCode?: number | undefined);
20
21
  }
21
- /**
22
- * Common interface for all signer implementations
23
- */
24
- /**
25
- * Abstraction for signing operations used by the node keystore.
26
- */
27
- export interface Signer {
28
- /** The Ethereum address for this signer */
29
- readonly address: EthAddress;
30
- /** Sign a message using eth_sign (with Ethereum message prefix) */
31
- signMessage(message: Buffer32): Promise<Signature>;
32
- /** Sign typed data using EIP-712 */
33
- signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
34
- }
35
- /**
36
- * Local signer using in-memory private key
37
- */
38
22
  /**
39
23
  * Local signer that holds an in-memory Secp256k1 private key.
40
24
  */
41
- export declare class LocalSigner implements Signer {
25
+ export declare class LocalSigner implements EthSigner {
26
+ private privateKey;
42
27
  private readonly signer;
43
28
  constructor(privateKey: Buffer32);
44
29
  get address(): EthAddress;
45
30
  signMessage(message: Buffer32): Promise<Signature>;
46
31
  signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
32
+ signTransaction(transaction: TransactionSerializable): Promise<Signature>;
47
33
  }
48
- /**
49
- * Remote signer using Web3Signer HTTP API
50
- */
51
34
  /**
52
35
  * Remote signer that proxies signing operations to a Web3Signer-compatible HTTP endpoint.
53
36
  */
54
- export declare class RemoteSigner implements Signer {
37
+ export declare class RemoteSigner implements EthSigner {
55
38
  readonly address: EthAddress;
56
39
  private readonly config;
57
- constructor(address: EthAddress, config: EthRemoteSignerConfig);
40
+ private fetch;
41
+ constructor(address: EthAddress, config: EthRemoteSignerConfig, fetch?: typeof globalThis.fetch);
58
42
  /**
59
43
  * Sign a message using eth_sign via remote JSON-RPC.
60
44
  */
@@ -63,6 +47,7 @@ export declare class RemoteSigner implements Signer {
63
47
  * Sign typed data using eth_signTypedData_v4 via remote JSON-RPC.
64
48
  */
65
49
  signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
50
+ signTransaction(transaction: TransactionSerializable): Promise<Signature>;
66
51
  /**
67
52
  * Make a JSON-RPC sign request using eth_sign
68
53
  */
@@ -70,13 +55,14 @@ export declare class RemoteSigner implements Signer {
70
55
  * Make a JSON-RPC eth_sign request.
71
56
  */
72
57
  private makeJsonRpcSignRequest;
73
- /**
74
- * Make a JSON-RPC sign typed data request using eth_signTypedData_v4
75
- */
76
58
  /**
77
59
  * Make a JSON-RPC eth_signTypedData_v4 request.
78
60
  */
79
61
  private makeJsonRpcSignTypedDataRequest;
62
+ /**
63
+ * Make a JSON-RPC eth_signTransaction request.
64
+ */
65
+ private makeJsonRpcSignTransactionRequest;
80
66
  /**
81
67
  * Resolve the effective remote signer URL from config.
82
68
  */
@@ -1 +1 @@
1
- {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAE5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAGhD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAG3B,MAAM,EAAE,UAAU,GAAG,sBAAsB;IAC3C,GAAG,EAAE,MAAM;IACX,UAAU,CAAC,EAAE,MAAM;IACnB,SAAS,CAAC,EAAE,MAAM;gBAJzB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,UAAU,GAAG,sBAAsB,EAC3C,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,SAAS,CAAC,EAAE,MAAM,YAAA;CAK5B;AAED;;GAEG;AACH;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAE7B,mEAAmE;IACnE,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAEnD,oCAAoC;IACpC,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACnE;AAED;;GAEG;AACH;;GAEG;AACH,qBAAa,WAAY,YAAW,MAAM;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAE7B,UAAU,EAAE,QAAQ;IAIhC,IAAI,OAAO,IAAI,UAAU,CAExB;IAED,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIlD,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;CAIlE;AAED;;GAEG;AACH;;GAEG;AACH,qBAAa,YAAa,YAAW,MAAM;aAEvB,OAAO,EAAE,UAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADP,OAAO,EAAE,UAAU,EAClB,MAAM,EAAE,qBAAqB;IAGhD;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIxD;;OAEG;IACG,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIvE;;OAEG;IACH;;OAEG;YACW,sBAAsB;IAoDpC;;OAEG;IACH;;OAEG;YACW,+BAA+B;IAoD7C;;OAEG;IACH,OAAO,CAAC,YAAY;CAMrB"}
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAiC,MAAM,iCAAiC,CAAC;AAG3F,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EAKzB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAG3B,MAAM,EAAE,UAAU,GAAG,qBAAqB,GAAG,sBAAsB;IACnE,GAAG,EAAE,MAAM;IACX,UAAU,CAAC,EAAE,MAAM;IACnB,SAAS,CAAC,EAAE,MAAM;gBAJzB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,UAAU,GAAG,qBAAqB,GAAG,sBAAsB,EACnE,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,SAAS,CAAC,EAAE,MAAM,YAAA;CAK5B;AAED;;GAEG;AACH,qBAAa,WAAY,YAAW,SAAS;IAG/B,OAAO,CAAC,UAAU;IAF9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAErB,UAAU,EAAE,QAAQ;IAIxC,IAAI,OAAO,IAAI,UAAU,CAExB;IAED,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIlD,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAKjE,eAAe,CAAC,WAAW,EAAE,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;CAc1E;AAmBD;;GAEG;AACH,qBAAa,YAAa,YAAW,SAAS;aAE1B,OAAO,EAAE,UAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,KAAK;gBAFG,OAAO,EAAE,UAAU,EAClB,MAAM,EAAE,qBAAqB,EACtC,KAAK,GAAE,OAAO,UAAU,CAAC,KAAwB;IAG3D;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIxD;;OAEG;IACG,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIvE,eAAe,CAAC,WAAW,EAAE,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIzE;;OAEG;IACH;;OAEG;YACW,sBAAsB;IAoDpC;;OAEG;YACW,+BAA+B;IAoD7C;;OAEG;YACW,iCAAiC;IAqF/C;;OAEG;IACH,OAAO,CAAC,YAAY;CAMrB"}
package/dest/signer.js CHANGED
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Common interface for different signing backends (local, remote, encrypted)
5
5
  */ import { Buffer32 } from '@aztec/foundation/buffer';
6
- import { Secp256k1Signer } from '@aztec/foundation/crypto';
6
+ import { Secp256k1Signer, toRecoveryBit } from '@aztec/foundation/crypto';
7
7
  import { Signature } from '@aztec/foundation/eth-signature';
8
- import { hashTypedData } from 'viem';
8
+ import { withHexPrefix } from '@aztec/foundation/string';
9
+ import { hashTypedData, keccak256, parseTransaction, serializeTransaction } from 'viem';
9
10
  /**
10
11
  * Error thrown for remote signer HTTP or JSON-RPC failures
11
12
  */ export class SignerError extends Error {
@@ -19,12 +20,12 @@ import { hashTypedData } from 'viem';
19
20
  }
20
21
  }
21
22
  /**
22
- * Local signer using in-memory private key
23
- */ /**
24
23
  * Local signer that holds an in-memory Secp256k1 private key.
25
24
  */ export class LocalSigner {
25
+ privateKey;
26
26
  signer;
27
27
  constructor(privateKey){
28
+ this.privateKey = privateKey;
28
29
  this.signer = new Secp256k1Signer(privateKey);
29
30
  }
30
31
  get address() {
@@ -37,17 +38,28 @@ import { hashTypedData } from 'viem';
37
38
  const digest = hashTypedData(typedData);
38
39
  return Promise.resolve(this.signer.sign(Buffer32.fromString(digest)));
39
40
  }
41
+ signTransaction(transaction) {
42
+ // Taken from viem's `signTransaction` implementation
43
+ const tx = transaction.type === 'eip4844' ? {
44
+ ...transaction,
45
+ sidecars: false
46
+ } : transaction;
47
+ const serializedTx = serializeTransaction(tx);
48
+ const txHash = keccak256(serializedTx);
49
+ const sig = this.signer.sign(Buffer32.fromString(txHash.slice(2)));
50
+ return Promise.resolve(new Signature(sig.r, sig.s, toRecoveryBit(sig.v)));
51
+ }
40
52
  }
41
53
  /**
42
- * Remote signer using Web3Signer HTTP API
43
- */ /**
44
54
  * Remote signer that proxies signing operations to a Web3Signer-compatible HTTP endpoint.
45
55
  */ export class RemoteSigner {
46
56
  address;
47
57
  config;
48
- constructor(address, config){
58
+ fetch;
59
+ constructor(address, config, fetch = globalThis.fetch){
49
60
  this.address = address;
50
61
  this.config = config;
62
+ this.fetch = fetch;
51
63
  }
52
64
  /**
53
65
  * Sign a message using eth_sign via remote JSON-RPC.
@@ -59,6 +71,9 @@ import { hashTypedData } from 'viem';
59
71
  */ async signTypedData(typedData) {
60
72
  return await this.makeJsonRpcSignTypedDataRequest(typedData);
61
73
  }
74
+ signTransaction(transaction) {
75
+ return this.makeJsonRpcSignTransactionRequest(transaction);
76
+ }
62
77
  /**
63
78
  * Make a JSON-RPC sign request using eth_sign
64
79
  */ /**
@@ -74,7 +89,7 @@ import { hashTypedData } from 'viem';
74
89
  ],
75
90
  id: 1
76
91
  };
77
- const response = await fetch(url, {
92
+ const response = await this.fetch(url, {
78
93
  method: 'POST',
79
94
  headers: {
80
95
  'Content-Type': 'application/json'
@@ -99,8 +114,6 @@ import { hashTypedData } from 'viem';
99
114
  return Signature.fromString(signatureHex);
100
115
  }
101
116
  /**
102
- * Make a JSON-RPC sign typed data request using eth_signTypedData_v4
103
- */ /**
104
117
  * Make a JSON-RPC eth_signTypedData_v4 request.
105
118
  */ async makeJsonRpcSignTypedDataRequest(typedData) {
106
119
  const url = this.getSignerUrl();
@@ -113,7 +126,7 @@ import { hashTypedData } from 'viem';
113
126
  ],
114
127
  id: 1
115
128
  };
116
- const response = await fetch(url, {
129
+ const response = await this.fetch(url, {
117
130
  method: 'POST',
118
131
  headers: {
119
132
  'Content-Type': 'application/json'
@@ -138,6 +151,60 @@ import { hashTypedData } from 'viem';
138
151
  return Signature.fromString(signatureHex);
139
152
  }
140
153
  /**
154
+ * Make a JSON-RPC eth_signTransaction request.
155
+ */ async makeJsonRpcSignTransactionRequest(tx) {
156
+ if (tx.type !== 'eip1559') {
157
+ throw new Error('This signer does not support tx type: ' + tx.type);
158
+ }
159
+ const url = this.getSignerUrl();
160
+ const txObject = {
161
+ from: this.address.toString(),
162
+ to: tx.to ?? null,
163
+ data: tx.data,
164
+ value: typeof tx.value !== 'undefined' ? withHexPrefix(tx.value.toString(16)) : undefined,
165
+ nonce: typeof tx.nonce !== 'undefined' ? withHexPrefix(tx.nonce.toString(16)) : undefined,
166
+ gas: typeof tx.gas !== 'undefined' ? withHexPrefix(tx.gas.toString(16)) : undefined,
167
+ maxFeePerGas: typeof tx.maxFeePerGas !== 'undefined' ? withHexPrefix(tx.maxFeePerGas.toString(16)) : undefined,
168
+ maxPriorityFeePerGas: typeof tx.maxPriorityFeePerGas !== 'undefined' ? withHexPrefix(tx.maxPriorityFeePerGas.toString(16)) : undefined
169
+ };
170
+ const body = {
171
+ jsonrpc: '2.0',
172
+ method: 'eth_signTransaction',
173
+ params: [
174
+ txObject
175
+ ],
176
+ id: 1
177
+ };
178
+ const response = await this.fetch(url, {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'application/json'
182
+ },
183
+ body: JSON.stringify(body)
184
+ });
185
+ if (!response.ok) {
186
+ const errorText = await response.text();
187
+ throw new SignerError(`Web3Signer request failed for eth_signTransaction at ${url}: ${response.status} ${response.statusText} - ${errorText}`, 'eth_signTransaction', url, response.status);
188
+ }
189
+ const result = await response.json();
190
+ if (result.error) {
191
+ throw new SignerError(`Web3Signer JSON-RPC error for eth_signTransaction at ${url}: ${result.error.code} - ${result.error.message}`, 'eth_signTransaction', url, undefined, result.error.code);
192
+ }
193
+ if (!result.result) {
194
+ throw new Error('Invalid response from Web3Signer: no result found');
195
+ }
196
+ let rawTxHex = result.result;
197
+ if (!rawTxHex.startsWith('0x')) {
198
+ rawTxHex = '0x' + rawTxHex;
199
+ }
200
+ // we get back to whole signed tx. Deserialize it in order to read the signature
201
+ const parsedTxWithSignature = parseTransaction(rawTxHex);
202
+ if (parsedTxWithSignature.r === undefined || parsedTxWithSignature.s === undefined || parsedTxWithSignature.v === undefined) {
203
+ throw new Error('Tx not signed by Web3Signer');
204
+ }
205
+ return Signature.fromViemTransactionSignature(parsedTxWithSignature);
206
+ }
207
+ /**
141
208
  * Resolve the effective remote signer URL from config.
142
209
  */ getSignerUrl() {
143
210
  if (typeof this.config === 'string') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/node-keystore",
3
- "version": "2.0.0-nightly.20250826",
3
+ "version": "2.0.0-nightly.20250828",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -62,8 +62,9 @@
62
62
  ]
63
63
  },
64
64
  "dependencies": {
65
- "@aztec/foundation": "2.0.0-nightly.20250826",
66
- "@aztec/stdlib": "2.0.0-nightly.20250826",
65
+ "@aztec/ethereum": "2.0.0-nightly.20250828",
66
+ "@aztec/foundation": "2.0.0-nightly.20250828",
67
+ "@aztec/stdlib": "2.0.0-nightly.20250828",
67
68
  "@ethersproject/wallet": "^5.7.0",
68
69
  "tslib": "^2.4.0",
69
70
  "viem": "2.23.7",
package/src/config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config';
2
+
3
+ export type KeyStoreConfig = {
4
+ keyStoreDirectory: string | undefined;
5
+ };
6
+
7
+ export const keyStoreConfigMappings: ConfigMappingsType<KeyStoreConfig> = {
8
+ keyStoreDirectory: {
9
+ env: 'KEY_STORE_DIRECTORY',
10
+ description: 'Location of key store directory',
11
+ },
12
+ };
13
+
14
+ export function getKeyStoreConfigFromEnv(): KeyStoreConfig {
15
+ return getConfigFromMappings<KeyStoreConfig>(keyStoreConfigMappings);
16
+ }
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from './loader.js';
3
3
  export * from './schemas.js';
4
4
  export * from './signer.js';
5
5
  export * from './keystore_manager.js';
6
+ export * from './config.js';
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Manages keystore configuration and delegates signing operations to appropriate signers.
5
5
  */
6
+ import type { EthSigner } from '@aztec/ethereum';
6
7
  import { Buffer32 } from '@aztec/foundation/buffer';
7
8
  import { EthAddress } from '@aztec/foundation/eth-address';
8
9
  import type { Signature } from '@aztec/foundation/eth-signature';
@@ -13,7 +14,7 @@ import { extname, join } from 'path';
13
14
  import type { TypedDataDefinition } from 'viem';
14
15
  import { mnemonicToAccount } from 'viem/accounts';
15
16
 
16
- import { LocalSigner, RemoteSigner, type Signer } from './signer.js';
17
+ import { LocalSigner, RemoteSigner } from './signer.js';
17
18
  import type {
18
19
  EthAccount,
19
20
  EthAccounts,
@@ -152,7 +153,7 @@ export class KeystoreManager {
152
153
  /**
153
154
  * Create signers for validator attester accounts
154
155
  */
155
- createAttesterSigners(validatorIndex: number): Signer[] {
156
+ createAttesterSigners(validatorIndex: number): EthSigner[] {
156
157
  const validator = this.getValidator(validatorIndex);
157
158
  return this.createSignersFromEthAccounts(validator.attester, validator.remoteSigner || this.keystore.remoteSigner);
158
159
  }
@@ -160,7 +161,7 @@ export class KeystoreManager {
160
161
  /**
161
162
  * Create signers for validator publisher accounts (falls back to attester if not specified)
162
163
  */
163
- createPublisherSigners(validatorIndex: number): Signer[] {
164
+ createPublisherSigners(validatorIndex: number): EthSigner[] {
164
165
  const validator = this.getValidator(validatorIndex);
165
166
 
166
167
  if (validator.publisher) {
@@ -174,10 +175,21 @@ export class KeystoreManager {
174
175
  return this.createAttesterSigners(validatorIndex);
175
176
  }
176
177
 
178
+ createAllValidatorPublisherSigners(): EthSigner[] {
179
+ const numValidators = this.getValidatorCount();
180
+ const allPublishers = [];
181
+
182
+ for (let i = 0; i < numValidators; i++) {
183
+ allPublishers.push(...this.createPublisherSigners(i));
184
+ }
185
+
186
+ return allPublishers;
187
+ }
188
+
177
189
  /**
178
190
  * Create signers for slasher accounts
179
191
  */
180
- createSlasherSigners(): Signer[] {
192
+ createSlasherSigners(): EthSigner[] {
181
193
  if (!this.keystore.slasher) {
182
194
  return [];
183
195
  }
@@ -188,9 +200,9 @@ export class KeystoreManager {
188
200
  /**
189
201
  * Create signers for prover accounts
190
202
  */
191
- createProverSigners(): Signer[] {
203
+ createProverSigners(): { id: EthAddress | undefined; signers: EthSigner[] } | undefined {
192
204
  if (!this.keystore.prover) {
193
- return [];
205
+ return undefined;
194
206
  }
195
207
 
196
208
  // Handle simple prover case (just a private key)
@@ -199,19 +211,26 @@ export class KeystoreManager {
199
211
  'path' in this.keystore.prover ||
200
212
  'address' in this.keystore.prover
201
213
  ) {
202
- return this.createSignersFromEthAccounts(this.keystore.prover as EthAccount, this.keystore.remoteSigner);
214
+ const signers = this.createSignersFromEthAccounts(this.keystore.prover as EthAccount, this.keystore.remoteSigner);
215
+ return {
216
+ id: undefined,
217
+ signers,
218
+ };
203
219
  }
204
220
 
205
221
  // Handle complex prover case with id and publishers
206
222
  const proverConfig = this.keystore.prover;
207
- const signers: Signer[] = [];
223
+ const signers: EthSigner[] = [];
208
224
 
209
225
  for (const publisherAccounts of proverConfig.publisher) {
210
226
  const publisherSigners = this.createSignersFromEthAccounts(publisherAccounts, this.keystore.remoteSigner);
211
227
  signers.push(...publisherSigners);
212
228
  }
213
229
 
214
- return signers;
230
+ return {
231
+ id: EthAddress.fromString(proverConfig.id),
232
+ signers,
233
+ };
215
234
  }
216
235
 
217
236
  /**
@@ -302,13 +321,16 @@ export class KeystoreManager {
302
321
  /**
303
322
  * Create signers from EthAccounts configuration
304
323
  */
305
- private createSignersFromEthAccounts(accounts: EthAccounts, defaultRemoteSigner?: EthRemoteSignerConfig): Signer[] {
324
+ private createSignersFromEthAccounts(
325
+ accounts: EthAccounts,
326
+ defaultRemoteSigner?: EthRemoteSignerConfig,
327
+ ): EthSigner[] {
306
328
  if (typeof accounts === 'string') {
307
329
  return [this.createSignerFromEthAccount(accounts, defaultRemoteSigner)];
308
330
  }
309
331
 
310
332
  if (Array.isArray(accounts)) {
311
- const signers: Signer[] = [];
333
+ const signers: EthSigner[] = [];
312
334
  for (const account of accounts) {
313
335
  const accountSigners = this.createSignersFromEthAccounts(account, defaultRemoteSigner);
314
336
  signers.push(...accountSigners);
@@ -333,7 +355,7 @@ export class KeystoreManager {
333
355
  /**
334
356
  * Create a signer from a single EthAccount configuration
335
357
  */
336
- private createSignerFromEthAccount(account: EthAccount, defaultRemoteSigner?: EthRemoteSignerConfig): Signer {
358
+ private createSignerFromEthAccount(account: EthAccount, defaultRemoteSigner?: EthRemoteSignerConfig): EthSigner {
337
359
  // Private key (hex string)
338
360
  if (typeof account === 'string') {
339
361
  if (account.startsWith('0x') && account.length === 66) {
@@ -383,14 +405,14 @@ export class KeystoreManager {
383
405
  /**
384
406
  * Create signer from JSON V3 keystore file or directory
385
407
  */
386
- private createSignerFromJsonV3(config: EthJsonKeyFileV3Config): Signer[] {
408
+ private createSignerFromJsonV3(config: EthJsonKeyFileV3Config): EthSigner[] {
387
409
  try {
388
410
  const stats = statSync(config.path);
389
411
 
390
412
  if (stats.isDirectory()) {
391
413
  // Handle directory - load all JSON files
392
414
  const files = readdirSync(config.path);
393
- const signers: Signer[] = [];
415
+ const signers: EthSigner[] = [];
394
416
  const seenAddresses = new Map<string, string>(); // address -> file name
395
417
 
396
418
  for (const file of files) {
@@ -436,7 +458,7 @@ export class KeystoreManager {
436
458
  /**
437
459
  * Create signer from a single JSON V3 keystore file
438
460
  */
439
- private createSignerFromSingleJsonV3File(filePath: string, password?: string): Signer {
461
+ private createSignerFromSingleJsonV3File(filePath: string, password?: string): EthSigner {
440
462
  try {
441
463
  // Read the keystore file
442
464
  const keystoreJson = readFileSync(filePath, 'utf8');
@@ -463,9 +485,9 @@ export class KeystoreManager {
463
485
  /**
464
486
  * Create signers from mnemonic configuration using BIP44 derivation
465
487
  */
466
- private createSignersFromMnemonic(config: EthMnemonicConfig): Signer[] {
488
+ private createSignersFromMnemonic(config: EthMnemonicConfig): EthSigner[] {
467
489
  const { mnemonic, addressIndex = 0, accountIndex = 0, addressCount = 1, accountCount = 1 } = config;
468
- const signers: Signer[] = [];
490
+ const signers: EthSigner[] = [];
469
491
 
470
492
  try {
471
493
  // Use viem's mnemonic derivation (imported at top of file)
@@ -496,14 +518,14 @@ export class KeystoreManager {
496
518
  /**
497
519
  * Sign message with a specific signer
498
520
  */
499
- async signMessage(signer: Signer, message: Buffer32): Promise<Signature> {
521
+ async signMessage(signer: EthSigner, message: Buffer32): Promise<Signature> {
500
522
  return await signer.signMessage(message);
501
523
  }
502
524
 
503
525
  /**
504
526
  * Sign typed data with a specific signer
505
527
  */
506
- async signTypedData(signer: Signer, typedData: TypedDataDefinition): Promise<Signature> {
528
+ async signTypedData(signer: EthSigner, typedData: TypedDataDefinition): Promise<Signature> {
507
529
  return await signer.signTypedData(typedData);
508
530
  }
509
531
 
package/src/signer.ts CHANGED
@@ -3,13 +3,21 @@
3
3
  *
4
4
  * Common interface for different signing backends (local, remote, encrypted)
5
5
  */
6
+ import type { EthSigner } from '@aztec/ethereum';
6
7
  import { Buffer32 } from '@aztec/foundation/buffer';
7
- import { Secp256k1Signer } from '@aztec/foundation/crypto';
8
+ import { Secp256k1Signer, toRecoveryBit } from '@aztec/foundation/crypto';
8
9
  import type { EthAddress } from '@aztec/foundation/eth-address';
9
- import { Signature } from '@aztec/foundation/eth-signature';
10
-
11
- import type { TypedDataDefinition } from 'viem';
12
- import { hashTypedData } from 'viem';
10
+ import { Signature, type ViemTransactionSignature } from '@aztec/foundation/eth-signature';
11
+ import { withHexPrefix } from '@aztec/foundation/string';
12
+
13
+ import {
14
+ type TransactionSerializable,
15
+ type TypedDataDefinition,
16
+ hashTypedData,
17
+ keccak256,
18
+ parseTransaction,
19
+ serializeTransaction,
20
+ } from 'viem';
13
21
 
14
22
  import type { EthRemoteSignerConfig } from './types.js';
15
23
 
@@ -19,7 +27,7 @@ import type { EthRemoteSignerConfig } from './types.js';
19
27
  export class SignerError extends Error {
20
28
  constructor(
21
29
  message: string,
22
- public method: 'eth_sign' | 'eth_signTypedData_v4',
30
+ public method: 'eth_sign' | 'eth_signTransaction' | 'eth_signTypedData_v4',
23
31
  public url: string,
24
32
  public statusCode?: number,
25
33
  public errorCode?: number,
@@ -29,33 +37,13 @@ export class SignerError extends Error {
29
37
  }
30
38
  }
31
39
 
32
- /**
33
- * Common interface for all signer implementations
34
- */
35
- /**
36
- * Abstraction for signing operations used by the node keystore.
37
- */
38
- export interface Signer {
39
- /** The Ethereum address for this signer */
40
- readonly address: EthAddress;
41
-
42
- /** Sign a message using eth_sign (with Ethereum message prefix) */
43
- signMessage(message: Buffer32): Promise<Signature>;
44
-
45
- /** Sign typed data using EIP-712 */
46
- signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
47
- }
48
-
49
- /**
50
- * Local signer using in-memory private key
51
- */
52
40
  /**
53
41
  * Local signer that holds an in-memory Secp256k1 private key.
54
42
  */
55
- export class LocalSigner implements Signer {
43
+ export class LocalSigner implements EthSigner {
56
44
  private readonly signer: Secp256k1Signer;
57
45
 
58
- constructor(privateKey: Buffer32) {
46
+ constructor(private privateKey: Buffer32) {
59
47
  this.signer = new Secp256k1Signer(privateKey);
60
48
  }
61
49
 
@@ -71,18 +59,48 @@ export class LocalSigner implements Signer {
71
59
  const digest = hashTypedData(typedData);
72
60
  return Promise.resolve(this.signer.sign(Buffer32.fromString(digest)));
73
61
  }
62
+
63
+ signTransaction(transaction: TransactionSerializable): Promise<Signature> {
64
+ // Taken from viem's `signTransaction` implementation
65
+ const tx: TransactionSerializable =
66
+ transaction.type === 'eip4844'
67
+ ? {
68
+ ...transaction,
69
+ sidecars: false,
70
+ }
71
+ : transaction;
72
+ const serializedTx = serializeTransaction(tx);
73
+ const txHash = keccak256(serializedTx);
74
+ const sig = this.signer.sign(Buffer32.fromString(txHash.slice(2)));
75
+ return Promise.resolve(new Signature(sig.r, sig.s, toRecoveryBit(sig.v)));
76
+ }
74
77
  }
75
78
 
76
- /**
77
- * Remote signer using Web3Signer HTTP API
78
- */
79
+ // reference - https://docs.web3signer.consensys.io/reference/api/json-rpc#eth_signtransaction
80
+ type RemoteSignerTxObject = {
81
+ from: string;
82
+ to?: string | null;
83
+ gas?: string;
84
+ maxPriorityFeePerGas?: string;
85
+ maxFeePerGas?: string;
86
+ nonce?: string;
87
+ value?: string;
88
+ data?: string;
89
+
90
+ // EIP-4844 extension - https://github.com/Consensys/web3signer/pull/1096
91
+ maxFeePerBlobGas?: string;
92
+ blobVersionedHashes?: readonly string[];
93
+ blobs?: readonly string[];
94
+ };
95
+
79
96
  /**
80
97
  * Remote signer that proxies signing operations to a Web3Signer-compatible HTTP endpoint.
81
98
  */
82
- export class RemoteSigner implements Signer {
99
+ export class RemoteSigner implements EthSigner {
83
100
  constructor(
84
101
  public readonly address: EthAddress,
85
102
  private readonly config: EthRemoteSignerConfig,
103
+ private fetch: typeof globalThis.fetch = globalThis.fetch,
86
104
  ) {}
87
105
 
88
106
  /**
@@ -99,6 +117,10 @@ export class RemoteSigner implements Signer {
99
117
  return await this.makeJsonRpcSignTypedDataRequest(typedData);
100
118
  }
101
119
 
120
+ signTransaction(transaction: TransactionSerializable): Promise<Signature> {
121
+ return this.makeJsonRpcSignTransactionRequest(transaction);
122
+ }
123
+
102
124
  /**
103
125
  * Make a JSON-RPC sign request using eth_sign
104
126
  */
@@ -115,7 +137,7 @@ export class RemoteSigner implements Signer {
115
137
  id: 1,
116
138
  };
117
139
 
118
- const response = await fetch(url, {
140
+ const response = await this.fetch(url, {
119
141
  method: 'POST',
120
142
  headers: {
121
143
  'Content-Type': 'application/json',
@@ -157,9 +179,6 @@ export class RemoteSigner implements Signer {
157
179
  return Signature.fromString(signatureHex as `0x${string}`);
158
180
  }
159
181
 
160
- /**
161
- * Make a JSON-RPC sign typed data request using eth_signTypedData_v4
162
- */
163
182
  /**
164
183
  * Make a JSON-RPC eth_signTypedData_v4 request.
165
184
  */
@@ -173,7 +192,7 @@ export class RemoteSigner implements Signer {
173
192
  id: 1,
174
193
  };
175
194
 
176
- const response = await fetch(url, {
195
+ const response = await this.fetch(url, {
177
196
  method: 'POST',
178
197
  headers: {
179
198
  'Content-Type': 'application/json',
@@ -215,6 +234,94 @@ export class RemoteSigner implements Signer {
215
234
  return Signature.fromString(signatureHex as `0x${string}`);
216
235
  }
217
236
 
237
+ /**
238
+ * Make a JSON-RPC eth_signTransaction request.
239
+ */
240
+ private async makeJsonRpcSignTransactionRequest(tx: TransactionSerializable): Promise<Signature> {
241
+ if (tx.type !== 'eip1559') {
242
+ throw new Error('This signer does not support tx type: ' + tx.type);
243
+ }
244
+
245
+ const url = this.getSignerUrl();
246
+
247
+ const txObject: RemoteSignerTxObject = {
248
+ from: this.address.toString(),
249
+ to: tx.to ?? null,
250
+ data: tx.data,
251
+ value: typeof tx.value !== 'undefined' ? withHexPrefix(tx.value.toString(16)) : undefined,
252
+ nonce: typeof tx.nonce !== 'undefined' ? withHexPrefix(tx.nonce.toString(16)) : undefined,
253
+ gas: typeof tx.gas !== 'undefined' ? withHexPrefix(tx.gas.toString(16)) : undefined,
254
+ maxFeePerGas: typeof tx.maxFeePerGas !== 'undefined' ? withHexPrefix(tx.maxFeePerGas.toString(16)) : undefined,
255
+ maxPriorityFeePerGas:
256
+ typeof tx.maxPriorityFeePerGas !== 'undefined'
257
+ ? withHexPrefix(tx.maxPriorityFeePerGas.toString(16))
258
+ : undefined,
259
+
260
+ // maxFeePerBlobGas:
261
+ // typeof tx.maxFeePerBlobGas !== 'undefined' ? withHexPrefix(tx.maxFeePerBlobGas.toString(16)) : undefined,
262
+ // blobVersionedHashes: tx.blobVersionedHashes,
263
+ // blobs: tx.blobs?.map(blob => (typeof blob === 'string' ? blob : bufferToHex(Buffer.from(blob)))),
264
+ };
265
+
266
+ const body = {
267
+ jsonrpc: '2.0',
268
+ method: 'eth_signTransaction',
269
+ params: [txObject],
270
+ id: 1,
271
+ };
272
+
273
+ const response = await this.fetch(url, {
274
+ method: 'POST',
275
+ headers: {
276
+ 'Content-Type': 'application/json',
277
+ },
278
+ body: JSON.stringify(body),
279
+ });
280
+
281
+ if (!response.ok) {
282
+ const errorText = await response.text();
283
+ throw new SignerError(
284
+ `Web3Signer request failed for eth_signTransaction at ${url}: ${response.status} ${response.statusText} - ${errorText}`,
285
+ 'eth_signTransaction',
286
+ url,
287
+ response.status,
288
+ );
289
+ }
290
+
291
+ const result = await response.json();
292
+
293
+ if (result.error) {
294
+ throw new SignerError(
295
+ `Web3Signer JSON-RPC error for eth_signTransaction at ${url}: ${result.error.code} - ${result.error.message}`,
296
+ 'eth_signTransaction',
297
+ url,
298
+ undefined,
299
+ result.error.code,
300
+ );
301
+ }
302
+
303
+ if (!result.result) {
304
+ throw new Error('Invalid response from Web3Signer: no result found');
305
+ }
306
+
307
+ let rawTxHex = result.result;
308
+ if (!rawTxHex.startsWith('0x')) {
309
+ rawTxHex = '0x' + rawTxHex;
310
+ }
311
+
312
+ // we get back to whole signed tx. Deserialize it in order to read the signature
313
+ const parsedTxWithSignature = parseTransaction(rawTxHex);
314
+ if (
315
+ parsedTxWithSignature.r === undefined ||
316
+ parsedTxWithSignature.s === undefined ||
317
+ parsedTxWithSignature.v === undefined
318
+ ) {
319
+ throw new Error('Tx not signed by Web3Signer');
320
+ }
321
+
322
+ return Signature.fromViemTransactionSignature(parsedTxWithSignature as ViemTransactionSignature);
323
+ }
324
+
218
325
  /**
219
326
  * Resolve the effective remote signer URL from config.
220
327
  */