@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/README.md +22 -37
- package/dist/app/index.html +33 -31
- package/dist/{chunk-KNQ2DKNN.js → chunk-4GWMHKPP.js} +169 -13
- package/dist/chunk-4GWMHKPP.js.map +1 -0
- package/dist/{chunk-NP35YO5O.js → chunk-CQ2QIZ6Z.js} +162 -211
- package/dist/chunk-CQ2QIZ6Z.js.map +1 -0
- package/dist/{chunk-L6F2B3GX.js → chunk-QFHCXJ2V.js} +96 -99
- package/dist/chunk-QFHCXJ2V.js.map +1 -0
- package/dist/daemon.js +2 -3
- package/dist/daemon.js.map +1 -1
- package/dist/e2e/run-journey.d.ts +1 -0
- package/dist/e2e/run-journey.js +19953 -0
- package/dist/e2e/run-journey.js.map +1 -0
- package/dist/index.d.ts +61 -67
- package/dist/index.js +344 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +2 -3
- package/dist/mcp.js.map +1 -1
- package/package.json +10 -7
- package/dist/anon-proxy-W3KMM7GU-FN7ZJY7P.js +0 -25
- package/dist/anon-proxy-W3KMM7GU-FN7ZJY7P.js.map +0 -1
- package/dist/chunk-KNQ2DKNN.js.map +0 -1
- package/dist/chunk-L6F2B3GX.js.map +0 -1
- package/dist/chunk-NP35YO5O.js.map +0 -1
- package/dist/chunk-SKQTKZIH.js +0 -278
- package/dist/chunk-SKQTKZIH.js.map +0 -1
- package/dist/gateway-QOK47RKS-SEGTXBR3.js +0 -16
- package/dist/gateway-QOK47RKS-SEGTXBR3.js.map +0 -1
- package/dist/socks5-WTJBYGME-6COK4LXW.js +0 -139
- package/dist/socks5-WTJBYGME-6COK4LXW.js.map +0 -1
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
|
|
32
|
-
*
|
|
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'
|
|
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
|
|
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
|
|
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. `
|
|
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
|
-
/**
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
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
|
-
|
|
690
|
-
/**
|
|
691
|
-
|
|
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,
|
|
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
|
|
746
|
-
*
|
|
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
|
|
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.
|
|
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
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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
|
|
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
|
|
1032
|
-
*
|
|
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 —
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|