gora8-signer 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -33,13 +33,15 @@ npm install gora8-signer
33
33
  ## Quick start — standalone signer process
34
34
 
35
35
  ```bash
36
- # Generate a key (encrypted-file store shown here; OS keychain is used
37
- # automatically instead when available — see Storage below)
36
+ # Generate both keys — EVM and Solana — together (encrypted-file store
37
+ # shown here; OS keychain is used automatically instead when available —
38
+ # see Storage below)
38
39
  export GORA8_SIGNER_KEK=$(openssl rand -hex 32) # dev only — see Storage
39
- npx gora8-signer init my-agent-id
40
+ npx --package=gora8-signer gora8-signer init my-agent-id
41
+ # {"evmAddress":"0x...","solanaAddress":"..."}
40
42
 
41
43
  # Run the signer as its own process
42
- npx gora8-signer start my-agent-id
44
+ npx --package=gora8-signer gora8-signer start my-agent-id
43
45
  ```
44
46
 
45
47
  Your agent's own code then talks to it over the socket:
@@ -48,9 +50,16 @@ Your agent's own code then talks to it over the socket:
48
50
  import { SignerIpcClient } from "gora8-signer";
49
51
 
50
52
  const signer = new SignerIpcClient("~/.gora8/signer/my-agent-id/signer.sock");
51
- const address = await signer.address();
53
+ const evmAddress = await signer.address();
54
+ const solanaAddress = await signer.solanaAddress();
52
55
  ```
53
56
 
57
+ Both keys are entirely independent secrets — different curves
58
+ (secp256k1 vs. Ed25519), different storage entries — generated,
59
+ stored, and used the same way, on the same storage backends. An
60
+ agent that only ever transacts on one chain still gets both; the
61
+ unused one simply never gets asked to sign anything.
62
+
54
63
  ## Storage
55
64
 
56
65
  Defaults to the OS-native keychain (macOS Keychain, Windows Credential
@@ -63,6 +72,15 @@ a mounted secret, whatever your deployment already uses — never gora8's.
63
72
  The env-var-based provider shown above is for local development only;
64
73
  implement `KekProvider` against your real KMS for production.
65
74
 
75
+ Auto-detection isn't airtight: some minimal container images (e.g.
76
+ Alpine) ship the OS-keychain library's native binding without a working
77
+ Secret Service underneath it, so it *loads* successfully but can't
78
+ actually store or retrieve anything — confirmed live, not hypothesized.
79
+ If you know your deployment target has no real OS keychain, set
80
+ `GORA8_SIGNER_SECRET_BACKEND=file` (or `keychain` to force the other
81
+ way, erroring loudly instead of silently falling back if it isn't
82
+ actually usable) rather than relying on auto-detection to figure it out.
83
+
66
84
  ## Policy enforcement
67
85
 
68
86
  Before signing anything, the signer fetches your agent's current signed
@@ -1,5 +1,6 @@
1
1
  import { type Hex } from "viem";
2
2
  import { type MerkleTree } from "./merkle-tree.js";
3
+ import { type DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
3
4
  export interface AuditEntry {
4
5
  timestamp: string;
5
6
  operation: string;
@@ -9,11 +10,63 @@ export interface AuditEntry {
9
10
  counterparty: string | null;
10
11
  txHash?: string;
11
12
  }
12
- export declare class AuditLog {
13
+ export interface AuditLogBackend {
14
+ /** Appends one entry. Must preserve append order for the same reason
15
+ * Merkle-leaf ordering has to be stable — a backend whose writes can
16
+ * reorder or interleave under concurrency (multiple serverless
17
+ * instances appending at once, say) needs its own ordering
18
+ * guarantee (a monotonic sort key, a single-writer queue in front of
19
+ * it, etc.) or computeCommitment()'s root stops meaning "the log as
20
+ * it actually happened." Local, single-process storage gets this for
21
+ * free; a distributed backend has to earn it explicitly. */
22
+ append(entry: AuditEntry): Promise<void>;
23
+ readAll(): Promise<AuditEntry[]>;
24
+ }
25
+ /** File-backed, append-only JSONL — the original (and still the
26
+ * default local/persistent-process) storage, extracted here as one
27
+ * implementation of AuditLogBackend rather than baked into AuditLog
28
+ * itself. */
29
+ export declare class LocalFileAuditLogBackend implements AuditLogBackend {
13
30
  private readonly filePath;
14
31
  constructor(filePath: string);
15
32
  append(entry: AuditEntry): Promise<void>;
16
33
  readAll(): Promise<AuditEntry[]>;
34
+ }
35
+ /** DynamoDB-backed — the distributed case LocalFileAuditLogBackend's
36
+ * doc comment flagged as not yet built: multiple concurrent
37
+ * processes appending without a shared filesystem to serialize
38
+ * writes through. Ordering (this file's whole reason for calling it
39
+ * out as a backend-specific concern, not something the AuditLog
40
+ * orchestration layer can paper over) comes from a DynamoDB atomic
41
+ * counter, not wall-clock timestamps — two concurrent appends always
42
+ * get two different, strictly increasing sequence numbers via a
43
+ * single conditionless `ADD` (DynamoDB serializes this itself; no
44
+ * compare-and-swap needed here, unlike the rate-limit backend, since
45
+ * an atomic increment is exactly what's needed and DynamoDB provides
46
+ * it natively). Entries are stored under a different partition
47
+ * (`<key>#entries`, sort key = that sequence number) than the counter
48
+ * itself (`<key>#seq`), so `readAll()`'s Query never has to filter
49
+ * the counter item out. Requires a table with a String partition key
50
+ * named `pk` and a Number sort key named `sortKey`. */
51
+ export declare class DynamoDbAuditLogBackend implements AuditLogBackend {
52
+ private readonly client;
53
+ private readonly tableName;
54
+ private readonly key;
55
+ constructor(client: DynamoDBDocumentClient, tableName: string, key: string);
56
+ private nextSeq;
57
+ append(entry: AuditEntry): Promise<void>;
58
+ readAll(): Promise<AuditEntry[]>;
59
+ }
60
+ /** The orchestration layer — hash-chain leaf computation, Merkle
61
+ * commitment, and spend-tracking, all built on top of whatever
62
+ * AuditLogBackend is injected. Public API unchanged from before this
63
+ * file was split (append/readAll/spentSince/computeCommitment) — only
64
+ * the constructor changed, from a file path to a backend. */
65
+ export declare class AuditLog {
66
+ private readonly backend;
67
+ constructor(backend: AuditLogBackend);
68
+ append(entry: AuditEntry): Promise<void>;
69
+ readAll(): Promise<AuditEntry[]>;
17
70
  /** Sum of `amount` across allowed entries since `since` — the local,
18
71
  * independent spend tracker policy.ts's SpendTracker interface
19
72
  * expects (see that file's doc comment on why this doesn't rely on
package/dist/audit-log.js CHANGED
@@ -2,21 +2,12 @@ import { appendFile, readFile, mkdir } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
3
  import { keccak256, toHex } from "viem";
4
4
  import { buildMerkleTree } from "./merkle-tree.js";
5
- function leafFor(entry) {
6
- // Canonical JSON key order so the same logical entry always hashes to
7
- // the same leaf regardless of object construction order.
8
- const canonical = JSON.stringify({
9
- timestamp: entry.timestamp,
10
- operation: entry.operation,
11
- allowed: entry.allowed,
12
- reason: entry.reason ?? null,
13
- amount: entry.amount,
14
- counterparty: entry.counterparty,
15
- txHash: entry.txHash ?? null,
16
- });
17
- return keccak256(toHex(canonical));
18
- }
19
- export class AuditLog {
5
+ import { QueryCommand, PutCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
6
+ /** File-backed, append-only JSONL — the original (and still the
7
+ * default local/persistent-process) storage, extracted here as one
8
+ * implementation of AuditLogBackend rather than baked into AuditLog
9
+ * itself. */
10
+ export class LocalFileAuditLogBackend {
20
11
  filePath;
21
12
  constructor(filePath) {
22
13
  this.filePath = filePath;
@@ -39,6 +30,112 @@ export class AuditLog {
39
30
  throw err;
40
31
  }
41
32
  }
33
+ }
34
+ /** DynamoDB-backed — the distributed case LocalFileAuditLogBackend's
35
+ * doc comment flagged as not yet built: multiple concurrent
36
+ * processes appending without a shared filesystem to serialize
37
+ * writes through. Ordering (this file's whole reason for calling it
38
+ * out as a backend-specific concern, not something the AuditLog
39
+ * orchestration layer can paper over) comes from a DynamoDB atomic
40
+ * counter, not wall-clock timestamps — two concurrent appends always
41
+ * get two different, strictly increasing sequence numbers via a
42
+ * single conditionless `ADD` (DynamoDB serializes this itself; no
43
+ * compare-and-swap needed here, unlike the rate-limit backend, since
44
+ * an atomic increment is exactly what's needed and DynamoDB provides
45
+ * it natively). Entries are stored under a different partition
46
+ * (`<key>#entries`, sort key = that sequence number) than the counter
47
+ * itself (`<key>#seq`), so `readAll()`'s Query never has to filter
48
+ * the counter item out. Requires a table with a String partition key
49
+ * named `pk` and a Number sort key named `sortKey`. */
50
+ export class DynamoDbAuditLogBackend {
51
+ client;
52
+ tableName;
53
+ key;
54
+ constructor(client, tableName, key) {
55
+ this.client = client;
56
+ this.tableName = tableName;
57
+ this.key = key;
58
+ }
59
+ async nextSeq() {
60
+ const res = await this.client.send(new UpdateCommand({
61
+ TableName: this.tableName,
62
+ Key: { pk: `${this.key}#seq`, sortKey: 0 },
63
+ UpdateExpression: "ADD seq :one",
64
+ ExpressionAttributeValues: { ":one": 1 },
65
+ ReturnValues: "UPDATED_NEW",
66
+ }));
67
+ const seq = res.Attributes?.seq;
68
+ if (typeof seq !== "number")
69
+ throw new Error(`DynamoDbAuditLogBackend: counter update for "${this.key}" returned no numeric seq.`);
70
+ return seq;
71
+ }
72
+ async append(entry) {
73
+ const seq = await this.nextSeq();
74
+ // AuditEntry's optional fields (reason, txHash) arrive as
75
+ // `undefined`, not omitted, whenever they're unset — fine for
76
+ // LocalFileAuditLogBackend (JSON.stringify silently drops undefined
77
+ // keys), but DynamoDB's default marshaller rejects `undefined`
78
+ // outright rather than dropping it (found live-verifying this: the
79
+ // very first signRegisterIdentity through a real CLI run threw here,
80
+ // since a successful signature's `reason` is always undefined). The
81
+ // JSON round-trip restores exactly the same "absent, not null"
82
+ // behavior the file backend already has, rather than requiring every
83
+ // caller to remember to construct their DynamoDBDocumentClient with
84
+ // `marshallOptions: { removeUndefinedValues: true }`.
85
+ const cleanEntry = JSON.parse(JSON.stringify(entry));
86
+ await this.client.send(new PutCommand({
87
+ TableName: this.tableName,
88
+ Item: { pk: `${this.key}#entries`, sortKey: seq, entry: cleanEntry },
89
+ }));
90
+ }
91
+ async readAll() {
92
+ const entries = [];
93
+ let exclusiveStartKey;
94
+ do {
95
+ const res = await this.client.send(new QueryCommand({
96
+ TableName: this.tableName,
97
+ KeyConditionExpression: "pk = :pk",
98
+ ExpressionAttributeValues: { ":pk": `${this.key}#entries` },
99
+ ScanIndexForward: true, // ascending sortKey — append order, same as the file backend's line order
100
+ ExclusiveStartKey: exclusiveStartKey,
101
+ }));
102
+ for (const item of res.Items ?? [])
103
+ entries.push(item.entry);
104
+ exclusiveStartKey = res.LastEvaluatedKey;
105
+ } while (exclusiveStartKey);
106
+ return entries;
107
+ }
108
+ }
109
+ function leafFor(entry) {
110
+ // Canonical JSON key order so the same logical entry always hashes to
111
+ // the same leaf regardless of object construction order.
112
+ const canonical = JSON.stringify({
113
+ timestamp: entry.timestamp,
114
+ operation: entry.operation,
115
+ allowed: entry.allowed,
116
+ reason: entry.reason ?? null,
117
+ amount: entry.amount,
118
+ counterparty: entry.counterparty,
119
+ txHash: entry.txHash ?? null,
120
+ });
121
+ return keccak256(toHex(canonical));
122
+ }
123
+ /** The orchestration layer — hash-chain leaf computation, Merkle
124
+ * commitment, and spend-tracking, all built on top of whatever
125
+ * AuditLogBackend is injected. Public API unchanged from before this
126
+ * file was split (append/readAll/spentSince/computeCommitment) — only
127
+ * the constructor changed, from a file path to a backend. */
128
+ export class AuditLog {
129
+ backend;
130
+ constructor(backend) {
131
+ this.backend = backend;
132
+ }
133
+ append(entry) {
134
+ return this.backend.append(entry);
135
+ }
136
+ readAll() {
137
+ return this.backend.readAll();
138
+ }
42
139
  /** Sum of `amount` across allowed entries since `since` — the local,
43
140
  * independent spend tracker policy.ts's SpendTracker interface
44
141
  * expects (see that file's doc comment on why this doesn't rely on
package/dist/cli.js CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
- import { defaultKeyStore, EnvKekProvider } from "./keystore.js";
4
+ import { KMSClient } from "@aws-sdk/client-kms";
5
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
6
+ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
7
+ import { defaultEvmSigningBackend, defaultSolanaSigningBackend } from "./signing-backend.js";
8
+ import { KmsSigningBackend, SolanaKmsSigningBackend } from "./kms-signing-backend.js";
9
+ import { EnvKekProvider } from "./keystore.js";
5
10
  import { PolicyEngine } from "./policy.js";
6
- import { AuditLog } from "./audit-log.js";
7
- import { Signer, SlidingWindowRateLimiter } from "./signer.js";
11
+ import { AuditLog, LocalFileAuditLogBackend, DynamoDbAuditLogBackend } from "./audit-log.js";
12
+ import { LocalFileRateLimitBackend, DynamoDbRateLimitBackend } from "./rate-limit-backend.js";
13
+ import { Signer } from "./signer.js";
8
14
  import { SignerIpcServer } from "./ipc-server.js";
9
15
  // Minimal CLI so `gora8 deploy` (the Go CLI) can shell out to this
10
16
  // package directly rather than needing its own reimplementation of key
@@ -16,6 +22,90 @@ import { SignerIpcServer } from "./ipc-server.js";
16
22
  function dataDir(agentId) {
17
23
  return join(homedir(), ".gora8", "signer", agentId);
18
24
  }
25
+ // Per-chain backend selection, independent of each other (same
26
+ // reasoning as everywhere else in this codebase: EVM and Solana are two
27
+ // entirely separate secrets, so one chain can be KMS-backed while the
28
+ // other stays local without conflict). Presence of the KMS key id env
29
+ // var *is* the selection — no separate "backend kind" var to drift out
30
+ // of sync with it. `provisioning` is null for a KMS-backed chain: the
31
+ // key already exists in KMS, provisioned out of band through the KMS
32
+ // console/API (see LocalKeyProvisioning's doc comment in
33
+ // signing-backend.ts), so `init` has nothing to generate for it.
34
+ let sharedKmsClient;
35
+ function kmsClient() {
36
+ // process.env.AWS_REGION, if set, is picked up by the SDK's own
37
+ // default provider chain automatically — no gora8-specific region
38
+ // handling needed here.
39
+ if (!sharedKmsClient)
40
+ sharedKmsClient = new KMSClient({});
41
+ return sharedKmsClient;
42
+ }
43
+ let sharedDynamoDbClient;
44
+ function dynamoDbClient() {
45
+ if (!sharedDynamoDbClient)
46
+ sharedDynamoDbClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
47
+ return sharedDynamoDbClient;
48
+ }
49
+ // Same independent-per-concern selection as the signing backends above:
50
+ // presence of the table-name env var is the selection, no separate
51
+ // "backend kind" var. This is what actually closes the "persistent
52
+ // process assumed" gap a KMS-backed signing key alone doesn't close —
53
+ // KMS made the *key* stateless-deployment-safe; these make the rate
54
+ // limiter and audit log safe for the same topology (a Lambda/Cloud-Run
55
+ // invocation with no durable local disk, possibly multiple concurrent
56
+ // instances of the same agent).
57
+ function selectAuditLogBackend(agentId, dir) {
58
+ const tableName = process.env.GORA8_AUDIT_LOG_DYNAMODB_TABLE;
59
+ if (tableName) {
60
+ console.error(`Audit log backend: DynamoDB (table "${tableName}")`);
61
+ return new DynamoDbAuditLogBackend(dynamoDbClient(), tableName, agentId);
62
+ }
63
+ console.error("Audit log backend: local file");
64
+ return new LocalFileAuditLogBackend(join(dir, "audit.jsonl"));
65
+ }
66
+ function selectRateLimitBackend(agentId, dir) {
67
+ const tableName = process.env.GORA8_RATE_LIMIT_DYNAMODB_TABLE;
68
+ if (tableName) {
69
+ console.error(`Rate limit backend: DynamoDB (table "${tableName}")`);
70
+ return new DynamoDbRateLimitBackend(dynamoDbClient(), tableName, agentId);
71
+ }
72
+ console.error("Rate limit backend: local file");
73
+ return new LocalFileRateLimitBackend(join(dir, "rate-limit.json"));
74
+ }
75
+ async function selectEvmBackend(agentId, dir, kek) {
76
+ const keyId = process.env.GORA8_EVM_KMS_KEY_ID;
77
+ if (keyId) {
78
+ console.error(`EVM signing backend: AWS KMS (key "${keyId}")`);
79
+ return { backend: new KmsSigningBackend(kmsClient(), keyId), provisioning: null };
80
+ }
81
+ console.error("EVM signing backend: local (OS keychain / encrypted file)");
82
+ const local = await defaultEvmSigningBackend(agentId, join(dir, "key.enc.json"), kek);
83
+ return { backend: local, provisioning: local };
84
+ }
85
+ async function selectSolanaBackend(agentId, dir, kek) {
86
+ const keyId = process.env.GORA8_SOLANA_KMS_KEY_ID;
87
+ if (keyId) {
88
+ console.error(`Solana signing backend: AWS KMS (key "${keyId}")`);
89
+ return { backend: new SolanaKmsSigningBackend(kmsClient(), keyId), provisioning: null };
90
+ }
91
+ console.error("Solana signing backend: local (OS keychain / encrypted file)");
92
+ const local = await defaultSolanaSigningBackend(agentId, join(dir, "solana-key.enc.json"), kek);
93
+ return { backend: local, provisioning: local };
94
+ }
95
+ /** Shared by both chains' `init` handling: generate-and-store for a
96
+ * local backend (skipping if a key already exists), or just report the
97
+ * address for a KMS-backed one (`provisioning === null`) — there's
98
+ * nothing for `init` to generate against KMS. */
99
+ async function resolveOrGenerateAddress(label, agentId, backend, provisioning) {
100
+ if (!provisioning)
101
+ return backend.address();
102
+ const hasKey = await provisioning.hasKey();
103
+ if (hasKey) {
104
+ console.error(`A ${label} key already exists for agent "${agentId}". Not generating a new one.`);
105
+ return backend.address();
106
+ }
107
+ return provisioning.generateAndStore();
108
+ }
19
109
  async function fetchMandate(agentId, apiBase) {
20
110
  const res = await fetch(`${apiBase}/v1/agents/${agentId}/mandate`);
21
111
  if (!res.ok)
@@ -30,18 +120,20 @@ async function main() {
30
120
  process.exit(1);
31
121
  }
32
122
  const dir = dataDir(agentId);
33
- const keyStore = await defaultKeyStore(agentId, join(dir, "key.enc.json"), new EnvKekProvider());
34
- const auditLog = new AuditLog(join(dir, "audit.jsonl"));
123
+ const kek = new EnvKekProvider();
124
+ const { backend: evmSigningBackend, provisioning: evmProvisioning } = await selectEvmBackend(agentId, dir, kek);
125
+ const { backend: solanaSigningBackend, provisioning: solanaProvisioning } = await selectSolanaBackend(agentId, dir, kek);
126
+ const auditLog = new AuditLog(selectAuditLogBackend(agentId, dir));
35
127
  switch (command) {
36
128
  case "init": {
37
- if (await keyStore.hasKey()) {
38
- console.error(`A key already exists for agent "${agentId}". Not generating a new one.`);
39
- const address = await keyStore.address();
40
- console.log(address);
41
- process.exit(1);
42
- }
43
- const address = await keyStore.generateAndStore();
44
- console.log(address);
129
+ // Both chains handled together, same as `gora8 deploy` provisions
130
+ // both an EVM and a Solana wallet for every agent today — two
131
+ // entirely independent secrets (different curves, independently
132
+ // local-or-KMS per resolveOrGenerateAddress above), not one key
133
+ // used two ways.
134
+ const evmAddress = await resolveOrGenerateAddress("EVM", agentId, evmSigningBackend, evmProvisioning);
135
+ const solanaAddress = await resolveOrGenerateAddress("Solana", agentId, solanaSigningBackend, solanaProvisioning);
136
+ console.log(JSON.stringify({ evmAddress, solanaAddress }));
45
137
  break;
46
138
  }
47
139
  case "start": {
@@ -62,10 +154,15 @@ async function main() {
62
154
  const mandateAgentId = process.env.GORA8_AGENT_ID || agentId;
63
155
  const policyEngine = new PolicyEngine({ fetchMandate: () => fetchMandate(mandateAgentId, apiBase) }, auditLog);
64
156
  const signer = new Signer({
65
- keyStore,
157
+ evmSigningBackend,
158
+ solanaSigningBackend,
66
159
  policyEngine,
67
160
  auditLog,
68
- rateLimiter: new SlidingWindowRateLimiter(30, 60_000), // 30 signatures/min sanity backstop
161
+ rateLimit: {
162
+ backend: selectRateLimitBackend(agentId, dir),
163
+ windowMs: 60_000,
164
+ maxCalls: 30, // sanity backstop
165
+ },
69
166
  });
70
167
  const socketPath = process.env.GORA8_SIGNER_SOCKET ?? join(dir, "signer.sock");
71
168
  const server = new SignerIpcServer(signer, socketPath);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,15 @@
1
- export { OsKeychainStore, EncryptedFileStore, EnvKekProvider, defaultKeyStore } from "./keystore.js";
2
- export type { KeyStore, KekProvider } from "./keystore.js";
1
+ export { OsKeychainSecretStore, EncryptedFileSecretStore, EnvKekProvider, defaultSecretStore } from "./keystore.js";
2
+ export type { SecretStore, KekProvider } from "./keystore.js";
3
+ export { defaultEvmSigningBackend, defaultSolanaSigningBackend } from "./signing-backend.js";
4
+ export type { EvmSigningBackend, SolanaSigningBackend, LocalKeyProvisioning } from "./signing-backend.js";
5
+ export { KmsSigningBackend, defaultKmsSigningBackend, SolanaKmsSigningBackend, defaultSolanaKmsSigningBackend } from "./kms-signing-backend.js";
3
6
  export { PolicyEngine } from "./policy.js";
4
7
  export type { Mandate, SigningRequest, PolicyDecision, PolicyEngineOptions, SpendTracker } from "./policy.js";
5
- export { AuditLog } from "./audit-log.js";
6
- export type { AuditEntry } from "./audit-log.js";
7
- export { Signer, SlidingWindowRateLimiter } from "./signer.js";
8
+ export { AuditLog, LocalFileAuditLogBackend, DynamoDbAuditLogBackend } from "./audit-log.js";
9
+ export type { AuditEntry, AuditLogBackend } from "./audit-log.js";
10
+ export { InMemoryRateLimitBackend, LocalFileRateLimitBackend, DynamoDbRateLimitBackend } from "./rate-limit-backend.js";
11
+ export type { RateLimitStateBackend } from "./rate-limit-backend.js";
12
+ export { Signer } from "./signer.js";
8
13
  export type { SignerOptions } from "./signer.js";
9
14
  export { SignerIpcServer } from "./ipc-server.js";
10
15
  export { SignerIpcClient } from "./ipc-client.js";
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
- export { OsKeychainStore, EncryptedFileStore, EnvKekProvider, defaultKeyStore } from "./keystore.js";
1
+ export { OsKeychainSecretStore, EncryptedFileSecretStore, EnvKekProvider, defaultSecretStore } from "./keystore.js";
2
+ export { defaultEvmSigningBackend, defaultSolanaSigningBackend } from "./signing-backend.js";
3
+ export { KmsSigningBackend, defaultKmsSigningBackend, SolanaKmsSigningBackend, defaultSolanaKmsSigningBackend } from "./kms-signing-backend.js";
2
4
  export { PolicyEngine } from "./policy.js";
3
- export { AuditLog } from "./audit-log.js";
4
- export { Signer, SlidingWindowRateLimiter } from "./signer.js";
5
+ export { AuditLog, LocalFileAuditLogBackend, DynamoDbAuditLogBackend } from "./audit-log.js";
6
+ export { InMemoryRateLimitBackend, LocalFileRateLimitBackend, DynamoDbRateLimitBackend } from "./rate-limit-backend.js";
7
+ export { Signer } from "./signer.js";
5
8
  export { SignerIpcServer } from "./ipc-server.js";
6
9
  export { SignerIpcClient } from "./ipc-client.js";
7
10
  export { buildMerkleTree, getMerkleProof, verifyMerkleProof } from "./merkle-tree.js";
@@ -24,8 +24,8 @@ export declare class SignerIpcClient {
24
24
  to: `0x${string}`;
25
25
  value: bigint;
26
26
  data: `0x${string}`;
27
- digest: `0x${string}`;
28
- amount: number;
27
+ executeNonce: bigint;
28
+ chainId: number;
29
29
  }): Promise<{
30
30
  signature: `0x${string}`;
31
31
  }>;
@@ -35,16 +35,31 @@ export declare class SignerIpcClient {
35
35
  }): Promise<{
36
36
  signature: `0x${string}`;
37
37
  }>;
38
+ signAgreementCommit(params: {
39
+ termsHash: `0x${string}`;
40
+ counterparty: `0x${string}`;
41
+ }): Promise<{
42
+ signature: `0x${string}`;
43
+ }>;
38
44
  signWithdrawal(params: {
39
- to: `0x${string}`;
40
- amount: number;
41
45
  unsignedTx: {
42
46
  to: `0x${string}`;
43
47
  value: bigint;
44
48
  data: `0x${string}`;
49
+ nonce: number;
50
+ gas?: bigint;
51
+ gasPrice?: bigint;
45
52
  };
46
53
  chainId: number;
47
54
  }): Promise<{
48
55
  signedTransaction: `0x${string}`;
49
56
  }>;
57
+ solanaAddress(): Promise<string>;
58
+ signSolanaTransaction(params: {
59
+ serializedTransaction: string;
60
+ amount: number | null;
61
+ counterparty: string | null;
62
+ }): Promise<{
63
+ signature: string;
64
+ }>;
50
65
  }
@@ -1,9 +1,10 @@
1
1
  import { createConnection } from "node:net";
2
2
  import { randomUUID } from "node:crypto";
3
3
  // Thin client for the agent's own application code — the only thing it
4
- // imports to request a signature. It never has access to KeyStore,
5
- // PolicyEngine, or the raw key at all; it can only send one of the four
6
- // named requests over the socket and get back a signature or a refusal.
4
+ // imports to request a signature. It never has access to the signing
5
+ // backend, PolicyEngine, or any key material at all; it can only send
6
+ // one of the named requests over the socket and get back a signature or
7
+ // a refusal.
7
8
  export class SignerIpcClient {
8
9
  socketPath;
9
10
  constructor(socketPath) {
@@ -47,7 +48,16 @@ export class SignerIpcClient {
47
48
  signAgreementTerms(params) {
48
49
  return this.call("signAgreementTerms", params);
49
50
  }
51
+ signAgreementCommit(params) {
52
+ return this.call("signAgreementCommit", params);
53
+ }
50
54
  signWithdrawal(params) {
51
55
  return this.call("signWithdrawal", params);
52
56
  }
57
+ solanaAddress() {
58
+ return this.call("solanaAddress");
59
+ }
60
+ signSolanaTransaction(params) {
61
+ return this.call("signSolanaTransaction", params);
62
+ }
53
63
  }
@@ -68,8 +68,14 @@ export class SignerIpcServer {
68
68
  return this.signer.signMandateExecute(params);
69
69
  case "signAgreementTerms":
70
70
  return this.signer.signAgreementTerms(params);
71
+ case "signAgreementCommit":
72
+ return this.signer.signAgreementCommit(params);
71
73
  case "signWithdrawal":
72
74
  return this.signer.signWithdrawal(params);
75
+ case "solanaAddress":
76
+ return this.signer.solanaAddress();
77
+ case "signSolanaTransaction":
78
+ return this.signer.signSolanaTransaction(params);
73
79
  default:
74
80
  throw new Error(`Unknown method: ${method}`);
75
81
  }