@agether/sdk 2.3.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +439 -29
- package/dist/clients/AgentIdentityClient.d.ts +188 -0
- package/dist/clients/AgentIdentityClient.d.ts.map +1 -0
- package/dist/clients/AgentIdentityClient.js +337 -0
- package/dist/clients/AgetherClient.d.ts +74 -0
- package/dist/clients/AgetherClient.d.ts.map +1 -0
- package/dist/clients/AgetherClient.js +172 -0
- package/dist/clients/MorphoClient.d.ts +482 -0
- package/dist/clients/MorphoClient.d.ts.map +1 -0
- package/dist/clients/MorphoClient.js +1717 -0
- package/dist/clients/ScoringClient.d.ts +89 -0
- package/dist/clients/ScoringClient.d.ts.map +1 -0
- package/dist/clients/ScoringClient.js +93 -0
- package/dist/clients/X402Client.d.ts +168 -0
- package/dist/clients/X402Client.d.ts.map +1 -0
- package/dist/clients/X402Client.js +378 -0
- package/dist/index.d.mts +103 -1
- package/dist/index.d.ts +103 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +438 -28
- package/dist/index.mjs +438 -28
- package/dist/types/index.d.ts +132 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +46 -0
- package/dist/utils/abis.d.ts +29 -0
- package/dist/utils/abis.d.ts.map +1 -0
- package/dist/utils/abis.js +139 -0
- package/dist/utils/config.d.ts +36 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +168 -0
- package/dist/utils/format.d.ts +44 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +75 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -532,11 +532,6 @@ var AgetherClient = class _AgetherClient {
|
|
|
532
532
|
// src/clients/MorphoClient.ts
|
|
533
533
|
var import_ethers2 = require("ethers");
|
|
534
534
|
var import_axios = __toESM(require("axios"));
|
|
535
|
-
var BASE_COLLATERALS = {
|
|
536
|
-
WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
|
|
537
|
-
wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
|
|
538
|
-
cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
|
|
539
|
-
};
|
|
540
535
|
var MORPHO_API_URL = "https://api.morpho.org/graphql";
|
|
541
536
|
var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
542
537
|
var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -545,6 +540,8 @@ var erc20Iface = new import_ethers2.ethers.Interface(ERC20_ABI);
|
|
|
545
540
|
var MorphoClient = class {
|
|
546
541
|
constructor(config) {
|
|
547
542
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
543
|
+
/** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
|
|
544
|
+
this._tokenCache = /* @__PURE__ */ new Map();
|
|
548
545
|
this._discoveredAt = 0;
|
|
549
546
|
const chainId = config.chainId ?? 8453 /* Base */;
|
|
550
547
|
const defaultCfg = getDefaultConfig(chainId);
|
|
@@ -731,15 +728,16 @@ var MorphoClient = class {
|
|
|
731
728
|
const usdc = new import_ethers2.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
|
|
732
729
|
const ethBal = await this.provider.getBalance(eoaAddr);
|
|
733
730
|
const usdcBal = await usdc.balanceOf(eoaAddr);
|
|
731
|
+
const discoveredTokens = await this._getDiscoveredTokens();
|
|
734
732
|
const eoaCollateral = {};
|
|
735
|
-
for (const
|
|
733
|
+
for (const info of discoveredTokens) {
|
|
736
734
|
try {
|
|
737
735
|
const token = new import_ethers2.Contract(info.address, ERC20_ABI, this.provider);
|
|
738
736
|
const bal = await token.balanceOf(eoaAddr);
|
|
739
|
-
eoaCollateral[symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
|
|
737
|
+
eoaCollateral[info.symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
|
|
740
738
|
} catch (e) {
|
|
741
|
-
console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
742
|
-
eoaCollateral[symbol] = "0";
|
|
739
|
+
console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
740
|
+
eoaCollateral[info.symbol] = "0";
|
|
743
741
|
}
|
|
744
742
|
}
|
|
745
743
|
const result = {
|
|
@@ -754,14 +752,14 @@ var MorphoClient = class {
|
|
|
754
752
|
const acctEth = await this.provider.getBalance(acctAddr);
|
|
755
753
|
const acctUsdc = await usdc.balanceOf(acctAddr);
|
|
756
754
|
const acctCollateral = {};
|
|
757
|
-
for (const
|
|
755
|
+
for (const info of discoveredTokens) {
|
|
758
756
|
try {
|
|
759
757
|
const token = new import_ethers2.Contract(info.address, ERC20_ABI, this.provider);
|
|
760
758
|
const bal = await token.balanceOf(acctAddr);
|
|
761
|
-
acctCollateral[symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
|
|
759
|
+
acctCollateral[info.symbol] = import_ethers2.ethers.formatUnits(bal, info.decimals);
|
|
762
760
|
} catch (e) {
|
|
763
|
-
console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
764
|
-
acctCollateral[symbol] = "0";
|
|
761
|
+
console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
762
|
+
acctCollateral[info.symbol] = "0";
|
|
765
763
|
}
|
|
766
764
|
}
|
|
767
765
|
result.agentAccount = {
|
|
@@ -847,6 +845,28 @@ var MorphoClient = class {
|
|
|
847
845
|
irm: mi.irm,
|
|
848
846
|
lltv: mi.lltv
|
|
849
847
|
});
|
|
848
|
+
this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
|
|
849
|
+
address: mi.collateralAsset.address,
|
|
850
|
+
symbol: mi.collateralAsset.symbol,
|
|
851
|
+
decimals: mi.collateralAsset.decimals
|
|
852
|
+
});
|
|
853
|
+
this._tokenCache.set(mi.collateralAsset.address.toLowerCase(), {
|
|
854
|
+
address: mi.collateralAsset.address,
|
|
855
|
+
symbol: mi.collateralAsset.symbol,
|
|
856
|
+
decimals: mi.collateralAsset.decimals
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
if (mi.loanAsset.address !== import_ethers2.ethers.ZeroAddress) {
|
|
860
|
+
this._tokenCache.set(mi.loanAsset.symbol.toUpperCase(), {
|
|
861
|
+
address: mi.loanAsset.address,
|
|
862
|
+
symbol: mi.loanAsset.symbol,
|
|
863
|
+
decimals: mi.loanAsset.decimals
|
|
864
|
+
});
|
|
865
|
+
this._tokenCache.set(mi.loanAsset.address.toLowerCase(), {
|
|
866
|
+
address: mi.loanAsset.address,
|
|
867
|
+
symbol: mi.loanAsset.symbol,
|
|
868
|
+
decimals: mi.loanAsset.decimals
|
|
869
|
+
});
|
|
850
870
|
}
|
|
851
871
|
}
|
|
852
872
|
return this._discoveredMarkets;
|
|
@@ -860,8 +880,17 @@ var MorphoClient = class {
|
|
|
860
880
|
* Tries cache → API → onchain idToMarketParams.
|
|
861
881
|
*/
|
|
862
882
|
async findMarketForCollateral(collateralSymbolOrAddress) {
|
|
863
|
-
|
|
864
|
-
|
|
883
|
+
let colAddr;
|
|
884
|
+
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
885
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
886
|
+
} else {
|
|
887
|
+
try {
|
|
888
|
+
const resolved = await this._resolveToken(collateralSymbolOrAddress);
|
|
889
|
+
colAddr = resolved.address.toLowerCase();
|
|
890
|
+
} catch {
|
|
891
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
892
|
+
}
|
|
893
|
+
}
|
|
865
894
|
const cached = this._marketCache.get(colAddr);
|
|
866
895
|
if (cached) return cached;
|
|
867
896
|
await this.getMarkets();
|
|
@@ -1020,8 +1049,17 @@ var MorphoClient = class {
|
|
|
1020
1049
|
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1021
1050
|
let collateralFilter = "";
|
|
1022
1051
|
if (collateralSymbolOrAddress) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1052
|
+
let colAddr;
|
|
1053
|
+
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
1054
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
1055
|
+
} else {
|
|
1056
|
+
try {
|
|
1057
|
+
const resolved = await this._resolveToken(collateralSymbolOrAddress);
|
|
1058
|
+
colAddr = resolved.address.toLowerCase();
|
|
1059
|
+
} catch {
|
|
1060
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1025
1063
|
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
1026
1064
|
}
|
|
1027
1065
|
const query = `{
|
|
@@ -1080,8 +1118,7 @@ var MorphoClient = class {
|
|
|
1080
1118
|
* @returns Estimated yield in USD for the period
|
|
1081
1119
|
*/
|
|
1082
1120
|
async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
|
|
1083
|
-
const colInfo =
|
|
1084
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1121
|
+
const colInfo = await this._resolveToken(collateralSymbol);
|
|
1085
1122
|
const rates = await this.getMarketRates(collateralSymbol);
|
|
1086
1123
|
if (rates.length === 0) {
|
|
1087
1124
|
throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
|
|
@@ -1119,7 +1156,259 @@ var MorphoClient = class {
|
|
|
1119
1156
|
};
|
|
1120
1157
|
}
|
|
1121
1158
|
// ════════════════════════════════════════════════════════
|
|
1122
|
-
// Lending
|
|
1159
|
+
// Supply-Side (Lending) — earn yield by supplying USDC
|
|
1160
|
+
// ════════════════════════════════════════════════════════
|
|
1161
|
+
/**
|
|
1162
|
+
* Supply USDC to a Morpho Blue market as a lender (earn yield).
|
|
1163
|
+
*
|
|
1164
|
+
* Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
|
|
1165
|
+
* you deposit the loanToken (USDC) into the market's supply pool and earn
|
|
1166
|
+
* interest paid by borrowers.
|
|
1167
|
+
*
|
|
1168
|
+
* @param usdcAmount - Amount of USDC to supply (e.g. '500')
|
|
1169
|
+
* @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
|
|
1170
|
+
* Optional — defaults to highest-APY market
|
|
1171
|
+
*/
|
|
1172
|
+
async supplyAsset(usdcAmount, collateralSymbol) {
|
|
1173
|
+
const acctAddr = await this.getAccountAddress();
|
|
1174
|
+
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1175
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1176
|
+
const usdcAddr = this.config.contracts.usdc;
|
|
1177
|
+
let params;
|
|
1178
|
+
let usedCollateral;
|
|
1179
|
+
if (collateralSymbol) {
|
|
1180
|
+
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1181
|
+
usedCollateral = collateralSymbol;
|
|
1182
|
+
} else {
|
|
1183
|
+
const rates = await this.getMarketRates();
|
|
1184
|
+
if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
|
|
1185
|
+
const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
|
|
1186
|
+
params = await this.findMarketForCollateral(best.collateralToken);
|
|
1187
|
+
usedCollateral = best.collateralToken;
|
|
1188
|
+
}
|
|
1189
|
+
const marketId = import_ethers2.ethers.keccak256(
|
|
1190
|
+
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1191
|
+
["address", "address", "address", "address", "uint256"],
|
|
1192
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1193
|
+
)
|
|
1194
|
+
);
|
|
1195
|
+
const usdcContract = new import_ethers2.Contract(usdcAddr, ERC20_ABI, this._signer);
|
|
1196
|
+
const acctBalance = await usdcContract.balanceOf(acctAddr);
|
|
1197
|
+
if (acctBalance < amount) {
|
|
1198
|
+
const shortfall = amount - acctBalance;
|
|
1199
|
+
const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
|
|
1200
|
+
if (eoaBalance < shortfall) {
|
|
1201
|
+
throw new AgetherError(
|
|
1202
|
+
`Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, 6)}.`,
|
|
1203
|
+
"INSUFFICIENT_BALANCE"
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
const transferTx = await usdcContract.transfer(acctAddr, shortfall);
|
|
1207
|
+
await transferTx.wait();
|
|
1208
|
+
this._refreshSigner();
|
|
1209
|
+
}
|
|
1210
|
+
const targets = [usdcAddr, morphoAddr];
|
|
1211
|
+
const values = [0n, 0n];
|
|
1212
|
+
const datas = [
|
|
1213
|
+
erc20Iface.encodeFunctionData("approve", [morphoAddr, amount]),
|
|
1214
|
+
morphoIface.encodeFunctionData("supply", [
|
|
1215
|
+
this._toTuple(params),
|
|
1216
|
+
amount,
|
|
1217
|
+
0n,
|
|
1218
|
+
acctAddr,
|
|
1219
|
+
"0x"
|
|
1220
|
+
])
|
|
1221
|
+
];
|
|
1222
|
+
const receipt = await this.batch(targets, values, datas);
|
|
1223
|
+
return {
|
|
1224
|
+
tx: receipt.hash,
|
|
1225
|
+
amount: usdcAmount,
|
|
1226
|
+
marketId,
|
|
1227
|
+
collateralToken: usedCollateral,
|
|
1228
|
+
agentAccount: acctAddr
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Withdraw supplied USDC (+ earned interest) from a Morpho Blue market.
|
|
1233
|
+
*
|
|
1234
|
+
* @param usdcAmount - Amount to withdraw (e.g. '100' or 'all' for full position)
|
|
1235
|
+
* @param collateralSymbol - Market collateral to identify which market
|
|
1236
|
+
* @param receiver - Destination address (defaults to EOA)
|
|
1237
|
+
*/
|
|
1238
|
+
async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
|
|
1239
|
+
const acctAddr = await this.getAccountAddress();
|
|
1240
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1241
|
+
const dest = receiver || await this.getSignerAddress();
|
|
1242
|
+
let params;
|
|
1243
|
+
if (collateralSymbol) {
|
|
1244
|
+
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1245
|
+
} else {
|
|
1246
|
+
const { params: p } = await this._findActiveSupplyMarket();
|
|
1247
|
+
params = p;
|
|
1248
|
+
}
|
|
1249
|
+
const marketId = import_ethers2.ethers.keccak256(
|
|
1250
|
+
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1251
|
+
["address", "address", "address", "address", "uint256"],
|
|
1252
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1253
|
+
)
|
|
1254
|
+
);
|
|
1255
|
+
let withdrawAssets;
|
|
1256
|
+
let withdrawShares;
|
|
1257
|
+
if (usdcAmount === "all") {
|
|
1258
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1259
|
+
withdrawShares = BigInt(pos.supplyShares);
|
|
1260
|
+
withdrawAssets = 0n;
|
|
1261
|
+
if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
|
|
1262
|
+
} else {
|
|
1263
|
+
withdrawAssets = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1264
|
+
withdrawShares = 0n;
|
|
1265
|
+
}
|
|
1266
|
+
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1267
|
+
this._toTuple(params),
|
|
1268
|
+
withdrawAssets,
|
|
1269
|
+
withdrawShares,
|
|
1270
|
+
acctAddr,
|
|
1271
|
+
dest
|
|
1272
|
+
]);
|
|
1273
|
+
const receipt = await this.exec(morphoAddr, data);
|
|
1274
|
+
let remainingSupply = "0";
|
|
1275
|
+
try {
|
|
1276
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1277
|
+
const mkt = await this.morphoBlue.market(marketId);
|
|
1278
|
+
const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
|
|
1279
|
+
const totalSupplyShares = BigInt(mkt.totalSupplyShares);
|
|
1280
|
+
const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
|
|
1281
|
+
remainingSupply = import_ethers2.ethers.formatUnits(currentAssets, 6);
|
|
1282
|
+
} catch (e) {
|
|
1283
|
+
console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
|
|
1284
|
+
}
|
|
1285
|
+
return {
|
|
1286
|
+
tx: receipt.hash,
|
|
1287
|
+
amount: usdcAmount,
|
|
1288
|
+
remainingSupply,
|
|
1289
|
+
destination: dest
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Get supply (lending) positions with yield tracking.
|
|
1294
|
+
*
|
|
1295
|
+
* Uses Morpho GraphQL API (no eth_getLogs / no DB / no indexer):
|
|
1296
|
+
* 1. `userByAddress` → all market positions with current supplyAssets, supplyApy
|
|
1297
|
+
* 2. `transactions` → all MarketSupply/MarketWithdraw history for net deposited
|
|
1298
|
+
* 3. earnedYield = currentSupplyAssets − netDeposited
|
|
1299
|
+
*
|
|
1300
|
+
* @param collateralSymbol - Market collateral token (optional, returns all if omitted)
|
|
1301
|
+
*/
|
|
1302
|
+
async getSupplyPositions(collateralSymbol) {
|
|
1303
|
+
const acctAddr = (await this.getAccountAddress()).toLowerCase();
|
|
1304
|
+
const chainId = this.config.chainId;
|
|
1305
|
+
const positionsQuery = `{
|
|
1306
|
+
userByAddress(address: "${acctAddr}", chainId: ${chainId}) {
|
|
1307
|
+
marketPositions {
|
|
1308
|
+
market {
|
|
1309
|
+
uniqueKey
|
|
1310
|
+
loanAsset { symbol address decimals }
|
|
1311
|
+
collateralAsset { symbol address }
|
|
1312
|
+
state { supplyApy }
|
|
1313
|
+
}
|
|
1314
|
+
state {
|
|
1315
|
+
supplyShares
|
|
1316
|
+
supplyAssets
|
|
1317
|
+
supplyAssetsUsd
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}`;
|
|
1322
|
+
const posResp = await import_axios.default.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
|
|
1323
|
+
const user = posResp.data?.data?.userByAddress;
|
|
1324
|
+
if (!user?.marketPositions) return [];
|
|
1325
|
+
const activePositions = user.marketPositions.filter(
|
|
1326
|
+
(p) => p.state && BigInt(p.state.supplyShares ?? "0") > 0n
|
|
1327
|
+
);
|
|
1328
|
+
if (activePositions.length === 0) return [];
|
|
1329
|
+
const filtered = collateralSymbol ? activePositions.filter((p) => {
|
|
1330
|
+
const sym = p.market.collateralAsset?.symbol;
|
|
1331
|
+
return sym && sym.toUpperCase() === collateralSymbol.toUpperCase();
|
|
1332
|
+
}) : activePositions;
|
|
1333
|
+
if (filtered.length === 0) return [];
|
|
1334
|
+
const netDepositedMap = await this._computeNetDepositedAll(acctAddr, chainId);
|
|
1335
|
+
const results = [];
|
|
1336
|
+
for (const p of filtered) {
|
|
1337
|
+
const currentAssets = BigInt(p.state.supplyAssets ?? "0");
|
|
1338
|
+
const marketKey = p.market.uniqueKey.toLowerCase();
|
|
1339
|
+
const netDeposited = netDepositedMap.get(marketKey) ?? 0n;
|
|
1340
|
+
const earnedYield = currentAssets > netDeposited ? currentAssets - netDeposited : 0n;
|
|
1341
|
+
results.push({
|
|
1342
|
+
marketId: p.market.uniqueKey,
|
|
1343
|
+
loanToken: p.market.loanAsset.symbol,
|
|
1344
|
+
collateralToken: p.market.collateralAsset?.symbol ?? "none",
|
|
1345
|
+
supplyShares: p.state.supplyShares,
|
|
1346
|
+
suppliedAssets: import_ethers2.ethers.formatUnits(currentAssets, p.market.loanAsset.decimals),
|
|
1347
|
+
netDeposited: import_ethers2.ethers.formatUnits(netDeposited, p.market.loanAsset.decimals),
|
|
1348
|
+
earnedYield: import_ethers2.ethers.formatUnits(earnedYield, p.market.loanAsset.decimals),
|
|
1349
|
+
supplyApy: p.market.state?.supplyApy ?? 0
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
return results;
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Pay a recipient using ONLY earned yield from a supply position.
|
|
1356
|
+
*
|
|
1357
|
+
* Computes available yield, verifies the requested amount doesn't exceed it,
|
|
1358
|
+
* then withdraws from the supply position and sends directly to the recipient.
|
|
1359
|
+
*
|
|
1360
|
+
* @param recipient - Address to receive the USDC
|
|
1361
|
+
* @param usdcAmount - Amount to pay from yield (e.g. '5.50')
|
|
1362
|
+
* @param collateralSymbol - Market collateral to identify which supply position
|
|
1363
|
+
*/
|
|
1364
|
+
async payFromYield(recipient, usdcAmount, collateralSymbol) {
|
|
1365
|
+
const acctAddr = await this.getAccountAddress();
|
|
1366
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1367
|
+
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1368
|
+
const positions = await this.getSupplyPositions(collateralSymbol);
|
|
1369
|
+
if (positions.length === 0) {
|
|
1370
|
+
throw new AgetherError("No supply position found", "NO_SUPPLY");
|
|
1371
|
+
}
|
|
1372
|
+
const pos = positions.reduce(
|
|
1373
|
+
(a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
|
|
1374
|
+
);
|
|
1375
|
+
const availableYield = import_ethers2.ethers.parseUnits(pos.earnedYield, 6);
|
|
1376
|
+
if (amount > availableYield) {
|
|
1377
|
+
throw new AgetherError(
|
|
1378
|
+
`Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
|
|
1379
|
+
"EXCEEDS_YIELD"
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
const params = await this.findMarketForCollateral(pos.collateralToken);
|
|
1383
|
+
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1384
|
+
this._toTuple(params),
|
|
1385
|
+
amount,
|
|
1386
|
+
0n,
|
|
1387
|
+
acctAddr,
|
|
1388
|
+
recipient
|
|
1389
|
+
]);
|
|
1390
|
+
const receipt = await this.exec(morphoAddr, data);
|
|
1391
|
+
let remainingYield = "0";
|
|
1392
|
+
let remainingSupply = "0";
|
|
1393
|
+
try {
|
|
1394
|
+
const updatedPositions = await this.getSupplyPositions(pos.collateralToken);
|
|
1395
|
+
if (updatedPositions.length > 0) {
|
|
1396
|
+
remainingYield = updatedPositions[0].earnedYield;
|
|
1397
|
+
remainingSupply = updatedPositions[0].suppliedAssets;
|
|
1398
|
+
}
|
|
1399
|
+
} catch (e) {
|
|
1400
|
+
console.warn("[agether] failed to read remaining yield:", e instanceof Error ? e.message : e);
|
|
1401
|
+
}
|
|
1402
|
+
return {
|
|
1403
|
+
tx: receipt.hash,
|
|
1404
|
+
yieldWithdrawn: usdcAmount,
|
|
1405
|
+
recipient,
|
|
1406
|
+
remainingYield,
|
|
1407
|
+
remainingSupply
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
// ════════════════════════════════════════════════════════
|
|
1411
|
+
// Collateral & Borrowing Operations (all via AgentAccount)
|
|
1123
1412
|
// ════════════════════════════════════════════════════════
|
|
1124
1413
|
/**
|
|
1125
1414
|
* Deposit collateral into Morpho Blue.
|
|
@@ -1131,8 +1420,7 @@ var MorphoClient = class {
|
|
|
1131
1420
|
*/
|
|
1132
1421
|
async supplyCollateral(tokenSymbol, amount, marketParams) {
|
|
1133
1422
|
const acctAddr = await this.getAccountAddress();
|
|
1134
|
-
const colInfo =
|
|
1135
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1423
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1136
1424
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1137
1425
|
const weiAmount = import_ethers2.ethers.parseUnits(amount, colInfo.decimals);
|
|
1138
1426
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
@@ -1253,8 +1541,7 @@ var MorphoClient = class {
|
|
|
1253
1541
|
*/
|
|
1254
1542
|
async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
|
|
1255
1543
|
const acctAddr = await this.getAccountAddress();
|
|
1256
|
-
const colInfo =
|
|
1257
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1544
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1258
1545
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1259
1546
|
const colWei = import_ethers2.ethers.parseUnits(collateralAmount, colInfo.decimals);
|
|
1260
1547
|
const borrowWei = import_ethers2.ethers.parseUnits(borrowUsdcAmount, 6);
|
|
@@ -1422,8 +1709,7 @@ var MorphoClient = class {
|
|
|
1422
1709
|
*/
|
|
1423
1710
|
async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
|
|
1424
1711
|
const acctAddr = await this.getAccountAddress();
|
|
1425
|
-
const colInfo =
|
|
1426
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1712
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1427
1713
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1428
1714
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1429
1715
|
const usdcAddr = this.config.contracts.usdc;
|
|
@@ -1520,8 +1806,7 @@ var MorphoClient = class {
|
|
|
1520
1806
|
* (The agent must then supplyCollateral themselves via their own account.)
|
|
1521
1807
|
*/
|
|
1522
1808
|
async sponsor(target, tokenSymbol, amount) {
|
|
1523
|
-
const colInfo =
|
|
1524
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1809
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1525
1810
|
let targetAddr;
|
|
1526
1811
|
if (target.address) {
|
|
1527
1812
|
targetAddr = target.address;
|
|
@@ -1750,6 +2035,131 @@ var MorphoClient = class {
|
|
|
1750
2035
|
const params = await this.findMarketForCollateral("WETH");
|
|
1751
2036
|
return { params, symbol: "WETH" };
|
|
1752
2037
|
}
|
|
2038
|
+
/** Find the first market where the agent has a supply (lending) position. */
|
|
2039
|
+
async _findActiveSupplyMarket() {
|
|
2040
|
+
const acctAddr = await this.getAccountAddress();
|
|
2041
|
+
const markets = await this.getMarkets();
|
|
2042
|
+
for (const m of markets) {
|
|
2043
|
+
if (!m.collateralAsset || m.collateralAsset.address === import_ethers2.ethers.ZeroAddress) continue;
|
|
2044
|
+
try {
|
|
2045
|
+
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
2046
|
+
if (BigInt(pos.supplyShares) > 0n) {
|
|
2047
|
+
return {
|
|
2048
|
+
params: {
|
|
2049
|
+
loanToken: m.loanAsset.address,
|
|
2050
|
+
collateralToken: m.collateralAsset.address,
|
|
2051
|
+
oracle: m.oracle,
|
|
2052
|
+
irm: m.irm,
|
|
2053
|
+
lltv: m.lltv
|
|
2054
|
+
},
|
|
2055
|
+
symbol: m.collateralAsset.symbol
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
} catch (e) {
|
|
2059
|
+
console.warn("[agether] _findActiveSupplyMarket position check failed:", e instanceof Error ? e.message : e);
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
throw new AgetherError("No active supply position found", "NO_SUPPLY");
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
2067
|
+
*
|
|
2068
|
+
* Uses the dynamic `_tokenCache` populated by `getMarkets()` from the
|
|
2069
|
+
* Morpho GraphQL API — no hardcoded token list needed.
|
|
2070
|
+
*
|
|
2071
|
+
* @param symbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
|
|
2072
|
+
*/
|
|
2073
|
+
async _resolveToken(symbolOrAddress) {
|
|
2074
|
+
const key = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
|
|
2075
|
+
const cached = this._tokenCache.get(key);
|
|
2076
|
+
if (cached) return cached;
|
|
2077
|
+
await this.getMarkets();
|
|
2078
|
+
const fromApi = this._tokenCache.get(key);
|
|
2079
|
+
if (fromApi) return fromApi;
|
|
2080
|
+
throw new AgetherError(
|
|
2081
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
|
|
2082
|
+
"UNKNOWN_COLLATERAL"
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Get all discovered collateral tokens (for balance iteration, etc.).
|
|
2087
|
+
* Returns unique tokens from the Morpho API market discovery.
|
|
2088
|
+
*/
|
|
2089
|
+
async _getDiscoveredTokens() {
|
|
2090
|
+
await this.getMarkets();
|
|
2091
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2092
|
+
const tokens = [];
|
|
2093
|
+
for (const [key, info] of this._tokenCache) {
|
|
2094
|
+
if (key.startsWith("0x")) continue;
|
|
2095
|
+
if (seen.has(info.address.toLowerCase())) continue;
|
|
2096
|
+
seen.add(info.address.toLowerCase());
|
|
2097
|
+
if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
|
|
2098
|
+
tokens.push(info);
|
|
2099
|
+
}
|
|
2100
|
+
return tokens;
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Compute net deposited amounts per market using Morpho GraphQL API.
|
|
2104
|
+
*
|
|
2105
|
+
* Fetches all MarketSupply and MarketWithdraw transactions for the account,
|
|
2106
|
+
* then computes: netDeposited[marketId] = Σ Supply.assets − Σ Withdraw.assets
|
|
2107
|
+
*
|
|
2108
|
+
* Returns a Map<marketId (lowercase), bigint>.
|
|
2109
|
+
*
|
|
2110
|
+
* Uses pagination (100 per page) for completeness, though agent accounts
|
|
2111
|
+
* typically have single-digit transaction counts.
|
|
2112
|
+
*/
|
|
2113
|
+
async _computeNetDepositedAll(accountAddr, chainId) {
|
|
2114
|
+
const result = /* @__PURE__ */ new Map();
|
|
2115
|
+
let skip = 0;
|
|
2116
|
+
const pageSize = 100;
|
|
2117
|
+
let hasMore = true;
|
|
2118
|
+
while (hasMore) {
|
|
2119
|
+
const txQuery = `{
|
|
2120
|
+
transactions(
|
|
2121
|
+
first: ${pageSize}
|
|
2122
|
+
skip: ${skip}
|
|
2123
|
+
where: {
|
|
2124
|
+
userAddress_in: ["${accountAddr}"]
|
|
2125
|
+
type_in: [MarketSupply, MarketWithdraw]
|
|
2126
|
+
chainId_in: [${chainId}]
|
|
2127
|
+
}
|
|
2128
|
+
) {
|
|
2129
|
+
pageInfo { count countTotal }
|
|
2130
|
+
items {
|
|
2131
|
+
type
|
|
2132
|
+
data {
|
|
2133
|
+
... on MarketTransferTransactionData {
|
|
2134
|
+
assets
|
|
2135
|
+
market { uniqueKey }
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}`;
|
|
2141
|
+
const resp = await import_axios.default.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
|
|
2142
|
+
const txData = resp.data?.data?.transactions;
|
|
2143
|
+
if (!txData?.items) break;
|
|
2144
|
+
for (const tx of txData.items) {
|
|
2145
|
+
const marketKey = tx.data?.market?.uniqueKey?.toLowerCase();
|
|
2146
|
+
if (!marketKey || !tx.data?.assets) continue;
|
|
2147
|
+
const assets = BigInt(tx.data.assets);
|
|
2148
|
+
const current = result.get(marketKey) ?? 0n;
|
|
2149
|
+
if (tx.type === "MarketSupply") {
|
|
2150
|
+
result.set(marketKey, current + assets);
|
|
2151
|
+
} else if (tx.type === "MarketWithdraw") {
|
|
2152
|
+
const newVal = current - assets;
|
|
2153
|
+
result.set(marketKey, newVal > 0n ? newVal : 0n);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
const fetched = skip + txData.items.length;
|
|
2157
|
+
const total = txData.pageInfo?.countTotal ?? 0;
|
|
2158
|
+
hasMore = fetched < total;
|
|
2159
|
+
skip += pageSize;
|
|
2160
|
+
}
|
|
2161
|
+
return result;
|
|
2162
|
+
}
|
|
1753
2163
|
};
|
|
1754
2164
|
|
|
1755
2165
|
// src/clients/ScoringClient.ts
|