@toon-protocol/client-mcp 0.26.2
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/LICENSE +190 -0
- package/README.md +261 -0
- package/dist/anon-proxy-6N362VEV-M7AX2QD7.js +24 -0
- package/dist/anon-proxy-6N362VEV-M7AX2QD7.js.map +1 -0
- package/dist/chunk-245J23EB.js +278 -0
- package/dist/chunk-245J23EB.js.map +1 -0
- package/dist/chunk-2SGZPDGE.js +625 -0
- package/dist/chunk-2SGZPDGE.js.map +1 -0
- package/dist/chunk-32QD72IL.js +83 -0
- package/dist/chunk-32QD72IL.js.map +1 -0
- package/dist/chunk-5YIZ2JQO.js +205 -0
- package/dist/chunk-5YIZ2JQO.js.map +1 -0
- package/dist/chunk-LR7W2ISE.js +657 -0
- package/dist/chunk-LR7W2ISE.js.map +1 -0
- package/dist/chunk-QTDCFXPF.js +2802 -0
- package/dist/chunk-QTDCFXPF.js.map +1 -0
- package/dist/chunk-VA7XC4FD.js +185 -0
- package/dist/chunk-VA7XC4FD.js.map +1 -0
- package/dist/chunk-WMYY5I3H.js +10818 -0
- package/dist/chunk-WMYY5I3H.js.map +1 -0
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +137 -0
- package/dist/daemon.js.map +1 -0
- package/dist/ed25519-OFFWPWRE.js +26 -0
- package/dist/ed25519-OFFWPWRE.js.map +1 -0
- package/dist/gateway-QOK47RKS-HB65KIKC.js +15 -0
- package/dist/gateway-QOK47RKS-HB65KIKC.js.map +1 -0
- package/dist/hmac-7WSXTWW4.js +11 -0
- package/dist/hmac-7WSXTWW4.js.map +1 -0
- package/dist/index.d.ts +642 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +80 -0
- package/dist/mcp.js.map +1 -0
- package/dist/sha512-LMOIUNFJ.js +33 -0
- package/dist/sha512-LMOIUNFJ.js.map +1 -0
- package/dist/socks5-WTJBYGME-IXWLQDE7.js +138 -0
- package/dist/socks5-WTJBYGME-IXWLQDE7.js.map +1 -0
- package/package.json +59 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
import { NostrEvent } from 'nostr-tools/pure';
|
|
2
|
+
import { ToonClientConfig } from '@toon-protocol/client';
|
|
3
|
+
import { FastifyInstance } from 'fastify';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared request/response contract for the `toon-clientd` localhost control
|
|
7
|
+
* plane. Both the daemon (server) and the MCP server / control-client (caller)
|
|
8
|
+
* import these types so the wire shape stays in lockstep.
|
|
9
|
+
*
|
|
10
|
+
* Every endpoint is plain JSON over HTTP on `127.0.0.1:<port>`. The daemon owns
|
|
11
|
+
* the long-lived BTP session + payment channels + relay subscription; callers
|
|
12
|
+
* are stateless and never see chain keys.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The chain family a paid write settles on. */
|
|
16
|
+
type SettlementChain = 'evm' | 'solana' | 'mina';
|
|
17
|
+
/** Per-chain settlement readiness, mirrored from `ToonClient.getNetworkStatus()`. */
|
|
18
|
+
interface ChainStatus {
|
|
19
|
+
chain: string;
|
|
20
|
+
/** Whether settlement is configured + the apex can verify inbound claims. */
|
|
21
|
+
ready: boolean;
|
|
22
|
+
detail?: string;
|
|
23
|
+
}
|
|
24
|
+
/** `GET /status` — daemon + connection health. */
|
|
25
|
+
interface StatusResponse {
|
|
26
|
+
/** Daemon process uptime, ms. */
|
|
27
|
+
uptimeMs: number;
|
|
28
|
+
/**
|
|
29
|
+
* True while the managed anon proxy / BTP session / channel are still coming
|
|
30
|
+
* up. Tools should surface "bootstrapping — retry" rather than blocking.
|
|
31
|
+
*/
|
|
32
|
+
bootstrapping: boolean;
|
|
33
|
+
/** True once the client has started and a channel is open (ready to publish). */
|
|
34
|
+
ready: boolean;
|
|
35
|
+
/** The active settlement chain for paid writes to the apex. */
|
|
36
|
+
settlementChain: SettlementChain;
|
|
37
|
+
identity: {
|
|
38
|
+
nostrPubkey: string;
|
|
39
|
+
evmAddress?: string;
|
|
40
|
+
solanaAddress?: string;
|
|
41
|
+
minaAddress?: string;
|
|
42
|
+
};
|
|
43
|
+
transport: {
|
|
44
|
+
type: 'direct' | 'socks5' | 'gateway';
|
|
45
|
+
socksProxy?: string;
|
|
46
|
+
btpUrl?: string;
|
|
47
|
+
};
|
|
48
|
+
relay: {
|
|
49
|
+
url: string;
|
|
50
|
+
connected: boolean;
|
|
51
|
+
/** Number of events currently held in the read buffer. */
|
|
52
|
+
buffered: number;
|
|
53
|
+
/** Active subscription ids. */
|
|
54
|
+
subscriptions: string[];
|
|
55
|
+
};
|
|
56
|
+
/** Per-chain settlement status when a named `network` tier is configured. */
|
|
57
|
+
network?: ChainStatus[];
|
|
58
|
+
/** Last error observed during bootstrap, if any (non-fatal). */
|
|
59
|
+
lastError?: string;
|
|
60
|
+
}
|
|
61
|
+
/** `POST /publish` — pay-to-write a single Nostr event. */
|
|
62
|
+
interface PublishRequest {
|
|
63
|
+
/** A fully-signed Nostr event (id + sig present). */
|
|
64
|
+
event: NostrEvent;
|
|
65
|
+
/** ILP destination override (default: the configured apex/town address). */
|
|
66
|
+
destination?: string;
|
|
67
|
+
/** Fee override in base units. Defaults to the daemon's configured fee. */
|
|
68
|
+
fee?: string;
|
|
69
|
+
}
|
|
70
|
+
interface PublishResponse {
|
|
71
|
+
eventId: string;
|
|
72
|
+
/** FULFILL response data (base64), e.g. an Arweave tx id from a DVM. */
|
|
73
|
+
data?: string;
|
|
74
|
+
/** Channel the claim was signed against. */
|
|
75
|
+
channelId: string;
|
|
76
|
+
/** Channel nonce after this publish (advances by one per paid write). */
|
|
77
|
+
nonce: number;
|
|
78
|
+
}
|
|
79
|
+
/** `POST /subscribe` — register a persistent free-read subscription. */
|
|
80
|
+
interface SubscribeRequest {
|
|
81
|
+
/** NIP-01 filter(s). A single object or an array of OR-ed filters. */
|
|
82
|
+
filters: NostrFilter | NostrFilter[];
|
|
83
|
+
/** Optional caller-supplied subscription id (else one is generated). */
|
|
84
|
+
subId?: string;
|
|
85
|
+
}
|
|
86
|
+
interface SubscribeResponse {
|
|
87
|
+
subId: string;
|
|
88
|
+
}
|
|
89
|
+
/** `GET /events` — drain buffered events for a subscription (free read). */
|
|
90
|
+
interface EventsQuery {
|
|
91
|
+
/** Restrict to a single subscription id. */
|
|
92
|
+
subId?: string;
|
|
93
|
+
/** Cursor from a prior `EventsResponse.cursor`; returns only newer events. */
|
|
94
|
+
cursor?: number;
|
|
95
|
+
/** Max events to return (default 200). */
|
|
96
|
+
limit?: number;
|
|
97
|
+
}
|
|
98
|
+
interface EventsResponse {
|
|
99
|
+
events: NostrEvent[];
|
|
100
|
+
/** Opaque monotonic cursor; pass back to fetch only events after these. */
|
|
101
|
+
cursor: number;
|
|
102
|
+
/** Whether more events remain beyond `limit`. */
|
|
103
|
+
hasMore: boolean;
|
|
104
|
+
}
|
|
105
|
+
/** `POST /channels` — open (or return existing) a payment channel. */
|
|
106
|
+
interface OpenChannelRequest {
|
|
107
|
+
/** ILP destination of the peer to open against (default: configured apex). */
|
|
108
|
+
destination?: string;
|
|
109
|
+
}
|
|
110
|
+
interface ChannelInfo {
|
|
111
|
+
channelId: string;
|
|
112
|
+
nonce: number;
|
|
113
|
+
cumulativeAmount: string;
|
|
114
|
+
}
|
|
115
|
+
/** `GET /channels` — list tracked channels with nonce watermarks. */
|
|
116
|
+
interface ChannelsResponse {
|
|
117
|
+
channels: ChannelInfo[];
|
|
118
|
+
}
|
|
119
|
+
/** `POST /swap` — pay asset A to a mill peer, receive asset B + a target claim. */
|
|
120
|
+
interface SwapRequest {
|
|
121
|
+
/** Mill peer ILP destination. */
|
|
122
|
+
destination: string;
|
|
123
|
+
/** Amount to send in base units. */
|
|
124
|
+
amount: string;
|
|
125
|
+
/** Optional base64 TOON payload describing the swap intent. */
|
|
126
|
+
toonData?: string;
|
|
127
|
+
}
|
|
128
|
+
interface SwapResponse {
|
|
129
|
+
accepted: boolean;
|
|
130
|
+
/** base64 FULFILL data (the signed target-chain claim), when accepted. */
|
|
131
|
+
data?: string;
|
|
132
|
+
code?: string;
|
|
133
|
+
message?: string;
|
|
134
|
+
}
|
|
135
|
+
/** Uniform error envelope returned with non-2xx responses. */
|
|
136
|
+
interface ErrorResponse {
|
|
137
|
+
error: string;
|
|
138
|
+
detail?: string;
|
|
139
|
+
/** True when the caller should retry (e.g. still bootstrapping). */
|
|
140
|
+
retryable?: boolean;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* NIP-01 subscription filter. Tag filters use `#<single-letter>` keys, e.g.
|
|
144
|
+
* `{ '#e': [...], '#p': [...] }`.
|
|
145
|
+
*/
|
|
146
|
+
interface NostrFilter {
|
|
147
|
+
ids?: string[];
|
|
148
|
+
authors?: string[];
|
|
149
|
+
kinds?: number[];
|
|
150
|
+
since?: number;
|
|
151
|
+
until?: number;
|
|
152
|
+
limit?: number;
|
|
153
|
+
search?: string;
|
|
154
|
+
[tag: `#${string}`]: string[] | undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Thin HTTP client for the `toon-clientd` localhost control plane. Used by the
|
|
159
|
+
* MCP server (and any other caller) to drive the daemon without holding any
|
|
160
|
+
* chain keys or long-lived connections itself.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/** Error thrown when the daemon returns a non-2xx response. */
|
|
164
|
+
declare class ControlApiError extends Error {
|
|
165
|
+
readonly status: number;
|
|
166
|
+
readonly retryable: boolean;
|
|
167
|
+
readonly detail?: string | undefined;
|
|
168
|
+
constructor(message: string, status: number, retryable: boolean, detail?: string | undefined);
|
|
169
|
+
}
|
|
170
|
+
/** Thrown when the daemon is unreachable (not running / wrong port). */
|
|
171
|
+
declare class DaemonUnreachableError extends Error {
|
|
172
|
+
readonly baseUrl: string;
|
|
173
|
+
readonly causedBy?: unknown | undefined;
|
|
174
|
+
constructor(baseUrl: string, causedBy?: unknown | undefined);
|
|
175
|
+
}
|
|
176
|
+
interface ControlClientOptions {
|
|
177
|
+
/** Base URL of the daemon, e.g. `http://127.0.0.1:8787`. */
|
|
178
|
+
baseUrl: string;
|
|
179
|
+
/** Per-request timeout, ms. Default 35000 (publishes can wait on FULFILL). */
|
|
180
|
+
timeoutMs?: number;
|
|
181
|
+
/** Inject a fetch implementation (tests). Defaults to global `fetch`. */
|
|
182
|
+
fetchImpl?: typeof fetch;
|
|
183
|
+
}
|
|
184
|
+
declare class ControlClient {
|
|
185
|
+
private readonly baseUrl;
|
|
186
|
+
private readonly timeoutMs;
|
|
187
|
+
private readonly fetchImpl;
|
|
188
|
+
constructor(opts: ControlClientOptions);
|
|
189
|
+
/** True if the daemon answers `GET /status` (used as a liveness probe). */
|
|
190
|
+
ping(): Promise<boolean>;
|
|
191
|
+
status(): Promise<StatusResponse>;
|
|
192
|
+
publish(body: PublishRequest): Promise<PublishResponse>;
|
|
193
|
+
subscribe(body: SubscribeRequest): Promise<SubscribeResponse>;
|
|
194
|
+
events(query?: EventsQuery): Promise<EventsResponse>;
|
|
195
|
+
openChannel(body?: OpenChannelRequest): Promise<{
|
|
196
|
+
channelId: string;
|
|
197
|
+
}>;
|
|
198
|
+
channels(): Promise<ChannelsResponse>;
|
|
199
|
+
swap(body: SwapRequest): Promise<SwapResponse>;
|
|
200
|
+
private request;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Persistent town-relay Nostr-WS subscription — the read half of the TOON
|
|
205
|
+
* client, which `@toon-protocol/client` does not provide (its bootstrap only
|
|
206
|
+
* issues one-shot WS queries and `DiscoveryTracker` is passive).
|
|
207
|
+
*
|
|
208
|
+
* Reads are FREE: this opens a long-lived NIP-01 connection to the town relay,
|
|
209
|
+
* keeps a bounded ring buffer of received events (de-duplicated by `event.id`),
|
|
210
|
+
* and lets callers drain new events via a monotonic cursor. It auto-reconnects
|
|
211
|
+
* with exponential backoff and re-issues every active REQ on reconnect.
|
|
212
|
+
*
|
|
213
|
+
* The WebSocket is injectable (`wsFactory`) so unit tests can drive the wire
|
|
214
|
+
* protocol without a real relay; the default factory uses the `ws` package and,
|
|
215
|
+
* when a `socks5h://` proxy is configured, routes through it so `.anyone`
|
|
216
|
+
* hidden-service relays are reachable.
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/** Minimal WebSocket surface this module depends on (subset of `ws`). */
|
|
220
|
+
interface MinimalWebSocket {
|
|
221
|
+
send(data: string): void;
|
|
222
|
+
close(): void;
|
|
223
|
+
on(event: 'open' | 'close', cb: () => void): void;
|
|
224
|
+
on(event: 'message', cb: (data: unknown) => void): void;
|
|
225
|
+
on(event: 'error', cb: (err: unknown) => void): void;
|
|
226
|
+
}
|
|
227
|
+
type WebSocketFactory = (url: string) => MinimalWebSocket;
|
|
228
|
+
interface RelaySubscriptionOptions {
|
|
229
|
+
/** Town relay WS URL, e.g. `wss://<host>.anyone/` or `ws://localhost:7100`. */
|
|
230
|
+
relayUrl: string;
|
|
231
|
+
/** Optional `socks5h://host:port` proxy for `.anyone` relays. */
|
|
232
|
+
socksProxy?: string;
|
|
233
|
+
/** Max events retained in the ring buffer (oldest evicted). Default 5000. */
|
|
234
|
+
bufferSize?: number;
|
|
235
|
+
/** Base reconnect delay, ms. Default 1000. */
|
|
236
|
+
reconnectBaseMs?: number;
|
|
237
|
+
/** Max reconnect delay, ms. Default 30000. */
|
|
238
|
+
reconnectMaxMs?: number;
|
|
239
|
+
/** Inject a WebSocket factory (tests / proxy customisation). */
|
|
240
|
+
wsFactory?: WebSocketFactory;
|
|
241
|
+
/**
|
|
242
|
+
* Decode an `EVENT` payload that arrived as a string. The TOON relay sends
|
|
243
|
+
* events TOON-encoded (key/value text) rather than as a JSON object, so the
|
|
244
|
+
* daemon injects a TOON decoder here. When the payload is already a JSON
|
|
245
|
+
* object it is used directly and this is not called.
|
|
246
|
+
*/
|
|
247
|
+
decodeEvent?: (raw: string) => NostrEvent;
|
|
248
|
+
/** Optional logger. */
|
|
249
|
+
logger?: (msg: string) => void;
|
|
250
|
+
}
|
|
251
|
+
/** Result of {@link RelaySubscription.getEvents}. */
|
|
252
|
+
interface DrainResult {
|
|
253
|
+
events: NostrEvent[];
|
|
254
|
+
cursor: number;
|
|
255
|
+
hasMore: boolean;
|
|
256
|
+
}
|
|
257
|
+
declare class RelaySubscription {
|
|
258
|
+
private readonly relayUrl;
|
|
259
|
+
private readonly socksProxy?;
|
|
260
|
+
private readonly bufferSize;
|
|
261
|
+
private readonly reconnectBaseMs;
|
|
262
|
+
private readonly reconnectMaxMs;
|
|
263
|
+
private readonly log;
|
|
264
|
+
private readonly wsFactory;
|
|
265
|
+
private readonly decodeEvent?;
|
|
266
|
+
/** Active subscriptions: subId -> filters (re-sent on every (re)connect). */
|
|
267
|
+
private readonly subscriptions;
|
|
268
|
+
/** Ring buffer of received events, ordered by ascending `seq`. */
|
|
269
|
+
private buffer;
|
|
270
|
+
/** De-dup index: event.id -> seq (kept in lockstep with the buffer). */
|
|
271
|
+
private readonly seen;
|
|
272
|
+
private seqCounter;
|
|
273
|
+
private subIdCounter;
|
|
274
|
+
private ws;
|
|
275
|
+
private connected;
|
|
276
|
+
private closing;
|
|
277
|
+
private reconnectAttempts;
|
|
278
|
+
private reconnectTimer;
|
|
279
|
+
constructor(opts: RelaySubscriptionOptions);
|
|
280
|
+
/** Whether the underlying socket is currently open. */
|
|
281
|
+
isConnected(): boolean;
|
|
282
|
+
/** Number of events currently held in the buffer. */
|
|
283
|
+
bufferedCount(): number;
|
|
284
|
+
/** Active subscription ids. */
|
|
285
|
+
activeSubscriptions(): string[];
|
|
286
|
+
/** Open the connection (idempotent). */
|
|
287
|
+
start(): void;
|
|
288
|
+
/**
|
|
289
|
+
* Register a persistent subscription and (if connected) send the REQ.
|
|
290
|
+
* Returns the subscription id (caller-supplied or generated).
|
|
291
|
+
*/
|
|
292
|
+
subscribe(filters: NostrFilter | NostrFilter[], subId?: string): string;
|
|
293
|
+
/** Cancel a subscription and send CLOSE if connected. */
|
|
294
|
+
unsubscribe(subId: string): void;
|
|
295
|
+
/**
|
|
296
|
+
* Drain events newer than `cursor`. The cursor is the highest `seq` returned;
|
|
297
|
+
* pass it back to fetch only events received since. Filtering by `subId`
|
|
298
|
+
* restricts to one subscription.
|
|
299
|
+
*/
|
|
300
|
+
getEvents(opts?: {
|
|
301
|
+
subId?: string;
|
|
302
|
+
cursor?: number;
|
|
303
|
+
limit?: number;
|
|
304
|
+
}): DrainResult;
|
|
305
|
+
/** Close the connection permanently and stop reconnecting. */
|
|
306
|
+
close(): void;
|
|
307
|
+
private open;
|
|
308
|
+
private scheduleReconnect;
|
|
309
|
+
private sendReq;
|
|
310
|
+
private sendRaw;
|
|
311
|
+
private handleMessage;
|
|
312
|
+
/**
|
|
313
|
+
* Normalise an `EVENT` payload to a NostrEvent. Standard relays send a JSON
|
|
314
|
+
* object; the TOON relay sends a TOON-encoded string, decoded via the injected
|
|
315
|
+
* `decodeEvent`. Returns undefined when it can't be parsed.
|
|
316
|
+
*/
|
|
317
|
+
private parseEventPayload;
|
|
318
|
+
private bufferEvent;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Daemon configuration: resolved from a JSON config file and/or environment
|
|
323
|
+
* variables, then expanded into a `ToonClientConfig` (BTP + channels + signer)
|
|
324
|
+
* plus daemon-only settings (HTTP port, relay URL, apex negotiation).
|
|
325
|
+
*
|
|
326
|
+
* The mnemonic is sourced from (in precedence order):
|
|
327
|
+
* 1. `TOON_CLIENT_MNEMONIC` env var,
|
|
328
|
+
* 2. an encrypted keystore (#207) at `keystorePath`, decrypted with
|
|
329
|
+
* `TOON_CLIENT_KEYSTORE_PASSWORD`,
|
|
330
|
+
* 3. the `mnemonic` field of the config file (discouraged — plaintext on disk).
|
|
331
|
+
*/
|
|
332
|
+
|
|
333
|
+
/** Apex/town settlement parameters injected as a peer negotiation. */
|
|
334
|
+
interface ApexNegotiationConfig {
|
|
335
|
+
/** ILP destination address, e.g. `g.townhouse.town`. */
|
|
336
|
+
destination: string;
|
|
337
|
+
/** Peer id key used in the negotiation map (last ILP segment, e.g. `town`). */
|
|
338
|
+
peerId: string;
|
|
339
|
+
/** Settlement chain family. */
|
|
340
|
+
chain: SettlementChain;
|
|
341
|
+
/** Negotiated chain key, e.g. `evm:base:84532`. */
|
|
342
|
+
chainKey: string;
|
|
343
|
+
/** Numeric chain id (EVM only; 0 for solana/mina). */
|
|
344
|
+
chainId: number;
|
|
345
|
+
/** The apex's settlement (receive) address on `chain`. */
|
|
346
|
+
settlementAddress: string;
|
|
347
|
+
/** Token contract / mint / zkApp address. */
|
|
348
|
+
tokenAddress?: string;
|
|
349
|
+
/** EVM TokenNetwork / Solana programId / Mina zkApp address. */
|
|
350
|
+
tokenNetwork?: string;
|
|
351
|
+
}
|
|
352
|
+
interface DaemonConfigFile {
|
|
353
|
+
/** Named network tier (drives settlement presets, #209). */
|
|
354
|
+
network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';
|
|
355
|
+
mnemonic?: string;
|
|
356
|
+
mnemonicAccountIndex?: number;
|
|
357
|
+
keystorePath?: string;
|
|
358
|
+
/** BTP WebSocket URL of the apex/connector. */
|
|
359
|
+
btpUrl?: string;
|
|
360
|
+
/** Transport: `direct` or a `socks5h://` proxy for `.anyone` hosts. */
|
|
361
|
+
socksProxy?: string;
|
|
362
|
+
/** Auto-manage the anon proxy for `.anyone` BTP hosts. Default true for HS. */
|
|
363
|
+
managedAnonProxy?: boolean;
|
|
364
|
+
/** Loopback SOCKS port the managed anon proxy binds (also used for reads). Default 9050. */
|
|
365
|
+
managedAnonSocksPort?: number;
|
|
366
|
+
/** Town relay WS URL for FREE reads. */
|
|
367
|
+
relayUrl?: string;
|
|
368
|
+
/** Default ILP publish destination. Default `g.townhouse.town`. */
|
|
369
|
+
destination?: string;
|
|
370
|
+
/** Default fee per paid write, base units. Default `1`. */
|
|
371
|
+
feePerEvent?: string;
|
|
372
|
+
/** Channel nonce-watermark persistence file. Default `<dir>/channels.json`. */
|
|
373
|
+
channelStorePath?: string;
|
|
374
|
+
/** Localhost control-plane port. Default 8787. */
|
|
375
|
+
httpPort?: number;
|
|
376
|
+
/**
|
|
377
|
+
* Active settlement chain for paid writes to the apex. A single daemon settles
|
|
378
|
+
* to a given peer on ONE chain (the `ChannelManager` keys channels per peer +
|
|
379
|
+
* each `ToonClient` owns one BTP session). Default `evm`. Override with
|
|
380
|
+
* `TOON_CLIENT_CHAIN`. For simultaneous multi-chain, run one daemon per chain
|
|
381
|
+
* (distinct `httpPort` + `channelStorePath`).
|
|
382
|
+
*/
|
|
383
|
+
chain?: SettlementChain;
|
|
384
|
+
/** Manual apex negotiation (HS / direct-apex mode where bootstrap finds 0 peers). */
|
|
385
|
+
apex?: ApexNegotiationConfig;
|
|
386
|
+
/**
|
|
387
|
+
* Per-chain apex negotiations. The entry for the active `chain` is used; the
|
|
388
|
+
* others are retained so switching chains needs only a `chain`/restart change.
|
|
389
|
+
*/
|
|
390
|
+
apexChains?: Partial<Record<SettlementChain, ApexNegotiationConfig>>;
|
|
391
|
+
/** Extra settlement overrides passed straight through to ToonClient. */
|
|
392
|
+
supportedChains?: string[];
|
|
393
|
+
settlementAddresses?: Record<string, string>;
|
|
394
|
+
preferredTokens?: Record<string, string>;
|
|
395
|
+
tokenNetworks?: Record<string, string>;
|
|
396
|
+
chainRpcUrls?: Record<string, string>;
|
|
397
|
+
/** Solana on-chain payment-channel params (required when `chain` is solana). */
|
|
398
|
+
solanaChannel?: ToonClientConfig['solanaChannel'];
|
|
399
|
+
/** Mina on-chain payment-channel params (required when `chain` is mina). */
|
|
400
|
+
minaChannel?: ToonClientConfig['minaChannel'];
|
|
401
|
+
}
|
|
402
|
+
interface ResolvedDaemonConfig {
|
|
403
|
+
httpPort: number;
|
|
404
|
+
relayUrl: string;
|
|
405
|
+
socksProxy?: string;
|
|
406
|
+
destination: string;
|
|
407
|
+
feePerEvent: bigint;
|
|
408
|
+
apex?: ApexNegotiationConfig;
|
|
409
|
+
/** The active settlement chain for paid writes. */
|
|
410
|
+
chain: SettlementChain;
|
|
411
|
+
/** File mapping (destination, chain) → on-chain channelId for restart resume. */
|
|
412
|
+
apexChannelStorePath: string;
|
|
413
|
+
/** Fully-built config for the `ToonClient` constructor. */
|
|
414
|
+
toonClientConfig: ToonClientConfig;
|
|
415
|
+
network?: string;
|
|
416
|
+
}
|
|
417
|
+
/** Default config directory: `~/.toon-client`. Overridable via env. */
|
|
418
|
+
declare function configDir(): string;
|
|
419
|
+
/** Default config file path. */
|
|
420
|
+
declare function defaultConfigPath(): string;
|
|
421
|
+
/** Read + parse the JSON config file, returning `{}` when absent. */
|
|
422
|
+
declare function readConfigFile(path: string): DaemonConfigFile;
|
|
423
|
+
/** Resolve the mnemonic from env / keystore / config (in precedence order). */
|
|
424
|
+
declare function resolveMnemonic(file: DaemonConfigFile): string;
|
|
425
|
+
/**
|
|
426
|
+
* Build the full resolved daemon config (file overlaid with env, mnemonic
|
|
427
|
+
* resolved, ToonClientConfig assembled). Env overrides supported:
|
|
428
|
+
* TOON_CLIENT_BTP_URL, TOON_CLIENT_RELAY_URL, TOON_CLIENT_SOCKS,
|
|
429
|
+
* TOON_CLIENT_HTTP_PORT, TOON_CLIENT_NETWORK.
|
|
430
|
+
*/
|
|
431
|
+
declare function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig;
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* ClientRunner — the daemon's connection owner. Wraps a single `ToonClient`
|
|
435
|
+
* (BTP session + payment channels + signer) plus a persistent
|
|
436
|
+
* `RelaySubscription` (free reads), and exposes the high-level operations the
|
|
437
|
+
* HTTP routes map onto.
|
|
438
|
+
*
|
|
439
|
+
* Bootstrap is asynchronous and non-blocking: `start()` returns immediately and
|
|
440
|
+
* the connection comes up in the background (the managed anon proxy alone can
|
|
441
|
+
* take 30–90s). Until it is ready, write operations report `bootstrapping` so
|
|
442
|
+
* tools surface "retry" rather than hang.
|
|
443
|
+
*
|
|
444
|
+
* The `ToonClient` is injected via `ToonClientLike` so unit tests can drive the
|
|
445
|
+
* runner with a fake — no BTP/anon/on-chain dependency.
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
/** The subset of `ToonClient` the runner depends on. */
|
|
449
|
+
interface ToonClientLike {
|
|
450
|
+
start(): Promise<{
|
|
451
|
+
peersDiscovered: number;
|
|
452
|
+
mode: string;
|
|
453
|
+
}>;
|
|
454
|
+
stop(): Promise<void>;
|
|
455
|
+
getPublicKey(): string;
|
|
456
|
+
getEvmAddress(): string | undefined;
|
|
457
|
+
getSolanaAddress(): string | undefined;
|
|
458
|
+
getMinaAddress(): string | undefined;
|
|
459
|
+
getNetworkStatus(): {
|
|
460
|
+
evm: string;
|
|
461
|
+
solana: string;
|
|
462
|
+
mina: string;
|
|
463
|
+
} | undefined;
|
|
464
|
+
publishEvent(event: NostrEvent, options?: {
|
|
465
|
+
destination?: string;
|
|
466
|
+
claim?: unknown;
|
|
467
|
+
ilpAmount?: bigint;
|
|
468
|
+
}): Promise<{
|
|
469
|
+
success: boolean;
|
|
470
|
+
eventId?: string;
|
|
471
|
+
data?: string;
|
|
472
|
+
error?: string;
|
|
473
|
+
}>;
|
|
474
|
+
signBalanceProof(channelId: string, amount: bigint): Promise<unknown>;
|
|
475
|
+
openChannel(destination?: string): Promise<string>;
|
|
476
|
+
getTrackedChannels(): string[];
|
|
477
|
+
getChannelNonce(channelId: string): number;
|
|
478
|
+
getChannelCumulativeAmount(channelId: string): bigint;
|
|
479
|
+
sendSwapPacket(params: {
|
|
480
|
+
destination: string;
|
|
481
|
+
amount: bigint;
|
|
482
|
+
toonData: Uint8Array;
|
|
483
|
+
claim?: unknown;
|
|
484
|
+
}): Promise<{
|
|
485
|
+
accepted: boolean;
|
|
486
|
+
data?: string;
|
|
487
|
+
code?: string;
|
|
488
|
+
message?: string;
|
|
489
|
+
}>;
|
|
490
|
+
}
|
|
491
|
+
interface ClientRunnerDeps {
|
|
492
|
+
config: ResolvedDaemonConfig;
|
|
493
|
+
/** Factory producing the (real or fake) ToonClient. */
|
|
494
|
+
createClient: () => ToonClientLike;
|
|
495
|
+
/** Factory producing the relay subscription (defaults to the real one). */
|
|
496
|
+
createRelay?: () => RelaySubscription;
|
|
497
|
+
logger?: (msg: string) => void;
|
|
498
|
+
}
|
|
499
|
+
declare class ClientRunner {
|
|
500
|
+
private readonly config;
|
|
501
|
+
private readonly client;
|
|
502
|
+
private readonly relay;
|
|
503
|
+
private readonly log;
|
|
504
|
+
private readonly startedAt;
|
|
505
|
+
private bootstrapping;
|
|
506
|
+
private ready;
|
|
507
|
+
private lastError;
|
|
508
|
+
/** Channel opened against the default apex destination. */
|
|
509
|
+
private apexChannelId;
|
|
510
|
+
private stopped;
|
|
511
|
+
constructor(deps: ClientRunnerDeps);
|
|
512
|
+
/**
|
|
513
|
+
* Begin bootstrapping in the background. Resolves once kicked off; the
|
|
514
|
+
* connection becomes ready asynchronously. Awaitable for tests via the
|
|
515
|
+
* returned promise of the underlying work.
|
|
516
|
+
*/
|
|
517
|
+
start(): void;
|
|
518
|
+
/** The background bootstrap routine (exposed for awaiting in tests). */
|
|
519
|
+
bootstrap(): Promise<void>;
|
|
520
|
+
/**
|
|
521
|
+
* Open the apex channel — or, on a restart, RESUME the existing one.
|
|
522
|
+
*
|
|
523
|
+
* `ChannelManager` persists the nonce watermark (by channelId) but not the
|
|
524
|
+
* peer→channelId mapping, so a naive `openChannel()` after restart re-deposits
|
|
525
|
+
* into a fresh channel and reverts on-chain. We persist the channelId here and,
|
|
526
|
+
* when present, `trackChannel()` the live channel (which rehydrates the nonce
|
|
527
|
+
* from the channel store) — no on-chain write, watermark continues.
|
|
528
|
+
*/
|
|
529
|
+
private openOrResumeApexChannel;
|
|
530
|
+
/**
|
|
531
|
+
* Inject the apex settlement negotiation directly into the ToonClient when
|
|
532
|
+
* configured. Required for HS / direct-apex mode where bootstrap discovers 0
|
|
533
|
+
* peers (no relay-based discovery). Mirrors the docker entrypoint's approach.
|
|
534
|
+
*/
|
|
535
|
+
private injectApexNegotiation;
|
|
536
|
+
isReady(): boolean;
|
|
537
|
+
isBootstrapping(): boolean;
|
|
538
|
+
getStatus(): StatusResponse;
|
|
539
|
+
/** Pay-to-write a single event. Throws NOT_READY while bootstrapping. */
|
|
540
|
+
publish(req: PublishRequest): Promise<PublishResponse>;
|
|
541
|
+
/** Register a free-read subscription (does not require the paid path). */
|
|
542
|
+
subscribe(req: SubscribeRequest): SubscribeResponse;
|
|
543
|
+
/** Drain buffered events newer than the cursor (free read). */
|
|
544
|
+
getEvents(query: EventsQuery): EventsResponse;
|
|
545
|
+
/** Open (or return) a payment channel for a destination. */
|
|
546
|
+
openChannel(destination?: string): Promise<{
|
|
547
|
+
channelId: string;
|
|
548
|
+
}>;
|
|
549
|
+
/** List tracked channels with their nonce watermark + cumulative amount. */
|
|
550
|
+
getChannels(): ChannelsResponse;
|
|
551
|
+
/** Send a swap packet to a mill peer. */
|
|
552
|
+
swap(req: SwapRequest): Promise<SwapResponse>;
|
|
553
|
+
/** Graceful teardown of the relay subscription + ToonClient. */
|
|
554
|
+
stop(): Promise<void>;
|
|
555
|
+
private assertReady;
|
|
556
|
+
}
|
|
557
|
+
/** Thrown by paid-write operations while the daemon is not yet ready. */
|
|
558
|
+
declare class NotReadyError extends Error {
|
|
559
|
+
readonly retryable = true;
|
|
560
|
+
constructor(message: string);
|
|
561
|
+
}
|
|
562
|
+
/** Thrown when the relay/connector rejects a paid write. */
|
|
563
|
+
declare class PublishRejectedError extends Error {
|
|
564
|
+
constructor(message: string);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Fastify route registration for the `toon-clientd` control plane. Each route
|
|
569
|
+
* is a thin adapter: parse/validate the request, call the `ClientRunner`, map
|
|
570
|
+
* errors to the uniform `ErrorResponse` envelope.
|
|
571
|
+
*
|
|
572
|
+
* Bound to loopback only by the daemon entry — there is no auth layer because
|
|
573
|
+
* the surface never leaves `127.0.0.1`.
|
|
574
|
+
*/
|
|
575
|
+
|
|
576
|
+
declare function registerRoutes(app: FastifyInstance, runner: ClientRunner): void;
|
|
577
|
+
|
|
578
|
+
/** Whether a process with `pid` is currently alive. */
|
|
579
|
+
declare function isProcessAlive(pid: number): boolean;
|
|
580
|
+
/** Read the recorded daemon PID, or null when absent/invalid. */
|
|
581
|
+
declare function readPid(path?: string): number | null;
|
|
582
|
+
/**
|
|
583
|
+
* Acquire the single-instance lock for the current process. Throws if another
|
|
584
|
+
* live daemon already holds it. Reclaims a stale lock (dead PID).
|
|
585
|
+
*/
|
|
586
|
+
declare function acquireLock(path?: string): void;
|
|
587
|
+
/** Release the lock if it belongs to this process. */
|
|
588
|
+
declare function releaseLock(path?: string): void;
|
|
589
|
+
/** Whether a daemon is currently running per the PID lock. */
|
|
590
|
+
declare function isDaemonRunning(path?: string): boolean;
|
|
591
|
+
interface SpawnDaemonOptions {
|
|
592
|
+
/** Path to the `toon-clientd` bin (defaults to this package's daemon entry). */
|
|
593
|
+
daemonEntry?: string;
|
|
594
|
+
/** Extra env for the spawned process. */
|
|
595
|
+
env?: NodeJS.ProcessEnv;
|
|
596
|
+
/** Directory for the detached stdout/stderr log. */
|
|
597
|
+
logDir?: string;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Spawn the daemon as a detached, fully background process (survives the
|
|
601
|
+
* parent — e.g. the ephemeral MCP/agent session — exiting). Returns the child
|
|
602
|
+
* PID. The caller should poll {@link waitForReady} before issuing requests.
|
|
603
|
+
*/
|
|
604
|
+
declare function spawnDaemonDetached(opts?: SpawnDaemonOptions): number;
|
|
605
|
+
/**
|
|
606
|
+
* Poll the control plane until the daemon answers `GET /status`, up to
|
|
607
|
+
* `timeoutMs`. Resolves true once reachable (NOT necessarily done
|
|
608
|
+
* bootstrapping — anon can take 30–90s; callers surface `bootstrapping`).
|
|
609
|
+
*/
|
|
610
|
+
declare function waitForReady(baseUrl: string, timeoutMs?: number, intervalMs?: number): Promise<boolean>;
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* MCP tool definitions + dispatch. The MCP server is a thin proxy: each tool
|
|
614
|
+
* maps to a `toon-clientd` control-plane call. This module is the testable core
|
|
615
|
+
* (no stdio / SDK transport) so the tool→HTTP mapping and the
|
|
616
|
+
* "bootstrapping — retry" handling can be unit-tested directly.
|
|
617
|
+
*/
|
|
618
|
+
|
|
619
|
+
/** A JSON-Schema-described MCP tool. */
|
|
620
|
+
interface ToolDefinition {
|
|
621
|
+
name: string;
|
|
622
|
+
description: string;
|
|
623
|
+
inputSchema: Record<string, unknown>;
|
|
624
|
+
}
|
|
625
|
+
/** MCP tool-call result shape (subset of the SDK's CallToolResult). */
|
|
626
|
+
interface ToolResult {
|
|
627
|
+
content: {
|
|
628
|
+
type: 'text';
|
|
629
|
+
text: string;
|
|
630
|
+
}[];
|
|
631
|
+
isError?: boolean;
|
|
632
|
+
}
|
|
633
|
+
declare const TOOL_DEFINITIONS: ToolDefinition[];
|
|
634
|
+
/**
|
|
635
|
+
* Dispatch an MCP tool call to the daemon control plane. Always resolves with a
|
|
636
|
+
* `ToolResult` (errors are encoded as `isError: true` text, not thrown, so the
|
|
637
|
+
* agent sees a readable message). A retryable error (daemon still
|
|
638
|
+
* bootstrapping) yields a clear "retry shortly" message.
|
|
639
|
+
*/
|
|
640
|
+
declare function dispatchTool(client: ControlClient, name: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
641
|
+
|
|
642
|
+
export { type ApexNegotiationConfig, type ChainStatus, type ChannelInfo, type ChannelsResponse, ClientRunner, type ClientRunnerDeps, ControlApiError, ControlClient, type ControlClientOptions, 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 ResolvedDaemonConfig, type SettlementChain, type StatusResponse, type SubscribeRequest, type SubscribeResponse, type SwapRequest, type SwapResponse, TOOL_DEFINITIONS, type ToolDefinition, type ToolResult, type ToonClientLike, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, dispatchTool, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, spawnDaemonDetached, waitForReady };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ClientRunner,
|
|
4
|
+
NotReadyError,
|
|
5
|
+
PublishRejectedError,
|
|
6
|
+
RelaySubscription,
|
|
7
|
+
registerRoutes
|
|
8
|
+
} from "./chunk-2SGZPDGE.js";
|
|
9
|
+
import {
|
|
10
|
+
TOOL_DEFINITIONS,
|
|
11
|
+
dispatchTool
|
|
12
|
+
} from "./chunk-5YIZ2JQO.js";
|
|
13
|
+
import {
|
|
14
|
+
ControlApiError,
|
|
15
|
+
ControlClient,
|
|
16
|
+
DaemonUnreachableError,
|
|
17
|
+
acquireLock,
|
|
18
|
+
configDir,
|
|
19
|
+
defaultConfigPath,
|
|
20
|
+
isDaemonRunning,
|
|
21
|
+
isProcessAlive,
|
|
22
|
+
readConfigFile,
|
|
23
|
+
readPid,
|
|
24
|
+
releaseLock,
|
|
25
|
+
resolveConfig,
|
|
26
|
+
resolveMnemonic,
|
|
27
|
+
spawnDaemonDetached,
|
|
28
|
+
waitForReady
|
|
29
|
+
} from "./chunk-WMYY5I3H.js";
|
|
30
|
+
import "./chunk-32QD72IL.js";
|
|
31
|
+
import "./chunk-QTDCFXPF.js";
|
|
32
|
+
import "./chunk-LR7W2ISE.js";
|
|
33
|
+
import "./chunk-VA7XC4FD.js";
|
|
34
|
+
import "./chunk-245J23EB.js";
|
|
35
|
+
export {
|
|
36
|
+
ClientRunner,
|
|
37
|
+
ControlApiError,
|
|
38
|
+
ControlClient,
|
|
39
|
+
DaemonUnreachableError,
|
|
40
|
+
NotReadyError,
|
|
41
|
+
PublishRejectedError,
|
|
42
|
+
RelaySubscription,
|
|
43
|
+
TOOL_DEFINITIONS,
|
|
44
|
+
acquireLock,
|
|
45
|
+
configDir,
|
|
46
|
+
defaultConfigPath,
|
|
47
|
+
dispatchTool,
|
|
48
|
+
isDaemonRunning,
|
|
49
|
+
isProcessAlive,
|
|
50
|
+
readConfigFile,
|
|
51
|
+
readPid,
|
|
52
|
+
registerRoutes,
|
|
53
|
+
releaseLock,
|
|
54
|
+
resolveConfig,
|
|
55
|
+
resolveMnemonic,
|
|
56
|
+
spawnDaemonDetached,
|
|
57
|
+
waitForReady
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=index.js.map
|