@toon-protocol/client-mcp 0.1.0

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.
Files changed (41) hide show
  1. package/README.md +302 -0
  2. package/dist/anon-proxy-W3KMM7GU-FN7ZJY7P.js +25 -0
  3. package/dist/anon-proxy-W3KMM7GU-FN7ZJY7P.js.map +1 -0
  4. package/dist/chunk-32QD72IL.js +83 -0
  5. package/dist/chunk-32QD72IL.js.map +1 -0
  6. package/dist/chunk-3NAWISI5.js +2245 -0
  7. package/dist/chunk-3NAWISI5.js.map +1 -0
  8. package/dist/chunk-5KFXUT5Q.js +11321 -0
  9. package/dist/chunk-5KFXUT5Q.js.map +1 -0
  10. package/dist/chunk-F22GNSF6.js +12 -0
  11. package/dist/chunk-F22GNSF6.js.map +1 -0
  12. package/dist/chunk-FSS45ZX3.js +2633 -0
  13. package/dist/chunk-FSS45ZX3.js.map +1 -0
  14. package/dist/chunk-LR7W2ISE.js +657 -0
  15. package/dist/chunk-LR7W2ISE.js.map +1 -0
  16. package/dist/chunk-SKQTKZIH.js +278 -0
  17. package/dist/chunk-SKQTKZIH.js.map +1 -0
  18. package/dist/chunk-VA7XC4FD.js +185 -0
  19. package/dist/chunk-VA7XC4FD.js.map +1 -0
  20. package/dist/chunk-ZQKYZJWT.js +359 -0
  21. package/dist/chunk-ZQKYZJWT.js.map +1 -0
  22. package/dist/daemon.d.ts +1 -0
  23. package/dist/daemon.js +141 -0
  24. package/dist/daemon.js.map +1 -0
  25. package/dist/ed25519-2QVPINLS.js +27 -0
  26. package/dist/ed25519-2QVPINLS.js.map +1 -0
  27. package/dist/gateway-QOK47RKS-SEGTXBR3.js +16 -0
  28. package/dist/gateway-QOK47RKS-SEGTXBR3.js.map +1 -0
  29. package/dist/hmac-26UC6YKX.js +12 -0
  30. package/dist/hmac-26UC6YKX.js.map +1 -0
  31. package/dist/index.d.ts +965 -0
  32. package/dist/index.js +68 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/mcp.d.ts +1 -0
  35. package/dist/mcp.js +81 -0
  36. package/dist/mcp.js.map +1 -0
  37. package/dist/sha512-WYC446ZM.js +34 -0
  38. package/dist/sha512-WYC446ZM.js.map +1 -0
  39. package/dist/socks5-WTJBYGME-6COK4LXW.js +139 -0
  40. package/dist/socks5-WTJBYGME-6COK4LXW.js.map +1 -0
  41. package/package.json +60 -0
@@ -0,0 +1,965 @@
1
+ import { NostrEvent } from 'nostr-tools/pure';
2
+ import { SwapPair } from '@toon-protocol/core';
3
+ import { ToonClientConfig } from '@toon-protocol/client';
4
+ import { FastifyInstance } from 'fastify';
5
+
6
+ /**
7
+ * Shared request/response contract for the `toon-clientd` localhost control
8
+ * plane. Both the daemon (server) and the MCP server / control-client (caller)
9
+ * import these types so the wire shape stays in lockstep.
10
+ *
11
+ * Every endpoint is plain JSON over HTTP on `127.0.0.1:<port>`. The daemon owns
12
+ * the long-lived BTP session + payment channels + relay subscription; callers
13
+ * are stateless and never see chain keys.
14
+ */
15
+
16
+ /** The chain family a paid write settles on. */
17
+ type SettlementChain = 'evm' | 'solana' | 'mina';
18
+ /** Per-chain settlement readiness, mirrored from `ToonClient.getNetworkStatus()`. */
19
+ interface ChainStatus {
20
+ chain: string;
21
+ /** Whether settlement is configured + the apex can verify inbound claims. */
22
+ ready: boolean;
23
+ detail?: string;
24
+ }
25
+ /** `GET /status` — daemon + connection health. */
26
+ interface StatusResponse {
27
+ /** Daemon process uptime, ms. */
28
+ uptimeMs: number;
29
+ /**
30
+ * True while the managed anon proxy / BTP session / channel are still coming
31
+ * up. Tools should surface "bootstrapping — retry" rather than blocking.
32
+ */
33
+ bootstrapping: boolean;
34
+ /** True once the client has started and a channel is open (ready to publish). */
35
+ ready: boolean;
36
+ /** The active settlement chain for paid writes to the apex. */
37
+ settlementChain: SettlementChain;
38
+ identity: {
39
+ nostrPubkey: string;
40
+ evmAddress?: string;
41
+ solanaAddress?: string;
42
+ minaAddress?: string;
43
+ };
44
+ transport: {
45
+ type: 'direct' | 'socks5' | 'gateway';
46
+ socksProxy?: string;
47
+ btpUrl?: string;
48
+ };
49
+ relay: {
50
+ url: string;
51
+ connected: boolean;
52
+ /** Number of events currently held in the read buffer. */
53
+ buffered: number;
54
+ /** Active subscription ids. */
55
+ subscriptions: string[];
56
+ /**
57
+ * Error from the daemon-managed read proxy, if any. Set only in the
58
+ * btp-direct + relay-`.anyone` case where the daemon starts its own `anon`
59
+ * proxy for reads; a failure here means hidden-service reads are down while
60
+ * direct paid writes are unaffected.
61
+ */
62
+ proxyError?: string;
63
+ };
64
+ /** Per-chain settlement status when a named `network` tier is configured. */
65
+ network?: ChainStatus[];
66
+ /** Last error observed during bootstrap, if any (non-fatal). */
67
+ lastError?: string;
68
+ }
69
+ /** `POST /publish` — pay-to-write a single Nostr event. */
70
+ interface PublishRequest {
71
+ /** A fully-signed Nostr event (id + sig present). */
72
+ event: NostrEvent;
73
+ /** ILP destination override (default: the configured apex/town address). */
74
+ destination?: string;
75
+ /** Fee override in base units. Defaults to the daemon's configured fee. */
76
+ fee?: string;
77
+ /**
78
+ * Which apex (BTP write target) to publish through. Defaults to the
79
+ * config-seeded apex. Writes always go through BTP — never to a relay
80
+ * directly — so this selects among the registered apexes, not relays.
81
+ */
82
+ btpUrl?: string;
83
+ }
84
+ interface PublishResponse {
85
+ eventId: string;
86
+ /** FULFILL response data (base64), e.g. an Arweave tx id from a DVM. */
87
+ data?: string;
88
+ /** Channel the claim was signed against. */
89
+ channelId: string;
90
+ /** Channel nonce after this publish (advances by one per paid write). */
91
+ nonce: number;
92
+ }
93
+ /** `POST /subscribe` — register a persistent free-read subscription. */
94
+ interface SubscribeRequest {
95
+ /** NIP-01 filter(s). A single object or an array of OR-ed filters. */
96
+ filters: NostrFilter | NostrFilter[];
97
+ /** Optional caller-supplied subscription id (else one is generated). */
98
+ subId?: string;
99
+ /**
100
+ * Restrict the subscription to a single relay. Omit to FAN OUT across every
101
+ * registered relay (the same subId is registered on each); reads merge into
102
+ * one ordered stream.
103
+ */
104
+ relayUrl?: string;
105
+ }
106
+ interface SubscribeResponse {
107
+ subId: string;
108
+ /** The relays the subscription was registered on. */
109
+ relays: string[];
110
+ }
111
+ /** `GET /events` — drain buffered events for a subscription (free read). */
112
+ interface EventsQuery {
113
+ /** Restrict to a single subscription id. */
114
+ subId?: string;
115
+ /** Cursor from a prior `EventsResponse.cursor`; returns only newer events. */
116
+ cursor?: number;
117
+ /** Max events to return (default 200). */
118
+ limit?: number;
119
+ /** Restrict the drain to events received from a single relay. */
120
+ relayUrl?: string;
121
+ }
122
+ interface EventsResponse {
123
+ events: NostrEvent[];
124
+ /** Opaque monotonic cursor; pass back to fetch only events after these. */
125
+ cursor: number;
126
+ /** Whether more events remain beyond `limit`. */
127
+ hasMore: boolean;
128
+ }
129
+ /** `POST /channels` — open (or return existing) a payment channel. */
130
+ interface OpenChannelRequest {
131
+ /** ILP destination of the peer to open against (default: configured apex). */
132
+ destination?: string;
133
+ }
134
+ interface ChannelInfo {
135
+ channelId: string;
136
+ nonce: number;
137
+ cumulativeAmount: string;
138
+ }
139
+ /** `GET /channels` — list tracked channels with nonce watermarks. */
140
+ interface ChannelsResponse {
141
+ channels: ChannelInfo[];
142
+ }
143
+ /**
144
+ * `POST /swap` — pay asset A to a mill peer, receive asset B + a signed
145
+ * target-chain claim. The daemon builds the NIP-59 gift-wrapped kind:20032 swap
146
+ * rumor and streams it via SDK `streamSwap`, signing the source-asset claim
147
+ * against the open apex channel (the mill peer must be routed via
148
+ * `apexChildPeers`).
149
+ */
150
+ interface SwapRequest {
151
+ /** Mill peer ILP destination (e.g. `g.townhouse.mill`). */
152
+ destination: string;
153
+ /** Total source-asset amount to swap, in source micro-units. */
154
+ amount: string;
155
+ /** Mill's 64-char lowercase hex Nostr pubkey (NIP-59 gift-wrap recipient). */
156
+ millPubkey: string;
157
+ /**
158
+ * The swap pair to execute — from kind:10032 discovery, or operator-supplied
159
+ * when the mill announces pairs to a relay other than the town relay.
160
+ */
161
+ pair: SwapPair;
162
+ /**
163
+ * Sender's payout address on `pair.to.chain` (EVM 0x-hex / Solana or Mina
164
+ * base58). Echoed on every rumor as the `chain-recipient` tag.
165
+ */
166
+ chainRecipient: string;
167
+ /** Split the swap into N equal packets (default 1). */
168
+ packetCount?: number;
169
+ /**
170
+ * Which apex to settle the source-asset claim through (default: the
171
+ * config-seeded apex). The mill must be a child peer of this apex.
172
+ */
173
+ btpUrl?: string;
174
+ }
175
+ /** One accumulated, decrypted claim harvested from a single swap packet. */
176
+ interface SwapClaim {
177
+ /** Source-asset amount sent for this packet (micro-units, decimal). */
178
+ sourceAmount: string;
179
+ /** Target-asset amount claimed (micro-units, decimal). */
180
+ targetAmount: string;
181
+ /** Decrypted signed target-chain claim bytes, base64. */
182
+ claim: string;
183
+ /** Target-chain channel id (real on-chain id, or a dev placeholder). */
184
+ channelId?: string;
185
+ /** Sender's payout address echoed by the mill. */
186
+ recipient?: string;
187
+ /** Mill's on-chain signer address. */
188
+ millSignerAddress?: string;
189
+ /** Mill-side claim id. */
190
+ claimId?: string;
191
+ /** Balance-proof nonce on the target channel (decimal). */
192
+ nonce?: string;
193
+ /** Cumulative transferred on the target channel (micro-units, decimal). */
194
+ cumulativeAmount?: string;
195
+ }
196
+ interface SwapResponse {
197
+ /** True when at least one packet FULFILLed with a usable claim. */
198
+ accepted: boolean;
199
+ /** Number of packets the mill FULFILLed. */
200
+ packetsAccepted: number;
201
+ /** Per-packet accumulated claims (settlement metadata + signed claim). */
202
+ claims: SwapClaim[];
203
+ /** Total source sent across accepted packets (micro-units, decimal). */
204
+ cumulativeSource: string;
205
+ /** Total target received across accepted packets (micro-units, decimal). */
206
+ cumulativeTarget: string;
207
+ /** Final stream state. */
208
+ state: 'completed' | 'failed' | 'stopped';
209
+ /** First rejection code from the mill, if any (e.g. `F99`). */
210
+ code?: string;
211
+ /** First rejection message, if any. */
212
+ message?: string;
213
+ }
214
+ /** `POST /relays` — add a relay READ target (fans into all fan-out reads). */
215
+ interface AddRelayRequest {
216
+ /** Relay WS URL, e.g. `ws://host:7100` or a `.anyone` hidden service. */
217
+ relayUrl: string;
218
+ }
219
+ /** `DELETE /relays` — remove a relay read target. */
220
+ interface RemoveRelayRequest {
221
+ relayUrl: string;
222
+ }
223
+ /**
224
+ * `POST /apex` — add an apex WRITE target. Settlement params are DISCOVERED by
225
+ * reading the apex's `kind:10032` announcement off a relay, so the caller never
226
+ * hand-supplies chain/settlement details.
227
+ */
228
+ interface AddApexRequest {
229
+ /** ILP address of the apex to add (e.g. `g.townhouse.town`). */
230
+ ilpAddress: string;
231
+ /**
232
+ * Relay to discover the apex's `kind:10032` on. If it isn't already a read
233
+ * target it is added (and persisted) first.
234
+ */
235
+ relayUrl: string;
236
+ /** Optional apex Nostr pubkey to narrow the discovery filter (64-char hex). */
237
+ pubkey?: string;
238
+ /** Preferred settlement chain family; defaults to the apex's first chain. */
239
+ chain?: SettlementChain;
240
+ /** Child peers reached via this apex's channel (e.g. `["dvm","mill"]`). */
241
+ childPeers?: string[];
242
+ /** Per-write fee override (base units) for this apex. */
243
+ feePerEvent?: string;
244
+ }
245
+ interface AddApexResponse {
246
+ btpUrl: string;
247
+ destination: string;
248
+ chain: SettlementChain;
249
+ /** Whether the apex bootstrapped + opened a channel by the time we replied. */
250
+ ready: boolean;
251
+ }
252
+ /** `DELETE /apex` — remove an apex write target by its BTP URL. */
253
+ interface RemoveApexRequest {
254
+ btpUrl: string;
255
+ }
256
+ /** Status of one registered relay read target. */
257
+ interface RelayTargetStatus {
258
+ relayUrl: string;
259
+ connected: boolean;
260
+ buffered: number;
261
+ subscriptions: string[];
262
+ /** True for the permanent config-seeded relay (not removable). */
263
+ isDefault: boolean;
264
+ }
265
+ /** Status of one registered apex write target. */
266
+ interface ApexTargetStatus {
267
+ btpUrl: string;
268
+ destination: string;
269
+ chain: SettlementChain;
270
+ ready: boolean;
271
+ bootstrapping: boolean;
272
+ channelId?: string;
273
+ lastError?: string;
274
+ /** True for the permanent config-seeded apex (not removable). */
275
+ isDefault: boolean;
276
+ }
277
+ /** `GET /targets` — list every registered relay + apex target. */
278
+ interface TargetsResponse {
279
+ relays: RelayTargetStatus[];
280
+ apexes: ApexTargetStatus[];
281
+ }
282
+ /** Uniform error envelope returned with non-2xx responses. */
283
+ interface ErrorResponse {
284
+ error: string;
285
+ detail?: string;
286
+ /** True when the caller should retry (e.g. still bootstrapping). */
287
+ retryable?: boolean;
288
+ }
289
+ /**
290
+ * NIP-01 subscription filter. Tag filters use `#<single-letter>` keys, e.g.
291
+ * `{ '#e': [...], '#p': [...] }`.
292
+ */
293
+ interface NostrFilter {
294
+ ids?: string[];
295
+ authors?: string[];
296
+ kinds?: number[];
297
+ since?: number;
298
+ until?: number;
299
+ limit?: number;
300
+ search?: string;
301
+ [tag: `#${string}`]: string[] | undefined;
302
+ }
303
+
304
+ /**
305
+ * Thin HTTP client for the `toon-clientd` localhost control plane. Used by the
306
+ * MCP server (and any other caller) to drive the daemon without holding any
307
+ * chain keys or long-lived connections itself.
308
+ */
309
+
310
+ /** Error thrown when the daemon returns a non-2xx response. */
311
+ declare class ControlApiError extends Error {
312
+ readonly status: number;
313
+ readonly retryable: boolean;
314
+ readonly detail?: string | undefined;
315
+ constructor(message: string, status: number, retryable: boolean, detail?: string | undefined);
316
+ }
317
+ /** Thrown when the daemon is unreachable (not running / wrong port). */
318
+ declare class DaemonUnreachableError extends Error {
319
+ readonly baseUrl: string;
320
+ readonly causedBy?: unknown | undefined;
321
+ constructor(baseUrl: string, causedBy?: unknown | undefined);
322
+ }
323
+ interface ControlClientOptions {
324
+ /** Base URL of the daemon, e.g. `http://127.0.0.1:8787`. */
325
+ baseUrl: string;
326
+ /** Per-request timeout, ms. Default 35000 (publishes can wait on FULFILL). */
327
+ timeoutMs?: number;
328
+ /** Inject a fetch implementation (tests). Defaults to global `fetch`. */
329
+ fetchImpl?: typeof fetch;
330
+ }
331
+ declare class ControlClient {
332
+ private readonly baseUrl;
333
+ private readonly timeoutMs;
334
+ private readonly fetchImpl;
335
+ constructor(opts: ControlClientOptions);
336
+ /** True if the daemon answers `GET /status` (used as a liveness probe). */
337
+ ping(): Promise<boolean>;
338
+ status(): Promise<StatusResponse>;
339
+ publish(body: PublishRequest): Promise<PublishResponse>;
340
+ subscribe(body: SubscribeRequest): Promise<SubscribeResponse>;
341
+ events(query?: EventsQuery): Promise<EventsResponse>;
342
+ openChannel(body?: OpenChannelRequest): Promise<{
343
+ channelId: string;
344
+ }>;
345
+ channels(): Promise<ChannelsResponse>;
346
+ swap(body: SwapRequest): Promise<SwapResponse>;
347
+ targets(): Promise<TargetsResponse>;
348
+ addRelay(body: AddRelayRequest): Promise<TargetsResponse>;
349
+ removeRelay(body: RemoveRelayRequest): Promise<TargetsResponse>;
350
+ addApex(body: AddApexRequest): Promise<AddApexResponse>;
351
+ removeApex(body: RemoveApexRequest): Promise<TargetsResponse>;
352
+ private request;
353
+ }
354
+
355
+ /**
356
+ * Persistent town-relay Nostr-WS subscription — the read half of the TOON
357
+ * client, which `@toon-protocol/client` does not provide (its bootstrap only
358
+ * issues one-shot WS queries and `DiscoveryTracker` is passive).
359
+ *
360
+ * Reads are FREE: this opens a long-lived NIP-01 connection to the town relay,
361
+ * keeps a bounded ring buffer of received events (de-duplicated by `event.id`),
362
+ * and lets callers drain new events via a monotonic cursor. It auto-reconnects
363
+ * with exponential backoff and re-issues every active REQ on reconnect.
364
+ *
365
+ * The WebSocket is injectable (`wsFactory`) so unit tests can drive the wire
366
+ * protocol without a real relay; the default factory uses the `ws` package and,
367
+ * when a `socks5h://` proxy is configured, routes through it so `.anyone`
368
+ * hidden-service relays are reachable.
369
+ */
370
+
371
+ /** Minimal WebSocket surface this module depends on (subset of `ws`). */
372
+ interface MinimalWebSocket {
373
+ send(data: string): void;
374
+ close(): void;
375
+ on(event: 'open' | 'close', cb: () => void): void;
376
+ on(event: 'message', cb: (data: unknown) => void): void;
377
+ on(event: 'error', cb: (err: unknown) => void): void;
378
+ }
379
+ type WebSocketFactory = (url: string) => MinimalWebSocket;
380
+ interface RelaySubscriptionOptions {
381
+ /** Town relay WS URL, e.g. `wss://<host>.anyone/` or `ws://localhost:7100`. */
382
+ relayUrl: string;
383
+ /** Optional `socks5h://host:port` proxy for `.anyone` relays. */
384
+ socksProxy?: string;
385
+ /** Max events retained in the ring buffer (oldest evicted). Default 5000. */
386
+ bufferSize?: number;
387
+ /** Base reconnect delay, ms. Default 1000. */
388
+ reconnectBaseMs?: number;
389
+ /** Max reconnect delay, ms. Default 30000. */
390
+ reconnectMaxMs?: number;
391
+ /** Inject a WebSocket factory (tests / proxy customisation). */
392
+ wsFactory?: WebSocketFactory;
393
+ /**
394
+ * Decode an `EVENT` payload that arrived as a string. The TOON relay sends
395
+ * events TOON-encoded (key/value text) rather than as a JSON object, so the
396
+ * daemon injects a TOON decoder here. When the payload is already a JSON
397
+ * object it is used directly and this is not called.
398
+ */
399
+ decodeEvent?: (raw: string) => NostrEvent;
400
+ /**
401
+ * Invoked once per newly-buffered (de-duplicated) event. The daemon uses this
402
+ * to feed a runner-level MERGED buffer across many relays — so a fan-out read
403
+ * (`toon_read` with no relayUrl) draws from one ordered stream with a single
404
+ * scalar cursor. The relay still keeps its own buffer for `bufferedCount` /
405
+ * single-relay drains.
406
+ */
407
+ onEvent?: (subId: string, event: NostrEvent) => void;
408
+ /** Optional logger. */
409
+ logger?: (msg: string) => void;
410
+ }
411
+ /** Result of {@link RelaySubscription.getEvents}. */
412
+ interface DrainResult {
413
+ events: NostrEvent[];
414
+ cursor: number;
415
+ hasMore: boolean;
416
+ }
417
+ declare class RelaySubscription {
418
+ private readonly relayUrl;
419
+ private readonly socksProxy?;
420
+ private readonly bufferSize;
421
+ private readonly reconnectBaseMs;
422
+ private readonly reconnectMaxMs;
423
+ private readonly log;
424
+ private readonly wsFactory;
425
+ private readonly decodeEvent?;
426
+ private readonly onEvent?;
427
+ /** Active subscriptions: subId -> filters (re-sent on every (re)connect). */
428
+ private readonly subscriptions;
429
+ /** Ring buffer of received events, ordered by ascending `seq`. */
430
+ private buffer;
431
+ /** De-dup index: event.id -> seq (kept in lockstep with the buffer). */
432
+ private readonly seen;
433
+ private seqCounter;
434
+ private subIdCounter;
435
+ private ws;
436
+ private connected;
437
+ private closing;
438
+ private reconnectAttempts;
439
+ private reconnectTimer;
440
+ constructor(opts: RelaySubscriptionOptions);
441
+ /** Whether the underlying socket is currently open. */
442
+ isConnected(): boolean;
443
+ /** Number of events currently held in the buffer. */
444
+ bufferedCount(): number;
445
+ /** Active subscription ids. */
446
+ activeSubscriptions(): string[];
447
+ /** Open the connection (idempotent). */
448
+ start(): void;
449
+ /**
450
+ * Register a persistent subscription and (if connected) send the REQ.
451
+ * Returns the subscription id (caller-supplied or generated).
452
+ */
453
+ subscribe(filters: NostrFilter | NostrFilter[], subId?: string): string;
454
+ /** Cancel a subscription and send CLOSE if connected. */
455
+ unsubscribe(subId: string): void;
456
+ /**
457
+ * Drain events newer than `cursor`. The cursor is the highest `seq` returned;
458
+ * pass it back to fetch only events received since. Filtering by `subId`
459
+ * restricts to one subscription.
460
+ */
461
+ getEvents(opts?: {
462
+ subId?: string;
463
+ cursor?: number;
464
+ limit?: number;
465
+ }): DrainResult;
466
+ /** Close the connection permanently and stop reconnecting. */
467
+ close(): void;
468
+ private open;
469
+ private scheduleReconnect;
470
+ private sendReq;
471
+ private sendRaw;
472
+ private handleMessage;
473
+ /**
474
+ * Normalise an `EVENT` payload to a NostrEvent. Standard relays send a JSON
475
+ * object; the TOON relay sends a TOON-encoded string, decoded via the injected
476
+ * `decodeEvent`. Returns undefined when it can't be parsed.
477
+ */
478
+ private parseEventPayload;
479
+ private bufferEvent;
480
+ }
481
+
482
+ /**
483
+ * Daemon configuration: resolved from a JSON config file and/or environment
484
+ * variables, then expanded into a `ToonClientConfig` (BTP + channels + signer)
485
+ * plus daemon-only settings (HTTP port, relay URL, apex negotiation).
486
+ *
487
+ * The mnemonic is sourced from (in precedence order):
488
+ * 1. `TOON_CLIENT_MNEMONIC` env var,
489
+ * 2. an encrypted keystore (#207) at `keystorePath`, decrypted with
490
+ * `TOON_CLIENT_KEYSTORE_PASSWORD`,
491
+ * 3. the `mnemonic` field of the config file (discouraged — plaintext on disk).
492
+ */
493
+
494
+ /** Apex/town settlement parameters injected as a peer negotiation. */
495
+ interface ApexNegotiationConfig {
496
+ /** ILP destination address, e.g. `g.townhouse.town`. */
497
+ destination: string;
498
+ /** Peer id key used in the negotiation map (last ILP segment, e.g. `town`). */
499
+ peerId: string;
500
+ /** Settlement chain family. */
501
+ chain: SettlementChain;
502
+ /** Negotiated chain key, e.g. `evm:base:84532`. */
503
+ chainKey: string;
504
+ /** Numeric chain id (EVM only; 0 for solana/mina). */
505
+ chainId: number;
506
+ /** The apex's settlement (receive) address on `chain`. */
507
+ settlementAddress: string;
508
+ /** Token contract / mint / zkApp address. */
509
+ tokenAddress?: string;
510
+ /** EVM TokenNetwork / Solana programId / Mina zkApp address. */
511
+ tokenNetwork?: string;
512
+ }
513
+ interface DaemonConfigFile {
514
+ /** Named network tier (drives settlement presets, #209). */
515
+ network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';
516
+ mnemonic?: string;
517
+ mnemonicAccountIndex?: number;
518
+ keystorePath?: string;
519
+ /**
520
+ * Set when the daemon auto-generated the keystore (#251 first-run onboarding).
521
+ * Such a keystore is encrypted with a default password so the identity reloads
522
+ * across restarts without `TOON_CLIENT_KEYSTORE_PASSWORD`. A user-imported
523
+ * keystore leaves this unset and still requires the env password.
524
+ */
525
+ keystoreAutoPassword?: boolean;
526
+ /** Human-facing onboarding notes written by first-run scaffolding (ignored at runtime). */
527
+ _help?: Record<string, string>;
528
+ /** BTP WebSocket URL of the apex/connector. */
529
+ btpUrl?: string;
530
+ /** Transport: `direct` or a `socks5h://` proxy for `.anyone` hosts. */
531
+ socksProxy?: string;
532
+ /** Auto-manage the anon proxy for `.anyone` BTP hosts. Default true for HS. */
533
+ managedAnonProxy?: boolean;
534
+ /** Loopback SOCKS port the managed anon proxy binds (also used for reads). Default 9050. */
535
+ managedAnonSocksPort?: number;
536
+ /** Town relay WS URL for FREE reads. */
537
+ relayUrl?: string;
538
+ /** Default ILP publish destination. Default `g.townhouse.town`. */
539
+ destination?: string;
540
+ /** Default fee per paid write, base units. Default `1`. */
541
+ feePerEvent?: string;
542
+ /** Channel nonce-watermark persistence file. Default `<dir>/channels.json`. */
543
+ channelStorePath?: string;
544
+ /** Localhost control-plane port. Default 8787. */
545
+ httpPort?: number;
546
+ /**
547
+ * Active settlement chain for paid writes to the apex. A single daemon settles
548
+ * to a given peer on ONE chain (the `ChannelManager` keys channels per peer +
549
+ * each `ToonClient` owns one BTP session). Default `evm`. Override with
550
+ * `TOON_CLIENT_CHAIN`. For simultaneous multi-chain, run one daemon per chain
551
+ * (distinct `httpPort` + `channelStorePath`).
552
+ */
553
+ chain?: SettlementChain;
554
+ /** Manual apex negotiation (HS / direct-apex mode where bootstrap finds 0 peers). */
555
+ apex?: ApexNegotiationConfig;
556
+ /**
557
+ * Per-chain apex negotiations. The entry for the active `chain` is used; the
558
+ * others are retained so switching chains needs only a `chain`/restart change.
559
+ */
560
+ apexChains?: Partial<Record<SettlementChain, ApexNegotiationConfig>>;
561
+ /**
562
+ * Additional apex CHILD peers (last ILP segment, e.g. `["dvm","mill"]`)
563
+ * reachable via the SAME apex channel — used when publishing/swapping to
564
+ * `g.townhouse.dvm` / `g.townhouse.mill`. The runner injects the apex
565
+ * negotiation under each and points it at the open apex channel (no extra
566
+ * on-chain channel). The apex `peerId` itself is always handled.
567
+ */
568
+ apexChildPeers?: string[];
569
+ /** Extra settlement overrides passed straight through to ToonClient. */
570
+ supportedChains?: string[];
571
+ settlementAddresses?: Record<string, string>;
572
+ preferredTokens?: Record<string, string>;
573
+ tokenNetworks?: Record<string, string>;
574
+ chainRpcUrls?: Record<string, string>;
575
+ /** Solana on-chain payment-channel params (required when `chain` is solana). */
576
+ solanaChannel?: ToonClientConfig['solanaChannel'];
577
+ /** Mina on-chain payment-channel params (required when `chain` is mina). */
578
+ minaChannel?: ToonClientConfig['minaChannel'];
579
+ }
580
+ interface ResolvedDaemonConfig {
581
+ httpPort: number;
582
+ relayUrl: string;
583
+ socksProxy?: string;
584
+ /**
585
+ * When true the daemon must start its OWN managed `anon` proxy for free reads
586
+ * — the btp-direct + relay-`.anyone` case the ToonClient does not cover (it
587
+ * only auto-starts a proxy for a `.anyone` btpUrl). `readProxySocksPort` is the
588
+ * loopback port to bind; `socksProxy` already points the relay at it.
589
+ */
590
+ manageReadProxy: boolean;
591
+ /** Loopback SOCKS port for the daemon-managed read proxy (when `manageReadProxy`). */
592
+ readProxySocksPort?: number;
593
+ destination: string;
594
+ feePerEvent: bigint;
595
+ apex?: ApexNegotiationConfig;
596
+ /** Apex child peers reached via the same apex channel (e.g. dvm, mill). */
597
+ apexChildPeers?: string[];
598
+ /** The active settlement chain for paid writes. */
599
+ chain: SettlementChain;
600
+ /** File mapping (destination, chain) → on-chain channelId for restart resume. */
601
+ apexChannelStorePath: string;
602
+ /** Fully-built config for the `ToonClient` constructor. */
603
+ toonClientConfig: ToonClientConfig;
604
+ network?: string;
605
+ }
606
+ /**
607
+ * Password used to encrypt an auto-generated keystore (#251 first-run
608
+ * onboarding) when `TOON_CLIENT_KEYSTORE_PASSWORD` is unset. At-rest
609
+ * obfuscation only — its purpose is letting the daemon reload the identity
610
+ * across restarts with no env var. Users wanting a real password re-import the
611
+ * keystore and set the env var.
612
+ */
613
+ declare const DEFAULT_KEYSTORE_PASSWORD = "toon-client-default";
614
+ /** Default config directory: `~/.toon-client`. Overridable via env. */
615
+ declare function configDir(): string;
616
+ /** Default config file path. */
617
+ declare function defaultConfigPath(): string;
618
+ /** Read + parse the JSON config file, returning `{}` when absent. */
619
+ declare function readConfigFile(path: string): DaemonConfigFile;
620
+ /** Resolve the mnemonic from env / keystore / config (in precedence order). */
621
+ declare function resolveMnemonic(file: DaemonConfigFile): string;
622
+ /**
623
+ * Build the full resolved daemon config (file overlaid with env, mnemonic
624
+ * resolved, ToonClientConfig assembled). Env overrides supported:
625
+ * TOON_CLIENT_BTP_URL, TOON_CLIENT_RELAY_URL, TOON_CLIENT_SOCKS,
626
+ * TOON_CLIENT_HTTP_PORT, TOON_CLIENT_NETWORK.
627
+ */
628
+ declare function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig;
629
+
630
+ /**
631
+ * ClientRunner — the daemon's connection owner. The TOON client is 1-to-MANY:
632
+ * it can write through several apexes (each a `ToonClient` + BTP session +
633
+ * payment channel) and read from several relays (each a `RelaySubscription`).
634
+ *
635
+ * • Writes go through BTP, never to a relay directly — `publish`/`swap` select
636
+ * an apex (default: the config-seeded one).
637
+ * • Reads FAN OUT — `subscribe`/`getEvents` apply across every relay and merge
638
+ * into one ordered stream with a single scalar cursor (the runner owns the
639
+ * merged buffer; each `RelaySubscription` mirrors new events into it).
640
+ *
641
+ * Targets are added at runtime (`addRelay`/`addApex`), persisted to
642
+ * `targets.json`, replayed on the next boot, and removable. The config-seeded
643
+ * relay + apex are the permanent DEFAULT targets and cannot be removed.
644
+ *
645
+ * Each apex bootstraps asynchronously and non-blocking: the connection comes up
646
+ * in the background (the managed anon proxy alone can take 30–90s). Until ready,
647
+ * writes against it report `bootstrapping` so tools surface "retry".
648
+ */
649
+
650
+ /** The subset of `ToonClient` the runner depends on. */
651
+ interface ToonClientLike {
652
+ start(): Promise<{
653
+ peersDiscovered: number;
654
+ mode: string;
655
+ }>;
656
+ stop(): Promise<void>;
657
+ getPublicKey(): string;
658
+ getEvmAddress(): string | undefined;
659
+ getSolanaAddress(): string | undefined;
660
+ getMinaAddress(): string | undefined;
661
+ getNetworkStatus(): {
662
+ evm: string;
663
+ solana: string;
664
+ mina: string;
665
+ } | undefined;
666
+ publishEvent(event: NostrEvent, options?: {
667
+ destination?: string;
668
+ claim?: unknown;
669
+ ilpAmount?: bigint;
670
+ }): Promise<{
671
+ success: boolean;
672
+ eventId?: string;
673
+ data?: string;
674
+ error?: string;
675
+ }>;
676
+ signBalanceProof(channelId: string, amount: bigint): Promise<unknown>;
677
+ openChannel(destination?: string): Promise<string>;
678
+ getTrackedChannels(): string[];
679
+ getChannelNonce(channelId: string): number;
680
+ getChannelCumulativeAmount(channelId: string): bigint;
681
+ sendSwapPacket(params: {
682
+ destination: string;
683
+ amount: bigint;
684
+ toonData: Uint8Array;
685
+ claim?: unknown;
686
+ }): Promise<{
687
+ accepted: boolean;
688
+ data?: string;
689
+ code?: string;
690
+ message?: string;
691
+ }>;
692
+ }
693
+ /** A started managed proxy: just the teardown handle the runner needs. */
694
+ interface ManagedProxyHandle {
695
+ stop(): Promise<void>;
696
+ }
697
+ /** Starts a managed `anon` read proxy on a loopback SOCKS port. */
698
+ type StartReadProxy = (opts: {
699
+ socksPort: number;
700
+ log?: (msg: string) => void;
701
+ }) => Promise<ManagedProxyHandle>;
702
+ /** Builds a `ToonClient` (or a fake) for a given resolved client config. */
703
+ type CreateClient = (config: ToonClientConfig) => ToonClientLike;
704
+ /** Builds a `RelaySubscription` for a given relay URL + optional read proxy. */
705
+ type CreateRelay = (opts: {
706
+ relayUrl: string;
707
+ socksProxy?: string;
708
+ onEvent: (subId: string, event: NostrEvent) => void;
709
+ logger?: (msg: string) => void;
710
+ }) => RelaySubscription;
711
+ interface ClientRunnerDeps {
712
+ config: ResolvedDaemonConfig;
713
+ /** Factory producing the (real or fake) ToonClient for a client config. */
714
+ createClient: CreateClient;
715
+ /** Factory producing a relay subscription (defaults to the real one). */
716
+ createRelay?: CreateRelay;
717
+ /**
718
+ * Starts the daemon-managed read proxy (defaults to the real
719
+ * `startManagedAnonProxy`). Injected so tests avoid the anon download/spawn.
720
+ */
721
+ startReadProxy?: StartReadProxy;
722
+ logger?: (msg: string) => void;
723
+ /** Path to the dynamic-targets store (tests override). */
724
+ targetsPath?: string;
725
+ }
726
+ declare class ClientRunner {
727
+ private readonly config;
728
+ private readonly createClient;
729
+ private readonly createRelay;
730
+ private readonly startReadProxy;
731
+ private readonly log;
732
+ private readonly targetsPath?;
733
+ private readonly startedAt;
734
+ /** Apex write targets, keyed by btpUrl. */
735
+ private readonly apexes;
736
+ /** Relay read targets, keyed by relayUrl. */
737
+ private readonly relays;
738
+ /** Runner-level merged read buffer across all relays (de-duped by event.id). */
739
+ private merged;
740
+ private readonly mergedSeen;
741
+ private mergedSeq;
742
+ /**
743
+ * Fan-out subscriptions (no relayUrl restriction): replayed onto relays added
744
+ * later so a new relay immediately participates in existing reads.
745
+ */
746
+ private readonly fanoutSubs;
747
+ private subIdCounter;
748
+ private readonly defaultBtpUrl;
749
+ private readonly defaultRelayUrl;
750
+ /** Teardown for the daemon-managed read proxy (btp-direct + relay-`.anyone`). */
751
+ private stopReadProxy;
752
+ private readProxyError;
753
+ private stopped;
754
+ private started;
755
+ constructor(deps: ClientRunnerDeps);
756
+ /**
757
+ * Start the live connections: the shared read proxy, every relay socket, the
758
+ * default apex bootstrap (non-blocking), then replay persisted dynamic
759
+ * targets. Returns immediately; apexes become ready asynchronously.
760
+ */
761
+ start(): void;
762
+ /** Await the default apex's bootstrap (kicking it off if not already running). */
763
+ bootstrap(): Promise<void>;
764
+ /**
765
+ * Build + register a relay (idempotent by URL), wiring its events into the
766
+ * merged buffer and replaying active fan-out subscriptions. Does NOT start the
767
+ * socket — callers start it (so construction stays side-effect-free for tests).
768
+ */
769
+ private registerRelay;
770
+ /**
771
+ * Add a relay read target at runtime. `.anyone` relays reuse the managed read
772
+ * proxy (started here if needed). Persisted unless `persist` is false.
773
+ */
774
+ addRelay(relayUrl: string, persist?: boolean): Promise<void>;
775
+ /** Remove a relay read target. The config-seeded default cannot be removed. */
776
+ removeRelay(relayUrl: string): void;
777
+ /** Mirror a newly-buffered relay event into the merged cross-relay buffer. */
778
+ private pushMerged;
779
+ /**
780
+ * Register a free-read subscription. With no `relayUrl` it FANS OUT across
781
+ * every relay (and onto relays added later); with one it targets that relay.
782
+ */
783
+ subscribe(req: SubscribeRequest): SubscribeResponse;
784
+ /** Drain merged events newer than the cursor (free read), optionally scoped. */
785
+ getEvents(query: EventsQuery): EventsResponse;
786
+ private makeApex;
787
+ /**
788
+ * Bootstrap one apex (memoized): start, inject negotiation, open/resume the
789
+ * channel, route child peers. Concurrent callers await the same in-flight
790
+ * work rather than re-running it.
791
+ */
792
+ private bootstrapApex;
793
+ private doBootstrapApex;
794
+ /**
795
+ * Add an apex write target. Settlement params are discovered by reading the
796
+ * apex's kind:10032 off the given relay (added first if unknown). Persisted.
797
+ */
798
+ addApex(req: AddApexRequest): Promise<AddApexResponse>;
799
+ /** Build + register + bootstrap an apex from a (persisted) target record. */
800
+ private instantiateApex;
801
+ /** Remove an apex write target. The config-seeded default cannot be removed. */
802
+ removeApex(btpUrl: string): Promise<void>;
803
+ /** Derive a per-apex ToonClientConfig from the default (shared identity/transport). */
804
+ private deriveApexClientConfig;
805
+ private apexChannelStorePathFor;
806
+ private replayPersistedTargets;
807
+ private ensureReadProxy;
808
+ private bringUpReadProxy;
809
+ /** Open the apex channel — or, on a restart, RESUME the existing one. */
810
+ private openOrResumeApexChannel;
811
+ /** Inject the apex settlement negotiation directly into its ToonClient. */
812
+ private injectApexNegotiation;
813
+ /** Route apex CHILD peers (dvm/mill) through the SAME apex payment channel. */
814
+ private routeChildPeersThroughApexChannel;
815
+ private defaultApex;
816
+ /** Whether any apex has finished bootstrapping. */
817
+ isReady(): boolean;
818
+ isBootstrapping(): boolean;
819
+ getStatus(): StatusResponse;
820
+ /** Full registry of relay + apex targets with per-target status. */
821
+ getTargets(): TargetsResponse;
822
+ /** Pay-to-write a single event through the selected (or default) apex. */
823
+ publish(req: PublishRequest): Promise<PublishResponse>;
824
+ /** Open (or return) a payment channel on the selected (or default) apex. */
825
+ openChannel(destination?: string, btpUrl?: string): Promise<{
826
+ channelId: string;
827
+ }>;
828
+ /** List tracked channels across ALL apexes with nonce + cumulative amount. */
829
+ getChannels(): ChannelsResponse;
830
+ /** Swap source→target asset against a mill peer via the selected apex. */
831
+ swap(req: SwapRequest): Promise<SwapResponse>;
832
+ /** Graceful teardown: close every relay + stop every apex client + read proxy. */
833
+ stop(): Promise<void>;
834
+ private selectApex;
835
+ private assertApexReady;
836
+ }
837
+ /** Thrown by paid-write operations while the target apex is not yet ready. */
838
+ declare class NotReadyError extends Error {
839
+ readonly retryable = true;
840
+ constructor(message: string);
841
+ }
842
+ /** Thrown when the relay/connector rejects a paid write. */
843
+ declare class PublishRejectedError extends Error {
844
+ constructor(message: string);
845
+ }
846
+
847
+ /**
848
+ * First-run onboarding for `toon-clientd`.
849
+ *
850
+ * A brand-new user (`npx`/plugin install) has no identity and no config file.
851
+ * Before the daemon resolves its config it calls {@link scaffoldFirstRun},
852
+ * which makes a fresh install start with zero manual setup:
853
+ *
854
+ * 1. **Identity** — if no mnemonic source is configured (no
855
+ * `TOON_CLIENT_MNEMONIC`, no `keystorePath`, no `mnemonic`), generate a
856
+ * fresh BIP-39 mnemonic, encrypt it to `~/.toon-client/keystore.json`, and
857
+ * record `keystorePath` (+ `keystoreAutoPassword`) in `config.json`. The
858
+ * keystore is encrypted with `TOON_CLIENT_KEYSTORE_PASSWORD` when set, else
859
+ * a default password so the identity survives restarts with no env var.
860
+ * The mnemonic + derived addresses are printed ONCE for backup.
861
+ * 2. **Transport scaffolding** — ensure `config.json` carries the transport
862
+ * knobs (`btpUrl`/`relayUrl`/`managedAnonProxy`) plus a `_help` block
863
+ * documenting direct-vs-`.anyone` selection, so the user can see what to
864
+ * point at.
865
+ *
866
+ * This is all idempotent: on later runs an identity already exists, so nothing
867
+ * is regenerated and the config is left untouched.
868
+ */
869
+
870
+ /** Default keystore path: `~/.toon-client/keystore.json`. */
871
+ declare function defaultKeystorePath(): string;
872
+ /**
873
+ * True when SOME mnemonic source is already configured: the env var, a keystore
874
+ * path, or an inline mnemonic. When false the daemon would otherwise hard-fail
875
+ * with "No mnemonic configured".
876
+ */
877
+ declare function hasConfiguredIdentity(file: DaemonConfigFile): boolean;
878
+ interface ScaffoldOptions {
879
+ /** Config file path (defaults to `TOON_CLIENT_CONFIG` / `~/.toon-client/config.json`). */
880
+ configPath?: string;
881
+ /** Log sink (defaults to stderr, since stdout may carry MCP/JSON). */
882
+ log?: (msg: string) => void;
883
+ }
884
+ /**
885
+ * Generate + persist an identity and/or scaffold transport config on first run.
886
+ * Safe to call on every startup — it only acts when something is missing.
887
+ */
888
+ declare function scaffoldFirstRun(opts?: ScaffoldOptions): Promise<void>;
889
+
890
+ /**
891
+ * Fastify route registration for the `toon-clientd` control plane. Each route
892
+ * is a thin adapter: parse/validate the request, call the `ClientRunner`, map
893
+ * errors to the uniform `ErrorResponse` envelope.
894
+ *
895
+ * Bound to loopback only by the daemon entry — there is no auth layer because
896
+ * the surface never leaves `127.0.0.1`.
897
+ */
898
+
899
+ declare function registerRoutes(app: FastifyInstance, runner: ClientRunner): void;
900
+
901
+ /** Whether a process with `pid` is currently alive. */
902
+ declare function isProcessAlive(pid: number): boolean;
903
+ /** Read the recorded daemon PID, or null when absent/invalid. */
904
+ declare function readPid(path?: string): number | null;
905
+ /**
906
+ * Acquire the single-instance lock for the current process. Throws if another
907
+ * live daemon already holds it. Reclaims a stale lock (dead PID).
908
+ */
909
+ declare function acquireLock(path?: string): void;
910
+ /** Release the lock if it belongs to this process. */
911
+ declare function releaseLock(path?: string): void;
912
+ /** Whether a daemon is currently running per the PID lock. */
913
+ declare function isDaemonRunning(path?: string): boolean;
914
+ interface SpawnDaemonOptions {
915
+ /** Path to the `toon-clientd` bin (defaults to this package's daemon entry). */
916
+ daemonEntry?: string;
917
+ /** Extra env for the spawned process. */
918
+ env?: NodeJS.ProcessEnv;
919
+ /** Directory for the detached stdout/stderr log. */
920
+ logDir?: string;
921
+ }
922
+ /**
923
+ * Spawn the daemon as a detached, fully background process (survives the
924
+ * parent — e.g. the ephemeral MCP/agent session — exiting). Returns the child
925
+ * PID. The caller should poll {@link waitForReady} before issuing requests.
926
+ */
927
+ declare function spawnDaemonDetached(opts?: SpawnDaemonOptions): number;
928
+ /**
929
+ * Poll the control plane until the daemon answers `GET /status`, up to
930
+ * `timeoutMs`. Resolves true once reachable (NOT necessarily done
931
+ * bootstrapping — anon can take 30–90s; callers surface `bootstrapping`).
932
+ */
933
+ declare function waitForReady(baseUrl: string, timeoutMs?: number, intervalMs?: number): Promise<boolean>;
934
+
935
+ /**
936
+ * MCP tool definitions + dispatch. The MCP server is a thin proxy: each tool
937
+ * maps to a `toon-clientd` control-plane call. This module is the testable core
938
+ * (no stdio / SDK transport) so the tool→HTTP mapping and the
939
+ * "bootstrapping — retry" handling can be unit-tested directly.
940
+ */
941
+
942
+ /** A JSON-Schema-described MCP tool. */
943
+ interface ToolDefinition {
944
+ name: string;
945
+ description: string;
946
+ inputSchema: Record<string, unknown>;
947
+ }
948
+ /** MCP tool-call result shape (subset of the SDK's CallToolResult). */
949
+ interface ToolResult {
950
+ content: {
951
+ type: 'text';
952
+ text: string;
953
+ }[];
954
+ isError?: boolean;
955
+ }
956
+ declare const TOOL_DEFINITIONS: ToolDefinition[];
957
+ /**
958
+ * Dispatch an MCP tool call to the daemon control plane. Always resolves with a
959
+ * `ToolResult` (errors are encoded as `isError: true` text, not thrown, so the
960
+ * agent sees a readable message). A retryable error (daemon still
961
+ * bootstrapping) yields a clear "retry shortly" message.
962
+ */
963
+ declare function dispatchTool(client: ControlClient, name: string, args: Record<string, unknown>): Promise<ToolResult>;
964
+
965
+ export { type AddApexRequest, type AddApexResponse, type AddRelayRequest, type ApexNegotiationConfig, type ApexTargetStatus, type ChainStatus, type ChannelInfo, type ChannelsResponse, ClientRunner, type ClientRunnerDeps, ControlApiError, ControlClient, type ControlClientOptions, DEFAULT_KEYSTORE_PASSWORD, type DaemonConfigFile, DaemonUnreachableError, type DrainResult, type ErrorResponse, type EventsQuery, type EventsResponse, type MinimalWebSocket, type NostrFilter, NotReadyError, type OpenChannelRequest, PublishRejectedError, type PublishRequest, type PublishResponse, RelaySubscription, type RelaySubscriptionOptions, type RelayTargetStatus, type RemoveApexRequest, type RemoveRelayRequest, type ResolvedDaemonConfig, type SettlementChain, type StatusResponse, type SubscribeRequest, type SubscribeResponse, type SwapClaim, type SwapRequest, type SwapResponse, TOOL_DEFINITIONS, type TargetsResponse, type ToolDefinition, type ToolResult, type ToonClientLike, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, defaultKeystorePath, dispatchTool, hasConfiguredIdentity, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, scaffoldFirstRun, spawnDaemonDetached, waitForReady };