@toon-protocol/client-mcp 0.2.0 → 0.3.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.
package/dist/index.d.ts CHANGED
@@ -28,8 +28,8 @@ interface StatusResponse {
28
28
  /** Daemon process uptime, ms. */
29
29
  uptimeMs: number;
30
30
  /**
31
- * True while the managed anon proxy / BTP session / channel are still coming
32
- * up. Tools should surface "bootstrapping — retry" rather than blocking.
31
+ * True while the BTP session / channel are still coming up. Tools should
32
+ * surface "bootstrapping — retry" rather than blocking.
33
33
  */
34
34
  bootstrapping: boolean;
35
35
  /** True once the client has started and a channel is open (ready to publish). */
@@ -43,8 +43,7 @@ interface StatusResponse {
43
43
  minaAddress?: string;
44
44
  };
45
45
  transport: {
46
- type: 'direct' | 'socks5' | 'gateway';
47
- socksProxy?: string;
46
+ type: 'direct';
48
47
  btpUrl?: string;
49
48
  };
50
49
  relay: {
@@ -54,13 +53,6 @@ interface StatusResponse {
54
53
  buffered: number;
55
54
  /** Active subscription ids. */
56
55
  subscriptions: string[];
57
- /**
58
- * Error from the daemon-managed read proxy, if any. Set only in the
59
- * btp-direct + relay-`.anyone` case where the daemon starts its own `anon`
60
- * proxy for reads; a failure here means hidden-service reads are down while
61
- * direct paid writes are unaffected.
62
- */
63
- proxyError?: string;
64
56
  };
65
57
  /** Per-chain settlement status when a named `network` tier is configured. */
66
58
  network?: ChainStatus[];
@@ -308,7 +300,7 @@ interface HttpFetchPaidResponse {
308
300
  }
309
301
  /** `POST /relays` — add a relay READ target (fans into all fan-out reads). */
310
302
  interface AddRelayRequest {
311
- /** Relay WS URL, e.g. `ws://host:7100` or a `.anyone` hidden service. */
303
+ /** Relay WS URL, e.g. `ws://host:7100`. */
312
304
  relayUrl: string;
313
305
  }
314
306
  /** `DELETE /relays` — remove a relay read target. */
@@ -462,9 +454,7 @@ declare class ControlClient {
462
454
  * with exponential backoff and re-issues every active REQ on reconnect.
463
455
  *
464
456
  * The WebSocket is injectable (`wsFactory`) so unit tests can drive the wire
465
- * protocol without a real relay; the default factory uses the `ws` package and,
466
- * when a `socks5h://` proxy is configured, routes through it so `.anyone`
467
- * hidden-service relays are reachable.
457
+ * protocol without a real relay; the default factory uses the `ws` package.
468
458
  */
469
459
 
470
460
  /** Minimal WebSocket surface this module depends on (subset of `ws`). */
@@ -477,10 +467,8 @@ interface MinimalWebSocket {
477
467
  }
478
468
  type WebSocketFactory = (url: string) => MinimalWebSocket;
479
469
  interface RelaySubscriptionOptions {
480
- /** Town relay WS URL, e.g. `wss://<host>.anyone/` or `ws://localhost:7100`. */
470
+ /** Town relay WS URL, e.g. `ws://localhost:7100`. */
481
471
  relayUrl: string;
482
- /** Optional `socks5h://host:port` proxy for `.anyone` relays. */
483
- socksProxy?: string;
484
472
  /** Max events retained in the ring buffer (oldest evicted). Default 5000. */
485
473
  bufferSize?: number;
486
474
  /** Base reconnect delay, ms. Default 1000. */
@@ -515,7 +503,6 @@ interface DrainResult {
515
503
  }
516
504
  declare class RelaySubscription {
517
505
  private readonly relayUrl;
518
- private readonly socksProxy?;
519
506
  private readonly bufferSize;
520
507
  private readonly reconnectBaseMs;
521
508
  private readonly reconnectMaxMs;
@@ -626,12 +613,19 @@ interface DaemonConfigFile {
626
613
  _help?: Record<string, string>;
627
614
  /** BTP WebSocket URL of the apex/connector. */
628
615
  btpUrl?: string;
629
- /** Transport: `direct` or a `socks5h://` proxy for `.anyone` hosts. */
630
- socksProxy?: string;
631
- /** Auto-manage the anon proxy for `.anyone` BTP hosts. Default true for HS. */
632
- managedAnonProxy?: boolean;
633
- /** Loopback SOCKS port the managed anon proxy binds (also used for reads). Default 9050. */
634
- managedAnonSocksPort?: number;
616
+ /**
617
+ * Connector-PROXY base URL (devnet payment-proxy, e.g.
618
+ * `https://proxy.devnet.toonprotocol.dev`). When set, the daemon routes paid
619
+ * writes through the proxy's `POST /ilp` (ILP-over-HTTP) WITHOUT a BTP socket;
620
+ * `btpUrl` then becomes optional. Env override: `TOON_CLIENT_PROXY_URL`.
621
+ */
622
+ proxyUrl?: string;
623
+ /**
624
+ * Devnet faucet base URL (e.g. `https://faucet.devnet.toonprotocol.dev`),
625
+ * carried through to the ToonClient config for tooling/e2e funding. Env
626
+ * override: `TOON_CLIENT_FAUCET_URL`.
627
+ */
628
+ faucetUrl?: string;
635
629
  /** Town relay WS URL for FREE reads. */
636
630
  relayUrl?: string;
637
631
  /** Default ILP publish destination. Default `g.townhouse.town`. */
@@ -679,16 +673,17 @@ interface DaemonConfigFile {
679
673
  interface ResolvedDaemonConfig {
680
674
  httpPort: number;
681
675
  relayUrl: string;
682
- socksProxy?: string;
683
676
  /**
684
- * When true the daemon must start its OWN managed `anon` proxy for free reads
685
- * the btp-direct + relay-`.anyone` case the ToonClient does not cover (it
686
- * only auto-starts a proxy for a `.anyone` btpUrl). `readProxySocksPort` is the
687
- * loopback port to bind; `socksProxy` already points the relay at it.
677
+ * Whether a write uplink (proxy or BTP) is configured. FREE reads work
678
+ * without one; a write attempt with `hasUplink === false` is rejected at the
679
+ * control plane with a clear "configure an uplink" error (issue #69). Reads
680
+ * (`subscribe`/`query`/`getEvents`) never consult this.
688
681
  */
689
- manageReadProxy: boolean;
690
- /** Loopback SOCKS port for the daemon-managed read proxy (when `manageReadProxy`). */
691
- readProxySocksPort?: number;
682
+ hasUplink: boolean;
683
+ /** Connector-proxy base URL (devnet payment-proxy), when configured. */
684
+ proxyUrl?: string;
685
+ /** Devnet faucet base URL, when configured. */
686
+ faucetUrl?: string;
692
687
  destination: string;
693
688
  feePerEvent: bigint;
694
689
  apex?: ApexNegotiationConfig;
@@ -721,8 +716,9 @@ declare function resolveMnemonic(file: DaemonConfigFile): string;
721
716
  /**
722
717
  * Build the full resolved daemon config (file overlaid with env, mnemonic
723
718
  * resolved, ToonClientConfig assembled). Env overrides supported:
724
- * TOON_CLIENT_BTP_URL, TOON_CLIENT_RELAY_URL, TOON_CLIENT_SOCKS,
725
- * TOON_CLIENT_HTTP_PORT, TOON_CLIENT_NETWORK.
719
+ * TOON_CLIENT_BTP_URL, TOON_CLIENT_PROXY_URL, TOON_CLIENT_FAUCET_URL,
720
+ * TOON_CLIENT_RELAY_URL, TOON_CLIENT_HTTP_PORT, TOON_CLIENT_NETWORK,
721
+ * TOON_CLIENT_DESTINATION.
726
722
  */
727
723
  declare function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig;
728
724
 
@@ -742,8 +738,8 @@ declare function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig;
742
738
  * relay + apex are the permanent DEFAULT targets and cannot be removed.
743
739
  *
744
740
  * Each apex bootstraps asynchronously and non-blocking: the connection comes up
745
- * in the background (the managed anon proxy alone can take 30–90s). Until ready,
746
- * writes against it report `bootstrapping` so tools surface "retry".
741
+ * in the background. Until ready, writes against it report `bootstrapping` so
742
+ * tools surface "retry".
747
743
  */
748
744
 
749
745
  /** The subset of `ToonClient` the runner depends on. */
@@ -824,20 +820,11 @@ interface ToonClientLike {
824
820
  }): Promise<Response>;
825
821
  }
826
822
  /** A started managed proxy: just the teardown handle the runner needs. */
827
- interface ManagedProxyHandle {
828
- stop(): Promise<void>;
829
- }
830
- /** Starts a managed `anon` read proxy on a loopback SOCKS port. */
831
- type StartReadProxy = (opts: {
832
- socksPort: number;
833
- log?: (msg: string) => void;
834
- }) => Promise<ManagedProxyHandle>;
835
823
  /** Builds a `ToonClient` (or a fake) for a given resolved client config. */
836
824
  type CreateClient = (config: ToonClientConfig) => ToonClientLike;
837
- /** Builds a `RelaySubscription` for a given relay URL + optional read proxy. */
825
+ /** Builds a `RelaySubscription` for a given relay URL. */
838
826
  type CreateRelay = (opts: {
839
827
  relayUrl: string;
840
- socksProxy?: string;
841
828
  onEvent: (subId: string, event: NostrEvent) => void;
842
829
  logger?: (msg: string) => void;
843
830
  }) => RelaySubscription;
@@ -847,11 +834,6 @@ interface ClientRunnerDeps {
847
834
  createClient: CreateClient;
848
835
  /** Factory producing a relay subscription (defaults to the real one). */
849
836
  createRelay?: CreateRelay;
850
- /**
851
- * Starts the daemon-managed read proxy (defaults to the real
852
- * `startManagedAnonProxy`). Injected so tests avoid the anon download/spawn.
853
- */
854
- startReadProxy?: StartReadProxy;
855
837
  logger?: (msg: string) => void;
856
838
  /** Path to the dynamic-targets store (tests override). */
857
839
  targetsPath?: string;
@@ -860,7 +842,6 @@ declare class ClientRunner {
860
842
  private readonly config;
861
843
  private readonly createClient;
862
844
  private readonly createRelay;
863
- private readonly startReadProxy;
864
845
  private readonly log;
865
846
  private readonly targetsPath?;
866
847
  private readonly startedAt;
@@ -880,9 +861,6 @@ declare class ClientRunner {
880
861
  private subIdCounter;
881
862
  private readonly defaultBtpUrl;
882
863
  private readonly defaultRelayUrl;
883
- /** Teardown for the daemon-managed read proxy (btp-direct + relay-`.anyone`). */
884
- private stopReadProxy;
885
- private readProxyError;
886
864
  private stopped;
887
865
  private started;
888
866
  constructor(deps: ClientRunnerDeps);
@@ -901,8 +879,7 @@ declare class ClientRunner {
901
879
  */
902
880
  private registerRelay;
903
881
  /**
904
- * Add a relay read target at runtime. `.anyone` relays reuse the managed read
905
- * proxy (started here if needed). Persisted unless `persist` is false.
882
+ * Add a relay read target at runtime. Persisted unless `persist` is false.
906
883
  */
907
884
  addRelay(relayUrl: string, persist?: boolean): Promise<void>;
908
885
  /** Remove a relay read target. The config-seeded default cannot be removed. */
@@ -947,10 +924,27 @@ declare class ClientRunner {
947
924
  private deriveApexClientConfig;
948
925
  private apexChannelStorePathFor;
949
926
  private replayPersistedTargets;
950
- private ensureReadProxy;
951
- private bringUpReadProxy;
952
- /** Open the apex channel — or, on a restart, RESUME the existing one. */
927
+ /**
928
+ * Open the apex channel — or, on a restart, RESUME the existing one.
929
+ *
930
+ * With `resumeOnly`, only a persisted channel is resumed (no on-chain open);
931
+ * returns undefined when none exists so the caller can defer the open to the
932
+ * first write (funded-after-start demo flow, #69).
933
+ */
953
934
  private openOrResumeApexChannel;
935
+ /**
936
+ * Persist a (lazily- or eagerly-) opened apex channel so a restart RESUMES it
937
+ * (tracked, no re-deposit) rather than opening a second on-chain channel.
938
+ * No-op when the apex carries no negotiation (nothing to key the store on).
939
+ */
940
+ private persistApexChannel;
941
+ /**
942
+ * Discover the apex's settlement negotiation from its kind:10032 on the
943
+ * default relay and attach it to the apex (proxy-mode fallback when no config
944
+ * negotiation was supplied, #69). Throws ApexDiscoveryError on timeout/missing
945
+ * settlement params so the apex's `lastError` reports exactly what is missing.
946
+ */
947
+ private discoverApexNegotiation;
954
948
  /** Inject the apex settlement negotiation directly into its ToonClient. */
955
949
  private injectApexNegotiation;
956
950
  /** Route apex CHILD peers (dvm/mill) through the SAME apex payment channel. */
@@ -998,7 +992,7 @@ declare class ClientRunner {
998
992
  * translate the resulting Web `Response` into the wire envelope.
999
993
  */
1000
994
  httpFetchPaid(req: HttpFetchPaidRequest): Promise<HttpFetchPaidResponse>;
1001
- /** Graceful teardown: close every relay + stop every apex client + read proxy. */
995
+ /** Graceful teardown: close every relay + stop every apex client. */
1002
996
  stop(): Promise<void>;
1003
997
  private selectApex;
1004
998
  private assertApexReady;
@@ -1028,9 +1022,8 @@ declare class PublishRejectedError extends Error {
1028
1022
  * a default password so the identity survives restarts with no env var.
1029
1023
  * The mnemonic + derived addresses are printed ONCE for backup.
1030
1024
  * 2. **Transport scaffolding** — ensure `config.json` carries the transport
1031
- * knobs (`btpUrl`/`relayUrl`/`managedAnonProxy`) plus a `_help` block
1032
- * documenting direct-vs-`.anyone` selection, so the user can see what to
1033
- * point at.
1025
+ * knobs (`btpUrl`/`relayUrl`) plus a `_help` block documenting them, so the
1026
+ * user can see what to point at.
1034
1027
  *
1035
1028
  * This is all idempotent: on later runs an identity already exists, so nothing
1036
1029
  * is regenerated and the config is left untouched.
@@ -1097,7 +1090,8 @@ declare function spawnDaemonDetached(opts?: SpawnDaemonOptions): number;
1097
1090
  /**
1098
1091
  * Poll the control plane until the daemon answers `GET /status`, up to
1099
1092
  * `timeoutMs`. Resolves true once reachable (NOT necessarily done
1100
- * bootstrapping — anon can take 30–90s; callers surface `bootstrapping`).
1093
+ * bootstrapping — the BTP session can take a moment; callers surface
1094
+ * `bootstrapping`).
1101
1095
  */
1102
1096
  declare function waitForReady(baseUrl: string, timeoutMs?: number, intervalMs?: number): Promise<boolean>;
1103
1097
 
@@ -1146,7 +1140,7 @@ interface JourneyStep {
1146
1140
  /** Build the tool input from accumulated prior-step state. */
1147
1141
  buildInput: (state: JourneyState) => Record<string, unknown>;
1148
1142
  /** Render the step's result data as a ViewSpec panel. */
1149
- renderPanel: (data: unknown) => ViewSpec;
1143
+ renderPanel: (data: unknown, state: JourneyState) => ViewSpec;
1150
1144
  }
1151
1145
  /** Ordered sequence of steps with plan metadata. */
1152
1146
  interface JourneyPlan {
package/dist/index.js CHANGED
@@ -8,11 +8,17 @@ import {
8
8
  hasConfiguredIdentity,
9
9
  registerRoutes,
10
10
  scaffoldFirstRun
11
- } from "./chunk-L6F2B3GX.js";
11
+ } from "./chunk-QFHCXJ2V.js";
12
12
  import {
13
+ PUBLISH_TOOL,
13
14
  TOOL_DEFINITIONS,
15
+ UPLOAD_TOOL,
16
+ buildFeedFilter,
17
+ buildFileMetadataFilter,
18
+ buildFollowListFilter,
19
+ buildProfileFilter,
14
20
  dispatchTool
15
- } from "./chunk-KNQ2DKNN.js";
21
+ } from "./chunk-4GWMHKPP.js";
16
22
  import {
17
23
  ControlApiError,
18
24
  ControlClient,
@@ -30,12 +36,11 @@ import {
30
36
  resolveMnemonic,
31
37
  spawnDaemonDetached,
32
38
  waitForReady
33
- } from "./chunk-NP35YO5O.js";
39
+ } from "./chunk-CQ2QIZ6Z.js";
34
40
  import "./chunk-32QD72IL.js";
35
41
  import "./chunk-DLYE6U2Z.js";
36
42
  import "./chunk-LR7W2ISE.js";
37
43
  import "./chunk-VA7XC4FD.js";
38
- import "./chunk-SKQTKZIH.js";
39
44
  import "./chunk-F22GNSF6.js";
40
45
 
41
46
  // src/journey/runner.ts
@@ -53,7 +58,7 @@ async function runJourney(plan, client) {
53
58
  }
54
59
  const data = extractData(toolResult);
55
60
  state[step.id] = data;
56
- const viewSpec = step.renderPanel(data);
61
+ const viewSpec = step.renderPanel(data, state);
57
62
  const panel = {
58
63
  content: [{ type: "text", text: `Journey step: ${step.id}` }],
59
64
  structuredContent: { viewSpec }
@@ -72,6 +77,340 @@ function extractData(result) {
72
77
  return text;
73
78
  }
74
79
  }
80
+
81
+ // src/journey/socialfi.ts
82
+ function pubkeyFromState(state, fallback) {
83
+ const onboard = state["onboard"];
84
+ return onboard?.identity?.nostrPubkey ?? fallback ?? "";
85
+ }
86
+ function socialFiJourney(opts) {
87
+ return {
88
+ id: "socialfi",
89
+ title: "SocialFi Journey",
90
+ steps: [
91
+ // ── Step 1: onboard ─────────────────────────────────────────────────────
92
+ {
93
+ id: "onboard",
94
+ toolName: "toon_status",
95
+ buildInput: () => ({}),
96
+ renderPanel: (data) => {
97
+ const s = data;
98
+ return {
99
+ title: "Onboard",
100
+ root: {
101
+ atom: "section",
102
+ props: { title: s?.ready ? "Ready to publish" : "Connecting\u2026" },
103
+ children: [{ atom: "card", children: [{ atom: "generic-event" }] }]
104
+ }
105
+ };
106
+ }
107
+ },
108
+ // ── Step 2: publish profile (kind:0) ────────────────────────────────────
109
+ {
110
+ id: "publish-profile",
111
+ toolName: "toon_publish_unsigned",
112
+ buildInput: () => ({
113
+ kind: 0,
114
+ content: JSON.stringify({ name: "TOON User", about: "Published via the SocialFi journey." })
115
+ }),
116
+ renderPanel: (_data, state) => ({
117
+ title: "Profile",
118
+ root: {
119
+ atom: "profile-header",
120
+ bind: { query: buildProfileFilter([pubkeyFromState(state, opts?.pubkey)]) }
121
+ }
122
+ })
123
+ },
124
+ // ── Step 3: publish note (kind:1) ───────────────────────────────────────
125
+ {
126
+ id: "publish-note",
127
+ toolName: "toon_publish_unsigned",
128
+ buildInput: () => ({
129
+ kind: 1,
130
+ content: "Hello from TOON Protocol!"
131
+ }),
132
+ renderPanel: (_data, state) => ({
133
+ title: "Note",
134
+ root: {
135
+ atom: "stack",
136
+ children: [
137
+ {
138
+ atom: "composer",
139
+ props: { placeholder: "What's happening?", label: "Post" },
140
+ actions: { post: { tool: PUBLISH_TOOL, args: { kind: 1 } } }
141
+ },
142
+ {
143
+ atom: "note-card",
144
+ bind: { query: buildFeedFilter([pubkeyFromState(state, opts?.pubkey)], 50), kindAuto: true }
145
+ }
146
+ ]
147
+ }
148
+ })
149
+ },
150
+ // ── Step 4: follow (kind:3) ─────────────────────────────────────────────
151
+ {
152
+ id: "follow",
153
+ toolName: "toon_publish_unsigned",
154
+ buildInput: (state) => {
155
+ const pubkey = pubkeyFromState(state, opts?.pubkey);
156
+ return { kind: 3, tags: [["p", pubkey]] };
157
+ },
158
+ renderPanel: (_data, state) => {
159
+ const pubkey = pubkeyFromState(state, opts?.pubkey);
160
+ return {
161
+ title: "Follow",
162
+ root: {
163
+ atom: "stack",
164
+ children: [
165
+ {
166
+ atom: "follow-button",
167
+ props: { label: "Follow" },
168
+ actions: { follow: { tool: PUBLISH_TOOL, args: { kind: 3, tags: [["p", pubkey]] } } }
169
+ },
170
+ {
171
+ atom: "note-card",
172
+ bind: { query: buildFollowListFilter(pubkey), kindAuto: true }
173
+ }
174
+ ]
175
+ }
176
+ };
177
+ }
178
+ },
179
+ // ── Step 5: DVM upload (kind:1063) + media-embed read-back ──────────────
180
+ // Use toon_status (read-only) for the auto-call; the actual upload is
181
+ // user-initiated via the panel's media-uploader action (spendy, confirmed).
182
+ {
183
+ id: "dvm-upload",
184
+ toolName: "toon_status",
185
+ buildInput: () => ({}),
186
+ renderPanel: (_data, state) => ({
187
+ title: "Media Upload",
188
+ root: {
189
+ atom: "stack",
190
+ children: [
191
+ {
192
+ atom: "media-uploader",
193
+ props: { label: "Upload media" },
194
+ actions: {
195
+ upload: {
196
+ tool: UPLOAD_TOOL,
197
+ args: { kind: 1063 },
198
+ spendy: true,
199
+ confirmLabel: "Upload to Arweave (spendy)"
200
+ }
201
+ }
202
+ },
203
+ {
204
+ atom: "media-embed",
205
+ bind: { query: buildFileMetadataFilter([pubkeyFromState(state, opts?.pubkey)], 30), kindAuto: true }
206
+ }
207
+ ]
208
+ }
209
+ })
210
+ }
211
+ ]
212
+ };
213
+ }
214
+
215
+ // src/journey/defi.ts
216
+ function deFiJourney(opts) {
217
+ let capturedSwap;
218
+ return {
219
+ id: "defi",
220
+ title: "DeFi Journey: Open Channel \u2192 Swap \u2192 Settlement Receipt",
221
+ steps: [
222
+ {
223
+ id: "open-channel",
224
+ toolName: "toon_open_channel",
225
+ buildInput: (_state) => ({ destination: opts.destination }),
226
+ renderPanel: (data) => {
227
+ const { channelId } = data;
228
+ return {
229
+ title: "Payment Channel",
230
+ root: {
231
+ atom: "stack",
232
+ children: [{ atom: "channel-card", props: { channelId } }]
233
+ }
234
+ };
235
+ }
236
+ },
237
+ {
238
+ id: "swap",
239
+ toolName: "toon_swap",
240
+ buildInput: (_state) => {
241
+ const args = {
242
+ destination: opts.destination,
243
+ amount: opts.amount,
244
+ millPubkey: opts.millPubkey,
245
+ pair: opts.pair,
246
+ chainRecipient: opts.chainRecipient
247
+ };
248
+ if (opts.packetCount !== void 0) {
249
+ args["packetCount"] = opts.packetCount;
250
+ }
251
+ return args;
252
+ },
253
+ renderPanel: (data) => {
254
+ capturedSwap = data;
255
+ return {
256
+ title: "Swap",
257
+ root: {
258
+ atom: "stack",
259
+ children: [
260
+ {
261
+ atom: "swap-form",
262
+ props: {
263
+ accepted: capturedSwap.accepted,
264
+ packetsAccepted: capturedSwap.packetsAccepted,
265
+ cumulativeSource: capturedSwap.cumulativeSource,
266
+ cumulativeTarget: capturedSwap.cumulativeTarget,
267
+ state: capturedSwap.state,
268
+ claims: capturedSwap.claims
269
+ }
270
+ }
271
+ ]
272
+ }
273
+ };
274
+ }
275
+ },
276
+ {
277
+ id: "settlement-receipt",
278
+ toolName: "toon_channels",
279
+ buildInput: (_state) => ({}),
280
+ renderPanel: (data) => {
281
+ const { channels } = data;
282
+ const swap = capturedSwap;
283
+ return {
284
+ title: "Settlement Receipt",
285
+ root: {
286
+ atom: "stack",
287
+ children: [
288
+ {
289
+ atom: "settlement-receipt",
290
+ props: {
291
+ accepted: swap?.accepted,
292
+ cumulativeSource: swap?.cumulativeSource,
293
+ cumulativeTarget: swap?.cumulativeTarget,
294
+ claims: swap?.claims ?? [],
295
+ channels
296
+ }
297
+ }
298
+ ]
299
+ }
300
+ };
301
+ }
302
+ }
303
+ ]
304
+ };
305
+ }
306
+
307
+ // src/journey/demo.ts
308
+ function chainJourneys(id, title, ...plans) {
309
+ const steps = plans.flatMap((p) => p.steps);
310
+ const seen = /* @__PURE__ */ new Set();
311
+ for (const step of steps) {
312
+ if (seen.has(step.id)) {
313
+ throw new Error(
314
+ `chainJourneys: duplicate step id "${step.id}" across chained plans \u2014 state threading requires unique step ids`
315
+ );
316
+ }
317
+ seen.add(step.id);
318
+ }
319
+ return { id, title, steps };
320
+ }
321
+ function capstoneJourney(opts) {
322
+ return chainJourneys(
323
+ "capstone",
324
+ "Capstone Journey: SocialFi \u2192 DeFi",
325
+ socialFiJourney(opts.socialFi),
326
+ deFiJourney(opts.deFi)
327
+ );
328
+ }
329
+ function extractReceipt(result) {
330
+ const swapStep = result.steps.find((s) => s.stepId === "swap");
331
+ const channelsStep = result.steps.find((s) => s.stepId === "settlement-receipt");
332
+ if (!swapStep || !channelsStep) return void 0;
333
+ const swap = parseToolText(swapStep.toolResult.content[0]?.text);
334
+ const channelsRes = parseToolText(
335
+ channelsStep.toolResult.content[0]?.text
336
+ );
337
+ if (!swap || !channelsRes) return void 0;
338
+ return {
339
+ accepted: swap.accepted,
340
+ state: swap.state,
341
+ cumulativeSource: swap.cumulativeSource,
342
+ cumulativeTarget: swap.cumulativeTarget,
343
+ claims: swap.claims ?? [],
344
+ channels: channelsRes.channels
345
+ };
346
+ }
347
+ function parseToolText(text) {
348
+ if (!text) return void 0;
349
+ try {
350
+ return JSON.parse(text);
351
+ } catch {
352
+ return void 0;
353
+ }
354
+ }
355
+ async function runCapstoneDemo(client, opts, logger = console) {
356
+ const plan = capstoneJourney(opts);
357
+ logger.log(`
358
+ === ${plan.title} (${plan.steps.length} steps) ===
359
+ `);
360
+ const result = await runJourney(plan, client);
361
+ for (const step of result.steps) {
362
+ const viewSpec = step.panel.structuredContent?.["viewSpec"];
363
+ logger.log(`--- panel: ${step.stepId} ---`);
364
+ logger.log(JSON.stringify(viewSpec, null, 2));
365
+ }
366
+ if (!result.completed) {
367
+ logger.error(
368
+ `
369
+ [capstone] FAILED at step "${result.error?.stepId}": ${result.error?.message}`
370
+ );
371
+ return 1;
372
+ }
373
+ const receipt = extractReceipt(result);
374
+ logger.log("\n=== Settlement Receipt ===");
375
+ logger.log(JSON.stringify(receipt, null, 2));
376
+ logger.log(`
377
+ [capstone] completed all ${result.steps.length} steps.`);
378
+ return 0;
379
+ }
380
+ async function main(env = process.env) {
381
+ const baseUrl = env["TOON_DAEMON_URL"] ?? "http://127.0.0.1:8787";
382
+ const destination = env["TOON_SWAP_DEST"];
383
+ const amount = env["TOON_SWAP_AMOUNT"];
384
+ const millPubkey = env["TOON_MILL_PUBKEY"];
385
+ const chainRecipient = env["TOON_CHAIN_RECIPIENT"];
386
+ const pairRaw = env["TOON_SWAP_PAIR"];
387
+ if (!destination || !amount || !millPubkey || !chainRecipient || !pairRaw) {
388
+ console.error(
389
+ "[capstone] missing required env: TOON_SWAP_DEST, TOON_SWAP_AMOUNT, TOON_MILL_PUBKEY, TOON_CHAIN_RECIPIENT, TOON_SWAP_PAIR are all required for the live DeFi leg. See the module header for the full env contract."
390
+ );
391
+ return 2;
392
+ }
393
+ let pair;
394
+ try {
395
+ pair = JSON.parse(pairRaw);
396
+ } catch (e) {
397
+ console.error(`[capstone] TOON_SWAP_PAIR is not valid JSON: ${String(e)}`);
398
+ return 2;
399
+ }
400
+ const client = new ControlClient({ baseUrl });
401
+ const socialFiPubkey = env["TOON_SOCIALFI_PUBKEY"];
402
+ return runCapstoneDemo(client, {
403
+ ...socialFiPubkey ? { socialFi: { pubkey: socialFiPubkey } } : {},
404
+ deFi: { destination, amount, millPubkey, chainRecipient, pair }
405
+ });
406
+ }
407
+ var invokedDirectly = typeof process !== "undefined" && Array.isArray(process.argv) && /[/\\]journey[/\\]demo\.(ts|js|mjs)$/.test(process.argv[1] ?? "");
408
+ if (invokedDirectly) {
409
+ main().then((code) => process.exit(code)).catch((err) => {
410
+ console.error("[capstone] fatal:", err instanceof Error ? err.message : err);
411
+ process.exit(1);
412
+ });
413
+ }
75
414
  export {
76
415
  ClientRunner,
77
416
  ControlApiError,