@sherwoodagent/cli 0.83.0 → 0.84.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.
Files changed (71) hide show
  1. package/dist/{abis-ZQ6I3S6L.js → abis-WVW2EBI3.js} +2 -2
  2. package/dist/{addresses-MS6BB7AB.js → addresses-LFYVOPOH.js} +3 -3
  3. package/dist/{agent-I6AUSTZG.js → agent-TBLC3WNJ.js} +7 -7
  4. package/dist/{chat-5SI7K5FA.js → chat-UZ3BBEXV.js} +8 -8
  5. package/dist/{chunk-MRUTBOMZ.js → chunk-45FELUWY.js} +2 -2
  6. package/dist/{chunk-UK2G5AZG.js → chunk-56YYNNNQ.js} +5 -5
  7. package/dist/{chunk-N5RIB6ZZ.js → chunk-6DRNSZ2Z.js} +41 -5
  8. package/dist/chunk-6DRNSZ2Z.js.map +1 -0
  9. package/dist/{chunk-ZTR2GPZO.js → chunk-BOC7VB5B.js} +36 -5
  10. package/dist/chunk-BOC7VB5B.js.map +1 -0
  11. package/dist/{chunk-I7TMY7AS.js → chunk-DNRIKCJ5.js} +85 -31
  12. package/dist/chunk-DNRIKCJ5.js.map +1 -0
  13. package/dist/{chunk-XSP64L7L.js → chunk-F3MDCUGO.js} +4 -368
  14. package/dist/chunk-F3MDCUGO.js.map +1 -0
  15. package/dist/{chunk-5O3KO7ZI.js → chunk-JUVXLTL2.js} +56 -5
  16. package/dist/chunk-JUVXLTL2.js.map +1 -0
  17. package/dist/{chunk-7ILOT3SI.js → chunk-KVB5P2OE.js} +2 -2
  18. package/dist/{chunk-I7NXKJCG.js → chunk-MQ55QOGD.js} +5 -5
  19. package/dist/{chunk-ZEZQM42M.js → chunk-NCFN66ZH.js} +39 -6
  20. package/dist/chunk-NCFN66ZH.js.map +1 -0
  21. package/dist/{chunk-ZW2VKCFZ.js → chunk-PVTZMRC6.js} +34 -2
  22. package/dist/chunk-PVTZMRC6.js.map +1 -0
  23. package/dist/{chunk-5BYJ3TGZ.js → chunk-QRMZRAMG.js} +20 -4
  24. package/dist/chunk-QRMZRAMG.js.map +1 -0
  25. package/dist/{chunk-4BMAFJKO.js → chunk-SNR5R5TO.js} +6 -6
  26. package/dist/{client-Z7LNEG2F.js → client-3SE632Q2.js} +3 -3
  27. package/dist/{eas-DWBT332K.js → eas-QPKX6A5A.js} +12 -6
  28. package/dist/{governor-ZVNTHHML.js → governor-7QKGNHMR.js} +12 -6
  29. package/dist/index.js +2183 -1305
  30. package/dist/index.js.map +1 -1
  31. package/dist/{network-HCFCNZUI.js → network-FEHRFDPJ.js} +6 -2
  32. package/dist/{price-GEUCSTO4.js → price-SVF25X4O.js} +6 -6
  33. package/dist/{providers-PWUJ2AR7.js → providers-BMOHHVUN.js} +4 -4
  34. package/dist/{research-SW6PLO32.js → research-YSNJ5HTM.js} +15 -12
  35. package/dist/research-YSNJ5HTM.js.map +1 -0
  36. package/dist/{session-6RSOG5NE.js → session-RMAIFRS6.js} +44 -13
  37. package/dist/session-RMAIFRS6.js.map +1 -0
  38. package/dist/{trade-45JPCKGP.js → trade-YPLTIS56.js} +21 -21
  39. package/dist/trade-YPLTIS56.js.map +1 -0
  40. package/dist/{vault-GAIOZZPC.js → vault-UCFFOOTS.js} +8 -8
  41. package/dist/{xmtp-A2KIQWGA.js → xmtp-S7IGTIMZ.js} +6 -6
  42. package/package.json +6 -3
  43. package/scripts/pin-xmtp-bindings.cjs +26 -10
  44. package/dist/chunk-5BYJ3TGZ.js.map +0 -1
  45. package/dist/chunk-5O3KO7ZI.js.map +0 -1
  46. package/dist/chunk-I7TMY7AS.js.map +0 -1
  47. package/dist/chunk-N5RIB6ZZ.js.map +0 -1
  48. package/dist/chunk-XSP64L7L.js.map +0 -1
  49. package/dist/chunk-ZEZQM42M.js.map +0 -1
  50. package/dist/chunk-ZTR2GPZO.js.map +0 -1
  51. package/dist/chunk-ZW2VKCFZ.js.map +0 -1
  52. package/dist/research-SW6PLO32.js.map +0 -1
  53. package/dist/session-6RSOG5NE.js.map +0 -1
  54. package/dist/trade-45JPCKGP.js.map +0 -1
  55. /package/dist/{abis-ZQ6I3S6L.js.map → abis-WVW2EBI3.js.map} +0 -0
  56. /package/dist/{addresses-MS6BB7AB.js.map → addresses-LFYVOPOH.js.map} +0 -0
  57. /package/dist/{agent-I6AUSTZG.js.map → agent-TBLC3WNJ.js.map} +0 -0
  58. /package/dist/{chat-5SI7K5FA.js.map → chat-UZ3BBEXV.js.map} +0 -0
  59. /package/dist/{chunk-MRUTBOMZ.js.map → chunk-45FELUWY.js.map} +0 -0
  60. /package/dist/{chunk-UK2G5AZG.js.map → chunk-56YYNNNQ.js.map} +0 -0
  61. /package/dist/{chunk-7ILOT3SI.js.map → chunk-KVB5P2OE.js.map} +0 -0
  62. /package/dist/{chunk-I7NXKJCG.js.map → chunk-MQ55QOGD.js.map} +0 -0
  63. /package/dist/{chunk-4BMAFJKO.js.map → chunk-SNR5R5TO.js.map} +0 -0
  64. /package/dist/{client-Z7LNEG2F.js.map → client-3SE632Q2.js.map} +0 -0
  65. /package/dist/{eas-DWBT332K.js.map → eas-QPKX6A5A.js.map} +0 -0
  66. /package/dist/{governor-ZVNTHHML.js.map → governor-7QKGNHMR.js.map} +0 -0
  67. /package/dist/{network-HCFCNZUI.js.map → network-FEHRFDPJ.js.map} +0 -0
  68. /package/dist/{price-GEUCSTO4.js.map → price-SVF25X4O.js.map} +0 -0
  69. /package/dist/{providers-PWUJ2AR7.js.map → providers-BMOHHVUN.js.map} +0 -0
  70. /package/dist/{vault-GAIOZZPC.js.map → vault-UCFFOOTS.js.map} +0 -0
  71. /package/dist/{xmtp-A2KIQWGA.js.map → xmtp-S7IGTIMZ.js.map} +0 -0
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  PROPOSAL_STATES,
3
3
  getProposal
4
- } from "./chunk-ZTR2GPZO.js";
4
+ } from "./chunk-BOC7VB5B.js";
5
5
  import {
6
6
  getPublicClient
7
- } from "./chunk-5BYJ3TGZ.js";
7
+ } from "./chunk-QRMZRAMG.js";
8
8
  import {
9
9
  resolveMetadataSummary
10
10
  } from "./chunk-VR4VHX4D.js";
@@ -18,14 +18,33 @@ var VAULT_EVENTS = [
18
18
  parseAbiItem("event DepositorApproved(address indexed depositor)"),
19
19
  parseAbiItem("event DepositorRemoved(address indexed depositor)"),
20
20
  parseAbiItem("event RedemptionsLockedEvent()"),
21
- parseAbiItem("event RedemptionsUnlockedEvent()")
21
+ parseAbiItem("event RedemptionsUnlockedEvent()"),
22
+ // ISyndicateVault.SandboxRun — a proposal's sandbox was minted, funded and run.
23
+ // NAME COLLISION WARNING: ICallSandbox also declares a `SandboxRun` event with a
24
+ // DIFFERENT shape (SandboxRun(address,uint256,uint256), topic0 0x2638b671…) than
25
+ // this vault-side one (topic0 0x6fbfaa03…). Downstream consumers (the
26
+ // sherwood-monitor Hermes plugin) route by the `type` string, so ONLY the vault
27
+ // event may be emitted as "SandboxRun". If the ICallSandbox event is ever wired
28
+ // here, it MUST get a distinct type string (e.g. "CallSandboxRun").
29
+ parseAbiItem("event SandboxRun(uint256 indexed pid, address indexed sandbox, uint256 funding)")
22
30
  ];
23
31
  var GOVERNOR_EVENTS = [
24
32
  parseAbiItem("event ProposalCreated(uint256 indexed proposalId, address indexed proposer, address indexed vault, uint256 performanceFeeBps, uint256 strategyDuration, uint256 executeCallCount, uint256 settlementCallCount, string metadataURI)"),
25
33
  parseAbiItem("event VoteCast(uint256 indexed proposalId, address indexed voter, uint8 support, uint256 weight)"),
26
34
  parseAbiItem("event ProposalExecuted(uint256 indexed proposalId, address indexed vault, uint256 capitalSnapshot)"),
27
35
  parseAbiItem("event ProposalSettled(uint256 indexed proposalId, address indexed vault, int256 pnl, uint256 performanceFee, uint256 duration)"),
28
- parseAbiItem("event ProposalCancelled(uint256 indexed proposalId, address indexed cancelledBy)")
36
+ parseAbiItem("event ProposalCancelled(uint256 indexed proposalId, address indexed cancelledBy)"),
37
+ // ISyndicateGovernor.SandboxPayloadStored — a proposeWithSandbox payload was
38
+ // stored for proposalId. The call set itself is not in the log (read it via
39
+ // `sandboxPayload`); the counts are the cheap off-chain signal.
40
+ parseAbiItem("event SandboxPayloadStored(uint256 indexed proposalId, uint256 funding, uint256 callCount, uint256 tokenCount)")
41
+ ];
42
+ var GUARDIAN_REGISTRY_EVENTS = [
43
+ parseAbiItem("event ReviewOpened(uint256 indexed proposalId, uint128 totalStakeAtOpen)"),
44
+ parseAbiItem("event ReviewResolved(uint256 indexed proposalId, bool blocked, uint256 slashedAmount)")
45
+ ];
46
+ var STAKED_WOOD_EVENTS = [
47
+ parseAbiItem("event GuardianSlashed(bytes32 indexed reviewKey, address indexed approver, uint256 ownSlash, uint256 delegatedSlash)")
29
48
  ];
30
49
  function logToChainEvent(log, eventName) {
31
50
  const args = {};
@@ -95,6 +114,36 @@ async function getGovernorEvents(governorAddress, vaultAddress, fromBlock, toBlo
95
114
  return logToChainEvent(log, eventName);
96
115
  });
97
116
  }
117
+ async function getGuardianRegistryEvents(registryAddress, fromBlock, toBlock) {
118
+ if (registryAddress === "0x0000000000000000000000000000000000000000") {
119
+ return [];
120
+ }
121
+ const logs = await getLogsChunked({
122
+ address: registryAddress,
123
+ events: GUARDIAN_REGISTRY_EVENTS,
124
+ fromBlock,
125
+ toBlock
126
+ });
127
+ return logs.map((log) => {
128
+ const eventName = log.eventName || "UnknownGuardianRegistryEvent";
129
+ return logToChainEvent(log, eventName);
130
+ });
131
+ }
132
+ async function getStakedWoodEvents(stakedWoodAddress, fromBlock, toBlock) {
133
+ if (stakedWoodAddress === "0x0000000000000000000000000000000000000000") {
134
+ return [];
135
+ }
136
+ const logs = await getLogsChunked({
137
+ address: stakedWoodAddress,
138
+ events: STAKED_WOOD_EVENTS,
139
+ fromBlock,
140
+ toBlock
141
+ });
142
+ return logs.map((log) => {
143
+ const eventName = log.eventName || "UnknownStakedWoodEvent";
144
+ return logToChainEvent(log, eventName);
145
+ });
146
+ }
98
147
  async function getCurrentBlock() {
99
148
  const client = getPublicClient();
100
149
  return client.getBlockNumber();
@@ -167,7 +216,9 @@ export {
167
216
  getLogsChunked,
168
217
  getVaultEvents,
169
218
  getGovernorEvents,
219
+ getGuardianRegistryEvents,
220
+ getStakedWoodEvents,
170
221
  getCurrentBlock,
171
222
  enrichProposalEvents
172
223
  };
173
- //# sourceMappingURL=chunk-5O3KO7ZI.js.map
224
+ //# sourceMappingURL=chunk-JUVXLTL2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/events.ts"],"sourcesContent":["/**\n * On-chain event reader — fetches vault and governor events via viem getLogs.\n *\n * Uses HTTP RPC (no WebSocket needed). Block ranges are capped at 10,000\n * per call to avoid RPC timeouts (~83 minutes on Base at 2 blocks/sec).\n */\n\nimport type { Address, Log } from \"viem\";\nimport { parseAbiItem } from \"viem\";\nimport { getPublicClient } from \"./client.js\";\nimport { getProposal, PROPOSAL_STATES } from \"./governor.js\";\nimport { resolveMetadataSummary } from \"./ipfs.js\";\n\nconst MAX_BLOCK_RANGE = 10_000n;\n\n// ── Event signatures (parseAbiItem format for getLogs) ──\n\nexport const VAULT_EVENTS = [\n parseAbiItem(\"event AgentRegistered(uint256 indexed agentId, address indexed agentAddress)\"),\n parseAbiItem(\"event AgentRemoved(address indexed agentAddress)\"),\n parseAbiItem(\"event DepositorApproved(address indexed depositor)\"),\n parseAbiItem(\"event DepositorRemoved(address indexed depositor)\"),\n parseAbiItem(\"event RedemptionsLockedEvent()\"),\n parseAbiItem(\"event RedemptionsUnlockedEvent()\"),\n // ISyndicateVault.SandboxRun — a proposal's sandbox was minted, funded and run.\n // NAME COLLISION WARNING: ICallSandbox also declares a `SandboxRun` event with a\n // DIFFERENT shape (SandboxRun(address,uint256,uint256), topic0 0x2638b671…) than\n // this vault-side one (topic0 0x6fbfaa03…). Downstream consumers (the\n // sherwood-monitor Hermes plugin) route by the `type` string, so ONLY the vault\n // event may be emitted as \"SandboxRun\". If the ICallSandbox event is ever wired\n // here, it MUST get a distinct type string (e.g. \"CallSandboxRun\").\n parseAbiItem(\"event SandboxRun(uint256 indexed pid, address indexed sandbox, uint256 funding)\"),\n] as const;\n\nexport const GOVERNOR_EVENTS = [\n parseAbiItem(\"event ProposalCreated(uint256 indexed proposalId, address indexed proposer, address indexed vault, uint256 performanceFeeBps, uint256 strategyDuration, uint256 executeCallCount, uint256 settlementCallCount, string metadataURI)\"),\n parseAbiItem(\"event VoteCast(uint256 indexed proposalId, address indexed voter, uint8 support, uint256 weight)\"),\n parseAbiItem(\"event ProposalExecuted(uint256 indexed proposalId, address indexed vault, uint256 capitalSnapshot)\"),\n parseAbiItem(\"event ProposalSettled(uint256 indexed proposalId, address indexed vault, int256 pnl, uint256 performanceFee, uint256 duration)\"),\n parseAbiItem(\"event ProposalCancelled(uint256 indexed proposalId, address indexed cancelledBy)\"),\n // ISyndicateGovernor.SandboxPayloadStored — a proposeWithSandbox payload was\n // stored for proposalId. The call set itself is not in the log (read it via\n // `sandboxPayload`); the counts are the cheap off-chain signal.\n parseAbiItem(\"event SandboxPayloadStored(uint256 indexed proposalId, uint256 funding, uint256 callCount, uint256 tokenCount)\"),\n] as const;\n\n// IGuardianRegistry — guardian review lifecycle for proposals.\nexport const GUARDIAN_REGISTRY_EVENTS = [\n parseAbiItem(\"event ReviewOpened(uint256 indexed proposalId, uint128 totalStakeAtOpen)\"),\n parseAbiItem(\"event ReviewResolved(uint256 indexed proposalId, bool blocked, uint256 slashedAmount)\"),\n] as const;\n\n// StakedWood — approver slashes for blocked proposals.\nexport const STAKED_WOOD_EVENTS = [\n parseAbiItem(\"event GuardianSlashed(bytes32 indexed reviewKey, address indexed approver, uint256 ownSlash, uint256 delegatedSlash)\"),\n] as const;\n\n/** Normalized event returned by the session check. */\nexport interface ChainEvent {\n source: \"chain\";\n type: string;\n block: number;\n tx: string;\n args: Record<string, string>;\n}\n\nfunction logToChainEvent(log: Log, eventName: string): ChainEvent {\n const args: Record<string, string> = {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const decoded = (log as any).args;\n if (decoded) {\n for (const [key, value] of Object.entries(decoded)) {\n args[key] = String(value);\n }\n }\n\n return {\n source: \"chain\",\n type: eventName,\n block: Number(log.blockNumber),\n tx: log.transactionHash || \"\",\n args,\n };\n}\n\n/**\n * Fetch events in chunks to stay within RPC limits.\n * Returns all logs from `fromBlock` to `toBlock` inclusive.\n * Exported for reuse (e.g. vault.ts getNetDeposited's 0→latest scan).\n */\nexport async function getLogsChunked(params: {\n address: Address;\n events: readonly ReturnType<typeof parseAbiItem>[];\n fromBlock: bigint;\n toBlock: bigint;\n}): Promise<Log[]> {\n const client = getPublicClient();\n const { address, events, fromBlock, toBlock } = params;\n\n const allLogs: Log[] = [];\n let cursor = fromBlock;\n\n while (cursor <= toBlock) {\n const end =\n cursor + MAX_BLOCK_RANGE - 1n > toBlock\n ? toBlock\n : cursor + MAX_BLOCK_RANGE - 1n;\n\n const logs = await client.getLogs({\n address,\n events: events as never,\n fromBlock: cursor,\n toBlock: end,\n });\n\n allLogs.push(...logs);\n cursor = end + 1n;\n }\n\n return allLogs;\n}\n\n/** Fetch vault events (AgentRegistered, Ragequit, etc.) */\nexport async function getVaultEvents(\n vaultAddress: Address,\n fromBlock: bigint,\n toBlock: bigint,\n): Promise<ChainEvent[]> {\n const logs = await getLogsChunked({\n address: vaultAddress,\n events: VAULT_EVENTS,\n fromBlock,\n toBlock,\n });\n\n return logs.map((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const eventName = (log as any).eventName || \"UnknownVaultEvent\";\n return logToChainEvent(log, eventName);\n });\n}\n\n/** Fetch governor events filtered to a specific vault. */\nexport async function getGovernorEvents(\n governorAddress: Address,\n vaultAddress: Address,\n fromBlock: bigint,\n toBlock: bigint,\n): Promise<ChainEvent[]> {\n if (governorAddress === \"0x0000000000000000000000000000000000000000\") {\n return []; // Governor not deployed yet\n }\n\n const logs = await getLogsChunked({\n address: governorAddress,\n events: GOVERNOR_EVENTS,\n fromBlock,\n toBlock,\n });\n\n // Filter governor events to only those involving our vault\n return logs\n .filter((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const args = (log as any).args;\n if (!args) return true;\n // ProposalCreated, ProposalExecuted, ProposalSettled have `vault` in args\n if (args.vault) {\n return (args.vault as string).toLowerCase() === vaultAddress.toLowerCase();\n }\n // VoteCast, ProposalCancelled don't have vault — include them\n // (agents care about all votes/cancellations on their governor)\n return true;\n })\n .map((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const eventName = (log as any).eventName || \"UnknownGovernorEvent\";\n return logToChainEvent(log, eventName);\n });\n}\n\n/**\n * Fetch guardian review events (ReviewOpened, ReviewResolved) from the\n * GuardianRegistry. Returns [] when the registry is not deployed (zero address).\n * Not filterable per vault — proposalIds are governor-scoped, so callers get\n * every review on the chain's registry (mirrors the VoteCast rationale above).\n */\nexport async function getGuardianRegistryEvents(\n registryAddress: Address,\n fromBlock: bigint,\n toBlock: bigint,\n): Promise<ChainEvent[]> {\n if (registryAddress === \"0x0000000000000000000000000000000000000000\") {\n return []; // GuardianRegistry not deployed on this chain\n }\n\n const logs = await getLogsChunked({\n address: registryAddress,\n events: GUARDIAN_REGISTRY_EVENTS,\n fromBlock,\n toBlock,\n });\n\n return logs.map((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const eventName = (log as any).eventName || \"UnknownGuardianRegistryEvent\";\n return logToChainEvent(log, eventName);\n });\n}\n\n/**\n * Fetch guardian slash events (GuardianSlashed) from StakedWood.\n * Returns [] when sWOOD is not deployed (zero address).\n */\nexport async function getStakedWoodEvents(\n stakedWoodAddress: Address,\n fromBlock: bigint,\n toBlock: bigint,\n): Promise<ChainEvent[]> {\n if (stakedWoodAddress === \"0x0000000000000000000000000000000000000000\") {\n return []; // StakedWood not deployed on this chain\n }\n\n const logs = await getLogsChunked({\n address: stakedWoodAddress,\n events: STAKED_WOOD_EVENTS,\n fromBlock,\n toBlock,\n });\n\n return logs.map((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const eventName = (log as any).eventName || \"UnknownStakedWoodEvent\";\n return logToChainEvent(log, eventName);\n });\n}\n\n/** Get current block number from the RPC. */\nexport async function getCurrentBlock(): Promise<bigint> {\n const client = getPublicClient();\n return client.getBlockNumber();\n}\n\n// ── Proposal event enrichment ──\n\nconst PROPOSAL_EVENT_TYPES = new Set([\n \"ProposalCreated\",\n \"VoteCast\",\n \"ProposalExecuted\",\n \"ProposalSettled\",\n \"ProposalCancelled\",\n]);\n\n/**\n * Enrich proposal-related events with IPFS metadata (name, description)\n * and current on-chain state. Best-effort — events are returned un-enriched\n * if RPC or IPFS calls fail.\n *\n * `vault` identifies which per-vault governor to read proposal state from\n * (proposal IDs are scoped to a vault's governor in the per-vault design).\n *\n * An optional metadataCache avoids redundant IPFS fetches across poll cycles\n * in streaming mode (proposal metadata is immutable once pinned).\n */\nexport async function enrichProposalEvents(\n vault: Address,\n events: ChainEvent[],\n metadataCache?: Map<string, { name: string; description: string }>,\n): Promise<ChainEvent[]> {\n const cache = metadataCache ?? new Map<string, { name: string; description: string }>();\n\n const proposalEvents = events.filter((e) => PROPOSAL_EVENT_TYPES.has(e.type));\n if (proposalEvents.length === 0) return events;\n\n // Collect unique proposalIds and their metadata sources\n const idsNeedingLookup = new Set<string>();\n const createdURIs = new Map<string, string>(); // proposalId → metadataURI from event args\n\n for (const event of proposalEvents) {\n const pid = event.args.proposalId;\n if (!pid) continue;\n\n if (event.type === \"ProposalCreated\" && event.args.metadataURI) {\n createdURIs.set(pid, event.args.metadataURI);\n } else if (!cache.has(pid)) {\n idsNeedingLookup.add(pid);\n }\n }\n\n // Fetch on-chain proposal data for non-Created events (parallel, deduplicated)\n const proposalData = new Map<string, { metadataURI: string; state: number }>();\n\n await Promise.all(\n Array.from(idsNeedingLookup).map(async (pid) => {\n try {\n const p = await getProposal(vault, BigInt(pid));\n proposalData.set(pid, { metadataURI: p.metadataURI, state: p.state });\n } catch {\n // RPC failure — skip enrichment for this proposalId\n }\n }),\n );\n\n // Resolve IPFS metadata (parallel, deduplicated, uses cache)\n const allURIs = new Map<string, string>();\n for (const [pid, uri] of createdURIs) allURIs.set(pid, uri);\n for (const [pid, data] of proposalData) {\n if (!allURIs.has(pid)) allURIs.set(pid, data.metadataURI);\n }\n\n await Promise.all(\n Array.from(allURIs.entries()).map(async ([pid, uri]) => {\n if (cache.has(pid) || !uri) return;\n const summary = await resolveMetadataSummary(uri);\n if (summary) cache.set(pid, summary);\n }),\n );\n\n // Inject enrichment fields into event args\n return events.map((event) => {\n if (!PROPOSAL_EVENT_TYPES.has(event.type)) return event;\n const pid = event.args.proposalId;\n if (!pid) return event;\n\n const meta = cache.get(pid);\n const pData = proposalData.get(pid);\n const enrichedArgs = { ...event.args };\n\n if (meta) {\n enrichedArgs.proposalName = meta.name;\n enrichedArgs.proposalDescription = meta.description;\n }\n\n if (event.type === \"ProposalCreated\") {\n enrichedArgs.proposalState = \"Pending\";\n } else if (pData) {\n enrichedArgs.proposalState = PROPOSAL_STATES[pData.state] || \"Unknown\";\n }\n\n return { ...event, args: enrichedArgs };\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getAccount
3
- } from "./chunk-5BYJ3TGZ.js";
3
+ } from "./chunk-QRMZRAMG.js";
4
4
  import {
5
5
  getVeniceApiKey,
6
6
  setVeniceApiKey
@@ -186,4 +186,4 @@ export {
186
186
  chatCompletion,
187
187
  listModels
188
188
  };
189
- //# sourceMappingURL=chunk-7ILOT3SI.js.map
189
+ //# sourceMappingURL=chunk-KVB5P2OE.js.map
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  ENS,
3
3
  SHERWOOD
4
- } from "./chunk-I7TMY7AS.js";
4
+ } from "./chunk-DNRIKCJ5.js";
5
5
  import {
6
6
  getPublicClient
7
- } from "./chunk-5BYJ3TGZ.js";
7
+ } from "./chunk-QRMZRAMG.js";
8
8
  import {
9
9
  VALID_NETWORKS,
10
10
  withNetwork
11
- } from "./chunk-ZW2VKCFZ.js";
11
+ } from "./chunk-PVTZMRC6.js";
12
12
  import {
13
13
  L2_REGISTRY_ABI,
14
14
  SYNDICATE_FACTORY_ABI
15
- } from "./chunk-XSP64L7L.js";
15
+ } from "./chunk-F3MDCUGO.js";
16
16
 
17
17
  // src/lib/ens.ts
18
18
  import { namehash } from "viem/ens";
@@ -111,4 +111,4 @@ export {
111
111
  setTextRecord,
112
112
  getTextRecord
113
113
  };
114
- //# sourceMappingURL=chunk-I7NXKJCG.js.map
114
+ //# sourceMappingURL=chunk-MQ55QOGD.js.map
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  EAS_CONTRACTS,
3
3
  EAS_SCHEMAS
4
- } from "./chunk-I7TMY7AS.js";
4
+ } from "./chunk-DNRIKCJ5.js";
5
5
  import {
6
6
  getAccount,
7
7
  getPublicClient,
8
8
  waitForReceipt,
9
9
  writeContractWithRetry
10
- } from "./chunk-5BYJ3TGZ.js";
10
+ } from "./chunk-QRMZRAMG.js";
11
11
  import {
12
12
  CHAIN_REGISTRY,
13
13
  getChain,
@@ -15,10 +15,10 @@ import {
15
15
  getNetwork,
16
16
  isTestnet,
17
17
  withNetwork
18
- } from "./chunk-ZW2VKCFZ.js";
18
+ } from "./chunk-PVTZMRC6.js";
19
19
  import {
20
20
  EAS_ABI
21
- } from "./chunk-XSP64L7L.js";
21
+ } from "./chunk-F3MDCUGO.js";
22
22
 
23
23
  // src/lib/eas.ts
24
24
  import { encodeAbiParameters, parseAbiParameters, decodeAbiParameters } from "viem";
@@ -40,7 +40,30 @@ function assertSchemasRegistered() {
40
40
  }
41
41
  }
42
42
  function getEasNetwork() {
43
- return getNetwork();
43
+ return EAS_NETWORK;
44
+ }
45
+ var EAS_NETWORK = "robinhood-mainnet";
46
+ function attestationWritesEnabled() {
47
+ return getNetwork() !== "robinhood-fork";
48
+ }
49
+ function skipWriteOnFork(kind) {
50
+ if (attestationWritesEnabled()) return false;
51
+ console.log(
52
+ ` ${kind} attestation skipped: writes are disabled on robinhood-fork (ephemeral chain; attestations live on Robinhood mainnet).`
53
+ );
54
+ return true;
55
+ }
56
+ async function assertCanPayForAttestation() {
57
+ await withNetwork(EAS_NETWORK, async () => {
58
+ const account = getAccount();
59
+ const balance = await getPublicClient().getBalance({ address: account.address });
60
+ if (balance === 0n) {
61
+ throw new Error(
62
+ `No ETH on Robinhood mainnet (chain 4663) for ${account.address}.
63
+ Attestations are recorded on Robinhood mainnet regardless of which chain your fund is on, so this wallet needs a small mainnet balance (a single attestation costs well under 0.0001 ETH).`
64
+ );
65
+ }
66
+ });
44
67
  }
45
68
  function getEasGraphqlUrl() {
46
69
  const url = getChainConfig().easGraphqlUrl;
@@ -93,6 +116,7 @@ function stripReferrerPrefix(message) {
93
116
  return message.replace(REF_PREFIX_RE, "").replace(CHAIN_PREFIX_RE, "");
94
117
  }
95
118
  async function createJoinRequest(syndicateId, agentId, vault, creatorAddress, message, referrerAgentId) {
119
+ if (skipWriteOnFork("join request")) return null;
96
120
  const sourceNetwork = getNetwork();
97
121
  const easNetwork = getEasNetwork();
98
122
  const messageWithRef = formatMessageWithRef(message, referrerAgentId);
@@ -130,6 +154,7 @@ async function createJoinRequest(syndicateId, agentId, vault, creatorAddress, me
130
154
  });
131
155
  }
132
156
  async function createApproval(syndicateId, agentId, vault, agentAddress) {
157
+ if (skipWriteOnFork("approval")) return null;
133
158
  return withNetwork(getEasNetwork(), async () => {
134
159
  assertSchemasRegistered();
135
160
  const data = encodeAbiParameters(AGENT_APPROVED_PARAMS, [
@@ -179,12 +204,14 @@ async function revokeAttestation(schemaUid, attestationUid) {
179
204
  });
180
205
  }
181
206
  async function revokeJoinRequest(attestationUid) {
207
+ if (skipWriteOnFork("revoke")) return null;
182
208
  return withNetwork(
183
209
  getEasNetwork(),
184
210
  () => revokeAttestation(EAS_SCHEMAS().SYNDICATE_JOIN_REQUEST, attestationUid)
185
211
  );
186
212
  }
187
213
  async function createResearchAttestation(provider, queryType, prompt, costUsdc, resultUri, recipient) {
214
+ if (skipWriteOnFork("research")) return null;
188
215
  const schemas = EAS_SCHEMAS();
189
216
  if (schemas.X402_RESEARCH === ZERO_BYTES32) {
190
217
  throw new Error(
@@ -224,6 +251,7 @@ async function createResearchAttestation(provider, queryType, prompt, costUsdc,
224
251
  return { uid, hash };
225
252
  }
226
253
  async function createVeniceProvisionAttestation(agent) {
254
+ if (skipWriteOnFork("venice provision")) return null;
227
255
  const schemas = EAS_SCHEMAS();
228
256
  if (schemas.VENICE_PROVISION === ZERO_BYTES32) return skipAttestation("VENICE_PROVISION");
229
257
  const client = getPublicClient();
@@ -253,6 +281,7 @@ async function createVeniceProvisionAttestation(agent) {
253
281
  return { uid, hash };
254
282
  }
255
283
  async function createVeniceInferenceAttestation(model, promptTokens, completionTokens, promptHash, recipient) {
284
+ if (skipWriteOnFork("venice inference")) return null;
256
285
  const schemas = EAS_SCHEMAS();
257
286
  if (schemas.VENICE_INFERENCE === ZERO_BYTES32) return skipAttestation("VENICE_INFERENCE");
258
287
  const client = getPublicClient();
@@ -287,6 +316,7 @@ async function createVeniceInferenceAttestation(model, promptTokens, completionT
287
316
  return { uid, hash };
288
317
  }
289
318
  async function createTradeAttestation(tokenIn, tokenOut, amountIn, amountOut, txHash, routing, recipient) {
319
+ if (skipWriteOnFork("trade")) return null;
290
320
  const schemas = EAS_SCHEMAS();
291
321
  if (schemas.TRADE_EXECUTED === ZERO_BYTES32) return skipAttestation("TRADE_EXECUTED");
292
322
  const client = getPublicClient();
@@ -435,6 +465,9 @@ async function queryJoinRequestsImpl(recipient) {
435
465
 
436
466
  export {
437
467
  getEasNetwork,
468
+ EAS_NETWORK,
469
+ attestationWritesEnabled,
470
+ assertCanPayForAttestation,
438
471
  getEasScanUrl,
439
472
  getEasExplorerUrl,
440
473
  formatMessageWithRef,
@@ -453,4 +486,4 @@ export {
453
486
  queryApprovals,
454
487
  queryJoinRequests
455
488
  };
456
- //# sourceMappingURL=chunk-ZEZQM42M.js.map
489
+ //# sourceMappingURL=chunk-NCFN66ZH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/eas.ts"],"sourcesContent":["/**\n * EAS (Ethereum Attestation Service) wrapper for fund join requests and approvals.\n *\n * Uses viem for on-chain writes and the EAS GraphQL API for queries.\n * No ethers dependency — attestation data is encoded with viem's encodeAbiParameters.\n */\n\nimport type { Address, Hex } from \"viem\";\nimport { encodeAbiParameters, parseAbiParameters, decodeAbiParameters } from \"viem\";\nimport { getPublicClient, getAccount, writeContractWithRetry, waitForReceipt } from \"./client.js\";\nimport { getChain, getNetwork, getChainConfig, withNetwork, isTestnet, CHAIN_REGISTRY, type Network } from \"./network.js\";\nimport { EAS_CONTRACTS, EAS_SCHEMAS } from \"./addresses.js\";\nimport { EAS_ABI } from \"./abis.js\";\n\n// ── Schema definitions ──\n\nconst ZERO_BYTES32 = \"0x0000000000000000000000000000000000000000000000000000000000000000\" as Hex;\n\n// keccak256(\"Attested(address,address,bytes32,bytes32)\")\nconst ATTESTED_TOPIC = \"0x8bf46bf4cfd674fa735a3d63ec1c9ad4153f033c290341f3a588b75685141b35\" as Hex;\n\nconst JOIN_REQUEST_PARAMS = parseAbiParameters(\"uint256, uint256, address, string\");\nconst AGENT_APPROVED_PARAMS = parseAbiParameters(\"uint256, uint256, address\");\nconst X402_RESEARCH_PARAMS = parseAbiParameters(\"string, string, string, string, string\");\nconst VENICE_PROVISION_PARAMS = parseAbiParameters(\"address, string\");\nconst VENICE_INFERENCE_PARAMS = parseAbiParameters(\"string, uint256, uint256, string\");\nconst TRADE_EXECUTED_PARAMS = parseAbiParameters(\"address, address, uint256, string, string, string\");\n\nfunction assertSchemasRegistered() {\n const schemas = EAS_SCHEMAS();\n if (schemas.SYNDICATE_JOIN_REQUEST === ZERO_BYTES32 || schemas.AGENT_APPROVED === ZERO_BYTES32) {\n const flag = isTestnet() ? \" --testnet\" : \"\";\n throw new Error(\n `EAS schemas not registered on ${getNetwork()}. Run: cd cli && npx tsx scripts/register-eas-schemas.ts${flag}`,\n );\n }\n}\n\n/**\n * The chain where fund-coordination attestations live. EAS is not deployed\n * on EITHER Robinhood chain (both registry entries carry `easGraphqlUrl: null`\n * and ZERO contracts), so the default flip to robinhood-fork changes nothing\n * here — this returns the active network — downstream `getEasGraphqlUrl` / `EAS_CONTRACTS()` then fail\n * with a clear \"EAS not available\" error. When an EAS-bearing chain is added\n * back, route coordination attestations to it here.\n */\nexport function getEasNetwork(): Network {\n return EAS_NETWORK;\n}\n\n/**\n * Attestations are written and read on ONE chain regardless of which chain the\n * fund lives on — a join request for a 46630 fund is recorded on 4663.\n */\nexport const EAS_NETWORK: Network = \"robinhood-mainnet\";\n\n/**\n * Attestation WRITES are a no-op on the Tenderly fork.\n *\n * The fork is ephemeral and is the default network, so routing its writes to\n * mainnet would burn real ETH recording vnet activity into a permanent public\n * record. Reads are unaffected — a fork session still displays 4663\n * attestations. See spec §10.3.\n */\nexport function attestationWritesEnabled(): boolean {\n return getNetwork() !== \"robinhood-fork\";\n}\n\n/** Thrown-free guard used by every write path; returns true when it skipped. */\nfunction skipWriteOnFork(kind: string): boolean {\n if (attestationWritesEnabled()) return false;\n console.log(\n ` ${kind} attestation skipped: writes are disabled on robinhood-fork ` +\n `(ephemeral chain; attestations live on Robinhood mainnet).`,\n );\n return true;\n}\n\n/**\n * Fails before an attestation write when the signer cannot pay for it on the\n * EAS chain. Attestations cost real mainnet ETH even when the fund is on a\n * testnet, which is surprising enough that a bare out-of-gas revert would be\n * the worst possible way to discover it.\n */\nexport async function assertCanPayForAttestation(): Promise<void> {\n await withNetwork(EAS_NETWORK, async () => {\n const account = getAccount();\n const balance = await getPublicClient().getBalance({ address: account.address });\n if (balance === 0n) {\n throw new Error(\n `No ETH on Robinhood mainnet (chain 4663) for ${account.address}.\\n` +\n `Attestations are recorded on Robinhood mainnet regardless of which ` +\n `chain your fund is on, so this wallet needs a small mainnet balance ` +\n `(a single attestation costs well under 0.0001 ETH).`,\n );\n }\n });\n}\n\n// ── GraphQL ──\n\nfunction getEasGraphqlUrl(): string {\n const url = getChainConfig().easGraphqlUrl;\n if (!url) {\n throw new Error(\n `EAS is not available on ${getNetwork()}. Attestation operations require a chain with EAS (e.g. base, base-sepolia).`,\n );\n }\n return url;\n}\n\nexport function getEasScanUrl(uid: Hex): string {\n // Always use the EAS chain's scan host — attestations live on Base, even\n // for HyperEVM funds. Read the registry directly rather than mutating\n // the singleton, since this is a pure URL build.\n const host = CHAIN_REGISTRY[getEasNetwork()].easScanHost;\n if (!host) {\n throw new Error(`EAS scan host is not configured on ${getEasNetwork()}.`);\n }\n return `https://${host}/attestation/view/${uid}`;\n}\n\n/**\n * The block-explorer URL for a tx that ran on the EAS chain. Use this for\n * attestation create/revoke txs so the link points to base(-sepolia) scan\n * even when the active --chain is HyperEVM / Robinhood.\n */\nexport function getEasExplorerUrl(txHash: string): string {\n const host = CHAIN_REGISTRY[getEasNetwork()].explorerHost;\n return `https://${host}/tx/${txHash}`;\n}\n\n// ── Attestation Creation ──\n\n/**\n * Extract the attestation UID from a transaction receipt.\n * The EAS contract emits: event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)\n * uid is a non-indexed parameter in the event data.\n */\nfunction extractAttestationUid(receipt: { logs: readonly { topics: readonly Hex[]; data: Hex }[] }): Hex {\n for (const log of receipt.logs) {\n // Match the Attested event signature to avoid false positives from other events\n if (log.topics[0] !== ATTESTED_TOPIC) continue;\n // Attested event has 4 topics (sig + 3 indexed) and data contains the uid (bytes32)\n if (log.topics.length === 4 && log.data.length >= 66) {\n return (\"0x\" + log.data.slice(2, 66)) as Hex;\n }\n }\n throw new Error(\"Could not extract attestation UID from transaction receipt\");\n}\n\n// ── Message Prefix Helpers ──\n//\n// Join-request messages can carry CLI metadata as bracketed prefixes. Order\n// is `[chain:<network>] [ref:<id>] body` — chain outer because the chain\n// disambiguates the fund, ref inner because it's optional and per-user.\n// Both prefixes are stripped before display.\n\nconst CHAIN_PREFIX_RE = /^\\[chain:([a-z0-9-]+)\\]\\s*/;\n// Tolerate an optional leading [chain:...] prefix so parseReferrer keeps\n// working when both metadata fields are encoded.\nconst REF_PREFIX_RE = /^(?:\\[chain:[a-z0-9-]+\\]\\s*)?\\[ref:(\\d+)\\]\\s*/;\n\n/**\n * Format a message with an optional referrer prefix.\n * e.g. formatMessageWithRef(\"Hello\", 42) → \"[ref:42] Hello\"\n */\nexport function formatMessageWithRef(message: string, referrerAgentId?: number): string {\n if (referrerAgentId == null) return message;\n return `[ref:${referrerAgentId}] ${message}`;\n}\n\n/**\n * Parse a referrer agentId from a message, if present.\n * e.g. parseReferrer(\"[ref:42] Hello\") → 42\n * parseReferrer(\"[chain:hyperevm] [ref:42] Hello\") → 42\n */\nexport function parseReferrer(message: string): number | null {\n const match = message.match(REF_PREFIX_RE);\n return match ? parseInt(match[1], 10) : null;\n}\n\n/**\n * Format a message with a `[chain:<network>]` prefix when the fund\n * lives on a chain other than the EAS chain (Base / Base-Sepolia). Skipped\n * when source matches the EAS chain to avoid noise on the common case.\n */\nexport function formatMessageWithChain(\n message: string,\n sourceNetwork: Network,\n easNetwork: Network,\n): string {\n if (sourceNetwork === easNetwork) return message;\n return `[chain:${sourceNetwork}] ${message}`;\n}\n\n/**\n * Parse the source-chain prefix from a message, if present.\n * Returns null when no `[chain:<network>]` prefix is found, which means the\n * attestation was created from the EAS chain itself (e.g. base attestation\n * for a base fund).\n */\nexport function parseChain(message: string): string | null {\n const match = message.match(CHAIN_PREFIX_RE);\n return match ? match[1] : null;\n}\n\n/**\n * Strip both `[chain:...]` and `[ref:...]` prefixes from a message,\n * returning the clean human-facing body. Name kept for backward compat.\n */\nexport function stripReferrerPrefix(message: string): string {\n // REF_PREFIX_RE already swallows an optional leading chain prefix when ref\n // is present; the second replace handles a chain prefix without ref.\n return message.replace(REF_PREFIX_RE, \"\").replace(CHAIN_PREFIX_RE, \"\");\n}\n\n/**\n * Create a SYNDICATE_JOIN_REQUEST attestation.\n * Attester: the calling agent. Recipient: the fund creator.\n * If referrerAgentId is provided, it's embedded in the message as a [ref:N] prefix.\n */\nexport async function createJoinRequest(\n syndicateId: bigint,\n agentId: bigint,\n vault: Address,\n creatorAddress: Address,\n message: string,\n referrerAgentId?: number,\n): Promise<{ uid: Hex; hash: Hex } | null> {\n if (skipWriteOnFork(\"join request\")) return null;\n // Capture the fund's source chain BEFORE switching to the EAS chain\n // so we can encode it in the message prefix.\n const sourceNetwork = getNetwork();\n const easNetwork = getEasNetwork();\n const messageWithRef = formatMessageWithRef(message, referrerAgentId);\n const encodedMessage = formatMessageWithChain(messageWithRef, sourceNetwork, easNetwork);\n\n return withNetwork(easNetwork, async () => {\n assertSchemasRegistered();\n\n const data = encodeAbiParameters(JOIN_REQUEST_PARAMS, [\n syndicateId, agentId, vault, encodedMessage,\n ]);\n\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: EAS_SCHEMAS().SYNDICATE_JOIN_REQUEST,\n data: {\n recipient: creatorAddress,\n expirationTime: 0n,\n revocable: true,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await waitForReceipt(hash);\n const uid = extractAttestationUid(receipt);\n\n return { uid, hash };\n });\n}\n\n/**\n * Create an AGENT_APPROVED attestation.\n * Attester: the fund creator. Recipient: the agent wallet.\n */\nexport async function createApproval(\n syndicateId: bigint,\n agentId: bigint,\n vault: Address,\n agentAddress: Address,\n): Promise<{ uid: Hex; hash: Hex } | null> {\n if (skipWriteOnFork(\"approval\")) return null;\n return withNetwork(getEasNetwork(), async () => {\n assertSchemasRegistered();\n\n const data = encodeAbiParameters(AGENT_APPROVED_PARAMS, [\n syndicateId, agentId, vault,\n ]);\n\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: EAS_SCHEMAS().AGENT_APPROVED,\n data: {\n recipient: agentAddress,\n expirationTime: 0n,\n revocable: true,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await waitForReceipt(hash);\n const uid = extractAttestationUid(receipt);\n\n return { uid, hash };\n });\n}\n\n/**\n * Revoke an attestation. Only the original attester can revoke. Uses the\n * active chain's EAS — call sites that revoke fund-coordination\n * attestations (join requests, approvals) should go through\n * `revokeJoinRequest` / `revokeApproval` so the revoke lands on the same\n * chain as the original attestation (Base / Base-Sepolia).\n */\nexport async function revokeAttestation(\n schemaUid: Hex,\n attestationUid: Hex,\n): Promise<Hex> {\n return writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"revoke\",\n args: [{\n schema: schemaUid,\n data: {\n uid: attestationUid,\n value: 0n,\n },\n }],\n value: 0n,\n });\n}\n\n/** Revoke a SYNDICATE_JOIN_REQUEST on the EAS chain (Base / Base-Sepolia). */\nexport async function revokeJoinRequest(attestationUid: Hex): Promise<Hex | null> {\n if (skipWriteOnFork(\"revoke\")) return null;\n return withNetwork(getEasNetwork(), () =>\n revokeAttestation(EAS_SCHEMAS().SYNDICATE_JOIN_REQUEST, attestationUid),\n );\n}\n\n/**\n * Create an X402_RESEARCH attestation — records a research query on-chain.\n * Attester: the agent. Recipient: vault address (links to fund) or agent itself.\n * Schema: \"string provider, string queryType, string prompt, string costUsdc, string resultUri\"\n */\nexport async function createResearchAttestation(\n provider: string,\n queryType: string,\n prompt: string,\n costUsdc: string,\n resultUri: string,\n recipient?: Address,\n): Promise<{ uid: Hex; hash: Hex } | null> {\n if (skipWriteOnFork(\"research\")) return null;\n const schemas = EAS_SCHEMAS();\n if (schemas.X402_RESEARCH === ZERO_BYTES32) {\n throw new Error(\n \"X402_RESEARCH schema not registered. Run: npx tsx scripts/register-eas-schemas.ts --testnet\",\n );\n }\n\n const client = getPublicClient();\n const account = getAccount();\n\n const data = encodeAbiParameters(X402_RESEARCH_PARAMS, [\n provider, queryType, prompt, costUsdc, resultUri,\n ]);\n\n const hash = await writeContractWithRetry({\n account,\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: schemas.X402_RESEARCH,\n data: {\n recipient: recipient ?? account.address,\n expirationTime: 0n,\n revocable: false,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await waitForReceipt(hash);\n const uid = extractAttestationUid(receipt);\n\n return { uid, hash };\n}\n\n/**\n * Create a VENICE_PROVISION attestation — records that an agent provisioned a Venice API key.\n * Schema: \"address agent, string status\"\n */\nexport async function createVeniceProvisionAttestation(\n agent: Address,\n): Promise<{ uid: Hex; hash: Hex } | null> {\n if (skipWriteOnFork(\"venice provision\")) return null;\n const schemas = EAS_SCHEMAS();\n if (schemas.VENICE_PROVISION === ZERO_BYTES32) return skipAttestation(\"VENICE_PROVISION\");\n\n const client = getPublicClient();\n const account = getAccount();\n\n const data = encodeAbiParameters(VENICE_PROVISION_PARAMS, [agent, \"provisioned\"]);\n\n const hash = await writeContractWithRetry({\n account,\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: schemas.VENICE_PROVISION,\n data: {\n recipient: agent,\n expirationTime: 0n,\n revocable: false,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await waitForReceipt(hash);\n const uid = extractAttestationUid(receipt);\n return { uid, hash };\n}\n\n/**\n * Create a VENICE_INFERENCE attestation — records an inference call.\n * Attester: the agent. Recipient: vault address (links to fund) or agent itself.\n * Schema: \"string model, uint256 promptTokens, uint256 completionTokens, string promptHash\"\n */\nexport async function createVeniceInferenceAttestation(\n model: string,\n promptTokens: number,\n completionTokens: number,\n promptHash: string,\n recipient?: Address,\n): Promise<{ uid: Hex; hash: Hex } | null> {\n if (skipWriteOnFork(\"venice inference\")) return null;\n const schemas = EAS_SCHEMAS();\n if (schemas.VENICE_INFERENCE === ZERO_BYTES32) return skipAttestation(\"VENICE_INFERENCE\");\n\n const client = getPublicClient();\n const account = getAccount();\n\n const data = encodeAbiParameters(VENICE_INFERENCE_PARAMS, [\n model,\n BigInt(promptTokens),\n BigInt(completionTokens),\n promptHash,\n ]);\n\n const hash = await writeContractWithRetry({\n account,\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: schemas.VENICE_INFERENCE,\n data: {\n recipient: recipient ?? account.address,\n expirationTime: 0n,\n revocable: false,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await waitForReceipt(hash);\n const uid = extractAttestationUid(receipt);\n return { uid, hash };\n}\n\n/**\n * Create a TRADE_EXECUTED attestation — records a swap on-chain.\n * Attester: the agent. Recipient: vault address (links to fund) or agent itself.\n * Schema: \"address tokenIn, address tokenOut, uint256 amountIn, string amountOut, string txHash, string routing\"\n */\nexport async function createTradeAttestation(\n tokenIn: Address,\n tokenOut: Address,\n amountIn: bigint,\n amountOut: string,\n txHash: string,\n routing: string,\n recipient?: Address,\n): Promise<{ uid: Hex; hash: Hex } | null> {\n if (skipWriteOnFork(\"trade\")) return null;\n const schemas = EAS_SCHEMAS();\n if (schemas.TRADE_EXECUTED === ZERO_BYTES32) return skipAttestation(\"TRADE_EXECUTED\");\n\n const client = getPublicClient();\n const account = getAccount();\n\n const data = encodeAbiParameters(TRADE_EXECUTED_PARAMS, [\n tokenIn, tokenOut, amountIn, amountOut, txHash, routing,\n ]);\n\n const hash = await writeContractWithRetry({\n account,\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: schemas.TRADE_EXECUTED,\n data: {\n recipient: recipient ?? account.address,\n expirationTime: 0n,\n revocable: false,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await waitForReceipt(hash);\n const uid = extractAttestationUid(receipt);\n return { uid, hash };\n}\n\n/** Skip attestation gracefully when schema isn't registered on this chain. */\nfunction skipAttestation(name: string): { uid: Hex; hash: Hex } {\n return { uid: ZERO_BYTES32, hash: ZERO_BYTES32 };\n}\n\n// ── Attestation Queries ──\n\nexport interface JoinRequestAttestation {\n uid: Hex;\n attester: Address;\n recipient: Address;\n time: number;\n decoded: {\n syndicateId: bigint;\n agentId: bigint;\n vault: Address;\n message: string;\n };\n}\n\n/**\n * Query pending (non-revoked) join requests for a given recipient (creator address).\n * Uses the EAS GraphQL API.\n */\nexport interface ApprovalAttestation {\n uid: Hex;\n attester: Address;\n recipient: Address;\n time: number;\n decoded: {\n syndicateId: bigint;\n agentId: bigint;\n vault: Address;\n };\n}\n\n/**\n * Query existing (non-revoked) AGENT_APPROVED attestations created by a given attester (creator).\n * Used to check for duplicates before creating a new approval and to filter already-approved agents from requests.\n */\nexport async function queryApprovals(\n attester: Address,\n): Promise<ApprovalAttestation[]> {\n return withNetwork(getEasNetwork(), () => queryApprovalsImpl(attester));\n}\n\nasync function queryApprovalsImpl(attester: Address): Promise<ApprovalAttestation[]> {\n assertSchemasRegistered();\n const schemaUid = EAS_SCHEMAS().AGENT_APPROVED;\n const url = getEasGraphqlUrl();\n\n const query = `\n query Approvals($schemaId: String!, $attester: String!) {\n attestations(\n where: {\n schemaId: { equals: $schemaId }\n attester: { equals: $attester }\n revoked: { equals: false }\n }\n orderBy: [{ time: desc }]\n ) {\n id\n attester\n recipient\n time\n data\n }\n }\n `;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query,\n variables: { schemaId: schemaUid, attester },\n }),\n });\n\n if (!response.ok) {\n throw new Error(`EAS GraphQL query failed: ${response.statusText}`);\n }\n\n const json = await response.json() as {\n data?: {\n attestations: Array<{\n id: string;\n attester: string;\n recipient: string;\n time: number;\n data: string;\n }>;\n };\n };\n\n if (!json.data?.attestations) return [];\n\n return json.data.attestations.map((a) => {\n const decoded = decodeAbiParameters(AGENT_APPROVED_PARAMS, a.data as Hex);\n return {\n uid: a.id as Hex,\n attester: a.attester as Address,\n recipient: a.recipient as Address,\n time: a.time,\n decoded: {\n syndicateId: decoded[0],\n agentId: decoded[1],\n vault: decoded[2],\n },\n };\n });\n}\n\nexport async function queryJoinRequests(\n recipient: Address,\n): Promise<JoinRequestAttestation[]> {\n return withNetwork(getEasNetwork(), () => queryJoinRequestsImpl(recipient));\n}\n\nasync function queryJoinRequestsImpl(recipient: Address): Promise<JoinRequestAttestation[]> {\n assertSchemasRegistered();\n const schemaUid = EAS_SCHEMAS().SYNDICATE_JOIN_REQUEST;\n const url = getEasGraphqlUrl();\n\n const query = `\n query JoinRequests($schemaId: String!, $recipient: String!) {\n attestations(\n where: {\n schemaId: { equals: $schemaId }\n recipient: { equals: $recipient }\n revoked: { equals: false }\n }\n orderBy: [{ time: desc }]\n ) {\n id\n attester\n recipient\n time\n data\n }\n }\n `;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query,\n variables: { schemaId: schemaUid, recipient },\n }),\n });\n\n if (!response.ok) {\n throw new Error(`EAS GraphQL query failed: ${response.statusText}`);\n }\n\n const json = await response.json() as {\n data?: {\n attestations: Array<{\n id: string;\n attester: string;\n recipient: string;\n time: number;\n data: string;\n }>;\n };\n };\n\n if (!json.data?.attestations) return [];\n\n return json.data.attestations.map((a) => {\n const decoded = decodeAbiParameters(JOIN_REQUEST_PARAMS, a.data as Hex);\n return {\n uid: a.id as Hex,\n attester: a.attester as Address,\n recipient: a.recipient as Address,\n time: a.time,\n decoded: {\n syndicateId: decoded[0],\n agentId: decoded[1],\n vault: decoded[2],\n message: decoded[3],\n },\n };\n });\n}\n"],"mappings":"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","names":[]}
@@ -39,6 +39,15 @@ var robinhoodFork = defineChain({
39
39
  testnet: true
40
40
  });
41
41
  var DEFAULT_MIN_GAS_RESERVE = parseEther("0.0005");
42
+ var robinhoodMainnet = defineChain({
43
+ id: 4663,
44
+ name: "Robinhood Chain",
45
+ nativeCurrency: { name: "Ethereum", symbol: "ETH", decimals: 18 },
46
+ rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
47
+ blockExplorers: {
48
+ default: { name: "Blockscout", url: "https://robinhoodchain.blockscout.com" }
49
+ }
50
+ });
42
51
  var CHAIN_REGISTRY = {
43
52
  "robinhood-testnet": {
44
53
  chain: robinhoodTestnet,
@@ -67,9 +76,30 @@ var CHAIN_REGISTRY = {
67
76
  // Robinhood-mainnet-fork gas (~0.17 gwei) is higher than testnet's ~14 wei —
68
77
  // keep a modest reserve so the ETH-wrap deposit path isn't rejected.
69
78
  minGasReserve: parseEther("0.0005")
79
+ },
80
+ "robinhood-mainnet": {
81
+ chain: robinhoodMainnet,
82
+ // Public endpoint kept only as a last-resort fallback: it sits behind
83
+ // Cloudflare and challenges non-browser clients under load, and Robinhood's
84
+ // own docs call it unsuitable for production. ROBINHOOD_RPC_URL (Alchemy)
85
+ // is what actually gets used.
86
+ rpcFallbacks: ["https://rpc.mainnet.chain.robinhood.com"],
87
+ rpcEnvVar: "ROBINHOOD_RPC_URL",
88
+ explorerHost: "robinhoodchain.blockscout.com",
89
+ // Self-hosted EAS indexer — easscan does not index 4663. Serves GraphQL at
90
+ // the root, so no /graphql suffix.
91
+ easGraphqlUrl: "https://sherwood-indexer-production.up.railway.app",
92
+ // No easscan-equivalent UI for this chain yet; attestation permalinks are
93
+ // pending the app's own /attestation route (spec §10.2).
94
+ easScanHost: null,
95
+ xmtpEnv: "production",
96
+ isTestnet: false
70
97
  }
71
98
  };
72
- var VALID_NETWORKS = Object.keys(CHAIN_REGISTRY);
99
+ var COORDINATION_ONLY_NETWORKS = ["robinhood-mainnet"];
100
+ var VALID_NETWORKS = Object.keys(CHAIN_REGISTRY).filter(
101
+ (n) => !COORDINATION_ONLY_NETWORKS.includes(n)
102
+ );
73
103
  var DEFAULT_NETWORK = "robinhood-fork";
74
104
  var _network = DEFAULT_NETWORK;
75
105
  function setNetwork(n) {
@@ -139,7 +169,9 @@ export {
139
169
  robinhoodTestnet,
140
170
  robinhoodFork,
141
171
  DEFAULT_MIN_GAS_RESERVE,
172
+ robinhoodMainnet,
142
173
  CHAIN_REGISTRY,
174
+ COORDINATION_ONLY_NETWORKS,
143
175
  VALID_NETWORKS,
144
176
  DEFAULT_NETWORK,
145
177
  setNetwork,
@@ -154,4 +186,4 @@ export {
154
186
  getExplorerUrl,
155
187
  isTestnet
156
188
  };
157
- //# sourceMappingURL=chunk-ZW2VKCFZ.js.map
189
+ //# sourceMappingURL=chunk-PVTZMRC6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/network.ts"],"sourcesContent":["/**\n * Network state singleton.\n *\n * Called once at CLI startup via the --chain flag's preAction hook.\n * Every other module reads from here — never hardcodes a chain.\n *\n * Deployment targets: the Robinhood Chain mainnet fork (chain 9994663) — the\n * DEFAULT, and what the product ships against (app.sherwood.sh and the docs\n * both point there) — plus the Robinhood L2 testnet (chain 46630), still\n * reachable via `--chain robinhood-testnet`. Sherwood launches on Robinhood\n * first; other chains may be added back later, at which point they re-enter\n * the Network union + CHAIN_REGISTRY below.\n */\n\nimport { type Chain, defineChain, parseEther } from \"viem\";\nimport { getConfigRpcUrl } from \"./config.js\";\n\n// ── Robinhood L2 Testnet (Arbitrum Orbit, chain ID 46630) ──\n\nexport const robinhoodTestnet = defineChain({\n id: 46630,\n name: \"Robinhood Chain Testnet\",\n nativeCurrency: { name: \"Ethereum\", symbol: \"ETH\", decimals: 18 },\n rpcUrls: {\n default: { http: [\"https://rpc.testnet.chain.robinhood.com\"] },\n },\n blockExplorers: {\n default: {\n name: \"Blockscout\",\n url: \"https://explorer.testnet.chain.robinhood.com\",\n },\n },\n testnet: true,\n});\n\n// ── Robinhood Chain mainnet fork (Tenderly Virtual TestNet, chain ID 9994663) ──\n//\n// A fork of Robinhood Chain *mainnet* (USDG stablecoin, official Uniswap v3+v4,\n// Chainlink push feeds, tokenized-stock liquidity). Used for mainnet-faithful\n// e2e + guardian-network simulations. Ephemeral: Tenderly vnets expire — the RPC\n// is overridable via ROBINHOOD_FORK_RPC_URL when a new vnet is minted.\n\n/**\n * The PUBLIC RPC endpoint of vnet `a3fb16`\n * (id 6ad5961e-fbca-452f-939f-ca9a8c020933) — safe to bundle and to hand to\n * users: it serves reads and accepts broadcast txs, but exposes no\n * state-cheat surface.\n *\n * Admin/cheat calls (`tenderly_setBalance`, `tenderly_setStorageAt`,\n * `evm_increaseTime`, …) need that vnet's *admin* RPC, which is private —\n * pull it from the Tenderly dashboard and pass it via ROBINHOOD_FORK_RPC_URL.\n *\n * Two vnets answer to chain id 9994663; block height is the tell — the live\n * one is past block 21,170,000, the stale one forked at ~4.4M.\n */\nconst ROBINHOOD_FORK_PUBLIC_RPC =\n \"https://virtual.robinhood-chain.eu.rpc.tenderly.co/moonwell/wormhole-bridge/231af1\";\n\n/**\n * Tenderly scopes a vnet explorer under its vnet id — the bare\n * `dashboard.tenderly.co` host resolves to the user's own dashboard, not to\n * this fork's blocks, so tx/address links must carry the full prefix.\n */\nconst ROBINHOOD_FORK_EXPLORER =\n \"https://dashboard.tenderly.co/explorer/vnet/6ad5961e-fbca-452f-939f-ca9a8c020933\";\n\nexport const robinhoodFork = defineChain({\n id: 9994663,\n name: \"Robinhood Chain (fork)\",\n nativeCurrency: { name: \"Ethereum\", symbol: \"ETH\", decimals: 18 },\n rpcUrls: {\n default: {\n http: [ROBINHOOD_FORK_PUBLIC_RPC],\n },\n },\n blockExplorers: {\n default: {\n name: \"Tenderly\",\n url: ROBINHOOD_FORK_EXPLORER,\n },\n },\n testnet: true,\n});\n\n// ── Types ──\n\nexport type Network = \"robinhood-testnet\" | \"robinhood-fork\" | \"robinhood-mainnet\";\n\nexport interface ChainConfig {\n chain: Chain;\n /**\n * Ordered list of public RPC URLs tried in sequence when no user RPC is set.\n * First entry is the primary; the rest are fallbacks used transparently via\n * viem's `fallback` transport on rate-limit / network errors.\n */\n rpcFallbacks: string[];\n /** Legacy env var for backwards compat (optional per chain) */\n rpcEnvVar?: string;\n explorerHost: string;\n /**\n * Full URL prefix for explorer links, when the explorer is not rooted at\n * `https://${explorerHost}` — a Tenderly vnet scopes its blocks under\n * `/explorer/vnet/<id>`. Defaults to `https://${explorerHost}`.\n */\n explorerBaseUrl?: string;\n /** EAS GraphQL endpoint — null if EAS is not available on this chain */\n easGraphqlUrl: string | null;\n /** EAS scan host — null if unavailable */\n easScanHost: string | null;\n xmtpEnv: \"production\" | \"dev\";\n isTestnet: boolean;\n /**\n * ETH held back for gas in the native-ETH-wrap deposit path — the floor and\n * fallback for the live per-tx cushion estimate (`estimateGasCushion` in\n * client.ts), NOT a fixed requirement. High-fee chains (Base) want a\n * Base-scale reserve; near-zero-fee chains (Robinhood testnet, ~14-wei base\n * fee) want a tiny one so a valid deposit isn't rejected client-side. Unset →\n * `DEFAULT_MIN_GAS_RESERVE`.\n */\n minGasReserve?: bigint;\n}\n\n/**\n * Base-scale default reserve, used when a chain omits `minGasReserve`. Matches\n * the old hardcoded 0.0005-ETH cushion so higher-fee chains keep prior behavior.\n */\nexport const DEFAULT_MIN_GAS_RESERVE = parseEther(\"0.0005\");\n\n/**\n * Robinhood Chain mainnet (4663) — the EAS **coordination chain**.\n *\n * Sherwood core is NOT deployed here. This entry exists so attestations have a\n * single permanent home regardless of which chain a fund lives on, and it is\n * deliberately excluded from VALID_NETWORKS so it can never be picked as one.\n *\n * See docs/superpowers/specs/2026-08-26-eas-robinhood-mainnet-design.md §5.1.\n */\nexport const robinhoodMainnet = defineChain({\n id: 4663,\n name: \"Robinhood Chain\",\n nativeCurrency: {name: \"Ethereum\", symbol: \"ETH\", decimals: 18},\n rpcUrls: {default: {http: [\"https://rpc.mainnet.chain.robinhood.com\"]}},\n blockExplorers: {\n default: {name: \"Blockscout\", url: \"https://robinhoodchain.blockscout.com\"},\n },\n});\n\n// ── Chain Registry ──\n\nexport const CHAIN_REGISTRY: Record<Network, ChainConfig> = {\n \"robinhood-testnet\": {\n chain: robinhoodTestnet,\n rpcFallbacks: [\"https://rpc.testnet.chain.robinhood.com\"],\n rpcEnvVar: \"ROBINHOOD_TESTNET_RPC_URL\",\n explorerHost: \"explorer.testnet.chain.robinhood.com\",\n easGraphqlUrl: null,\n easScanHost: null,\n xmtpEnv: \"production\",\n isTestnet: true,\n // ~14-wei base fee: a full wrap+approve+deposit sequence costs a few\n // million wei (~1e-11 ETH). 0.00002 ETH is a generous floor that still\n // leaves a 0.00058-ETH wallet room to wrap 0.0005 and deposit.\n minGasReserve: parseEther(\"0.00002\"),\n },\n \"robinhood-fork\": {\n chain: robinhoodFork,\n rpcFallbacks: [ROBINHOOD_FORK_PUBLIC_RPC],\n rpcEnvVar: \"ROBINHOOD_FORK_RPC_URL\",\n explorerHost: \"dashboard.tenderly.co\",\n explorerBaseUrl: ROBINHOOD_FORK_EXPLORER,\n easGraphqlUrl: null,\n easScanHost: null,\n xmtpEnv: \"production\",\n isTestnet: true,\n // Robinhood-mainnet-fork gas (~0.17 gwei) is higher than testnet's ~14 wei —\n // keep a modest reserve so the ETH-wrap deposit path isn't rejected.\n minGasReserve: parseEther(\"0.0005\"),\n },\n \"robinhood-mainnet\": {\n chain: robinhoodMainnet,\n // Public endpoint kept only as a last-resort fallback: it sits behind\n // Cloudflare and challenges non-browser clients under load, and Robinhood's\n // own docs call it unsuitable for production. ROBINHOOD_RPC_URL (Alchemy)\n // is what actually gets used.\n rpcFallbacks: [\"https://rpc.mainnet.chain.robinhood.com\"],\n rpcEnvVar: \"ROBINHOOD_RPC_URL\",\n explorerHost: \"robinhoodchain.blockscout.com\",\n // Self-hosted EAS indexer — easscan does not index 4663. Serves GraphQL at\n // the root, so no /graphql suffix.\n easGraphqlUrl: \"https://sherwood-indexer-production.up.railway.app\",\n // No easscan-equivalent UI for this chain yet; attestation permalinks are\n // pending the app's own /attestation route (spec §10.2).\n easScanHost: null,\n xmtpEnv: \"production\",\n isTestnet: false,\n },\n};\n\n/**\n * Networks a user may select with `--chain`.\n *\n * NOT simply the CHAIN_REGISTRY keys: `robinhood-mainnet` is a coordination\n * chain for attestations only (no Sherwood contracts deployed there), so\n * selecting it as a fund's home chain would resolve every address to zero.\n */\nexport const COORDINATION_ONLY_NETWORKS: readonly Network[] = [\"robinhood-mainnet\"];\n\nexport const VALID_NETWORKS = (Object.keys(CHAIN_REGISTRY) as Network[]).filter(\n (n) => !COORDINATION_ONLY_NETWORKS.includes(n),\n);\n\n/**\n * The network a bare CLI invocation (no `--chain`) targets.\n *\n * The fork is where the product actually lives: app.sherwood.sh defaults to\n * chain 9994663, the docs banner declares it, and the redeployed 46630 factory\n * holds zero syndicates — so a default of `robinhood-testnet` landed a fresh\n * agent on an empty chain. `--chain robinhood-testnet` still selects 46630.\n */\nexport const DEFAULT_NETWORK: Network = \"robinhood-fork\";\n\n// ── Singleton state ──\n\nlet _network: Network = DEFAULT_NETWORK;\n\nexport function setNetwork(n: Network) {\n const config = CHAIN_REGISTRY[n];\n if (!config) {\n throw new Error(\n `Unknown network: ${n}. Valid: ${VALID_NETWORKS.join(\", \")}`,\n );\n }\n _network = n;\n}\n\nexport function getNetwork(): Network {\n return _network;\n}\n\n/**\n * Run `fn` with the network singleton temporarily switched to `network`,\n * restoring the previous network in `finally`.\n *\n * The viem clients (`getPublicClient`, `getWalletClient`) auto-invalidate on\n * chain change, so callers get fresh per-chain RPC + chain context inside the\n * block. Used for cross-chain fund resolution.\n */\nexport async function withNetwork<T>(\n network: Network,\n fn: () => Promise<T>,\n): Promise<T> {\n const previous = _network;\n _network = network;\n try {\n return await fn();\n } finally {\n _network = previous;\n }\n}\n\nexport function getChainConfig(): ChainConfig {\n return CHAIN_REGISTRY[_network];\n}\n\n/**\n * Per-chain ETH gas reserve floor/fallback for the ETH-wrap deposit path.\n * Falls back to the Base-scale default when the active chain doesn't set one.\n */\nexport function getMinGasReserve(): bigint {\n return CHAIN_REGISTRY[_network].minGasReserve ?? DEFAULT_MIN_GAS_RESERVE;\n}\n\n/**\n * Dev/CI-only chain-id override (`SIM_CHAIN_ID`) for custom-chainId vnet forks.\n * viem asserts the node's chainId matches the chain definition on every write,\n * so signing for such a fork requires the id to be swapped at the source.\n *\n * Interlock: honored ONLY while `SIM_ADDRESSES_FILE` is also set (the vnet\n * e2e harness's address override). A bare SIM_CHAIN_ID does nothing, so this\n * can never redirect signing on a real network run.\n */\nexport function applyChainIdOverride(chain: Chain): Chain {\n const raw = process.env.SIM_CHAIN_ID;\n if (!raw || !process.env.SIM_ADDRESSES_FILE) return chain;\n const id = Number(raw);\n if (!Number.isInteger(id) || id <= 0) return chain;\n return { ...chain, id };\n}\n\nexport function getChain(): Chain {\n return applyChainIdOverride(CHAIN_REGISTRY[_network].chain);\n}\n\n/**\n * Ordered list of RPC URLs to try, deduped:\n * 1. User config (~/.sherwood/config.json) — honored first\n * 2. The active chain's legacy env var (`rpcEnvVar` — ROBINHOOD_FORK_RPC_URL\n * on the default fork, ROBINHOOD_TESTNET_RPC_URL on 46630)\n * 3. Public fallback list from CHAIN_REGISTRY\n *\n * Fed to viem's `fallback()` transport so rate-limited RPCs transparently\n * advance to the next entry. See issue #182.\n */\nexport function getRpcUrls(): string[] {\n const cfg = CHAIN_REGISTRY[_network];\n const urls: string[] = [];\n\n const fromConfig = getConfigRpcUrl(_network);\n if (fromConfig) urls.push(fromConfig);\n\n if (cfg.rpcEnvVar && process.env[cfg.rpcEnvVar]) {\n urls.push(process.env[cfg.rpcEnvVar]!);\n }\n\n // Vnet harness (SIM_CHAIN_ID + SIM_ADDRESSES_FILE interlock): the fork RPC\n // is the ONLY valid endpoint — the public fallbacks point at the real\n // network. viem's fallback() otherwise rotates there on a vnet hiccup and\n // silently estimates gas/fees against MAINNET state (live finding: an\n // approve estimated at 21.9k gas on mainnet — no code at that address —\n // reverted out-of-gas on the vnet where it needs ~46k). A vnet call must\n // fail loudly, never retarget the real chain.\n if (process.env.SIM_CHAIN_ID && process.env.SIM_ADDRESSES_FILE && urls.length > 0) {\n return Array.from(new Set(urls));\n }\n\n urls.push(...cfg.rpcFallbacks);\n\n return Array.from(new Set(urls));\n}\n\nexport function getRpcUrl(): string {\n return getRpcUrls()[0];\n}\n\nexport function getExplorerUrl(txHash: string): string {\n const cfg = CHAIN_REGISTRY[_network];\n const base = cfg.explorerBaseUrl ?? `https://${cfg.explorerHost}`;\n return `${base}/tx/${txHash}`;\n}\n\nexport function isTestnet(): boolean {\n return CHAIN_REGISTRY[_network].isTestnet;\n}\n"],"mappings":";;;;;AAcA,SAAqB,aAAa,kBAAkB;AAK7C,IAAM,mBAAmB,YAAY;AAAA,EAC1C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,EAAE,MAAM,YAAY,QAAQ,OAAO,UAAU,GAAG;AAAA,EAChE,SAAS;AAAA,IACP,SAAS,EAAE,MAAM,CAAC,yCAAyC,EAAE;AAAA,EAC/D;AAAA,EACA,gBAAgB;AAAA,IACd,SAAS;AAAA,MACP,MAAM;AAAA,MACN,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EACA,SAAS;AACX,CAAC;AAsBD,IAAM,4BACJ;AAOF,IAAM,0BACJ;AAEK,IAAM,gBAAgB,YAAY;AAAA,EACvC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,EAAE,MAAM,YAAY,QAAQ,OAAO,UAAU,GAAG;AAAA,EAChE,SAAS;AAAA,IACP,SAAS;AAAA,MACP,MAAM,CAAC,yBAAyB;AAAA,IAClC;AAAA,EACF;AAAA,EACA,gBAAgB;AAAA,IACd,SAAS;AAAA,MACP,MAAM;AAAA,MACN,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EACA,SAAS;AACX,CAAC;AA4CM,IAAM,0BAA0B,WAAW,QAAQ;AAWnD,IAAM,mBAAmB,YAAY;AAAA,EAC1C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,EAAC,MAAM,YAAY,QAAQ,OAAO,UAAU,GAAE;AAAA,EAC9D,SAAS,EAAC,SAAS,EAAC,MAAM,CAAC,yCAAyC,EAAC,EAAC;AAAA,EACtE,gBAAgB;AAAA,IACd,SAAS,EAAC,MAAM,cAAc,KAAK,wCAAuC;AAAA,EAC5E;AACF,CAAC;AAIM,IAAM,iBAA+C;AAAA,EAC1D,qBAAqB;AAAA,IACnB,OAAO;AAAA,IACP,cAAc,CAAC,yCAAyC;AAAA,IACxD,WAAW;AAAA,IACX,cAAc;AAAA,IACd,eAAe;AAAA,IACf,aAAa;AAAA,IACb,SAAS;AAAA,IACT,WAAW;AAAA;AAAA;AAAA;AAAA,IAIX,eAAe,WAAW,SAAS;AAAA,EACrC;AAAA,EACA,kBAAkB;AAAA,IAChB,OAAO;AAAA,IACP,cAAc,CAAC,yBAAyB;AAAA,IACxC,WAAW;AAAA,IACX,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,SAAS;AAAA,IACT,WAAW;AAAA;AAAA;AAAA,IAGX,eAAe,WAAW,QAAQ;AAAA,EACpC;AAAA,EACA,qBAAqB;AAAA,IACnB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKP,cAAc,CAAC,yCAAyC;AAAA,IACxD,WAAW;AAAA,IACX,cAAc;AAAA;AAAA;AAAA,IAGd,eAAe;AAAA;AAAA;AAAA,IAGf,aAAa;AAAA,IACb,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AACF;AASO,IAAM,6BAAiD,CAAC,mBAAmB;AAE3E,IAAM,iBAAkB,OAAO,KAAK,cAAc,EAAgB;AAAA,EACvE,CAAC,MAAM,CAAC,2BAA2B,SAAS,CAAC;AAC/C;AAUO,IAAM,kBAA2B;AAIxC,IAAI,WAAoB;AAEjB,SAAS,WAAW,GAAY;AACrC,QAAM,SAAS,eAAe,CAAC;AAC/B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,oBAAoB,CAAC,YAAY,eAAe,KAAK,IAAI,CAAC;AAAA,IAC5D;AAAA,EACF;AACA,aAAW;AACb;AAEO,SAAS,aAAsB;AACpC,SAAO;AACT;AAUA,eAAsB,YACpB,SACA,IACY;AACZ,QAAM,WAAW;AACjB,aAAW;AACX,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,UAAE;AACA,eAAW;AAAA,EACb;AACF;AAEO,SAAS,iBAA8B;AAC5C,SAAO,eAAe,QAAQ;AAChC;AAMO,SAAS,mBAA2B;AACzC,SAAO,eAAe,QAAQ,EAAE,iBAAiB;AACnD;AAWO,SAAS,qBAAqB,OAAqB;AACxD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,mBAAoB,QAAO;AACpD,QAAM,KAAK,OAAO,GAAG;AACrB,MAAI,CAAC,OAAO,UAAU,EAAE,KAAK,MAAM,EAAG,QAAO;AAC7C,SAAO,EAAE,GAAG,OAAO,GAAG;AACxB;AAEO,SAAS,WAAkB;AAChC,SAAO,qBAAqB,eAAe,QAAQ,EAAE,KAAK;AAC5D;AAYO,SAAS,aAAuB;AACrC,QAAM,MAAM,eAAe,QAAQ;AACnC,QAAM,OAAiB,CAAC;AAExB,QAAM,aAAa,gBAAgB,QAAQ;AAC3C,MAAI,WAAY,MAAK,KAAK,UAAU;AAEpC,MAAI,IAAI,aAAa,QAAQ,IAAI,IAAI,SAAS,GAAG;AAC/C,SAAK,KAAK,QAAQ,IAAI,IAAI,SAAS,CAAE;AAAA,EACvC;AASA,MAAI,QAAQ,IAAI,gBAAgB,QAAQ,IAAI,sBAAsB,KAAK,SAAS,GAAG;AACjF,WAAO,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,EACjC;AAEA,OAAK,KAAK,GAAG,IAAI,YAAY;AAE7B,SAAO,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC;AACjC;AAEO,SAAS,YAAoB;AAClC,SAAO,WAAW,EAAE,CAAC;AACvB;AAEO,SAAS,eAAe,QAAwB;AACrD,QAAM,MAAM,eAAe,QAAQ;AACnC,QAAM,OAAO,IAAI,mBAAmB,WAAW,IAAI,YAAY;AAC/D,SAAO,GAAG,IAAI,OAAO,MAAM;AAC7B;AAEO,SAAS,YAAqB;AACnC,SAAO,eAAe,QAAQ,EAAE;AAClC;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  getChain,
3
3
  getMinGasReserve,
4
4
  getRpcUrls
5
- } from "./chunk-ZW2VKCFZ.js";
5
+ } from "./chunk-PVTZMRC6.js";
6
6
  import {
7
7
  requirePrivateKey
8
8
  } from "./chunk-3TZIOAPH.js";
@@ -35,6 +35,8 @@ var CONTRACT_ERRORS_ABI = [
35
35
  { type: "error", name: "ZeroAddress", inputs: [] },
36
36
  { type: "error", name: "CannotRescueAsset", inputs: [] },
37
37
  { type: "error", name: "NotFactory", inputs: [] },
38
+ { type: "error", name: "SandboxFundingExceedsCeiling", inputs: [{ name: "funding", type: "uint256" }, { name: "ceiling", type: "uint256" }] },
39
+ { type: "error", name: "DisallowedBatchCallee", inputs: [{ name: "target", type: "address" }] },
38
40
  // ── SyndicateGovernor ──
39
41
  { type: "error", name: "VaultNotRegistered", inputs: [] },
40
42
  { type: "error", name: "VaultAlreadyRegistered", inputs: [] },
@@ -79,6 +81,8 @@ var CONTRACT_ERRORS_ABI = [
79
81
  { type: "error", name: "NotAuthorized", inputs: [] },
80
82
  { type: "error", name: "InvalidMaxCoProposers", inputs: [] },
81
83
  { type: "error", name: "Reentrancy", inputs: [] },
84
+ { type: "error", name: "SandboxNotAvailable", inputs: [{ name: "vault", type: "address" }] },
85
+ { type: "error", name: "Tier2CallCapExceedsCeiling", inputs: [{ name: "index", type: "uint256" }] },
82
86
  // ── GovernorParameters (timelock) ──
83
87
  { type: "error", name: "ChangeAlreadyPending", inputs: [] },
84
88
  { type: "error", name: "NoChangePending", inputs: [] },
@@ -124,7 +128,11 @@ var CONTRACT_ERRORS_ABI = [
124
128
  { type: "error", name: "InvalidSlippage", inputs: [] },
125
129
  { type: "error", name: "InvalidPriceDecimals", inputs: [] },
126
130
  // ── VeniceInferenceStrategy ──
127
- { type: "error", name: "NoAgent", inputs: [] }
131
+ { type: "error", name: "NoAgent", inputs: [] },
132
+ // ── ExposureLedger ──
133
+ { type: "error", name: "InsufficientApproveCoverage", inputs: [] },
134
+ // ── StakedWood ──
135
+ { type: "error", name: "CoverageStillOpen", inputs: [] }
128
136
  ];
129
137
  var ERROR_MESSAGES = {
130
138
  // Vault — permissions / state
@@ -148,6 +156,8 @@ var ERROR_MESSAGES = {
148
156
  TransferFailed: "ERC-20 token transfer failed. Check token balance and approvals.",
149
157
  ZeroAddress: "A required address parameter is the zero address.",
150
158
  CannotRescueAsset: "Cannot rescue the vault's underlying asset \u2014 use redeem() instead.",
159
+ SandboxFundingExceedsCeiling: "Sandbox funding exceeds the live tier-2 per-call capital ceiling. Reduce funding to fit under the ceiling.",
160
+ DisallowedBatchCallee: "A governor-batch call targeted an address that is neither the vault's underlying asset nor a TierRegistry-allowlisted adapter.",
151
161
  // Governor — proposal lifecycle
152
162
  VaultNotRegistered: "This vault is not registered with the governor. The factory registers vaults automatically on creation.",
153
163
  VaultAlreadyRegistered: "This vault is already registered with the governor.",
@@ -194,6 +204,8 @@ var ERROR_MESSAGES = {
194
204
  NotAuthorized: "You are not authorized to perform this action.",
195
205
  InvalidMaxCoProposers: "Max co-proposers value is outside allowed bounds.",
196
206
  Reentrancy: "Reentrancy detected \u2014 this call cannot be made during another call.",
207
+ SandboxNotAvailable: "This vault has no CallSandbox implementation, so sandbox proposals cannot run. Use a vault created after sandbox wiring, or propose without a sandbox.",
208
+ Tier2CallCapExceedsCeiling: "A tier-2 call cap exceeds the vault's tier-2 per-call ceiling (totalAssets \xD7 tier2CallCapBps / 10_000). Lower that call's cap.",
197
209
  // Governor — parameter timelock
198
210
  ChangeAlreadyPending: "A parameter change is already pending for this parameter. Finalize or cancel it first.",
199
211
  NoChangePending: "No parameter change is pending for this key.",
@@ -239,7 +251,11 @@ var ERROR_MESSAGES = {
239
251
  InvalidSlippage: "maxSlippageBps must be > 0 and < 10000.",
240
252
  InvalidPriceDecimals: "priceDecimals entry exceeds 36. Use 8 for tokenized-stock feeds, 18 for crypto pairs.",
241
253
  // Strategy — Venice
242
- NoAgent: "No agent is registered on the vault. Register an agent first."
254
+ NoAgent: "No agent is registered on the vault. Register an agent first.",
255
+ // ExposureLedger
256
+ InsufficientApproveCoverage: "No bonded guardian coverage was raised for this proposal. Execution requires at least one identified, bonded approver with slashable coverage.",
257
+ // StakedWood
258
+ CoverageStillOpen: "Cannot release the unstake yet \u2014 this guardian still backs a proposal that could be challenged. The unstake request stays open; retry after coverage closes."
243
259
  };
244
260
  function decodeContractError(data) {
245
261
  try {
@@ -524,4 +540,4 @@ export {
524
540
  writeContractWithRetry,
525
541
  waitForReceipt
526
542
  };
527
- //# sourceMappingURL=chunk-5BYJ3TGZ.js.map
543
+ //# sourceMappingURL=chunk-QRMZRAMG.js.map