@sherwoodagent/cli 0.65.2 → 0.72.3

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 (93) hide show
  1. package/dist/{abis-4HWN2E3H.js → abis-KP7M4IPM.js} +4 -2
  2. package/dist/addresses-YDPUPGWQ.js +45 -0
  3. package/dist/{agent-AETROOEW.js → agent-6IRZUPFT.js} +10 -10
  4. package/dist/{agent-AETROOEW.js.map → agent-6IRZUPFT.js.map} +1 -1
  5. package/dist/{chat-JSSTSE4K.js → chat-G5M3OK6M.js} +37 -36
  6. package/dist/chat-G5M3OK6M.js.map +1 -0
  7. package/dist/{chunk-IL43IMOC.js → chunk-3NMJ4LUL.js} +23 -18
  8. package/dist/chunk-3NMJ4LUL.js.map +1 -0
  9. package/dist/{chunk-UBAX22KA.js → chunk-52U76TRE.js} +4 -4
  10. package/dist/chunk-52U76TRE.js.map +1 -0
  11. package/dist/{chunk-W2ZUPR3W.js → chunk-5DWB5Y7G.js} +9 -9
  12. package/dist/chunk-5DWB5Y7G.js.map +1 -0
  13. package/dist/{chunk-YZHZSBES.js → chunk-BQMOE4FJ.js} +144 -37
  14. package/dist/chunk-BQMOE4FJ.js.map +1 -0
  15. package/dist/{chunk-VYF2TXPX.js → chunk-D4SPHYXA.js} +6 -6
  16. package/dist/{chunk-UCQEEF3Z.js → chunk-FOAX5PGR.js} +6 -6
  17. package/dist/chunk-FOAX5PGR.js.map +1 -0
  18. package/dist/chunk-FOS47623.js +127 -0
  19. package/dist/chunk-FOS47623.js.map +1 -0
  20. package/dist/{chunk-2W4KGGPU.js → chunk-GSC7PVEO.js} +27 -27
  21. package/dist/chunk-GSC7PVEO.js.map +1 -0
  22. package/dist/chunk-HYUUR6SC.js +111 -0
  23. package/dist/chunk-HYUUR6SC.js.map +1 -0
  24. package/dist/{chunk-RO26TXXE.js → chunk-NWY2U753.js} +2 -2
  25. package/dist/chunk-RAITRCM7.js +308 -0
  26. package/dist/chunk-RAITRCM7.js.map +1 -0
  27. package/dist/{chunk-A6TOJEUD.js → chunk-S6XA7PK4.js} +124 -128
  28. package/dist/chunk-S6XA7PK4.js.map +1 -0
  29. package/dist/{chunk-O4G3SBCI.js → chunk-UUHWD7Y3.js} +109 -129
  30. package/dist/chunk-UUHWD7Y3.js.map +1 -0
  31. package/dist/{chunk-YJSCIQUZ.js → chunk-WECQ6NVS.js} +6 -6
  32. package/dist/chunk-WECQ6NVS.js.map +1 -0
  33. package/dist/{chunk-RHRLYVKM.js → chunk-ZYXFKVYF.js} +25 -12
  34. package/dist/chunk-ZYXFKVYF.js.map +1 -0
  35. package/dist/{client-KBBFAMEI.js → client-QN6SA76M.js} +4 -4
  36. package/dist/{config-REASKDIK.js → config-FAQO37S7.js} +10 -10
  37. package/dist/{cron-EOULYO3R.js → cron-QMLRWDJG.js} +19 -17
  38. package/dist/cron-QMLRWDJG.js.map +1 -0
  39. package/dist/{eas-K4ZBACYN.js → eas-HM2AUOEY.js} +7 -7
  40. package/dist/{governor-2CDSMSG7.js → governor-RRBTDGDX.js} +17 -13
  41. package/dist/index.js +2026 -1222
  42. package/dist/index.js.map +1 -1
  43. package/dist/{ipfs-FAYWUCS2.js → ipfs-VGWTUORE.js} +2 -2
  44. package/dist/{network-3ZU7UBDU.js → network-L6JKNYOJ.js} +7 -7
  45. package/dist/{price-LCTECRQP.js → price-AJ4JMHCT.js} +6 -6
  46. package/dist/{research-FUQOQWKK.js → research-6VEEPEPP.js} +22 -22
  47. package/dist/research-6VEEPEPP.js.map +1 -0
  48. package/dist/research-7Z6SFPPX.js +14 -0
  49. package/dist/{session-FJB2RV2X.js → session-PURMITWE.js} +28 -28
  50. package/dist/session-PURMITWE.js.map +1 -0
  51. package/dist/{session-XAW5UIXP.js → session-XBQY7SBJ.js} +2 -2
  52. package/dist/{trade-U7H5IFH6.js → trade-YTGLGCRQ.js} +34 -34
  53. package/dist/trade-YTGLGCRQ.js.map +1 -0
  54. package/dist/vault-NIPA5JV4.js +54 -0
  55. package/dist/vault-NIPA5JV4.js.map +1 -0
  56. package/dist/{xmtp-6NJ4PW6L.js → xmtp-M32JSE4I.js} +13 -12
  57. package/dist/xmtp-M32JSE4I.js.map +1 -0
  58. package/package.json +4 -3
  59. package/dist/addresses-J2LMGUCT.js +0 -35
  60. package/dist/chat-JSSTSE4K.js.map +0 -1
  61. package/dist/chunk-2LRC7I33.js +0 -186
  62. package/dist/chunk-2LRC7I33.js.map +0 -1
  63. package/dist/chunk-2W4KGGPU.js.map +0 -1
  64. package/dist/chunk-A6TOJEUD.js.map +0 -1
  65. package/dist/chunk-GWMTAW6A.js +0 -494
  66. package/dist/chunk-GWMTAW6A.js.map +0 -1
  67. package/dist/chunk-IL43IMOC.js.map +0 -1
  68. package/dist/chunk-O4G3SBCI.js.map +0 -1
  69. package/dist/chunk-RHRLYVKM.js.map +0 -1
  70. package/dist/chunk-UBAX22KA.js.map +0 -1
  71. package/dist/chunk-UCQEEF3Z.js.map +0 -1
  72. package/dist/chunk-W2ZUPR3W.js.map +0 -1
  73. package/dist/chunk-YJSCIQUZ.js.map +0 -1
  74. package/dist/chunk-YZHZSBES.js.map +0 -1
  75. package/dist/cron-EOULYO3R.js.map +0 -1
  76. package/dist/research-FUQOQWKK.js.map +0 -1
  77. package/dist/research-XEXTHDT3.js +0 -14
  78. package/dist/session-FJB2RV2X.js.map +0 -1
  79. package/dist/trade-U7H5IFH6.js.map +0 -1
  80. package/dist/xmtp-6NJ4PW6L.js.map +0 -1
  81. /package/dist/{abis-4HWN2E3H.js.map → abis-KP7M4IPM.js.map} +0 -0
  82. /package/dist/{addresses-J2LMGUCT.js.map → addresses-YDPUPGWQ.js.map} +0 -0
  83. /package/dist/{chunk-VYF2TXPX.js.map → chunk-D4SPHYXA.js.map} +0 -0
  84. /package/dist/{chunk-RO26TXXE.js.map → chunk-NWY2U753.js.map} +0 -0
  85. /package/dist/{client-KBBFAMEI.js.map → client-QN6SA76M.js.map} +0 -0
  86. /package/dist/{config-REASKDIK.js.map → config-FAQO37S7.js.map} +0 -0
  87. /package/dist/{eas-K4ZBACYN.js.map → eas-HM2AUOEY.js.map} +0 -0
  88. /package/dist/{governor-2CDSMSG7.js.map → governor-RRBTDGDX.js.map} +0 -0
  89. /package/dist/{ipfs-FAYWUCS2.js.map → ipfs-VGWTUORE.js.map} +0 -0
  90. /package/dist/{network-3ZU7UBDU.js.map → network-L6JKNYOJ.js.map} +0 -0
  91. /package/dist/{price-LCTECRQP.js.map → price-AJ4JMHCT.js.map} +0 -0
  92. /package/dist/{research-XEXTHDT3.js.map → research-7Z6SFPPX.js.map} +0 -0
  93. /package/dist/{session-XAW5UIXP.js.map → session-XBQY7SBJ.js.map} +0 -0
@@ -1,30 +1,23 @@
1
1
  import {
2
- ENS,
3
- SHERWOOD,
4
2
  TOKENS
5
- } from "./chunk-GWMTAW6A.js";
3
+ } from "./chunk-RAITRCM7.js";
6
4
  import {
7
5
  getAccount,
8
6
  getPublicClient,
9
7
  getWalletClient,
10
8
  waitForReceipt,
11
9
  writeContractWithRetry
12
- } from "./chunk-YJSCIQUZ.js";
10
+ } from "./chunk-WECQ6NVS.js";
13
11
  import {
14
- CHAIN_REGISTRY,
15
- VALID_NETWORKS,
16
- getChain,
17
- withNetwork
18
- } from "./chunk-2LRC7I33.js";
12
+ getChain
13
+ } from "./chunk-HYUUR6SC.js";
19
14
  import {
20
15
  getChainContracts
21
- } from "./chunk-2W4KGGPU.js";
16
+ } from "./chunk-GSC7PVEO.js";
22
17
  import {
23
18
  ERC20_ABI,
24
- L2_REGISTRY_ABI,
25
- SYNDICATE_FACTORY_ABI,
26
19
  SYNDICATE_VAULT_ABI
27
- } from "./chunk-YZHZSBES.js";
20
+ } from "./chunk-BQMOE4FJ.js";
28
21
 
29
22
  // src/lib/vault.ts
30
23
  import { formatUnits, parseEther } from "viem";
@@ -66,6 +59,26 @@ async function getVaultOwner(vault) {
66
59
  functionName: "owner"
67
60
  });
68
61
  }
62
+ async function getAgentFeeBps(vault) {
63
+ const client = getPublicClient();
64
+ return client.readContract({
65
+ address: vault ?? getVaultAddress(),
66
+ abi: SYNDICATE_VAULT_ABI,
67
+ functionName: "agentFeeBps"
68
+ });
69
+ }
70
+ async function setAgentFeeBps(bps) {
71
+ const hash = await writeContractWithRetry({
72
+ account: getAccount(),
73
+ chain: getChain(),
74
+ address: getVaultAddress(),
75
+ abi: SYNDICATE_VAULT_ABI,
76
+ functionName: "setAgentFeeBps",
77
+ args: [bps]
78
+ });
79
+ await waitForReceipt(hash);
80
+ return hash;
81
+ }
69
82
  var WETH_ABI = [
70
83
  {
71
84
  name: "deposit",
@@ -118,9 +131,18 @@ async function preflightDeposit(amount) {
118
131
  functionName: "redemptionsLocked"
119
132
  });
120
133
  if (locked) {
121
- throw new Error(
122
- "Deposits are locked while a strategy is executing. Wait for the proposal to be settled."
123
- );
134
+ const cap = await client.readContract({
135
+ address: vaultAddress,
136
+ abi: SYNDICATE_VAULT_ABI,
137
+ functionName: "maxDeposit",
138
+ args: [account.address]
139
+ });
140
+ if (cap < amount) {
141
+ throw new Error(
142
+ cap === 0n ? "Deposits are locked while a strategy is executing (Lane A unavailable for this strategy). Queue an async deposit instead:\n sherwood queue request-deposit --vault <vault> --amount <usdc>" : `Deposit exceeds the Lane A instant cap while a strategy is executing (maxDeposit ${cap.toString()} raw asset units, requested ${amount.toString()}). Reduce the amount or queue it:
143
+ sherwood queue request-deposit --vault <vault> --amount <usdc>`
144
+ );
145
+ }
124
146
  }
125
147
  const openDeposits = await client.readContract({
126
148
  address: vaultAddress,
@@ -136,7 +158,7 @@ async function preflightDeposit(amount) {
136
158
  });
137
159
  if (!approved) {
138
160
  throw new Error(
139
- "Your address is not an approved depositor. Ask the vault creator to approve you:\n sherwood syndicate approve-depositor --depositor " + account.address
161
+ "Your address is not an approved depositor. Ask the vault creator to approve you:\n sherwood fund approve-depositor --depositor " + account.address
140
162
  );
141
163
  }
142
164
  }
@@ -174,7 +196,10 @@ async function depositWithEthWrap(amount) {
174
196
  account,
175
197
  chain: getChain()
176
198
  });
177
- await client.waitForTransactionReceipt({ hash: wrapHash });
199
+ const wrapReceipt = await client.waitForTransactionReceipt({ hash: wrapHash });
200
+ if (wrapReceipt.status !== "success") {
201
+ throw new Error(`ETH->WETH wrap reverted on-chain (tx ${wrapHash}).`);
202
+ }
178
203
  }
179
204
  return deposit(amount);
180
205
  }
@@ -198,8 +223,17 @@ async function deposit(amount) {
198
223
  functionName: "approve",
199
224
  args: [vaultAddress, amount]
200
225
  });
201
- await waitForReceipt(approveHash);
226
+ const approveReceipt = await waitForReceipt(approveHash);
227
+ if (approveReceipt.status !== "success") {
228
+ throw new Error(`Asset approve reverted on-chain (tx ${approveHash}).`);
229
+ }
202
230
  }
231
+ const sharesBefore = await client.readContract({
232
+ address: vaultAddress,
233
+ abi: SYNDICATE_VAULT_ABI,
234
+ functionName: "balanceOf",
235
+ args: [account.address]
236
+ });
203
237
  const depositHash = await writeContractWithRetry({
204
238
  account,
205
239
  chain: getChain(),
@@ -208,7 +242,23 @@ async function deposit(amount) {
208
242
  functionName: "deposit",
209
243
  args: [amount, account.address]
210
244
  });
211
- await waitForReceipt(depositHash);
245
+ const receipt = await waitForReceipt(depositHash);
246
+ if (receipt.status !== "success") {
247
+ throw new Error(
248
+ `Deposit transaction reverted on-chain (tx ${depositHash}). No shares were minted.`
249
+ );
250
+ }
251
+ const sharesAfter = await client.readContract({
252
+ address: vaultAddress,
253
+ abi: SYNDICATE_VAULT_ABI,
254
+ functionName: "balanceOf",
255
+ args: [account.address]
256
+ });
257
+ if (sharesAfter <= sharesBefore) {
258
+ throw new Error(
259
+ `Deposit confirmed on-chain (tx ${depositHash}) but minted 0 shares (balance ${sharesBefore} -> ${sharesAfter}). Aborting to avoid reporting a phantom deposit.`
260
+ );
261
+ }
212
262
  return depositHash;
213
263
  }
214
264
  async function preflightRedeem(shares) {
@@ -243,9 +293,18 @@ async function preflightRedeem(shares) {
243
293
  functionName: "redemptionsLocked"
244
294
  });
245
295
  if (locked) {
246
- throw new Error(
247
- "Redemptions are locked while a strategy is executing. Wait for the proposal to be settled."
248
- );
296
+ const cap = await client.readContract({
297
+ address: vaultAddress,
298
+ abi: SYNDICATE_VAULT_ABI,
299
+ functionName: "maxRedeem",
300
+ args: [account.address]
301
+ });
302
+ if (cap < shares) {
303
+ throw new Error(
304
+ cap === 0n ? "Redemptions are locked while a strategy is executing (Lane A unavailable for this strategy). Queue an async redeem instead:\n sherwood queue request-redeem --vault <vault> --shares <raw-shares>" : `Redeem exceeds the Lane A instant cap while a strategy is executing (maxRedeem ${cap.toString()} raw shares, requested ${shares.toString()}). Reduce the share amount or queue it:
305
+ sherwood queue request-redeem --vault <vault> --shares <raw-shares>`
306
+ );
307
+ }
249
308
  }
250
309
  const ethBalance = await client.getBalance({ address: account.address });
251
310
  const minGas = parseEther("0.0005");
@@ -329,6 +388,18 @@ async function removeDepositor(depositor) {
329
388
  await waitForReceipt(hash);
330
389
  return hash;
331
390
  }
391
+ async function approveDepositors(depositors) {
392
+ const hash = await writeContractWithRetry({
393
+ account: getAccount(),
394
+ chain: getChain(),
395
+ address: getVaultAddress(),
396
+ abi: SYNDICATE_VAULT_ABI,
397
+ functionName: "approveDepositors",
398
+ args: [depositors]
399
+ });
400
+ await waitForReceipt(hash);
401
+ return hash;
402
+ }
332
403
  async function isAgent(agentAddress) {
333
404
  const client = getPublicClient();
334
405
  return client.readContract({
@@ -338,6 +409,15 @@ async function isAgent(agentAddress) {
338
409
  args: [agentAddress]
339
410
  });
340
411
  }
412
+ async function isApprovedDepositor(depositor) {
413
+ const client = getPublicClient();
414
+ return client.readContract({
415
+ address: getVaultAddress(),
416
+ abi: SYNDICATE_VAULT_ABI,
417
+ functionName: "isApprovedDepositor",
418
+ args: [depositor]
419
+ });
420
+ }
341
421
  async function getBalance(address) {
342
422
  const client = getPublicClient();
343
423
  const vaultAddress = getVaultAddress();
@@ -419,111 +499,14 @@ async function getVaultInfo() {
419
499
  };
420
500
  }
421
501
 
422
- // src/lib/ens.ts
423
- import { encodeFunctionData } from "viem";
424
- import { namehash } from "viem/ens";
425
- var ENS_DOMAIN = "sherwoodagent.eth";
426
- function getFactoryAddress() {
427
- return SHERWOOD().FACTORY;
428
- }
429
- async function resolveSyndicate(subdomain) {
430
- const client = getPublicClient();
431
- const factory = getFactoryAddress();
432
- const syndicateId = await client.readContract({
433
- address: factory,
434
- abi: SYNDICATE_FACTORY_ABI,
435
- functionName: "subdomainToSyndicate",
436
- args: [subdomain]
437
- });
438
- if (syndicateId === 0n) {
439
- throw new Error(`Syndicate "${subdomain}" not found`);
440
- }
441
- const result = await client.readContract({
442
- address: factory,
443
- abi: SYNDICATE_FACTORY_ABI,
444
- functionName: "syndicates",
445
- args: [syndicateId]
446
- });
447
- return {
448
- id: result[0],
449
- vault: result[1],
450
- creator: result[2],
451
- subdomain: result[6]
452
- };
453
- }
454
- async function searchSyndicateAcrossChains(subdomain) {
455
- const includeTestnets = process.env.ENABLE_TESTNET === "true";
456
- const candidates = VALID_NETWORKS.filter(
457
- (n) => includeTestnets || !CHAIN_REGISTRY[n].isTestnet
458
- );
459
- const hits = [];
460
- for (const net of candidates) {
461
- try {
462
- const result = await withNetwork(net, () => resolveSyndicate(subdomain));
463
- hits.push({ network: net, result });
464
- } catch {
465
- }
466
- }
467
- return hits;
468
- }
469
- async function resolveVaultSyndicate(vaultAddress) {
470
- const client = getPublicClient();
471
- const factory = getFactoryAddress();
472
- const syndicateId = await client.readContract({
473
- address: factory,
474
- abi: SYNDICATE_FACTORY_ABI,
475
- functionName: "vaultToSyndicate",
476
- args: [vaultAddress]
477
- });
478
- if (syndicateId === 0n) {
479
- throw new Error(`No syndicate found for vault ${vaultAddress}`);
480
- }
481
- const result = await client.readContract({
482
- address: factory,
483
- abi: SYNDICATE_FACTORY_ABI,
484
- functionName: "syndicates",
485
- args: [syndicateId]
486
- });
487
- return {
488
- id: result[0],
489
- vault: result[1],
490
- creator: result[2],
491
- subdomain: result[6]
492
- };
493
- }
494
- function getSubdomainNode(subdomain) {
495
- return namehash(`${subdomain}.${ENS_DOMAIN}`);
496
- }
497
- async function setTextRecord(subdomain, key, value, vaultAddress) {
498
- const l2Registry = ENS().L2_REGISTRY;
499
- const node = getSubdomainNode(subdomain);
500
- setVaultAddress(vaultAddress);
501
- const setTextData = encodeFunctionData({
502
- abi: L2_REGISTRY_ABI,
503
- functionName: "setText",
504
- args: [node, key, value]
505
- });
506
- return executeBatch(
507
- [{ target: l2Registry, data: setTextData, value: 0n }]
508
- );
509
- }
510
- async function getTextRecord(subdomain, key) {
511
- const client = getPublicClient();
512
- const node = getSubdomainNode(subdomain);
513
- return client.readContract({
514
- address: ENS().L2_REGISTRY,
515
- abi: L2_REGISTRY_ABI,
516
- functionName: "text",
517
- args: [node, key]
518
- });
519
- }
520
-
521
502
  export {
522
503
  setVaultAddress,
523
504
  getVaultAddress,
524
505
  getAssetAddress,
525
506
  getAssetDecimals,
526
507
  getVaultOwner,
508
+ getAgentFeeBps,
509
+ setAgentFeeBps,
527
510
  preflightDeposit,
528
511
  depositWithEthWrap,
529
512
  deposit,
@@ -533,14 +516,11 @@ export {
533
516
  executeBatch,
534
517
  approveDepositor,
535
518
  removeDepositor,
519
+ approveDepositors,
536
520
  isAgent,
521
+ isApprovedDepositor,
537
522
  getBalance,
538
523
  registerAgent,
539
- getVaultInfo,
540
- resolveSyndicate,
541
- searchSyndicateAcrossChains,
542
- resolveVaultSyndicate,
543
- setTextRecord,
544
- getTextRecord
524
+ getVaultInfo
545
525
  };
546
- //# sourceMappingURL=chunk-O4G3SBCI.js.map
526
+ //# sourceMappingURL=chunk-UUHWD7Y3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/vault.ts"],"sourcesContent":["/**\n * SyndicateVault contract wrapper.\n *\n * The vault is the onchain identity — it holds all positions via delegatecall\n * to a shared BatchExecutorLib. No separate executor contract needed.\n */\n\nimport type { Address, Hex } from \"viem\";\nimport { formatUnits, parseEther } from \"viem\";\nimport { getChain } from \"./network.js\";\nimport {\n getPublicClient,\n getAccount,\n getWalletClient,\n writeContractWithRetry,\n waitForReceipt,\n} from \"./client.js\";\nimport { SYNDICATE_VAULT_ABI, ERC20_ABI } from \"./abis.js\";\nimport type { BatchCall } from \"./batch.js\";\nimport { getChainContracts } from \"./config.js\";\nimport { TOKENS } from \"./addresses.js\";\n\n// Per-command override (set by --vault flag in index.ts)\nlet _vaultOverride: Address | null = null;\n\nexport function setVaultAddress(addr: Address): void {\n _vaultOverride = addr;\n}\n\nexport function getVaultAddress(): Address {\n // 1. Per-command override (--vault flag)\n if (_vaultOverride) return _vaultOverride;\n\n // 2. Config (~/.sherwood/config.json) — default vault\n const chainId = getChain().id;\n const fromConfig = getChainContracts(chainId).vault;\n if (fromConfig) return fromConfig as Address;\n\n throw new Error(\n \"Vault address not found. Pass --vault <addr> or run 'sherwood config set --vault <addr>'.\",\n );\n}\n\n// ── Asset Helpers ──\n\n/**\n * Read the vault's underlying ERC-20 asset address.\n */\nexport async function getAssetAddress(): Promise<Address> {\n const client = getPublicClient();\n return client.readContract({\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"asset\",\n }) as Promise<Address>;\n}\n\n/**\n * Read decimals from the vault's underlying asset.\n * Works with any ERC-20 (USDC=6, WETH=18, WBTC=8, etc.).\n */\nexport async function getAssetDecimals(): Promise<number> {\n const client = getPublicClient();\n const asset = await getAssetAddress();\n return client.readContract({\n address: asset,\n abi: ERC20_ABI,\n functionName: \"decimals\",\n }) as Promise<number>;\n}\n\n/**\n * Read the vault owner (OwnableUpgradeable).\n */\nexport async function getVaultOwner(vault?: Address): Promise<Address> {\n const client = getPublicClient();\n return client.readContract({\n address: vault ?? getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"owner\",\n }) as Promise<Address>;\n}\n\n/**\n * Read the vault's agent performance fee (basis points). Owner-set, default\n * 500 (5%). Snapshotted onto a proposal at propose time and clamped to the\n * governor's `maxPerformanceFeeBps` at settlement.\n */\nexport async function getAgentFeeBps(vault?: Address): Promise<bigint> {\n const client = getPublicClient();\n return client.readContract({\n address: vault ?? getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"agentFeeBps\",\n }) as Promise<bigint>;\n}\n\n/**\n * Set the vault's agent performance fee (basis points). Vault owner only;\n * reverts above 1500 (15%).\n */\nexport async function setAgentFeeBps(bps: bigint): Promise<Hex> {\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"setAgentFeeBps\",\n args: [bps],\n });\n await waitForReceipt(hash);\n return hash;\n}\n\n// ── WETH ABI (for auto-wrapping ETH) ──\n\nconst WETH_ABI = [\n {\n name: \"deposit\",\n type: \"function\",\n stateMutability: \"payable\",\n inputs: [],\n outputs: [],\n },\n] as const;\n\n// ── Pre-flight Checks ──\n\n/**\n * Validate that a deposit can succeed before submitting any transactions.\n * Throws descriptive errors if any pre-condition fails.\n */\nexport async function preflightDeposit(amount: bigint): Promise<void> {\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n const account = getAccount();\n const asset = await getAssetAddress();\n const decimals = await getAssetDecimals();\n\n // 1. Check asset balance\n const balance = (await client.readContract({\n address: asset,\n abi: ERC20_ABI,\n functionName: \"balanceOf\",\n args: [account.address],\n })) as bigint;\n\n if (balance < amount) {\n const symbol = (await client.readContract({\n address: asset,\n abi: ERC20_ABI,\n functionName: \"symbol\",\n })) as string;\n throw new Error(\n `Insufficient ${symbol}. Have ${formatUnits(balance, decimals)}, need ${formatUnits(amount, decimals)}.`,\n );\n }\n\n // 2. Check ETH for gas\n const ethBalance = await client.getBalance({ address: account.address });\n const minGas = parseEther(\"0.0005\");\n if (ethBalance < minGas) {\n throw new Error(\n `Insufficient ETH for gas. Have ${formatUnits(ethBalance, 18)} ETH, need at least 0.0005 ETH. Top up your wallet with ETH on Base.`,\n );\n }\n\n // 3. Check vault not paused\n const paused = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"paused\",\n })) as boolean;\n if (paused) {\n throw new Error(\"Vault is paused — deposits are temporarily disabled.\");\n }\n\n // 4. Active proposal: deposits lock UNLESS Lane A (the V2 oracle instant\n // lane, PR #357) is live for the running strategy — then the vault accepts\n // instant entries up to maxDeposit. The old unconditional refusal made Lane\n // A unusable through the CLI (vnet e2e finding, issue #416).\n const locked = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"redemptionsLocked\",\n })) as boolean;\n if (locked) {\n const cap = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"maxDeposit\",\n args: [account.address],\n })) as bigint;\n if (cap < amount) {\n throw new Error(\n cap === 0n\n ? \"Deposits are locked while a strategy is executing (Lane A unavailable for this strategy). \" +\n \"Queue an async deposit instead:\\n sherwood queue request-deposit --vault <vault> --amount <usdc>\"\n : `Deposit exceeds the Lane A instant cap while a strategy is executing (maxDeposit ${cap.toString()} ` +\n `raw asset units, requested ${amount.toString()}). Reduce the amount or queue it:\\n` +\n \" sherwood queue request-deposit --vault <vault> --amount <usdc>\",\n );\n }\n }\n\n // 5. Check depositor approval (if whitelist vault)\n const openDeposits = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"openDeposits\",\n })) as boolean;\n if (!openDeposits) {\n const approved = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"isApprovedDepositor\",\n args: [account.address],\n })) as boolean;\n if (!approved) {\n throw new Error(\n \"Your address is not an approved depositor. Ask the vault creator to approve you:\\n sherwood fund approve-depositor --depositor \" +\n account.address,\n );\n }\n }\n}\n\n// ── LP Functions ──\n\n/**\n * Deposit into the vault with optional ETH auto-wrapping for WETH vaults.\n * When `useEth` is true and the vault asset is WETH, wraps ETH first.\n */\nexport async function depositWithEthWrap(amount: bigint): Promise<Hex> {\n const client = getPublicClient();\n const account = getAccount();\n const asset = await getAssetAddress();\n const wethAddress = TOKENS().WETH;\n\n if (asset.toLowerCase() !== wethAddress.toLowerCase()) {\n throw new Error(\"--use-eth is only supported for WETH vaults.\");\n }\n\n // Check current WETH balance — only wrap the shortfall\n const wethBalance = (await client.readContract({\n address: wethAddress,\n abi: ERC20_ABI,\n functionName: \"balanceOf\",\n args: [account.address],\n })) as bigint;\n\n if (wethBalance < amount) {\n const shortfall = amount - wethBalance;\n\n // Verify sufficient ETH to cover shortfall + gas\n const ethBalance = await client.getBalance({ address: account.address });\n const minGas = parseEther(\"0.0005\");\n if (ethBalance < shortfall + minGas) {\n throw new Error(\n `Insufficient ETH. Have ${formatUnits(ethBalance, 18)} ETH, need ${formatUnits(shortfall, 18)} to wrap + gas.`,\n );\n }\n\n // Wrap ETH → WETH\n const wallet = getWalletClient();\n const wrapHash = await wallet.writeContract({\n address: wethAddress,\n abi: WETH_ABI,\n functionName: \"deposit\",\n args: [],\n value: shortfall,\n account,\n chain: getChain(),\n });\n const wrapReceipt = await client.waitForTransactionReceipt({ hash: wrapHash });\n // A reverted wrap leaves WETH short; the downstream deposit would then\n // revert on transferFrom. Fail loudly here instead.\n if (wrapReceipt.status !== \"success\") {\n throw new Error(`ETH->WETH wrap reverted on-chain (tx ${wrapHash}).`);\n }\n }\n\n // Proceed with normal deposit\n return deposit(amount);\n}\n\n/**\n * Deposit into the vault. Handles approval + deposit for the vault's asset.\n */\nexport async function deposit(amount: bigint): Promise<Hex> {\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n const account = getAccount();\n const asset = await getAssetAddress();\n\n // Check existing allowance — skip approve if sufficient\n const currentAllowance = (await client.readContract({\n address: asset,\n abi: ERC20_ABI,\n functionName: \"allowance\",\n args: [account.address, vaultAddress],\n })) as bigint;\n\n if (currentAllowance < amount) {\n const approveHash = await writeContractWithRetry({\n account,\n chain: getChain(),\n address: asset,\n abi: ERC20_ABI,\n functionName: \"approve\",\n args: [vaultAddress, amount],\n });\n // Wait for approve to confirm before deposit — prevents nonce collision\n const approveReceipt = await waitForReceipt(approveHash);\n // A reverted approve leaves allowance below `amount`; the deposit's\n // transferFrom would then revert. Surface it here.\n if (approveReceipt.status !== \"success\") {\n throw new Error(`Asset approve reverted on-chain (tx ${approveHash}).`);\n }\n }\n\n // Snapshot share balance before deposit so we can assert shares were minted.\n const sharesBefore = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"balanceOf\",\n args: [account.address],\n })) as bigint;\n\n // Deposit — retry wrapper handles nonce/gas automatically\n const depositHash = await writeContractWithRetry({\n account,\n chain: getChain(),\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"deposit\",\n args: [amount, account.address],\n });\n const receipt = await waitForReceipt(depositHash);\n\n // A mined-but-reverted deposit is NOT a success. viem's\n // waitForTransactionReceipt resolves even for `status: \"reverted\"` txs, so\n // without this guard a reverted deposit would be reported as a successful\n // one and the caller would believe it holds shares it does not.\n if (receipt.status !== \"success\") {\n throw new Error(\n `Deposit transaction reverted on-chain (tx ${depositHash}). No shares were minted.`,\n );\n }\n\n // Confirm shares were actually minted to the receiver. Guards against a\n // silent zero-share mint (e.g. rounding on an inflated vault) that would\n // otherwise pass unnoticed because the tx did not revert.\n const sharesAfter = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"balanceOf\",\n args: [account.address],\n })) as bigint;\n if (sharesAfter <= sharesBefore) {\n throw new Error(\n `Deposit confirmed on-chain (tx ${depositHash}) but minted 0 shares ` +\n `(balance ${sharesBefore} -> ${sharesAfter}). Aborting to avoid reporting a phantom deposit.`,\n );\n }\n\n return depositHash;\n}\n\n// ── Redeem ──\n\n/**\n * Pre-flight check for redeem. Validates that the caller has enough shares\n * and that the vault allows redemption right now.\n */\nexport async function preflightRedeem(shares: bigint): Promise<void> {\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n const account = getAccount();\n\n if (shares === 0n) {\n throw new Error(\"No shares to redeem.\");\n }\n\n // 1. Check share balance\n const balance = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"balanceOf\",\n args: [account.address],\n })) as bigint;\n\n if (balance < shares) {\n throw new Error(\n `Insufficient shares. Have ${balance.toString()}, need ${shares.toString()}.`,\n );\n }\n\n // 2. Check vault not paused\n const paused = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"paused\",\n })) as boolean;\n if (paused) {\n throw new Error(\"Vault is paused — redemptions are temporarily disabled.\");\n }\n\n // 3. Active proposal: redemptions lock UNLESS Lane A (the V2 oracle instant\n // lane, PR #357) is live — then instant exits are allowed up to maxRedeem.\n // The old unconditional refusal made Lane A unusable through the CLI (vnet\n // e2e finding, issue #416).\n const locked = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"redemptionsLocked\",\n })) as boolean;\n if (locked) {\n const cap = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"maxRedeem\",\n args: [account.address],\n })) as bigint;\n if (cap < shares) {\n throw new Error(\n cap === 0n\n ? \"Redemptions are locked while a strategy is executing (Lane A unavailable for this strategy). \" +\n \"Queue an async redeem instead:\\n sherwood queue request-redeem --vault <vault> --shares <raw-shares>\"\n : `Redeem exceeds the Lane A instant cap while a strategy is executing (maxRedeem ${cap.toString()} ` +\n `raw shares, requested ${shares.toString()}). Reduce the share amount or queue it:\\n` +\n \" sherwood queue request-redeem --vault <vault> --shares <raw-shares>\",\n );\n }\n }\n\n // 4. Check ETH for gas\n const ethBalance = await client.getBalance({ address: account.address });\n const minGas = parseEther(\"0.0005\");\n if (ethBalance < minGas) {\n throw new Error(\n `Insufficient ETH for gas. Have ${formatUnits(ethBalance, 18)} ETH, need at least 0.0005 ETH.`,\n );\n }\n}\n\n/**\n * Redeem shares for the underlying asset (standard ERC-4626 redeem).\n * Returns the tx hash and the assets received.\n */\nexport async function redeem(\n shares: bigint,\n receiver?: Address,\n): Promise<{ hash: Hex; assetsOut: bigint }> {\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n const account = getAccount();\n const to = receiver ?? account.address;\n\n // Preview so we can report assets received\n const assetsOut = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"previewRedeem\",\n args: [shares],\n })) as bigint;\n\n const hash = await writeContractWithRetry({\n account,\n chain: getChain(),\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"redeem\",\n args: [shares, to, account.address],\n });\n await waitForReceipt(hash);\n return { hash, assetsOut };\n}\n\n/**\n * Read the current share balance for an address (defaults to the caller).\n */\nexport async function getShareBalance(address?: Address): Promise<bigint> {\n const client = getPublicClient();\n const owner = address ?? getAccount().address;\n return client.readContract({\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"balanceOf\",\n args: [owner],\n }) as Promise<bigint>;\n}\n\n// ── Batch Execution ──\n\n/**\n * Execute a batch of protocol calls through the vault (owner only).\n * The vault delegatecalls to the executor lib.\n * All calls execute as the vault — positions live on the vault.\n */\nexport async function executeBatch(calls: BatchCall[]): Promise<Hex> {\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"executeBatch\",\n args: [\n calls.map((c) => ({\n target: c.target,\n data: c.data,\n value: c.value,\n })),\n ],\n });\n await waitForReceipt(hash);\n return hash;\n}\n\n// ── Depositor Management ──\n\n/**\n * Approve a depositor address (owner only).\n */\nexport async function approveDepositor(depositor: Address): Promise<Hex> {\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"approveDepositor\",\n args: [depositor],\n });\n await waitForReceipt(hash);\n return hash;\n}\n\n/**\n * Remove a depositor from the whitelist (owner only).\n */\nexport async function removeDepositor(depositor: Address): Promise<Hex> {\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"removeDepositor\",\n args: [depositor],\n });\n await waitForReceipt(hash);\n return hash;\n}\n\n/**\n * Approve multiple depositors in a batch (owner only).\n */\nexport async function approveDepositors(depositors: Address[]): Promise<Hex> {\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"approveDepositors\",\n args: [depositors],\n });\n await waitForReceipt(hash);\n return hash;\n}\n\n/**\n * Check if an address is a registered agent on the vault.\n */\nexport async function isAgent(agentAddress: Address): Promise<boolean> {\n const client = getPublicClient();\n return client.readContract({\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"isAgent\",\n args: [agentAddress],\n }) as Promise<boolean>;\n}\n\n/**\n * Check if an address is an approved depositor.\n */\nexport async function isApprovedDepositor(depositor: Address): Promise<boolean> {\n const client = getPublicClient();\n return client.readContract({\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"isApprovedDepositor\",\n args: [depositor],\n }) as Promise<boolean>;\n}\n\n/**\n * Get LP share balance and asset value.\n */\nexport async function getBalance(address?: Address): Promise<{\n shares: bigint;\n assetsValue: string;\n percentOfVault: string;\n}> {\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n const account = address || getAccount().address;\n\n const [shares, totalSupply] = await Promise.all([\n client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"balanceOf\",\n args: [account],\n }) as Promise<bigint>,\n client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"totalSupply\",\n }) as Promise<bigint>,\n ]);\n\n let assetsValue = 0n;\n if (shares > 0n) {\n assetsValue = (await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"convertToAssets\",\n args: [shares],\n })) as bigint;\n }\n\n const percent =\n totalSupply > 0n ? ((Number(shares) / Number(totalSupply)) * 100).toFixed(2) : \"0.00\";\n\n const decimals = await getAssetDecimals();\n\n return {\n shares,\n assetsValue: formatUnits(assetsValue, decimals),\n percentOfVault: `${percent}%`,\n };\n}\n\n// ── Agent Management ──\n\n/**\n * Register a new agent (owner only). Requires ERC-8004 agent identity.\n */\nexport async function registerAgent(\n agentId: bigint,\n agentAddress: Address,\n): Promise<Hex> {\n const hash = await writeContractWithRetry({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"registerAgent\",\n args: [agentId, agentAddress],\n });\n await waitForReceipt(hash);\n return hash;\n}\n\n// ── Views ──\n\nexport interface VaultInfo {\n address: Address;\n totalAssets: string;\n agentCount: bigint;\n redemptionsLocked: boolean;\n managementFeeBps: bigint;\n}\n\n/**\n * Get vault overview info.\n */\nexport async function getVaultInfo(): Promise<VaultInfo> {\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n\n const [totalAssets, agentCount, redemptionsLocked, managementFeeBps, decimals] =\n await Promise.all([\n client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"totalAssets\",\n }) as Promise<bigint>,\n client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"getAgentCount\",\n }) as Promise<bigint>,\n client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"redemptionsLocked\",\n }) as Promise<boolean>,\n client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"managementFeeBps\",\n }) as Promise<bigint>,\n getAssetDecimals(),\n ]);\n\n return {\n address: vaultAddress,\n totalAssets: formatUnits(totalAssets, decimals),\n agentCount,\n redemptionsLocked,\n managementFeeBps,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  getChain,
3
3
  getRpcUrls
4
- } from "./chunk-2LRC7I33.js";
4
+ } from "./chunk-HYUUR6SC.js";
5
5
  import {
6
6
  loadConfig
7
- } from "./chunk-2W4KGGPU.js";
7
+ } from "./chunk-GSC7PVEO.js";
8
8
 
9
9
  // src/lib/client.ts
10
10
  import { createPublicClient, createWalletClient, fallback, http } from "viem";
@@ -128,7 +128,7 @@ var CONTRACT_ERRORS_ABI = [
128
128
  var ERROR_MESSAGES = {
129
129
  // Vault — permissions / state
130
130
  InvalidOwner: "The caller is not the vault owner.",
131
- NotActiveAgent: "Your wallet is not registered as an active agent on this vault. Ask the creator to run: sherwood syndicate approve --agent-id <id> --wallet <your-address>",
131
+ NotActiveAgent: "Your wallet is not registered as an active agent on this vault. Ask the creator to run: sherwood fund approve --agent-id <id> --wallet <your-address>",
132
132
  NotApprovedDepositor: "Your address is not on the depositor whitelist. Ask the vault creator to approve you.",
133
133
  NotGovernor: "This operation can only be called by the governor contract.",
134
134
  NotFactory: "This operation can only be called by the factory contract.",
@@ -150,7 +150,7 @@ var ERROR_MESSAGES = {
150
150
  // Governor — proposal lifecycle
151
151
  VaultNotRegistered: "This vault is not registered with the governor. The factory registers vaults automatically on creation.",
152
152
  VaultAlreadyRegistered: "This vault is already registered with the governor.",
153
- NotRegisteredAgent: "Your wallet is not a registered agent on this vault. Register first via: sherwood syndicate approve",
153
+ NotRegisteredAgent: "Your wallet is not a registered agent on this vault. Register first via: sherwood fund approve",
154
154
  PerformanceFeeTooHigh: "Performance fee exceeds the governor's maxPerformanceFeeBps. Check: sherwood governor info",
155
155
  StrategyDurationTooLong: "Strategy duration exceeds the governor's maxStrategyDuration. Check: sherwood governor info",
156
156
  StrategyDurationTooShort: "Strategy duration is below the governor's minStrategyDuration. Check: sherwood governor info",
@@ -204,7 +204,7 @@ var ERROR_MESSAGES = {
204
204
  InvalidENSRegistrar: "ENS registrar address is invalid.",
205
205
  SubdomainTooShort: "Subdomain must be at least 3 characters.",
206
206
  SubdomainTaken: "This subdomain is already taken. Choose a different name.",
207
- NotCreator: "Only the syndicate creator can perform this action.",
207
+ NotCreator: "Only the fund creator can perform this action.",
208
208
  InvalidGovernor: "Governor address is invalid.",
209
209
  InsufficientCreationFee: "Insufficient creation fee. Check the required fee amount.",
210
210
  InvalidFeeToken: "Invalid fee token address.",
@@ -509,4 +509,4 @@ export {
509
509
  writeContractWithRetry,
510
510
  waitForReceipt
511
511
  };
512
- //# sourceMappingURL=chunk-YJSCIQUZ.js.map
512
+ //# sourceMappingURL=chunk-WECQ6NVS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/client.ts","../src/lib/errors.ts"],"sourcesContent":["/**\n * viem client factory — resolves chain and RPC from the network module.\n * Private key is read from ~/.sherwood/config.json (set via `sherwood config set --private-key`),\n * with PRIVATE_KEY env var as fallback.\n */\n\n// dotenv loaded at entrypoint\nimport type { Hex, TransactionReceipt } from \"viem\";\nimport { createPublicClient, createWalletClient, fallback, http } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { getChain, getRpcUrls } from \"./network.js\";\nimport { loadConfig } from \"./config.js\";\nimport { formatContractError } from \"./errors.js\";\nexport { formatContractError } from \"./errors.js\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _publicClient: any = null;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _walletClient: any = null;\n\n/**\n * Resolve the private key: config → env → error.\n */\nfunction getPrivateKey(): `0x${string}` {\n // 1. Config (~/.sherwood/config.json)\n const config = loadConfig();\n if (config.privateKey) {\n const k = config.privateKey;\n return (k.startsWith(\"0x\") ? k : `0x${k}`) as `0x${string}`;\n }\n\n // 2. Env var fallback\n const env = process.env.PRIVATE_KEY;\n if (env) {\n return (env.startsWith(\"0x\") ? env : `0x${env}`) as `0x${string}`;\n }\n\n throw new Error(\n \"Private key not found. Run 'sherwood config set --private-key <key>' or set PRIVATE_KEY env var.\",\n );\n}\n\n/**\n * Build a viem fallback transport over the chain's RPC URL list.\n * Transparently advances to the next URL on rate-limit / network errors\n * (issue #182). `retryCount: 0` on the inner `http` avoids wasting 10+s\n * spinning on a dead RPC before the fallback layer advances.\n */\nfunction buildTransport() {\n const urls = getRpcUrls();\n return fallback(\n urls.map((url) => http(url, { retryCount: 0, timeout: 8_000 })),\n { rank: false, retryCount: 0 },\n );\n}\n\nexport function getPublicClient() {\n const chain = getChain();\n // Auto-invalidate if network changed since last creation\n if (_publicClient && _publicClient.chain?.id !== chain.id) {\n _publicClient = null;\n }\n if (!_publicClient) {\n _publicClient = createPublicClient({\n chain,\n transport: buildTransport(),\n });\n }\n return _publicClient as ReturnType<typeof createPublicClient>;\n}\n\nexport function getWalletClient() {\n const chain = getChain();\n // Auto-invalidate if network changed since last creation\n if (_walletClient && _walletClient.chain?.id !== chain.id) {\n _walletClient = null;\n }\n if (!_walletClient) {\n const account = privateKeyToAccount(getPrivateKey());\n _walletClient = createWalletClient({\n account,\n chain,\n transport: buildTransport(),\n });\n }\n return _walletClient as ReturnType<typeof createWalletClient>;\n}\n\n/**\n * Reset cached clients. Required for tests that call setNetwork()\n * after a client was already created.\n */\nexport function resetClients() {\n _publicClient = null;\n _walletClient = null;\n}\n\nexport function getAccount() {\n return privateKeyToAccount(getPrivateKey());\n}\n\n/**\n * Estimate EIP-1559 fees with a 20% buffer to avoid stuck txs on Base gas spikes.\n */\nexport async function estimateFeesWithBuffer() {\n const client = getPublicClient();\n const { maxFeePerGas, maxPriorityFeePerGas } =\n await client.estimateFeesPerGas();\n return {\n maxFeePerGas: (maxFeePerGas * 120n) / 100n,\n maxPriorityFeePerGas: (maxPriorityFeePerGas * 120n) / 100n,\n };\n}\n\n// ── Nonce management + retry helpers ──\n//\n// Handles: nonce collisions, underpriced replacements, stuck pending txs.\n// Gas bump ceiling: base * 1.2 (buffer) * 1.1^3 (max retries) ≈ 1.6x base fee.\n\nconst MAX_RETRIES = 3;\nconst RETRY_DELAY_MS = 1500; // backoff between retries\nconst STUCK_NONCE_CONFIRM_DELAY_MS = 2500; // staleness check delay\nconst GAS_BUMP_NUMERATOR = 110n;\nconst GAS_BUMP_DENOMINATOR = 100n;\n\nfunction isUnderpricedError(msg: string): boolean {\n return msg.includes(\"replacement transaction underpriced\");\n}\n\nfunction isNonceStaleError(msg: string): boolean {\n return msg.includes(\"nonce too low\") || msg.includes(\"NONCE_EXPIRED\");\n}\n\nfunction isRetryableError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return isUnderpricedError(msg) || isNonceStaleError(msg);\n}\n\nfunction bumpFees(fees: { maxFeePerGas: bigint; maxPriorityFeePerGas: bigint }) {\n return {\n maxFeePerGas: (fees.maxFeePerGas * GAS_BUMP_NUMERATOR) / GAS_BUMP_DENOMINATOR,\n maxPriorityFeePerGas: (fees.maxPriorityFeePerGas * GAS_BUMP_NUMERATOR) / GAS_BUMP_DENOMINATOR,\n };\n}\n\n// ── Stuck nonce detection ──\n\n/**\n * Check if the wallet has a stuck nonce (pending tx count > confirmed tx count).\n * Returns the stuck nonce number, or null if the wallet is clean.\n *\n * Includes a staleness guard: if pending > confirmed, waits ~2.5s and re-checks\n * to avoid false positives from propagation delays.\n */\nexport async function detectStuckNonce(): Promise<number | null> {\n const client = getPublicClient();\n const address = getAccount().address;\n const [confirmed, pending] = await Promise.all([\n client.getTransactionCount({ address, blockTag: \"latest\" }),\n client.getTransactionCount({ address, blockTag: \"pending\" }),\n ]);\n if (pending <= confirmed) return null;\n\n // Staleness guard: re-check after a short delay to avoid false positives\n await new Promise((r) => setTimeout(r, STUCK_NONCE_CONFIRM_DELAY_MS));\n const [confirmed2, pending2] = await Promise.all([\n client.getTransactionCount({ address, blockTag: \"latest\" }),\n client.getTransactionCount({ address, blockTag: \"pending\" }),\n ]);\n return pending2 > confirmed2 ? confirmed2 : null;\n}\n\n/**\n * Clear a single stuck nonce by sending a 0-value self-transfer with aggressively bumped gas.\n * Uses 5x the buffered fee estimate to guarantee replacement even if the original tx\n * was sent during a gas spike (EIP-1559 requires >= 110% of original maxPriorityFeePerGas).\n */\nasync function unstickWalletSingle(): Promise<Hex> {\n const stuckNonce = await detectStuckNonce();\n if (stuckNonce === null) {\n throw new Error(\"No stuck nonce detected — wallet is clean.\");\n }\n\n console.warn(` Unsticking wallet: replacing stuck tx at nonce ${stuckNonce}...`);\n const wallet = getWalletClient();\n const account = getAccount();\n\n // Use 5x gas buffer to guarantee replacement even for txs sent during gas spikes\n const { maxFeePerGas, maxPriorityFeePerGas } = await estimateFeesWithBuffer();\n const hash = await wallet.sendTransaction({\n account,\n chain: wallet.chain,\n to: account.address,\n value: 0n,\n nonce: stuckNonce,\n maxFeePerGas: maxFeePerGas * 5n,\n maxPriorityFeePerGas: maxPriorityFeePerGas * 5n,\n });\n\n const client = getPublicClient();\n await client.waitForTransactionReceipt({ hash });\n console.warn(` Wallet unstuck (nonce ${stuckNonce} replaced).`);\n return hash;\n}\n\n/**\n * Unstick a wallet by clearing ALL stuck nonces, not just the first one.\n * Loops until pending == confirmed nonce count.\n */\nexport async function unstickWallet(): Promise<Hex[]> {\n const hashes: Hex[] = [];\n while ((await detectStuckNonce()) !== null) {\n hashes.push(await unstickWalletSingle());\n }\n if (hashes.length === 0) {\n throw new Error(\"No stuck nonce detected — wallet is clean.\");\n }\n return hashes;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype TxSender = (params: any) => Promise<Hex>;\n\n/**\n * Shared retry loop: explicit nonce + EIP-1559 fee buffer + gas-bump on underpriced errors.\n * On \"nonce too low\" / \"NONCE_EXPIRED\", re-fetches nonce from pending state before retrying.\n * Also detects stuck nonces before sending and auto-unsticks the wallet.\n */\nasync function withRetry(send: TxSender, txParams: Record<string, unknown>): Promise<Hex> {\n const client = getPublicClient();\n const account = getAccount();\n let fees = await estimateFeesWithBuffer();\n\n // Auto-detect and clear ALL stuck nonces before attempting the tx\n while ((await detectStuckNonce()) !== null) {\n await unstickWalletSingle();\n }\n\n let nonce = await client.getTransactionCount({ address: account.address, blockTag: \"pending\" });\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n return await send({ ...txParams, ...fees, nonce });\n } catch (err) {\n if (attempt >= MAX_RETRIES || !isRetryableError(err)) {\n // Decode contract revert into a human-readable message before throwing\n throw new Error(formatContractError(err));\n }\n\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(` Retry ${attempt + 1}/${MAX_RETRIES}: ${isNonceStaleError(msg) ? \"refreshing nonce\" : \"bumping gas\"}...`);\n\n // Backoff between retries to let mempool settle\n await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));\n\n if (isNonceStaleError(msg)) {\n // Fresh nonce = fresh tx, no replacement needed — only bump fees for underpriced errors\n nonce = await client.getTransactionCount({ address: account.address, blockTag: \"pending\" });\n } else {\n fees = bumpFees(fees);\n }\n }\n }\n throw new Error(\"withRetry: exhausted retries\");\n}\n\n/**\n * Send a raw transaction with automatic gas-bump retry on nonce/underpriced errors.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function sendTxWithRetry(txParams: Record<string, any>): Promise<Hex> {\n const wallet = getWalletClient();\n return withRetry((p) => wallet.sendTransaction(p), txParams);\n}\n\n/**\n * Call writeContract with automatic gas-bump retry on nonce/underpriced errors.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function writeContractWithRetry(txParams: Record<string, any>): Promise<Hex> {\n const wallet = getWalletClient();\n return withRetry((p) => wallet.writeContract(p), txParams);\n}\n\n/**\n * Wait for a tx receipt. On timeout, checks for stuck nonce and auto-unsticks.\n *\n * **Important**: After unsticking, the error is still re-thrown because the original\n * tx is lost. Callers should use `writeContractWithRetry` / `sendTxWithRetry` which\n * re-fetch nonces automatically. Do NOT manually retry after this throws — use the\n * retry-aware wrappers instead.\n */\nexport async function waitForReceipt(hash: Hex): Promise<TransactionReceipt> {\n const client = getPublicClient();\n try {\n return await client.waitForTransactionReceipt({ hash });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (err: any) {\n // If receipt wait times out, check for stuck nonce\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"timeout\") || msg.includes(\"Timed out\")) {\n if ((await detectStuckNonce()) !== null) {\n console.warn(` Tx ${hash} appears stuck — attempting to unstick wallet...`);\n while ((await detectStuckNonce()) !== null) {\n await unstickWalletSingle();\n }\n }\n }\n throw err;\n }\n}\n","/**\n * Contract error decoding — turns raw reverts into human-readable messages.\n *\n * Uses viem's decodeErrorResult() against a combined ABI of all custom errors\n * from SyndicateVault, SyndicateGovernor, GovernorParameters, SyndicateFactory,\n * and strategy contracts.\n */\n\nimport { type Hex, decodeErrorResult, BaseError } from \"viem\";\n\n// ── Combined error-only ABI for decoding ──\n\nexport const CONTRACT_ERRORS_ABI = [\n // ── SyndicateVault ──\n { type: \"error\", name: \"InvalidOwner\", inputs: [] },\n { type: \"error\", name: \"InvalidExecutorImpl\", inputs: [] },\n { type: \"error\", name: \"NotActiveAgent\", inputs: [] },\n { type: \"error\", name: \"SimulationFailed\", inputs: [] },\n { type: \"error\", name: \"InvalidDepositor\", inputs: [] },\n { type: \"error\", name: \"DepositorAlreadyApproved\", inputs: [] },\n { type: \"error\", name: \"DepositorNotApproved\", inputs: [] },\n { type: \"error\", name: \"NotApprovedDepositor\", inputs: [] },\n { type: \"error\", name: \"AgentAlreadyRegistered\", inputs: [] },\n { type: \"error\", name: \"AgentNotActive\", inputs: [] },\n { type: \"error\", name: \"InvalidAgentRegistry\", inputs: [] },\n { type: \"error\", name: \"NotAgentOwner\", inputs: [] },\n { type: \"error\", name: \"NotGovernor\", inputs: [] },\n { type: \"error\", name: \"RedemptionsLocked\", inputs: [] },\n { type: \"error\", name: \"DepositsLocked\", inputs: [] },\n { type: \"error\", name: \"InvalidAgentAddress\", inputs: [] },\n { type: \"error\", name: \"TransferFailed\", inputs: [] },\n { type: \"error\", name: \"ZeroAddress\", inputs: [] },\n { type: \"error\", name: \"CannotRescueAsset\", inputs: [] },\n { type: \"error\", name: \"NotFactory\", inputs: [] },\n\n // ── SyndicateGovernor ──\n { type: \"error\", name: \"VaultNotRegistered\", inputs: [] },\n { type: \"error\", name: \"VaultAlreadyRegistered\", inputs: [] },\n { type: \"error\", name: \"NotRegisteredAgent\", inputs: [] },\n { type: \"error\", name: \"PerformanceFeeTooHigh\", inputs: [] },\n { type: \"error\", name: \"StrategyDurationTooLong\", inputs: [] },\n { type: \"error\", name: \"StrategyDurationTooShort\", inputs: [] },\n { type: \"error\", name: \"EmptyExecuteCalls\", inputs: [] },\n { type: \"error\", name: \"EmptySettlementCalls\", inputs: [] },\n { type: \"error\", name: \"NotWithinVotingPeriod\", inputs: [] },\n { type: \"error\", name: \"NoVotingPower\", inputs: [] },\n { type: \"error\", name: \"AlreadyVoted\", inputs: [] },\n { type: \"error\", name: \"ProposalNotFound\", inputs: [] },\n { type: \"error\", name: \"ProposalNotApproved\", inputs: [] },\n { type: \"error\", name: \"ExecutionWindowExpired\", inputs: [] },\n { type: \"error\", name: \"StrategyAlreadyActive\", inputs: [] },\n { type: \"error\", name: \"CooldownNotElapsed\", inputs: [] },\n { type: \"error\", name: \"ProposalNotExecuted\", inputs: [] },\n { type: \"error\", name: \"ProposalNotCancellable\", inputs: [] },\n { type: \"error\", name: \"NotProposer\", inputs: [] },\n { type: \"error\", name: \"InvalidVotingPeriod\", inputs: [] },\n { type: \"error\", name: \"InvalidExecutionWindow\", inputs: [] },\n { type: \"error\", name: \"InvalidVetoThresholdBps\", inputs: [] },\n { type: \"error\", name: \"InvalidMaxPerformanceFeeBps\", inputs: [] },\n { type: \"error\", name: \"InvalidStrategyDurationBounds\", inputs: [] },\n { type: \"error\", name: \"InvalidCooldownPeriod\", inputs: [] },\n { type: \"error\", name: \"InvalidVault\", inputs: [] },\n { type: \"error\", name: \"NotVaultOwner\", inputs: [] },\n { type: \"error\", name: \"StrategyDurationNotElapsed\", inputs: [] },\n { type: \"error\", name: \"InvalidProtocolFeeBps\", inputs: [] },\n { type: \"error\", name: \"InvalidProtocolFeeRecipient\", inputs: [] },\n { type: \"error\", name: \"NotCoProposer\", inputs: [] },\n { type: \"error\", name: \"CollaborationExpired\", inputs: [] },\n { type: \"error\", name: \"AlreadyApproved\", inputs: [] },\n { type: \"error\", name: \"InvalidSplits\", inputs: [] },\n { type: \"error\", name: \"TooManyCoProposers\", inputs: [] },\n { type: \"error\", name: \"SplitTooLow\", inputs: [] },\n { type: \"error\", name: \"LeadSplitTooLow\", inputs: [] },\n { type: \"error\", name: \"DuplicateCoProposer\", inputs: [] },\n { type: \"error\", name: \"NotDraftState\", inputs: [] },\n { type: \"error\", name: \"InvalidCollaborationWindow\", inputs: [] },\n { type: \"error\", name: \"NotAuthorized\", inputs: [] },\n { type: \"error\", name: \"InvalidMaxCoProposers\", inputs: [] },\n { type: \"error\", name: \"Reentrancy\", inputs: [] },\n\n // ── GovernorParameters (timelock) ──\n { type: \"error\", name: \"ChangeAlreadyPending\", inputs: [] },\n { type: \"error\", name: \"NoChangePending\", inputs: [] },\n { type: \"error\", name: \"ChangeNotReady\", inputs: [] },\n { type: \"error\", name: \"InvalidParameterChangeDelay\", inputs: [] },\n { type: \"error\", name: \"InvalidParameterKey\", inputs: [] },\n\n // ── SyndicateFactory ──\n { type: \"error\", name: \"InvalidVaultImpl\", inputs: [] },\n { type: \"error\", name: \"InvalidENSRegistrar\", inputs: [] },\n { type: \"error\", name: \"SubdomainTooShort\", inputs: [] },\n { type: \"error\", name: \"SubdomainTaken\", inputs: [] },\n { type: \"error\", name: \"NotCreator\", inputs: [] },\n { type: \"error\", name: \"InvalidGovernor\", inputs: [] },\n { type: \"error\", name: \"InsufficientCreationFee\", inputs: [] },\n { type: \"error\", name: \"InvalidFeeToken\", inputs: [] },\n { type: \"error\", name: \"ManagementFeeTooHigh\", inputs: [] },\n { type: \"error\", name: \"UpgradesDisabled\", inputs: [] },\n { type: \"error\", name: \"VaultNotDeployed\", inputs: [] },\n { type: \"error\", name: \"StrategyActive\", inputs: [] },\n\n // ── BaseStrategy ──\n { type: \"error\", name: \"AlreadyInitialized\", inputs: [] },\n { type: \"error\", name: \"NotVault\", inputs: [] },\n { type: \"error\", name: \"NotExecuted\", inputs: [] },\n { type: \"error\", name: \"AlreadyExecuted\", inputs: [] },\n { type: \"error\", name: \"AlreadySettled\", inputs: [] },\n\n // ── MoonwellSupplyStrategy / WstETHMoonwellStrategy ──\n { type: \"error\", name: \"InvalidAmount\", inputs: [] },\n { type: \"error\", name: \"MintFailed\", inputs: [] },\n { type: \"error\", name: \"RedeemFailed\", inputs: [] },\n\n // ── AerodromeLPStrategy ──\n { type: \"error\", name: \"GaugeMismatch\", inputs: [] },\n\n // ── WstETHMoonwellStrategy / VeniceInferenceStrategy ──\n { type: \"error\", name: \"SwapFailed\", inputs: [] },\n { type: \"error\", name: \"AlreadySettledParams\", inputs: [] },\n\n // ── PortfolioStrategy / UniswapSwapAdapter ──\n { type: \"error\", name: \"QuoteUnavailable\", inputs: [] },\n { type: \"error\", name: \"UnsupportedMode\", inputs: [] },\n { type: \"error\", name: \"InvalidWeights\", inputs: [] },\n { type: \"error\", name: \"LengthMismatch\", inputs: [] },\n { type: \"error\", name: \"TooManyTokens\", inputs: [] },\n { type: \"error\", name: \"RebalancingInProgress\", inputs: [] },\n { type: \"error\", name: \"StalePrice\", inputs: [] },\n { type: \"error\", name: \"InvalidSlippage\", inputs: [] },\n { type: \"error\", name: \"InvalidPriceDecimals\", inputs: [] },\n\n // ── VeniceInferenceStrategy ──\n { type: \"error\", name: \"NoAgent\", inputs: [] },\n] as const;\n\n// ── Human-readable error messages ──\n\nconst ERROR_MESSAGES: Record<string, string> = {\n // Vault — permissions / state\n InvalidOwner: \"The caller is not the vault owner.\",\n NotActiveAgent: \"Your wallet is not registered as an active agent on this vault. Ask the creator to run: sherwood fund approve --agent-id <id> --wallet <your-address>\",\n NotApprovedDepositor: \"Your address is not on the depositor whitelist. Ask the vault creator to approve you.\",\n NotGovernor: \"This operation can only be called by the governor contract.\",\n NotFactory: \"This operation can only be called by the factory contract.\",\n RedemptionsLocked: \"Redemptions are locked while a strategy is executing. Wait for the proposal to be settled.\",\n DepositsLocked: \"Deposits are locked while an active proposal exists. Wait for settlement.\",\n InvalidDepositor: \"Invalid depositor address.\",\n DepositorAlreadyApproved: \"This depositor is already on the whitelist.\",\n DepositorNotApproved: \"This depositor is not on the whitelist.\",\n AgentAlreadyRegistered: \"This agent wallet is already registered on the vault.\",\n AgentNotActive: \"This agent is registered but not active.\",\n InvalidAgentRegistry: \"Agent registry address is invalid.\",\n NotAgentOwner: \"You do not own this ERC-8004 agent identity NFT.\",\n InvalidAgentAddress: \"Invalid agent wallet address (cannot be zero).\",\n InvalidExecutorImpl: \"Executor implementation address is invalid.\",\n SimulationFailed: \"Transaction simulation failed — the batch calls would revert onchain.\",\n TransferFailed: \"ERC-20 token transfer failed. Check token balance and approvals.\",\n ZeroAddress: \"A required address parameter is the zero address.\",\n CannotRescueAsset: \"Cannot rescue the vault's underlying asset — use redeem() instead.\",\n\n // Governor — proposal lifecycle\n VaultNotRegistered: \"This vault is not registered with the governor. The factory registers vaults automatically on creation.\",\n VaultAlreadyRegistered: \"This vault is already registered with the governor.\",\n NotRegisteredAgent: \"Your wallet is not a registered agent on this vault. Register first via: sherwood fund approve\",\n PerformanceFeeTooHigh: \"Performance fee exceeds the governor's maxPerformanceFeeBps. Check: sherwood governor info\",\n StrategyDurationTooLong: \"Strategy duration exceeds the governor's maxStrategyDuration. Check: sherwood governor info\",\n StrategyDurationTooShort: \"Strategy duration is below the governor's minStrategyDuration. Check: sherwood governor info\",\n EmptyExecuteCalls: \"Proposal must include at least one execute call.\",\n EmptySettlementCalls: \"Proposal must include at least one settlement call.\",\n NotWithinVotingPeriod: \"Voting period has not started or has already ended. Check: sherwood proposal show <id>\",\n NoVotingPower: \"You have no voting power (zero vault shares at the snapshot time).\",\n AlreadyVoted: \"You have already voted on this proposal.\",\n ProposalNotFound: \"Proposal does not exist. Check the proposal ID.\",\n ProposalNotApproved: \"Proposal is not in Approved state. It may still be in voting or was rejected.\",\n ExecutionWindowExpired: \"The execution window has closed. The proposal is now Expired.\",\n StrategyAlreadyActive: \"Another strategy is currently executing on this vault. Settle it first before executing a new proposal.\",\n CooldownNotElapsed: \"A proposal was recently settled. Wait for the cooldown period to elapse. Check: sherwood governor info\",\n ProposalNotExecuted: \"Proposal has not been executed yet. Execute it first.\",\n ProposalNotCancellable: \"This proposal cannot be cancelled in its current state.\",\n NotProposer: \"Only the original proposer can perform this action.\",\n StrategyDurationNotElapsed: \"Strategy duration has not elapsed yet. Only the proposer can settle early.\",\n\n // Governor — parameter validation\n InvalidVotingPeriod: \"Voting period is outside allowed bounds (check governor constants).\",\n InvalidExecutionWindow: \"Execution window is outside allowed bounds.\",\n InvalidVetoThresholdBps: \"Veto threshold must be between the allowed bounds (in bps).\",\n InvalidMaxPerformanceFeeBps: \"Max performance fee is outside allowed bounds.\",\n InvalidStrategyDurationBounds: \"Strategy duration bounds are invalid (min must be < max).\",\n InvalidCooldownPeriod: \"Cooldown period is outside allowed bounds.\",\n InvalidVault: \"Invalid vault address.\",\n NotVaultOwner: \"Only the vault owner can perform this action.\",\n InvalidProtocolFeeBps: \"Protocol fee exceeds the 10% maximum (1000 bps).\",\n InvalidProtocolFeeRecipient: \"Protocol fee recipient must be set before enabling protocol fees.\",\n\n // Governor — collaborative proposals\n NotCoProposer: \"You are not listed as a co-proposer on this proposal.\",\n CollaborationExpired: \"The collaboration window has expired.\",\n AlreadyApproved: \"This co-proposer has already approved.\",\n InvalidSplits: \"Co-proposer fee splits must sum correctly with the lead proposer split.\",\n TooManyCoProposers: \"Exceeds the maximum number of co-proposers. Check: sherwood governor info\",\n SplitTooLow: \"A co-proposer's fee split is below the minimum.\",\n LeadSplitTooLow: \"The lead proposer's fee split is below the minimum.\",\n DuplicateCoProposer: \"Duplicate co-proposer address in the list.\",\n NotDraftState: \"Proposal must be in Draft state for this operation.\",\n InvalidCollaborationWindow: \"Collaboration window is outside allowed bounds.\",\n NotAuthorized: \"You are not authorized to perform this action.\",\n InvalidMaxCoProposers: \"Max co-proposers value is outside allowed bounds.\",\n Reentrancy: \"Reentrancy detected — this call cannot be made during another call.\",\n\n // Governor — parameter timelock\n ChangeAlreadyPending: \"A parameter change is already pending for this parameter. Finalize or cancel it first.\",\n NoChangePending: \"No parameter change is pending for this key.\",\n ChangeNotReady: \"Parameter change timelock has not elapsed yet. Wait for the delay to pass, then finalize.\",\n InvalidParameterChangeDelay: \"Parameter change delay is outside allowed bounds.\",\n InvalidParameterKey: \"Unknown parameter key.\",\n\n // Factory\n InvalidVaultImpl: \"Vault implementation address is invalid.\",\n InvalidENSRegistrar: \"ENS registrar address is invalid.\",\n SubdomainTooShort: \"Subdomain must be at least 3 characters.\",\n SubdomainTaken: \"This subdomain is already taken. Choose a different name.\",\n NotCreator: \"Only the fund creator can perform this action.\",\n InvalidGovernor: \"Governor address is invalid.\",\n InsufficientCreationFee: \"Insufficient creation fee. Check the required fee amount.\",\n InvalidFeeToken: \"Invalid fee token address.\",\n ManagementFeeTooHigh: \"Management fee exceeds the maximum allowed.\",\n UpgradesDisabled: \"Vault upgrades are currently disabled by the factory owner.\",\n VaultNotDeployed: \"Vault has not been deployed yet.\",\n StrategyActive: \"Cannot perform this action while a strategy is actively executing.\",\n\n // Strategy — base\n AlreadyInitialized: \"This strategy clone was already initialized. Deploy a fresh clone.\",\n NotVault: \"Only the vault can call this strategy function.\",\n NotExecuted: \"Strategy has not been executed yet.\",\n AlreadyExecuted: \"Strategy has already been executed.\",\n AlreadySettled: \"Strategy has already been settled.\",\n\n // Strategy — Moonwell / WstETH\n InvalidAmount: \"Invalid amount — must be greater than zero.\",\n MintFailed: \"Moonwell cToken mint failed. The underlying supply may have hit the market cap.\",\n RedeemFailed: \"Moonwell cToken redeem failed. Check that sufficient liquidity is available.\",\n\n // Strategy — Aerodrome\n GaugeMismatch: \"Aerodrome gauge does not match the expected pool.\",\n\n // Strategy — WstETH / Venice\n SwapFailed: \"Token swap failed. Check slippage tolerance and pool liquidity.\",\n AlreadySettledParams: \"Settlement parameters have already been set.\",\n\n // Strategy — Portfolio / UniswapSwapAdapter\n QuoteUnavailable:\n \"Uniswap quoter returned 0 or reverted for a portfolio allocation. \" +\n \"Usually means: (a) no V3 pool exists at the requested fee tier for one of the tokens, \" +\n \"(b) the strategy clone was initialized against an old/broken UniswapSwapAdapter \" +\n \"(check the clone's swapAdapter() vs the current chains JSON), or \" +\n \"(c) the adapter is calling Quoter with the wrong ABI (pre-PR #317 bug).\",\n UnsupportedMode: \"UniswapSwapAdapter doesn't support that swap mode. Only V3 single-hop (mode 0) and multi-hop (mode 1) are supported.\",\n InvalidWeights: \"Portfolio weights must sum to 10000 bps (100%).\",\n LengthMismatch: \"Portfolio input arrays (tokens / weights / swapExtraData / priceDecimals) must have the same length.\",\n TooManyTokens: \"Portfolio basket exceeds the maximum allocation count (20).\",\n RebalancingInProgress: \"A rebalance is already in flight on this clone. Wait for it to finish.\",\n StalePrice: \"Chainlink Data Streams report has expired. Refresh prices and retry.\",\n InvalidSlippage: \"maxSlippageBps must be > 0 and < 10000.\",\n InvalidPriceDecimals: \"priceDecimals entry exceeds 36. Use 8 for tokenized-stock feeds, 18 for crypto pairs.\",\n\n // Strategy — Venice\n NoAgent: \"No agent is registered on the vault. Register an agent first.\",\n};\n\n// ── Decoding ──\n\n/**\n * Attempt to decode a hex revert selector into a human-readable error message.\n * Returns null if the selector doesn't match any known contract error.\n */\nexport function decodeContractError(data: Hex): { name: string; message: string } | null {\n try {\n const decoded = decodeErrorResult({\n abi: CONTRACT_ERRORS_ABI,\n data,\n });\n const message = ERROR_MESSAGES[decoded.errorName] || `Contract error: ${decoded.errorName}`;\n return { name: decoded.errorName, message };\n } catch {\n return null;\n }\n}\n\n/**\n * Extract a hex error selector from a viem error's walk chain.\n * viem wraps contract call reverts in ContractFunctionRevertedError which\n * contains a `data` field with the raw 4-byte (or longer) revert payload.\n */\nfunction extractRevertData(err: unknown): Hex | null {\n if (!(err instanceof BaseError)) return null;\n\n // Walk the viem error chain to find revert data\n const revertError = err.walk((e) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (e as any).data !== undefined;\n });\n\n if (revertError) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const data = (revertError as any).data;\n // data can be the hex string directly, or an object with a data field\n if (typeof data === \"string\" && data.startsWith(\"0x\")) {\n return data as Hex;\n }\n if (typeof data === \"object\" && data?.data && typeof data.data === \"string\") {\n return data.data as Hex;\n }\n }\n\n // Fallback: extract 0x + hex from the error message (e.g. \"reverted with 0x62df0545\")\n const msg = err.message || \"\";\n const match = msg.match(/(?:signature|data)\\s*\"?(0x[0-9a-fA-F]+)\"?/);\n if (match) return match[1] as Hex;\n\n return null;\n}\n\n// ── Known common EVM / ERC-20 revert strings ──\n\nconst KNOWN_REVERT_STRINGS: Record<string, string> = {\n \"ERC20: transfer amount exceeds balance\": \"Insufficient token balance for this transfer.\",\n \"ERC20: transfer amount exceeds allowance\": \"Token allowance insufficient. An approval step may have been skipped.\",\n \"ERC20: insufficient allowance\": \"Token allowance insufficient. An approval step may have been skipped.\",\n \"Ownable: caller is not the owner\": \"Only the contract owner can call this function.\",\n \"Pausable: paused\": \"The contract is currently paused.\",\n};\n\n/**\n * Format any caught error into a user-friendly message.\n *\n * Tries (in order):\n * 1. Decode custom error selector from our contract ABIs\n * 2. Match known EVM/ERC-20 revert reason strings\n * 3. Identify nonce / gas issues with actionable advice\n * 4. Fall back to the raw error message\n */\nexport function formatContractError(err: unknown): string {\n // 1. Try to decode custom contract error\n const revertData = extractRevertData(err);\n if (revertData && revertData.length >= 10) {\n const decoded = decodeContractError(revertData);\n if (decoded) return decoded.message;\n }\n\n const rawMsg = err instanceof Error ? err.message : String(err);\n\n // 2. Check for known revert reason strings\n for (const [pattern, message] of Object.entries(KNOWN_REVERT_STRINGS)) {\n if (rawMsg.includes(pattern)) return message;\n }\n\n // 3. Nonce / gas issues — already handled by retry, but surface clearly if they bubble up\n if (rawMsg.includes(\"replacement transaction underpriced\")) {\n return \"A previous transaction is stuck. The CLI retried with higher gas but the error persists. Try again in a moment.\";\n }\n if (rawMsg.includes(\"nonce too low\") || rawMsg.includes(\"NONCE_EXPIRED\")) {\n return \"Transaction nonce conflict — a previous transaction is still pending. Wait a moment and retry.\";\n }\n if (rawMsg.includes(\"insufficient funds for gas\")) {\n return \"Insufficient ETH for gas fees. Top up your wallet with ETH on Base.\";\n }\n\n // 4. Clean up common viem wrapper noise\n // Strip \"ContractFunctionRevertedError:\" prefix and \"Docs:\" links\n let cleaned = rawMsg\n .replace(/ContractFunctionRevertedError:\\s*/g, \"\")\n .replace(/Docs:\\s*https:\\/\\/\\S+/g, \"\")\n .replace(/Version:\\s*viem@\\S+/g, \"\")\n .replace(/Request Arguments:[\\s\\S]*$/m, \"\")\n .replace(/Contract Call:[\\s\\S]*?(?=\\n\\n|\\n[A-Z]|$)/m, \"\")\n .trim();\n\n // If we still have \"reverted with the following signature: 0x...\" try one more decode\n const sigMatch = cleaned.match(/signature[:\\s]+\"?(0x[0-9a-fA-F]{8,})\"?/);\n if (sigMatch) {\n const decoded = decodeContractError(sigMatch[1] as Hex);\n if (decoded) return decoded.message;\n // If we can't decode it, at least explain what it is\n cleaned = `Contract reverted with unknown error selector ${sigMatch[1]}. Check the transaction on Basescan for details.`;\n }\n\n return cleaned;\n}\n"],"mappings":"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","names":[]}
@@ -9,23 +9,36 @@ function getPinataGateway() {
9
9
  }
10
10
  async function uploadToIPFS(content, name) {
11
11
  const url = getUploadApiUrl();
12
- const response = await fetch(url, {
13
- method: "POST",
14
- headers: { "Content-Type": "application/json" },
15
- body: JSON.stringify({ content, name })
16
- });
17
- if (!response.ok) {
18
- const text = await response.text();
19
- throw new Error(`IPFS upload failed (${response.status}): ${text}`);
12
+ const attempts = 3;
13
+ let lastErr;
14
+ for (let i = 0; i < attempts; i++) {
15
+ try {
16
+ const response = await fetch(url, {
17
+ method: "POST",
18
+ headers: { "Content-Type": "application/json" },
19
+ body: JSON.stringify({ content, name })
20
+ });
21
+ if (!response.ok) {
22
+ const text = await response.text();
23
+ if (response.status < 500) throw new Error(`IPFS upload failed (${response.status}): ${text}`);
24
+ lastErr = new Error(`IPFS upload failed (${response.status}): ${text}`);
25
+ } else {
26
+ const result = await response.json();
27
+ return `ipfs://${result.ipfsHash}`;
28
+ }
29
+ } catch (err) {
30
+ if (err instanceof Error && err.message.startsWith("IPFS upload failed (4")) throw err;
31
+ lastErr = err;
32
+ }
33
+ if (i < attempts - 1) await new Promise((r) => setTimeout(r, 2e3 * (i + 1)));
20
34
  }
21
- const result = await response.json();
22
- return `ipfs://${result.ipfsHash}`;
35
+ throw lastErr instanceof Error ? lastErr : new Error(`IPFS upload failed: ${String(lastErr)}`);
23
36
  }
24
37
  async function pinJSON(content, name) {
25
38
  return uploadToIPFS(content, name);
26
39
  }
27
40
  async function uploadMetadata(metadata) {
28
- const name = `sherwood-syndicate-${metadata.name.toLowerCase().replace(/\s+/g, "-")}`;
41
+ const name = `sherwood-fund-${metadata.name.toLowerCase().replace(/\s+/g, "-")}`;
29
42
  return uploadToIPFS(metadata, name);
30
43
  }
31
44
  async function fetchMetadata(ipfsURI) {
@@ -65,4 +78,4 @@ export {
65
78
  fetchMetadata,
66
79
  resolveMetadataSummary
67
80
  };
68
- //# sourceMappingURL=chunk-RHRLYVKM.js.map
81
+ //# sourceMappingURL=chunk-ZYXFKVYF.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 * 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":";AAkCA,IAAM,yBAAyB;AAG/B,IAAM,qBAAqB;AAE3B,SAAS,kBAA0B;AACjC,SAAO,QAAQ,IAAI,mBACf,GAAG,QAAQ,IAAI,gBAAgB,qBAC/B;AACN;AAEA,SAAS,mBAA2B;AAClC,SAAO,QAAQ,IAAI,kBAAkB;AACvC;AAMA,eAAe,aAAa,SAAkC,MAA+B;AAC3F,QAAM,MAAM,gBAAgB;AAI5B,QAAM,WAAW;AACjB,MAAI;AACJ,WAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC;AAAA,MACxC,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,YAAI,SAAS,SAAS,IAAK,OAAM,IAAI,MAAM,uBAAuB,SAAS,MAAM,MAAM,IAAI,EAAE;AAC7F,kBAAU,IAAI,MAAM,uBAAuB,SAAS,MAAM,MAAM,IAAI,EAAE;AAAA,MACxE,OAAO;AACL,cAAM,SAAU,MAAM,SAAS,KAAK;AACpC,eAAO,UAAU,OAAO,QAAQ;AAAA,MAClC;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,SAAS,IAAI,QAAQ,WAAW,uBAAuB,EAAG,OAAM;AACnF,gBAAU;AAAA,IACZ;AACA,QAAI,IAAI,WAAW,EAAG,OAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,OAAQ,IAAI,EAAE,CAAC;AAAA,EAC9E;AACA,QAAM,mBAAmB,QAAQ,UAAU,IAAI,MAAM,uBAAuB,OAAO,OAAO,CAAC,EAAE;AAC/F;AAOA,eAAsB,QAAQ,SAAkC,MAA+B;AAC7F,SAAO,aAAa,SAAS,IAAI;AACnC;AAMA,eAAsB,eAAe,UAAyC;AAC5E,QAAM,OAAO,iBAAiB,SAAS,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG,CAAC;AAC9E,SAAO,aAAa,UAAgD,IAAI;AAC1E;AAMA,eAAsB,cAAc,SAAwC;AAC1E,QAAM,UAAU,iBAAiB;AACjC,MAAI;AAEJ,MAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,UAAM,QAAQ,MAAM,CAAC;AAAA,EACvB,WAAW,QAAQ,WAAW,IAAI,KAAK,QAAQ,WAAW,MAAM,GAAG;AACjE,UAAM;AAAA,EACR,OAAO;AACL,UAAM,IAAI,MAAM,qBAAqB,OAAO,EAAE;AAAA,EAChD;AAEA,QAAM,MAAM,GAAG,OAAO,SAAS,GAAG;AAClC,QAAM,WAAW,MAAM,MAAM,GAAG;AAEhC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,iCAAiC,GAAG,KAAK,SAAS,MAAM,GAAG;AAAA,EAC7E;AAEA,SAAQ,MAAM,SAAS,KAAK;AAC9B;AAMA,eAAsB,uBACpB,KACuD;AACvD,MAAI;AACF,QAAI,IAAI,WAAW,+BAA+B,GAAG;AACnD,YAAM,MAAM,IAAI,MAAM,gCAAgC,MAAM;AAC5D,YAAM,OAAO,KAAK,MAAM,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,OAAO,CAAC;AACpE,aAAO,EAAE,MAAM,KAAK,QAAQ,IAAI,aAAa,KAAK,eAAe,GAAG;AAAA,IACtE;AACA,UAAM,OAAO,MAAM,cAAc,GAAG;AACpC,WAAO,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,YAAY;AAAA,EAC1D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}