@agether/sdk 1.11.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,2375 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // src/types/index.ts
34
+ var AgetherError;
35
+ var init_types = __esm({
36
+ "src/types/index.ts"() {
37
+ "use strict";
38
+ AgetherError = class extends Error {
39
+ constructor(message, code, details) {
40
+ super(message);
41
+ this.code = code;
42
+ this.details = details;
43
+ this.name = "AgetherError";
44
+ }
45
+ };
46
+ }
47
+ });
48
+
49
+ // src/utils/abis.ts
50
+ var IDENTITY_REGISTRY_ABI, SAFE_AGENT_FACTORY_ABI, ACCOUNT_FACTORY_ABI, ERC8004_VALIDATION_MODULE_ABI, AGENT_REPUTATION_ABI, MORPHO_BLUE_ABI, ERC20_ABI, ENTRYPOINT_V07_ABI, SAFE7579_ACCOUNT_ABI;
51
+ var init_abis = __esm({
52
+ "src/utils/abis.ts"() {
53
+ "use strict";
54
+ IDENTITY_REGISTRY_ABI = [
55
+ "function ownerOf(uint256 agentId) view returns (address)",
56
+ "function balanceOf(address owner) view returns (uint256)",
57
+ "function totalSupply() view returns (uint256)",
58
+ "function exists(uint256 agentId) view returns (bool)",
59
+ "function register() returns (uint256 agentId)",
60
+ "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
61
+ ];
62
+ SAFE_AGENT_FACTORY_ABI = [
63
+ "function getAccount(uint256 agentId) view returns (address)",
64
+ "function accountExists(uint256 agentId) view returns (bool)",
65
+ "function totalAccounts() view returns (uint256)",
66
+ "function getAgentId(address account) view returns (uint256)",
67
+ "function getAgentIdByIndex(uint256 index) view returns (uint256)",
68
+ "function getAllAgentIds() view returns (uint256[])",
69
+ "function createAccount(uint256 agentId) returns (address safeAccount)",
70
+ "function identityRegistry() view returns (address)",
71
+ "function validationModule() view returns (address)",
72
+ "function hookMultiplexer() view returns (address)",
73
+ "function safeSingleton() view returns (address)",
74
+ "function safe7579() view returns (address)",
75
+ "function bootstrap() view returns (address)",
76
+ "function SENTINEL_OWNER() view returns (address)",
77
+ "event AccountCreated(uint256 indexed agentId, address indexed safeAccount, address indexed owner)"
78
+ ];
79
+ ACCOUNT_FACTORY_ABI = SAFE_AGENT_FACTORY_ABI;
80
+ ERC8004_VALIDATION_MODULE_ABI = [
81
+ // View
82
+ "function getConfig(address account) view returns (address registry, uint256 agentId)",
83
+ "function getOwner(address account) view returns (address)",
84
+ "function isInstalled(address account) view returns (bool)",
85
+ "function isKYAApproved(address account) view returns (bool)",
86
+ "function validationRegistry() view returns (address)",
87
+ "function owner() view returns (address)",
88
+ // Admin (via TimelockController)
89
+ "function setValidationRegistry(address registry_)"
90
+ ];
91
+ AGENT_REPUTATION_ABI = [
92
+ "function getCreditScore(uint256 agentId) view returns (uint256)",
93
+ "function getAttestation(uint256 agentId) view returns (tuple(uint256 score, uint256 timestamp, address signer))",
94
+ "function isScoreFresh(uint256 agentId) view returns (bool fresh, uint256 age)",
95
+ "function isEligible(uint256 agentId, uint256 minScore) view returns (bool eligible, uint256 currentScore)",
96
+ "function oracleSigner() view returns (address)",
97
+ "function submitScore(uint256 agentId, uint256 score_, uint256 timestamp_, bytes signature)",
98
+ "function setOracleSigner(address signer_)",
99
+ "event ScoreUpdated(uint256 indexed agentId, uint256 score, uint256 timestamp, address signer)"
100
+ ];
101
+ MORPHO_BLUE_ABI = [
102
+ // Supply & Withdraw (lending side)
103
+ "function supply(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) returns (uint256 assetsSupplied, uint256 sharesSupplied)",
104
+ "function withdraw(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn)",
105
+ // Collateral
106
+ "function supplyCollateral(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, bytes data)",
107
+ "function withdrawCollateral(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, address receiver)",
108
+ // Borrow & Repay
109
+ "function borrow(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver) returns (uint256 assetsBorrowed, uint256 sharesBorrowed)",
110
+ "function repay(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, uint256 shares, address onBehalf, bytes data) returns (uint256 assetsRepaid, uint256 sharesRepaid)",
111
+ // Authorization
112
+ "function setAuthorization(address authorized, bool newIsAuthorized)",
113
+ "function isAuthorized(address authorizer, address authorized) view returns (bool)",
114
+ // Views
115
+ "function position(bytes32 id, address user) view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)",
116
+ "function market(bytes32 id) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
117
+ "function idToMarketParams(bytes32 id) view returns (tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv))"
118
+ ];
119
+ ERC20_ABI = [
120
+ "function balanceOf(address account) view returns (uint256)",
121
+ "function allowance(address owner, address spender) view returns (uint256)",
122
+ "function approve(address spender, uint256 amount) returns (bool)",
123
+ "function transfer(address to, uint256 amount) returns (bool)",
124
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)",
125
+ "function decimals() view returns (uint8)",
126
+ "function symbol() view returns (string)",
127
+ "function name() view returns (string)"
128
+ ];
129
+ ENTRYPOINT_V07_ABI = [
130
+ "function handleOps(tuple(address sender, uint256 nonce, bytes initCode, bytes callData, bytes32 accountGasLimits, uint256 preVerificationGas, bytes32 gasFees, bytes paymasterAndData, bytes signature)[] ops, address payable beneficiary)",
131
+ "function getUserOpHash(tuple(address sender, uint256 nonce, bytes initCode, bytes callData, bytes32 accountGasLimits, uint256 preVerificationGas, bytes32 gasFees, bytes paymasterAndData, bytes signature) userOp) view returns (bytes32)",
132
+ "function getNonce(address sender, uint192 key) view returns (uint256 nonce)",
133
+ "function balanceOf(address account) view returns (uint256)",
134
+ "function depositTo(address account) payable",
135
+ "event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)"
136
+ ];
137
+ SAFE7579_ACCOUNT_ABI = [
138
+ // ERC-7579 execution (called via UserOp through Safe7579 fallback)
139
+ "function execute(bytes32 mode, bytes executionCalldata) payable",
140
+ "function executeFromExecutor(bytes32 mode, bytes executionCalldata) payable returns (bytes[])",
141
+ // ERC-7579 module queries
142
+ "function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)",
143
+ // EIP-1271
144
+ "function isValidSignature(bytes32 hash, bytes signature) view returns (bytes4)"
145
+ ];
146
+ }
147
+ });
148
+
149
+ // src/utils/config.ts
150
+ function getDefaultConfig(chainId) {
151
+ return {
152
+ chainId,
153
+ rpcUrl: RPC_URLS[chainId],
154
+ contracts: CONTRACT_ADDRESSES[chainId],
155
+ scoringEndpoint: SCORING_ENDPOINTS[chainId]
156
+ };
157
+ }
158
+ var ZERO, ENTRYPOINT_V07, ERC8004_IDENTITY_REGISTRY, ERC8004_IDENTITY_REGISTRY_TESTNET, MORPHO_BLUE, SAFE_SINGLETON, SAFE_PROXY_FACTORY, SAFE7579, CONTRACT_ADDRESSES, RPC_URLS, SCORING_ENDPOINTS;
159
+ var init_config = __esm({
160
+ "src/utils/config.ts"() {
161
+ "use strict";
162
+ init_types();
163
+ ZERO = "0x0000000000000000000000000000000000000000";
164
+ ENTRYPOINT_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
165
+ ERC8004_IDENTITY_REGISTRY = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432";
166
+ ERC8004_IDENTITY_REGISTRY_TESTNET = "0x8004A818BFB912233c491871b3d84c89A494BD9e";
167
+ MORPHO_BLUE = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb";
168
+ SAFE_SINGLETON = "0x41675C099F32341bf84BFc5382aF534df5C7461a";
169
+ SAFE_PROXY_FACTORY = "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67";
170
+ SAFE7579 = "0x7579EE8307284F293B1927136486880611F20002";
171
+ CONTRACT_ADDRESSES = {
172
+ [1 /* Ethereum */]: {
173
+ safeSingleton: SAFE_SINGLETON,
174
+ safeProxyFactory: SAFE_PROXY_FACTORY,
175
+ safe7579: SAFE7579,
176
+ entryPoint: ENTRYPOINT_V07,
177
+ safeAgentFactory: ZERO,
178
+ safe7579Bootstrap: ZERO,
179
+ erc8004ValidationModule: ZERO,
180
+ hookMultiplexer: ZERO,
181
+ validationRegistry: ZERO,
182
+ agentReputation: ZERO,
183
+ timelockController: ZERO,
184
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
185
+ identityRegistry: ERC8004_IDENTITY_REGISTRY,
186
+ morphoBlue: MORPHO_BLUE
187
+ },
188
+ [8453 /* Base */]: {
189
+ safeSingleton: SAFE_SINGLETON,
190
+ safeProxyFactory: SAFE_PROXY_FACTORY,
191
+ safe7579: SAFE7579,
192
+ entryPoint: ENTRYPOINT_V07,
193
+ safeAgentFactory: "0xB0A88ffe28491E793F7706829278f20d724947d1",
194
+ safe7579Bootstrap: "0x72A636bc23B2644138489c3bBE3B05a0a7184b33",
195
+ erc8004ValidationModule: "0x49e27A6B4d012B87271897b51d0296ABcFCb0BBd",
196
+ hookMultiplexer: "0x12c77f17F91f06a11C2C34C618ce9d78f9a34541",
197
+ validationRegistry: "0x88E21e8883c093E4c8d0d0cE68f1c93Cf6190f51",
198
+ agentReputation: "0x4C2d42cbD35f6541f0902499CFEC27C1Cf5683E3",
199
+ timelockController: "0x0517b4f73b61774C88A2B1c5745141315E831015",
200
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
201
+ identityRegistry: ERC8004_IDENTITY_REGISTRY,
202
+ morphoBlue: MORPHO_BLUE
203
+ },
204
+ [84532 /* BaseSepolia */]: {
205
+ safeSingleton: SAFE_SINGLETON,
206
+ safeProxyFactory: SAFE_PROXY_FACTORY,
207
+ safe7579: SAFE7579,
208
+ entryPoint: ENTRYPOINT_V07,
209
+ safeAgentFactory: ZERO,
210
+ safe7579Bootstrap: ZERO,
211
+ erc8004ValidationModule: ZERO,
212
+ hookMultiplexer: ZERO,
213
+ validationRegistry: ZERO,
214
+ agentReputation: ZERO,
215
+ timelockController: ZERO,
216
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
217
+ identityRegistry: ERC8004_IDENTITY_REGISTRY_TESTNET,
218
+ morphoBlue: ZERO
219
+ },
220
+ [11155111 /* Sepolia */]: {
221
+ safeSingleton: SAFE_SINGLETON,
222
+ safeProxyFactory: SAFE_PROXY_FACTORY,
223
+ safe7579: SAFE7579,
224
+ entryPoint: ENTRYPOINT_V07,
225
+ safeAgentFactory: ZERO,
226
+ safe7579Bootstrap: ZERO,
227
+ erc8004ValidationModule: ZERO,
228
+ hookMultiplexer: ZERO,
229
+ validationRegistry: ZERO,
230
+ agentReputation: ZERO,
231
+ timelockController: ZERO,
232
+ usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
233
+ identityRegistry: ERC8004_IDENTITY_REGISTRY_TESTNET,
234
+ morphoBlue: ZERO
235
+ },
236
+ [31337 /* Hardhat */]: {
237
+ safeSingleton: SAFE_SINGLETON,
238
+ safeProxyFactory: SAFE_PROXY_FACTORY,
239
+ safe7579: SAFE7579,
240
+ entryPoint: ENTRYPOINT_V07,
241
+ safeAgentFactory: ZERO,
242
+ safe7579Bootstrap: ZERO,
243
+ erc8004ValidationModule: ZERO,
244
+ hookMultiplexer: ZERO,
245
+ validationRegistry: ZERO,
246
+ agentReputation: ZERO,
247
+ timelockController: ZERO,
248
+ usdc: "0x56d4d6aEe0278c5Df2FA23Ecb32eC146C9446FDf",
249
+ identityRegistry: ERC8004_IDENTITY_REGISTRY,
250
+ morphoBlue: ZERO
251
+ }
252
+ };
253
+ RPC_URLS = {
254
+ [1 /* Ethereum */]: "https://ethereum-rpc.publicnode.com",
255
+ [8453 /* Base */]: "https://base-rpc.publicnode.com",
256
+ [84532 /* BaseSepolia */]: "https://sepolia.base.org",
257
+ [11155111 /* Sepolia */]: "https://rpc.sepolia.org",
258
+ [31337 /* Hardhat */]: "http://127.0.0.1:8545"
259
+ };
260
+ SCORING_ENDPOINTS = {
261
+ [1 /* Ethereum */]: "https://scoring.agether.ai/v1",
262
+ [8453 /* Base */]: "http://95.179.189.214:3001",
263
+ [84532 /* BaseSepolia */]: "http://95.179.189.214:3001",
264
+ [11155111 /* Sepolia */]: "https://scoring-testnet.agether.ai/v1",
265
+ [31337 /* Hardhat */]: "http://127.0.0.1:3001"
266
+ };
267
+ }
268
+ });
269
+
270
+ // src/clients/MorphoClient.ts
271
+ var MorphoClient_exports = {};
272
+ __export(MorphoClient_exports, {
273
+ MorphoClient: () => MorphoClient
274
+ });
275
+ var import_ethers, import_axios, BASE_COLLATERALS, MORPHO_API_URL, MODE_SINGLE, MODE_BATCH, morphoIface, erc20Iface, MorphoClient;
276
+ var init_MorphoClient = __esm({
277
+ "src/clients/MorphoClient.ts"() {
278
+ "use strict";
279
+ import_ethers = require("ethers");
280
+ import_axios = __toESM(require("axios"));
281
+ init_types();
282
+ init_abis();
283
+ init_config();
284
+ BASE_COLLATERALS = {
285
+ WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
286
+ wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
287
+ cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
288
+ };
289
+ MORPHO_API_URL = "https://api.morpho.org/graphql";
290
+ MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
291
+ MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
292
+ morphoIface = new import_ethers.ethers.Interface(MORPHO_BLUE_ABI);
293
+ erc20Iface = new import_ethers.ethers.Interface(ERC20_ABI);
294
+ MorphoClient = class {
295
+ constructor(config) {
296
+ this._marketCache = /* @__PURE__ */ new Map();
297
+ this._discoveredAt = 0;
298
+ const chainId = config.chainId ?? 8453 /* Base */;
299
+ const defaultCfg = getDefaultConfig(chainId);
300
+ this.config = defaultCfg;
301
+ this.agentId = config.agentId;
302
+ this._rpcUrl = config.rpcUrl || defaultCfg.rpcUrl;
303
+ if ("signer" in config && config.signer) {
304
+ this._useExternalSigner = true;
305
+ const signerProvider = config.signer.provider;
306
+ if (signerProvider) {
307
+ this.provider = signerProvider;
308
+ this._signer = config.signer;
309
+ } else {
310
+ this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
311
+ this._signer = config.signer.connect(this.provider);
312
+ }
313
+ if ("address" in config.signer && typeof config.signer.address === "string") {
314
+ this._eoaAddress = config.signer.address;
315
+ }
316
+ } else {
317
+ this._privateKey = config.privateKey;
318
+ this._useExternalSigner = false;
319
+ this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
320
+ const wallet = new import_ethers.ethers.Wallet(this._privateKey, this.provider);
321
+ this._signer = wallet;
322
+ this._eoaAddress = wallet.address;
323
+ }
324
+ const addrs = { ...defaultCfg.contracts, ...config.contracts };
325
+ this.safeAgentFactory = new import_ethers.Contract(addrs.safeAgentFactory, ACCOUNT_FACTORY_ABI, this._signer);
326
+ this.morphoBlue = new import_ethers.Contract(addrs.morphoBlue, MORPHO_BLUE_ABI, this.provider);
327
+ this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this._signer);
328
+ this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
329
+ this.entryPoint = new import_ethers.Contract(addrs.entryPoint, ENTRYPOINT_V07_ABI, this._signer);
330
+ this.validationModule = new import_ethers.Contract(addrs.erc8004ValidationModule, ERC8004_VALIDATION_MODULE_ABI, this.provider);
331
+ }
332
+ // ════════════════════════════════════════════════════════
333
+ // KYA Gate Check
334
+ // ════════════════════════════════════════════════════════
335
+ /**
336
+ * Check whether the KYA (Know Your Agent) code verification gate is active.
337
+ * Reads the ERC8004ValidationModule's validationRegistry — when set to
338
+ * a non-zero address, the module enforces KYA code approval.
339
+ */
340
+ async isKyaRequired() {
341
+ try {
342
+ const registryAddr = await this.validationModule.validationRegistry();
343
+ return registryAddr !== import_ethers.ethers.ZeroAddress;
344
+ } catch {
345
+ return false;
346
+ }
347
+ }
348
+ // ════════════════════════════════════════════════════════
349
+ // Account Management
350
+ // ════════════════════════════════════════════════════════
351
+ /** Resolve the AgentAccount address (cached, with retry for flaky RPCs). */
352
+ async getAccountAddress() {
353
+ if (this._accountAddress) return this._accountAddress;
354
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
355
+ const MAX_RETRIES = 3;
356
+ let lastErr;
357
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
358
+ try {
359
+ const addr = await this.safeAgentFactory.getAccount(BigInt(this.agentId));
360
+ if (addr === import_ethers.ethers.ZeroAddress) {
361
+ throw new AgetherError("No AgentAccount found. Call register() first.", "NO_ACCOUNT");
362
+ }
363
+ this._accountAddress = addr;
364
+ return addr;
365
+ } catch (err) {
366
+ if (err instanceof AgetherError) throw err;
367
+ lastErr = err;
368
+ if (attempt < MAX_RETRIES) {
369
+ await new Promise((r) => setTimeout(r, 500 * attempt));
370
+ }
371
+ }
372
+ }
373
+ throw lastErr;
374
+ }
375
+ getAgentId() {
376
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
377
+ return this.agentId;
378
+ }
379
+ /**
380
+ * Get the EOA wallet address (synchronous, best-effort).
381
+ *
382
+ * For the `privateKey` path this always works. For the `signer` path
383
+ * it works if the signer exposes `.address` synchronously (e.g. ethers.Wallet).
384
+ * If the address has not been resolved yet, throws — call `getSignerAddress()` first.
385
+ */
386
+ getWalletAddress() {
387
+ if (this._eoaAddress) return this._eoaAddress;
388
+ const signer = this._signer;
389
+ if (signer.address && typeof signer.address === "string") {
390
+ const addr = signer.address;
391
+ this._eoaAddress = addr;
392
+ return addr;
393
+ }
394
+ throw new AgetherError(
395
+ "EOA address not yet resolved. Call getSignerAddress() (async) first, or use a signer that exposes .address synchronously.",
396
+ "ADDRESS_NOT_RESOLVED"
397
+ );
398
+ }
399
+ /**
400
+ * Resolve the EOA signer address (async, works with all signer types).
401
+ * Result is cached after the first call.
402
+ */
403
+ async getSignerAddress() {
404
+ if (!this._eoaAddress) {
405
+ this._eoaAddress = await this._signer.getAddress();
406
+ }
407
+ return this._eoaAddress;
408
+ }
409
+ /** Mint a new ERC-8004 identity and return the agentId. */
410
+ async _mintNewIdentity() {
411
+ const regTx = await this.identityRegistry.register();
412
+ const regReceipt = await regTx.wait();
413
+ this._refreshSigner();
414
+ let agentId = 0n;
415
+ for (const log of regReceipt.logs) {
416
+ try {
417
+ const parsed = this.identityRegistry.interface.parseLog({ topics: log.topics, data: log.data });
418
+ if (parsed?.name === "Transfer") {
419
+ agentId = parsed.args[2];
420
+ break;
421
+ }
422
+ } catch (e) {
423
+ console.warn("[agether] parseLog skip:", e instanceof Error ? e.message : e);
424
+ continue;
425
+ }
426
+ }
427
+ if (agentId === 0n) throw new AgetherError("Failed to parse agentId from registration", "PARSE_ERROR");
428
+ return agentId;
429
+ }
430
+ /**
431
+ * Register: create ERC-8004 identity + AgentAccount in one flow.
432
+ * If already registered, returns existing state.
433
+ */
434
+ async register(_name) {
435
+ const eoaAddr = await this.getSignerAddress();
436
+ if (this.agentId) {
437
+ const exists = await this.safeAgentFactory.accountExists(BigInt(this.agentId));
438
+ if (exists) {
439
+ const acct = await this.safeAgentFactory.getAccount(BigInt(this.agentId));
440
+ this._accountAddress = acct;
441
+ const kyaRequired2 = await this.isKyaRequired();
442
+ return { agentId: this.agentId, address: eoaAddr, agentAccount: acct, alreadyRegistered: true, kyaRequired: kyaRequired2 };
443
+ }
444
+ }
445
+ let agentId;
446
+ if (this.agentId) {
447
+ const balance = await this.identityRegistry.balanceOf(eoaAddr);
448
+ if (balance > 0n) {
449
+ agentId = BigInt(this.agentId);
450
+ } else {
451
+ agentId = await this._mintNewIdentity();
452
+ }
453
+ } else {
454
+ agentId = await this._mintNewIdentity();
455
+ }
456
+ this.agentId = agentId.toString();
457
+ const acctExists = await this.safeAgentFactory.accountExists(agentId);
458
+ let txHash;
459
+ if (!acctExists) {
460
+ const tx = await this.safeAgentFactory.createAccount(agentId);
461
+ const receipt = await tx.wait();
462
+ this._refreshSigner();
463
+ txHash = receipt.hash;
464
+ }
465
+ const acctAddr = await this.safeAgentFactory.getAccount(agentId);
466
+ this._accountAddress = acctAddr;
467
+ const kyaRequired = await this.isKyaRequired();
468
+ return {
469
+ agentId: this.agentId,
470
+ address: eoaAddr,
471
+ agentAccount: acctAddr,
472
+ alreadyRegistered: acctExists,
473
+ kyaRequired,
474
+ tx: txHash
475
+ };
476
+ }
477
+ /** Get ETH / USDC / collateral balances for EOA and AgentAccount. */
478
+ async getBalances() {
479
+ const eoaAddr = await this.getSignerAddress();
480
+ const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
481
+ const ethBal = await this.provider.getBalance(eoaAddr);
482
+ const usdcBal = await usdc.balanceOf(eoaAddr);
483
+ const eoaCollateral = {};
484
+ for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
485
+ try {
486
+ const token = new import_ethers.Contract(info.address, ERC20_ABI, this.provider);
487
+ const bal = await token.balanceOf(eoaAddr);
488
+ eoaCollateral[symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
489
+ } catch (e) {
490
+ console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
491
+ eoaCollateral[symbol] = "0";
492
+ }
493
+ }
494
+ const result = {
495
+ agentId: this.agentId || "?",
496
+ address: eoaAddr,
497
+ eth: import_ethers.ethers.formatEther(ethBal),
498
+ usdc: import_ethers.ethers.formatUnits(usdcBal, 6),
499
+ collateral: eoaCollateral
500
+ };
501
+ try {
502
+ const acctAddr = await this.getAccountAddress();
503
+ const acctEth = await this.provider.getBalance(acctAddr);
504
+ const acctUsdc = await usdc.balanceOf(acctAddr);
505
+ const acctCollateral = {};
506
+ for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
507
+ try {
508
+ const token = new import_ethers.Contract(info.address, ERC20_ABI, this.provider);
509
+ const bal = await token.balanceOf(acctAddr);
510
+ acctCollateral[symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
511
+ } catch (e) {
512
+ console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
513
+ acctCollateral[symbol] = "0";
514
+ }
515
+ }
516
+ result.agentAccount = {
517
+ address: acctAddr,
518
+ eth: import_ethers.ethers.formatEther(acctEth),
519
+ usdc: import_ethers.ethers.formatUnits(acctUsdc, 6),
520
+ collateral: acctCollateral
521
+ };
522
+ } catch (err) {
523
+ if (err instanceof AgetherError && err.code === "NO_ACCOUNT") {
524
+ } else {
525
+ console.warn("[agether] getBalances: failed to fetch AgentAccount data:", err.message ?? err);
526
+ }
527
+ }
528
+ return result;
529
+ }
530
+ /** Transfer USDC from EOA to AgentAccount. */
531
+ async fundAccount(usdcAmount) {
532
+ const acctAddr = await this.getAccountAddress();
533
+ const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this._signer);
534
+ const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
535
+ const tx = await usdc.transfer(acctAddr, amount);
536
+ const receipt = await tx.wait();
537
+ this._refreshSigner();
538
+ return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
539
+ }
540
+ // ════════════════════════════════════════════════════════
541
+ // Market Discovery (Morpho GraphQL API)
542
+ // ════════════════════════════════════════════════════════
543
+ /**
544
+ * Fetch USDC borrow markets on Base from Morpho API.
545
+ * Caches results for 5 minutes.
546
+ */
547
+ async getMarkets(forceRefresh = false) {
548
+ if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
549
+ return this._discoveredMarkets;
550
+ }
551
+ const chainId = this.config.chainId;
552
+ const usdcAddr = this.config.contracts.usdc.toLowerCase();
553
+ const query = `{
554
+ markets(
555
+ first: 50
556
+ orderBy: SupplyAssetsUsd
557
+ orderDirection: Desc
558
+ where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"] }
559
+ ) {
560
+ items {
561
+ uniqueKey
562
+ lltv
563
+ oracleAddress
564
+ irmAddress
565
+ loanAsset { address symbol decimals }
566
+ collateralAsset { address symbol decimals }
567
+ state {
568
+ borrowAssets
569
+ supplyAssets
570
+ utilization
571
+ }
572
+ }
573
+ }
574
+ }`;
575
+ try {
576
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
577
+ const items = resp.data?.data?.markets?.items ?? [];
578
+ this._discoveredMarkets = items.map((m) => ({
579
+ uniqueKey: m.uniqueKey,
580
+ loanAsset: m.loanAsset,
581
+ collateralAsset: m.collateralAsset ?? { address: import_ethers.ethers.ZeroAddress, symbol: "N/A", decimals: 0 },
582
+ oracle: m.oracleAddress,
583
+ irm: m.irmAddress,
584
+ lltv: BigInt(m.lltv),
585
+ totalSupplyAssets: BigInt(m.state?.supplyAssets ?? "0"),
586
+ totalBorrowAssets: BigInt(m.state?.borrowAssets ?? "0"),
587
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0
588
+ }));
589
+ this._discoveredAt = Date.now();
590
+ for (const mi of this._discoveredMarkets) {
591
+ if (mi.collateralAsset.address !== import_ethers.ethers.ZeroAddress) {
592
+ this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
593
+ loanToken: mi.loanAsset.address,
594
+ collateralToken: mi.collateralAsset.address,
595
+ oracle: mi.oracle,
596
+ irm: mi.irm,
597
+ lltv: mi.lltv
598
+ });
599
+ }
600
+ }
601
+ return this._discoveredMarkets;
602
+ } catch (e) {
603
+ console.warn("[agether] getMarkets failed, using cache:", e instanceof Error ? e.message : e);
604
+ return this._discoveredMarkets ?? [];
605
+ }
606
+ }
607
+ /**
608
+ * Get MarketParams for a collateral token.
609
+ * Tries cache → API → on-chain idToMarketParams.
610
+ */
611
+ async findMarketForCollateral(collateralSymbolOrAddress) {
612
+ const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
613
+ const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
614
+ const cached = this._marketCache.get(colAddr);
615
+ if (cached) return cached;
616
+ await this.getMarkets();
617
+ const fromApi = this._marketCache.get(colAddr);
618
+ if (fromApi) return fromApi;
619
+ throw new AgetherError(
620
+ `No Morpho market found for collateral ${collateralSymbolOrAddress}`,
621
+ "MARKET_NOT_FOUND"
622
+ );
623
+ }
624
+ /** Read MarketParams on-chain by market ID (bytes32). */
625
+ async getMarketParams(marketId) {
626
+ const result = await this.morphoBlue.idToMarketParams(marketId);
627
+ return {
628
+ loanToken: result.loanToken,
629
+ collateralToken: result.collateralToken,
630
+ oracle: result.oracle,
631
+ irm: result.irm,
632
+ lltv: result.lltv
633
+ };
634
+ }
635
+ // ════════════════════════════════════════════════════════
636
+ // Position Reads
637
+ // ════════════════════════════════════════════════════════
638
+ /** Read on-chain position for a specific market. */
639
+ async getPosition(marketId) {
640
+ const acctAddr = await this.getAccountAddress();
641
+ const pos = await this.morphoBlue.position(marketId, acctAddr);
642
+ return {
643
+ supplyShares: pos.supplyShares,
644
+ borrowShares: pos.borrowShares,
645
+ collateral: pos.collateral
646
+ };
647
+ }
648
+ /**
649
+ * Full status: positions across all discovered markets.
650
+ */
651
+ async getStatus() {
652
+ const acctAddr = await this.getAccountAddress();
653
+ const markets = await this.getMarkets();
654
+ const positions = [];
655
+ let totalDebt = 0n;
656
+ for (const m of markets) {
657
+ if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
658
+ try {
659
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
660
+ if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
661
+ let debt = 0n;
662
+ if (pos.borrowShares > 0n) {
663
+ try {
664
+ const mkt = await this.morphoBlue.market(m.uniqueKey);
665
+ const totalBorrowShares = BigInt(mkt.totalBorrowShares);
666
+ const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
667
+ debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
668
+ totalDebt += debt;
669
+ } catch (e) {
670
+ console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
671
+ }
672
+ }
673
+ positions.push({
674
+ marketId: m.uniqueKey,
675
+ collateralToken: m.collateralAsset.symbol,
676
+ collateral: import_ethers.ethers.formatUnits(pos.collateral, m.collateralAsset.decimals),
677
+ borrowShares: pos.borrowShares.toString(),
678
+ supplyShares: pos.supplyShares.toString(),
679
+ debt: import_ethers.ethers.formatUnits(debt, 6)
680
+ });
681
+ } catch (e) {
682
+ console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
683
+ continue;
684
+ }
685
+ }
686
+ return {
687
+ agentId: this.agentId || "?",
688
+ agentAccount: acctAddr,
689
+ totalDebt: import_ethers.ethers.formatUnits(totalDebt, 6),
690
+ positions
691
+ };
692
+ }
693
+ // ════════════════════════════════════════════════════════
694
+ // Balance & Borrowing Capacity
695
+ // ════════════════════════════════════════════════════════
696
+ /**
697
+ * Get the USDC balance of the AgentAccount.
698
+ * @returns USDC balance in raw units (6 decimals)
699
+ */
700
+ async getUsdcBalance() {
701
+ const acctAddr = await this.getAccountAddress();
702
+ const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
703
+ return usdc.balanceOf(acctAddr);
704
+ }
705
+ /**
706
+ * Calculate the maximum additional USDC that can be borrowed
707
+ * given the agent's current collateral and debt across all markets.
708
+ *
709
+ * For each market with collateral deposited:
710
+ * maxBorrow = (collateralValue * LLTV) - currentDebt
711
+ *
712
+ * Uses the Morpho oracle to price collateral → loan token.
713
+ *
714
+ * @returns Maximum additional USDC borrowable (6 decimals)
715
+ */
716
+ async getMaxBorrowable() {
717
+ const acctAddr = await this.getAccountAddress();
718
+ const markets = await this.getMarkets();
719
+ let totalAdditional = 0n;
720
+ const byMarket = [];
721
+ for (const m of markets) {
722
+ if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
723
+ try {
724
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
725
+ if (pos.collateral === 0n) continue;
726
+ const mktState = await this.morphoBlue.market(m.uniqueKey);
727
+ const totalBorrowShares = BigInt(mktState.totalBorrowShares);
728
+ const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
729
+ const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
730
+ let collateralValueInLoan;
731
+ try {
732
+ const oracleContract = new import_ethers.Contract(m.oracle, [
733
+ "function price() view returns (uint256)"
734
+ ], this.provider);
735
+ const oraclePrice = await oracleContract.price();
736
+ const ORACLE_PRICE_SCALE = 10n ** 36n;
737
+ collateralValueInLoan = BigInt(pos.collateral) * oraclePrice / ORACLE_PRICE_SCALE;
738
+ } catch (e) {
739
+ console.warn(`[agether] oracle price fetch failed:`, e instanceof Error ? e.message : e);
740
+ continue;
741
+ }
742
+ const maxBorrowTotal = collateralValueInLoan * m.lltv / 10n ** 18n;
743
+ const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
744
+ totalAdditional += maxAdditional;
745
+ byMarket.push({
746
+ collateralToken: m.collateralAsset.symbol,
747
+ maxAdditional,
748
+ currentDebt,
749
+ collateralValue: collateralValueInLoan
750
+ });
751
+ } catch (e) {
752
+ console.warn(`[agether] maxBorrow calc failed:`, e instanceof Error ? e.message : e);
753
+ continue;
754
+ }
755
+ }
756
+ return { total: totalAdditional, byMarket };
757
+ }
758
+ // ════════════════════════════════════════════════════════
759
+ // Market Rates & Yield Estimation
760
+ // ════════════════════════════════════════════════════════
761
+ /**
762
+ * Fetch current supply/borrow APY for a collateral market from Morpho GraphQL API.
763
+ *
764
+ * Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
765
+ * is what lenders earn; borrow APY is what borrowers pay.
766
+ */
767
+ async getMarketRates(collateralSymbolOrAddress) {
768
+ const chainId = this.config.chainId;
769
+ const usdcAddr = this.config.contracts.usdc.toLowerCase();
770
+ let collateralFilter = "";
771
+ if (collateralSymbolOrAddress) {
772
+ const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
773
+ const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
774
+ collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
775
+ }
776
+ const query = `{
777
+ markets(
778
+ first: 50
779
+ orderBy: SupplyAssetsUsd
780
+ orderDirection: Desc
781
+ where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"]${collateralFilter} }
782
+ ) {
783
+ items {
784
+ uniqueKey
785
+ lltv
786
+ loanAsset { address symbol decimals }
787
+ collateralAsset { address symbol decimals }
788
+ state {
789
+ borrowAssets
790
+ supplyAssets
791
+ utilization
792
+ supplyApy
793
+ borrowApy
794
+ }
795
+ }
796
+ }
797
+ }`;
798
+ try {
799
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
800
+ const items = resp.data?.data?.markets?.items ?? [];
801
+ return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers.ethers.ZeroAddress).map((m) => ({
802
+ collateralToken: m.collateralAsset.symbol,
803
+ loanToken: m.loanAsset.symbol,
804
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
805
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
806
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
807
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 1e6 : 0,
808
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 1e6 : 0,
809
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
810
+ marketId: m.uniqueKey
811
+ }));
812
+ } catch (e) {
813
+ console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
814
+ return [];
815
+ }
816
+ }
817
+ /**
818
+ * Estimate theoretical yield for a given collateral amount over a period.
819
+ *
820
+ * ⚠️ IMPORTANT: On Morpho Blue, collateral does NOT earn yield directly.
821
+ * This estimates what the collateral WOULD earn if it were instead supplied
822
+ * as a lender (not used as collateral). This is a theoretical upper bound
823
+ * useful for setting spending caps.
824
+ *
825
+ * @param collateralSymbol - e.g. 'WETH'
826
+ * @param amount - collateral amount in human-readable (e.g. '1.5')
827
+ * @param periodDays - estimation period in days (default: 1)
828
+ * @param ethPriceUsd - ETH price in USD for value conversion (if not provided, uses oracle)
829
+ * @returns Estimated yield in USD for the period
830
+ */
831
+ async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
832
+ const colInfo = BASE_COLLATERALS[collateralSymbol];
833
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
834
+ const rates = await this.getMarketRates(collateralSymbol);
835
+ if (rates.length === 0) {
836
+ throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
837
+ }
838
+ const market = rates[0];
839
+ const supplyApy = market.supplyApy;
840
+ let collateralValueUsd;
841
+ if (ethPriceUsd) {
842
+ collateralValueUsd = parseFloat(amount) * ethPriceUsd;
843
+ } else {
844
+ try {
845
+ const params = await this.findMarketForCollateral(collateralSymbol);
846
+ const oracleContract = new import_ethers.Contract(params.oracle, [
847
+ "function price() view returns (uint256)"
848
+ ], this.provider);
849
+ const oraclePrice = await oracleContract.price();
850
+ const ORACLE_PRICE_SCALE = 10n ** 36n;
851
+ const amountWei = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
852
+ const valueInUsdc = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
853
+ collateralValueUsd = Number(valueInUsdc) / 1e6;
854
+ } catch (e) {
855
+ console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
856
+ throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
857
+ }
858
+ }
859
+ const estimatedYieldUsd = collateralValueUsd * supplyApy * (periodDays / 365);
860
+ return {
861
+ collateralToken: collateralSymbol,
862
+ amount,
863
+ periodDays,
864
+ theoreticalSupplyApy: supplyApy,
865
+ estimatedYieldUsd,
866
+ collateralValueUsd,
867
+ disclaimer: "Collateral on Morpho Blue does NOT earn yield directly. This estimates what it WOULD earn if supplied as a lender instead. Use as a theoretical spending cap."
868
+ };
869
+ }
870
+ // ════════════════════════════════════════════════════════
871
+ // Lending Operations (all via AgentAccount.executeBatch)
872
+ // ════════════════════════════════════════════════════════
873
+ /**
874
+ * Deposit collateral into Morpho Blue.
875
+ *
876
+ * Flow:
877
+ * 1. EOA transfers collateral to AgentAccount
878
+ * 2. AgentAccount.executeBatch:
879
+ * [collateral.approve(MorphoBlue), Morpho.supplyCollateral(params)]
880
+ */
881
+ async supplyCollateral(tokenSymbol, amount, marketParams) {
882
+ const acctAddr = await this.getAccountAddress();
883
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
884
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
885
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
886
+ const weiAmount = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
887
+ const morphoAddr = this.config.contracts.morphoBlue;
888
+ const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this._signer);
889
+ const transferTx = await colToken.transfer(acctAddr, weiAmount);
890
+ await transferTx.wait();
891
+ this._refreshSigner();
892
+ const targets = [colInfo.address, morphoAddr];
893
+ const values = [0n, 0n];
894
+ const datas = [
895
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, weiAmount]),
896
+ morphoIface.encodeFunctionData("supplyCollateral", [
897
+ this._toTuple(params),
898
+ weiAmount,
899
+ acctAddr,
900
+ "0x"
901
+ ])
902
+ ];
903
+ const receipt = await this.batch(targets, values, datas);
904
+ return {
905
+ tx: receipt.hash,
906
+ collateralToken: tokenSymbol,
907
+ amount,
908
+ agentAccount: acctAddr
909
+ };
910
+ }
911
+ /**
912
+ * Borrow USDC against existing collateral.
913
+ *
914
+ * AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
915
+ *
916
+ * @param usdcAmount - USDC amount (e.g. '100')
917
+ * @param tokenSymbol - collateral symbol to identify which market (default: first with collateral)
918
+ */
919
+ async borrow(usdcAmount, tokenSymbol, marketParams) {
920
+ const acctAddr = await this.getAccountAddress();
921
+ const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
922
+ const morphoAddr = this.config.contracts.morphoBlue;
923
+ let params;
924
+ let usedToken = tokenSymbol || "WETH";
925
+ if (marketParams) {
926
+ params = marketParams;
927
+ } else if (tokenSymbol) {
928
+ params = await this.findMarketForCollateral(tokenSymbol);
929
+ } else {
930
+ const { params: p, symbol } = await this._findActiveMarket();
931
+ params = p;
932
+ usedToken = symbol;
933
+ }
934
+ const data = morphoIface.encodeFunctionData("borrow", [
935
+ this._toTuple(params),
936
+ amount,
937
+ 0n,
938
+ acctAddr,
939
+ acctAddr
940
+ ]);
941
+ const receipt = await this.exec(morphoAddr, data);
942
+ return {
943
+ tx: receipt.hash,
944
+ amount: usdcAmount,
945
+ collateralToken: usedToken,
946
+ agentAccount: acctAddr
947
+ };
948
+ }
949
+ /**
950
+ * Deposit collateral AND borrow USDC in one batched transaction.
951
+ *
952
+ * AgentAccount.executeBatch:
953
+ * [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
954
+ *
955
+ * The collateral must be transferred to AgentAccount first.
956
+ */
957
+ async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
958
+ const acctAddr = await this.getAccountAddress();
959
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
960
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
961
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
962
+ const colWei = import_ethers.ethers.parseUnits(collateralAmount, colInfo.decimals);
963
+ const borrowWei = import_ethers.ethers.parseUnits(borrowUsdcAmount, 6);
964
+ const morphoAddr = this.config.contracts.morphoBlue;
965
+ const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this._signer);
966
+ const transferTx = await colToken.transfer(acctAddr, colWei);
967
+ await transferTx.wait();
968
+ this._refreshSigner();
969
+ const targets = [colInfo.address, morphoAddr, morphoAddr];
970
+ const values = [0n, 0n, 0n];
971
+ const datas = [
972
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, colWei]),
973
+ morphoIface.encodeFunctionData("supplyCollateral", [
974
+ this._toTuple(params),
975
+ colWei,
976
+ acctAddr,
977
+ "0x"
978
+ ]),
979
+ morphoIface.encodeFunctionData("borrow", [
980
+ this._toTuple(params),
981
+ borrowWei,
982
+ 0n,
983
+ acctAddr,
984
+ acctAddr
985
+ ])
986
+ ];
987
+ const receipt = await this.batch(targets, values, datas);
988
+ return {
989
+ tx: receipt.hash,
990
+ collateralToken: tokenSymbol,
991
+ collateralAmount,
992
+ borrowAmount: borrowUsdcAmount,
993
+ agentAccount: acctAddr
994
+ };
995
+ }
996
+ /**
997
+ * Repay borrowed USDC from AgentAccount.
998
+ *
999
+ * AgentAccount.executeBatch:
1000
+ * [USDC.approve(MorphoBlue), Morpho.repay(params)]
1001
+ */
1002
+ async repay(usdcAmount, tokenSymbol, marketParams) {
1003
+ const acctAddr = await this.getAccountAddress();
1004
+ const morphoAddr = this.config.contracts.morphoBlue;
1005
+ const usdcAddr = this.config.contracts.usdc;
1006
+ let params;
1007
+ if (marketParams) {
1008
+ params = marketParams;
1009
+ } else if (tokenSymbol) {
1010
+ params = await this.findMarketForCollateral(tokenSymbol);
1011
+ } else {
1012
+ const { params: p } = await this._findActiveMarket();
1013
+ params = p;
1014
+ }
1015
+ let repayAssets;
1016
+ let repayShares;
1017
+ let approveAmount;
1018
+ if (usdcAmount === "all") {
1019
+ const markets = await this.getMarkets();
1020
+ const mkt = markets.find(
1021
+ (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
1022
+ );
1023
+ if (mkt) {
1024
+ const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
1025
+ repayShares = BigInt(pos.borrowShares);
1026
+ repayAssets = 0n;
1027
+ const onChainMkt = await this.morphoBlue.market(mkt.uniqueKey);
1028
+ const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
1029
+ const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
1030
+ const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
1031
+ approveAmount = estimated > 0n ? estimated : import_ethers.ethers.parseUnits("1", 6);
1032
+ } else {
1033
+ repayAssets = import_ethers.ethers.parseUnits("999999", 6);
1034
+ repayShares = 0n;
1035
+ approveAmount = repayAssets;
1036
+ }
1037
+ } else {
1038
+ repayAssets = import_ethers.ethers.parseUnits(usdcAmount, 6);
1039
+ repayShares = 0n;
1040
+ approveAmount = repayAssets;
1041
+ }
1042
+ const usdcContract = new import_ethers.Contract(usdcAddr, ERC20_ABI, this._signer);
1043
+ const acctBalance = await usdcContract.balanceOf(acctAddr);
1044
+ if (acctBalance < approveAmount) {
1045
+ const shortfall = approveAmount - acctBalance;
1046
+ const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1047
+ if (eoaBalance < shortfall) {
1048
+ throw new AgetherError(
1049
+ `Insufficient USDC for repay. Need ${import_ethers.ethers.formatUnits(approveAmount, 6)} USDC, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, 6)}.`,
1050
+ "INSUFFICIENT_BALANCE"
1051
+ );
1052
+ }
1053
+ const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1054
+ await transferTx.wait();
1055
+ this._refreshSigner();
1056
+ }
1057
+ const targets = [usdcAddr, morphoAddr];
1058
+ const values = [0n, 0n];
1059
+ const datas = [
1060
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, approveAmount]),
1061
+ morphoIface.encodeFunctionData("repay", [
1062
+ this._toTuple(params),
1063
+ repayAssets,
1064
+ repayShares,
1065
+ acctAddr,
1066
+ "0x"
1067
+ ])
1068
+ ];
1069
+ const receipt = await this.batch(targets, values, datas);
1070
+ let remainingDebt = "0";
1071
+ try {
1072
+ const status = await this.getStatus();
1073
+ remainingDebt = status.totalDebt;
1074
+ } catch (e) {
1075
+ console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
1076
+ }
1077
+ return { tx: receipt.hash, amount: usdcAmount, remainingDebt };
1078
+ }
1079
+ /**
1080
+ * Withdraw collateral from Morpho Blue.
1081
+ *
1082
+ * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
1083
+ *
1084
+ * @param receiver - defaults to EOA wallet
1085
+ */
1086
+ async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
1087
+ const acctAddr = await this.getAccountAddress();
1088
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
1089
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1090
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1091
+ const morphoAddr = this.config.contracts.morphoBlue;
1092
+ const usdcAddr = this.config.contracts.usdc;
1093
+ const dest = receiver || await this.getSignerAddress();
1094
+ let weiAmount;
1095
+ const markets = await this.getMarkets();
1096
+ const market = markets.find(
1097
+ (m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
1098
+ );
1099
+ if (amount === "all") {
1100
+ if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
1101
+ const pos = await this.morphoBlue.position(market.uniqueKey, acctAddr);
1102
+ weiAmount = pos.collateral;
1103
+ if (weiAmount === 0n) throw new AgetherError("No collateral to withdraw", "NO_COLLATERAL");
1104
+ } else {
1105
+ weiAmount = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
1106
+ }
1107
+ let hasDustDebt = false;
1108
+ let dustBorrowShares = 0n;
1109
+ let dustApproveAmount = 0n;
1110
+ if (market) {
1111
+ try {
1112
+ const pos = await this.morphoBlue.position(market.uniqueKey, acctAddr);
1113
+ dustBorrowShares = BigInt(pos.borrowShares);
1114
+ if (dustBorrowShares > 0n) {
1115
+ hasDustDebt = true;
1116
+ const onChainMkt = await this.morphoBlue.market(market.uniqueKey);
1117
+ const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
1118
+ const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
1119
+ const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
1120
+ dustApproveAmount = estimated > 0n ? estimated : import_ethers.ethers.parseUnits("1", 6);
1121
+ console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${import_ethers.ethers.formatUnits(dustApproveAmount, 6)} USDC \u2014 auto-repaying before withdraw`);
1122
+ }
1123
+ } catch (e) {
1124
+ console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
1125
+ }
1126
+ }
1127
+ const withdrawData = morphoIface.encodeFunctionData("withdrawCollateral", [
1128
+ this._toTuple(params),
1129
+ weiAmount,
1130
+ acctAddr,
1131
+ dest
1132
+ ]);
1133
+ let receipt;
1134
+ if (hasDustDebt) {
1135
+ const usdcContract = new import_ethers.Contract(usdcAddr, ERC20_ABI, this._signer);
1136
+ const acctBalance = await usdcContract.balanceOf(acctAddr);
1137
+ if (acctBalance < dustApproveAmount) {
1138
+ const shortfall = dustApproveAmount - acctBalance;
1139
+ const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1140
+ if (eoaBalance >= shortfall) {
1141
+ console.log(`[agether] transferring ${import_ethers.ethers.formatUnits(shortfall, 6)} USDC from EOA \u2192 AgentAccount for dust repay`);
1142
+ const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1143
+ await transferTx.wait();
1144
+ this._refreshSigner();
1145
+ }
1146
+ }
1147
+ const targets = [usdcAddr, morphoAddr, morphoAddr];
1148
+ const values = [0n, 0n, 0n];
1149
+ const datas = [
1150
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
1151
+ morphoIface.encodeFunctionData("repay", [
1152
+ this._toTuple(params),
1153
+ 0n,
1154
+ dustBorrowShares,
1155
+ acctAddr,
1156
+ "0x"
1157
+ ]),
1158
+ withdrawData
1159
+ ];
1160
+ receipt = await this.batch(targets, values, datas);
1161
+ } else {
1162
+ receipt = await this.exec(morphoAddr, withdrawData);
1163
+ }
1164
+ let remainingCollateral = "0";
1165
+ try {
1166
+ if (market) {
1167
+ const pos = await this.morphoBlue.position(market.uniqueKey, acctAddr);
1168
+ remainingCollateral = import_ethers.ethers.formatUnits(pos.collateral, colInfo.decimals);
1169
+ }
1170
+ } catch (e) {
1171
+ console.warn("[agether] failed to read remaining collateral after withdraw:", e instanceof Error ? e.message : e);
1172
+ }
1173
+ return {
1174
+ tx: receipt.hash,
1175
+ token: tokenSymbol,
1176
+ amount: amount === "all" ? import_ethers.ethers.formatUnits(weiAmount, colInfo.decimals) : amount,
1177
+ remainingCollateral,
1178
+ destination: dest
1179
+ };
1180
+ }
1181
+ /**
1182
+ * Sponsor: transfer collateral to another agent's AgentAccount.
1183
+ * (The agent must then supplyCollateral themselves via their own account.)
1184
+ */
1185
+ async sponsor(target, tokenSymbol, amount) {
1186
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
1187
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1188
+ let targetAddr;
1189
+ if (target.address) {
1190
+ targetAddr = target.address;
1191
+ } else if (target.agentId) {
1192
+ targetAddr = await this.safeAgentFactory.getAccount(BigInt(target.agentId));
1193
+ if (targetAddr === import_ethers.ethers.ZeroAddress) throw new AgetherError("Target agent has no account", "NO_ACCOUNT");
1194
+ } else {
1195
+ throw new AgetherError("Provide agentId or address", "INVALID_TARGET");
1196
+ }
1197
+ const weiAmount = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
1198
+ const colToken = new import_ethers.Contract(colInfo.address, ERC20_ABI, this._signer);
1199
+ const tx = await colToken.transfer(targetAddr, weiAmount);
1200
+ const receipt = await tx.wait();
1201
+ this._refreshSigner();
1202
+ return { tx: receipt.hash, targetAccount: targetAddr, targetAgentId: target.agentId };
1203
+ }
1204
+ // ════════════════════════════════════════════════════════
1205
+ // Reputation (AgentReputation contract)
1206
+ // ════════════════════════════════════════════════════════
1207
+ async getCreditScore() {
1208
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
1209
+ return this.agentReputation.getCreditScore(BigInt(this.agentId));
1210
+ }
1211
+ async getAttestation() {
1212
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
1213
+ const att = await this.agentReputation.getAttestation(BigInt(this.agentId));
1214
+ return { score: att.score, timestamp: att.timestamp, signer: att.signer };
1215
+ }
1216
+ async isEligible(minScore = 500n) {
1217
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
1218
+ const [eligible, currentScore] = await this.agentReputation.isEligible(BigInt(this.agentId), minScore);
1219
+ return { eligible, currentScore };
1220
+ }
1221
+ async isScoreFresh() {
1222
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
1223
+ const [fresh, age] = await this.agentReputation.isScoreFresh(BigInt(this.agentId));
1224
+ return { fresh, age };
1225
+ }
1226
+ // ════════════════════════════════════════════════════════
1227
+ // Internal Helpers
1228
+ // ════════════════════════════════════════════════════════
1229
+ /**
1230
+ * Refresh the signer and re-bind contract instances.
1231
+ *
1232
+ * For the **privateKey** path: recreates provider + wallet so the next tx
1233
+ * fetches a fresh nonce from chain. Anvil (and some RPC providers) return a
1234
+ * stale `eth_getTransactionCount` right after a block is mined, causing
1235
+ * "nonce too low" on the follow-up tx.
1236
+ *
1237
+ * For the **external signer** path: the signer is immutable and owned by the
1238
+ * caller (e.g. custody provider). We only re-bind contract instances to
1239
+ * ensure they reference the current signer. Nonce management is the caller's
1240
+ * responsibility.
1241
+ */
1242
+ _refreshSigner() {
1243
+ if (this._useExternalSigner) {
1244
+ const addrs = this.config.contracts;
1245
+ this.safeAgentFactory = new import_ethers.Contract(addrs.safeAgentFactory, ACCOUNT_FACTORY_ABI, this._signer);
1246
+ this.entryPoint = new import_ethers.Contract(addrs.entryPoint, ENTRYPOINT_V07_ABI, this._signer);
1247
+ this.validationModule = new import_ethers.Contract(addrs.erc8004ValidationModule, ERC8004_VALIDATION_MODULE_ABI, this.provider);
1248
+ this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this._signer);
1249
+ this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
1250
+ } else {
1251
+ this.provider = new import_ethers.ethers.JsonRpcProvider(this._rpcUrl);
1252
+ const wallet = new import_ethers.ethers.Wallet(this._privateKey, this.provider);
1253
+ this._signer = wallet;
1254
+ this._eoaAddress = wallet.address;
1255
+ const addrs = this.config.contracts;
1256
+ this.safeAgentFactory = new import_ethers.Contract(addrs.safeAgentFactory, ACCOUNT_FACTORY_ABI, this._signer);
1257
+ this.entryPoint = new import_ethers.Contract(addrs.entryPoint, ENTRYPOINT_V07_ABI, this._signer);
1258
+ this.validationModule = new import_ethers.Contract(addrs.erc8004ValidationModule, ERC8004_VALIDATION_MODULE_ABI, this.provider);
1259
+ this.agentReputation = new import_ethers.Contract(addrs.agentReputation, AGENT_REPUTATION_ABI, this._signer);
1260
+ this.identityRegistry = new import_ethers.Contract(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this._signer);
1261
+ }
1262
+ }
1263
+ // ────────────────────────────────────────────────────────────
1264
+ // ERC-4337 UserOp helpers (Safe + Safe7579 + EntryPoint v0.7)
1265
+ // ────────────────────────────────────────────────────────────
1266
+ /**
1267
+ * Pack two uint128 values into a single bytes32:
1268
+ * bytes32 = (hi << 128) | lo
1269
+ */
1270
+ _packUint128(hi, lo) {
1271
+ return import_ethers.ethers.zeroPadValue(import_ethers.ethers.toBeHex(hi << 128n | lo), 32);
1272
+ }
1273
+ /**
1274
+ * Build, sign and submit a PackedUserOperation through EntryPoint.handleOps.
1275
+ *
1276
+ * @param callData – the ABI-encoded calldata for the Safe7579 account
1277
+ * (e.g. `execute(mode, executionCalldata)`)
1278
+ * @returns the transaction receipt of the handleOps call
1279
+ */
1280
+ async _submitUserOp(callData) {
1281
+ const sender = await this.getAccountAddress();
1282
+ const nonce = await this.entryPoint.getNonce(sender, 0);
1283
+ const feeData = await this.provider.getFeeData();
1284
+ const maxFeePerGas = feeData.maxFeePerGas ?? import_ethers.ethers.parseUnits("0.5", "gwei");
1285
+ const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? import_ethers.ethers.parseUnits("0.1", "gwei");
1286
+ const verificationGasLimit = 500000n;
1287
+ const callGasLimit = 800000n;
1288
+ const preVerificationGas = 100000n;
1289
+ const accountGasLimits = this._packUint128(verificationGasLimit, callGasLimit);
1290
+ const gasFees = this._packUint128(maxPriorityFeePerGas, maxFeePerGas);
1291
+ const userOp = {
1292
+ sender,
1293
+ nonce,
1294
+ initCode: "0x",
1295
+ callData,
1296
+ accountGasLimits,
1297
+ preVerificationGas,
1298
+ gasFees,
1299
+ paymasterAndData: "0x",
1300
+ signature: "0x"
1301
+ // placeholder — replaced after signing
1302
+ };
1303
+ const userOpHash = await this.entryPoint.getUserOpHash(userOp);
1304
+ const signature = await this._signer.signMessage(import_ethers.ethers.getBytes(userOpHash));
1305
+ userOp.signature = signature;
1306
+ const tx = await this.entryPoint.handleOps([userOp], await this.getSignerAddress());
1307
+ const receipt = await tx.wait();
1308
+ this._refreshSigner();
1309
+ return receipt;
1310
+ }
1311
+ /**
1312
+ * Execute a single call via Safe7579 account (ERC-7579 single mode)
1313
+ * through an ERC-4337 UserOperation.
1314
+ */
1315
+ async exec(target, data, value = 0n) {
1316
+ const executionCalldata = import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
1317
+ ["address", "uint256", "bytes"],
1318
+ [target, value, data]
1319
+ );
1320
+ const safe7579Iface = new import_ethers.ethers.Interface(SAFE7579_ACCOUNT_ABI);
1321
+ const callData = safe7579Iface.encodeFunctionData("execute", [MODE_SINGLE, executionCalldata]);
1322
+ return this._submitUserOp(callData);
1323
+ }
1324
+ /**
1325
+ * Execute multiple calls via Safe7579 account (ERC-7579 batch mode)
1326
+ * through an ERC-4337 UserOperation.
1327
+ */
1328
+ async batch(targets, values, datas) {
1329
+ const executions = targets.map((t, i) => [t, values[i], datas[i]]);
1330
+ const executionCalldata = import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
1331
+ ["(address,uint256,bytes)[]"],
1332
+ [executions]
1333
+ );
1334
+ const safe7579Iface = new import_ethers.ethers.Interface(SAFE7579_ACCOUNT_ABI);
1335
+ const callData = safe7579Iface.encodeFunctionData("execute", [MODE_BATCH, executionCalldata]);
1336
+ return this._submitUserOp(callData);
1337
+ }
1338
+ /** Convert MorphoMarketParams to Solidity tuple. */
1339
+ _toTuple(p) {
1340
+ return [p.loanToken, p.collateralToken, p.oracle, p.irm, p.lltv];
1341
+ }
1342
+ /** Find the first market where the agent has collateral deposited. */
1343
+ async _findActiveMarket() {
1344
+ const acctAddr = await this.getAccountAddress();
1345
+ const markets = await this.getMarkets();
1346
+ for (const m of markets) {
1347
+ if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
1348
+ try {
1349
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
1350
+ if (pos.collateral > 0n) {
1351
+ return {
1352
+ params: {
1353
+ loanToken: m.loanAsset.address,
1354
+ collateralToken: m.collateralAsset.address,
1355
+ oracle: m.oracle,
1356
+ irm: m.irm,
1357
+ lltv: m.lltv
1358
+ },
1359
+ symbol: m.collateralAsset.symbol
1360
+ };
1361
+ }
1362
+ } catch (e) {
1363
+ console.warn("[agether] _findActiveMarket position check failed:", e instanceof Error ? e.message : e);
1364
+ continue;
1365
+ }
1366
+ }
1367
+ const params = await this.findMarketForCollateral("WETH");
1368
+ return { params, symbol: "WETH" };
1369
+ }
1370
+ };
1371
+ }
1372
+ });
1373
+
1374
+ // src/clients/X402Client.ts
1375
+ var X402Client_exports = {};
1376
+ __export(X402Client_exports, {
1377
+ X402Client: () => X402Client
1378
+ });
1379
+ var import_fetch, import_client, import_client2, import_accounts, X402Client;
1380
+ var init_X402Client = __esm({
1381
+ "src/clients/X402Client.ts"() {
1382
+ "use strict";
1383
+ import_fetch = require("@x402/fetch");
1384
+ import_client = require("@x402/core/client");
1385
+ import_client2 = require("@x402/evm/exact/client");
1386
+ import_accounts = require("viem/accounts");
1387
+ X402Client = class {
1388
+ constructor(config) {
1389
+ this.config = config;
1390
+ let baseSigner;
1391
+ if ("walletClient" in config && config.walletClient) {
1392
+ const wc = config.walletClient;
1393
+ const account = wc.account;
1394
+ if (!account) {
1395
+ throw new Error(
1396
+ "X402Client: walletClient must have an attached account. Pass an account when creating the WalletClient or use privateKey mode instead."
1397
+ );
1398
+ }
1399
+ this.address = account.address;
1400
+ baseSigner = {
1401
+ address: account.address,
1402
+ signTypedData: (msg) => wc.signTypedData({
1403
+ account,
1404
+ domain: msg.domain,
1405
+ types: msg.types,
1406
+ primaryType: msg.primaryType,
1407
+ message: msg.message
1408
+ })
1409
+ };
1410
+ } else if ("privateKey" in config && config.privateKey) {
1411
+ const privateKey = config.privateKey.startsWith("0x") ? config.privateKey : `0x${config.privateKey}`;
1412
+ const account = (0, import_accounts.privateKeyToAccount)(privateKey);
1413
+ this.address = account.address;
1414
+ baseSigner = account;
1415
+ } else {
1416
+ throw new Error(
1417
+ "X402Client: provide either privateKey or walletClient in config."
1418
+ );
1419
+ }
1420
+ let signer;
1421
+ if (config.accountAddress) {
1422
+ const accountAddr = config.accountAddress;
1423
+ const inner = baseSigner;
1424
+ signer = (0, import_accounts.toAccount)({
1425
+ address: accountAddr,
1426
+ async signMessage({ message }) {
1427
+ if ("signMessage" in inner && typeof inner.signMessage === "function") {
1428
+ return inner.signMessage({ message });
1429
+ }
1430
+ throw new Error("signMessage not supported by underlying signer");
1431
+ },
1432
+ async signTransaction(tx) {
1433
+ if ("signTransaction" in inner && typeof inner.signTransaction === "function") {
1434
+ return inner.signTransaction(tx);
1435
+ }
1436
+ throw new Error("signTransaction not supported by underlying signer");
1437
+ },
1438
+ async signTypedData(typedData) {
1439
+ const sig = await inner.signTypedData(typedData);
1440
+ return `${sig}00`;
1441
+ }
1442
+ });
1443
+ this.address = accountAddr;
1444
+ } else {
1445
+ signer = baseSigner;
1446
+ }
1447
+ const client = new import_client.x402Client();
1448
+ (0, import_client2.registerExactEvmScheme)(client, { signer });
1449
+ this.paidFetch = (0, import_fetch.wrapFetchWithPayment)(fetch, client);
1450
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1451
+ const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
1452
+ this._spendingTracker = { date: today, totalBorrowed: 0n, dailyLimit };
1453
+ }
1454
+ async get(url, opts) {
1455
+ return this.request(url, { ...opts, method: "GET" });
1456
+ }
1457
+ async post(url, body, opts) {
1458
+ return this.request(url, {
1459
+ ...opts,
1460
+ method: "POST",
1461
+ body: body ? JSON.stringify(body) : void 0,
1462
+ headers: { "Content-Type": "application/json", ...opts?.headers }
1463
+ });
1464
+ }
1465
+ getAddress() {
1466
+ return this.address;
1467
+ }
1468
+ /** Get the current spending tracker state */
1469
+ getSpendingTracker() {
1470
+ this._resetTrackerIfNewDay();
1471
+ return { ...this._spendingTracker };
1472
+ }
1473
+ /** Get remaining daily spending allowance in USDC (human-readable) */
1474
+ getRemainingDailyAllowance() {
1475
+ this._resetTrackerIfNewDay();
1476
+ if (this._spendingTracker.dailyLimit === 0n) return "unlimited";
1477
+ const remaining = this._spendingTracker.dailyLimit - this._spendingTracker.totalBorrowed;
1478
+ return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
1479
+ }
1480
+ /**
1481
+ * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
1482
+ *
1483
+ * Flow:
1484
+ * 1. Check USDC balance on AgentAccount
1485
+ * 2. Probe the URL to discover payment amount (if 402)
1486
+ * 3. If insufficient USDC, calculate deficit
1487
+ * 4. Check spending limit
1488
+ * 5. Borrow from Morpho via MorphoClient
1489
+ * 6. Proceed with x402 payment
1490
+ */
1491
+ async payWithAutoDraw(url, opts) {
1492
+ const { morphoClient, ...fetchOpts } = opts || {};
1493
+ if (!this.config.autoDraw || !morphoClient) {
1494
+ return this.request(url, fetchOpts);
1495
+ }
1496
+ try {
1497
+ const usdcBalance = await morphoClient.getUsdcBalance();
1498
+ console.log(` [auto-draw] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
1499
+ const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
1500
+ if (paymentAmount !== null) {
1501
+ console.log(` [auto-draw] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
1502
+ const bufferStr = this.config.autoDrawBuffer || "0.5";
1503
+ const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
1504
+ const needed = paymentAmount + buffer;
1505
+ if (usdcBalance < needed) {
1506
+ const deficit = needed - usdcBalance;
1507
+ console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
1508
+ const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
1509
+ if (!limitCheck.allowed) {
1510
+ return {
1511
+ success: false,
1512
+ error: `Auto-draw blocked: ${limitCheck.reason}`
1513
+ };
1514
+ }
1515
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
1516
+ if (maxBorrowable.total < deficit) {
1517
+ return {
1518
+ success: false,
1519
+ error: `Auto-draw failed: insufficient collateral. Need ${(Number(deficit) / 1e6).toFixed(2)} USDC but can only borrow ${(Number(maxBorrowable.total) / 1e6).toFixed(2)} USDC more.`
1520
+ };
1521
+ }
1522
+ const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
1523
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
1524
+ const borrowResult = await morphoClient.borrow(borrowAmount);
1525
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
1526
+ this._trackSpending(deficit);
1527
+ const result = await this.request(url, fetchOpts);
1528
+ return {
1529
+ ...result,
1530
+ autoDrawInfo: {
1531
+ borrowed: borrowAmount,
1532
+ borrowTx: borrowResult.tx,
1533
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
1534
+ }
1535
+ };
1536
+ }
1537
+ }
1538
+ return this.request(url, fetchOpts);
1539
+ } catch (error) {
1540
+ console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
1541
+ return this.request(url, fetchOpts);
1542
+ }
1543
+ }
1544
+ // ──────────── Core request — @x402/fetch handles 402 automatically ────────────
1545
+ async request(url, options) {
1546
+ try {
1547
+ console.log(` [x402] ${options?.method || "GET"} ${url}`);
1548
+ const response = await this.paidFetch(url, {
1549
+ ...options,
1550
+ headers: {
1551
+ ...options?.headers,
1552
+ "X-Agent-Id": this.config.agentId || ""
1553
+ }
1554
+ });
1555
+ if (response.ok) {
1556
+ const data = await response.json();
1557
+ const paymentResponse = response.headers.get("PAYMENT-RESPONSE");
1558
+ let txHash;
1559
+ if (paymentResponse) {
1560
+ try {
1561
+ const settlement = JSON.parse(
1562
+ Buffer.from(paymentResponse, "base64").toString("utf-8")
1563
+ );
1564
+ txHash = settlement.transaction;
1565
+ } catch (e) {
1566
+ console.warn("[agether] x402 payment response parse failed:", e instanceof Error ? e.message : e);
1567
+ }
1568
+ }
1569
+ return {
1570
+ success: true,
1571
+ data,
1572
+ ...txHash ? {
1573
+ paymentInfo: {
1574
+ amount: "",
1575
+ asset: "USDC",
1576
+ network: "eip155:8453",
1577
+ txHash
1578
+ }
1579
+ } : {}
1580
+ };
1581
+ }
1582
+ const errBody = await response.text();
1583
+ return {
1584
+ success: false,
1585
+ error: `HTTP ${response.status}: ${errBody}`
1586
+ };
1587
+ } catch (error) {
1588
+ return {
1589
+ success: false,
1590
+ error: `Request failed: ${error instanceof Error ? error.message : String(error)}`
1591
+ };
1592
+ }
1593
+ }
1594
+ // ──────────── Auto-Draw Helpers ────────────
1595
+ /**
1596
+ * Probe a URL to discover payment requirements without paying.
1597
+ * Makes a request and parses the 402 PAYMENT-REQUIRED header.
1598
+ * @returns Payment amount in raw USDC units (6 decimals), or null if not a 402.
1599
+ */
1600
+ async _probePaymentAmount(url, options) {
1601
+ try {
1602
+ const response = await fetch(url, {
1603
+ ...options,
1604
+ headers: {
1605
+ ...options?.headers,
1606
+ "X-Agent-Id": this.config.agentId || ""
1607
+ }
1608
+ });
1609
+ if (response.status !== 402) return null;
1610
+ const paymentHeader = response.headers.get("X-PAYMENT") || response.headers.get("PAYMENT-REQUIRED");
1611
+ if (!paymentHeader) return null;
1612
+ try {
1613
+ const decoded = JSON.parse(Buffer.from(paymentHeader, "base64").toString("utf-8"));
1614
+ const requirements = Array.isArray(decoded) ? decoded : decoded.accepts || [decoded];
1615
+ if (requirements.length > 0) {
1616
+ const amount = requirements[0].maxAmountRequired || requirements[0].amount;
1617
+ if (amount) {
1618
+ return BigInt(amount);
1619
+ }
1620
+ }
1621
+ } catch (e) {
1622
+ console.warn("[agether] x402 payment header parse failed:", e instanceof Error ? e.message : e);
1623
+ }
1624
+ return null;
1625
+ } catch (e) {
1626
+ console.warn("[agether] x402 getPaymentRequired failed:", e instanceof Error ? e.message : e);
1627
+ return null;
1628
+ }
1629
+ }
1630
+ /** Reset spending tracker if it's a new day */
1631
+ _resetTrackerIfNewDay() {
1632
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1633
+ if (this._spendingTracker.date !== today) {
1634
+ this._spendingTracker = {
1635
+ date: today,
1636
+ totalBorrowed: 0n,
1637
+ dailyLimit: this._spendingTracker.dailyLimit
1638
+ };
1639
+ }
1640
+ }
1641
+ /** Track a new spending amount */
1642
+ _trackSpending(amount) {
1643
+ this._resetTrackerIfNewDay();
1644
+ this._spendingTracker.totalBorrowed += amount;
1645
+ }
1646
+ /**
1647
+ * Check if a borrow amount is within spending limits.
1648
+ * Considers both fixed daily limits and yield-limited spending.
1649
+ */
1650
+ async _checkSpendingLimit(amount, morphoClient) {
1651
+ this._resetTrackerIfNewDay();
1652
+ if (this.config.yieldLimitedSpending) {
1653
+ try {
1654
+ const status = await morphoClient.getStatus();
1655
+ let totalDailyYieldUsdc = 0;
1656
+ for (const pos of status.positions) {
1657
+ if (parseFloat(pos.collateral) > 0) {
1658
+ try {
1659
+ const estimate = await morphoClient.getYieldEstimate(
1660
+ pos.collateralToken,
1661
+ pos.collateral,
1662
+ 1
1663
+ // 1 day
1664
+ );
1665
+ totalDailyYieldUsdc += estimate.estimatedYieldUsd;
1666
+ } catch (e) {
1667
+ console.warn(`[agether] yield calc failed for ${pos.collateralToken}:`, e instanceof Error ? e.message : e);
1668
+ }
1669
+ }
1670
+ }
1671
+ const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
1672
+ const newTotal = this._spendingTracker.totalBorrowed + amount;
1673
+ if (yieldLimit > 0n && newTotal > yieldLimit) {
1674
+ return {
1675
+ allowed: false,
1676
+ reason: `Yield-limited spending exceeded. Daily yield cap: $${(Number(yieldLimit) / 1e6).toFixed(2)}, already spent: $${(Number(this._spendingTracker.totalBorrowed) / 1e6).toFixed(2)}, requested: $${(Number(amount) / 1e6).toFixed(2)}`
1677
+ };
1678
+ }
1679
+ } catch (e) {
1680
+ console.warn("[agether] yield-limited spending check failed, falling through to fixed limit:", e instanceof Error ? e.message : e);
1681
+ }
1682
+ }
1683
+ if (this._spendingTracker.dailyLimit > 0n) {
1684
+ const newTotal = this._spendingTracker.totalBorrowed + amount;
1685
+ if (newTotal > this._spendingTracker.dailyLimit) {
1686
+ return {
1687
+ allowed: false,
1688
+ reason: `Daily spending limit exceeded. Limit: $${(Number(this._spendingTracker.dailyLimit) / 1e6).toFixed(2)}, already spent: $${(Number(this._spendingTracker.totalBorrowed) / 1e6).toFixed(2)}, requested: $${(Number(amount) / 1e6).toFixed(2)}`
1689
+ };
1690
+ }
1691
+ }
1692
+ return { allowed: true };
1693
+ }
1694
+ };
1695
+ }
1696
+ });
1697
+
1698
+ // src/cli.ts
1699
+ var import_ethers2 = require("ethers");
1700
+ var fs = __toESM(require("fs"));
1701
+ var path = __toESM(require("path"));
1702
+ var os = __toESM(require("os"));
1703
+ var CONFIG_PATH = path.join(os.homedir(), ".agether", "config.json");
1704
+ var DEFAULT_RPC = "https://base-rpc.publicnode.com";
1705
+ var DEFAULT_BACKEND = "http://95.179.189.214:3001";
1706
+ function loadConfig() {
1707
+ try {
1708
+ if (fs.existsSync(CONFIG_PATH)) {
1709
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
1710
+ }
1711
+ } catch {
1712
+ }
1713
+ return null;
1714
+ }
1715
+ function saveConfig(config) {
1716
+ const dir = path.dirname(CONFIG_PATH);
1717
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1718
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
1719
+ }
1720
+ function requireConfig() {
1721
+ const config = loadConfig();
1722
+ if (!config) {
1723
+ console.error("\u274C Not initialized. Run: agether init <private-key>");
1724
+ process.exit(1);
1725
+ }
1726
+ return config;
1727
+ }
1728
+ async function waitForTx(tx, retries = 5) {
1729
+ for (let attempt = 1; attempt <= retries; attempt++) {
1730
+ try {
1731
+ const receipt = await tx.wait();
1732
+ if (receipt) return receipt;
1733
+ } catch (e) {
1734
+ if (attempt === retries) throw e;
1735
+ console.log(` \u23F3 Receipt fetch failed (attempt ${attempt}/${retries}), retrying...`);
1736
+ await new Promise((r) => setTimeout(r, 2e3 * attempt));
1737
+ }
1738
+ }
1739
+ throw new Error("Failed to get transaction receipt after retries");
1740
+ }
1741
+ async function getMorphoClient(config) {
1742
+ const { MorphoClient: MorphoClient2 } = await Promise.resolve().then(() => (init_MorphoClient(), MorphoClient_exports));
1743
+ return new MorphoClient2({
1744
+ privateKey: config.privateKey,
1745
+ rpcUrl: config.rpcUrl,
1746
+ agentId: config.agentId !== "0" ? config.agentId : void 0
1747
+ });
1748
+ }
1749
+ async function getX402Client(config) {
1750
+ const { X402Client: X402Client2 } = await Promise.resolve().then(() => (init_X402Client(), X402Client_exports));
1751
+ let accountAddress;
1752
+ try {
1753
+ const mc = await getMorphoClient(config);
1754
+ accountAddress = await mc.getAccountAddress();
1755
+ } catch {
1756
+ }
1757
+ return new X402Client2({
1758
+ privateKey: config.privateKey,
1759
+ rpcUrl: config.rpcUrl,
1760
+ backendUrl: config.backendUrl,
1761
+ agentId: config.agentId,
1762
+ accountAddress
1763
+ });
1764
+ }
1765
+ var ERC8004_ABI = [
1766
+ "function register(string agentURI) external returns (uint256 agentId)",
1767
+ "function register() external returns (uint256 agentId)",
1768
+ "function ownerOf(uint256 tokenId) view returns (address)",
1769
+ "function balanceOf(address owner) view returns (uint256)",
1770
+ "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
1771
+ ];
1772
+ var ACCOUNT_FACTORY_ABI2 = [
1773
+ "function getAccount(uint256 agentId) view returns (address)",
1774
+ "function accountExists(uint256 agentId) view returns (bool)",
1775
+ "function createAccount(uint256 agentId) returns (address account)",
1776
+ "event AccountCreated(uint256 indexed agentId, address indexed account, address indexed owner)"
1777
+ ];
1778
+ var VALIDATION_REGISTRY_ABI = [
1779
+ "function isAgentCodeApproved(uint256 agentId) view returns (bool)"
1780
+ ];
1781
+ var MOCK_ERC20_ABI = [
1782
+ "function mint(address to, uint256 amount) external"
1783
+ ];
1784
+ async function apiGet(backendUrl, endpoint) {
1785
+ const res = await fetch(`${backendUrl}${endpoint}`);
1786
+ return res.json();
1787
+ }
1788
+ function decodeError(error) {
1789
+ const msg = error?.message || String(error);
1790
+ if (msg.includes("CodeNotApproved")) return "Agent code not approved. Complete KYA first.";
1791
+ if (msg.includes("0xda04aecc") || msg.includes("ExceedsMaxLtv"))
1792
+ return "ExceedsMaxLtv \u2014 collateral too low for this borrow amount.";
1793
+ if (msg.includes("0xfeca99cb") || msg.includes("ExecutionFailed"))
1794
+ return "ExecutionFailed \u2014 the inner contract call reverted.";
1795
+ if (msg.includes("0xa920ef9f"))
1796
+ return "PositionNotActive \u2014 no collateral deposited for this token.";
1797
+ if (msg.length > 200) return msg.slice(0, 200) + "...";
1798
+ return msg;
1799
+ }
1800
+ async function cmdInit(privateKey, agentId) {
1801
+ const rpcUrl = process.env.AGETHER_RPC_URL || DEFAULT_RPC;
1802
+ const backendUrl = process.env.AGETHER_BACKEND_URL || DEFAULT_BACKEND;
1803
+ let wallet;
1804
+ try {
1805
+ wallet = new import_ethers2.ethers.Wallet(privateKey);
1806
+ } catch {
1807
+ console.error("\u274C Invalid private key");
1808
+ process.exit(1);
1809
+ }
1810
+ const config = { privateKey, agentId: agentId || "0", rpcUrl, backendUrl };
1811
+ saveConfig(config);
1812
+ console.log("\u2705 Initialized Agether CLI");
1813
+ console.log(` Address: ${wallet.address}`);
1814
+ console.log(` RPC: ${rpcUrl}`);
1815
+ console.log(` Backend: ${backendUrl}`);
1816
+ console.log(` Config: ${CONFIG_PATH}`);
1817
+ }
1818
+ async function cmdRegister(name) {
1819
+ const config = requireConfig();
1820
+ const provider = new import_ethers2.ethers.JsonRpcProvider(config.rpcUrl);
1821
+ const signer = new import_ethers2.ethers.Wallet(config.privateKey, provider);
1822
+ const agentName = name || `Agent-${signer.address.slice(0, 8)}`;
1823
+ console.log(`\u{1F916} Registering agent: ${agentName}
1824
+ `);
1825
+ console.log(` Wallet: ${signer.address}`);
1826
+ console.log("\n [1/4] Fetching contract addresses...");
1827
+ let contracts;
1828
+ try {
1829
+ const statusResp = await apiGet(config.backendUrl, "/status");
1830
+ contracts = statusResp.contracts || {};
1831
+ console.log(" \u2713 Backend OK");
1832
+ } catch (e) {
1833
+ console.error(` \u274C Failed to reach backend: ${e.message}`);
1834
+ process.exit(1);
1835
+ }
1836
+ const registryAddr = contracts.agentRegistry || contracts.identityRegistry;
1837
+ const factoryAddr = contracts.safeAgentFactory || contracts.accountFactory;
1838
+ const validationAddr = contracts.validationRegistry;
1839
+ if (!registryAddr || !factoryAddr) {
1840
+ console.error(" \u274C Backend missing agentRegistry or safeAgentFactory");
1841
+ process.exit(1);
1842
+ }
1843
+ console.log(" [2/4] Registering on ERC-8004 IdentityRegistry...");
1844
+ const agentRegistry = new import_ethers2.ethers.Contract(registryAddr, ERC8004_ABI, signer);
1845
+ let agentId;
1846
+ if (config.agentId && config.agentId !== "0") {
1847
+ agentId = BigInt(config.agentId);
1848
+ try {
1849
+ const owner = await agentRegistry.ownerOf(agentId);
1850
+ if (owner.toLowerCase() === signer.address.toLowerCase()) {
1851
+ console.log(` \u2713 Already registered as Agent #${agentId}`);
1852
+ } else {
1853
+ console.error(" \u274C agentId does not belong to this wallet");
1854
+ process.exit(1);
1855
+ }
1856
+ } catch {
1857
+ console.error(` \u274C agentId ${agentId} does not exist on-chain`);
1858
+ process.exit(1);
1859
+ }
1860
+ } else {
1861
+ const existingBalance = await agentRegistry.balanceOf(signer.address);
1862
+ if (existingBalance > 0n) {
1863
+ console.log(` \u26A0 Wallet already owns ${existingBalance} token(s), minting another`);
1864
+ }
1865
+ try {
1866
+ const registrationFile = JSON.stringify({
1867
+ type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
1868
+ name: agentName,
1869
+ description: "AI agent registered via Agether CLI",
1870
+ active: true
1871
+ });
1872
+ const agentURI = `data:application/json;base64,${Buffer.from(registrationFile).toString("base64")}`;
1873
+ const tx = await agentRegistry["register(string)"](agentURI);
1874
+ const receipt = await waitForTx(tx);
1875
+ const transferTopic = import_ethers2.ethers.id("Transfer(address,address,uint256)");
1876
+ const transferLog = receipt.logs.find((log) => log.topics[0] === transferTopic);
1877
+ if (transferLog && transferLog.topics.length >= 4) {
1878
+ agentId = BigInt(transferLog.topics[3]);
1879
+ } else {
1880
+ console.error(" \u274C Could not parse agentId from receipt");
1881
+ process.exit(1);
1882
+ }
1883
+ console.log(` \u2713 Agent #${agentId} registered`);
1884
+ console.log(` TX: ${tx.hash}`);
1885
+ config.agentId = agentId.toString();
1886
+ saveConfig(config);
1887
+ } catch (e) {
1888
+ if (config.agentId && config.agentId !== "0") {
1889
+ agentId = BigInt(config.agentId);
1890
+ console.log(` Using existing Agent #${agentId}`);
1891
+ } else {
1892
+ const balance = await agentRegistry.balanceOf(signer.address);
1893
+ if (balance > 0n) {
1894
+ console.error(` Wallet owns ${balance} token(s) but agentId unknown.`);
1895
+ console.error(" Set manually: agether init <pk> --agent-id <id>");
1896
+ } else {
1897
+ console.error(` \u274C Registration failed: ${e.message}`);
1898
+ }
1899
+ process.exit(1);
1900
+ }
1901
+ }
1902
+ }
1903
+ const network = await provider.getNetwork();
1904
+ const chainId = Number(network.chainId);
1905
+ if (chainId === 31337 || chainId === 1) {
1906
+ console.log(" [3/4] Minting test USDC (Hardhat fork)...");
1907
+ const deployerPk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
1908
+ const deployer = new import_ethers2.ethers.Wallet(deployerPk, provider);
1909
+ const usdcAddr = contracts.usdc;
1910
+ if (usdcAddr) {
1911
+ try {
1912
+ const usdc = new import_ethers2.ethers.Contract(usdcAddr, MOCK_ERC20_ABI, deployer);
1913
+ const tx = await usdc.mint(signer.address, BigInt(5e10));
1914
+ await waitForTx(tx);
1915
+ console.log(" \u2713 Minted $50,000 USDC");
1916
+ } catch {
1917
+ console.log(" \u26A0 Mint failed (probably real network)");
1918
+ }
1919
+ }
1920
+ } else {
1921
+ console.log(" [3/4] Skipping USDC mint (real network)");
1922
+ }
1923
+ console.log(" [4/4] Creating Safe account...");
1924
+ if (factoryAddr) {
1925
+ const factory = new import_ethers2.ethers.Contract(factoryAddr, ACCOUNT_FACTORY_ABI2, signer);
1926
+ try {
1927
+ const exists = await factory.accountExists(agentId);
1928
+ if (exists) {
1929
+ const addr = await factory.getAccount(agentId);
1930
+ console.log(` Already exists: ${addr}`);
1931
+ } else {
1932
+ const tx = await factory.createAccount(agentId);
1933
+ await waitForTx(tx);
1934
+ const addr = await factory.getAccount(agentId);
1935
+ console.log(` \u2713 Created: ${addr}`);
1936
+ console.log(` TX: ${tx.hash}`);
1937
+ }
1938
+ } catch (e) {
1939
+ console.error(` \u274C Failed: ${e.message}`);
1940
+ }
1941
+ }
1942
+ if (validationAddr) {
1943
+ const vr = new import_ethers2.ethers.Contract(validationAddr, VALIDATION_REGISTRY_ABI, provider);
1944
+ try {
1945
+ const approved = await vr.isAgentCodeApproved(agentId);
1946
+ console.log(`
1947
+ KYA Status: ${approved ? "\u2705 Approved" : "\u23F3 Pending"}`);
1948
+ } catch {
1949
+ console.log("\n KYA Status: \u26A0 Could not check");
1950
+ }
1951
+ }
1952
+ console.log(`
1953
+ \u2705 Agent #${agentId} ready!`);
1954
+ console.log(` Config: ${CONFIG_PATH}`);
1955
+ console.log("\n Next steps:");
1956
+ console.log(" agether balance");
1957
+ console.log(" agether deposit --amount 0.05 --token WETH");
1958
+ console.log(" agether borrow --amount 100");
1959
+ }
1960
+ async function cmdBalance() {
1961
+ const config = requireConfig();
1962
+ const mc = await getMorphoClient(config);
1963
+ const balances = await mc.getBalances();
1964
+ console.log(`
1965
+ \u{1F4B0} Agent #${balances.agentId} Balances
1966
+ `);
1967
+ console.log(` EOA: ${balances.address}`);
1968
+ console.log(` ETH: ${parseFloat(balances.eth).toFixed(6)}`);
1969
+ console.log(` USDC: $${parseFloat(balances.usdc).toFixed(2)}`);
1970
+ if (balances.agentAccount) {
1971
+ console.log(`
1972
+ AgentAccount: ${balances.agentAccount.address}`);
1973
+ console.log(` ETH: ${parseFloat(balances.agentAccount.eth).toFixed(6)}`);
1974
+ console.log(` USDC: $${parseFloat(balances.agentAccount.usdc).toFixed(2)}`);
1975
+ }
1976
+ }
1977
+ async function cmdStatus() {
1978
+ const config = requireConfig();
1979
+ const mc = await getMorphoClient(config);
1980
+ console.log("\n\u{1F4CA} Morpho Positions\n");
1981
+ const status = await mc.getStatus();
1982
+ console.log(` Agent #${status.agentId}`);
1983
+ console.log(` Account: ${status.agentAccount}`);
1984
+ console.log(` Total Debt: $${parseFloat(status.totalDebt).toFixed(2)}`);
1985
+ if (status.positions.length === 0) {
1986
+ console.log("\n No active positions. Deposit collateral first:");
1987
+ console.log(" agether deposit --amount 0.05 --token WETH");
1988
+ return;
1989
+ }
1990
+ console.log("\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
1991
+ console.log(" \u2502 Token \u2502 Collateral \u2502 Debt (USDC) \u2502");
1992
+ console.log(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
1993
+ for (const p of status.positions) {
1994
+ const tok = p.collateralToken.padEnd(9);
1995
+ const col = parseFloat(p.collateral).toFixed(6).padStart(16);
1996
+ const debt = ("$" + parseFloat(p.debt).toFixed(2)).padStart(16);
1997
+ console.log(` \u2502 ${tok} \u2502 ${col} \u2502 ${debt} \u2502`);
1998
+ }
1999
+ console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
2000
+ try {
2001
+ const score = await mc.getCreditScore();
2002
+ const { fresh, age } = await mc.isScoreFresh();
2003
+ const ageHrs = Number(age) / 3600;
2004
+ console.log(`
2005
+ Credit Score: ${score} ${fresh ? "\u2705" : `\u26A0\uFE0F stale (${ageHrs.toFixed(1)}h old)`}`);
2006
+ } catch {
2007
+ console.log("\n Credit Score: not yet computed");
2008
+ }
2009
+ }
2010
+ async function cmdScore() {
2011
+ const config = requireConfig();
2012
+ console.log(`
2013
+ \u{1F4C8} Agent #${config.agentId} Score
2014
+ `);
2015
+ try {
2016
+ const data = await apiGet(config.backendUrl, `/score/${config.agentId}/current`);
2017
+ if (data.score !== void 0) {
2018
+ console.log(` On-chain Score: ${data.score}`);
2019
+ console.log(` Timestamp: ${data.timestamp ? new Date(Number(data.timestamp) * 1e3).toISOString() : "N/A"}`);
2020
+ console.log(` Signer: ${data.signer || "N/A"}`);
2021
+ console.log(` Fresh: ${data.fresh ? "\u2705" : "\u26A0\uFE0F stale"}`);
2022
+ } else {
2023
+ console.log(" No score on-chain yet.");
2024
+ }
2025
+ } catch {
2026
+ console.log(" Could not fetch current score.");
2027
+ }
2028
+ console.log("\n To compute a fresh score (x402-gated, costs USDC):");
2029
+ console.log(` agether x402 ${config.backendUrl}/score/${config.agentId}`);
2030
+ }
2031
+ async function cmdMarkets() {
2032
+ const config = requireConfig();
2033
+ const mc = await getMorphoClient(config);
2034
+ console.log("\n\u{1F4C8} Morpho Blue Markets (Base USDC)\n");
2035
+ const markets = await mc.getMarkets();
2036
+ if (markets.length === 0) {
2037
+ console.log(" No markets found. API may be unavailable.");
2038
+ return;
2039
+ }
2040
+ console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
2041
+ console.log(" \u2502 Collateral \u2502 LLTV \u2502 Util % \u2502 Supply (USDC) \u2502");
2042
+ console.log(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
2043
+ for (const m of markets.slice(0, 15)) {
2044
+ const col = (m.collateralAsset?.symbol || "N/A").padEnd(13);
2045
+ const lltv = (Number(m.lltv) / 1e18 * 100).toFixed(0).padStart(6) + "%";
2046
+ const util = (m.utilization * 100).toFixed(1).padStart(8) + "%";
2047
+ const supply = ("$" + (Number(m.totalSupplyAssets) / 1e6).toLocaleString()).padStart(16);
2048
+ console.log(` \u2502 ${col} \u2502 ${lltv} \u2502 ${util} \u2502 ${supply} \u2502`);
2049
+ }
2050
+ console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
2051
+ }
2052
+ async function cmdDeposit(amount, token) {
2053
+ const config = requireConfig();
2054
+ const mc = await getMorphoClient(config);
2055
+ console.log(`
2056
+ \u{1F4B0} Depositing ${amount} ${token} as collateral...
2057
+ `);
2058
+ try {
2059
+ const result = await mc.supplyCollateral(token, amount);
2060
+ console.log(`\u2705 Deposited ${amount} ${token}`);
2061
+ console.log(` AgentAccount: ${result.agentAccount}`);
2062
+ console.log(` TX: ${result.tx}`);
2063
+ } catch (e) {
2064
+ console.error(`\u274C ${decodeError(e)}`);
2065
+ }
2066
+ }
2067
+ async function cmdBorrow(amount, token) {
2068
+ const config = requireConfig();
2069
+ const mc = await getMorphoClient(config);
2070
+ console.log(`
2071
+ \u{1F4B8} Borrowing $${amount} USDC...
2072
+ `);
2073
+ try {
2074
+ const result = await mc.borrow(amount, token);
2075
+ console.log(`\u2705 Borrowed $${amount} USDC`);
2076
+ console.log(` Collateral: ${result.collateralToken}`);
2077
+ console.log(` AgentAccount: ${result.agentAccount}`);
2078
+ console.log(` TX: ${result.tx}`);
2079
+ } catch (e) {
2080
+ console.error(`\u274C ${decodeError(e)}`);
2081
+ }
2082
+ }
2083
+ async function cmdDepositAndBorrow(collateralAmount, token, borrowAmount) {
2084
+ const config = requireConfig();
2085
+ const mc = await getMorphoClient(config);
2086
+ console.log(`
2087
+ \uFFFD\uFFFD Depositing ${collateralAmount} ${token} + Borrowing $${borrowAmount}...
2088
+ `);
2089
+ try {
2090
+ const result = await mc.depositAndBorrow(token, collateralAmount, borrowAmount);
2091
+ console.log(`\u2705 Deposited ${collateralAmount} ${token} & Borrowed $${borrowAmount} USDC`);
2092
+ console.log(` AgentAccount: ${result.agentAccount}`);
2093
+ console.log(` TX: ${result.tx}`);
2094
+ } catch (e) {
2095
+ console.error(`\u274C ${decodeError(e)}`);
2096
+ }
2097
+ }
2098
+ async function cmdRepay(amount, token) {
2099
+ const config = requireConfig();
2100
+ const mc = await getMorphoClient(config);
2101
+ console.log(`
2102
+ \u{1F4B3} Repaying $${amount} USDC...
2103
+ `);
2104
+ try {
2105
+ const result = await mc.repay(amount, token);
2106
+ console.log(`\u2705 Repaid $${amount}`);
2107
+ console.log(` Remaining debt: $${parseFloat(result.remainingDebt).toFixed(2)}`);
2108
+ console.log(` TX: ${result.tx}`);
2109
+ } catch (e) {
2110
+ console.error(`\u274C ${decodeError(e)}`);
2111
+ }
2112
+ }
2113
+ async function cmdWithdraw(amount, token) {
2114
+ const config = requireConfig();
2115
+ const mc = await getMorphoClient(config);
2116
+ console.log(`
2117
+ \u{1F4E4} Withdrawing ${amount} ${token}...
2118
+ `);
2119
+ try {
2120
+ const result = await mc.withdrawCollateral(token, amount);
2121
+ console.log(`\u2705 Withdrew ${result.amount} ${token}`);
2122
+ console.log(` Remaining collateral: ${result.remainingCollateral}`);
2123
+ console.log(` Destination: ${result.destination}`);
2124
+ console.log(` TX: ${result.tx}`);
2125
+ } catch (e) {
2126
+ console.error(`\u274C ${decodeError(e)}`);
2127
+ }
2128
+ }
2129
+ async function cmdSponsor(amount, token, agentId, address) {
2130
+ const config = requireConfig();
2131
+ const mc = await getMorphoClient(config);
2132
+ const target = agentId ? { agentId } : { address };
2133
+ console.log(`
2134
+ \u{1F381} Sponsoring ${amount} ${token} to ${agentId || address}...
2135
+ `);
2136
+ try {
2137
+ const result = await mc.sponsor(target, token, amount);
2138
+ console.log(`\u2705 Sponsored ${amount} ${token}`);
2139
+ console.log(` Target: ${result.targetAccount}`);
2140
+ console.log(` TX: ${result.tx}`);
2141
+ } catch (e) {
2142
+ console.error(`\u274C ${decodeError(e)}`);
2143
+ }
2144
+ }
2145
+ async function cmdFund(amount) {
2146
+ const config = requireConfig();
2147
+ const mc = await getMorphoClient(config);
2148
+ console.log(`
2149
+ \u{1F4B5} Funding AgentAccount with $${amount} USDC...
2150
+ `);
2151
+ try {
2152
+ const result = await mc.fundAccount(amount);
2153
+ console.log(`\u2705 Funded $${amount} USDC`);
2154
+ console.log(` AgentAccount: ${result.agentAccount}`);
2155
+ console.log(` TX: ${result.tx}`);
2156
+ } catch (e) {
2157
+ console.error(`\u274C ${decodeError(e)}`);
2158
+ }
2159
+ }
2160
+ async function cmdX402Call(url, method = "GET", body) {
2161
+ const config = requireConfig();
2162
+ console.log("\n\u{1F510} x402 Paid API Call\n");
2163
+ const x402 = await getX402Client(config);
2164
+ console.log(` Wallet: ${new import_ethers2.ethers.Wallet(config.privateKey).address}`);
2165
+ console.log(`
2166
+ \u{1F4E1} ${method} ${url}`);
2167
+ if (body) console.log(`\u{1F4E6} Body: ${body}`);
2168
+ try {
2169
+ let result;
2170
+ if (method === "POST" && body) {
2171
+ result = await x402.post(url, JSON.parse(body));
2172
+ } else {
2173
+ result = await x402.get(url);
2174
+ }
2175
+ if (result.success) {
2176
+ console.log("\n\u2705 Success!");
2177
+ if (result.paymentInfo) {
2178
+ console.log(`\u{1F4B0} Paid: ${result.paymentInfo.amount} ${result.paymentInfo.asset} on ${result.paymentInfo.network}`);
2179
+ if (result.paymentInfo.txHash) console.log(`\u{1F4DC} TX: ${result.paymentInfo.txHash}`);
2180
+ }
2181
+ console.log("\n\u{1F4C4} Response:");
2182
+ console.log(JSON.stringify(result.data, null, 2));
2183
+ } else {
2184
+ console.error(`
2185
+ \u274C Failed: ${result.error}`);
2186
+ }
2187
+ } catch (e) {
2188
+ console.error(`\u274C Error: ${e.message}`);
2189
+ }
2190
+ }
2191
+ function cmdHelp() {
2192
+ console.log(`
2193
+ \u{1F3E6} Agether CLI \u2014 Direct Morpho Blue Credit for AI Agents (Safe + Safe7579)
2194
+
2195
+ USAGE:
2196
+ agether <command> [options]
2197
+
2198
+ SETUP:
2199
+ init <private-key> [--agent-id <id>] Initialize with private key
2200
+ register [--name <n>] Register ERC-8004 + create Safe account
2201
+
2202
+ INFO:
2203
+ balance Check ETH + USDC balances
2204
+ status Show Morpho positions & credit score
2205
+ score Get credit score
2206
+ markets List Morpho Blue USDC markets
2207
+
2208
+ MORPHO LENDING:
2209
+ deposit --amount <n> --token <t> Deposit collateral (WETH, wstETH, cbETH)
2210
+ borrow --amount <usd> [--token <t>] Borrow USDC against collateral
2211
+ deposit-and-borrow --amount <n> --token <t> --borrow <usd>
2212
+ Deposit + borrow in one batched tx
2213
+ repay --amount <usd> [--token <t>] Repay borrowed USDC
2214
+ withdraw --amount <n> --token <t> Withdraw collateral (use 'all' for max)
2215
+ sponsor --amount <n> --token <t> --agent-id <id> Send collateral to another agent
2216
+ fund --amount <usd> Transfer USDC from EOA to Safe account
2217
+
2218
+ x402 PAYMENTS:
2219
+ x402 <url> [--method GET|POST] [--body <json>] Make a paid API call
2220
+
2221
+ ENVIRONMENT:
2222
+ AGETHER_RPC_URL RPC endpoint (default: ${DEFAULT_RPC})
2223
+ AGETHER_BACKEND_URL Backend URL (default: ${DEFAULT_BACKEND})
2224
+
2225
+ EXAMPLE FLOW:
2226
+ agether init 0xYOUR_PRIVATE_KEY
2227
+ agether register --name "MyAgent"
2228
+ agether deposit --amount 0.05 --token WETH # ~$125 collateral
2229
+ agether borrow --amount 50 # Borrow $50 USDC
2230
+ agether status # Check positions
2231
+ agether repay --amount 50 # Repay when done
2232
+ agether withdraw --amount all --token WETH # Withdraw collateral
2233
+ `);
2234
+ }
2235
+ function parseArgs(args) {
2236
+ const command = args[0] || "help";
2237
+ const positional = [];
2238
+ const options = {};
2239
+ for (let i = 1; i < args.length; i++) {
2240
+ const arg = args[i];
2241
+ if (arg.startsWith("--")) {
2242
+ const key = arg.slice(2);
2243
+ const next = args[i + 1];
2244
+ if (next && !next.startsWith("--")) {
2245
+ options[key] = next;
2246
+ i++;
2247
+ } else {
2248
+ options[key] = true;
2249
+ }
2250
+ } else {
2251
+ positional.push(arg);
2252
+ }
2253
+ }
2254
+ return { command, positional, options };
2255
+ }
2256
+ async function main() {
2257
+ const { command, positional, options } = parseArgs(process.argv.slice(2));
2258
+ try {
2259
+ switch (command) {
2260
+ case "init":
2261
+ if (!positional[0]) {
2262
+ console.error("\u274C Private key required: agether init <private-key>");
2263
+ process.exit(1);
2264
+ }
2265
+ await cmdInit(positional[0], options["agent-id"]);
2266
+ break;
2267
+ case "register":
2268
+ await cmdRegister(options.name);
2269
+ break;
2270
+ case "balance":
2271
+ await cmdBalance();
2272
+ break;
2273
+ case "status":
2274
+ await cmdStatus();
2275
+ break;
2276
+ case "score":
2277
+ await cmdScore();
2278
+ break;
2279
+ case "markets":
2280
+ await cmdMarkets();
2281
+ break;
2282
+ case "deposit":
2283
+ if (!options.amount || !options.token) {
2284
+ console.error("\u274C --amount and --token required");
2285
+ console.error(" agether deposit --amount 0.05 --token WETH");
2286
+ process.exit(1);
2287
+ }
2288
+ await cmdDeposit(options.amount, options.token);
2289
+ break;
2290
+ case "borrow":
2291
+ if (!options.amount) {
2292
+ console.error("\u274C --amount required (in USD)");
2293
+ console.error(" agether borrow --amount 100");
2294
+ process.exit(1);
2295
+ }
2296
+ await cmdBorrow(options.amount, options.token);
2297
+ break;
2298
+ case "deposit-and-borrow":
2299
+ if (!options.amount || !options.token || !options.borrow) {
2300
+ console.error("\u274C --amount, --token, and --borrow required");
2301
+ console.error(" agether deposit-and-borrow --amount 0.05 --token WETH --borrow 100");
2302
+ process.exit(1);
2303
+ }
2304
+ await cmdDepositAndBorrow(
2305
+ options.amount,
2306
+ options.token,
2307
+ options.borrow
2308
+ );
2309
+ break;
2310
+ case "repay":
2311
+ if (!options.amount) {
2312
+ console.error("\u274C --amount required");
2313
+ process.exit(1);
2314
+ }
2315
+ await cmdRepay(options.amount, options.token);
2316
+ break;
2317
+ case "withdraw":
2318
+ if (!options.amount || !options.token) {
2319
+ console.error("\u274C --amount and --token required");
2320
+ console.error(" agether withdraw --amount 0.05 --token WETH");
2321
+ console.error(" agether withdraw --amount all --token WETH");
2322
+ process.exit(1);
2323
+ }
2324
+ await cmdWithdraw(options.amount, options.token);
2325
+ break;
2326
+ case "sponsor":
2327
+ if (!options.amount || !options.token) {
2328
+ console.error("\u274C --amount and --token required, plus --agent-id or --address");
2329
+ process.exit(1);
2330
+ }
2331
+ if (!options["agent-id"] && !options.address) {
2332
+ console.error("\u274C --agent-id or --address required");
2333
+ process.exit(1);
2334
+ }
2335
+ await cmdSponsor(
2336
+ options.amount,
2337
+ options.token,
2338
+ options["agent-id"],
2339
+ options.address
2340
+ );
2341
+ break;
2342
+ case "fund":
2343
+ if (!options.amount) {
2344
+ console.error("\u274C --amount required (USDC)");
2345
+ process.exit(1);
2346
+ }
2347
+ await cmdFund(options.amount);
2348
+ break;
2349
+ case "x402":
2350
+ if (!positional[0]) {
2351
+ console.error("\u274C URL required: agether x402 <url>");
2352
+ process.exit(1);
2353
+ }
2354
+ await cmdX402Call(
2355
+ positional[0],
2356
+ options.method || "GET",
2357
+ options.body
2358
+ );
2359
+ break;
2360
+ case "help":
2361
+ case "--help":
2362
+ case "-h":
2363
+ cmdHelp();
2364
+ break;
2365
+ default:
2366
+ console.error(`\u274C Unknown command: ${command}`);
2367
+ cmdHelp();
2368
+ process.exit(1);
2369
+ }
2370
+ } catch (e) {
2371
+ console.error(`\u274C Error: ${e.message}`);
2372
+ process.exit(1);
2373
+ }
2374
+ }
2375
+ main();