@agether/sdk 1.11.0 → 1.11.1

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