@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.mjs
CHANGED
|
@@ -460,11 +460,6 @@ var AgetherClient = class _AgetherClient {
|
|
|
460
460
|
// src/clients/MorphoClient.ts
|
|
461
461
|
import { ethers as ethers2, Contract as Contract2 } from "ethers";
|
|
462
462
|
import axios from "axios";
|
|
463
|
-
var BASE_COLLATERALS = {
|
|
464
|
-
WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
|
|
465
|
-
wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
|
|
466
|
-
cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
|
|
467
|
-
};
|
|
468
463
|
var MORPHO_API_URL = "https://api.morpho.org/graphql";
|
|
469
464
|
var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
470
465
|
var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -473,6 +468,8 @@ var erc20Iface = new ethers2.Interface(ERC20_ABI);
|
|
|
473
468
|
var MorphoClient = class {
|
|
474
469
|
constructor(config) {
|
|
475
470
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
471
|
+
/** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
|
|
472
|
+
this._tokenCache = /* @__PURE__ */ new Map();
|
|
476
473
|
this._discoveredAt = 0;
|
|
477
474
|
const chainId = config.chainId ?? 8453 /* Base */;
|
|
478
475
|
const defaultCfg = getDefaultConfig(chainId);
|
|
@@ -659,15 +656,16 @@ var MorphoClient = class {
|
|
|
659
656
|
const usdc = new Contract2(this.config.contracts.usdc, ERC20_ABI, this.provider);
|
|
660
657
|
const ethBal = await this.provider.getBalance(eoaAddr);
|
|
661
658
|
const usdcBal = await usdc.balanceOf(eoaAddr);
|
|
659
|
+
const discoveredTokens = await this._getDiscoveredTokens();
|
|
662
660
|
const eoaCollateral = {};
|
|
663
|
-
for (const
|
|
661
|
+
for (const info of discoveredTokens) {
|
|
664
662
|
try {
|
|
665
663
|
const token = new Contract2(info.address, ERC20_ABI, this.provider);
|
|
666
664
|
const bal = await token.balanceOf(eoaAddr);
|
|
667
|
-
eoaCollateral[symbol] = ethers2.formatUnits(bal, info.decimals);
|
|
665
|
+
eoaCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
|
|
668
666
|
} catch (e) {
|
|
669
|
-
console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
670
|
-
eoaCollateral[symbol] = "0";
|
|
667
|
+
console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
668
|
+
eoaCollateral[info.symbol] = "0";
|
|
671
669
|
}
|
|
672
670
|
}
|
|
673
671
|
const result = {
|
|
@@ -682,14 +680,14 @@ var MorphoClient = class {
|
|
|
682
680
|
const acctEth = await this.provider.getBalance(acctAddr);
|
|
683
681
|
const acctUsdc = await usdc.balanceOf(acctAddr);
|
|
684
682
|
const acctCollateral = {};
|
|
685
|
-
for (const
|
|
683
|
+
for (const info of discoveredTokens) {
|
|
686
684
|
try {
|
|
687
685
|
const token = new Contract2(info.address, ERC20_ABI, this.provider);
|
|
688
686
|
const bal = await token.balanceOf(acctAddr);
|
|
689
|
-
acctCollateral[symbol] = ethers2.formatUnits(bal, info.decimals);
|
|
687
|
+
acctCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
|
|
690
688
|
} catch (e) {
|
|
691
|
-
console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
692
|
-
acctCollateral[symbol] = "0";
|
|
689
|
+
console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
690
|
+
acctCollateral[info.symbol] = "0";
|
|
693
691
|
}
|
|
694
692
|
}
|
|
695
693
|
result.agentAccount = {
|
|
@@ -775,6 +773,28 @@ var MorphoClient = class {
|
|
|
775
773
|
irm: mi.irm,
|
|
776
774
|
lltv: mi.lltv
|
|
777
775
|
});
|
|
776
|
+
this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
|
|
777
|
+
address: mi.collateralAsset.address,
|
|
778
|
+
symbol: mi.collateralAsset.symbol,
|
|
779
|
+
decimals: mi.collateralAsset.decimals
|
|
780
|
+
});
|
|
781
|
+
this._tokenCache.set(mi.collateralAsset.address.toLowerCase(), {
|
|
782
|
+
address: mi.collateralAsset.address,
|
|
783
|
+
symbol: mi.collateralAsset.symbol,
|
|
784
|
+
decimals: mi.collateralAsset.decimals
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (mi.loanAsset.address !== ethers2.ZeroAddress) {
|
|
788
|
+
this._tokenCache.set(mi.loanAsset.symbol.toUpperCase(), {
|
|
789
|
+
address: mi.loanAsset.address,
|
|
790
|
+
symbol: mi.loanAsset.symbol,
|
|
791
|
+
decimals: mi.loanAsset.decimals
|
|
792
|
+
});
|
|
793
|
+
this._tokenCache.set(mi.loanAsset.address.toLowerCase(), {
|
|
794
|
+
address: mi.loanAsset.address,
|
|
795
|
+
symbol: mi.loanAsset.symbol,
|
|
796
|
+
decimals: mi.loanAsset.decimals
|
|
797
|
+
});
|
|
778
798
|
}
|
|
779
799
|
}
|
|
780
800
|
return this._discoveredMarkets;
|
|
@@ -788,8 +808,17 @@ var MorphoClient = class {
|
|
|
788
808
|
* Tries cache → API → onchain idToMarketParams.
|
|
789
809
|
*/
|
|
790
810
|
async findMarketForCollateral(collateralSymbolOrAddress) {
|
|
791
|
-
|
|
792
|
-
|
|
811
|
+
let colAddr;
|
|
812
|
+
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
813
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
814
|
+
} else {
|
|
815
|
+
try {
|
|
816
|
+
const resolved = await this._resolveToken(collateralSymbolOrAddress);
|
|
817
|
+
colAddr = resolved.address.toLowerCase();
|
|
818
|
+
} catch {
|
|
819
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
793
822
|
const cached = this._marketCache.get(colAddr);
|
|
794
823
|
if (cached) return cached;
|
|
795
824
|
await this.getMarkets();
|
|
@@ -948,8 +977,17 @@ var MorphoClient = class {
|
|
|
948
977
|
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
949
978
|
let collateralFilter = "";
|
|
950
979
|
if (collateralSymbolOrAddress) {
|
|
951
|
-
|
|
952
|
-
|
|
980
|
+
let colAddr;
|
|
981
|
+
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
982
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
983
|
+
} else {
|
|
984
|
+
try {
|
|
985
|
+
const resolved = await this._resolveToken(collateralSymbolOrAddress);
|
|
986
|
+
colAddr = resolved.address.toLowerCase();
|
|
987
|
+
} catch {
|
|
988
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
989
|
+
}
|
|
990
|
+
}
|
|
953
991
|
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
954
992
|
}
|
|
955
993
|
const query = `{
|
|
@@ -1008,8 +1046,7 @@ var MorphoClient = class {
|
|
|
1008
1046
|
* @returns Estimated yield in USD for the period
|
|
1009
1047
|
*/
|
|
1010
1048
|
async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
|
|
1011
|
-
const colInfo =
|
|
1012
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1049
|
+
const colInfo = await this._resolveToken(collateralSymbol);
|
|
1013
1050
|
const rates = await this.getMarketRates(collateralSymbol);
|
|
1014
1051
|
if (rates.length === 0) {
|
|
1015
1052
|
throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
|
|
@@ -1047,7 +1084,259 @@ var MorphoClient = class {
|
|
|
1047
1084
|
};
|
|
1048
1085
|
}
|
|
1049
1086
|
// ════════════════════════════════════════════════════════
|
|
1050
|
-
// Lending
|
|
1087
|
+
// Supply-Side (Lending) — earn yield by supplying USDC
|
|
1088
|
+
// ════════════════════════════════════════════════════════
|
|
1089
|
+
/**
|
|
1090
|
+
* Supply USDC to a Morpho Blue market as a lender (earn yield).
|
|
1091
|
+
*
|
|
1092
|
+
* Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
|
|
1093
|
+
* you deposit the loanToken (USDC) into the market's supply pool and earn
|
|
1094
|
+
* interest paid by borrowers.
|
|
1095
|
+
*
|
|
1096
|
+
* @param usdcAmount - Amount of USDC to supply (e.g. '500')
|
|
1097
|
+
* @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
|
|
1098
|
+
* Optional — defaults to highest-APY market
|
|
1099
|
+
*/
|
|
1100
|
+
async supplyAsset(usdcAmount, collateralSymbol) {
|
|
1101
|
+
const acctAddr = await this.getAccountAddress();
|
|
1102
|
+
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1103
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1104
|
+
const usdcAddr = this.config.contracts.usdc;
|
|
1105
|
+
let params;
|
|
1106
|
+
let usedCollateral;
|
|
1107
|
+
if (collateralSymbol) {
|
|
1108
|
+
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1109
|
+
usedCollateral = collateralSymbol;
|
|
1110
|
+
} else {
|
|
1111
|
+
const rates = await this.getMarketRates();
|
|
1112
|
+
if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
|
|
1113
|
+
const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
|
|
1114
|
+
params = await this.findMarketForCollateral(best.collateralToken);
|
|
1115
|
+
usedCollateral = best.collateralToken;
|
|
1116
|
+
}
|
|
1117
|
+
const marketId = ethers2.keccak256(
|
|
1118
|
+
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
1119
|
+
["address", "address", "address", "address", "uint256"],
|
|
1120
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1121
|
+
)
|
|
1122
|
+
);
|
|
1123
|
+
const usdcContract = new Contract2(usdcAddr, ERC20_ABI, this._signer);
|
|
1124
|
+
const acctBalance = await usdcContract.balanceOf(acctAddr);
|
|
1125
|
+
if (acctBalance < amount) {
|
|
1126
|
+
const shortfall = amount - acctBalance;
|
|
1127
|
+
const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
|
|
1128
|
+
if (eoaBalance < shortfall) {
|
|
1129
|
+
throw new AgetherError(
|
|
1130
|
+
`Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${ethers2.formatUnits(acctBalance, 6)}, EOA has ${ethers2.formatUnits(eoaBalance, 6)}.`,
|
|
1131
|
+
"INSUFFICIENT_BALANCE"
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
const transferTx = await usdcContract.transfer(acctAddr, shortfall);
|
|
1135
|
+
await transferTx.wait();
|
|
1136
|
+
this._refreshSigner();
|
|
1137
|
+
}
|
|
1138
|
+
const targets = [usdcAddr, morphoAddr];
|
|
1139
|
+
const values = [0n, 0n];
|
|
1140
|
+
const datas = [
|
|
1141
|
+
erc20Iface.encodeFunctionData("approve", [morphoAddr, amount]),
|
|
1142
|
+
morphoIface.encodeFunctionData("supply", [
|
|
1143
|
+
this._toTuple(params),
|
|
1144
|
+
amount,
|
|
1145
|
+
0n,
|
|
1146
|
+
acctAddr,
|
|
1147
|
+
"0x"
|
|
1148
|
+
])
|
|
1149
|
+
];
|
|
1150
|
+
const receipt = await this.batch(targets, values, datas);
|
|
1151
|
+
return {
|
|
1152
|
+
tx: receipt.hash,
|
|
1153
|
+
amount: usdcAmount,
|
|
1154
|
+
marketId,
|
|
1155
|
+
collateralToken: usedCollateral,
|
|
1156
|
+
agentAccount: acctAddr
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Withdraw supplied USDC (+ earned interest) from a Morpho Blue market.
|
|
1161
|
+
*
|
|
1162
|
+
* @param usdcAmount - Amount to withdraw (e.g. '100' or 'all' for full position)
|
|
1163
|
+
* @param collateralSymbol - Market collateral to identify which market
|
|
1164
|
+
* @param receiver - Destination address (defaults to EOA)
|
|
1165
|
+
*/
|
|
1166
|
+
async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
|
|
1167
|
+
const acctAddr = await this.getAccountAddress();
|
|
1168
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1169
|
+
const dest = receiver || await this.getSignerAddress();
|
|
1170
|
+
let params;
|
|
1171
|
+
if (collateralSymbol) {
|
|
1172
|
+
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1173
|
+
} else {
|
|
1174
|
+
const { params: p } = await this._findActiveSupplyMarket();
|
|
1175
|
+
params = p;
|
|
1176
|
+
}
|
|
1177
|
+
const marketId = ethers2.keccak256(
|
|
1178
|
+
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
1179
|
+
["address", "address", "address", "address", "uint256"],
|
|
1180
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1181
|
+
)
|
|
1182
|
+
);
|
|
1183
|
+
let withdrawAssets;
|
|
1184
|
+
let withdrawShares;
|
|
1185
|
+
if (usdcAmount === "all") {
|
|
1186
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1187
|
+
withdrawShares = BigInt(pos.supplyShares);
|
|
1188
|
+
withdrawAssets = 0n;
|
|
1189
|
+
if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
|
|
1190
|
+
} else {
|
|
1191
|
+
withdrawAssets = ethers2.parseUnits(usdcAmount, 6);
|
|
1192
|
+
withdrawShares = 0n;
|
|
1193
|
+
}
|
|
1194
|
+
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1195
|
+
this._toTuple(params),
|
|
1196
|
+
withdrawAssets,
|
|
1197
|
+
withdrawShares,
|
|
1198
|
+
acctAddr,
|
|
1199
|
+
dest
|
|
1200
|
+
]);
|
|
1201
|
+
const receipt = await this.exec(morphoAddr, data);
|
|
1202
|
+
let remainingSupply = "0";
|
|
1203
|
+
try {
|
|
1204
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1205
|
+
const mkt = await this.morphoBlue.market(marketId);
|
|
1206
|
+
const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
|
|
1207
|
+
const totalSupplyShares = BigInt(mkt.totalSupplyShares);
|
|
1208
|
+
const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
|
|
1209
|
+
remainingSupply = ethers2.formatUnits(currentAssets, 6);
|
|
1210
|
+
} catch (e) {
|
|
1211
|
+
console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
|
|
1212
|
+
}
|
|
1213
|
+
return {
|
|
1214
|
+
tx: receipt.hash,
|
|
1215
|
+
amount: usdcAmount,
|
|
1216
|
+
remainingSupply,
|
|
1217
|
+
destination: dest
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Get supply (lending) positions with yield tracking.
|
|
1222
|
+
*
|
|
1223
|
+
* Uses Morpho GraphQL API (no eth_getLogs / no DB / no indexer):
|
|
1224
|
+
* 1. `userByAddress` → all market positions with current supplyAssets, supplyApy
|
|
1225
|
+
* 2. `transactions` → all MarketSupply/MarketWithdraw history for net deposited
|
|
1226
|
+
* 3. earnedYield = currentSupplyAssets − netDeposited
|
|
1227
|
+
*
|
|
1228
|
+
* @param collateralSymbol - Market collateral token (optional, returns all if omitted)
|
|
1229
|
+
*/
|
|
1230
|
+
async getSupplyPositions(collateralSymbol) {
|
|
1231
|
+
const acctAddr = (await this.getAccountAddress()).toLowerCase();
|
|
1232
|
+
const chainId = this.config.chainId;
|
|
1233
|
+
const positionsQuery = `{
|
|
1234
|
+
userByAddress(address: "${acctAddr}", chainId: ${chainId}) {
|
|
1235
|
+
marketPositions {
|
|
1236
|
+
market {
|
|
1237
|
+
uniqueKey
|
|
1238
|
+
loanAsset { symbol address decimals }
|
|
1239
|
+
collateralAsset { symbol address }
|
|
1240
|
+
state { supplyApy }
|
|
1241
|
+
}
|
|
1242
|
+
state {
|
|
1243
|
+
supplyShares
|
|
1244
|
+
supplyAssets
|
|
1245
|
+
supplyAssetsUsd
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}`;
|
|
1250
|
+
const posResp = await axios.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
|
|
1251
|
+
const user = posResp.data?.data?.userByAddress;
|
|
1252
|
+
if (!user?.marketPositions) return [];
|
|
1253
|
+
const activePositions = user.marketPositions.filter(
|
|
1254
|
+
(p) => p.state && BigInt(p.state.supplyShares ?? "0") > 0n
|
|
1255
|
+
);
|
|
1256
|
+
if (activePositions.length === 0) return [];
|
|
1257
|
+
const filtered = collateralSymbol ? activePositions.filter((p) => {
|
|
1258
|
+
const sym = p.market.collateralAsset?.symbol;
|
|
1259
|
+
return sym && sym.toUpperCase() === collateralSymbol.toUpperCase();
|
|
1260
|
+
}) : activePositions;
|
|
1261
|
+
if (filtered.length === 0) return [];
|
|
1262
|
+
const netDepositedMap = await this._computeNetDepositedAll(acctAddr, chainId);
|
|
1263
|
+
const results = [];
|
|
1264
|
+
for (const p of filtered) {
|
|
1265
|
+
const currentAssets = BigInt(p.state.supplyAssets ?? "0");
|
|
1266
|
+
const marketKey = p.market.uniqueKey.toLowerCase();
|
|
1267
|
+
const netDeposited = netDepositedMap.get(marketKey) ?? 0n;
|
|
1268
|
+
const earnedYield = currentAssets > netDeposited ? currentAssets - netDeposited : 0n;
|
|
1269
|
+
results.push({
|
|
1270
|
+
marketId: p.market.uniqueKey,
|
|
1271
|
+
loanToken: p.market.loanAsset.symbol,
|
|
1272
|
+
collateralToken: p.market.collateralAsset?.symbol ?? "none",
|
|
1273
|
+
supplyShares: p.state.supplyShares,
|
|
1274
|
+
suppliedAssets: ethers2.formatUnits(currentAssets, p.market.loanAsset.decimals),
|
|
1275
|
+
netDeposited: ethers2.formatUnits(netDeposited, p.market.loanAsset.decimals),
|
|
1276
|
+
earnedYield: ethers2.formatUnits(earnedYield, p.market.loanAsset.decimals),
|
|
1277
|
+
supplyApy: p.market.state?.supplyApy ?? 0
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
return results;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Pay a recipient using ONLY earned yield from a supply position.
|
|
1284
|
+
*
|
|
1285
|
+
* Computes available yield, verifies the requested amount doesn't exceed it,
|
|
1286
|
+
* then withdraws from the supply position and sends directly to the recipient.
|
|
1287
|
+
*
|
|
1288
|
+
* @param recipient - Address to receive the USDC
|
|
1289
|
+
* @param usdcAmount - Amount to pay from yield (e.g. '5.50')
|
|
1290
|
+
* @param collateralSymbol - Market collateral to identify which supply position
|
|
1291
|
+
*/
|
|
1292
|
+
async payFromYield(recipient, usdcAmount, collateralSymbol) {
|
|
1293
|
+
const acctAddr = await this.getAccountAddress();
|
|
1294
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1295
|
+
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1296
|
+
const positions = await this.getSupplyPositions(collateralSymbol);
|
|
1297
|
+
if (positions.length === 0) {
|
|
1298
|
+
throw new AgetherError("No supply position found", "NO_SUPPLY");
|
|
1299
|
+
}
|
|
1300
|
+
const pos = positions.reduce(
|
|
1301
|
+
(a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
|
|
1302
|
+
);
|
|
1303
|
+
const availableYield = ethers2.parseUnits(pos.earnedYield, 6);
|
|
1304
|
+
if (amount > availableYield) {
|
|
1305
|
+
throw new AgetherError(
|
|
1306
|
+
`Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
|
|
1307
|
+
"EXCEEDS_YIELD"
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
const params = await this.findMarketForCollateral(pos.collateralToken);
|
|
1311
|
+
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1312
|
+
this._toTuple(params),
|
|
1313
|
+
amount,
|
|
1314
|
+
0n,
|
|
1315
|
+
acctAddr,
|
|
1316
|
+
recipient
|
|
1317
|
+
]);
|
|
1318
|
+
const receipt = await this.exec(morphoAddr, data);
|
|
1319
|
+
let remainingYield = "0";
|
|
1320
|
+
let remainingSupply = "0";
|
|
1321
|
+
try {
|
|
1322
|
+
const updatedPositions = await this.getSupplyPositions(pos.collateralToken);
|
|
1323
|
+
if (updatedPositions.length > 0) {
|
|
1324
|
+
remainingYield = updatedPositions[0].earnedYield;
|
|
1325
|
+
remainingSupply = updatedPositions[0].suppliedAssets;
|
|
1326
|
+
}
|
|
1327
|
+
} catch (e) {
|
|
1328
|
+
console.warn("[agether] failed to read remaining yield:", e instanceof Error ? e.message : e);
|
|
1329
|
+
}
|
|
1330
|
+
return {
|
|
1331
|
+
tx: receipt.hash,
|
|
1332
|
+
yieldWithdrawn: usdcAmount,
|
|
1333
|
+
recipient,
|
|
1334
|
+
remainingYield,
|
|
1335
|
+
remainingSupply
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
// ════════════════════════════════════════════════════════
|
|
1339
|
+
// Collateral & Borrowing Operations (all via AgentAccount)
|
|
1051
1340
|
// ════════════════════════════════════════════════════════
|
|
1052
1341
|
/**
|
|
1053
1342
|
* Deposit collateral into Morpho Blue.
|
|
@@ -1059,8 +1348,7 @@ var MorphoClient = class {
|
|
|
1059
1348
|
*/
|
|
1060
1349
|
async supplyCollateral(tokenSymbol, amount, marketParams) {
|
|
1061
1350
|
const acctAddr = await this.getAccountAddress();
|
|
1062
|
-
const colInfo =
|
|
1063
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1351
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1064
1352
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1065
1353
|
const weiAmount = ethers2.parseUnits(amount, colInfo.decimals);
|
|
1066
1354
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
@@ -1181,8 +1469,7 @@ var MorphoClient = class {
|
|
|
1181
1469
|
*/
|
|
1182
1470
|
async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
|
|
1183
1471
|
const acctAddr = await this.getAccountAddress();
|
|
1184
|
-
const colInfo =
|
|
1185
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1472
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1186
1473
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1187
1474
|
const colWei = ethers2.parseUnits(collateralAmount, colInfo.decimals);
|
|
1188
1475
|
const borrowWei = ethers2.parseUnits(borrowUsdcAmount, 6);
|
|
@@ -1350,8 +1637,7 @@ var MorphoClient = class {
|
|
|
1350
1637
|
*/
|
|
1351
1638
|
async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
|
|
1352
1639
|
const acctAddr = await this.getAccountAddress();
|
|
1353
|
-
const colInfo =
|
|
1354
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1640
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1355
1641
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1356
1642
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1357
1643
|
const usdcAddr = this.config.contracts.usdc;
|
|
@@ -1448,8 +1734,7 @@ var MorphoClient = class {
|
|
|
1448
1734
|
* (The agent must then supplyCollateral themselves via their own account.)
|
|
1449
1735
|
*/
|
|
1450
1736
|
async sponsor(target, tokenSymbol, amount) {
|
|
1451
|
-
const colInfo =
|
|
1452
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1737
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1453
1738
|
let targetAddr;
|
|
1454
1739
|
if (target.address) {
|
|
1455
1740
|
targetAddr = target.address;
|
|
@@ -1678,6 +1963,131 @@ var MorphoClient = class {
|
|
|
1678
1963
|
const params = await this.findMarketForCollateral("WETH");
|
|
1679
1964
|
return { params, symbol: "WETH" };
|
|
1680
1965
|
}
|
|
1966
|
+
/** Find the first market where the agent has a supply (lending) position. */
|
|
1967
|
+
async _findActiveSupplyMarket() {
|
|
1968
|
+
const acctAddr = await this.getAccountAddress();
|
|
1969
|
+
const markets = await this.getMarkets();
|
|
1970
|
+
for (const m of markets) {
|
|
1971
|
+
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
1972
|
+
try {
|
|
1973
|
+
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1974
|
+
if (BigInt(pos.supplyShares) > 0n) {
|
|
1975
|
+
return {
|
|
1976
|
+
params: {
|
|
1977
|
+
loanToken: m.loanAsset.address,
|
|
1978
|
+
collateralToken: m.collateralAsset.address,
|
|
1979
|
+
oracle: m.oracle,
|
|
1980
|
+
irm: m.irm,
|
|
1981
|
+
lltv: m.lltv
|
|
1982
|
+
},
|
|
1983
|
+
symbol: m.collateralAsset.symbol
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
} catch (e) {
|
|
1987
|
+
console.warn("[agether] _findActiveSupplyMarket position check failed:", e instanceof Error ? e.message : e);
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
throw new AgetherError("No active supply position found", "NO_SUPPLY");
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
1995
|
+
*
|
|
1996
|
+
* Uses the dynamic `_tokenCache` populated by `getMarkets()` from the
|
|
1997
|
+
* Morpho GraphQL API — no hardcoded token list needed.
|
|
1998
|
+
*
|
|
1999
|
+
* @param symbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
|
|
2000
|
+
*/
|
|
2001
|
+
async _resolveToken(symbolOrAddress) {
|
|
2002
|
+
const key = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
|
|
2003
|
+
const cached = this._tokenCache.get(key);
|
|
2004
|
+
if (cached) return cached;
|
|
2005
|
+
await this.getMarkets();
|
|
2006
|
+
const fromApi = this._tokenCache.get(key);
|
|
2007
|
+
if (fromApi) return fromApi;
|
|
2008
|
+
throw new AgetherError(
|
|
2009
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
|
|
2010
|
+
"UNKNOWN_COLLATERAL"
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Get all discovered collateral tokens (for balance iteration, etc.).
|
|
2015
|
+
* Returns unique tokens from the Morpho API market discovery.
|
|
2016
|
+
*/
|
|
2017
|
+
async _getDiscoveredTokens() {
|
|
2018
|
+
await this.getMarkets();
|
|
2019
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2020
|
+
const tokens = [];
|
|
2021
|
+
for (const [key, info] of this._tokenCache) {
|
|
2022
|
+
if (key.startsWith("0x")) continue;
|
|
2023
|
+
if (seen.has(info.address.toLowerCase())) continue;
|
|
2024
|
+
seen.add(info.address.toLowerCase());
|
|
2025
|
+
if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
|
|
2026
|
+
tokens.push(info);
|
|
2027
|
+
}
|
|
2028
|
+
return tokens;
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Compute net deposited amounts per market using Morpho GraphQL API.
|
|
2032
|
+
*
|
|
2033
|
+
* Fetches all MarketSupply and MarketWithdraw transactions for the account,
|
|
2034
|
+
* then computes: netDeposited[marketId] = Σ Supply.assets − Σ Withdraw.assets
|
|
2035
|
+
*
|
|
2036
|
+
* Returns a Map<marketId (lowercase), bigint>.
|
|
2037
|
+
*
|
|
2038
|
+
* Uses pagination (100 per page) for completeness, though agent accounts
|
|
2039
|
+
* typically have single-digit transaction counts.
|
|
2040
|
+
*/
|
|
2041
|
+
async _computeNetDepositedAll(accountAddr, chainId) {
|
|
2042
|
+
const result = /* @__PURE__ */ new Map();
|
|
2043
|
+
let skip = 0;
|
|
2044
|
+
const pageSize = 100;
|
|
2045
|
+
let hasMore = true;
|
|
2046
|
+
while (hasMore) {
|
|
2047
|
+
const txQuery = `{
|
|
2048
|
+
transactions(
|
|
2049
|
+
first: ${pageSize}
|
|
2050
|
+
skip: ${skip}
|
|
2051
|
+
where: {
|
|
2052
|
+
userAddress_in: ["${accountAddr}"]
|
|
2053
|
+
type_in: [MarketSupply, MarketWithdraw]
|
|
2054
|
+
chainId_in: [${chainId}]
|
|
2055
|
+
}
|
|
2056
|
+
) {
|
|
2057
|
+
pageInfo { count countTotal }
|
|
2058
|
+
items {
|
|
2059
|
+
type
|
|
2060
|
+
data {
|
|
2061
|
+
... on MarketTransferTransactionData {
|
|
2062
|
+
assets
|
|
2063
|
+
market { uniqueKey }
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
}`;
|
|
2069
|
+
const resp = await axios.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
|
|
2070
|
+
const txData = resp.data?.data?.transactions;
|
|
2071
|
+
if (!txData?.items) break;
|
|
2072
|
+
for (const tx of txData.items) {
|
|
2073
|
+
const marketKey = tx.data?.market?.uniqueKey?.toLowerCase();
|
|
2074
|
+
if (!marketKey || !tx.data?.assets) continue;
|
|
2075
|
+
const assets = BigInt(tx.data.assets);
|
|
2076
|
+
const current = result.get(marketKey) ?? 0n;
|
|
2077
|
+
if (tx.type === "MarketSupply") {
|
|
2078
|
+
result.set(marketKey, current + assets);
|
|
2079
|
+
} else if (tx.type === "MarketWithdraw") {
|
|
2080
|
+
const newVal = current - assets;
|
|
2081
|
+
result.set(marketKey, newVal > 0n ? newVal : 0n);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const fetched = skip + txData.items.length;
|
|
2085
|
+
const total = txData.pageInfo?.countTotal ?? 0;
|
|
2086
|
+
hasMore = fetched < total;
|
|
2087
|
+
skip += pageSize;
|
|
2088
|
+
}
|
|
2089
|
+
return result;
|
|
2090
|
+
}
|
|
1681
2091
|
};
|
|
1682
2092
|
|
|
1683
2093
|
// src/clients/ScoringClient.ts
|