@crossmint/lobster.cash 0.1.1

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,367 @@
1
+ import { Keypair } from "@solana/web3.js";
2
+ import bs58 from "bs58";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
7
+ import {
8
+ clearPairing,
9
+ completeSetup,
10
+ configureWallet,
11
+ deleteWallet,
12
+ getOrderClientSecret,
13
+ getKeypair,
14
+ getOrCreateWallet,
15
+ getWallet,
16
+ isWalletConfigured,
17
+ listWallets,
18
+ saveOrderClientSecret,
19
+ signMessage,
20
+ updateSessionTokens,
21
+ updateSetupPending,
22
+ } from "./wallet.js";
23
+
24
+ const isWindows = process.platform === "win32";
25
+
26
+ function expectPerms(actual: number, expected: number) {
27
+ if (isWindows) {
28
+ // Windows doesn't support Unix permissions
29
+ expect([expected, 0o666, 0o777]).toContain(actual);
30
+ return;
31
+ }
32
+ expect(actual).toBe(expected);
33
+ }
34
+
35
+ describe("crossmint wallet", () => {
36
+ let tmpDir: string;
37
+ let originalEnv: string | undefined;
38
+
39
+ beforeEach(async () => {
40
+ tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "crossmint-wallet-test-"));
41
+ originalEnv = process.env.CROSSMINT_WALLETS_DIR;
42
+ process.env.CROSSMINT_WALLETS_DIR = tmpDir;
43
+ });
44
+
45
+ afterEach(async () => {
46
+ if (originalEnv === undefined) {
47
+ delete process.env.CROSSMINT_WALLETS_DIR;
48
+ } else {
49
+ process.env.CROSSMINT_WALLETS_DIR = originalEnv;
50
+ }
51
+ await fs.promises.rm(tmpDir, { recursive: true, force: true });
52
+ });
53
+
54
+ describe("keypair creation", () => {
55
+ it("creates a new Solana ed25519 keypair", () => {
56
+ const wallet = getOrCreateWallet("test-agent");
57
+
58
+ expect(wallet).toBeDefined();
59
+ expect(wallet.address).toBeDefined();
60
+ expect(wallet.secretKey).toBeDefined();
61
+ expect(wallet.createdAt).toBeDefined();
62
+
63
+ // Verify it's a valid Solana address (base58, 32-44 chars)
64
+ expect(wallet.address).toMatch(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/);
65
+
66
+ // Verify the secret key can reconstruct the keypair
67
+ const secretKeyBytes = bs58.decode(wallet.secretKey);
68
+ expect(secretKeyBytes.length).toBe(64); // ed25519 secret key is 64 bytes
69
+
70
+ const keypair = Keypair.fromSecretKey(secretKeyBytes);
71
+ expect(keypair.publicKey.toBase58()).toBe(wallet.address);
72
+ });
73
+
74
+ it("returns existing wallet on subsequent calls", () => {
75
+ const wallet1 = getOrCreateWallet("test-agent");
76
+ const wallet2 = getOrCreateWallet("test-agent");
77
+
78
+ expect(wallet1.address).toBe(wallet2.address);
79
+ expect(wallet1.secretKey).toBe(wallet2.secretKey);
80
+ expect(wallet1.createdAt).toBe(wallet2.createdAt);
81
+ });
82
+
83
+ it("creates different wallets for different agents", () => {
84
+ const wallet1 = getOrCreateWallet("agent-1");
85
+ const wallet2 = getOrCreateWallet("agent-2");
86
+
87
+ expect(wallet1.address).not.toBe(wallet2.address);
88
+ expect(wallet1.secretKey).not.toBe(wallet2.secretKey);
89
+ });
90
+
91
+ it("getKeypair returns a valid Solana Keypair", () => {
92
+ getOrCreateWallet("test-agent");
93
+ const keypair = getKeypair("test-agent");
94
+
95
+ expect(keypair).toBeInstanceOf(Keypair);
96
+ expect(keypair).not.toBeNull();
97
+ });
98
+
99
+ it("getKeypair returns null for non-existent agent", () => {
100
+ const keypair = getKeypair("non-existent");
101
+ expect(keypair).toBeNull();
102
+ });
103
+ });
104
+
105
+ describe("secure storage", () => {
106
+ it("stores wallet file with secure permissions (0o600)", async () => {
107
+ getOrCreateWallet("test-agent");
108
+
109
+ const storePath = path.join(tmpDir, "wallets.json");
110
+ const stats = await fs.promises.stat(storePath);
111
+ const mode = stats.mode & 0o777;
112
+
113
+ expectPerms(mode, 0o600);
114
+ });
115
+
116
+ it("creates wallets directory with secure permissions (0o700)", async () => {
117
+ // Remove the directory first to test creation
118
+ await fs.promises.rm(tmpDir, { recursive: true, force: true });
119
+ await fs.promises.mkdir(tmpDir, { recursive: true });
120
+
121
+ const walletsSubDir = path.join(tmpDir, "subdir");
122
+ process.env.CROSSMINT_WALLETS_DIR = walletsSubDir;
123
+
124
+ getOrCreateWallet("test-agent");
125
+
126
+ const stats = await fs.promises.stat(walletsSubDir);
127
+ const mode = stats.mode & 0o777;
128
+
129
+ expectPerms(mode, 0o700);
130
+ });
131
+
132
+ it("stores secret key as base58 encoded string", () => {
133
+ const wallet = getOrCreateWallet("test-agent");
134
+
135
+ // Should be valid base58
136
+ expect(() => bs58.decode(wallet.secretKey)).not.toThrow();
137
+
138
+ const decoded = bs58.decode(wallet.secretKey);
139
+ expect(decoded.length).toBe(64); // ed25519 secret key
140
+ });
141
+
142
+ it("persists wallet data to disk", async () => {
143
+ const wallet = getOrCreateWallet("test-agent");
144
+
145
+ const storePath = path.join(tmpDir, "wallets.json");
146
+ const raw = await fs.promises.readFile(storePath, "utf-8");
147
+ const data = JSON.parse(raw) as { wallets: Record<string, unknown> };
148
+
149
+ expect(data.wallets["test-agent"]).toBeDefined();
150
+ expect((data.wallets["test-agent"] as { address: string }).address).toBe(wallet.address);
151
+ });
152
+ });
153
+
154
+ describe("credential storage", () => {
155
+ it("stores smart wallet address from web app", () => {
156
+ getOrCreateWallet("test-agent");
157
+
158
+ const smartWalletAddress = "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm";
159
+ const apiKey = "sk_test_123456";
160
+
161
+ const configured = configureWallet("test-agent", smartWalletAddress, apiKey);
162
+
163
+ expect(configured.smartWalletAddress).toBe(smartWalletAddress);
164
+ expect(configured.configuredAt).toBeDefined();
165
+ });
166
+
167
+ it("stores client-side API key from web app", () => {
168
+ getOrCreateWallet("test-agent");
169
+
170
+ const smartWalletAddress = "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm";
171
+ const apiKey = "sk_test_abcdef123456";
172
+
173
+ const configured = configureWallet("test-agent", smartWalletAddress, apiKey);
174
+
175
+ expect(configured.apiKey).toBe(apiKey);
176
+ });
177
+
178
+ it("persists credentials to disk with secure permissions", async () => {
179
+ getOrCreateWallet("test-agent");
180
+
181
+ const smartWalletAddress = "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm";
182
+ const apiKey = "sk_test_xyz789";
183
+
184
+ configureWallet("test-agent", smartWalletAddress, apiKey);
185
+
186
+ const storePath = path.join(tmpDir, "wallets.json");
187
+ const raw = await fs.promises.readFile(storePath, "utf-8");
188
+ const data = JSON.parse(raw) as {
189
+ wallets: Record<string, { smartWalletAddress?: string; apiKey?: string }>;
190
+ };
191
+
192
+ expect(data.wallets["test-agent"].smartWalletAddress).toBe(smartWalletAddress);
193
+ expect(data.wallets["test-agent"].apiKey).toBe(apiKey);
194
+
195
+ // Verify file permissions remain secure after update
196
+ const stats = await fs.promises.stat(storePath);
197
+ const mode = stats.mode & 0o777;
198
+ expectPerms(mode, 0o600);
199
+ });
200
+
201
+ it("throws error when configuring non-existent wallet", () => {
202
+ expect(() => {
203
+ configureWallet("non-existent", "address", "key");
204
+ }).toThrow('No wallet found for agent "non-existent"');
205
+ });
206
+
207
+ it("isWalletConfigured returns false before configuration", () => {
208
+ getOrCreateWallet("test-agent");
209
+ expect(isWalletConfigured("test-agent")).toBe(false);
210
+ });
211
+
212
+ it("isWalletConfigured returns false after legacy configuration (migration required)", () => {
213
+ getOrCreateWallet("test-agent");
214
+ configureWallet("test-agent", "wallet-address", "api-key");
215
+ expect(isWalletConfigured("test-agent")).toBe(false);
216
+ });
217
+
218
+ it("isWalletConfigured returns true after server-token setup completion", () => {
219
+ getOrCreateWallet("test-agent");
220
+ completeSetup("test-agent", "wallet-address", "access-token", "refresh-token", 9999999999);
221
+ expect(isWalletConfigured("test-agent")).toBe(true);
222
+ });
223
+
224
+ it("isWalletConfigured returns false for non-existent agent", () => {
225
+ expect(isWalletConfigured("non-existent")).toBe(false);
226
+ });
227
+ });
228
+
229
+ describe("wallet management", () => {
230
+ it("getWallet returns null for non-existent agent", () => {
231
+ const wallet = getWallet("non-existent");
232
+ expect(wallet).toBeNull();
233
+ });
234
+
235
+ it("getWallet returns wallet data for existing agent", () => {
236
+ const created = getOrCreateWallet("test-agent");
237
+ const retrieved = getWallet("test-agent");
238
+
239
+ expect(retrieved).not.toBeNull();
240
+ expect(retrieved?.address).toBe(created.address);
241
+ });
242
+
243
+ it("listWallets returns all wallets", () => {
244
+ getOrCreateWallet("agent-1");
245
+ getOrCreateWallet("agent-2");
246
+ getOrCreateWallet("agent-3");
247
+
248
+ const wallets = listWallets();
249
+
250
+ expect(Object.keys(wallets)).toHaveLength(3);
251
+ expect(wallets["agent-1"]).toBeDefined();
252
+ expect(wallets["agent-2"]).toBeDefined();
253
+ expect(wallets["agent-3"]).toBeDefined();
254
+ });
255
+
256
+ it("deleteWallet removes wallet", () => {
257
+ getOrCreateWallet("test-agent");
258
+ expect(getWallet("test-agent")).not.toBeNull();
259
+
260
+ const deleted = deleteWallet("test-agent");
261
+
262
+ expect(deleted).toBe(true);
263
+ expect(getWallet("test-agent")).toBeNull();
264
+ });
265
+
266
+ it("deleteWallet returns false for non-existent agent", () => {
267
+ const deleted = deleteWallet("non-existent");
268
+ expect(deleted).toBe(false);
269
+ });
270
+ });
271
+
272
+ describe("setup/session state", () => {
273
+ it("stores pending pairing state", () => {
274
+ getOrCreateWallet("test-agent");
275
+ const updated = updateSetupPending("test-agent", "pairing-1", "https://app/consent");
276
+
277
+ expect(updated.pairingId).toBe("pairing-1");
278
+ expect(updated.consentUrl).toBe("https://app/consent");
279
+ expect(updated.setupStatus).toBe("awaiting-consent");
280
+ });
281
+
282
+ it("completeSetup stores wallet + tokens and clears pairing fields", () => {
283
+ getOrCreateWallet("test-agent");
284
+ updateSetupPending("test-agent", "pairing-1", "https://app/consent");
285
+
286
+ const updated = completeSetup(
287
+ "test-agent",
288
+ "wallet-address",
289
+ "access-token",
290
+ "refresh-token",
291
+ 1234567890
292
+ );
293
+
294
+ expect(updated.walletAddress).toBe("wallet-address");
295
+ expect(updated.accessToken).toBe("access-token");
296
+ expect(updated.refreshToken).toBe("refresh-token");
297
+ expect(updated.accessTokenExpiresAt).toBe(1234567890);
298
+ expect(updated.pairingId).toBeUndefined();
299
+ expect(updated.consentUrl).toBeUndefined();
300
+ expect(updated.setupStatus).toBe("configured");
301
+ expect(updated.configuredAt).toBeDefined();
302
+ });
303
+
304
+ it("updateSessionTokens rotates access and refresh tokens", () => {
305
+ getOrCreateWallet("test-agent");
306
+ completeSetup("test-agent", "wallet-address", "access-token", "refresh-token", 1000);
307
+
308
+ const updated = updateSessionTokens("test-agent", "new-access", "new-refresh", 2000);
309
+
310
+ expect(updated.accessToken).toBe("new-access");
311
+ expect(updated.refreshToken).toBe("new-refresh");
312
+ expect(updated.accessTokenExpiresAt).toBe(2000);
313
+ });
314
+
315
+ it("clearPairing removes pairing metadata", () => {
316
+ getOrCreateWallet("test-agent");
317
+ updateSetupPending("test-agent", "pairing-1", "https://app/consent");
318
+
319
+ const updated = clearPairing("test-agent");
320
+ expect(updated.pairingId).toBeUndefined();
321
+ expect(updated.consentUrl).toBeUndefined();
322
+ });
323
+ });
324
+
325
+ describe("order client secret storage", () => {
326
+ it("stores and retrieves order client secret", () => {
327
+ getOrCreateWallet("test-agent");
328
+
329
+ saveOrderClientSecret("test-agent", "order-1", "secret-1");
330
+
331
+ expect(getOrderClientSecret("test-agent", "order-1")).toBe("secret-1");
332
+ expect(getOrderClientSecret("test-agent", "missing-order")).toBeUndefined();
333
+ });
334
+ });
335
+
336
+ describe("message signing", () => {
337
+ it("signs messages with ed25519", async () => {
338
+ getOrCreateWallet("test-agent");
339
+
340
+ const message = new TextEncoder().encode("test message");
341
+ const signature = await signMessage("test-agent", message);
342
+
343
+ expect(signature).toBeInstanceOf(Uint8Array);
344
+ expect(signature.length).toBe(64); // ed25519 signature is 64 bytes
345
+ });
346
+
347
+ it("produces valid ed25519 signature", async () => {
348
+ const wallet = getOrCreateWallet("test-agent");
349
+
350
+ const message = new TextEncoder().encode("test message for verification");
351
+ const signature = await signMessage("test-agent", message);
352
+
353
+ // Verify signature using tweetnacl
354
+ const { sign } = await import("tweetnacl");
355
+ const publicKey = bs58.decode(wallet.address);
356
+
357
+ const isValid = sign.detached.verify(message, signature, publicKey);
358
+ expect(isValid).toBe(true);
359
+ });
360
+
361
+ it("throws error for non-existent agent", async () => {
362
+ await expect(signMessage("non-existent", new Uint8Array([1, 2, 3]))).rejects.toThrow(
363
+ "No wallet found for agent: non-existent"
364
+ );
365
+ });
366
+ });
367
+ });
package/src/wallet.ts ADDED
@@ -0,0 +1,328 @@
1
+ import { Keypair } from "@solana/web3.js";
2
+ import bs58 from "bs58";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+
7
+ export type SetupStatus =
8
+ | "uninitialized"
9
+ | "awaiting-consent"
10
+ | "awaiting-approval"
11
+ | "configured"
12
+ | "denied"
13
+ | "expired";
14
+
15
+ export type WalletData = {
16
+ // Local keypair (generated by OpenClaw)
17
+ secretKey: string; // base58 encoded secret key
18
+ address: string; // base58 public key (signer address)
19
+ createdAt: string;
20
+
21
+ // Server-driven setup/session state
22
+ setupStatus?: SetupStatus;
23
+ pairingId?: string;
24
+ consentUrl?: string;
25
+ setupUpdatedAt?: string;
26
+
27
+ // Active server-issued agent session data
28
+ walletAddress?: string;
29
+ accessToken?: string;
30
+ refreshToken?: string;
31
+ accessTokenExpiresAt?: number; // Unix seconds
32
+ configuredAt?: string;
33
+
34
+ // Stored order session secrets for follow-up order status/payment calls
35
+ orderClientSecrets?: Record<string, string>;
36
+
37
+ // Legacy fields kept for migration messaging only
38
+ smartWalletAddress?: string;
39
+ apiKey?: string;
40
+ };
41
+
42
+ export type WalletStore = {
43
+ wallets: Record<string, WalletData>;
44
+ };
45
+
46
+ function getWalletsDir(): string {
47
+ // Allow override for testing
48
+ const override = process.env.CROSSMINT_WALLETS_DIR;
49
+ if (override) {
50
+ return override;
51
+ }
52
+ return path.join(os.homedir(), ".openclaw", "crossmint-wallets");
53
+ }
54
+
55
+ function ensureWalletsDir(): void {
56
+ const dir = getWalletsDir();
57
+ if (!fs.existsSync(dir)) {
58
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
59
+ }
60
+ }
61
+
62
+ function getStorePath(): string {
63
+ return path.join(getWalletsDir(), "wallets.json");
64
+ }
65
+
66
+ function loadStore(): WalletStore {
67
+ ensureWalletsDir();
68
+ const storePath = getStorePath();
69
+ if (!fs.existsSync(storePath)) {
70
+ return { wallets: {} };
71
+ }
72
+ try {
73
+ const data = fs.readFileSync(storePath, "utf-8");
74
+ return JSON.parse(data) as WalletStore;
75
+ } catch {
76
+ return { wallets: {} };
77
+ }
78
+ }
79
+
80
+ function saveStore(store: WalletStore): void {
81
+ ensureWalletsDir();
82
+ const storePath = getStorePath();
83
+ fs.writeFileSync(storePath, JSON.stringify(store, null, 2), {
84
+ encoding: "utf-8",
85
+ mode: 0o600, // Secure file permissions
86
+ });
87
+ }
88
+
89
+ export function getOrCreateWallet(agentId: string): WalletData {
90
+ const store = loadStore();
91
+
92
+ if (store.wallets[agentId]) {
93
+ return store.wallets[agentId];
94
+ }
95
+
96
+ // Generate new Solana keypair
97
+ const keypair = Keypair.generate();
98
+ const walletData: WalletData = {
99
+ secretKey: bs58.encode(keypair.secretKey),
100
+ address: keypair.publicKey.toBase58(),
101
+ createdAt: new Date().toISOString(),
102
+ };
103
+
104
+ store.wallets[agentId] = walletData;
105
+ saveStore(store);
106
+
107
+ return walletData;
108
+ }
109
+
110
+ export function getWallet(agentId: string): WalletData | null {
111
+ const store = loadStore();
112
+ return store.wallets[agentId] ?? null;
113
+ }
114
+
115
+ export function isWalletConfigured(agentId: string): boolean {
116
+ const wallet = getWallet(agentId);
117
+ return !!(wallet?.walletAddress && wallet?.refreshToken);
118
+ }
119
+
120
+ export function configureWallet(
121
+ agentId: string,
122
+ smartWalletAddress: string,
123
+ apiKey: string
124
+ ): WalletData {
125
+ const store = loadStore();
126
+ const existing = store.wallets[agentId];
127
+
128
+ if (!existing) {
129
+ throw new Error(`No wallet found for agent "${agentId}". Generate keypair first.`);
130
+ }
131
+
132
+ store.wallets[agentId] = {
133
+ ...existing,
134
+ setupStatus: "configured",
135
+ walletAddress: smartWalletAddress,
136
+ smartWalletAddress,
137
+ apiKey,
138
+ configuredAt: new Date().toISOString(),
139
+ setupUpdatedAt: new Date().toISOString(),
140
+ };
141
+
142
+ saveStore(store);
143
+ return store.wallets[agentId];
144
+ }
145
+
146
+ export function updateSetupPending(
147
+ agentId: string,
148
+ pairingId: string,
149
+ consentUrl: string
150
+ ): WalletData {
151
+ const store = loadStore();
152
+ const existing = store.wallets[agentId];
153
+
154
+ if (!existing) {
155
+ throw new Error(`No wallet found for agent "${agentId}". Generate keypair first.`);
156
+ }
157
+
158
+ store.wallets[agentId] = {
159
+ ...existing,
160
+ setupStatus: "awaiting-consent",
161
+ pairingId,
162
+ consentUrl,
163
+ setupUpdatedAt: new Date().toISOString(),
164
+ };
165
+
166
+ saveStore(store);
167
+ return store.wallets[agentId];
168
+ }
169
+
170
+ export function updateSetupStatus(agentId: string, status: SetupStatus): WalletData {
171
+ const store = loadStore();
172
+ const existing = store.wallets[agentId];
173
+
174
+ if (!existing) {
175
+ throw new Error(`No wallet found for agent "${agentId}". Generate keypair first.`);
176
+ }
177
+
178
+ store.wallets[agentId] = {
179
+ ...existing,
180
+ setupStatus: status,
181
+ setupUpdatedAt: new Date().toISOString(),
182
+ };
183
+
184
+ saveStore(store);
185
+ return store.wallets[agentId];
186
+ }
187
+
188
+ export function clearPairing(agentId: string): WalletData {
189
+ const store = loadStore();
190
+ const existing = store.wallets[agentId];
191
+
192
+ if (!existing) {
193
+ throw new Error(`No wallet found for agent "${agentId}". Generate keypair first.`);
194
+ }
195
+
196
+ store.wallets[agentId] = {
197
+ ...existing,
198
+ pairingId: undefined,
199
+ consentUrl: undefined,
200
+ setupUpdatedAt: new Date().toISOString(),
201
+ };
202
+
203
+ saveStore(store);
204
+ return store.wallets[agentId];
205
+ }
206
+
207
+ export function completeSetup(
208
+ agentId: string,
209
+ walletAddress: string,
210
+ accessToken: string,
211
+ refreshToken: string,
212
+ accessTokenExpiresAt: number
213
+ ): WalletData {
214
+ const store = loadStore();
215
+ const existing = store.wallets[agentId];
216
+
217
+ if (!existing) {
218
+ throw new Error(`No wallet found for agent "${agentId}". Generate keypair first.`);
219
+ }
220
+
221
+ store.wallets[agentId] = {
222
+ ...existing,
223
+ setupStatus: "configured",
224
+ pairingId: undefined,
225
+ consentUrl: undefined,
226
+ walletAddress,
227
+ accessToken,
228
+ refreshToken,
229
+ accessTokenExpiresAt,
230
+ configuredAt: new Date().toISOString(),
231
+ setupUpdatedAt: new Date().toISOString(),
232
+ };
233
+
234
+ saveStore(store);
235
+ return store.wallets[agentId];
236
+ }
237
+
238
+ export function updateSessionTokens(
239
+ agentId: string,
240
+ accessToken: string,
241
+ refreshToken: string,
242
+ accessTokenExpiresAt: number
243
+ ): WalletData {
244
+ const store = loadStore();
245
+ const existing = store.wallets[agentId];
246
+
247
+ if (!existing) {
248
+ throw new Error(`No wallet found for agent "${agentId}". Generate keypair first.`);
249
+ }
250
+
251
+ store.wallets[agentId] = {
252
+ ...existing,
253
+ accessToken,
254
+ refreshToken,
255
+ accessTokenExpiresAt,
256
+ setupUpdatedAt: new Date().toISOString(),
257
+ };
258
+
259
+ saveStore(store);
260
+ return store.wallets[agentId];
261
+ }
262
+
263
+ export function saveOrderClientSecret(
264
+ agentId: string,
265
+ orderId: string,
266
+ clientSecret: string
267
+ ): void {
268
+ const store = loadStore();
269
+ const existing = store.wallets[agentId];
270
+
271
+ if (!existing) {
272
+ throw new Error(`No wallet found for agent "${agentId}".`);
273
+ }
274
+
275
+ const orderClientSecrets = {
276
+ ...(existing.orderClientSecrets || {}),
277
+ [orderId]: clientSecret,
278
+ };
279
+
280
+ store.wallets[agentId] = {
281
+ ...existing,
282
+ orderClientSecrets,
283
+ setupUpdatedAt: new Date().toISOString(),
284
+ };
285
+
286
+ saveStore(store);
287
+ }
288
+
289
+ export function getOrderClientSecret(agentId: string, orderId: string): string | undefined {
290
+ const wallet = getWallet(agentId);
291
+ return wallet?.orderClientSecrets?.[orderId];
292
+ }
293
+
294
+ export function listWallets(): Record<string, WalletData> {
295
+ const store = loadStore();
296
+ return store.wallets;
297
+ }
298
+
299
+ export function deleteWallet(agentId: string): boolean {
300
+ const store = loadStore();
301
+ if (!store.wallets[agentId]) {
302
+ return false;
303
+ }
304
+ delete store.wallets[agentId];
305
+ saveStore(store);
306
+ return true;
307
+ }
308
+
309
+ export function getKeypair(agentId: string): Keypair | null {
310
+ const walletData = getWallet(agentId);
311
+ if (!walletData) {
312
+ return null;
313
+ }
314
+
315
+ const secretKey = bs58.decode(walletData.secretKey);
316
+ return Keypair.fromSecretKey(secretKey);
317
+ }
318
+
319
+ export async function signMessage(agentId: string, message: Uint8Array): Promise<Uint8Array> {
320
+ const keypair = getKeypair(agentId);
321
+ if (!keypair) {
322
+ throw new Error(`No wallet found for agent: ${agentId}`);
323
+ }
324
+
325
+ // Solana uses ed25519 signing via nacl
326
+ const nacl = (await import("tweetnacl")).default;
327
+ return nacl.sign.detached(message, keypair.secretKey);
328
+ }