@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/cli.js +1135 -1799
- package/dist/index.d.mts +435 -840
- package/dist/index.d.ts +435 -840
- package/dist/index.js +1072 -1596
- package/dist/index.mjs +1064 -1581
- package/package.json +1 -1
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
|
|
31
|
-
constructor(available,
|
|
21
|
+
var InsufficientBalanceError = class extends AgetherError {
|
|
22
|
+
constructor(available, required) {
|
|
32
23
|
super(
|
|
33
|
-
`Insufficient
|
|
34
|
-
"
|
|
35
|
-
{ available: available.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
|
|
49
|
-
constructor(
|
|
39
|
+
var AgentNotApprovedError = class extends AgetherError {
|
|
40
|
+
constructor(agentId) {
|
|
50
41
|
super(
|
|
51
|
-
`
|
|
52
|
-
"
|
|
53
|
-
{
|
|
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
|
|
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
|
|
153
|
-
"function
|
|
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
|
|
156
|
-
"function
|
|
157
|
-
"function
|
|
158
|
-
"
|
|
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
|
|
167
|
-
|
|
168
|
-
"function
|
|
169
|
-
"function
|
|
170
|
-
|
|
171
|
-
"function
|
|
172
|
-
"function
|
|
173
|
-
|
|
174
|
-
"function
|
|
175
|
-
"function
|
|
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
|
-
|
|
135
|
+
morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"
|
|
197
136
|
},
|
|
198
137
|
[8453 /* Base */]: {
|
|
199
|
-
accountFactory: "
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
validationRegistry: "0x8842f2383A86134Dd80c3Ecf6Bbae2e38396A5ec",
|
|
138
|
+
accountFactory: "0x7D5D56416bAEA06a9DCBe3092eF335724C6320a0",
|
|
139
|
+
validationRegistry: "0xd196C32D2149270F56E209ba7aEE67CE9ceD2001",
|
|
140
|
+
agentReputation: "0x65c9cA1211809D3CF3A2707558198eb2b2bE623c",
|
|
203
141
|
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
204
142
|
identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
300
|
-
options.config.contracts.
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
const
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
|
|
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
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
*/
|
|
454
|
-
async repayFull() {
|
|
455
|
-
this._requireUndercollateralizedEnabled();
|
|
352
|
+
// Reputation
|
|
353
|
+
async getCreditScore() {
|
|
354
|
+
return this.agentReputation.getCreditScore(this.agentId);
|
|
456
355
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
//
|
|
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/
|
|
382
|
+
// src/clients/MorphoClient.ts
|
|
383
|
+
import { ethers as ethers2, Contract as Contract2 } from "ethers";
|
|
490
384
|
import axios from "axios";
|
|
491
|
-
var
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
*
|
|
504
|
-
*
|
|
431
|
+
* Register: create ERC-8004 identity + AgentAccount in one flow.
|
|
432
|
+
* If already registered, returns existing state.
|
|
505
433
|
*/
|
|
506
|
-
async
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
|
|
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
|
-
*
|
|
609
|
-
*
|
|
526
|
+
* Fetch USDC borrow markets on Base from Morpho API.
|
|
527
|
+
* Caches results for 5 minutes.
|
|
610
528
|
*/
|
|
611
|
-
async
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
617
|
-
*
|
|
589
|
+
* Get MarketParams for a collateral token.
|
|
590
|
+
* Tries cache → API → on-chain idToMarketParams.
|
|
618
591
|
*/
|
|
619
|
-
async
|
|
620
|
-
const
|
|
621
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
//
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
async
|
|
644
|
-
const
|
|
645
|
-
const
|
|
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
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
gasUsed: receipt.gasUsed
|
|
624
|
+
supplyShares: pos.supplyShares,
|
|
625
|
+
borrowShares: pos.borrowShares,
|
|
626
|
+
collateral: pos.collateral
|
|
664
627
|
};
|
|
665
628
|
}
|
|
666
629
|
/**
|
|
667
|
-
*
|
|
630
|
+
* Full status: positions across all discovered markets.
|
|
668
631
|
*/
|
|
669
|
-
async
|
|
670
|
-
const
|
|
671
|
-
const
|
|
672
|
-
const
|
|
673
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
*
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
706
|
+
tx: receipt.hash,
|
|
707
|
+
collateralToken: tokenSymbol,
|
|
708
|
+
amount,
|
|
709
|
+
agentAccount: acctAddr
|
|
694
710
|
};
|
|
695
711
|
}
|
|
696
|
-
// ============ View Functions ============
|
|
697
712
|
/**
|
|
698
|
-
*
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
|
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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
sharePrice
|
|
744
|
+
tx: receipt.hash,
|
|
745
|
+
amount: usdcAmount,
|
|
746
|
+
collateralToken: usedToken,
|
|
747
|
+
agentAccount: acctAddr
|
|
715
748
|
};
|
|
716
749
|
}
|
|
717
750
|
/**
|
|
718
|
-
*
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
727
|
-
|
|
728
|
-
|
|
789
|
+
tx: receipt.hash,
|
|
790
|
+
collateralToken: tokenSymbol,
|
|
791
|
+
collateralAmount,
|
|
792
|
+
borrowAmount: borrowUsdcAmount,
|
|
793
|
+
agentAccount: acctAddr
|
|
729
794
|
};
|
|
730
795
|
}
|
|
731
796
|
/**
|
|
732
|
-
*
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
*
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
*
|
|
892
|
+
* Sponsor: transfer collateral to another agent's AgentAccount.
|
|
893
|
+
* (The agent must then supplyCollateral themselves via their own account.)
|
|
752
894
|
*/
|
|
753
|
-
async
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
951
|
+
const tx = await account.execute(target, value, data, { gasLimit });
|
|
952
|
+
return tx.wait();
|
|
768
953
|
}
|
|
769
954
|
/**
|
|
770
|
-
*
|
|
955
|
+
* Execute multiple calls via AgentAccount.executeBatch.
|
|
771
956
|
*/
|
|
772
|
-
async
|
|
773
|
-
|
|
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
|
|
858
|
-
|
|
859
|
-
return balance > 0n;
|
|
962
|
+
const estimate = await account.executeBatch.estimateGas(targets, values, datas);
|
|
963
|
+
gasLimit = estimate * 130n / 100n;
|
|
860
964
|
} catch {
|
|
861
|
-
|
|
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
|
|
886
|
-
return
|
|
887
|
-
}
|
|
888
|
-
/**
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
const
|
|
897
|
-
|
|
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
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
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
|
-
*
|
|
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
|
|
1467
|
-
|
|
1468
|
-
|
|
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
|
|
1308
|
+
* Get the current on-chain score (free, no payment required).
|
|
1472
1309
|
*/
|
|
1473
|
-
async
|
|
1474
|
-
|
|
1475
|
-
|
|
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
|
|
1318
|
+
* Get detailed agent info from backend.
|
|
1485
1319
|
*/
|
|
1486
|
-
async
|
|
1487
|
-
const
|
|
1488
|
-
|
|
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
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
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
|
-
*
|
|
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
|
|
1507
|
-
const
|
|
1508
|
-
|
|
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
|
-
*
|
|
1522
|
-
* Each call is { target, value, data }.
|
|
1342
|
+
* Agent count and list.
|
|
1523
1343
|
*/
|
|
1524
|
-
async
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
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
|
|
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(
|
|
1544
|
-
|
|
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
|
|
1586
|
-
|
|
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
|
-
*
|
|
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
|
|
1614
|
-
const
|
|
1615
|
-
const
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
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
|
|
1626
|
-
const
|
|
1627
|
-
|
|
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
|
-
*
|
|
1432
|
+
* Register only if no identity exists; otherwise throw.
|
|
1433
|
+
* Prevents accidental double-registration.
|
|
1639
1434
|
*/
|
|
1640
|
-
async
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
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
|
|
1655
|
-
return {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
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
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
* @param amount - Human-readable USDC amount (e.g. "50")
|
|
1478
|
+
* Get agent URI (metadata JSON location)
|
|
1668
1479
|
*/
|
|
1669
|
-
async
|
|
1670
|
-
|
|
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
|
-
*
|
|
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
|
|
1690
|
-
const
|
|
1691
|
-
const
|
|
1692
|
-
|
|
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
|
-
*
|
|
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
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
const
|
|
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
|
-
*
|
|
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
|
|
1757
|
-
const
|
|
1758
|
-
|
|
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
|
-
*
|
|
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
|
|
1790
|
-
const
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
|
|
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
|
-
*
|
|
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
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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
|
-
*
|
|
1877
|
-
*
|
|
1878
|
-
* @param amount - Human-readable USDC amount (e.g. "50")
|
|
1539
|
+
* Give feedback to an agent
|
|
1879
1540
|
*/
|
|
1880
|
-
async
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
const
|
|
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
|
-
*
|
|
1914
|
-
*
|
|
1915
|
-
* @param tokenSymbol - Collateral token
|
|
1916
|
-
* @param amount - Human-readable amount or "all"
|
|
1562
|
+
* Give positive feedback (shorthand)
|
|
1917
1563
|
*/
|
|
1918
|
-
async
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
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
|
-
*
|
|
1572
|
+
* Give negative feedback (e.g., for defaults)
|
|
1957
1573
|
*/
|
|
1958
|
-
async
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
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
|
-
*
|
|
1582
|
+
* Record credit default in reputation system
|
|
1972
1583
|
*/
|
|
1973
|
-
async
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
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
|
|
1594
|
+
* Get reputation summary for an agent
|
|
1992
1595
|
*/
|
|
1993
|
-
async
|
|
1994
|
-
const
|
|
1995
|
-
|
|
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
|
-
*
|
|
1622
|
+
* Check if agent has negative credit reputation
|
|
1999
1623
|
*/
|
|
2000
|
-
async
|
|
2001
|
-
const
|
|
2002
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
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
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
2106
|
-
agentId: BigInt(agentId),
|
|
1652
|
+
eligible: true,
|
|
2107
1653
|
owner,
|
|
2108
|
-
|
|
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
|
-
// ============
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
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
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
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
|
};
|