@toon-protocol/client-mcp 0.18.0 → 0.19.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.
package/README.md CHANGED
@@ -288,11 +288,11 @@ The integration suite lives in `src/__integration__/`.
288
288
 
289
289
  ## Publishing
290
290
 
291
- This package is **published to npm automatically by CI/CD**, in lockstep with the
292
- repo's release tag (the same `vX.Y.Z` semantic-release cuts for
293
- `@toon-protocol/relay`). On a release, `publish-relay-images.yml` builds
294
- this package, sets its version to the tag, and runs
295
- `pnpm --filter @toon-protocol/client-mcp publish --access public`.
291
+ This package is **published to npm automatically by CI/CD** via
292
+ [changesets](https://github.com/changesets/changesets): `.github/workflows/release.yml`
293
+ runs `pnpm changeset publish` on every push to `main`, using the org `NPM_TOKEN`
294
+ secret. It ships whenever a changeset targeting it lands — independently of any
295
+ other repo's release cadence. Never run `npm publish` manually.
296
296
 
297
297
  It is **self-contained**: its `@toon-protocol/*` workspace deps (`client`, `core`)
298
298
  are **bundled into `dist`** at build time (tsup `noExternal`), so the published
@@ -302,5 +302,3 @@ packages (`fastify`, `@modelcontextprotocol/sdk`, `nostr-tools`, `viem`, `ws`,
302
302
  `@solana/web3.js`) installed only when you use those chains.
303
303
  A guard test (`src/package-structure.test.ts`) fails the build if a
304
304
  `@toon-protocol/*` runtime dep ever leaks in.
305
-
306
- To publish manually: `pnpm --filter @toon-protocol/client-mcp build && pnpm --filter @toon-protocol/client-mcp publish --access public`.
@@ -2,7 +2,7 @@ import { createRequire as __cr } from 'module'; const require = __cr(import.meta
2
2
  import {
3
3
  ControlApiError,
4
4
  DaemonUnreachableError
5
- } from "./chunk-LSPAGZXL.js";
5
+ } from "./chunk-OGMD7Z2F.js";
6
6
 
7
7
  // ../views/dist/tool-names.js
8
8
  var PUBLISH_TOOL = "toon_publish_unsigned";
@@ -1870,4 +1870,4 @@ export {
1870
1870
  TOOL_DEFINITIONS,
1871
1871
  dispatchTool
1872
1872
  };
1873
- //# sourceMappingURL=chunk-56TTQYS7.js.map
1873
+ //# sourceMappingURL=chunk-3PGURFAM.js.map
@@ -3,6 +3,7 @@ import {
3
3
  AdaptiveDeltaController,
4
4
  DEFAULT_KEYSTORE_PASSWORD,
5
5
  ILP_PEER_INFO_KIND,
6
+ InMemoryPreimageRetentionStore,
6
7
  InMemoryReceivedClaimStore,
7
8
  JsonFileReceivedClaimStore,
8
9
  JsonFileSwapControllerStateStore,
@@ -15,14 +16,14 @@ import {
15
16
  extractArweaveTxId,
16
17
  fundWallet,
17
18
  generateKeystore,
18
- ingestReceivedClaims,
19
+ ingestAndReveal,
19
20
  isEventExpired,
20
21
  loadMinaSignerClient,
21
22
  mintExecutionCondition,
22
23
  parseIlpPeerInfo,
23
24
  readConfigFile,
24
25
  streamSwap
25
- } from "./chunk-LSPAGZXL.js";
26
+ } from "./chunk-OGMD7Z2F.js";
26
27
 
27
28
  // src/daemon/first-run.ts
28
29
  import { existsSync, mkdirSync, writeFileSync } from "fs";
@@ -835,7 +836,7 @@ async function fetchRemoteState(options) {
835
836
  };
836
837
  }
837
838
 
838
- // ../rig/dist/chunk-NIRC5LFS.js
839
+ // ../rig/dist/chunk-PG5PUKDR.js
839
840
  import { execFile, spawn } from "child_process";
840
841
  import { promisify } from "util";
841
842
  import { execFile as execFile2, spawn as spawn2 } from "child_process";
@@ -1033,6 +1034,42 @@ var GitRepoReader = class {
1033
1034
  }
1034
1035
  return objects;
1035
1036
  }
1037
+ /**
1038
+ * List every blob (file) reachable from a ref's root tree, recursively,
1039
+ * with the path it is served at (#368: the ar.io site manifest join key).
1040
+ * Uses `git ls-tree -r -z` — NUL-terminated records so binary/spaced paths
1041
+ * survive verbatim, and no path quoting to undo. Submodule (`commit`)
1042
+ * gitlink entries and directories are excluded; only real file blobs remain.
1043
+ */
1044
+ async listBlobs(rev) {
1045
+ assertRevision(rev, "ref");
1046
+ const { stdout } = await this.git(["ls-tree", "-r", "-z", rev, "--"]);
1047
+ const blobs = [];
1048
+ for (const record of stdout.split("\0")) {
1049
+ if (!record) continue;
1050
+ const tab = record.indexOf(" ");
1051
+ if (tab === -1) {
1052
+ throw new GitError(
1053
+ `unexpected ls-tree record: ${JSON.stringify(record)}`,
1054
+ void 0,
1055
+ ""
1056
+ );
1057
+ }
1058
+ const meta = record.slice(0, tab);
1059
+ const path = record.slice(tab + 1);
1060
+ const [, type, sha] = meta.split(" ");
1061
+ if (type !== "blob") continue;
1062
+ if (!sha || !FULL_SHA_RE.test(sha)) {
1063
+ throw new GitError(
1064
+ `unexpected ls-tree object id: ${JSON.stringify(record)}`,
1065
+ void 0,
1066
+ ""
1067
+ );
1068
+ }
1069
+ blobs.push({ path, sha });
1070
+ }
1071
+ return blobs;
1072
+ }
1036
1073
  /** Run git feeding `input` on stdin; resolves collected stdout bytes. */
1037
1074
  runWithStdin(args, input) {
1038
1075
  return new Promise((resolve2, reject) => {
@@ -1432,7 +1469,10 @@ async function executePush(options) {
1432
1469
  sha: object.sha,
1433
1470
  type: object.type,
1434
1471
  body,
1435
- repoId: plan.repoId
1472
+ repoId: plan.repoId,
1473
+ // #368: the path the blob was reached by drives its Content-Type; a
1474
+ // non-blob object (no path) uploads as octet-stream.
1475
+ ...object.path ? { path: object.path } : {}
1436
1476
  });
1437
1477
  merged.set(object.sha, receipt.txId);
1438
1478
  totalFeePaid += receipt.feePaid;
@@ -2695,7 +2735,8 @@ var ClientRunner = class {
2695
2735
  }
2696
2736
  };
2697
2737
  const senderSecretKey = generateSecretKey();
2698
- const swapClient = req.senderConditions ? this.withSenderConditions(apex.client) : apex.client;
2738
+ const preimages = new InMemoryPreimageRetentionStore();
2739
+ const swapClient = req.senderConditions ? this.withSenderConditions(apex.client, preimages) : apex.client;
2699
2740
  const result = await streamSwap({
2700
2741
  client: swapClient,
2701
2742
  swapPubkey: req.swapPubkey,
@@ -2714,15 +2755,19 @@ var ClientRunner = class {
2714
2755
  const firstReject = result.rejections[0];
2715
2756
  const expectedChain = req.pair.to.chain;
2716
2757
  const minaSignerClient = expectedChain.startsWith("mina") ? await loadMinaSignerClient() : void 0;
2717
- const ingest = ingestReceivedClaims({
2758
+ const reveal = () => ({ decision: "revealed" });
2759
+ const ingest = await ingestAndReveal({
2718
2760
  claims: result.claims,
2719
2761
  expectedChain,
2720
2762
  chainRecipient: req.chainRecipient,
2721
2763
  ...req.swapSignerAddress ? { expectedSignerAddress: req.swapSignerAddress } : {},
2722
2764
  store: this.receivedClaimStore,
2723
- ...minaSignerClient ? { minaSignerClient } : {}
2765
+ ...minaSignerClient ? { minaSignerClient } : {},
2766
+ preimages,
2767
+ reveal
2724
2768
  });
2725
- const verifiedSet = new Set(ingest.verified.map((v) => v.claim));
2769
+ preimages.clear();
2770
+ const verifiedSet = new Set(ingest.revealed.map((v) => v.claim));
2726
2771
  const rejectionByClaim = new Map(
2727
2772
  ingest.rejected.map((r) => [r.claim, r])
2728
2773
  );
@@ -2776,12 +2821,13 @@ var ClientRunner = class {
2776
2821
  `${ingest.rejected.length} received claim(s) FAILED verification and were NOT counted as value received (first: ${firstRejected.code} \u2014 ${firstRejected.message}). See per-claim verificationError.`
2777
2822
  );
2778
2823
  }
2779
- const hadIngestibleClaims = ingest.verified.length + ingest.rejected.length > 0;
2824
+ const hadIngestibleClaims = ingest.revealed.length + ingest.rejected.length > 0;
2780
2825
  return {
2781
- // A swap only counts as accepted when it yielded a VERIFIED claim (or a
2782
- // legacy no-metadata claim, whose path is unchanged). FULFILLed packets
2783
- // whose claims all failed verification are a failed swap, loudly.
2784
- accepted: ingest.verified.length + ingest.legacy.length > 0,
2826
+ // A swap only counts as accepted when it yielded a VERIFIED+REVEALED
2827
+ // claim (or a legacy no-metadata claim, whose path is unchanged).
2828
+ // FULFILLed packets whose claims all failed verification are a failed
2829
+ // swap, loudly.
2830
+ accepted: ingest.revealed.length + ingest.legacy.length > 0,
2785
2831
  packetsAccepted: result.claims.length,
2786
2832
  claims,
2787
2833
  cumulativeSource: result.cumulativeSource.toString(),
@@ -2803,9 +2849,9 @@ var ClientRunner = class {
2803
2849
  ...firstReject ? { code: firstReject.code, message: firstReject.message } : {},
2804
2850
  ...warnings.length > 0 ? { warning: warnings.join("\n") } : {},
2805
2851
  ...hadIngestibleClaims ? {
2806
- claimsVerified: ingest.verified.length,
2852
+ claimsVerified: ingest.revealed.length,
2807
2853
  claimsRejected: ingest.rejected.length,
2808
- valueReceived: ingest.valueReceived.toString()
2854
+ valueReceived: ingest.valueRevealed.toString()
2809
2855
  } : {}
2810
2856
  };
2811
2857
  }
@@ -2883,16 +2929,56 @@ var ClientRunner = class {
2883
2929
  results.push(base);
2884
2930
  continue;
2885
2931
  }
2886
- if (bundle.chainKind !== "evm") {
2932
+ if (bundle.chainKind === "solana") {
2887
2933
  results.push({
2888
2934
  ...base,
2889
2935
  error: {
2890
2936
  code: "SUBMISSION_UNSUPPORTED",
2891
- message: bundle.chainKind === "mina" ? "Mina receive-side redemption needs a co-signed claimFromChannel (signatureA AND signatureB) plus o1js proof generation \u2014 no client co-sign path exists yet. Out of scope for #352; follow-up: toon-client#357." : "Solana settlement submission is not wired yet (bundle carries a serialized Message; follow-up under toon-meta#145)."
2937
+ message: "Solana settlement submission is not wired yet (bundle carries a serialized Message; follow-up under toon-meta#145)."
2892
2938
  }
2893
2939
  });
2894
2940
  continue;
2895
2941
  }
2942
+ if (bundle.chainKind === "mina") {
2943
+ if (!this.identityClient.settleSwapBundle) {
2944
+ results.push({
2945
+ ...base,
2946
+ error: {
2947
+ code: "SUBMISSION_UNAVAILABLE",
2948
+ message: "The active client does not implement settleSwapBundle."
2949
+ }
2950
+ });
2951
+ continue;
2952
+ }
2953
+ try {
2954
+ const submitted = await this.identityClient.settleSwapBundle(bundle);
2955
+ const latest = this.receivedClaimStore.load(entry.chain, entry.channelId) ?? entry;
2956
+ this.receivedClaimStore.save({
2957
+ ...latest,
2958
+ settledAt: Date.now(),
2959
+ settledNonce: BigInt(bundle.nonce),
2960
+ settleTxHash: submitted.txHash
2961
+ });
2962
+ this.log(
2963
+ `[runner] swap settle: submitted ${bundle.chain}/${bundle.channelId} nonce ${bundle.nonce} cumulative ${bundle.cumulativeAmount} \u2192 ${submitted.txHash}`
2964
+ );
2965
+ results.push({
2966
+ ...base,
2967
+ submitted: true,
2968
+ txHash: submitted.txHash,
2969
+ ...submitted.status ? { txStatus: submitted.status } : {}
2970
+ });
2971
+ } catch (err) {
2972
+ results.push({
2973
+ ...base,
2974
+ error: {
2975
+ code: "SUBMISSION_FAILED",
2976
+ message: err instanceof Error ? err.message : String(err)
2977
+ }
2978
+ });
2979
+ }
2980
+ continue;
2981
+ }
2896
2982
  const rpcUrl = this.config.toonClientConfig.chainRpcUrls?.[bundle.chain];
2897
2983
  if (!rpcUrl) {
2898
2984
  results.push({
@@ -2948,20 +3034,30 @@ var ClientRunner = class {
2948
3034
  * Wrap an apex client so each `sendSwapPacket` call carries a freshly
2949
3035
  * minted sender-chosen execution condition (one preimage per packet, spec
2950
3036
  * R1 — reuse would let an observer of packet *i* fulfill packet *i+1*).
2951
- * `streamSwap` calls `sendSwapPacket` once per packet, so minting here
2952
- * yields exactly one condition per packet. The transports verify each
2953
- * FULFILL's preimage against the condition and fail the packet on mismatch.
3037
+ * `streamSwap` calls `sendSwapPacket` once per packet, in strictly
3038
+ * increasing `packetIndex` order, so minting here yields exactly one
3039
+ * condition per packet and the local counter tracks `packetIndex`. The
3040
+ * transports verify each FULFILL's preimage against the condition and fail
3041
+ * the packet on mismatch.
2954
3042
  *
2955
- * NOTE: the minted preimage is intentionally NOT retained yet — leg-B
2956
- * receive-side ingestion (where the sender reveals `P_i`) is a separate
2957
- * rolling-swap workstream (toon-meta docs/rolling-swap.md §3.2). Until a
2958
- * maker implements the connector#309 contract end-to-end, packets sent with
2959
- * conditions will fail closed rather than settle unverified.
3043
+ * Preimage retention (toon-client#360, spec §3.2 leg-B reveal): every minted
3044
+ * `P_i` is retained in `preimages`, keyed by `packetIndex` the identifier
3045
+ * shared with `AccumulatedClaim.packetIndex` so the receive-side reveal
3046
+ * step (`ingestAndReveal`) can correlate and consume the preimage for the
3047
+ * claim it commits. Retention is session-scoped (one store per `swap()`
3048
+ * call); a fresh stream mints fresh preimages.
2960
3049
  */
2961
- withSenderConditions(client) {
3050
+ withSenderConditions(client, preimages) {
2962
3051
  const wrapped = Object.create(client);
3052
+ let packetIndex = 0;
2963
3053
  wrapped.sendSwapPacket = (params) => {
2964
- const { condition } = mintExecutionCondition();
3054
+ const { preimage, condition } = mintExecutionCondition();
3055
+ preimages.retain({
3056
+ packetIndex: packetIndex++,
3057
+ preimage,
3058
+ condition,
3059
+ retainedAt: Date.now()
3060
+ });
2965
3061
  return client.sendSwapPacket({
2966
3062
  ...params,
2967
3063
  executionCondition: condition
@@ -3932,4 +4028,4 @@ export {
3932
4028
  PublishRejectedError,
3933
4029
  registerRoutes
3934
4030
  };
3935
- //# sourceMappingURL=chunk-FXPDLGDN.js.map
4031
+ //# sourceMappingURL=chunk-ITONR5ML.js.map