@agether/sdk 1.4.1 → 1.5.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/index.mjs CHANGED
@@ -2,15 +2,6 @@
2
2
  import { ethers, Contract } from "ethers";
3
3
 
4
4
  // src/types/index.ts
5
- var CreditStatus = /* @__PURE__ */ ((CreditStatus3) => {
6
- CreditStatus3[CreditStatus3["None"] = 0] = "None";
7
- CreditStatus3[CreditStatus3["Pending"] = 1] = "Pending";
8
- CreditStatus3[CreditStatus3["Active"] = 2] = "Active";
9
- CreditStatus3[CreditStatus3["Frozen"] = 3] = "Frozen";
10
- CreditStatus3[CreditStatus3["Closed"] = 4] = "Closed";
11
- CreditStatus3[CreditStatus3["Defaulted"] = 5] = "Defaulted";
12
- return CreditStatus3;
13
- })(CreditStatus || {});
14
5
  var ChainId = /* @__PURE__ */ ((ChainId3) => {
15
6
  ChainId3[ChainId3["Ethereum"] = 1] = "Ethereum";
16
7
  ChainId3[ChainId3["Base"] = 8453] = "Base";
@@ -27,12 +18,12 @@ var AgetherError = class extends Error {
27
18
  this.name = "AgetherError";
28
19
  }
29
20
  };
30
- var InsufficientCreditError = class extends AgetherError {
31
- constructor(available, requested) {
21
+ var InsufficientBalanceError = class extends AgetherError {
22
+ constructor(available, required) {
32
23
  super(
33
- `Insufficient credit: available ${available}, requested ${requested}`,
34
- "INSUFFICIENT_CREDIT",
35
- { available: available.toString(), requested: requested.toString() }
24
+ `Insufficient balance: available ${available}, required ${required}`,
25
+ "INSUFFICIENT_BALANCE",
26
+ { available: available.toString(), required: required.toString() }
36
27
  );
37
28
  }
38
29
  };
@@ -45,12 +36,12 @@ var ScoringRejectedError = class extends AgetherError {
45
36
  );
46
37
  }
47
38
  };
48
- var CreditNotActiveError = class extends AgetherError {
49
- constructor(account, status) {
39
+ var AgentNotApprovedError = class extends AgetherError {
40
+ constructor(agentId) {
50
41
  super(
51
- `Credit line for ${account} is not active (status: ${CreditStatus[status]})`,
52
- "CREDIT_NOT_ACTIVE",
53
- { account, status: CreditStatus[status] }
42
+ `Agent ${agentId} is not KYA-approved. Submit code for validation first.`,
43
+ "AGENT_NOT_APPROVED",
44
+ { agentId: agentId.toString() }
54
45
  );
55
46
  }
56
47
  };
@@ -76,103 +67,51 @@ var ACCOUNT_FACTORY_ABI = [
76
67
  var AGENT_ACCOUNT_ABI = [
77
68
  "function agentId() view returns (uint256)",
78
69
  "function owner() view returns (address)",
70
+ "function factory() view returns (address)",
71
+ "function validationRegistry() view returns (address)",
72
+ "function identityRegistry() view returns (address)",
79
73
  "function balanceOf(address token) view returns (uint256)",
80
74
  "function ethBalance() view returns (uint256)",
81
75
  "function execute(address target, uint256 value, bytes data) payable returns (bytes)",
82
76
  "function executeBatch(address[] targets, uint256[] values, bytes[] datas) payable returns (bytes[])",
83
- "function drawCredit(address creditProvider, uint256 amount)",
84
- "function repayCredit(address creditProvider, uint256 amount)",
85
77
  "function fund(address token, uint256 amount)",
86
78
  "function withdraw(address token, uint256 amount, address to)",
87
- "function approveCreditProvider(address creditProvider)"
88
- ];
89
- var CREDIT_PROVIDER_ABI = [
90
- "function asset() view returns (address)",
91
- "function isEligible(address account) view returns (bool)",
92
- "function getCreditInfo(address account) view returns (tuple(uint256 limit, uint256 used, uint256 available, uint256 accruedInterest, uint256 aprBps, bool isActive, bool requiresCollateral))",
93
- "function getTotalDebt(address account) view returns (uint256)",
94
- "function maxDrawable(address account) view returns (uint256)",
95
- "function draw(address account, uint256 amount)",
96
- "function repay(address account, uint256 amount)",
97
- "event CreditDrawn(address indexed account, uint256 amount, uint256 totalUsed)",
98
- "event CreditRepaid(address indexed account, uint256 amount, uint256 totalUsed)",
99
- "event CreditLineOpened(address indexed account, uint256 limit, uint256 aprBps)",
100
- "event CreditLineClosed(address indexed account)"
101
- ];
102
- var REPUTATION_CREDIT_ABI = [
103
- ...CREDIT_PROVIDER_ABI,
104
- // Application / Approval flow
105
- "function applyForCredit(address account, uint256 requestedLimit)",
106
- "function approveCreditLine(address account, uint256 limit, uint256 aprBps)",
107
- "function rejectCreditLine(address account, string reason)",
108
- "function requestLimitIncrease(address account, uint256 newRequestedLimit)",
109
- "function approveLimitIncrease(address account, uint256 newLimit)",
110
- "function freezeCreditLine(address account)",
111
- "function unfreezeCreditLine(address account)",
112
- // Admin
113
- "function setCreditLine(address account, uint256 limit, uint256 aprBps)",
114
- "function openScoredCreditLine(address account)",
115
- "function closeCreditLine(address account)",
116
- "function declareDefault(address account)",
117
- // View
118
- "function getCreditLineStatus(address account) view returns (uint8)",
119
- "function getCreditLineDetails(address account) view returns (uint256 agentId, uint256 limit, uint256 used, uint256 aprBps, uint256 accruedInterest, uint256 requestedLimit, uint256 createdAt, uint256 lastActivityAt, uint8 status)",
120
- "function getAgentAccount(uint256 agentId) view returns (address)",
121
- "function previewScoredLimit(uint256 agentId) view returns (uint256 limit, uint256 score, bool eligible)",
122
- "function dailyDrawLimit() view returns (uint256)",
123
- "function totalBorrowed() view returns (uint256)",
124
- // Events
125
- "event CreditApplied(uint256 indexed creditLineId, uint256 indexed agentId, uint256 requestedLimit)",
126
- "event CreditApproved(uint256 indexed creditLineId, uint256 limit, uint256 aprBps)",
127
- "event CreditRejected(uint256 indexed creditLineId, string reason)",
128
- "event CreditFrozen(uint256 indexed creditLineId)",
129
- "event CreditUnfrozen(uint256 indexed creditLineId)",
130
- "event DefaultDeclared(address indexed account, uint256 amount)"
131
- ];
132
- var LP_VAULT_ABI = [
133
- "function asset() view returns (address)",
134
- "function totalAssets() view returns (uint256)",
135
- "function totalSupply() view returns (uint256)",
136
- "function balanceOf(address account) view returns (uint256)",
137
- "function convertToShares(uint256 assets) view returns (uint256)",
138
- "function convertToAssets(uint256 shares) view returns (uint256)",
139
- "function previewDeposit(uint256 assets) view returns (uint256)",
140
- "function previewRedeem(uint256 shares) view returns (uint256)",
141
- "function deposit(uint256 assets, address receiver) returns (uint256)",
142
- "function withdraw(uint256 assets, address receiver, address owner) returns (uint256)",
143
- "function redeem(uint256 shares, address receiver, address owner) returns (uint256)",
144
- "function totalBorrowed() view returns (uint256)",
145
- "function availableLiquidity() view returns (uint256)",
146
- "event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)",
147
- "event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares)"
79
+ "function withdrawETH(uint256 amount, address to)",
80
+ "function isValidSignature(bytes32 hash, bytes signature) view returns (bytes4)"
148
81
  ];
149
82
  var AGENT_REPUTATION_ABI = [
150
- "function getReputation(uint256 agentId) view returns (tuple(uint256 totalBorrowed, uint256 totalRepaid, uint256 totalInterestPaid, uint256 onTimePayments, uint256 latePayments, uint256 missedPayments, uint256 peakUtilization, uint256 avgUtilization, uint256 utilizationSamples, uint256 firstActivityAt, uint256 lastActivityAt, uint256 lastScoreUpdate, uint256 creditScore, uint256 creditLinesOpened, uint256 lastCreditOpenedAt, uint256 currentOutstanding, uint256 currentLimit, uint256 totalDeposits, uint256 totalWithdrawals))",
151
83
  "function getCreditScore(uint256 agentId) view returns (uint256)",
152
- "function getBayesianScore(uint256 agentId) view returns (uint256 combinedScore, uint256 confidence)",
153
- "function getRecommendedLimit(uint256 agentId, uint256 baseLimit) view returns (uint256)",
84
+ "function getAttestation(uint256 agentId) view returns (tuple(uint256 score, uint256 timestamp, address signer))",
85
+ "function isScoreFresh(uint256 agentId) view returns (bool fresh, uint256 age)",
154
86
  "function isEligible(uint256 agentId, uint256 minScore) view returns (bool eligible, uint256 currentScore)",
155
- "function getDecayedScore(uint256 agentId) view returns (uint256)",
156
- "function getScoreExplanation(uint256 agentId) view returns (uint256 historical, uint256 currentRisk, uint256 utilization, uint256 onChainTx, uint256 newCredit, uint256 rawScore, uint256 decayedScore)",
157
- "function getLoanPositions(uint256 agentId) view returns (tuple(uint256 amount, uint256 collateral, uint256 ltvBps, uint256 timestamp, bool liquidated, bool repaid, bool active)[])",
158
- "function stressMultiplier() view returns (uint256)",
159
- "function decayPeriod() view returns (uint256)"
87
+ "function oracleSigner() view returns (address)",
88
+ "function submitScore(uint256 agentId, uint256 score_, uint256 timestamp_, bytes signature)",
89
+ "function setOracleSigner(address signer_)",
90
+ "event ScoreUpdated(uint256 indexed agentId, uint256 score, uint256 timestamp, address signer)"
160
91
  ];
161
92
  var VALIDATION_REGISTRY_ABI = [
162
93
  "function isAgentCodeApproved(uint256 agentId) view returns (bool)",
163
94
  "function isAgentCodeApprovedForTag(uint256 agentId, string tag) view returns (bool)",
164
- "function getAgentValidations(uint256 agentId) view returns (bytes32[])"
95
+ "function getAgentValidations(uint256 agentId) view returns (bytes32[])",
96
+ "function getValidation(bytes32 requestHash) view returns (tuple(address validatorAddress, uint256 agentId, string requestURI, uint8 response, string responseURI, bytes32 responseHash, string tag, uint256 requestedAt, uint256 respondedAt, bool hasResponse))"
165
97
  ];
166
- var MORPHO_CREDIT_ABI = [
167
- ...CREDIT_PROVIDER_ABI,
168
- "function depositCollateral(address collateralToken, uint256 amount)",
169
- "function depositCollateralFor(address onBehalf, address collateralToken, uint256 amount)",
170
- "function withdrawCollateral(address collateralToken, uint256 amount)",
171
- "function drawWithCollateral(address collateralToken, uint256 amount)",
172
- "function repayWithCollateral(address collateralToken, uint256 amount)",
173
- "function getPosition(address account, address collateralToken) view returns (tuple(uint256 collateralAmount, uint256 borrowedAmount, uint256 borrowShares, bool isActive))",
174
- "function getSupportedCollaterals() view returns (address[])",
175
- "function markets(address collateralToken) view returns (tuple(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) params, bytes32 marketId, uint256 maxLtvBps, bool active))"
98
+ var MORPHO_BLUE_ABI = [
99
+ // Supply & Withdraw (lending side)
100
+ "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)",
101
+ "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)",
102
+ // Collateral
103
+ "function supplyCollateral(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, bytes data)",
104
+ "function withdrawCollateral(tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, uint256 assets, address onBehalf, address receiver)",
105
+ // Borrow & Repay
106
+ "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)",
107
+ "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)",
108
+ // Authorization
109
+ "function setAuthorization(address authorized, bool newIsAuthorized)",
110
+ "function isAuthorized(address authorizer, address authorized) view returns (bool)",
111
+ // Views
112
+ "function position(bytes32 id, address user) view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)",
113
+ "function market(bytes32 id) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)",
114
+ "function idToMarketParams(bytes32 id) view returns (tuple(address loanToken, address collateralToken, address oracle, address irm, uint256 lltv))"
176
115
  ];
177
116
  var ERC20_ABI = [
178
117
  "function balanceOf(address account) view returns (uint256)",
@@ -181,57 +120,51 @@ var ERC20_ABI = [
181
120
  "function transfer(address to, uint256 amount) returns (bool)",
182
121
  "function transferFrom(address from, address to, uint256 amount) returns (bool)",
183
122
  "function decimals() view returns (uint8)",
184
- "function symbol() view returns (string)"
123
+ "function symbol() view returns (string)",
124
+ "function name() view returns (string)"
185
125
  ];
186
126
 
187
127
  // src/utils/config.ts
188
128
  var CONTRACT_ADDRESSES = {
189
129
  [1 /* Ethereum */]: {
190
130
  accountFactory: "0x0000000000000000000000000000000000000000",
191
- reputationCredit: "0x0000000000000000000000000000000000000000",
192
- lpVault: "0x0000000000000000000000000000000000000000",
193
131
  validationRegistry: "0x0000000000000000000000000000000000000000",
132
+ agentReputation: "0x0000000000000000000000000000000000000000",
194
133
  usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
195
134
  identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
196
- agentReputation: "0x0000000000000000000000000000000000000000"
135
+ morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"
197
136
  },
198
137
  [8453 /* Base */]: {
199
- accountFactory: "0xeB72f248Ad9F4bf4024e8D9da75cf7AAD37B58f5",
200
- reputationCredit: "0x57B2B4ef3e7B8BE5FC86c6369602125d240F552A",
201
- lpVault: "0x612A80D6c3175F8283e9C7EE71d5177fE9acc338",
202
- validationRegistry: "0x8842f2383A86134Dd80c3Ecf6Bbae2e38396A5ec",
138
+ accountFactory: "0x7D5D56416bAEA06a9DCBe3092eF335724C6320a0",
139
+ validationRegistry: "0xd196C32D2149270F56E209ba7aEE67CE9ceD2001",
140
+ agentReputation: "0x65c9cA1211809D3CF3A2707558198eb2b2bE623c",
203
141
  usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
204
142
  identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
205
- agentReputation: "0xF1bed094D4E33E47CC8C72E086FFFde09e2211b4",
206
- morphoCredit: "0x7dFfa40E17471F7f26F5662D0F07a31977F47BeB"
143
+ morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"
207
144
  },
208
145
  [84532 /* BaseSepolia */]: {
209
146
  accountFactory: "0x0000000000000000000000000000000000000000",
210
- reputationCredit: "0x0000000000000000000000000000000000000000",
211
- lpVault: "0x0000000000000000000000000000000000000000",
212
147
  validationRegistry: "0x0000000000000000000000000000000000000000",
148
+ agentReputation: "0x0000000000000000000000000000000000000000",
213
149
  usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
214
150
  identityRegistry: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
215
- agentReputation: "0x0000000000000000000000000000000000000000"
151
+ morphoBlue: "0x0000000000000000000000000000000000000000"
216
152
  },
217
153
  [11155111 /* Sepolia */]: {
218
154
  accountFactory: "0x0000000000000000000000000000000000000000",
219
- reputationCredit: "0x0000000000000000000000000000000000000000",
220
- lpVault: "0x0000000000000000000000000000000000000000",
221
155
  validationRegistry: "0x0000000000000000000000000000000000000000",
156
+ agentReputation: "0x0000000000000000000000000000000000000000",
222
157
  usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
223
158
  identityRegistry: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
224
- agentReputation: "0x0000000000000000000000000000000000000000"
159
+ morphoBlue: "0x0000000000000000000000000000000000000000"
225
160
  },
226
161
  [31337 /* Hardhat */]: {
227
162
  accountFactory: "0x0000000000000000000000000000000000000000",
228
- reputationCredit: "0x0000000000000000000000000000000000000000",
229
- lpVault: "0x0000000000000000000000000000000000000000",
230
163
  validationRegistry: "0x0000000000000000000000000000000000000000",
164
+ agentReputation: "0x0000000000000000000000000000000000000000",
231
165
  usdc: "0x56d4d6aEe0278c5Df2FA23Ecb32eC146C9446FDf",
232
166
  identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
233
- agentReputation: "0x0000000000000000000000000000000000000000",
234
- morphoCredit: "0x0000000000000000000000000000000000000000"
167
+ morphoBlue: "0x0000000000000000000000000000000000000000"
235
168
  }
236
169
  };
237
170
  var RPC_URLS = {
@@ -267,61 +200,44 @@ function createConfig(chainId, options) {
267
200
  }
268
201
  };
269
202
  }
270
- function getUSDCAddress(chainId) {
271
- return CONTRACT_ADDRESSES[chainId].usdc;
272
- }
273
203
 
274
204
  // src/clients/AgetherClient.ts
275
205
  var AgetherClient = class _AgetherClient {
276
- /**
277
- * Undercollateralized credit is disabled for now.
278
- * All credit operations go through MorphoCredit (overcollateralized).
279
- */
280
- _requireUndercollateralizedEnabled() {
281
- throw new AgetherError(
282
- "Undercollateralized credit is not available yet. Use MorphoCredit for overcollateralized lending via the CLI: agether morpho-deposit / morpho-draw / morpho-repay",
283
- "UNDERCOLLATERALIZED_NOT_AVAILABLE"
284
- );
285
- }
286
206
  constructor(options) {
287
207
  this.config = options.config;
288
208
  this.signer = options.signer;
289
209
  this.agentId = options.agentId;
290
210
  const provider = options.signer.provider;
291
- if (!provider) {
292
- throw new AgetherError("Signer must have a provider", "NO_PROVIDER");
293
- }
211
+ if (!provider) throw new AgetherError("Signer must have a provider", "NO_PROVIDER");
294
212
  this.accountFactory = new Contract(
295
213
  options.config.contracts.accountFactory,
296
214
  ACCOUNT_FACTORY_ABI,
297
215
  options.signer
298
216
  );
299
- this.reputationCredit = new Contract(
300
- options.config.contracts.reputationCredit,
301
- REPUTATION_CREDIT_ABI,
302
- options.signer
217
+ this.identityRegistry = new Contract(
218
+ options.config.contracts.identityRegistry,
219
+ IDENTITY_REGISTRY_ABI,
220
+ provider
221
+ );
222
+ this.validationRegistry = new Contract(
223
+ options.config.contracts.validationRegistry,
224
+ VALIDATION_REGISTRY_ABI,
225
+ provider
226
+ );
227
+ this.agentReputation = new Contract(
228
+ options.config.contracts.agentReputation,
229
+ AGENT_REPUTATION_ABI,
230
+ provider
303
231
  );
304
232
  }
305
- // ============ Static Factory ============
306
- /**
307
- * Create client from private key.
308
- *
309
- * Simplest usage — only needs key, agentId, and chainId:
310
- * AgetherClient.fromPrivateKey(key, 42n, ChainId.Sepolia)
311
- *
312
- * All contract addresses and RPC URLs are resolved automatically.
313
- */
233
+ // Static Factory
314
234
  static fromPrivateKey(privateKey, agentId, chainIdOrConfig) {
315
235
  const config = typeof chainIdOrConfig === "number" ? getDefaultConfig(chainIdOrConfig) : chainIdOrConfig;
316
236
  const provider = new ethers.JsonRpcProvider(config.rpcUrl);
317
237
  const signer = new ethers.Wallet(privateKey, provider);
318
238
  return new _AgetherClient({ config, signer, agentId });
319
239
  }
320
- // ============ Account Management ============
321
- /**
322
- * Create an AgentAccount (smart wallet) for this agent.
323
- * Returns the account address.
324
- */
240
+ // Account Management
325
241
  async createAccount() {
326
242
  const tx = await this.accountFactory.createAccount(this.agentId);
327
243
  const receipt = await tx.wait();
@@ -339,138 +255,113 @@ var AgetherClient = class _AgetherClient {
339
255
  }
340
256
  return this.accountAddress;
341
257
  }
342
- /**
343
- * Get the AgentAccount address for this agent.
344
- */
345
258
  async getAccountAddress() {
346
259
  if (this.accountAddress) return this.accountAddress;
347
260
  const addr = await this.accountFactory.getAccount(this.agentId);
348
261
  if (addr === ethers.ZeroAddress) {
349
- throw new AgetherError(
350
- "No account found. Create one first with createAccount().",
351
- "NO_ACCOUNT"
352
- );
262
+ throw new AgetherError("No account found. Create one with createAccount().", "NO_ACCOUNT");
353
263
  }
354
264
  this.accountAddress = addr;
355
265
  return addr;
356
266
  }
357
- /**
358
- * Check if an account already exists.
359
- */
360
267
  async accountExists() {
361
268
  return this.accountFactory.accountExists(this.agentId);
362
269
  }
363
- // ============ Credit Application ============
364
- /**
365
- * Apply for a credit line.
366
- * @deprecated Undercollateralized credit is not available yet. Use MorphoCredit.
367
- */
368
- async apply(_limitOrApplication) {
369
- this._requireUndercollateralizedEnabled();
270
+ async predictAddress() {
271
+ return this.accountFactory.predictAddress(this.agentId);
370
272
  }
371
- // ============ Credit Line Info ============
372
- /**
373
- * Get full credit line details from ReputationCredit.
374
- */
375
- async getCreditLine() {
376
- const account = await this.getAccountAddress();
377
- const data = await this.reputationCredit.getCreditLineDetails(account);
378
- return {
379
- agentId: data[0],
380
- limit: data[1],
381
- used: data[2],
382
- aprBps: data[3],
383
- accruedInterest: data[4],
384
- requestedLimit: data[5],
385
- createdAt: data[6],
386
- lastActivityAt: data[7],
387
- status: Number(data[8])
273
+ // Balances
274
+ async getBalances() {
275
+ const provider = this.signer.provider;
276
+ const eoaAddr = await this.signer.getAddress();
277
+ const usdc = new Contract(this.config.contracts.usdc, ERC20_ABI, provider);
278
+ const ethBal = await provider.getBalance(eoaAddr);
279
+ const usdcBal = await usdc.balanceOf(eoaAddr);
280
+ const result = {
281
+ eoa: { eth: ethers.formatEther(ethBal), usdc: ethers.formatUnits(usdcBal, 6) }
388
282
  };
283
+ try {
284
+ const acctAddr = await this.getAccountAddress();
285
+ const acctEth = await provider.getBalance(acctAddr);
286
+ const acctUsdc = await usdc.balanceOf(acctAddr);
287
+ result.account = {
288
+ address: acctAddr,
289
+ eth: ethers.formatEther(acctEth),
290
+ usdc: ethers.formatUnits(acctUsdc, 6)
291
+ };
292
+ } catch {
293
+ }
294
+ return result;
389
295
  }
390
- /**
391
- * Get ICreditProvider.CreditInfo view.
392
- */
393
- async getCreditInfo() {
394
- const account = await this.getAccountAddress();
395
- const info = await this.reputationCredit.getCreditInfo(account);
296
+ async fundAccount(usdcAmount) {
297
+ const acctAddr = await this.getAccountAddress();
298
+ const usdc = new Contract(this.config.contracts.usdc, ERC20_ABI, this.signer);
299
+ const amount = ethers.parseUnits(usdcAmount, 6);
300
+ const tx = await usdc.transfer(acctAddr, amount);
301
+ const receipt = await tx.wait();
396
302
  return {
397
- limit: info.limit,
398
- used: info.used,
399
- available: info.available,
400
- accruedInterest: info.accruedInterest,
401
- aprBps: info.aprBps,
402
- isActive: info.isActive,
403
- requiresCollateral: info.requiresCollateral
303
+ txHash: receipt.hash,
304
+ blockNumber: receipt.blockNumber,
305
+ status: receipt.status === 1 ? "success" : "failed",
306
+ gasUsed: receipt.gasUsed
404
307
  };
405
308
  }
406
- /**
407
- * Get credit line status.
408
- */
409
- async getStatus() {
410
- const account = await this.getAccountAddress();
411
- const status = await this.reputationCredit.getCreditLineStatus(account);
412
- return Number(status);
413
- }
414
- /**
415
- * Get available credit.
416
- */
417
- async getAvailableCredit() {
418
- const account = await this.getAccountAddress();
419
- return this.reputationCredit.maxDrawable(account);
309
+ async withdrawUsdc(usdcAmount) {
310
+ const acctAddr = await this.getAccountAddress();
311
+ const account = new Contract(acctAddr, AGENT_ACCOUNT_ABI, this.signer);
312
+ const amount = ethers.parseUnits(usdcAmount, 6);
313
+ const eoaAddr = await this.signer.getAddress();
314
+ const tx = await account.withdraw(this.config.contracts.usdc, amount, eoaAddr);
315
+ const receipt = await tx.wait();
316
+ return {
317
+ txHash: receipt.hash,
318
+ blockNumber: receipt.blockNumber,
319
+ status: receipt.status === 1 ? "success" : "failed",
320
+ gasUsed: receipt.gasUsed
321
+ };
420
322
  }
421
- /**
422
- * Get total debt (principal + interest).
423
- */
424
- async getTotalDebt() {
425
- const account = await this.getAccountAddress();
426
- return this.reputationCredit.getTotalDebt(account);
323
+ async withdrawEth(ethAmount) {
324
+ const acctAddr = await this.getAccountAddress();
325
+ const account = new Contract(acctAddr, AGENT_ACCOUNT_ABI, this.signer);
326
+ const amount = ethers.parseEther(ethAmount);
327
+ const eoaAddr = await this.signer.getAddress();
328
+ const tx = await account.withdrawETH(amount, eoaAddr);
329
+ const receipt = await tx.wait();
330
+ return {
331
+ txHash: receipt.hash,
332
+ blockNumber: receipt.blockNumber,
333
+ status: receipt.status === 1 ? "success" : "failed",
334
+ gasUsed: receipt.gasUsed
335
+ };
427
336
  }
428
- /**
429
- * Check if agent is eligible for credit.
430
- */
431
- async isEligible() {
432
- const account = await this.getAccountAddress();
433
- return this.reputationCredit.isEligible(account);
337
+ // Identity & Validation
338
+ async isKyaApproved() {
339
+ return this.validationRegistry.isAgentCodeApproved(this.agentId);
434
340
  }
435
- // ============ Draw & Repay ============
436
- /**
437
- * Draw funds from credit line via AgentAccount.
438
- * @deprecated Undercollateralized credit is not available yet. Use MorphoCredit.
439
- */
440
- async draw(_amountOrRequest) {
441
- this._requireUndercollateralizedEnabled();
341
+ async identityExists() {
342
+ try {
343
+ await this.identityRegistry.ownerOf(this.agentId);
344
+ return true;
345
+ } catch {
346
+ return false;
347
+ }
442
348
  }
443
- /**
444
- * Repay credit line debt via AgentAccount.
445
- * @deprecated Undercollateralized credit is not available yet. Use MorphoCredit.
446
- */
447
- async repay(_amountOrRequest) {
448
- this._requireUndercollateralizedEnabled();
349
+ async getIdentityOwner() {
350
+ return this.identityRegistry.ownerOf(this.agentId);
449
351
  }
450
- /**
451
- * Repay full debt.
452
- * @deprecated Undercollateralized credit is not available yet. Use MorphoCredit.
453
- */
454
- async repayFull() {
455
- this._requireUndercollateralizedEnabled();
352
+ // Reputation
353
+ async getCreditScore() {
354
+ return this.agentReputation.getCreditScore(this.agentId);
456
355
  }
457
- // ============ Limit Increase ============
458
- /**
459
- * Request a limit increase on-chain.
460
- * @deprecated Undercollateralized credit is not available yet. Use MorphoCredit.
461
- */
462
- async requestLimitIncrease(_newLimit) {
463
- this._requireUndercollateralizedEnabled();
356
+ async isScoreFresh() {
357
+ const [fresh, age] = await this.agentReputation.isScoreFresh(this.agentId);
358
+ return { fresh, age };
464
359
  }
465
- // ============ x402 Payments ============
466
- /**
467
- * Pay for a service using credit line.
468
- * @deprecated Undercollateralized credit is not available yet. Use MorphoCredit.
469
- */
470
- async pay(_service, _amount, _asset = "USDC") {
471
- this._requireUndercollateralizedEnabled();
360
+ async isEligible(minScore = 500n) {
361
+ const [eligible, currentScore] = await this.agentReputation.isEligible(this.agentId, minScore);
362
+ return { eligible, currentScore };
472
363
  }
473
- // ============ Getters ============
364
+ // Getters
474
365
  get chainId() {
475
366
  return this.config.chainId;
476
367
  }
@@ -480,641 +371,638 @@ var AgetherClient = class _AgetherClient {
480
371
  get currentAccountAddress() {
481
372
  return this.accountAddress;
482
373
  }
483
- /** Get the underlying signer */
484
374
  getSigner() {
485
375
  return this.signer;
486
376
  }
377
+ getAgentId() {
378
+ return this.agentId;
379
+ }
487
380
  };
488
381
 
489
- // src/clients/ScoringClient.ts
382
+ // src/clients/MorphoClient.ts
383
+ import { ethers as ethers2, Contract as Contract2 } from "ethers";
490
384
  import axios from "axios";
491
- var ScoringClient = class {
492
- constructor(endpoint, apiKey) {
493
- this.client = axios.create({
494
- baseURL: endpoint,
495
- headers: {
496
- "Content-Type": "application/json",
497
- ...apiKey && { Authorization: `Bearer ${apiKey}` }
498
- },
499
- timeout: 3e4
500
- });
385
+ var BASE_COLLATERALS = {
386
+ WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
387
+ wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
388
+ cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
389
+ };
390
+ var MORPHO_API_URL = "https://api.morpho.org/graphql";
391
+ var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
392
+ var erc20Iface = new ethers2.Interface(ERC20_ABI);
393
+ var MorphoClient = class {
394
+ constructor(config) {
395
+ this._marketCache = /* @__PURE__ */ new Map();
396
+ this._discoveredAt = 0;
397
+ const chainId = config.chainId ?? 8453 /* Base */;
398
+ const defaultCfg = getDefaultConfig(chainId);
399
+ this.config = defaultCfg;
400
+ this.agentId = config.agentId;
401
+ this.provider = new ethers2.JsonRpcProvider(config.rpcUrl || defaultCfg.rpcUrl);
402
+ this.wallet = new ethers2.Wallet(config.privateKey, this.provider);
403
+ const addrs = { ...defaultCfg.contracts, ...config.contracts };
404
+ this.accountFactory = new Contract2(addrs.accountFactory, ACCOUNT_FACTORY_ABI, this.wallet);
405
+ this.morphoBlue = new Contract2(addrs.morphoBlue, MORPHO_BLUE_ABI, this.provider);
406
+ this.agentReputation = new Contract2(addrs.agentReputation, AGENT_REPUTATION_ABI, this.provider);
407
+ this.identityRegistry = new Contract2(addrs.identityRegistry, IDENTITY_REGISTRY_ABI, this.provider);
408
+ }
409
+ // ════════════════════════════════════════════════════════
410
+ // Account Management
411
+ // ════════════════════════════════════════════════════════
412
+ /** Resolve the AgentAccount address (cached). */
413
+ async getAccountAddress() {
414
+ if (this._accountAddress) return this._accountAddress;
415
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
416
+ const addr = await this.accountFactory.getAccount(BigInt(this.agentId));
417
+ if (addr === ethers2.ZeroAddress) {
418
+ throw new AgetherError("No AgentAccount found. Call register() first.", "NO_ACCOUNT");
419
+ }
420
+ this._accountAddress = addr;
421
+ return addr;
422
+ }
423
+ getAgentId() {
424
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
425
+ return this.agentId;
426
+ }
427
+ getWalletAddress() {
428
+ return this.wallet.address;
501
429
  }
502
430
  /**
503
- * Evaluate a credit application (does not submit on-chain)
504
- * Backend endpoint: POST /credit/evaluate
431
+ * Register: create ERC-8004 identity + AgentAccount in one flow.
432
+ * If already registered, returns existing state.
505
433
  */
506
- async evaluateCredit(request) {
507
- try {
508
- const response = await this.client.post("/credit/evaluate", {
509
- agentId: request.agentId.toString(),
510
- requestedLimit: request.requestedLimit.toString(),
511
- codeHash: request.codeHash
512
- });
513
- const data = response.data;
514
- if (!data.approved) {
515
- throw new ScoringRejectedError(data.riskScore, data.reason);
516
- }
517
- return {
518
- approved: data.approved,
519
- limit: BigInt(data.limit),
520
- aprBps: data.aprBps,
521
- riskScore: data.riskScore,
522
- bayesianScore: data.bayesianScore ?? 0,
523
- confidence: data.confidence ?? 0,
524
- reason: data.reason
525
- };
526
- } catch (error) {
527
- if (error instanceof ScoringRejectedError) {
528
- throw error;
434
+ async register(_name) {
435
+ const eoaAddr = this.wallet.address;
436
+ if (this.agentId) {
437
+ const exists = await this.accountFactory.accountExists(BigInt(this.agentId));
438
+ if (exists) {
439
+ const acct = await this.accountFactory.getAccount(BigInt(this.agentId));
440
+ this._accountAddress = acct;
441
+ return { agentId: this.agentId, address: eoaAddr, agentAccount: acct, alreadyRegistered: true };
529
442
  }
530
- if (axios.isAxiosError(error)) {
531
- throw new AgetherError(
532
- `Scoring request failed: ${error.message}`,
533
- "SCORING_REQUEST_FAILED",
534
- { status: error.response?.status }
535
- );
536
- }
537
- throw error;
538
443
  }
539
- }
540
- /**
541
- * Process a credit application (evaluate + on-chain approve/reject)
542
- * Backend endpoint: POST /admin/credit/process
543
- *
544
- * Requires the credit application to be in Pending status on-chain
545
- * (agent must have called applyForCredit first).
546
- */
547
- async processApplication(agentId, requestedLimit, codeHash) {
548
- try {
549
- const response = await this.client.post("/admin/credit/process", {
550
- agentId: agentId.toString(),
551
- requestedLimit: requestedLimit.toString(),
552
- codeHash
553
- });
554
- return response.data;
555
- } catch (error) {
556
- if (axios.isAxiosError(error)) {
557
- if (error.response?.status === 400) {
558
- return error.response.data;
444
+ const balance = await this.identityRegistry.balanceOf(eoaAddr);
445
+ let agentId;
446
+ if (balance > 0n && this.agentId) {
447
+ agentId = BigInt(this.agentId);
448
+ } else if (balance > 0n) {
449
+ throw new AgetherError(
450
+ "Wallet already has an ERC-8004 identity but agentId is unknown. Pass agentId in config.",
451
+ "AGENT_ID_UNKNOWN"
452
+ );
453
+ } else {
454
+ const regTx = await this.identityRegistry.register();
455
+ const regReceipt = await regTx.wait();
456
+ agentId = 0n;
457
+ for (const log of regReceipt.logs) {
458
+ try {
459
+ const parsed = this.identityRegistry.interface.parseLog({ topics: log.topics, data: log.data });
460
+ if (parsed?.name === "Transfer") {
461
+ agentId = parsed.args[2];
462
+ break;
463
+ }
464
+ } catch {
465
+ continue;
559
466
  }
560
- throw new AgetherError(
561
- `Process application failed: ${error.message}`,
562
- "PROCESS_APPLICATION_FAILED",
563
- { status: error.response?.status }
564
- );
565
467
  }
566
- throw error;
468
+ if (agentId === 0n) throw new AgetherError("Failed to parse agentId from registration", "PARSE_ERROR");
567
469
  }
470
+ this.agentId = agentId.toString();
471
+ const acctExists = await this.accountFactory.accountExists(agentId);
472
+ let txHash;
473
+ if (!acctExists) {
474
+ const tx = await this.accountFactory.createAccount(agentId);
475
+ const receipt = await tx.wait();
476
+ txHash = receipt.hash;
477
+ }
478
+ const acctAddr = await this.accountFactory.getAccount(agentId);
479
+ this._accountAddress = acctAddr;
480
+ return {
481
+ agentId: this.agentId,
482
+ address: eoaAddr,
483
+ agentAccount: acctAddr,
484
+ alreadyRegistered: acctExists,
485
+ tx: txHash
486
+ };
568
487
  }
569
- /**
570
- * Get credit score for an agent
571
- * Backend endpoint: GET /credit/score/:agentId
572
- */
573
- async getCreditScore(agentId) {
574
- const response = await this.client.get(
575
- `/credit/score/${agentId.toString()}`
576
- );
577
- return response.data;
578
- }
579
- /**
580
- * Preview scored limit for an agent
581
- * Backend endpoint: GET /credit/preview/:agentId
582
- */
583
- async previewScoredLimit(agentId) {
584
- const response = await this.client.get(
585
- `/credit/preview/${agentId.toString()}`
586
- );
587
- return response.data;
588
- }
589
- /**
590
- * Get credit line info for an agent
591
- * Backend endpoint: GET /credit/agent/:agentId
592
- */
593
- async getAgentCredit(agentId) {
594
- const response = await this.client.get(
595
- `/credit/agent/${agentId.toString()}`
596
- );
597
- return response.data;
488
+ /** Get ETH / USDC balances for EOA and AgentAccount. */
489
+ async getBalances() {
490
+ const eoaAddr = this.wallet.address;
491
+ const usdc = new Contract2(this.config.contracts.usdc, ERC20_ABI, this.provider);
492
+ const ethBal = await this.provider.getBalance(eoaAddr);
493
+ const usdcBal = await usdc.balanceOf(eoaAddr);
494
+ const result = {
495
+ agentId: this.agentId || "?",
496
+ address: eoaAddr,
497
+ eth: ethers2.formatEther(ethBal),
498
+ usdc: ethers2.formatUnits(usdcBal, 6)
499
+ };
500
+ try {
501
+ const acctAddr = await this.getAccountAddress();
502
+ const acctEth = await this.provider.getBalance(acctAddr);
503
+ const acctUsdc = await usdc.balanceOf(acctAddr);
504
+ result.agentAccount = {
505
+ address: acctAddr,
506
+ eth: ethers2.formatEther(acctEth),
507
+ usdc: ethers2.formatUnits(acctUsdc, 6)
508
+ };
509
+ } catch {
510
+ }
511
+ return result;
598
512
  }
599
- /**
600
- * Get protocol info
601
- * Backend endpoint: GET /credit/protocol-info
602
- */
603
- async getProtocolInfo() {
604
- const response = await this.client.get("/credit/protocol-info");
605
- return response.data;
513
+ /** Transfer USDC from EOA to AgentAccount. */
514
+ async fundAccount(usdcAmount) {
515
+ const acctAddr = await this.getAccountAddress();
516
+ const usdc = new Contract2(this.config.contracts.usdc, ERC20_ABI, this.wallet);
517
+ const amount = ethers2.parseUnits(usdcAmount, 6);
518
+ const tx = await usdc.transfer(acctAddr, amount);
519
+ const receipt = await tx.wait();
520
+ return { tx: receipt.hash, amount: usdcAmount, agentAccount: acctAddr };
606
521
  }
522
+ // ════════════════════════════════════════════════════════
523
+ // Market Discovery (Morpho GraphQL API)
524
+ // ════════════════════════════════════════════════════════
607
525
  /**
608
- * Get backend service status
609
- * Backend endpoint: GET /status
526
+ * Fetch USDC borrow markets on Base from Morpho API.
527
+ * Caches results for 5 minutes.
610
528
  */
611
- async getStatus() {
612
- const response = await this.client.get("/status");
613
- return response.data;
529
+ async getMarkets(forceRefresh = false) {
530
+ if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
531
+ return this._discoveredMarkets;
532
+ }
533
+ const chainId = this.config.chainId;
534
+ const usdcAddr = this.config.contracts.usdc.toLowerCase();
535
+ const query = `{
536
+ markets(
537
+ first: 50
538
+ orderBy: SupplyAssetsUsd
539
+ orderDirection: Desc
540
+ where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"] }
541
+ ) {
542
+ items {
543
+ uniqueKey
544
+ lltv
545
+ oracleAddress
546
+ irmAddress
547
+ loanAsset { address symbol decimals }
548
+ collateralAsset { address symbol decimals }
549
+ state {
550
+ borrowAssets
551
+ supplyAssets
552
+ utilization
553
+ }
554
+ }
555
+ }
556
+ }`;
557
+ try {
558
+ const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
559
+ const items = resp.data?.data?.markets?.items ?? [];
560
+ this._discoveredMarkets = items.map((m) => ({
561
+ uniqueKey: m.uniqueKey,
562
+ loanAsset: m.loanAsset,
563
+ collateralAsset: m.collateralAsset ?? { address: ethers2.ZeroAddress, symbol: "N/A", decimals: 0 },
564
+ oracle: m.oracleAddress,
565
+ irm: m.irmAddress,
566
+ lltv: BigInt(m.lltv),
567
+ totalSupplyAssets: BigInt(m.state?.supplyAssets ?? "0"),
568
+ totalBorrowAssets: BigInt(m.state?.borrowAssets ?? "0"),
569
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0
570
+ }));
571
+ this._discoveredAt = Date.now();
572
+ for (const mi of this._discoveredMarkets) {
573
+ if (mi.collateralAsset.address !== ethers2.ZeroAddress) {
574
+ this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
575
+ loanToken: mi.loanAsset.address,
576
+ collateralToken: mi.collateralAsset.address,
577
+ oracle: mi.oracle,
578
+ irm: mi.irm,
579
+ lltv: mi.lltv
580
+ });
581
+ }
582
+ }
583
+ return this._discoveredMarkets;
584
+ } catch {
585
+ return this._discoveredMarkets ?? [];
586
+ }
614
587
  }
615
588
  /**
616
- * Get full OCCR score explanation with subscores, decay, and loan positions
617
- * Backend endpoint: GET /agents/:agentId/score-explanation
589
+ * Get MarketParams for a collateral token.
590
+ * Tries cache API → on-chain idToMarketParams.
618
591
  */
619
- async getScoreExplanation(agentId) {
620
- const response = await this.client.get(
621
- `/agents/${agentId.toString()}/score-explanation`
592
+ async findMarketForCollateral(collateralSymbolOrAddress) {
593
+ const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
594
+ const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
595
+ const cached = this._marketCache.get(colAddr);
596
+ if (cached) return cached;
597
+ await this.getMarkets();
598
+ const fromApi = this._marketCache.get(colAddr);
599
+ if (fromApi) return fromApi;
600
+ throw new AgetherError(
601
+ `No Morpho market found for collateral ${collateralSymbolOrAddress}`,
602
+ "MARKET_NOT_FOUND"
622
603
  );
623
- return response.data;
624
604
  }
625
- };
626
-
627
- // src/clients/VaultClient.ts
628
- import { Contract as Contract2 } from "ethers";
629
- var VaultClient = class {
630
- constructor(options) {
631
- this.config = options.config;
632
- this.signer = options.signer;
633
- this.vault = new Contract2(
634
- options.config.contracts.lpVault,
635
- LP_VAULT_ABI,
636
- options.signer
637
- );
605
+ /** Read MarketParams on-chain by market ID (bytes32). */
606
+ async getMarketParams(marketId) {
607
+ const result = await this.morphoBlue.idToMarketParams(marketId);
608
+ return {
609
+ loanToken: result.loanToken,
610
+ collateralToken: result.collateralToken,
611
+ oracle: result.oracle,
612
+ irm: result.irm,
613
+ lltv: result.lltv
614
+ };
638
615
  }
639
- // ============ LP Operations ============
640
- /**
641
- * Deposit assets to vault
642
- */
643
- async deposit(amount) {
644
- const asset = await this.getAsset();
645
- const allowance = await asset.allowance(
646
- await this.signer.getAddress(),
647
- this.config.contracts.lpVault
648
- );
649
- if (allowance < amount) {
650
- const approveTx = await asset.approve(
651
- this.config.contracts.lpVault,
652
- amount
653
- );
654
- await approveTx.wait();
655
- }
656
- const receiver = await this.signer.getAddress();
657
- const tx = await this.vault.deposit(amount, receiver);
658
- const receipt = await tx.wait();
616
+ // ════════════════════════════════════════════════════════
617
+ // Position Reads
618
+ // ════════════════════════════════════════════════════════
619
+ /** Read on-chain position for a specific market. */
620
+ async getPosition(marketId) {
621
+ const acctAddr = await this.getAccountAddress();
622
+ const pos = await this.morphoBlue.position(marketId, acctAddr);
659
623
  return {
660
- txHash: receipt.hash,
661
- blockNumber: receipt.blockNumber,
662
- status: receipt.status === 1 ? "success" : "failed",
663
- gasUsed: receipt.gasUsed
624
+ supplyShares: pos.supplyShares,
625
+ borrowShares: pos.borrowShares,
626
+ collateral: pos.collateral
664
627
  };
665
628
  }
666
629
  /**
667
- * Withdraw assets from vault
630
+ * Full status: positions across all discovered markets.
668
631
  */
669
- async withdraw(amount) {
670
- const receiver = await this.signer.getAddress();
671
- const owner = receiver;
672
- const tx = await this.vault.withdraw(amount, receiver, owner);
673
- const receipt = await tx.wait();
632
+ async getStatus() {
633
+ const acctAddr = await this.getAccountAddress();
634
+ const markets = await this.getMarkets();
635
+ const positions = [];
636
+ let totalDebt = 0n;
637
+ for (const m of markets) {
638
+ if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
639
+ try {
640
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
641
+ if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
642
+ let debt = 0n;
643
+ if (pos.borrowShares > 0n) {
644
+ try {
645
+ const mkt = await this.morphoBlue.market(m.uniqueKey);
646
+ const totalBorrowShares = BigInt(mkt.totalBorrowShares);
647
+ const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
648
+ debt = totalBorrowShares > 0n ? BigInt(pos.borrowShares) * totalBorrowAssets / totalBorrowShares : 0n;
649
+ totalDebt += debt;
650
+ } catch {
651
+ }
652
+ }
653
+ positions.push({
654
+ marketId: m.uniqueKey,
655
+ collateralToken: m.collateralAsset.symbol,
656
+ collateral: ethers2.formatUnits(pos.collateral, m.collateralAsset.decimals),
657
+ borrowShares: pos.borrowShares.toString(),
658
+ supplyShares: pos.supplyShares.toString(),
659
+ debt: ethers2.formatUnits(debt, 6)
660
+ });
661
+ } catch {
662
+ continue;
663
+ }
664
+ }
674
665
  return {
675
- txHash: receipt.hash,
676
- blockNumber: receipt.blockNumber,
677
- status: receipt.status === 1 ? "success" : "failed",
678
- gasUsed: receipt.gasUsed
666
+ agentId: this.agentId || "?",
667
+ agentAccount: acctAddr,
668
+ totalDebt: ethers2.formatUnits(totalDebt, 6),
669
+ positions
679
670
  };
680
671
  }
672
+ // ════════════════════════════════════════════════════════
673
+ // Lending Operations (all via AgentAccount.executeBatch)
674
+ // ════════════════════════════════════════════════════════
681
675
  /**
682
- * Redeem shares for assets
683
- */
684
- async redeem(shares) {
685
- const receiver = await this.signer.getAddress();
686
- const owner = receiver;
687
- const tx = await this.vault.redeem(shares, receiver, owner);
688
- const receipt = await tx.wait();
676
+ * Deposit collateral into Morpho Blue.
677
+ *
678
+ * Flow:
679
+ * 1. EOA transfers collateral to AgentAccount
680
+ * 2. AgentAccount.executeBatch:
681
+ * [collateral.approve(MorphoBlue), Morpho.supplyCollateral(params)]
682
+ */
683
+ async supplyCollateral(tokenSymbol, amount, marketParams) {
684
+ const acctAddr = await this.getAccountAddress();
685
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
686
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
687
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
688
+ const weiAmount = ethers2.parseUnits(amount, colInfo.decimals);
689
+ const morphoAddr = this.config.contracts.morphoBlue;
690
+ const colToken = new Contract2(colInfo.address, ERC20_ABI, this.wallet);
691
+ const transferTx = await colToken.transfer(acctAddr, weiAmount);
692
+ await transferTx.wait();
693
+ const targets = [colInfo.address, morphoAddr];
694
+ const values = [0n, 0n];
695
+ const datas = [
696
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, weiAmount]),
697
+ morphoIface.encodeFunctionData("supplyCollateral", [
698
+ this._toTuple(params),
699
+ weiAmount,
700
+ acctAddr,
701
+ "0x"
702
+ ])
703
+ ];
704
+ const receipt = await this.batch(targets, values, datas);
689
705
  return {
690
- txHash: receipt.hash,
691
- blockNumber: receipt.blockNumber,
692
- status: receipt.status === 1 ? "success" : "failed",
693
- gasUsed: receipt.gasUsed
706
+ tx: receipt.hash,
707
+ collateralToken: tokenSymbol,
708
+ amount,
709
+ agentAccount: acctAddr
694
710
  };
695
711
  }
696
- // ============ View Functions ============
697
712
  /**
698
- * Get vault statistics
699
- */
700
- async getStats() {
701
- const [totalAssets, totalBorrowed, availableLiquidity] = await Promise.all([
702
- this.vault.totalAssets(),
703
- this.vault.totalBorrowed(),
704
- this.vault.availableLiquidity()
713
+ * Borrow USDC against existing collateral.
714
+ *
715
+ * AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
716
+ *
717
+ * @param usdcAmount - USDC amount (e.g. '100')
718
+ * @param tokenSymbol - collateral symbol to identify which market (default: first with collateral)
719
+ */
720
+ async borrow(usdcAmount, tokenSymbol, marketParams) {
721
+ const acctAddr = await this.getAccountAddress();
722
+ const amount = ethers2.parseUnits(usdcAmount, 6);
723
+ const morphoAddr = this.config.contracts.morphoBlue;
724
+ let params;
725
+ let usedToken = tokenSymbol || "WETH";
726
+ if (marketParams) {
727
+ params = marketParams;
728
+ } else if (tokenSymbol) {
729
+ params = await this.findMarketForCollateral(tokenSymbol);
730
+ } else {
731
+ const { params: p, symbol } = await this._findActiveMarket();
732
+ params = p;
733
+ usedToken = symbol;
734
+ }
735
+ const data = morphoIface.encodeFunctionData("borrow", [
736
+ this._toTuple(params),
737
+ amount,
738
+ 0n,
739
+ acctAddr,
740
+ acctAddr
705
741
  ]);
706
- const utilizationRate = totalAssets > 0n ? Number(totalBorrowed * 10000n / totalAssets) / 100 : 0;
707
- const totalSupply = await this.vault.totalSupply();
708
- const sharePrice = totalSupply > 0n ? totalAssets * BigInt(1e18) / totalSupply : BigInt(1e18);
742
+ const receipt = await this.exec(morphoAddr, data);
709
743
  return {
710
- totalAssets,
711
- totalBorrowed,
712
- availableLiquidity,
713
- utilizationRate,
714
- sharePrice
744
+ tx: receipt.hash,
745
+ amount: usdcAmount,
746
+ collateralToken: usedToken,
747
+ agentAccount: acctAddr
715
748
  };
716
749
  }
717
750
  /**
718
- * Get LP position
719
- */
720
- async getPosition(address) {
721
- const owner = address || await this.signer.getAddress();
722
- const shares = await this.vault.balanceOf(owner);
723
- const assets = await this.vault.convertToAssets(shares);
724
- const pendingYield = 0n;
751
+ * Deposit collateral AND borrow USDC in one batched transaction.
752
+ *
753
+ * AgentAccount.executeBatch:
754
+ * [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
755
+ *
756
+ * The collateral must be transferred to AgentAccount first.
757
+ */
758
+ async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
759
+ const acctAddr = await this.getAccountAddress();
760
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
761
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
762
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
763
+ const colWei = ethers2.parseUnits(collateralAmount, colInfo.decimals);
764
+ const borrowWei = ethers2.parseUnits(borrowUsdcAmount, 6);
765
+ const morphoAddr = this.config.contracts.morphoBlue;
766
+ const colToken = new Contract2(colInfo.address, ERC20_ABI, this.wallet);
767
+ const transferTx = await colToken.transfer(acctAddr, colWei);
768
+ await transferTx.wait();
769
+ const targets = [colInfo.address, morphoAddr, morphoAddr];
770
+ const values = [0n, 0n, 0n];
771
+ const datas = [
772
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, colWei]),
773
+ morphoIface.encodeFunctionData("supplyCollateral", [
774
+ this._toTuple(params),
775
+ colWei,
776
+ acctAddr,
777
+ "0x"
778
+ ]),
779
+ morphoIface.encodeFunctionData("borrow", [
780
+ this._toTuple(params),
781
+ borrowWei,
782
+ 0n,
783
+ acctAddr,
784
+ acctAddr
785
+ ])
786
+ ];
787
+ const receipt = await this.batch(targets, values, datas);
725
788
  return {
726
- shares,
727
- assets,
728
- pendingYield
789
+ tx: receipt.hash,
790
+ collateralToken: tokenSymbol,
791
+ collateralAmount,
792
+ borrowAmount: borrowUsdcAmount,
793
+ agentAccount: acctAddr
729
794
  };
730
795
  }
731
796
  /**
732
- * Preview deposit (how many shares for assets)
733
- */
734
- async previewDeposit(assets) {
735
- return await this.vault.previewDeposit(assets);
736
- }
737
- /**
738
- * Preview withdraw (how many assets for shares)
739
- */
740
- async previewRedeem(shares) {
741
- return await this.vault.previewRedeem(shares);
797
+ * Repay borrowed USDC from AgentAccount.
798
+ *
799
+ * AgentAccount.executeBatch:
800
+ * [USDC.approve(MorphoBlue), Morpho.repay(params)]
801
+ */
802
+ async repay(usdcAmount, tokenSymbol, marketParams) {
803
+ const acctAddr = await this.getAccountAddress();
804
+ const amount = ethers2.parseUnits(usdcAmount, 6);
805
+ const morphoAddr = this.config.contracts.morphoBlue;
806
+ const usdcAddr = this.config.contracts.usdc;
807
+ let params;
808
+ if (marketParams) {
809
+ params = marketParams;
810
+ } else if (tokenSymbol) {
811
+ params = await this.findMarketForCollateral(tokenSymbol);
812
+ } else {
813
+ const { params: p } = await this._findActiveMarket();
814
+ params = p;
815
+ }
816
+ const targets = [usdcAddr, morphoAddr];
817
+ const values = [0n, 0n];
818
+ const datas = [
819
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, amount]),
820
+ morphoIface.encodeFunctionData("repay", [
821
+ this._toTuple(params),
822
+ amount,
823
+ 0n,
824
+ acctAddr,
825
+ "0x"
826
+ ])
827
+ ];
828
+ const receipt = await this.batch(targets, values, datas);
829
+ let remainingDebt = "0";
830
+ try {
831
+ const status = await this.getStatus();
832
+ remainingDebt = status.totalDebt;
833
+ } catch {
834
+ }
835
+ return { tx: receipt.hash, amount: usdcAmount, remainingDebt };
742
836
  }
743
837
  /**
744
- * Check if withdrawal is allowed (sufficient liquidity)
745
- */
746
- async canWithdraw(assets) {
747
- const liquidity = await this.vault.availableLiquidity();
748
- return liquidity >= assets;
838
+ * Withdraw collateral from Morpho Blue.
839
+ *
840
+ * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
841
+ *
842
+ * @param receiver - defaults to EOA wallet
843
+ */
844
+ async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
845
+ const acctAddr = await this.getAccountAddress();
846
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
847
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
848
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
849
+ const morphoAddr = this.config.contracts.morphoBlue;
850
+ const dest = receiver || this.wallet.address;
851
+ let weiAmount;
852
+ if (amount === "all") {
853
+ const markets = await this.getMarkets();
854
+ const market = markets.find(
855
+ (m) => m.collateralAsset.address.toLowerCase() === colInfo.address.toLowerCase()
856
+ );
857
+ if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
858
+ const pos = await this.morphoBlue.position(market.uniqueKey, acctAddr);
859
+ weiAmount = pos.collateral;
860
+ if (weiAmount === 0n) throw new AgetherError("No collateral to withdraw", "NO_COLLATERAL");
861
+ } else {
862
+ weiAmount = ethers2.parseUnits(amount, colInfo.decimals);
863
+ }
864
+ const data = morphoIface.encodeFunctionData("withdrawCollateral", [
865
+ this._toTuple(params),
866
+ weiAmount,
867
+ acctAddr,
868
+ dest
869
+ ]);
870
+ const receipt = await this.exec(morphoAddr, data);
871
+ let remainingCollateral = "0";
872
+ try {
873
+ const markets = await this.getMarkets();
874
+ const market = markets.find(
875
+ (m) => m.collateralAsset.address.toLowerCase() === colInfo.address.toLowerCase()
876
+ );
877
+ if (market) {
878
+ const pos = await this.morphoBlue.position(market.uniqueKey, acctAddr);
879
+ remainingCollateral = ethers2.formatUnits(pos.collateral, colInfo.decimals);
880
+ }
881
+ } catch {
882
+ }
883
+ return {
884
+ tx: receipt.hash,
885
+ token: tokenSymbol,
886
+ amount: amount === "all" ? ethers2.formatUnits(weiAmount, colInfo.decimals) : amount,
887
+ remainingCollateral,
888
+ destination: dest
889
+ };
749
890
  }
750
891
  /**
751
- * Get current APY estimate
892
+ * Sponsor: transfer collateral to another agent's AgentAccount.
893
+ * (The agent must then supplyCollateral themselves via their own account.)
752
894
  */
753
- async estimateAPY() {
754
- const stats = await this.getStats();
755
- const avgAPR = 1200;
756
- const protocolFee = 1e3;
757
- const lpYield = avgAPR * (1e4 - protocolFee) / 1e4;
758
- const effectiveYield = lpYield * stats.utilizationRate / 100;
759
- return effectiveYield / 100;
760
- }
761
- // ============ Utility ============
762
- async getAsset() {
763
- if (!this.asset) {
764
- const assetAddress = await this.vault.asset();
765
- this.asset = new Contract2(assetAddress, ERC20_ABI, this.signer);
895
+ async sponsor(target, tokenSymbol, amount) {
896
+ const colInfo = BASE_COLLATERALS[tokenSymbol];
897
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
898
+ let targetAddr;
899
+ if (target.address) {
900
+ targetAddr = target.address;
901
+ } else if (target.agentId) {
902
+ targetAddr = await this.accountFactory.getAccount(BigInt(target.agentId));
903
+ if (targetAddr === ethers2.ZeroAddress) throw new AgetherError("Target agent has no account", "NO_ACCOUNT");
904
+ } else {
905
+ throw new AgetherError("Provide agentId or address", "INVALID_TARGET");
906
+ }
907
+ const weiAmount = ethers2.parseUnits(amount, colInfo.decimals);
908
+ const colToken = new Contract2(colInfo.address, ERC20_ABI, this.wallet);
909
+ const tx = await colToken.transfer(targetAddr, weiAmount);
910
+ const receipt = await tx.wait();
911
+ return { tx: receipt.hash, targetAccount: targetAddr, targetAgentId: target.agentId };
912
+ }
913
+ // ════════════════════════════════════════════════════════
914
+ // Reputation (AgentReputation contract)
915
+ // ════════════════════════════════════════════════════════
916
+ async getCreditScore() {
917
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
918
+ return this.agentReputation.getCreditScore(BigInt(this.agentId));
919
+ }
920
+ async getAttestation() {
921
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
922
+ const att = await this.agentReputation.getAttestation(BigInt(this.agentId));
923
+ return { score: att.score, timestamp: att.timestamp, signer: att.signer };
924
+ }
925
+ async isEligible(minScore = 500n) {
926
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
927
+ const [eligible, currentScore] = await this.agentReputation.isEligible(BigInt(this.agentId), minScore);
928
+ return { eligible, currentScore };
929
+ }
930
+ async isScoreFresh() {
931
+ if (!this.agentId) throw new AgetherError("agentId not set", "NO_AGENT_ID");
932
+ const [fresh, age] = await this.agentReputation.isScoreFresh(BigInt(this.agentId));
933
+ return { fresh, age };
934
+ }
935
+ // ════════════════════════════════════════════════════════
936
+ // Internal Helpers
937
+ // ════════════════════════════════════════════════════════
938
+ /**
939
+ * Execute a single call via AgentAccount.execute.
940
+ */
941
+ async exec(target, data, value = 0n) {
942
+ const acctAddr = await this.getAccountAddress();
943
+ const account = new Contract2(acctAddr, AGENT_ACCOUNT_ABI, this.wallet);
944
+ let gasLimit;
945
+ try {
946
+ const estimate = await account.execute.estimateGas(target, value, data);
947
+ gasLimit = estimate * 130n / 100n;
948
+ } catch {
949
+ gasLimit = 500000n;
766
950
  }
767
- return this.asset;
951
+ const tx = await account.execute(target, value, data, { gasLimit });
952
+ return tx.wait();
768
953
  }
769
954
  /**
770
- * Get underlying asset address
955
+ * Execute multiple calls via AgentAccount.executeBatch.
771
956
  */
772
- async getAssetAddress() {
773
- return await this.vault.asset();
774
- }
775
- /**
776
- * Get vault share token address
777
- */
778
- getVaultAddress() {
779
- return this.config.contracts.lpVault;
780
- }
781
- };
782
-
783
- // src/clients/AgentIdentityClient.ts
784
- import { ethers as ethers2 } from "ethers";
785
- var ERC8004_IDENTITY_ABI = [
786
- // Registration
787
- "function register() returns (uint256)",
788
- "function register(string agentURI) returns (uint256)",
789
- "function register(string agentURI, tuple(string key, bytes value)[] metadata) returns (uint256)",
790
- // Management
791
- "function setAgentURI(uint256 agentId, string newURI)",
792
- "function setMetadata(uint256 agentId, string metadataKey, bytes metadataValue)",
793
- "function getMetadata(uint256 agentId, string metadataKey) view returns (bytes)",
794
- // ERC-721 standard
795
- "function ownerOf(uint256 tokenId) view returns (address)",
796
- "function balanceOf(address owner) view returns (uint256)",
797
- "function tokenURI(uint256 tokenId) view returns (string)",
798
- "function transferFrom(address from, address to, uint256 tokenId)",
799
- "function approve(address to, uint256 tokenId)",
800
- "function getApproved(uint256 tokenId) view returns (address)",
801
- // Events
802
- "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
803
- ];
804
- var ERC8004_REPUTATION_ABI = [
805
- // Feedback
806
- "function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash)",
807
- "function revokeFeedback(uint256 agentId, uint64 feedbackIndex)",
808
- // Queries
809
- "function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex) view returns (int128 value, uint8 valueDecimals, string tag1, string tag2, bool isRevoked)",
810
- "function getSummary(uint256 agentId, address[] clientAddresses, string tag1, string tag2) view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals)",
811
- "function getClients(uint256 agentId) view returns (address[])",
812
- "function getLastIndex(uint256 agentId, address clientAddress) view returns (uint64)",
813
- // Registry
814
- "function getIdentityRegistry() view returns (address)"
815
- ];
816
- var ERC8004_SEPOLIA = {
817
- identityRegistry: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
818
- reputationRegistry: "0x8004B663056A597Dffe9eCcC1965A193B7388713"
819
- };
820
- var AgentIdentityClient = class {
821
- constructor(options) {
822
- this.config = options.config;
823
- this.signer = options.signer;
824
- const identityAddr = options.config.contracts?.identityRegistry || ERC8004_SEPOLIA.identityRegistry;
825
- const reputationAddr = options.config.contracts?.reputationRegistry || ERC8004_SEPOLIA.reputationRegistry;
826
- this.identityRegistry = new ethers2.Contract(identityAddr, ERC8004_IDENTITY_ABI, this.signer);
827
- this.reputationRegistry = new ethers2.Contract(reputationAddr, ERC8004_REPUTATION_ABI, this.signer);
828
- }
829
- // ============ Identity Functions ============
830
- /**
831
- * Register a new agent (minimal - no metadata)
832
- */
833
- async register() {
834
- const tx = await this.identityRegistry["register()"]();
835
- const receipt = await tx.wait();
836
- const agentId = this.parseAgentIdFromReceipt(receipt);
837
- return { agentId, txHash: receipt.hash };
838
- }
839
- /**
840
- * Register agent with IPFS/HTTP URI to metadata JSON
841
- * @param agentURI URI pointing to agent metadata (ipfs:// or https://)
842
- */
843
- async registerWithURI(agentURI) {
844
- const tx = await this.identityRegistry["register(string)"](agentURI);
845
- const receipt = await tx.wait();
846
- const agentId = this.parseAgentIdFromReceipt(receipt);
847
- return { agentId, txHash: receipt.hash };
848
- }
849
- /**
850
- * Check if the signer already owns an ERC-8004 identity token.
851
- * Returns true if balanceOf > 0, false otherwise.
852
- * Note: Cannot determine the specific agentId without enumeration —
853
- * use agether init <pk> --agent-id <id> if you know your agentId.
854
- */
855
- async hasExistingIdentity() {
957
+ async batch(targets, values, datas) {
958
+ const acctAddr = await this.getAccountAddress();
959
+ const account = new Contract2(acctAddr, AGENT_ACCOUNT_ABI, this.wallet);
960
+ let gasLimit;
856
961
  try {
857
- const address = await this.signer.getAddress();
858
- const balance = await this.identityRegistry.balanceOf(address);
859
- return balance > 0n;
962
+ const estimate = await account.executeBatch.estimateGas(targets, values, datas);
963
+ gasLimit = estimate * 130n / 100n;
860
964
  } catch {
861
- return false;
862
- }
863
- }
864
- /**
865
- * Register only if no identity exists; otherwise throw.
866
- * Prevents accidental double-registration.
867
- */
868
- async registerOrGet() {
869
- const hasIdentity = await this.hasExistingIdentity();
870
- if (hasIdentity) {
871
- throw new Error("Wallet already owns an ERC-8004 identity. Use agether init <pk> --agent-id <id> to set it.");
872
- }
873
- const result = await this.register();
874
- return { ...result, existing: false };
875
- }
876
- /**
877
- * Register with URI only if no identity exists; otherwise throw.
878
- * Prevents accidental double-registration.
879
- */
880
- async registerOrGetWithURI(agentURI) {
881
- const hasIdentity = await this.hasExistingIdentity();
882
- if (hasIdentity) {
883
- throw new Error("Wallet already owns an ERC-8004 identity. Use agether init <pk> --agent-id <id> to set it.");
965
+ gasLimit = 800000n;
884
966
  }
885
- const result = await this.registerWithURI(agentURI);
886
- return { ...result, existing: false };
887
- }
888
- /**
889
- * Register agent with URI and on-chain metadata
890
- */
891
- async registerWithMetadata(agentURI, metadata) {
892
- const metadataEntries = metadata.map((m) => ({
893
- key: m.key,
894
- value: ethers2.toUtf8Bytes(m.value)
895
- }));
896
- const tx = await this.identityRegistry["register(string,tuple(string,bytes)[])"](
897
- agentURI,
898
- metadataEntries
899
- );
900
- const receipt = await tx.wait();
901
- const agentId = this.parseAgentIdFromReceipt(receipt);
902
- return { agentId, txHash: receipt.hash };
903
- }
904
- /**
905
- * Get agent owner address
906
- */
907
- async getOwner(agentId) {
908
- return await this.identityRegistry.ownerOf(agentId);
909
- }
910
- /**
911
- * Get agent URI (metadata JSON location)
912
- */
913
- async getAgentURI(agentId) {
914
- return await this.identityRegistry.tokenURI(agentId);
915
- }
916
- /**
917
- * Update agent URI
918
- */
919
- async setAgentURI(agentId, newURI) {
920
- const tx = await this.identityRegistry.setAgentURI(agentId, newURI);
921
- const receipt = await tx.wait();
922
- return receipt.hash;
923
- }
924
- /**
925
- * Set on-chain metadata (key-value)
926
- */
927
- async setMetadata(agentId, key, value) {
928
- const tx = await this.identityRegistry.setMetadata(
929
- agentId,
930
- key,
931
- ethers2.toUtf8Bytes(value)
932
- );
933
- const receipt = await tx.wait();
934
- return receipt.hash;
935
- }
936
- /**
937
- * Get on-chain metadata
938
- */
939
- async getMetadata(agentId, key) {
940
- const value = await this.identityRegistry.getMetadata(agentId, key);
941
- return ethers2.toUtf8String(value);
942
- }
943
- /**
944
- * Transfer agent to new owner
945
- */
946
- async transfer(agentId, to) {
947
- const from = await this.signer.getAddress();
948
- const tx = await this.identityRegistry.transferFrom(from, to, agentId);
949
- const receipt = await tx.wait();
950
- return receipt.hash;
951
- }
952
- /**
953
- * Fetch and parse agent metadata from URI
954
- */
955
- async fetchAgentMetadata(agentId) {
956
- try {
957
- const uri = await this.getAgentURI(agentId);
958
- let fetchUrl = uri;
959
- if (uri.startsWith("ipfs://")) {
960
- const cid = uri.replace("ipfs://", "");
961
- fetchUrl = `https://ipfs.io/ipfs/${cid}`;
962
- }
963
- const response = await fetch(fetchUrl);
964
- if (!response.ok) return null;
965
- return await response.json();
966
- } catch {
967
- return null;
968
- }
969
- }
970
- // ============ Reputation Functions ============
971
- /**
972
- * Give feedback to an agent
973
- */
974
- async giveFeedback(input) {
975
- const feedbackHash = ethers2.keccak256(
976
- ethers2.AbiCoder.defaultAbiCoder().encode(
977
- ["uint256", "int128", "uint256"],
978
- [input.agentId, input.value, Date.now()]
979
- )
980
- );
981
- const tx = await this.reputationRegistry.giveFeedback(
982
- input.agentId,
983
- input.value,
984
- input.decimals || 0,
985
- input.tag1 || "",
986
- input.tag2 || "",
987
- input.endpoint || "",
988
- input.feedbackURI || "",
989
- feedbackHash
990
- );
991
- const receipt = await tx.wait();
992
- return receipt.hash;
993
- }
994
- /**
995
- * Give positive feedback (shorthand)
996
- */
997
- async givePosisitiveFeedback(agentId, value = 100, tags = {}) {
998
- return this.giveFeedback({
999
- agentId,
1000
- value: Math.abs(value),
1001
- ...tags
1002
- });
1003
- }
1004
- /**
1005
- * Give negative feedback (e.g., for defaults)
1006
- */
1007
- async giveNegativeFeedback(agentId, value = -100, tags = {}) {
1008
- return this.giveFeedback({
1009
- agentId,
1010
- value: -Math.abs(value),
1011
- ...tags
1012
- });
1013
- }
1014
- /**
1015
- * Record credit default in reputation system
1016
- */
1017
- async recordCreditDefault(agentId, creditLineId) {
1018
- return this.giveFeedback({
1019
- agentId,
1020
- value: -100,
1021
- tag1: "credit",
1022
- tag2: "default",
1023
- feedbackURI: `agether://default/${creditLineId}`
1024
- });
1025
- }
1026
- /**
1027
- * Get reputation summary for an agent
1028
- */
1029
- async getReputation(agentId, tag1 = "", tag2 = "") {
1030
- const clients = await this.reputationRegistry.getClients(agentId);
1031
- if (clients.length === 0) {
1032
- return {
1033
- count: 0,
1034
- totalValue: 0,
1035
- averageValue: 0,
1036
- clients: []
1037
- };
1038
- }
1039
- const [count, summaryValue, decimals] = await this.reputationRegistry.getSummary(
1040
- agentId,
1041
- clients,
1042
- tag1,
1043
- tag2
1044
- );
1045
- const divisor = 10 ** Number(decimals);
1046
- const total = Number(summaryValue) / divisor;
1047
- return {
1048
- count: Number(count),
1049
- totalValue: total,
1050
- averageValue: Number(count) > 0 ? total / Number(count) : 0,
1051
- clients: clients.map((c) => c)
1052
- };
1053
- }
1054
- /**
1055
- * Check if agent has negative credit reputation
1056
- */
1057
- async hasNegativeCreditReputation(agentId) {
1058
- const rep = await this.getReputation(agentId, "credit", "");
1059
- return rep.count > 0 && rep.averageValue < 0;
1060
- }
1061
- // ============ Agether Integration ============
1062
- /**
1063
- * Verify agent is eligible for Agether credit
1064
- * Checks:
1065
- * 1. Agent exists in ERC-8004 registry
1066
- * 2. Agent has no negative credit reputation
1067
- */
1068
- async verifyForCredit(agentId) {
1069
- let owner;
1070
- try {
1071
- owner = await this.getOwner(agentId);
1072
- } catch {
1073
- return { eligible: false, reason: "Agent not found in ERC-8004 registry" };
1074
- }
1075
- const reputation = await this.getReputation(agentId, "credit", "");
1076
- if (reputation.count > 0 && reputation.averageValue < 0) {
1077
- return {
1078
- eligible: false,
1079
- reason: "Agent has negative credit reputation",
1080
- owner,
1081
- reputation
1082
- };
1083
- }
1084
- return {
1085
- eligible: true,
1086
- owner,
1087
- reputation
1088
- };
1089
- }
1090
- // ============ Helpers ============
1091
- parseAgentIdFromReceipt(receipt) {
1092
- for (const log of receipt.logs) {
967
+ const tx = await account.executeBatch(targets, values, datas, { gasLimit });
968
+ return tx.wait();
969
+ }
970
+ /** Convert MorphoMarketParams to Solidity tuple. */
971
+ _toTuple(p) {
972
+ return [p.loanToken, p.collateralToken, p.oracle, p.irm, p.lltv];
973
+ }
974
+ /** Find the first market where the agent has collateral deposited. */
975
+ async _findActiveMarket() {
976
+ const acctAddr = await this.getAccountAddress();
977
+ const markets = await this.getMarkets();
978
+ for (const m of markets) {
979
+ if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
1093
980
  try {
1094
- const parsed = this.identityRegistry.interface.parseLog({
1095
- topics: log.topics,
1096
- data: log.data
1097
- });
1098
- if (parsed?.name === "Transfer") {
1099
- return parsed.args[2];
981
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
982
+ if (pos.collateral > 0n) {
983
+ return {
984
+ params: {
985
+ loanToken: m.loanAsset.address,
986
+ collateralToken: m.collateralAsset.address,
987
+ oracle: m.oracle,
988
+ irm: m.irm,
989
+ lltv: m.lltv
990
+ },
991
+ symbol: m.collateralAsset.symbol
992
+ };
1100
993
  }
1101
994
  } catch {
1102
995
  continue;
1103
996
  }
1104
997
  }
1105
- return 0n;
1106
- }
1107
- /**
1108
- * Get contract addresses
1109
- */
1110
- getContractAddresses() {
1111
- return {
1112
- identity: this.identityRegistry.target,
1113
- reputation: this.reputationRegistry.target
1114
- };
998
+ const params = await this.findMarketForCollateral("WETH");
999
+ return { params, symbol: "WETH" };
1115
1000
  }
1116
1001
  };
1117
1002
 
1003
+ // src/clients/ScoringClient.ts
1004
+ import axios2 from "axios";
1005
+
1118
1006
  // src/clients/X402Client.ts
1119
1007
  import { ethers as ethers3 } from "ethers";
1120
1008
  var USDC_DOMAINS = {
@@ -1128,7 +1016,7 @@ function chainIdFromNetwork(network) {
1128
1016
  const m = network.match(/^eip155:(\d+)$/);
1129
1017
  return m ? Number(m[1]) : 1;
1130
1018
  }
1131
- var _X402Client = class _X402Client {
1019
+ var X402Client = class {
1132
1020
  constructor(config) {
1133
1021
  this.config = config;
1134
1022
  const provider = new ethers3.JsonRpcProvider(config.rpcUrl);
@@ -1177,9 +1065,6 @@ var _X402Client = class _X402Client {
1177
1065
  console.log(` amount : ${requirements.amount} (atomic)`);
1178
1066
  console.log(` asset : ${requirements.asset}`);
1179
1067
  console.log(` payTo : ${requirements.payTo}`);
1180
- if (this.config.autoDraw && this.config.accountAddress && this.config.morphoCreditAddress) {
1181
- await this.ensureBalance(BigInt(requirements.amount));
1182
- }
1183
1068
  console.log(" [3/4] Signing EIP-3009 transferWithAuthorization\u2026");
1184
1069
  const paymentPayload = await this.buildPaymentPayload(requirements, resource, url);
1185
1070
  const paymentB64 = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
@@ -1340,43 +1225,6 @@ var _X402Client = class _X402Client {
1340
1225
  }
1341
1226
  };
1342
1227
  }
1343
- async ensureBalance(requiredAmount) {
1344
- const accountAddr = this.config.accountAddress;
1345
- const morphoAddr = this.config.morphoCreditAddress;
1346
- const provider = this.wallet.provider;
1347
- const usdcAddr = USDC_DOMAINS["eip155:8453"]?.address || USDC_DOMAINS["eip155:1"].address;
1348
- const usdc = new ethers3.Contract(usdcAddr, _X402Client.ERC20_BALANCE_ABI, provider);
1349
- const balance = await usdc.balanceOf(accountAddr);
1350
- const needed = requiredAmount + requiredAmount / 10n;
1351
- if (balance >= needed) return;
1352
- const deficit = needed - balance;
1353
- const minDraw = ethers3.parseUnits("10", 6);
1354
- const drawAmount = deficit > minDraw ? deficit : minDraw;
1355
- console.log(` [auto-draw] USDC balance $${ethers3.formatUnits(balance, 6)} < needed $${ethers3.formatUnits(needed, 6)}`);
1356
- const morpho = new ethers3.Contract(morphoAddr, _X402Client.MORPHO_DRAW_ABI, provider);
1357
- let collateralAddr = null;
1358
- for (const addr of _X402Client.AUTO_DRAW_COLLATERALS) {
1359
- try {
1360
- const pos = await morpho.getPosition(accountAddr, addr);
1361
- if (pos.collateralAmount > 0n) {
1362
- collateralAddr = addr;
1363
- break;
1364
- }
1365
- } catch {
1366
- continue;
1367
- }
1368
- }
1369
- if (!collateralAddr) {
1370
- throw new Error("autoDraw failed: no collateral deposited in Morpho. Deposit collateral first.");
1371
- }
1372
- const account = new ethers3.Contract(accountAddr, _X402Client.AGENT_ACCOUNT_EXEC_ABI, this.wallet);
1373
- const morphoIface = new ethers3.Interface(_X402Client.MORPHO_DRAW_ABI);
1374
- const calldata = morphoIface.encodeFunctionData("drawWithCollateral", [collateralAddr, drawAmount]);
1375
- console.log(` [auto-draw] Borrowing $${ethers3.formatUnits(drawAmount, 6)} from Morpho...`);
1376
- const tx = await account.execute(morphoAddr, 0, calldata);
1377
- await tx.wait();
1378
- console.log(` [auto-draw] \u2713 Borrowed (tx: ${tx.hash.slice(0, 14)}\u2026)`);
1379
- }
1380
1228
  // ──────────── Risk check via our backend ────────────
1381
1229
  async riskCheck(paymentPayload, reqs) {
1382
1230
  try {
@@ -1408,787 +1256,429 @@ var _X402Client = class _X402Client {
1408
1256
  }
1409
1257
  }
1410
1258
  };
1411
- // ──────────── Auto-draw (Flow 9) ────────────
1412
- //
1413
- // When autoDraw is enabled and the AgentAccount has insufficient USDC,
1414
- // automatically borrow from Morpho credit line to cover the payment.
1415
- _X402Client.MORPHO_DRAW_ABI = [
1416
- "function drawWithCollateral(address collateralToken, uint256 amount)",
1417
- "function getPosition(address account, address collateralToken) view returns (tuple(uint256 collateralAmount, uint256 borrowedAmount, uint256 borrowShares, bool isActive))"
1418
- ];
1419
- _X402Client.AGENT_ACCOUNT_EXEC_ABI = [
1420
- "function execute(address target, uint256 value, bytes data) payable returns (bytes)"
1421
- ];
1422
- _X402Client.ERC20_BALANCE_ABI = [
1423
- "function balanceOf(address) view returns (uint256)"
1424
- ];
1425
- _X402Client.AUTO_DRAW_COLLATERALS = [
1426
- "0x4200000000000000000000000000000000000006",
1427
- // WETH
1428
- "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
1429
- // wstETH
1430
- "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22"
1431
- // cbETH
1432
- ];
1433
- var X402Client = _X402Client;
1434
1259
 
1435
- // src/clients/MorphoCreditClient.ts
1436
- import { ethers as ethers4, Contract as Contract3 } from "ethers";
1437
- var BASE_COLLATERALS = {
1438
- WETH: { symbol: "WETH", address: "0x4200000000000000000000000000000000000006", decimals: 18 },
1439
- wstETH: { symbol: "wstETH", address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", decimals: 18 },
1440
- cbETH: { symbol: "cbETH", address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", decimals: 18 }
1441
- };
1442
- var _MorphoCreditClient = class _MorphoCreditClient {
1443
- constructor(config, collaterals) {
1444
- // ── Batch helper ──
1445
- this.morphoIface = new ethers4.Interface(MORPHO_CREDIT_ABI);
1446
- this.erc20Iface = new ethers4.Interface(ERC20_ABI);
1447
- this.config = config;
1448
- const provider = new ethers4.JsonRpcProvider(config.rpcUrl);
1449
- this.signer = new ethers4.Wallet(config.privateKey, provider);
1450
- this.morpho = new Contract3(config.contracts.morphoCredit, MORPHO_CREDIT_ABI, this.signer);
1451
- this.factory = new Contract3(config.contracts.accountFactory, ACCOUNT_FACTORY_ABI, this.signer);
1452
- this.collaterals = collaterals || BASE_COLLATERALS;
1453
- if (config.agentId) this.resolvedAgentId = config.agentId.toString();
1454
- }
1455
- // ── Helpers ──
1456
- resolveToken(tokenSymbol) {
1457
- const token = this.collaterals[tokenSymbol];
1458
- if (!token) {
1459
- throw new Error(`Unsupported collateral token: ${tokenSymbol}. Supported: ${Object.keys(this.collaterals).join(", ")}`);
1260
+ // src/clients/ScoringClient.ts
1261
+ var ScoringClient = class {
1262
+ constructor(config) {
1263
+ this.endpoint = config.endpoint;
1264
+ this.client = axios2.create({
1265
+ baseURL: config.endpoint,
1266
+ headers: { "Content-Type": "application/json" },
1267
+ timeout: 3e4
1268
+ });
1269
+ if (config.x402) {
1270
+ this.x402Client = new X402Client(config.x402);
1460
1271
  }
1461
- return token;
1462
1272
  }
1273
+ // ════════════════════════════════════════════════════════
1274
+ // Score (x402-gated — computes & submits on-chain)
1275
+ // ════════════════════════════════════════════════════════
1463
1276
  /**
1464
- * Get agentId from config or cache. Call register() first if not set.
1277
+ * Request a fresh score computation.
1278
+ *
1279
+ * This is x402-gated: the backend returns 402, the X402Client
1280
+ * signs an EIP-3009 payment, and the backend computes + submits
1281
+ * the score on-chain via AgentReputation.submitScore().
1282
+ *
1283
+ * Returns the ScoreResult with breakdown and txHash.
1465
1284
  */
1466
- async getAgentId() {
1467
- if (this.resolvedAgentId) return this.resolvedAgentId;
1468
- throw new Error("No agentId. Pass agentId in config or call register() first.");
1285
+ async requestScore(agentId) {
1286
+ const id = agentId.toString();
1287
+ if (!this.x402Client) {
1288
+ throw new AgetherError(
1289
+ "x402 config required for paid scoring. Provide x402 in ScoringClientConfig.",
1290
+ "X402_NOT_CONFIGURED"
1291
+ );
1292
+ }
1293
+ const result = await this.x402Client.get(
1294
+ `${this.endpoint}/score/${id}`
1295
+ );
1296
+ if (!result.success || !result.data) {
1297
+ throw new AgetherError(
1298
+ `Scoring request failed: ${result.error || "unknown error"}`,
1299
+ "SCORING_FAILED"
1300
+ );
1301
+ }
1302
+ return result.data;
1469
1303
  }
1304
+ // ════════════════════════════════════════════════════════
1305
+ // Current Score (free — reads on-chain)
1306
+ // ════════════════════════════════════════════════════════
1470
1307
  /**
1471
- * Get the AgentAccount address for the configured agentId.
1308
+ * Get the current on-chain score (free, no payment required).
1472
1309
  */
1473
- async getAccountAddress() {
1474
- if (this.accountAddress) return this.accountAddress;
1475
- const agentId = await this.getAgentId();
1476
- const addr = await this.factory.getAccount(agentId);
1477
- if (addr === ethers4.ZeroAddress) {
1478
- throw new Error("No AgentAccount found. Register first.");
1479
- }
1480
- this.accountAddress = addr;
1481
- return addr;
1310
+ async getCurrentScore(agentId) {
1311
+ const response = await this.client.get(`/score/${agentId.toString()}/current`);
1312
+ return response.data;
1482
1313
  }
1314
+ // ════════════════════════════════════════════════════════
1315
+ // Agent Details
1316
+ // ════════════════════════════════════════════════════════
1483
1317
  /**
1484
- * Get the AgentAccount address for any agentId.
1318
+ * Get detailed agent info from backend.
1485
1319
  */
1486
- async getAccountForAgent(agentId) {
1487
- const addr = await this.factory.getAccount(agentId.toString());
1488
- if (addr === ethers4.ZeroAddress) {
1489
- throw new Error(`No AgentAccount for agent #${agentId}`);
1490
- }
1491
- return addr;
1320
+ async getAgentDetails(agentId) {
1321
+ const response = await this.client.get(`/agents/${agentId.toString()}/details`);
1322
+ return response.data;
1492
1323
  }
1493
- async ensureCreditProvider(accountAddr) {
1494
- const account = new Contract3(accountAddr, AGENT_ACCOUNT_ABI, this.signer);
1495
- try {
1496
- const tx = await account.approveCreditProvider(this.config.contracts.morphoCredit);
1497
- await tx.wait();
1498
- } catch {
1499
- }
1324
+ // ════════════════════════════════════════════════════════
1325
+ // Service Status
1326
+ // ════════════════════════════════════════════════════════
1327
+ /**
1328
+ * Health check.
1329
+ */
1330
+ async getHealth() {
1331
+ const response = await this.client.get("/health");
1332
+ return response.data;
1500
1333
  }
1501
1334
  /**
1502
- * Approve ERC-20 token from EOA → MorphoCredit, then deposit for account.
1503
- * This is 2 EOA txs (approve + depositCollateralFor) — cannot be batched
1504
- * because both are called from EOA, not from AgentAccount.
1335
+ * Detailed status (contracts, signer, chain).
1505
1336
  */
1506
- async approveAndDeposit(accountAddr, tokenInfo, amount) {
1507
- const token = new Contract3(tokenInfo.address, ERC20_ABI, this.signer);
1508
- const balance = await token.balanceOf(this.signer.address);
1509
- if (balance < amount) {
1510
- throw new Error(
1511
- `Insufficient ${tokenInfo.symbol}: have ${ethers4.formatUnits(balance, tokenInfo.decimals)}, need ${ethers4.formatUnits(amount, tokenInfo.decimals)}`
1512
- );
1513
- }
1514
- const approveTx = await token.approve(this.config.contracts.morphoCredit, amount);
1515
- await approveTx.wait();
1516
- const depositTx = await this.morpho.depositCollateralFor(accountAddr, tokenInfo.address, amount);
1517
- await depositTx.wait();
1518
- return depositTx.hash;
1337
+ async getStatus() {
1338
+ const response = await this.client.get("/status");
1339
+ return response.data;
1519
1340
  }
1520
1341
  /**
1521
- * Execute multiple calls via AgentAccount.executeBatch() in a single tx.
1522
- * Each call is { target, value, data }.
1342
+ * Agent count and list.
1523
1343
  */
1524
- async batch(accountAddr, calls) {
1525
- const account = new Contract3(accountAddr, AGENT_ACCOUNT_ABI, this.signer);
1526
- const tx = await account.executeBatch(
1527
- calls.map((c) => c.target),
1528
- calls.map((c) => c.value),
1529
- calls.map((c) => c.data)
1530
- );
1531
- await tx.wait();
1532
- return tx.hash;
1344
+ async getAgentCount() {
1345
+ const response = await this.client.get("/agents/count");
1346
+ return response.data;
1347
+ }
1348
+ };
1349
+
1350
+ // src/clients/AgentIdentityClient.ts
1351
+ import { ethers as ethers4 } from "ethers";
1352
+ var ERC8004_IDENTITY_ABI = [
1353
+ // Registration
1354
+ "function register() returns (uint256)",
1355
+ "function register(string agentURI) returns (uint256)",
1356
+ "function register(string agentURI, tuple(string key, bytes value)[] metadata) returns (uint256)",
1357
+ // Management
1358
+ "function setAgentURI(uint256 agentId, string newURI)",
1359
+ "function setMetadata(uint256 agentId, string metadataKey, bytes metadataValue)",
1360
+ "function getMetadata(uint256 agentId, string metadataKey) view returns (bytes)",
1361
+ // ERC-721 standard
1362
+ "function ownerOf(uint256 tokenId) view returns (address)",
1363
+ "function balanceOf(address owner) view returns (uint256)",
1364
+ "function tokenURI(uint256 tokenId) view returns (string)",
1365
+ "function transferFrom(address from, address to, uint256 tokenId)",
1366
+ "function approve(address to, uint256 tokenId)",
1367
+ "function getApproved(uint256 tokenId) view returns (address)",
1368
+ // Events
1369
+ "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
1370
+ ];
1371
+ var ERC8004_REPUTATION_ABI = [
1372
+ // Feedback
1373
+ "function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash)",
1374
+ "function revokeFeedback(uint256 agentId, uint64 feedbackIndex)",
1375
+ // Queries
1376
+ "function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex) view returns (int128 value, uint8 valueDecimals, string tag1, string tag2, bool isRevoked)",
1377
+ "function getSummary(uint256 agentId, address[] clientAddresses, string tag1, string tag2) view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals)",
1378
+ "function getClients(uint256 agentId) view returns (address[])",
1379
+ "function getLastIndex(uint256 agentId, address clientAddress) view returns (uint64)",
1380
+ // Registry
1381
+ "function getIdentityRegistry() view returns (address)"
1382
+ ];
1383
+ var ERC8004_SEPOLIA = {
1384
+ identityRegistry: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
1385
+ reputationRegistry: "0x8004B663056A597Dffe9eCcC1965A193B7388713"
1386
+ };
1387
+ var AgentIdentityClient = class {
1388
+ constructor(options) {
1389
+ this.config = options.config;
1390
+ this.signer = options.signer;
1391
+ const identityAddr = options.config.contracts?.identityRegistry || ERC8004_SEPOLIA.identityRegistry;
1392
+ const reputationAddr = options.config.contracts?.reputationRegistry || ERC8004_SEPOLIA.reputationRegistry;
1393
+ this.identityRegistry = new ethers4.Contract(identityAddr, ERC8004_IDENTITY_ABI, this.signer);
1394
+ this.reputationRegistry = new ethers4.Contract(reputationAddr, ERC8004_REPUTATION_ABI, this.signer);
1533
1395
  }
1534
- // ══════════════════════════════════════════
1535
- // Register
1536
- // ══════════════════════════════════════════
1396
+ // ============ Identity Functions ============
1537
1397
  /**
1538
- * Register a new ERC-8004 agent identity and create an AgentAccount.
1539
- * If already registered (agentId in config or on-chain), returns existing info.
1540
- *
1541
- * @param name - Agent display name
1398
+ * Register a new agent (minimal - no metadata)
1542
1399
  */
1543
- async register(name) {
1544
- if (!this.config.contracts.agentRegistry) {
1545
- throw new Error("agentRegistry address required for register()");
1546
- }
1547
- const registry = new Contract3(
1548
- this.config.contracts.agentRegistry,
1549
- _MorphoCreditClient.ERC8004_ABI,
1550
- this.signer
1551
- );
1552
- if (this.resolvedAgentId) {
1553
- const agentId2 = BigInt(this.resolvedAgentId);
1554
- const owner = await registry.ownerOf(agentId2);
1555
- if (owner.toLowerCase() !== this.signer.address.toLowerCase()) {
1556
- throw new Error("agentId in config does not belong to this wallet");
1557
- }
1558
- const exists2 = await this.factory.accountExists(agentId2);
1559
- if (!exists2) {
1560
- const tx2 = await this.factory.createAccount(agentId2);
1561
- await tx2.wait();
1562
- }
1563
- const accountAddr2 = await this.factory.getAccount(this.resolvedAgentId);
1564
- return {
1565
- tx: "",
1566
- agentId: this.resolvedAgentId,
1567
- address: this.signer.address,
1568
- agentAccount: accountAddr2,
1569
- alreadyRegistered: true
1570
- };
1571
- }
1572
- const registrationFile = JSON.stringify({
1573
- type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
1574
- name,
1575
- description: "AI agent registered via @agether/sdk",
1576
- active: true,
1577
- registrations: [{
1578
- agentId: 0,
1579
- agentRegistry: `eip155:8453:${this.config.contracts.agentRegistry}`
1580
- }]
1581
- });
1582
- const agentURI = `data:application/json;base64,${Buffer.from(registrationFile).toString("base64")}`;
1583
- const tx = await registry["register(string)"](agentURI);
1400
+ async register() {
1401
+ const tx = await this.identityRegistry["register()"]();
1584
1402
  const receipt = await tx.wait();
1585
- const transferTopic = ethers4.id("Transfer(address,address,uint256)");
1586
- const transferLog = receipt.logs.find((l) => l.topics[0] === transferTopic);
1587
- if (!transferLog?.topics?.[3]) {
1588
- throw new Error("Could not parse agentId from receipt");
1589
- }
1590
- const agentId = BigInt(transferLog.topics[3]);
1591
- this.resolvedAgentId = agentId.toString();
1592
- const exists = await this.factory.accountExists(agentId);
1593
- if (!exists) {
1594
- const accTx = await this.factory.createAccount(agentId);
1595
- await accTx.wait();
1596
- }
1597
- const accountAddr = await this.factory.getAccount(this.resolvedAgentId);
1598
- this.accountAddress = accountAddr;
1599
- return {
1600
- tx: tx.hash,
1601
- agentId: this.resolvedAgentId,
1602
- address: this.signer.address,
1603
- agentAccount: accountAddr,
1604
- alreadyRegistered: false
1605
- };
1403
+ const agentId = this.parseAgentIdFromReceipt(receipt);
1404
+ return { agentId, txHash: receipt.hash };
1606
1405
  }
1607
- // ══════════════════════════════════════════
1608
- // Balances & Status
1609
- // ══════════════════════════════════════════
1610
1406
  /**
1611
- * Get ETH and USDC balances for EOA wallet and AgentAccount.
1407
+ * Register agent with IPFS/HTTP URI to metadata JSON
1408
+ * @param agentURI URI pointing to agent metadata (ipfs:// or https://)
1612
1409
  */
1613
- async getBalances() {
1614
- const provider = this.signer.provider;
1615
- const ethBal = await provider.getBalance(this.signer.address);
1616
- const usdc = new Contract3(this.config.contracts.usdc, ERC20_ABI, provider);
1617
- const usdcBal = await usdc.balanceOf(this.signer.address);
1618
- const result = {
1619
- address: this.signer.address,
1620
- agentId: this.resolvedAgentId || "not registered",
1621
- eth: ethers4.formatEther(ethBal),
1622
- usdc: ethers4.formatUnits(usdcBal, 6)
1623
- };
1410
+ async registerWithURI(agentURI) {
1411
+ const tx = await this.identityRegistry["register(string)"](agentURI);
1412
+ const receipt = await tx.wait();
1413
+ const agentId = this.parseAgentIdFromReceipt(receipt);
1414
+ return { agentId, txHash: receipt.hash };
1415
+ }
1416
+ /**
1417
+ * Check if the signer already owns an ERC-8004 identity token.
1418
+ * Returns true if balanceOf > 0, false otherwise.
1419
+ * Note: Cannot determine the specific agentId without enumeration —
1420
+ * use agether init <pk> --agent-id <id> if you know your agentId.
1421
+ */
1422
+ async hasExistingIdentity() {
1624
1423
  try {
1625
- const accountAddr = await this.getAccountAddress();
1626
- const accEth = await provider.getBalance(accountAddr);
1627
- const accUsdc = await usdc.balanceOf(accountAddr);
1628
- result.agentAccount = {
1629
- address: accountAddr,
1630
- eth: ethers4.formatEther(accEth),
1631
- usdc: ethers4.formatUnits(accUsdc, 6)
1632
- };
1424
+ const address = await this.signer.getAddress();
1425
+ const balance = await this.identityRegistry.balanceOf(address);
1426
+ return balance > 0n;
1633
1427
  } catch {
1428
+ return false;
1634
1429
  }
1635
- return result;
1636
1430
  }
1637
1431
  /**
1638
- * Get full Morpho credit status all positions + total debt.
1432
+ * Register only if no identity exists; otherwise throw.
1433
+ * Prevents accidental double-registration.
1639
1434
  */
1640
- async getStatus() {
1641
- const accountAddr = await this.getAccountAddress();
1642
- const positions = [];
1643
- for (const [symbol, info] of Object.entries(this.collaterals)) {
1644
- const pos = await this.morpho.getPosition(accountAddr, info.address);
1645
- if (pos.isActive || pos.collateralAmount > 0n || pos.borrowedAmount > 0n) {
1646
- positions.push({
1647
- token: symbol,
1648
- collateral: ethers4.formatUnits(pos.collateralAmount, info.decimals),
1649
- debt: `$${ethers4.formatUnits(pos.borrowedAmount, 6)}`,
1650
- active: pos.isActive
1651
- });
1652
- }
1435
+ async registerOrGet() {
1436
+ const hasIdentity = await this.hasExistingIdentity();
1437
+ if (hasIdentity) {
1438
+ throw new Error("Wallet already owns an ERC-8004 identity. Use agether init <pk> --agent-id <id> to set it.");
1653
1439
  }
1654
- const totalDebt = await this.morpho.getTotalDebt(accountAddr);
1655
- return {
1656
- agentAccount: accountAddr,
1657
- totalDebt: `$${ethers4.formatUnits(totalDebt, 6)}`,
1658
- positions
1659
- };
1440
+ const result = await this.register();
1441
+ return { ...result, existing: false };
1442
+ }
1443
+ /**
1444
+ * Register with URI only if no identity exists; otherwise throw.
1445
+ * Prevents accidental double-registration.
1446
+ */
1447
+ async registerOrGetWithURI(agentURI) {
1448
+ const hasIdentity = await this.hasExistingIdentity();
1449
+ if (hasIdentity) {
1450
+ throw new Error("Wallet already owns an ERC-8004 identity. Use agether init <pk> --agent-id <id> to set it.");
1451
+ }
1452
+ const result = await this.registerWithURI(agentURI);
1453
+ return { ...result, existing: false };
1454
+ }
1455
+ /**
1456
+ * Register agent with URI and on-chain metadata
1457
+ */
1458
+ async registerWithMetadata(agentURI, metadata) {
1459
+ const metadataEntries = metadata.map((m) => ({
1460
+ key: m.key,
1461
+ value: ethers4.toUtf8Bytes(m.value)
1462
+ }));
1463
+ const tx = await this.identityRegistry["register(string,tuple(string,bytes)[])"](
1464
+ agentURI,
1465
+ metadataEntries
1466
+ );
1467
+ const receipt = await tx.wait();
1468
+ const agentId = this.parseAgentIdFromReceipt(receipt);
1469
+ return { agentId, txHash: receipt.hash };
1470
+ }
1471
+ /**
1472
+ * Get agent owner address
1473
+ */
1474
+ async getOwner(agentId) {
1475
+ return await this.identityRegistry.ownerOf(agentId);
1660
1476
  }
1661
- // ══════════════════════════════════════════
1662
- // Fund AgentAccount
1663
- // ══════════════════════════════════════════
1664
1477
  /**
1665
- * Transfer USDC from EOA wallet into AgentAccount.
1666
- *
1667
- * @param amount - Human-readable USDC amount (e.g. "50")
1478
+ * Get agent URI (metadata JSON location)
1668
1479
  */
1669
- async fundAccount(amount) {
1670
- const amountWei = ethers4.parseUnits(amount, 6);
1671
- const accountAddr = await this.getAccountAddress();
1672
- const usdc = new Contract3(this.config.contracts.usdc, ERC20_ABI, this.signer);
1673
- const balance = await usdc.balanceOf(this.signer.address);
1674
- if (balance < amountWei) {
1675
- throw new Error(`Insufficient USDC: have $${ethers4.formatUnits(balance, 6)}, need $${amount}`);
1676
- }
1677
- const tx = await usdc.transfer(accountAddr, amountWei);
1678
- await tx.wait();
1679
- return { tx: tx.hash, amount, agentAccount: accountAddr };
1480
+ async getAgentURI(agentId) {
1481
+ return await this.identityRegistry.tokenURI(agentId);
1680
1482
  }
1681
1483
  /**
1682
- * Check whether a borrow would exceed max LTV.
1683
- * Call before borrow() or depositAndBorrow() to get a clear error instead of on-chain revert.
1684
- *
1685
- * @param tokenSymbol - Collateral token
1686
- * @param additionalCollateral - Additional collateral being deposited ("0" if just borrowing)
1687
- * @param borrowAmount - USDC to borrow
1484
+ * Update agent URI
1688
1485
  */
1689
- async checkLtv(tokenSymbol, additionalCollateral, borrowAmount) {
1690
- const tokenInfo = this.resolveToken(tokenSymbol);
1691
- const accountAddr = await this.getAccountAddress();
1692
- const pos = await this.morpho.getPosition(accountAddr, tokenInfo.address);
1693
- const marketInfo = await this.morpho.markets(tokenInfo.address);
1694
- const oracle = new Contract3(marketInfo.params.oracle, _MorphoCreditClient.ORACLE_ABI, this.signer.provider);
1695
- const oraclePrice = await oracle.price();
1696
- const addCollateralWei = ethers4.parseUnits(additionalCollateral, tokenInfo.decimals);
1697
- const borrowWei = ethers4.parseUnits(borrowAmount, 6);
1698
- const totalCollateral = pos.collateralAmount + addCollateralWei;
1699
- const totalDebt = pos.borrowedAmount + borrowWei;
1700
- const collateralValue = totalCollateral * oraclePrice / BigInt("1000000000000000000000000000000000000");
1701
- const collateralUsd = Number(collateralValue) / 1e6;
1702
- const currentDebtUsd = Number(pos.borrowedAmount) / 1e6;
1703
- const maxLtvBps = Number(marketInfo.maxLtvBps);
1704
- const currentLtv = collateralValue > 0n ? Number(pos.borrowedAmount * 10000n / collateralValue) / 100 : 0;
1705
- const newLtv = collateralValue > 0n ? Number(totalDebt * 10000n / collateralValue) / 100 : totalDebt > 0n ? Infinity : 0;
1706
- const maxLtv = maxLtvBps / 100;
1707
- const maxBorrow = collateralUsd * maxLtvBps / 1e4;
1708
- const ok = newLtv <= maxLtv;
1709
- return {
1710
- ok,
1711
- currentLtv,
1712
- newLtv,
1713
- maxLtv,
1714
- collateralUsd,
1715
- currentDebt: currentDebtUsd,
1716
- maxBorrow,
1717
- message: ok ? void 0 : `ExceedsMaxLtv: borrowing $${borrowAmount} would push LTV to ${newLtv.toFixed(1)}% (max ${maxLtv}%). Collateral: $${collateralUsd.toFixed(2)}. Current debt: $${currentDebtUsd.toFixed(3)}. Max additional borrow: $${(maxBorrow - currentDebtUsd).toFixed(2)}. Deposit more collateral or borrow less.`
1718
- };
1486
+ async setAgentURI(agentId, newURI) {
1487
+ const tx = await this.identityRegistry.setAgentURI(agentId, newURI);
1488
+ const receipt = await tx.wait();
1489
+ return receipt.hash;
1719
1490
  }
1720
- // ══════════════════════════════════════════
1721
- // Flow 3: Deposit collateral only
1722
- // ══════════════════════════════════════════
1723
1491
  /**
1724
- * Deposit collateral from EOA into Morpho for own AgentAccount.
1725
- * Does NOT borrow — use `borrow()` or `depositAndBorrow()` for that.
1726
- *
1727
- * @param tokenSymbol - Collateral token (WETH, wstETH, cbETH)
1728
- * @param amount - Human-readable amount (e.g. "0.05")
1492
+ * Set on-chain metadata (key-value)
1729
1493
  */
1730
- async deposit(tokenSymbol, amount) {
1731
- const tokenInfo = this.resolveToken(tokenSymbol);
1732
- const amountWei = ethers4.parseUnits(amount, tokenInfo.decimals);
1733
- const accountAddr = await this.getAccountAddress();
1734
- const txHash = await this.approveAndDeposit(accountAddr, tokenInfo, amountWei);
1735
- await this.ensureCreditProvider(accountAddr);
1736
- const pos = await this.morpho.getPosition(accountAddr, tokenInfo.address);
1737
- return {
1738
- tx: txHash,
1739
- amount: amountWei,
1740
- token: tokenSymbol,
1741
- agentAccount: accountAddr,
1742
- totalCollateral: pos.collateralAmount
1743
- };
1494
+ async setMetadata(agentId, key, value) {
1495
+ const tx = await this.identityRegistry.setMetadata(
1496
+ agentId,
1497
+ key,
1498
+ ethers4.toUtf8Bytes(value)
1499
+ );
1500
+ const receipt = await tx.wait();
1501
+ return receipt.hash;
1744
1502
  }
1745
- // ══════════════════════════════════════════
1746
- // Flow 2: Deposit + borrow in one call
1747
- // ══════════════════════════════════════════
1748
1503
  /**
1749
- * Deposit collateral AND borrow USDC in a single SDK call.
1750
- * USDC lands in the AgentAccount.
1751
- *
1752
- * @param tokenSymbol - Collateral token (WETH, wstETH, cbETH)
1753
- * @param collateralAmount - Human-readable collateral amount (e.g. "0.05")
1754
- * @param borrowAmount - Human-readable USDC amount to borrow (e.g. "50")
1504
+ * Get on-chain metadata
1755
1505
  */
1756
- async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount) {
1757
- const tokenInfo = this.resolveToken(tokenSymbol);
1758
- const collateralWei = ethers4.parseUnits(collateralAmount, tokenInfo.decimals);
1759
- const borrowWei = ethers4.parseUnits(borrowAmount, 6);
1760
- const accountAddr = await this.getAccountAddress();
1761
- const ltvCheck = await this.checkLtv(tokenSymbol, collateralAmount, borrowAmount);
1762
- if (!ltvCheck.ok) throw new Error(ltvCheck.message);
1763
- const depositTxHash = await this.approveAndDeposit(accountAddr, tokenInfo, collateralWei);
1764
- await this.ensureCreditProvider(accountAddr);
1765
- const account = new Contract3(accountAddr, AGENT_ACCOUNT_ABI, this.signer);
1766
- const calldata = this.morphoIface.encodeFunctionData("drawWithCollateral", [tokenInfo.address, borrowWei]);
1767
- const borrowTx = await account.execute(this.config.contracts.morphoCredit, 0, calldata);
1768
- await borrowTx.wait();
1769
- const totalDebt = await this.morpho.getTotalDebt(accountAddr);
1770
- return {
1771
- depositTx: depositTxHash,
1772
- borrowTx: borrowTx.hash,
1773
- collateral: { amount: collateralWei, token: tokenSymbol },
1774
- borrowed: borrowWei,
1775
- agentAccount: accountAddr,
1776
- totalDebt
1777
- };
1506
+ async getMetadata(agentId, key) {
1507
+ const value = await this.identityRegistry.getMetadata(agentId, key);
1508
+ return ethers4.toUtf8String(value);
1778
1509
  }
1779
- // ══════════════════════════════════════════
1780
- // Flow 8: Borrow against existing collateral
1781
- // ══════════════════════════════════════════
1782
1510
  /**
1783
- * Borrow USDC against already-deposited collateral.
1784
- * Auto-detects which collateral token has a position.
1785
- * USDC lands in AgentAccount — ready for x402 payments.
1786
- *
1787
- * @param amount - Human-readable USDC amount (e.g. "100")
1511
+ * Transfer agent to new owner
1788
1512
  */
1789
- async borrow(amount) {
1790
- const borrowWei = ethers4.parseUnits(amount, 6);
1791
- const accountAddr = await this.getAccountAddress();
1792
- let activeToken = null;
1793
- for (const info of Object.values(this.collaterals)) {
1794
- const pos = await this.morpho.getPosition(accountAddr, info.address);
1795
- if (pos.collateralAmount > 0n) {
1796
- activeToken = info;
1797
- break;
1798
- }
1799
- }
1800
- if (!activeToken) {
1801
- throw new Error("No collateral deposited. Use deposit() or depositAndBorrow() first.");
1802
- }
1803
- const ltvCheck = await this.checkLtv(activeToken.symbol, "0", amount);
1804
- if (!ltvCheck.ok) throw new Error(ltvCheck.message);
1805
- await this.ensureCreditProvider(accountAddr);
1806
- const account = new Contract3(accountAddr, AGENT_ACCOUNT_ABI, this.signer);
1807
- const calldata = this.morphoIface.encodeFunctionData("drawWithCollateral", [activeToken.address, borrowWei]);
1808
- const tx = await account.execute(this.config.contracts.morphoCredit, 0, calldata);
1809
- await tx.wait();
1810
- const totalDebt = await this.morpho.getTotalDebt(accountAddr);
1811
- return {
1812
- tx: tx.hash,
1813
- amount: borrowWei,
1814
- agentAccount: accountAddr,
1815
- totalDebt,
1816
- collateralToken: activeToken.symbol
1817
- };
1513
+ async transfer(agentId, to) {
1514
+ const from = await this.signer.getAddress();
1515
+ const tx = await this.identityRegistry.transferFrom(from, to, agentId);
1516
+ const receipt = await tx.wait();
1517
+ return receipt.hash;
1818
1518
  }
1819
- // ══════════════════════════════════════════
1820
- // Flows 4-7: Sponsor (deposit for another agent)
1821
- // ══════════════════════════════════════════
1822
1519
  /**
1823
- * Deposit collateral for another agent. Caller pays from their wallet.
1824
- * Optionally borrow USDC for the agent (only works if caller owns the AgentAccount).
1825
- *
1826
- * Supports both agentId lookup and direct address.
1827
- *
1828
- * @param target - `{ agentId: "17676" }` or `{ address: "0x..." }`
1829
- * @param tokenSymbol - Collateral token
1830
- * @param amount - Human-readable collateral amount
1831
- * @param borrowAmount - Optional: USDC to borrow (only if caller is owner)
1520
+ * Fetch and parse agent metadata from URI
1832
1521
  */
1833
- async sponsor(target, tokenSymbol, amount, borrowAmount) {
1834
- const tokenInfo = this.resolveToken(tokenSymbol);
1835
- const collateralWei = ethers4.parseUnits(amount, tokenInfo.decimals);
1836
- let accountAddr;
1837
- let targetAgentId;
1838
- if ("agentId" in target) {
1839
- targetAgentId = target.agentId.toString();
1840
- accountAddr = await this.getAccountForAgent(target.agentId);
1841
- } else {
1842
- accountAddr = target.address;
1843
- }
1844
- const depositTxHash = await this.approveAndDeposit(accountAddr, tokenInfo, collateralWei);
1845
- const result = {
1846
- depositTx: depositTxHash,
1847
- targetAccount: accountAddr,
1848
- targetAgentId,
1849
- collateral: { amount: collateralWei, token: tokenSymbol },
1850
- totalCollateral: 0n,
1851
- totalDebt: 0n
1852
- };
1853
- if (borrowAmount) {
1854
- const borrowWei = ethers4.parseUnits(borrowAmount, 6);
1855
- try {
1856
- await this.ensureCreditProvider(accountAddr);
1857
- const account = new Contract3(accountAddr, AGENT_ACCOUNT_ABI, this.signer);
1858
- const calldata = this.morphoIface.encodeFunctionData("drawWithCollateral", [tokenInfo.address, borrowWei]);
1859
- const tx = await account.execute(this.config.contracts.morphoCredit, 0, calldata);
1860
- await tx.wait();
1861
- result.borrowTx = tx.hash;
1862
- result.borrowed = borrowWei;
1863
- } catch (e) {
1864
- throw new Error(`Borrow failed (caller may not own this AgentAccount): ${e.message}`);
1522
+ async fetchAgentMetadata(agentId) {
1523
+ try {
1524
+ const uri = await this.getAgentURI(agentId);
1525
+ let fetchUrl = uri;
1526
+ if (uri.startsWith("ipfs://")) {
1527
+ const cid = uri.replace("ipfs://", "");
1528
+ fetchUrl = `https://ipfs.io/ipfs/${cid}`;
1865
1529
  }
1530
+ const response = await fetch(fetchUrl);
1531
+ if (!response.ok) return null;
1532
+ return await response.json();
1533
+ } catch {
1534
+ return null;
1866
1535
  }
1867
- const pos = await this.morpho.getPosition(accountAddr, tokenInfo.address);
1868
- result.totalCollateral = pos.collateralAmount;
1869
- result.totalDebt = pos.borrowedAmount;
1870
- return result;
1871
1536
  }
1872
- // ══════════════════════════════════════════
1873
- // Repay & Withdraw
1874
- // ══════════════════════════════════════════
1537
+ // ============ Reputation Functions ============
1875
1538
  /**
1876
- * Repay borrowed USDC from AgentAccount back to Morpho.
1877
- *
1878
- * @param amount - Human-readable USDC amount (e.g. "50")
1539
+ * Give feedback to an agent
1879
1540
  */
1880
- async repay(amount) {
1881
- const amountWei = ethers4.parseUnits(amount, 6);
1882
- const accountAddr = await this.getAccountAddress();
1883
- let collateralAddr = null;
1884
- for (const info of Object.values(this.collaterals)) {
1885
- const pos = await this.morpho.getPosition(accountAddr, info.address);
1886
- if (pos.borrowedAmount > 0n) {
1887
- collateralAddr = info.address;
1888
- break;
1889
- }
1890
- }
1891
- if (!collateralAddr) throw new Error("No Morpho debt to repay.");
1892
- const usdc = new Contract3(this.config.contracts.usdc, ERC20_ABI, this.signer.provider);
1893
- const balance = await usdc.balanceOf(accountAddr);
1894
- if (balance < amountWei) {
1895
- throw new Error(`Insufficient USDC in AgentAccount: $${ethers4.formatUnits(balance, 6)}`);
1896
- }
1897
- const txHash = await this.batch(accountAddr, [
1898
- {
1899
- target: this.config.contracts.usdc,
1900
- value: 0n,
1901
- data: this.erc20Iface.encodeFunctionData("approve", [this.config.contracts.morphoCredit, amountWei])
1902
- },
1903
- {
1904
- target: this.config.contracts.morphoCredit,
1905
- value: 0n,
1906
- data: this.morphoIface.encodeFunctionData("repayWithCollateral", [collateralAddr, amountWei])
1907
- }
1908
- ]);
1909
- const totalDebt = await this.morpho.getTotalDebt(accountAddr);
1910
- return { tx: txHash, amount: amountWei, remainingDebt: totalDebt };
1541
+ async giveFeedback(input) {
1542
+ const feedbackHash = ethers4.keccak256(
1543
+ ethers4.AbiCoder.defaultAbiCoder().encode(
1544
+ ["uint256", "int128", "uint256"],
1545
+ [input.agentId, input.value, Date.now()]
1546
+ )
1547
+ );
1548
+ const tx = await this.reputationRegistry.giveFeedback(
1549
+ input.agentId,
1550
+ input.value,
1551
+ input.decimals || 0,
1552
+ input.tag1 || "",
1553
+ input.tag2 || "",
1554
+ input.endpoint || "",
1555
+ input.feedbackURI || "",
1556
+ feedbackHash
1557
+ );
1558
+ const receipt = await tx.wait();
1559
+ return receipt.hash;
1911
1560
  }
1912
1561
  /**
1913
- * Withdraw collateral from Morpho back to EOA.
1914
- *
1915
- * @param tokenSymbol - Collateral token
1916
- * @param amount - Human-readable amount or "all"
1562
+ * Give positive feedback (shorthand)
1917
1563
  */
1918
- async withdraw(tokenSymbol, amount) {
1919
- const tokenInfo = this.resolveToken(tokenSymbol);
1920
- const accountAddr = await this.getAccountAddress();
1921
- const pos = await this.morpho.getPosition(accountAddr, tokenInfo.address);
1922
- if (pos.collateralAmount === 0n) {
1923
- throw new Error(`No ${tokenSymbol} collateral deposited.`);
1924
- }
1925
- const withdrawAmount = amount.toLowerCase() === "all" ? pos.collateralAmount : ethers4.parseUnits(amount, tokenInfo.decimals);
1926
- if (withdrawAmount > pos.collateralAmount) {
1927
- throw new Error(
1928
- `Cannot withdraw more than deposited: max ${ethers4.formatUnits(pos.collateralAmount, tokenInfo.decimals)} ${tokenSymbol}`
1929
- );
1930
- }
1931
- const txHash = await this.batch(accountAddr, [
1932
- {
1933
- target: this.config.contracts.morphoCredit,
1934
- value: 0n,
1935
- data: this.morphoIface.encodeFunctionData("withdrawCollateral", [tokenInfo.address, withdrawAmount])
1936
- },
1937
- {
1938
- target: tokenInfo.address,
1939
- value: 0n,
1940
- data: this.erc20Iface.encodeFunctionData("transfer", [this.signer.address, withdrawAmount])
1941
- }
1942
- ]);
1943
- const newPos = await this.morpho.getPosition(accountAddr, tokenInfo.address);
1944
- return {
1945
- tx: txHash,
1946
- amount: withdrawAmount,
1947
- token: tokenSymbol,
1948
- destination: this.signer.address,
1949
- remainingCollateral: newPos.collateralAmount
1950
- };
1564
+ async givePosisitiveFeedback(agentId, value = 100, tags = {}) {
1565
+ return this.giveFeedback({
1566
+ agentId,
1567
+ value: Math.abs(value),
1568
+ ...tags
1569
+ });
1951
1570
  }
1952
- // ══════════════════════════════════════════
1953
- // View methods
1954
- // ══════════════════════════════════════════
1955
1571
  /**
1956
- * Get position for a specific collateral token.
1572
+ * Give negative feedback (e.g., for defaults)
1957
1573
  */
1958
- async getPosition(tokenSymbol) {
1959
- const tokenInfo = this.resolveToken(tokenSymbol);
1960
- const accountAddr = await this.getAccountAddress();
1961
- const pos = await this.morpho.getPosition(accountAddr, tokenInfo.address);
1962
- return {
1963
- token: tokenSymbol,
1964
- collateralAmount: pos.collateralAmount,
1965
- borrowedAmount: pos.borrowedAmount,
1966
- borrowShares: pos.borrowShares,
1967
- isActive: pos.isActive
1968
- };
1574
+ async giveNegativeFeedback(agentId, value = -100, tags = {}) {
1575
+ return this.giveFeedback({
1576
+ agentId,
1577
+ value: -Math.abs(value),
1578
+ ...tags
1579
+ });
1969
1580
  }
1970
1581
  /**
1971
- * Get all active positions across all collateral tokens.
1582
+ * Record credit default in reputation system
1972
1583
  */
1973
- async getAllPositions() {
1974
- const accountAddr = await this.getAccountAddress();
1975
- const positions = [];
1976
- for (const [symbol, info] of Object.entries(this.collaterals)) {
1977
- const pos = await this.morpho.getPosition(accountAddr, info.address);
1978
- if (pos.isActive || pos.collateralAmount > 0n || pos.borrowedAmount > 0n) {
1979
- positions.push({
1980
- token: symbol,
1981
- collateralAmount: pos.collateralAmount,
1982
- borrowedAmount: pos.borrowedAmount,
1983
- borrowShares: pos.borrowShares,
1984
- isActive: pos.isActive
1985
- });
1986
- }
1987
- }
1988
- return positions;
1584
+ async recordCreditDefault(agentId, creditLineId) {
1585
+ return this.giveFeedback({
1586
+ agentId,
1587
+ value: -100,
1588
+ tag1: "credit",
1589
+ tag2: "default",
1590
+ feedbackURI: `agether://default/${creditLineId}`
1591
+ });
1989
1592
  }
1990
1593
  /**
1991
- * Get total debt across all positions.
1594
+ * Get reputation summary for an agent
1992
1595
  */
1993
- async getTotalDebt() {
1994
- const accountAddr = await this.getAccountAddress();
1995
- return this.morpho.getTotalDebt(accountAddr);
1596
+ async getReputation(agentId, tag1 = "", tag2 = "") {
1597
+ const clients = await this.reputationRegistry.getClients(agentId);
1598
+ if (clients.length === 0) {
1599
+ return {
1600
+ count: 0,
1601
+ totalValue: 0,
1602
+ averageValue: 0,
1603
+ clients: []
1604
+ };
1605
+ }
1606
+ const [count, summaryValue, decimals] = await this.reputationRegistry.getSummary(
1607
+ agentId,
1608
+ clients,
1609
+ tag1,
1610
+ tag2
1611
+ );
1612
+ const divisor = 10 ** Number(decimals);
1613
+ const total = Number(summaryValue) / divisor;
1614
+ return {
1615
+ count: Number(count),
1616
+ totalValue: total,
1617
+ averageValue: Number(count) > 0 ? total / Number(count) : 0,
1618
+ clients: clients.map((c) => c)
1619
+ };
1996
1620
  }
1997
1621
  /**
1998
- * Get USDC balance in the AgentAccount.
1622
+ * Check if agent has negative credit reputation
1999
1623
  */
2000
- async getAccountUSDC() {
2001
- const accountAddr = await this.getAccountAddress();
2002
- const usdc = new Contract3(this.config.contracts.usdc, ERC20_ABI, this.signer.provider);
2003
- return usdc.balanceOf(accountAddr);
2004
- }
2005
- /** Get the wallet (signer) address */
2006
- getAddress() {
2007
- return this.signer.address;
2008
- }
2009
- };
2010
- _MorphoCreditClient.ERC8004_ABI = [
2011
- "function register(string agentURI) returns (uint256)",
2012
- "function ownerOf(uint256 tokenId) view returns (address)",
2013
- "function balanceOf(address owner) view returns (uint256)"
2014
- ];
2015
- // ══════════════════════════════════════════
2016
- // LTV Pre-check
2017
- // ══════════════════════════════════════════
2018
- _MorphoCreditClient.ORACLE_ABI = ["function price() view returns (uint256)"];
2019
- var MorphoCreditClient = _MorphoCreditClient;
2020
-
2021
- // src/clients/WalletClient.ts
2022
- import { ethers as ethers5, Contract as Contract4, Wallet } from "ethers";
2023
- var WalletClient = class {
2024
- constructor(config) {
2025
- this.privateKey = null;
2026
- this.factoryAddress = config.factoryAddress;
2027
- this.usdcAddress = config.usdcAddress || ethers5.ZeroAddress;
2028
- this.chainId = config.chainId;
2029
- this.rpcUrl = config.rpcUrl;
2030
- this.provider = new ethers5.JsonRpcProvider(config.rpcUrl);
2031
- this.privateKey = config.privateKey || null;
1624
+ async hasNegativeCreditReputation(agentId) {
1625
+ const rep = await this.getReputation(agentId, "credit", "");
1626
+ return rep.count > 0 && rep.averageValue < 0;
2032
1627
  }
1628
+ // ============ Agether Integration ============
2033
1629
  /**
2034
- * Create a fresh signer to avoid nonce caching issues
1630
+ * Verify agent is eligible for Agether credit
1631
+ * Checks:
1632
+ * 1. Agent exists in ERC-8004 registry
1633
+ * 2. Agent has no negative credit reputation
2035
1634
  */
2036
- getFreshSigner() {
2037
- if (!this.privateKey) {
2038
- throw new Error("Signer not configured - provide privateKey");
2039
- }
2040
- const freshProvider = new ethers5.JsonRpcProvider(this.rpcUrl);
2041
- return new Wallet(this.privateKey, freshProvider);
2042
- }
2043
- getFactoryContract() {
2044
- if (this.privateKey) {
2045
- return new Contract4(this.factoryAddress, ACCOUNT_FACTORY_ABI, this.getFreshSigner());
2046
- }
2047
- return new Contract4(this.factoryAddress, ACCOUNT_FACTORY_ABI, this.provider);
2048
- }
2049
- getAccountContract(accountAddress) {
2050
- if (this.privateKey) {
2051
- return new Contract4(accountAddress, AGENT_ACCOUNT_ABI, this.getFreshSigner());
2052
- }
2053
- return new Contract4(accountAddress, AGENT_ACCOUNT_ABI, this.provider);
2054
- }
2055
- getChainId() {
2056
- return this.chainId;
2057
- }
2058
- // ============ Account Factory ============
2059
- async accountExists(agentId) {
2060
- const factory = this.getFactoryContract();
2061
- return factory.accountExists(agentId);
2062
- }
2063
- async getAccount(agentId) {
2064
- const factory = this.getFactoryContract();
2065
- const address = await factory.getAccount(agentId);
2066
- if (address === ethers5.ZeroAddress) {
2067
- return null;
2068
- }
2069
- return address;
2070
- }
2071
- async predictAddress(agentId) {
2072
- const factory = this.getFactoryContract();
2073
- return factory.predictAddress(agentId);
2074
- }
2075
- async createAccount(agentId) {
2076
- if (!this.privateKey) {
2077
- throw new Error("Signer not configured - provide privateKey");
2078
- }
2079
- const factory = this.getFactoryContract();
2080
- const tx = await factory.createAccount(agentId);
2081
- await tx.wait();
2082
- const account = await this.getAccount(agentId);
2083
- if (!account) {
2084
- throw new Error("Account creation failed");
1635
+ async verifyForCredit(agentId) {
1636
+ let owner;
1637
+ try {
1638
+ owner = await this.getOwner(agentId);
1639
+ } catch {
1640
+ return { eligible: false, reason: "Agent not found in ERC-8004 registry" };
2085
1641
  }
2086
- return account;
2087
- }
2088
- async totalAccounts() {
2089
- const factory = this.getFactoryContract();
2090
- return factory.totalAccounts();
2091
- }
2092
- // ============ Account Info ============
2093
- async getWalletInfo(accountAddress) {
2094
- const account = this.getAccountContract(accountAddress);
2095
- const [agentId, owner, ethBalance] = await Promise.all([
2096
- account.agentId(),
2097
- account.owner(),
2098
- account.ethBalance()
2099
- ]);
2100
- let usdcBalance = 0n;
2101
- if (this.usdcAddress !== ethers5.ZeroAddress) {
2102
- usdcBalance = await account.balanceOf(this.usdcAddress);
1642
+ const reputation = await this.getReputation(agentId, "credit", "");
1643
+ if (reputation.count > 0 && reputation.averageValue < 0) {
1644
+ return {
1645
+ eligible: false,
1646
+ reason: "Agent has negative credit reputation",
1647
+ owner,
1648
+ reputation
1649
+ };
2103
1650
  }
2104
1651
  return {
2105
- address: accountAddress,
2106
- agentId: BigInt(agentId),
1652
+ eligible: true,
2107
1653
  owner,
2108
- ethBalance: BigInt(ethBalance),
2109
- usdcBalance: BigInt(usdcBalance)
2110
- };
2111
- }
2112
- async getProviderStatus(accountAddress, creditProvider) {
2113
- const provider = new Contract4(
2114
- creditProvider,
2115
- CREDIT_PROVIDER_ABI,
2116
- this.provider
2117
- );
2118
- const [isEligible, totalDebt, maxDrawable] = await Promise.all([
2119
- provider.isEligible(accountAddress),
2120
- provider.getTotalDebt(accountAddress),
2121
- provider.maxDrawable(accountAddress)
2122
- ]);
2123
- return {
2124
- provider: creditProvider,
2125
- isEligible,
2126
- totalDebt: BigInt(totalDebt),
2127
- maxDrawable: BigInt(maxDrawable)
1654
+ reputation
2128
1655
  };
2129
1656
  }
2130
- // ============ Fund / Withdraw ============
2131
- async fundAccount(accountAddress, tokenAddress, amount) {
2132
- if (!this.privateKey) {
2133
- throw new Error("Signer not configured");
2134
- }
2135
- const signer = this.getFreshSigner();
2136
- const token = new Contract4(tokenAddress, ERC20_ABI, signer);
2137
- const approveTx = await token.approve(accountAddress, amount);
2138
- await approveTx.wait();
2139
- const signer2 = this.getFreshSigner();
2140
- const account = new Contract4(accountAddress, AGENT_ACCOUNT_ABI, signer2);
2141
- const tx = await account.fund(tokenAddress, amount);
2142
- const receipt = await tx.wait();
2143
- return receipt.hash;
2144
- }
2145
- async withdraw(accountAddress, tokenAddress, amount, to) {
2146
- if (!this.privateKey) {
2147
- throw new Error("Signer not configured");
2148
- }
2149
- const account = this.getAccountContract(accountAddress);
2150
- const recipient = to || await this.getFreshSigner().getAddress();
2151
- const tx = await account.withdraw(tokenAddress, amount, recipient);
2152
- const receipt = await tx.wait();
2153
- return receipt.hash;
2154
- }
2155
- // ============ Credit Operations ============
2156
- async drawCredit(accountAddress, creditProvider, amount) {
2157
- if (!this.privateKey) {
2158
- throw new Error("Signer not configured");
2159
- }
2160
- const account = this.getAccountContract(accountAddress);
2161
- const tx = await account.drawCredit(creditProvider, amount);
2162
- const receipt = await tx.wait();
2163
- return receipt.hash;
2164
- }
2165
- async repayCredit(accountAddress, creditProvider, amount) {
2166
- if (!this.privateKey) {
2167
- throw new Error("Signer not configured");
2168
- }
2169
- const account = this.getAccountContract(accountAddress);
2170
- const tx = await account.repayCredit(creditProvider, amount);
2171
- const receipt = await tx.wait();
2172
- return receipt.hash;
2173
- }
2174
- // ============ Generic Execute ============
2175
- async execute(accountAddress, target, value, data) {
2176
- if (!this.privateKey) {
2177
- throw new Error("Signer not configured");
1657
+ // ============ Helpers ============
1658
+ parseAgentIdFromReceipt(receipt) {
1659
+ for (const log of receipt.logs) {
1660
+ try {
1661
+ const parsed = this.identityRegistry.interface.parseLog({
1662
+ topics: log.topics,
1663
+ data: log.data
1664
+ });
1665
+ if (parsed?.name === "Transfer") {
1666
+ return parsed.args[2];
1667
+ }
1668
+ } catch {
1669
+ continue;
1670
+ }
2178
1671
  }
2179
- const account = this.getAccountContract(accountAddress);
2180
- const tx = await account.execute(target, value, data);
2181
- const receipt = await tx.wait();
2182
- return receipt.hash;
1672
+ return 0n;
2183
1673
  }
2184
- // ============ Utility ============
2185
- generatePaymentMessageHash(accountAddress, recipient, amount, nonce, deadline, chainId) {
2186
- return ethers5.keccak256(
2187
- ethers5.AbiCoder.defaultAbiCoder().encode(
2188
- ["address", "uint256", "uint256", "uint256", "uint256", "address"],
2189
- [recipient, amount, nonce, deadline, chainId, accountAddress]
2190
- )
2191
- );
1674
+ /**
1675
+ * Get contract addresses
1676
+ */
1677
+ getContractAddresses() {
1678
+ return {
1679
+ identity: this.identityRegistry.target,
1680
+ reputation: this.reputationRegistry.target
1681
+ };
2192
1682
  }
2193
1683
  };
2194
1684
 
@@ -2239,24 +1729,18 @@ export {
2239
1729
  AGENT_ACCOUNT_ABI,
2240
1730
  AGENT_REPUTATION_ABI,
2241
1731
  AgentIdentityClient,
1732
+ AgentNotApprovedError,
2242
1733
  AgetherClient,
2243
1734
  AgetherError,
2244
- CREDIT_PROVIDER_ABI,
2245
1735
  ChainId,
2246
- CreditNotActiveError,
2247
- CreditStatus,
2248
1736
  ERC20_ABI,
2249
1737
  IDENTITY_REGISTRY_ABI,
2250
- InsufficientCreditError,
2251
- LP_VAULT_ABI,
2252
- MORPHO_CREDIT_ABI,
2253
- MorphoCreditClient,
2254
- REPUTATION_CREDIT_ABI,
1738
+ InsufficientBalanceError,
1739
+ MORPHO_BLUE_ABI,
1740
+ MorphoClient,
2255
1741
  ScoringClient,
2256
1742
  ScoringRejectedError,
2257
1743
  VALIDATION_REGISTRY_ABI,
2258
- VaultClient,
2259
- WalletClient,
2260
1744
  X402Client,
2261
1745
  bpsToRate,
2262
1746
  createConfig,
@@ -2268,7 +1752,6 @@ export {
2268
1752
  formatUSD,
2269
1753
  formatUnits,
2270
1754
  getDefaultConfig,
2271
- getUSDCAddress,
2272
1755
  parseUnits,
2273
1756
  rateToBps
2274
1757
  };