@sherwoodagent/cli 0.72.3 → 0.73.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.
@@ -24,7 +24,7 @@ import "./chunk-BQMOE4FJ.js";
24
24
  import {
25
25
  fetchMetadata,
26
26
  uploadMetadata
27
- } from "./chunk-ZYXFKVYF.js";
27
+ } from "./chunk-XDGCHURW.js";
28
28
 
29
29
  // src/commands/chat.ts
30
30
  import chalk from "chalk";
@@ -564,4 +564,4 @@ function registerChatCommands(program) {
564
564
  export {
565
565
  registerChatCommands
566
566
  };
567
- //# sourceMappingURL=chat-G5M3OK6M.js.map
567
+ //# sourceMappingURL=chat-ONGVCWAP.js.map
@@ -58,6 +58,21 @@ async function fetchMetadata(ipfsURI) {
58
58
  }
59
59
  return await response.json();
60
60
  }
61
+ function mergeFundMetadata(current, overrides) {
62
+ const base = { ...current ?? {} };
63
+ const name = overrides.name || base.name || overrides.subdomain;
64
+ const description = overrides.description || base.description || `${name} \u2014 a Sherwood fund on ${overrides.subdomain}.sherwoodagent.eth`;
65
+ return {
66
+ ...base,
67
+ schema: base.schema || "sherwood/fund/v1",
68
+ name,
69
+ description,
70
+ chain: base.chain || overrides.defaultChain,
71
+ strategies: base.strategies ?? [],
72
+ terms: base.terms ?? {},
73
+ links: base.links ?? {}
74
+ };
75
+ }
61
76
  async function resolveMetadataSummary(uri) {
62
77
  try {
63
78
  if (uri.startsWith("data:application/json;base64,")) {
@@ -76,6 +91,7 @@ export {
76
91
  pinJSON,
77
92
  uploadMetadata,
78
93
  fetchMetadata,
94
+ mergeFundMetadata,
79
95
  resolveMetadataSummary
80
96
  };
81
- //# sourceMappingURL=chunk-ZYXFKVYF.js.map
97
+ //# sourceMappingURL=chunk-XDGCHURW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/ipfs.ts"],"sourcesContent":["/**\n * IPFS metadata upload/fetch via Sherwood API.\n *\n * Uploads go through the server-side API at sherwood.sh/api/ipfs/upload\n * which holds the Pinata JWT. CLI and app only need the gateway URL for reads.\n */\n\nexport interface FundMetadata {\n schema: string;\n name: string;\n description: string;\n logo?: string;\n chain: string;\n xmtpGroupId?: string;\n strategies: {\n id: string;\n name: string;\n description: string;\n protocols: string[];\n riskLevel: string;\n }[];\n terms: {\n minDeposit?: string;\n minDepositFormatted?: string;\n feeModel?: string;\n lockPeriod?: number;\n };\n links: {\n moltbook?: string;\n dashboard?: string;\n github?: string;\n };\n}\n\nconst DEFAULT_PINATA_GATEWAY = \"https://sherwood.mypinata.cloud\";\n// www is canonical — the apex 307-redirects to it (an extra hop that has been\n// observed slow/flaky on cold starts; vnet e2e finding).\nconst DEFAULT_UPLOAD_API = \"https://www.sherwood.sh/api/ipfs/upload\";\n\nfunction getUploadApiUrl(): string {\n return process.env.SHERWOOD_API_URL\n ? `${process.env.SHERWOOD_API_URL}/api/ipfs/upload`\n : DEFAULT_UPLOAD_API;\n}\n\nfunction getPinataGateway(): string {\n return process.env.PINATA_GATEWAY || DEFAULT_PINATA_GATEWAY;\n}\n\n/**\n * Upload JSON to IPFS via the Sherwood API (server-side Pinata).\n * Returns the IPFS URI (ipfs://Qm...).\n */\nasync function uploadToIPFS(content: Record<string, unknown>, name: string): Promise<string> {\n const url = getUploadApiUrl();\n\n // Transient \"fetch failed\" / cold-start 5xx from the pin API shouldn't kill\n // a propose flow — retry briefly before surfacing (vnet e2e finding).\n const attempts = 3;\n let lastErr: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ content, name }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n // 4xx won't improve on retry; 5xx might.\n if (response.status < 500) throw new Error(`IPFS upload failed (${response.status}): ${text}`);\n lastErr = new Error(`IPFS upload failed (${response.status}): ${text}`);\n } else {\n const result = (await response.json()) as { ipfsHash: string };\n return `ipfs://${result.ipfsHash}`;\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"IPFS upload failed (4\")) throw err;\n lastErr = err;\n }\n if (i < attempts - 1) await new Promise((r) => setTimeout(r, 2000 * (i + 1)));\n }\n throw lastErr instanceof Error ? lastErr : new Error(`IPFS upload failed: ${String(lastErr)}`);\n}\n\n/**\n * Pin arbitrary JSON to IPFS.\n * Used for research results and other generic JSON payloads.\n * Returns the IPFS URI (ipfs://Qm...).\n */\nexport async function pinJSON(content: Record<string, unknown>, name: string): Promise<string> {\n return uploadToIPFS(content, name);\n}\n\n/**\n * Upload fund metadata to IPFS.\n * Returns the IPFS URI (ipfs://Qm...).\n */\nexport async function uploadMetadata(metadata: FundMetadata): Promise<string> {\n const name = `sherwood-fund-${metadata.name.toLowerCase().replace(/\\s+/g, \"-\")}`;\n return uploadToIPFS(metadata as unknown as Record<string, unknown>, name);\n}\n\n/**\n * Fetch and parse metadata from an IPFS URI.\n * Supports ipfs:// protocol URIs and raw CIDs.\n */\nexport async function fetchMetadata(ipfsURI: string): Promise<FundMetadata> {\n const gateway = getPinataGateway();\n let cid: string;\n\n if (ipfsURI.startsWith(\"ipfs://\")) {\n cid = ipfsURI.slice(7);\n } else if (ipfsURI.startsWith(\"Qm\") || ipfsURI.startsWith(\"bafy\")) {\n cid = ipfsURI;\n } else {\n throw new Error(`Invalid IPFS URI: ${ipfsURI}`);\n }\n\n const url = `${gateway}/ipfs/${cid}`;\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch metadata from ${url} (${response.status})`);\n }\n\n return (await response.json()) as FundMetadata;\n}\n\n/**\n * Merge flag-driven overrides onto the fund's CURRENT metadata.\n *\n * `update-metadata` used to rebuild the JSON from scratch, silently dropping\n * any field not re-supplied by a flag — notably `xmtpGroupId`, which is the\n * ONLY group-discovery path for the dashboard on ENS-less chains (issue #458).\n *\n * Semantics: flags (`name`/`description`) win; everything else on `current`\n * (`xmtpGroupId`, `terms`, `links`, `strategies`, and any unknown keys) is\n * preserved. Unknown keys survive because the spread of `current` is a runtime\n * copy of every own-enumerable property, not just the ones in `FundMetadata`.\n * Pass `current = null` (e.g. when the fetch failed) to fall back to the old\n * build-from-scratch behavior.\n */\nexport function mergeFundMetadata(\n current: Partial<FundMetadata> | null | undefined,\n overrides: {\n name?: string;\n description?: string;\n subdomain: string;\n defaultChain: string;\n },\n): FundMetadata {\n const base = { ...(current ?? {}) } as Partial<FundMetadata> & Record<string, unknown>;\n const name = overrides.name || base.name || overrides.subdomain;\n const description =\n overrides.description ||\n base.description ||\n `${name} — a Sherwood fund on ${overrides.subdomain}.sherwoodagent.eth`;\n return {\n ...base,\n schema: base.schema || \"sherwood/fund/v1\",\n name,\n description,\n chain: base.chain || overrides.defaultChain,\n strategies: base.strategies ?? [],\n terms: base.terms ?? {},\n links: base.links ?? {},\n };\n}\n\n/**\n * Resolve a metadata URI (ipfs:// or data: base64) to name + description.\n * Returns null on any error — callers should treat enrichment as best-effort.\n */\nexport async function resolveMetadataSummary(\n uri: string,\n): Promise<{ name: string; description: string } | null> {\n try {\n if (uri.startsWith(\"data:application/json;base64,\")) {\n const b64 = uri.slice(\"data:application/json;base64,\".length);\n const json = JSON.parse(Buffer.from(b64, \"base64\").toString(\"utf-8\"));\n return { name: json.name || \"\", description: json.description || \"\" };\n }\n const meta = await fetchMetadata(uri);\n return { name: meta.name, description: meta.description };\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -171,8 +171,9 @@ import {
171
171
  } from "./chunk-BQMOE4FJ.js";
172
172
  import {
173
173
  fetchMetadata,
174
+ mergeFundMetadata,
174
175
  uploadMetadata
175
- } from "./chunk-ZYXFKVYF.js";
176
+ } from "./chunk-XDGCHURW.js";
176
177
 
177
178
  // src/index.ts
178
179
  import { config as loadDotenv } from "dotenv";
@@ -1180,13 +1181,17 @@ var ROBINHOOD_L2 = {
1180
1181
  slug: "robinhood-l2",
1181
1182
  // V2 redeploy (chain 46630): fresh core contracts + a full guardian layer
1182
1183
  // (registry + sWOOD) and a deployed StrategyFactory for the keyless
1183
- // clone+init path (#387).
1184
+ // clone+init path (#387). Post-#421 per-vault-governor redeploy — kept in
1185
+ // sync with the canonical source of truth contracts/chains/46630.json (and
1186
+ // the app's app/src/lib/contracts.ts ROBINHOOD_TESTNET_ADDRESSES). A stale
1187
+ // factory here made /api/v1/funds?chain=46630 read the old deployment and
1188
+ // return {count: 0} while the UI listed funds (issue #459).
1184
1189
  sherwood: {
1185
- factory: "0xa5351b91E0A6348a78dA4E47bcd7DFC49fa353F3",
1186
- guardianRegistry: "0xA1b4C376c9C9e7257F654Fe1C4c10f51760407D4",
1187
- vaultImpl: "0x382cCfe80c430De671b15A45A7EDED96b5bFB383",
1188
- batchExecutorLib: "0x039825a3750c27C5f85Bb8486860B867A8D201E5",
1189
- strategyFactory: "0x85a598ADA4fb9654BaF77234864fa100C4B5a204"
1190
+ factory: "0xB9E71Fb33075328d6e94eCFFf8a8629D6d057cce",
1191
+ guardianRegistry: "0x57f0fa384d0d7e2F234535d1235440312866872B",
1192
+ vaultImpl: "0x189160156d470B9ce8A55206DE19Ea60D2638ec6",
1193
+ batchExecutorLib: "0xd83dc3A79Bb41a02Ca7aC812c0f52212C1BdBC1B",
1194
+ strategyFactory: "0xE1D082ef1CE17f9C6a46e97928337267BFF0C309"
1190
1195
  },
1191
1196
  // No ERC-8004 registry and no EAS path on Robinhood L2.
1192
1197
  identityRegistry: ZERO_ADDRESS,
@@ -3739,7 +3744,7 @@ function registerStrategyTemplateCommands(strategy2) {
3739
3744
  process.exit(1);
3740
3745
  }
3741
3746
  const { propose: propose2 } = await import("./governor-RRBTDGDX.js");
3742
- const { pinJSON } = await import("./ipfs-VGWTUORE.js");
3747
+ const { pinJSON } = await import("./ipfs-RWSTXTZM.js");
3743
3748
  const { parseDuration: parseDuration2 } = await import("./governor-RRBTDGDX.js");
3744
3749
  const strategyDuration = parseDuration2(opts.duration);
3745
3750
  const metaSpinner = ora("Pinning metadata to IPFS...").start();
@@ -10331,7 +10336,7 @@ try {
10331
10336
  } catch {
10332
10337
  }
10333
10338
  var require2 = createRequire(import.meta.url);
10334
- var CLI_VERSION = "0.72.3";
10339
+ var CLI_VERSION = "0.73.1";
10335
10340
  async function loadXmtp() {
10336
10341
  return import("./xmtp-M32JSE4I.js");
10337
10342
  }
@@ -10798,18 +10803,25 @@ fund.command("update-metadata").description("Update fund metadata (creator only)
10798
10803
  spinner.fail(`Fund #${opts.id} not found.`);
10799
10804
  process.exit(1);
10800
10805
  }
10801
- const name = opts.name || info.subdomain;
10802
- const description = opts.description || `${name} \u2014 a Sherwood fund on ${info.subdomain}.sherwoodagent.eth`;
10806
+ spinner.text = W6("Fetching current metadata...");
10807
+ let current = null;
10808
+ try {
10809
+ current = await fetchMetadata(info.metadataURI);
10810
+ } catch (fetchErr) {
10811
+ console.warn(
10812
+ chalk16.yellow(
10813
+ " \u26A0 Could not fetch current metadata \u2014 rebuilding from flags (unknown fields like xmtpGroupId may be dropped)"
10814
+ )
10815
+ );
10816
+ console.warn(DIM6(` ${fetchErr instanceof Error ? fetchErr.message : String(fetchErr)}`));
10817
+ }
10803
10818
  spinner.text = W6("Uploading metadata to IPFS...");
10804
- const metadata = {
10805
- schema: "sherwood/fund/v1",
10806
- name,
10807
- description,
10808
- chain: getChain().name,
10809
- strategies: [],
10810
- terms: {},
10811
- links: {}
10812
- };
10819
+ const metadata = mergeFundMetadata(current, {
10820
+ name: opts.name,
10821
+ description: opts.description,
10822
+ subdomain: info.subdomain,
10823
+ defaultChain: getChain().name
10824
+ });
10813
10825
  metadataURI = await uploadMetadata(metadata);
10814
10826
  spinner.text = W6("Updating on-chain metadata...");
10815
10827
  }
@@ -11655,7 +11667,7 @@ ${info.name} (${info.type})`);
11655
11667
  }
11656
11668
  });
11657
11669
  try {
11658
- const { registerChatCommands } = await import("./chat-G5M3OK6M.js");
11670
+ const { registerChatCommands } = await import("./chat-ONGVCWAP.js");
11659
11671
  registerChatCommands(program);
11660
11672
  } catch {
11661
11673
  program.command("chat <name> [action] [actionArgs...]").description("Fund chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
@@ -11665,7 +11677,7 @@ try {
11665
11677
  process.exit(1);
11666
11678
  });
11667
11679
  }
11668
- var { registerSessionCommands } = await import("./session-PURMITWE.js");
11680
+ var { registerSessionCommands } = await import("./session-6FMZMPCE.js");
11669
11681
  registerSessionCommands(program);
11670
11682
  registerVeniceCommands(program);
11671
11683
  registerAllowanceCommands(program);
@@ -11674,7 +11686,7 @@ registerProposalCommands(program);
11674
11686
  registerQueueCommands(program);
11675
11687
  registerGovernorCommands(program);
11676
11688
  registerGuardianCommands(program);
11677
- var { registerResearchCommands } = await import("./research-6VEEPEPP.js");
11689
+ var { registerResearchCommands } = await import("./research-2AFNVV7X.js");
11678
11690
  registerResearchCommands(program);
11679
11691
  var { registerAgentCommands } = await import("./agent-6IRZUPFT.js");
11680
11692
  registerAgentCommands(program);