@sherwoodagent/cli 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,350 @@
1
+ import {
2
+ ENS,
3
+ ERC20_ABI,
4
+ L2_REGISTRY_ABI,
5
+ SHERWOOD,
6
+ SYNDICATE_FACTORY_ABI,
7
+ SYNDICATE_VAULT_ABI
8
+ } from "./chunk-FGSXRXCZ.js";
9
+ import {
10
+ getAccount,
11
+ getChain,
12
+ getChainContracts,
13
+ getPublicClient,
14
+ getWalletClient
15
+ } from "./chunk-QMWMT6EH.js";
16
+
17
+ // src/lib/vault.ts
18
+ import { formatUnits, encodeFunctionData, decodeFunctionResult } from "viem";
19
+ var _vaultOverride = null;
20
+ function setVaultAddress(addr) {
21
+ _vaultOverride = addr;
22
+ }
23
+ function getVaultAddress() {
24
+ if (_vaultOverride) return _vaultOverride;
25
+ const chainId = getChain().id;
26
+ const fromConfig = getChainContracts(chainId).vault;
27
+ if (fromConfig) return fromConfig;
28
+ throw new Error(
29
+ "Vault address not found. Pass --vault <addr> or run 'sherwood config set --vault <addr>'."
30
+ );
31
+ }
32
+ async function getAssetAddress() {
33
+ const client = getPublicClient();
34
+ return client.readContract({
35
+ address: getVaultAddress(),
36
+ abi: SYNDICATE_VAULT_ABI,
37
+ functionName: "asset"
38
+ });
39
+ }
40
+ async function getAssetDecimals() {
41
+ const client = getPublicClient();
42
+ const asset = await getAssetAddress();
43
+ return client.readContract({
44
+ address: asset,
45
+ abi: ERC20_ABI,
46
+ functionName: "decimals"
47
+ });
48
+ }
49
+ async function deposit(amount) {
50
+ const wallet = getWalletClient();
51
+ const client = getPublicClient();
52
+ const vaultAddress = getVaultAddress();
53
+ const account = getAccount();
54
+ const asset = await getAssetAddress();
55
+ const approveHash = await wallet.writeContract({
56
+ account: getAccount(),
57
+ chain: getChain(),
58
+ address: asset,
59
+ abi: ERC20_ABI,
60
+ functionName: "approve",
61
+ args: [vaultAddress, amount]
62
+ });
63
+ await client.waitForTransactionReceipt({ hash: approveHash });
64
+ return wallet.writeContract({
65
+ account: getAccount(),
66
+ chain: getChain(),
67
+ address: vaultAddress,
68
+ abi: SYNDICATE_VAULT_ABI,
69
+ functionName: "deposit",
70
+ args: [amount, account.address]
71
+ });
72
+ }
73
+ async function ragequit() {
74
+ const wallet = getWalletClient();
75
+ const account = getAccount();
76
+ return wallet.writeContract({
77
+ account: getAccount(),
78
+ chain: getChain(),
79
+ address: getVaultAddress(),
80
+ abi: SYNDICATE_VAULT_ABI,
81
+ functionName: "ragequit",
82
+ args: [account.address]
83
+ });
84
+ }
85
+ async function executeBatch(calls) {
86
+ const wallet = getWalletClient();
87
+ return wallet.writeContract({
88
+ account: getAccount(),
89
+ chain: getChain(),
90
+ address: getVaultAddress(),
91
+ abi: SYNDICATE_VAULT_ABI,
92
+ functionName: "executeBatch",
93
+ args: [
94
+ calls.map((c) => ({
95
+ target: c.target,
96
+ data: c.data,
97
+ value: c.value
98
+ }))
99
+ ]
100
+ });
101
+ }
102
+ async function simulateBatch(calls) {
103
+ const client = getPublicClient();
104
+ const vaultAddress = getVaultAddress();
105
+ const calldata = encodeFunctionData({
106
+ abi: SYNDICATE_VAULT_ABI,
107
+ functionName: "simulateBatch",
108
+ args: [
109
+ calls.map((c) => ({
110
+ target: c.target,
111
+ data: c.data,
112
+ value: c.value
113
+ }))
114
+ ]
115
+ });
116
+ const { data } = await client.call({
117
+ to: vaultAddress,
118
+ data: calldata
119
+ });
120
+ if (!data) {
121
+ throw new Error("simulateBatch returned no data");
122
+ }
123
+ const decoded = decodeFunctionResult({
124
+ abi: SYNDICATE_VAULT_ABI,
125
+ functionName: "simulateBatch",
126
+ data
127
+ });
128
+ return decoded.map((r) => ({
129
+ success: r.success,
130
+ returnData: r.returnData
131
+ }));
132
+ }
133
+ async function approveDepositor(depositor) {
134
+ const wallet = getWalletClient();
135
+ return wallet.writeContract({
136
+ account: getAccount(),
137
+ chain: getChain(),
138
+ address: getVaultAddress(),
139
+ abi: SYNDICATE_VAULT_ABI,
140
+ functionName: "approveDepositor",
141
+ args: [depositor]
142
+ });
143
+ }
144
+ async function removeDepositor(depositor) {
145
+ const wallet = getWalletClient();
146
+ return wallet.writeContract({
147
+ account: getAccount(),
148
+ chain: getChain(),
149
+ address: getVaultAddress(),
150
+ abi: SYNDICATE_VAULT_ABI,
151
+ functionName: "removeDepositor",
152
+ args: [depositor]
153
+ });
154
+ }
155
+ async function isAgent(pkpAddress) {
156
+ const client = getPublicClient();
157
+ return client.readContract({
158
+ address: getVaultAddress(),
159
+ abi: SYNDICATE_VAULT_ABI,
160
+ functionName: "isAgent",
161
+ args: [pkpAddress]
162
+ });
163
+ }
164
+ async function getBalance(address) {
165
+ const client = getPublicClient();
166
+ const vaultAddress = getVaultAddress();
167
+ const account = address || getAccount().address;
168
+ const [shares, totalSupply] = await Promise.all([
169
+ client.readContract({
170
+ address: vaultAddress,
171
+ abi: SYNDICATE_VAULT_ABI,
172
+ functionName: "balanceOf",
173
+ args: [account]
174
+ }),
175
+ client.readContract({
176
+ address: vaultAddress,
177
+ abi: SYNDICATE_VAULT_ABI,
178
+ functionName: "totalSupply"
179
+ })
180
+ ]);
181
+ let assetsValue = 0n;
182
+ if (shares > 0n) {
183
+ assetsValue = await client.readContract({
184
+ address: vaultAddress,
185
+ abi: SYNDICATE_VAULT_ABI,
186
+ functionName: "convertToAssets",
187
+ args: [shares]
188
+ });
189
+ }
190
+ const percent = totalSupply > 0n ? (Number(shares) / Number(totalSupply) * 100).toFixed(2) : "0.00";
191
+ const decimals = await getAssetDecimals();
192
+ return {
193
+ shares,
194
+ assetsValue: formatUnits(assetsValue, decimals),
195
+ percentOfVault: `${percent}%`
196
+ };
197
+ }
198
+ async function registerAgent(agentId, pkpAddress, operatorEOA) {
199
+ const wallet = getWalletClient();
200
+ const client = getPublicClient();
201
+ const hash = await wallet.writeContract({
202
+ account: getAccount(),
203
+ chain: getChain(),
204
+ address: getVaultAddress(),
205
+ abi: SYNDICATE_VAULT_ABI,
206
+ functionName: "registerAgent",
207
+ args: [agentId, pkpAddress, operatorEOA]
208
+ });
209
+ await client.waitForTransactionReceipt({ hash });
210
+ return hash;
211
+ }
212
+ async function getVaultInfo() {
213
+ const client = getPublicClient();
214
+ const vaultAddress = getVaultAddress();
215
+ const [totalAssets, agentCount, redemptionsLocked, managementFeeBps, decimals] = await Promise.all([
216
+ client.readContract({
217
+ address: vaultAddress,
218
+ abi: SYNDICATE_VAULT_ABI,
219
+ functionName: "totalAssets"
220
+ }),
221
+ client.readContract({
222
+ address: vaultAddress,
223
+ abi: SYNDICATE_VAULT_ABI,
224
+ functionName: "getAgentCount"
225
+ }),
226
+ client.readContract({
227
+ address: vaultAddress,
228
+ abi: SYNDICATE_VAULT_ABI,
229
+ functionName: "redemptionsLocked"
230
+ }),
231
+ client.readContract({
232
+ address: vaultAddress,
233
+ abi: SYNDICATE_VAULT_ABI,
234
+ functionName: "managementFeeBps"
235
+ }),
236
+ getAssetDecimals()
237
+ ]);
238
+ return {
239
+ address: vaultAddress,
240
+ totalAssets: formatUnits(totalAssets, decimals),
241
+ agentCount,
242
+ redemptionsLocked,
243
+ managementFeeBps
244
+ };
245
+ }
246
+
247
+ // src/lib/ens.ts
248
+ import { encodeFunctionData as encodeFunctionData2 } from "viem";
249
+ import { namehash } from "viem/ens";
250
+ var ENS_DOMAIN = "sherwoodagent.eth";
251
+ function getFactoryAddress() {
252
+ return SHERWOOD().FACTORY;
253
+ }
254
+ async function resolveSyndicate(subdomain) {
255
+ const client = getPublicClient();
256
+ const factory = getFactoryAddress();
257
+ const syndicateId = await client.readContract({
258
+ address: factory,
259
+ abi: SYNDICATE_FACTORY_ABI,
260
+ functionName: "subdomainToSyndicate",
261
+ args: [subdomain]
262
+ });
263
+ if (syndicateId === 0n) {
264
+ throw new Error(`Syndicate "${subdomain}" not found`);
265
+ }
266
+ const result = await client.readContract({
267
+ address: factory,
268
+ abi: SYNDICATE_FACTORY_ABI,
269
+ functionName: "syndicates",
270
+ args: [syndicateId]
271
+ });
272
+ return {
273
+ id: result[0],
274
+ vault: result[1],
275
+ creator: result[2],
276
+ subdomain: result[6]
277
+ };
278
+ }
279
+ async function resolveVaultSyndicate(vaultAddress) {
280
+ const client = getPublicClient();
281
+ const factory = getFactoryAddress();
282
+ const syndicateId = await client.readContract({
283
+ address: factory,
284
+ abi: SYNDICATE_FACTORY_ABI,
285
+ functionName: "vaultToSyndicate",
286
+ args: [vaultAddress]
287
+ });
288
+ if (syndicateId === 0n) {
289
+ throw new Error(`No syndicate found for vault ${vaultAddress}`);
290
+ }
291
+ const result = await client.readContract({
292
+ address: factory,
293
+ abi: SYNDICATE_FACTORY_ABI,
294
+ functionName: "syndicates",
295
+ args: [syndicateId]
296
+ });
297
+ return {
298
+ id: result[0],
299
+ vault: result[1],
300
+ creator: result[2],
301
+ subdomain: result[6]
302
+ };
303
+ }
304
+ function getSubdomainNode(subdomain) {
305
+ return namehash(`${subdomain}.${ENS_DOMAIN}`);
306
+ }
307
+ async function setTextRecord(subdomain, key, value, vaultAddress) {
308
+ const l2Registry = ENS().L2_REGISTRY;
309
+ const node = getSubdomainNode(subdomain);
310
+ setVaultAddress(vaultAddress);
311
+ const setTextData = encodeFunctionData2({
312
+ abi: L2_REGISTRY_ABI,
313
+ functionName: "setText",
314
+ args: [node, key, value]
315
+ });
316
+ return executeBatch(
317
+ [{ target: l2Registry, data: setTextData, value: 0n }]
318
+ );
319
+ }
320
+ async function getTextRecord(subdomain, key) {
321
+ const client = getPublicClient();
322
+ const node = getSubdomainNode(subdomain);
323
+ return client.readContract({
324
+ address: ENS().L2_REGISTRY,
325
+ abi: L2_REGISTRY_ABI,
326
+ functionName: "text",
327
+ args: [node, key]
328
+ });
329
+ }
330
+
331
+ export {
332
+ setVaultAddress,
333
+ getVaultAddress,
334
+ getAssetDecimals,
335
+ deposit,
336
+ ragequit,
337
+ executeBatch,
338
+ simulateBatch,
339
+ approveDepositor,
340
+ removeDepositor,
341
+ isAgent,
342
+ getBalance,
343
+ registerAgent,
344
+ getVaultInfo,
345
+ resolveSyndicate,
346
+ resolveVaultSyndicate,
347
+ setTextRecord,
348
+ getTextRecord
349
+ };
350
+ //# sourceMappingURL=chunk-P4OHGJ3Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/vault.ts","../src/lib/ens.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, encodeFunctionData, decodeFunctionResult } from \"viem\";\nimport { getChain, getNetwork } from \"./network.js\";\nimport { getPublicClient, getWalletClient, getAccount } from \"./client.js\";\nimport { SYNDICATE_VAULT_ABI, ERC20_ABI } from \"./abis.js\";\nimport type { BatchCall } from \"./batch.js\";\nimport { getChainContracts } from \"./config.js\";\n\nexport interface SimulationResult {\n success: boolean;\n returnData: Hex;\n}\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// ── LP Functions ──\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 wallet = getWalletClient();\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n const account = getAccount();\n\n // Approve vault to pull the underlying asset\n const asset = await getAssetAddress();\n const approveHash = await wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: asset,\n abi: ERC20_ABI,\n functionName: \"approve\",\n args: [vaultAddress, amount],\n });\n await client.waitForTransactionReceipt({ hash: approveHash });\n\n // Deposit\n return wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"deposit\",\n args: [amount, account.address],\n });\n}\n\n/**\n * Ragequit — withdraw all shares for pro-rata assets.\n */\nexport async function ragequit(): Promise<Hex> {\n const wallet = getWalletClient();\n const account = getAccount();\n\n return wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"ragequit\",\n args: [account.address],\n });\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 wallet = getWalletClient();\n\n return wallet.writeContract({\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}\n\n/**\n * Simulate a batch via eth_call (no state committed).\n * simulateBatch is NOT a view function — must use raw eth_call.\n * Anyone can call this (no agent check).\n */\nexport async function simulateBatch(calls: BatchCall[]): Promise<SimulationResult[]> {\n const client = getPublicClient();\n const vaultAddress = getVaultAddress();\n\n const calldata = encodeFunctionData({\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"simulateBatch\",\n args: [\n calls.map((c) => ({\n target: c.target,\n data: c.data,\n value: c.value,\n })),\n ],\n });\n\n const { data } = await client.call({\n to: vaultAddress,\n data: calldata,\n });\n\n if (!data) {\n throw new Error(\"simulateBatch returned no data\");\n }\n\n const decoded = decodeFunctionResult({\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"simulateBatch\",\n data,\n });\n\n return (decoded as readonly { success: boolean; returnData: Hex }[]).map((r) => ({\n success: r.success,\n returnData: r.returnData,\n }));\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 wallet = getWalletClient();\n return wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"approveDepositor\",\n args: [depositor],\n });\n}\n\n/**\n * Remove a depositor from the whitelist (owner only).\n */\nexport async function removeDepositor(depositor: Address): Promise<Hex> {\n const wallet = getWalletClient();\n return wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"removeDepositor\",\n args: [depositor],\n });\n}\n\n/**\n * Approve multiple depositors in a batch (owner only).\n */\nexport async function approveDepositors(depositors: Address[]): Promise<Hex> {\n const wallet = getWalletClient();\n return wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"approveDepositors\",\n args: [depositors],\n });\n}\n\n/**\n * Check if a PKP address is a registered agent on the vault.\n */\nexport async function isAgent(pkpAddress: Address): Promise<boolean> {\n const client = getPublicClient();\n return client.readContract({\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"isAgent\",\n args: [pkpAddress],\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 pkpAddress: Address,\n operatorEOA: Address,\n): Promise<Hex> {\n const wallet = getWalletClient();\n const client = getPublicClient();\n\n const hash = await wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: getVaultAddress(),\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"registerAgent\",\n args: [agentId, pkpAddress, operatorEOA],\n });\n\n await client.waitForTransactionReceipt({ 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","/**\n * ENS resolution + text records via Durin L2Registry on Base.\n *\n * Two responsibilities:\n * 1. Resolve syndicate subdomain → on-chain syndicate data (via factory)\n * 2. Read/write ENS text records (via L2Registry, routed through vault's executeBatch)\n *\n * The vault owns the ENS subdomain node (registered in the factory via\n * `ensRegistrar.register(subdomain, vault)`). Only the vault can write text\n * records. We route writes through vault.executeBatch → L2Registry.setText,\n * which executes as the vault via delegatecall to the shared executor lib.\n */\n\nimport { encodeFunctionData } from \"viem\";\nimport type { Address, Hex } from \"viem\";\nimport { namehash } from \"viem/ens\";\nimport { getPublicClient, getWalletClient, getAccount } from \"./client.js\";\nimport { getChain, getNetwork } from \"./network.js\";\nimport { SYNDICATE_FACTORY_ABI, L2_REGISTRY_ABI } from \"./abis.js\";\nimport { ENS, SHERWOOD } from \"./addresses.js\";\nimport * as vaultLib from \"./vault.js\";\n\n/**\n * Wait for a transaction to be mined before proceeding.\n */\nasync function waitForTx(hash: Hex): Promise<void> {\n const client = getPublicClient();\n await client.waitForTransactionReceipt({ hash });\n}\n\nconst ENS_DOMAIN = \"sherwoodagent.eth\";\n\nfunction getFactoryAddress(): Address {\n return SHERWOOD().FACTORY;\n}\n\n// ── Syndicate Resolution (via factory) ──\n\nexport interface SyndicateResolution {\n id: bigint;\n vault: Address;\n creator: Address;\n subdomain: string;\n}\n\n/**\n * Resolve a syndicate subdomain to its on-chain data.\n * Uses factory.subdomainToSyndicate() → factory.syndicates().\n */\nexport async function resolveSyndicate(subdomain: string): Promise<SyndicateResolution> {\n const client = getPublicClient();\n const factory = getFactoryAddress();\n\n // Get syndicate ID from subdomain\n const syndicateId = (await client.readContract({\n address: factory,\n abi: SYNDICATE_FACTORY_ABI,\n functionName: \"subdomainToSyndicate\",\n args: [subdomain],\n })) as bigint;\n\n if (syndicateId === 0n) {\n throw new Error(`Syndicate \"${subdomain}\" not found`);\n }\n\n // Get full syndicate record\n const result = (await client.readContract({\n address: factory,\n abi: SYNDICATE_FACTORY_ABI,\n functionName: \"syndicates\",\n args: [syndicateId],\n })) as [bigint, Address, Address, string, bigint, boolean, string];\n\n return {\n id: result[0],\n vault: result[1],\n creator: result[2],\n subdomain: result[6],\n };\n}\n\n/**\n * Reverse lookup: vault address → syndicate info.\n * Uses factory.vaultToSyndicate() → factory.syndicates().\n */\nexport async function resolveVaultSyndicate(\n vaultAddress: Address,\n): Promise<SyndicateResolution> {\n const client = getPublicClient();\n const factory = getFactoryAddress();\n\n const syndicateId = (await client.readContract({\n address: factory,\n abi: SYNDICATE_FACTORY_ABI,\n functionName: \"vaultToSyndicate\",\n args: [vaultAddress],\n })) as bigint;\n\n if (syndicateId === 0n) {\n throw new Error(`No syndicate found for vault ${vaultAddress}`);\n }\n\n const result = (await client.readContract({\n address: factory,\n abi: SYNDICATE_FACTORY_ABI,\n functionName: \"syndicates\",\n args: [syndicateId],\n })) as [bigint, Address, Address, string, bigint, boolean, string];\n\n return {\n id: result[0],\n vault: result[1],\n creator: result[2],\n subdomain: result[6],\n };\n}\n\n// ── ENS Text Records (via L2Registry) ──\n\n/**\n * Compute the ENS node hash for a subdomain under sherwoodagent.eth.\n */\nfunction getSubdomainNode(subdomain: string): Hex {\n return namehash(`${subdomain}.${ENS_DOMAIN}`);\n}\n\n/**\n * Write a text record to the L2Registry via the vault's executeBatch.\n *\n * The vault owns the ENS node, so only the vault can call setText.\n * We route the call through vault.executeBatch (delegatecall → executor lib → L2Registry),\n * which means L2Registry sees msg.sender = vault address.\n *\n * Requires the caller to be a registered agent on the vault (creator is auto-registered).\n */\nexport async function setTextRecord(\n subdomain: string,\n key: string,\n value: string,\n vaultAddress: Address,\n): Promise<Hex> {\n const l2Registry = ENS().L2_REGISTRY;\n const node = getSubdomainNode(subdomain);\n\n vaultLib.setVaultAddress(vaultAddress);\n\n // Encode the L2Registry.setText call\n const setTextData = encodeFunctionData({\n abi: L2_REGISTRY_ABI,\n functionName: \"setText\",\n args: [node, key, value],\n });\n\n // Route through vault.executeBatch (owner only)\n return vaultLib.executeBatch(\n [{ target: l2Registry, data: setTextData, value: 0n }],\n );\n}\n\n/**\n * Read a text record from the L2Registry.\n * Used to look up xmtpGroupId when not cached locally.\n */\nexport async function getTextRecord(\n subdomain: string,\n key: string,\n): Promise<string> {\n const client = getPublicClient();\n const node = getSubdomainNode(subdomain);\n\n return client.readContract({\n address: ENS().L2_REGISTRY,\n abi: L2_REGISTRY_ABI,\n functionName: \"text\",\n args: [node, key],\n }) as Promise<string>;\n}\n"],"mappings":"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","names":["encodeFunctionData","encodeFunctionData"]}
@@ -0,0 +1,149 @@
1
+ // src/lib/network.ts
2
+ import { base, baseSepolia } from "viem/chains";
3
+ var _network = "base";
4
+ function setNetwork(n) {
5
+ _network = n;
6
+ }
7
+ function getNetwork() {
8
+ return _network;
9
+ }
10
+ function getChain() {
11
+ return _network === "base" ? base : baseSepolia;
12
+ }
13
+ function getRpcUrl() {
14
+ if (_network === "base-sepolia") {
15
+ return process.env.BASE_SEPOLIA_RPC_URL || "https://sepolia.base.org";
16
+ }
17
+ return process.env.BASE_RPC_URL || "https://mainnet.base.org";
18
+ }
19
+ function getExplorerUrl(txHash) {
20
+ const host = _network === "base" ? "basescan.org" : "sepolia.basescan.org";
21
+ return `https://${host}/tx/${txHash}`;
22
+ }
23
+
24
+ // src/lib/config.ts
25
+ import fs from "fs";
26
+ import path from "path";
27
+ var CONFIG_DIR = path.join(process.env.HOME || "~", ".sherwood");
28
+ var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
29
+ function loadConfig() {
30
+ if (fs.existsSync(CONFIG_PATH)) {
31
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
32
+ }
33
+ const config = { groupCache: {} };
34
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
35
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
36
+ return config;
37
+ }
38
+ function saveConfig(config) {
39
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
40
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
41
+ }
42
+ function cacheGroupId(subdomain, groupId) {
43
+ const config = loadConfig();
44
+ config.groupCache[subdomain] = groupId;
45
+ saveConfig(config);
46
+ }
47
+ function getCachedGroupId(subdomain) {
48
+ const config = loadConfig();
49
+ return config.groupCache[subdomain];
50
+ }
51
+ function setVeniceApiKey(apiKey) {
52
+ const config = loadConfig();
53
+ config.veniceApiKey = apiKey;
54
+ saveConfig(config);
55
+ }
56
+ function getVeniceApiKey() {
57
+ return loadConfig().veniceApiKey;
58
+ }
59
+ function setAgentId(agentId) {
60
+ const config = loadConfig();
61
+ config.agentId = agentId;
62
+ saveConfig(config);
63
+ }
64
+ function getAgentId() {
65
+ return loadConfig().agentId;
66
+ }
67
+ function setPrivateKey(key) {
68
+ const config = loadConfig();
69
+ config.privateKey = key.startsWith("0x") ? key : `0x${key}`;
70
+ saveConfig(config);
71
+ }
72
+ function getChainContracts(chainId) {
73
+ const config = loadConfig();
74
+ return config.contracts?.[String(chainId)] ?? {};
75
+ }
76
+ function setChainContract(chainId, key, value) {
77
+ const config = loadConfig();
78
+ if (!config.contracts) config.contracts = {};
79
+ const cid = String(chainId);
80
+ if (!config.contracts[cid]) config.contracts[cid] = {};
81
+ config.contracts[cid][key] = value;
82
+ saveConfig(config);
83
+ }
84
+
85
+ // src/lib/client.ts
86
+ import { createPublicClient, createWalletClient, http } from "viem";
87
+ import { privateKeyToAccount } from "viem/accounts";
88
+ var _publicClient = null;
89
+ var _walletClient = null;
90
+ function getPrivateKey() {
91
+ const config = loadConfig();
92
+ if (config.privateKey) {
93
+ const k = config.privateKey;
94
+ return k.startsWith("0x") ? k : `0x${k}`;
95
+ }
96
+ const env = process.env.PRIVATE_KEY;
97
+ if (env) {
98
+ return env.startsWith("0x") ? env : `0x${env}`;
99
+ }
100
+ throw new Error(
101
+ "Private key not found. Run 'sherwood config set --private-key <key>' or set PRIVATE_KEY env var."
102
+ );
103
+ }
104
+ function getPublicClient() {
105
+ if (!_publicClient) {
106
+ _publicClient = createPublicClient({
107
+ chain: getChain(),
108
+ transport: http(getRpcUrl())
109
+ });
110
+ }
111
+ return _publicClient;
112
+ }
113
+ function getWalletClient() {
114
+ if (!_walletClient) {
115
+ const account = privateKeyToAccount(getPrivateKey());
116
+ _walletClient = createWalletClient({
117
+ account,
118
+ chain: getChain(),
119
+ transport: http(getRpcUrl())
120
+ });
121
+ }
122
+ return _walletClient;
123
+ }
124
+ function getAccount() {
125
+ return privateKeyToAccount(getPrivateKey());
126
+ }
127
+
128
+ export {
129
+ setNetwork,
130
+ getNetwork,
131
+ getChain,
132
+ getRpcUrl,
133
+ getExplorerUrl,
134
+ loadConfig,
135
+ saveConfig,
136
+ cacheGroupId,
137
+ getCachedGroupId,
138
+ setVeniceApiKey,
139
+ getVeniceApiKey,
140
+ setAgentId,
141
+ getAgentId,
142
+ setPrivateKey,
143
+ getChainContracts,
144
+ setChainContract,
145
+ getPublicClient,
146
+ getWalletClient,
147
+ getAccount
148
+ };
149
+ //# sourceMappingURL=chunk-QMWMT6EH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/network.ts","../src/lib/config.ts","../src/lib/client.ts"],"sourcesContent":["/**\n * Network state singleton.\n *\n * Called once at CLI startup via the --testnet flag's preAction hook.\n * Every other module reads from here — never hardcodes a chain.\n */\n\nimport { base, baseSepolia } from \"viem/chains\";\n\nexport type Network = \"base\" | \"base-sepolia\";\n\nlet _network: Network = \"base\";\n\nexport function setNetwork(n: Network) {\n _network = n;\n}\n\nexport function getNetwork(): Network {\n return _network;\n}\n\nexport function getChain() {\n return _network === \"base\" ? base : baseSepolia;\n}\n\nexport function getRpcUrl(): string {\n if (_network === \"base-sepolia\") {\n return process.env.BASE_SEPOLIA_RPC_URL || \"https://sepolia.base.org\";\n }\n return process.env.BASE_RPC_URL || \"https://mainnet.base.org\";\n}\n\nexport function getExplorerUrl(txHash: string): string {\n const host = _network === \"base\" ? \"basescan.org\" : \"sepolia.basescan.org\";\n return `https://${host}/tx/${txHash}`;\n}\n\nexport function isTestnet(): boolean {\n return _network === \"base-sepolia\";\n}\n","/**\n * Local config management — ~/.sherwood/config.json\n *\n * Stores group ID cache, per-chain contract addresses, and wallet config.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nconst CONFIG_DIR = path.join(process.env.HOME || \"~\", \".sherwood\");\nconst CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\n\n/** Per-chain user-specific addresses (stored by chainId). */\nexport interface ChainContracts {\n vault?: string; // user's default vault address\n}\n\nexport interface SherwoodConfig {\n dbEncryptionKey?: string; // legacy — no longer used, XMTP CLI manages its own DB\n privateKey?: string; // wallet private key (0x-prefixed)\n xmtpInboxId?: string;\n groupCache: Record<string, string>; // subdomain → XMTP group ID\n veniceApiKey?: string; // Venice AI inference API key\n agentId?: number; // ERC-8004 identity token ID\n contracts?: Record<string, ChainContracts>; // chainId → user addresses\n}\n\nexport function loadConfig(): SherwoodConfig {\n if (fs.existsSync(CONFIG_PATH)) {\n return JSON.parse(fs.readFileSync(CONFIG_PATH, \"utf-8\"));\n }\n\n const config: SherwoodConfig = { groupCache: {} };\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n return config;\n}\n\nexport function saveConfig(config: SherwoodConfig): void {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));\n}\n\nexport function cacheGroupId(subdomain: string, groupId: string): void {\n const config = loadConfig();\n config.groupCache[subdomain] = groupId;\n saveConfig(config);\n}\n\nexport function getCachedGroupId(subdomain: string): string | undefined {\n const config = loadConfig();\n return config.groupCache[subdomain];\n}\n\nexport function setVeniceApiKey(apiKey: string): void {\n const config = loadConfig();\n config.veniceApiKey = apiKey;\n saveConfig(config);\n}\n\nexport function getVeniceApiKey(): string | undefined {\n return loadConfig().veniceApiKey;\n}\n\nexport function setAgentId(agentId: number): void {\n const config = loadConfig();\n config.agentId = agentId;\n saveConfig(config);\n}\n\nexport function getAgentId(): number | undefined {\n return loadConfig().agentId;\n}\n\nexport function setPrivateKey(key: string): void {\n const config = loadConfig();\n config.privateKey = key.startsWith(\"0x\") ? key : `0x${key}`;\n saveConfig(config);\n}\n\nexport function getPrivateKey(): string | undefined {\n return loadConfig().privateKey;\n}\n\n// ── Per-chain contract addresses ──\n\nexport function getChainContracts(chainId: number): ChainContracts {\n const config = loadConfig();\n return config.contracts?.[String(chainId)] ?? {};\n}\n\nexport function setChainContract(\n chainId: number,\n key: keyof ChainContracts,\n value: string,\n): void {\n const config = loadConfig();\n if (!config.contracts) config.contracts = {};\n const cid = String(chainId);\n if (!config.contracts[cid]) config.contracts[cid] = {};\n config.contracts[cid][key] = value;\n saveConfig(config);\n}\n","/**\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 { createPublicClient, createWalletClient, http } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { getChain, getRpcUrl } from \"./network.js\";\nimport { loadConfig } from \"./config.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\nexport function getPublicClient() {\n if (!_publicClient) {\n _publicClient = createPublicClient({\n chain: getChain(),\n transport: http(getRpcUrl()),\n });\n }\n return _publicClient as ReturnType<typeof createPublicClient>;\n}\n\nexport function getWalletClient() {\n if (!_walletClient) {\n const account = privateKeyToAccount(getPrivateKey());\n _walletClient = createWalletClient({\n account,\n chain: getChain(),\n transport: http(getRpcUrl()),\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"],"mappings":"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","names":[]}