@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/cli.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agether CLI — Direct Morpho Blue Credit for AI Agents
|
|
4
|
+
*
|
|
5
|
+
* Architecture (v2 — Safe + Safe7579):
|
|
6
|
+
* - All commands sign transactions directly with the agent's private key
|
|
7
|
+
* - Uses MorphoClient for lending (ERC-4337 UserOps through Safe account)
|
|
8
|
+
* - Uses X402Client for paid API calls
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* agether init <private-key> [--agent-id <id>] Initialize
|
|
12
|
+
* agether register [--name <n>] Register ERC-8004 + Safe account
|
|
13
|
+
* agether balance Check balances
|
|
14
|
+
* agether status Show Morpho positions
|
|
15
|
+
* agether score Get credit score (x402-gated)
|
|
16
|
+
* agether markets List Morpho markets
|
|
17
|
+
* agether deposit --amount 0.05 --token WETH Deposit collateral
|
|
18
|
+
* agether borrow --amount 100 Borrow USDC
|
|
19
|
+
* agether deposit-and-borrow --amount 0.05 --token WETH --borrow 100
|
|
20
|
+
* agether repay --amount 50 Repay USDC
|
|
21
|
+
* agether withdraw --amount 0.05 --token WETH Withdraw collateral
|
|
22
|
+
* agether sponsor --amount 0.05 --token WETH --agent-id 123
|
|
23
|
+
* agether fund --amount 50 Fund Safe account with USDC
|
|
24
|
+
* agether x402 <url> [--method GET|POST] [--body <json>]
|
|
25
|
+
*/
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
|
package/dist/cli.js
CHANGED
|
@@ -274,7 +274,7 @@ var MorphoClient_exports = {};
|
|
|
274
274
|
__export(MorphoClient_exports, {
|
|
275
275
|
MorphoClient: () => MorphoClient
|
|
276
276
|
});
|
|
277
|
-
var import_ethers, import_axios,
|
|
277
|
+
var import_ethers, import_axios, MORPHO_API_URL, MODE_SINGLE, MODE_BATCH, morphoIface, erc20Iface, MorphoClient;
|
|
278
278
|
var init_MorphoClient = __esm({
|
|
279
279
|
"src/clients/MorphoClient.ts"() {
|
|
280
280
|
"use strict";
|
|
@@ -283,11 +283,6 @@ var init_MorphoClient = __esm({
|
|
|
283
283
|
init_types();
|
|
284
284
|
init_abis();
|
|
285
285
|
init_config();
|
|
286
|
-
BASE_COLLATERALS = {
|
|
287
|
-
WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
|
|
288
|
-
wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
|
|
289
|
-
cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
|
|
290
|
-
};
|
|
291
286
|
MORPHO_API_URL = "https://api.morpho.org/graphql";
|
|
292
287
|
MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
293
288
|
MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -296,6 +291,8 @@ var init_MorphoClient = __esm({
|
|
|
296
291
|
MorphoClient = class {
|
|
297
292
|
constructor(config) {
|
|
298
293
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
294
|
+
/** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
|
|
295
|
+
this._tokenCache = /* @__PURE__ */ new Map();
|
|
299
296
|
this._discoveredAt = 0;
|
|
300
297
|
const chainId = config.chainId ?? 8453 /* Base */;
|
|
301
298
|
const defaultCfg = getDefaultConfig(chainId);
|
|
@@ -482,15 +479,16 @@ var init_MorphoClient = __esm({
|
|
|
482
479
|
const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
|
|
483
480
|
const ethBal = await this.provider.getBalance(eoaAddr);
|
|
484
481
|
const usdcBal = await usdc.balanceOf(eoaAddr);
|
|
482
|
+
const discoveredTokens = await this._getDiscoveredTokens();
|
|
485
483
|
const eoaCollateral = {};
|
|
486
|
-
for (const
|
|
484
|
+
for (const info of discoveredTokens) {
|
|
487
485
|
try {
|
|
488
486
|
const token = new import_ethers.Contract(info.address, ERC20_ABI, this.provider);
|
|
489
487
|
const bal = await token.balanceOf(eoaAddr);
|
|
490
|
-
eoaCollateral[symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
|
|
488
|
+
eoaCollateral[info.symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
|
|
491
489
|
} catch (e) {
|
|
492
|
-
console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
493
|
-
eoaCollateral[symbol] = "0";
|
|
490
|
+
console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
491
|
+
eoaCollateral[info.symbol] = "0";
|
|
494
492
|
}
|
|
495
493
|
}
|
|
496
494
|
const result = {
|
|
@@ -505,14 +503,14 @@ var init_MorphoClient = __esm({
|
|
|
505
503
|
const acctEth = await this.provider.getBalance(acctAddr);
|
|
506
504
|
const acctUsdc = await usdc.balanceOf(acctAddr);
|
|
507
505
|
const acctCollateral = {};
|
|
508
|
-
for (const
|
|
506
|
+
for (const info of discoveredTokens) {
|
|
509
507
|
try {
|
|
510
508
|
const token = new import_ethers.Contract(info.address, ERC20_ABI, this.provider);
|
|
511
509
|
const bal = await token.balanceOf(acctAddr);
|
|
512
|
-
acctCollateral[symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
|
|
510
|
+
acctCollateral[info.symbol] = import_ethers.ethers.formatUnits(bal, info.decimals);
|
|
513
511
|
} catch (e) {
|
|
514
|
-
console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
|
|
515
|
-
acctCollateral[symbol] = "0";
|
|
512
|
+
console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
|
|
513
|
+
acctCollateral[info.symbol] = "0";
|
|
516
514
|
}
|
|
517
515
|
}
|
|
518
516
|
result.agentAccount = {
|
|
@@ -598,6 +596,28 @@ var init_MorphoClient = __esm({
|
|
|
598
596
|
irm: mi.irm,
|
|
599
597
|
lltv: mi.lltv
|
|
600
598
|
});
|
|
599
|
+
this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
|
|
600
|
+
address: mi.collateralAsset.address,
|
|
601
|
+
symbol: mi.collateralAsset.symbol,
|
|
602
|
+
decimals: mi.collateralAsset.decimals
|
|
603
|
+
});
|
|
604
|
+
this._tokenCache.set(mi.collateralAsset.address.toLowerCase(), {
|
|
605
|
+
address: mi.collateralAsset.address,
|
|
606
|
+
symbol: mi.collateralAsset.symbol,
|
|
607
|
+
decimals: mi.collateralAsset.decimals
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
if (mi.loanAsset.address !== import_ethers.ethers.ZeroAddress) {
|
|
611
|
+
this._tokenCache.set(mi.loanAsset.symbol.toUpperCase(), {
|
|
612
|
+
address: mi.loanAsset.address,
|
|
613
|
+
symbol: mi.loanAsset.symbol,
|
|
614
|
+
decimals: mi.loanAsset.decimals
|
|
615
|
+
});
|
|
616
|
+
this._tokenCache.set(mi.loanAsset.address.toLowerCase(), {
|
|
617
|
+
address: mi.loanAsset.address,
|
|
618
|
+
symbol: mi.loanAsset.symbol,
|
|
619
|
+
decimals: mi.loanAsset.decimals
|
|
620
|
+
});
|
|
601
621
|
}
|
|
602
622
|
}
|
|
603
623
|
return this._discoveredMarkets;
|
|
@@ -611,8 +631,17 @@ var init_MorphoClient = __esm({
|
|
|
611
631
|
* Tries cache → API → onchain idToMarketParams.
|
|
612
632
|
*/
|
|
613
633
|
async findMarketForCollateral(collateralSymbolOrAddress) {
|
|
614
|
-
|
|
615
|
-
|
|
634
|
+
let colAddr;
|
|
635
|
+
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
636
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
637
|
+
} else {
|
|
638
|
+
try {
|
|
639
|
+
const resolved = await this._resolveToken(collateralSymbolOrAddress);
|
|
640
|
+
colAddr = resolved.address.toLowerCase();
|
|
641
|
+
} catch {
|
|
642
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
616
645
|
const cached = this._marketCache.get(colAddr);
|
|
617
646
|
if (cached) return cached;
|
|
618
647
|
await this.getMarkets();
|
|
@@ -771,8 +800,17 @@ var init_MorphoClient = __esm({
|
|
|
771
800
|
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
772
801
|
let collateralFilter = "";
|
|
773
802
|
if (collateralSymbolOrAddress) {
|
|
774
|
-
|
|
775
|
-
|
|
803
|
+
let colAddr;
|
|
804
|
+
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
805
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
806
|
+
} else {
|
|
807
|
+
try {
|
|
808
|
+
const resolved = await this._resolveToken(collateralSymbolOrAddress);
|
|
809
|
+
colAddr = resolved.address.toLowerCase();
|
|
810
|
+
} catch {
|
|
811
|
+
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
812
|
+
}
|
|
813
|
+
}
|
|
776
814
|
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
777
815
|
}
|
|
778
816
|
const query = `{
|
|
@@ -831,8 +869,7 @@ var init_MorphoClient = __esm({
|
|
|
831
869
|
* @returns Estimated yield in USD for the period
|
|
832
870
|
*/
|
|
833
871
|
async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
|
|
834
|
-
const colInfo =
|
|
835
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
|
|
872
|
+
const colInfo = await this._resolveToken(collateralSymbol);
|
|
836
873
|
const rates = await this.getMarketRates(collateralSymbol);
|
|
837
874
|
if (rates.length === 0) {
|
|
838
875
|
throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
|
|
@@ -870,7 +907,259 @@ var init_MorphoClient = __esm({
|
|
|
870
907
|
};
|
|
871
908
|
}
|
|
872
909
|
// ════════════════════════════════════════════════════════
|
|
873
|
-
// Lending
|
|
910
|
+
// Supply-Side (Lending) — earn yield by supplying USDC
|
|
911
|
+
// ════════════════════════════════════════════════════════
|
|
912
|
+
/**
|
|
913
|
+
* Supply USDC to a Morpho Blue market as a lender (earn yield).
|
|
914
|
+
*
|
|
915
|
+
* Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
|
|
916
|
+
* you deposit the loanToken (USDC) into the market's supply pool and earn
|
|
917
|
+
* interest paid by borrowers.
|
|
918
|
+
*
|
|
919
|
+
* @param usdcAmount - Amount of USDC to supply (e.g. '500')
|
|
920
|
+
* @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
|
|
921
|
+
* Optional — defaults to highest-APY market
|
|
922
|
+
*/
|
|
923
|
+
async supplyAsset(usdcAmount, collateralSymbol) {
|
|
924
|
+
const acctAddr = await this.getAccountAddress();
|
|
925
|
+
const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
|
|
926
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
927
|
+
const usdcAddr = this.config.contracts.usdc;
|
|
928
|
+
let params;
|
|
929
|
+
let usedCollateral;
|
|
930
|
+
if (collateralSymbol) {
|
|
931
|
+
params = await this.findMarketForCollateral(collateralSymbol);
|
|
932
|
+
usedCollateral = collateralSymbol;
|
|
933
|
+
} else {
|
|
934
|
+
const rates = await this.getMarketRates();
|
|
935
|
+
if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
|
|
936
|
+
const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
|
|
937
|
+
params = await this.findMarketForCollateral(best.collateralToken);
|
|
938
|
+
usedCollateral = best.collateralToken;
|
|
939
|
+
}
|
|
940
|
+
const marketId = import_ethers.ethers.keccak256(
|
|
941
|
+
import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
942
|
+
["address", "address", "address", "address", "uint256"],
|
|
943
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
944
|
+
)
|
|
945
|
+
);
|
|
946
|
+
const usdcContract = new import_ethers.Contract(usdcAddr, ERC20_ABI, this._signer);
|
|
947
|
+
const acctBalance = await usdcContract.balanceOf(acctAddr);
|
|
948
|
+
if (acctBalance < amount) {
|
|
949
|
+
const shortfall = amount - acctBalance;
|
|
950
|
+
const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
|
|
951
|
+
if (eoaBalance < shortfall) {
|
|
952
|
+
throw new AgetherError(
|
|
953
|
+
`Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, 6)}.`,
|
|
954
|
+
"INSUFFICIENT_BALANCE"
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
const transferTx = await usdcContract.transfer(acctAddr, shortfall);
|
|
958
|
+
await transferTx.wait();
|
|
959
|
+
this._refreshSigner();
|
|
960
|
+
}
|
|
961
|
+
const targets = [usdcAddr, morphoAddr];
|
|
962
|
+
const values = [0n, 0n];
|
|
963
|
+
const datas = [
|
|
964
|
+
erc20Iface.encodeFunctionData("approve", [morphoAddr, amount]),
|
|
965
|
+
morphoIface.encodeFunctionData("supply", [
|
|
966
|
+
this._toTuple(params),
|
|
967
|
+
amount,
|
|
968
|
+
0n,
|
|
969
|
+
acctAddr,
|
|
970
|
+
"0x"
|
|
971
|
+
])
|
|
972
|
+
];
|
|
973
|
+
const receipt = await this.batch(targets, values, datas);
|
|
974
|
+
return {
|
|
975
|
+
tx: receipt.hash,
|
|
976
|
+
amount: usdcAmount,
|
|
977
|
+
marketId,
|
|
978
|
+
collateralToken: usedCollateral,
|
|
979
|
+
agentAccount: acctAddr
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Withdraw supplied USDC (+ earned interest) from a Morpho Blue market.
|
|
984
|
+
*
|
|
985
|
+
* @param usdcAmount - Amount to withdraw (e.g. '100' or 'all' for full position)
|
|
986
|
+
* @param collateralSymbol - Market collateral to identify which market
|
|
987
|
+
* @param receiver - Destination address (defaults to EOA)
|
|
988
|
+
*/
|
|
989
|
+
async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
|
|
990
|
+
const acctAddr = await this.getAccountAddress();
|
|
991
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
992
|
+
const dest = receiver || await this.getSignerAddress();
|
|
993
|
+
let params;
|
|
994
|
+
if (collateralSymbol) {
|
|
995
|
+
params = await this.findMarketForCollateral(collateralSymbol);
|
|
996
|
+
} else {
|
|
997
|
+
const { params: p } = await this._findActiveSupplyMarket();
|
|
998
|
+
params = p;
|
|
999
|
+
}
|
|
1000
|
+
const marketId = import_ethers.ethers.keccak256(
|
|
1001
|
+
import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1002
|
+
["address", "address", "address", "address", "uint256"],
|
|
1003
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1004
|
+
)
|
|
1005
|
+
);
|
|
1006
|
+
let withdrawAssets;
|
|
1007
|
+
let withdrawShares;
|
|
1008
|
+
if (usdcAmount === "all") {
|
|
1009
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1010
|
+
withdrawShares = BigInt(pos.supplyShares);
|
|
1011
|
+
withdrawAssets = 0n;
|
|
1012
|
+
if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
|
|
1013
|
+
} else {
|
|
1014
|
+
withdrawAssets = import_ethers.ethers.parseUnits(usdcAmount, 6);
|
|
1015
|
+
withdrawShares = 0n;
|
|
1016
|
+
}
|
|
1017
|
+
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1018
|
+
this._toTuple(params),
|
|
1019
|
+
withdrawAssets,
|
|
1020
|
+
withdrawShares,
|
|
1021
|
+
acctAddr,
|
|
1022
|
+
dest
|
|
1023
|
+
]);
|
|
1024
|
+
const receipt = await this.exec(morphoAddr, data);
|
|
1025
|
+
let remainingSupply = "0";
|
|
1026
|
+
try {
|
|
1027
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1028
|
+
const mkt = await this.morphoBlue.market(marketId);
|
|
1029
|
+
const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
|
|
1030
|
+
const totalSupplyShares = BigInt(mkt.totalSupplyShares);
|
|
1031
|
+
const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
|
|
1032
|
+
remainingSupply = import_ethers.ethers.formatUnits(currentAssets, 6);
|
|
1033
|
+
} catch (e) {
|
|
1034
|
+
console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
tx: receipt.hash,
|
|
1038
|
+
amount: usdcAmount,
|
|
1039
|
+
remainingSupply,
|
|
1040
|
+
destination: dest
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Get supply (lending) positions with yield tracking.
|
|
1045
|
+
*
|
|
1046
|
+
* Uses Morpho GraphQL API (no eth_getLogs / no DB / no indexer):
|
|
1047
|
+
* 1. `userByAddress` → all market positions with current supplyAssets, supplyApy
|
|
1048
|
+
* 2. `transactions` → all MarketSupply/MarketWithdraw history for net deposited
|
|
1049
|
+
* 3. earnedYield = currentSupplyAssets − netDeposited
|
|
1050
|
+
*
|
|
1051
|
+
* @param collateralSymbol - Market collateral token (optional, returns all if omitted)
|
|
1052
|
+
*/
|
|
1053
|
+
async getSupplyPositions(collateralSymbol) {
|
|
1054
|
+
const acctAddr = (await this.getAccountAddress()).toLowerCase();
|
|
1055
|
+
const chainId = this.config.chainId;
|
|
1056
|
+
const positionsQuery = `{
|
|
1057
|
+
userByAddress(address: "${acctAddr}", chainId: ${chainId}) {
|
|
1058
|
+
marketPositions {
|
|
1059
|
+
market {
|
|
1060
|
+
uniqueKey
|
|
1061
|
+
loanAsset { symbol address decimals }
|
|
1062
|
+
collateralAsset { symbol address }
|
|
1063
|
+
state { supplyApy }
|
|
1064
|
+
}
|
|
1065
|
+
state {
|
|
1066
|
+
supplyShares
|
|
1067
|
+
supplyAssets
|
|
1068
|
+
supplyAssetsUsd
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}`;
|
|
1073
|
+
const posResp = await import_axios.default.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
|
|
1074
|
+
const user = posResp.data?.data?.userByAddress;
|
|
1075
|
+
if (!user?.marketPositions) return [];
|
|
1076
|
+
const activePositions = user.marketPositions.filter(
|
|
1077
|
+
(p) => p.state && BigInt(p.state.supplyShares ?? "0") > 0n
|
|
1078
|
+
);
|
|
1079
|
+
if (activePositions.length === 0) return [];
|
|
1080
|
+
const filtered = collateralSymbol ? activePositions.filter((p) => {
|
|
1081
|
+
const sym = p.market.collateralAsset?.symbol;
|
|
1082
|
+
return sym && sym.toUpperCase() === collateralSymbol.toUpperCase();
|
|
1083
|
+
}) : activePositions;
|
|
1084
|
+
if (filtered.length === 0) return [];
|
|
1085
|
+
const netDepositedMap = await this._computeNetDepositedAll(acctAddr, chainId);
|
|
1086
|
+
const results = [];
|
|
1087
|
+
for (const p of filtered) {
|
|
1088
|
+
const currentAssets = BigInt(p.state.supplyAssets ?? "0");
|
|
1089
|
+
const marketKey = p.market.uniqueKey.toLowerCase();
|
|
1090
|
+
const netDeposited = netDepositedMap.get(marketKey) ?? 0n;
|
|
1091
|
+
const earnedYield = currentAssets > netDeposited ? currentAssets - netDeposited : 0n;
|
|
1092
|
+
results.push({
|
|
1093
|
+
marketId: p.market.uniqueKey,
|
|
1094
|
+
loanToken: p.market.loanAsset.symbol,
|
|
1095
|
+
collateralToken: p.market.collateralAsset?.symbol ?? "none",
|
|
1096
|
+
supplyShares: p.state.supplyShares,
|
|
1097
|
+
suppliedAssets: import_ethers.ethers.formatUnits(currentAssets, p.market.loanAsset.decimals),
|
|
1098
|
+
netDeposited: import_ethers.ethers.formatUnits(netDeposited, p.market.loanAsset.decimals),
|
|
1099
|
+
earnedYield: import_ethers.ethers.formatUnits(earnedYield, p.market.loanAsset.decimals),
|
|
1100
|
+
supplyApy: p.market.state?.supplyApy ?? 0
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return results;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Pay a recipient using ONLY earned yield from a supply position.
|
|
1107
|
+
*
|
|
1108
|
+
* Computes available yield, verifies the requested amount doesn't exceed it,
|
|
1109
|
+
* then withdraws from the supply position and sends directly to the recipient.
|
|
1110
|
+
*
|
|
1111
|
+
* @param recipient - Address to receive the USDC
|
|
1112
|
+
* @param usdcAmount - Amount to pay from yield (e.g. '5.50')
|
|
1113
|
+
* @param collateralSymbol - Market collateral to identify which supply position
|
|
1114
|
+
*/
|
|
1115
|
+
async payFromYield(recipient, usdcAmount, collateralSymbol) {
|
|
1116
|
+
const acctAddr = await this.getAccountAddress();
|
|
1117
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1118
|
+
const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
|
|
1119
|
+
const positions = await this.getSupplyPositions(collateralSymbol);
|
|
1120
|
+
if (positions.length === 0) {
|
|
1121
|
+
throw new AgetherError("No supply position found", "NO_SUPPLY");
|
|
1122
|
+
}
|
|
1123
|
+
const pos = positions.reduce(
|
|
1124
|
+
(a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
|
|
1125
|
+
);
|
|
1126
|
+
const availableYield = import_ethers.ethers.parseUnits(pos.earnedYield, 6);
|
|
1127
|
+
if (amount > availableYield) {
|
|
1128
|
+
throw new AgetherError(
|
|
1129
|
+
`Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
|
|
1130
|
+
"EXCEEDS_YIELD"
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
const params = await this.findMarketForCollateral(pos.collateralToken);
|
|
1134
|
+
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1135
|
+
this._toTuple(params),
|
|
1136
|
+
amount,
|
|
1137
|
+
0n,
|
|
1138
|
+
acctAddr,
|
|
1139
|
+
recipient
|
|
1140
|
+
]);
|
|
1141
|
+
const receipt = await this.exec(morphoAddr, data);
|
|
1142
|
+
let remainingYield = "0";
|
|
1143
|
+
let remainingSupply = "0";
|
|
1144
|
+
try {
|
|
1145
|
+
const updatedPositions = await this.getSupplyPositions(pos.collateralToken);
|
|
1146
|
+
if (updatedPositions.length > 0) {
|
|
1147
|
+
remainingYield = updatedPositions[0].earnedYield;
|
|
1148
|
+
remainingSupply = updatedPositions[0].suppliedAssets;
|
|
1149
|
+
}
|
|
1150
|
+
} catch (e) {
|
|
1151
|
+
console.warn("[agether] failed to read remaining yield:", e instanceof Error ? e.message : e);
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
tx: receipt.hash,
|
|
1155
|
+
yieldWithdrawn: usdcAmount,
|
|
1156
|
+
recipient,
|
|
1157
|
+
remainingYield,
|
|
1158
|
+
remainingSupply
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
// ════════════════════════════════════════════════════════
|
|
1162
|
+
// Collateral & Borrowing Operations (all via AgentAccount)
|
|
874
1163
|
// ════════════════════════════════════════════════════════
|
|
875
1164
|
/**
|
|
876
1165
|
* Deposit collateral into Morpho Blue.
|
|
@@ -882,8 +1171,7 @@ var init_MorphoClient = __esm({
|
|
|
882
1171
|
*/
|
|
883
1172
|
async supplyCollateral(tokenSymbol, amount, marketParams) {
|
|
884
1173
|
const acctAddr = await this.getAccountAddress();
|
|
885
|
-
const colInfo =
|
|
886
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1174
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
887
1175
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
888
1176
|
const weiAmount = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
|
|
889
1177
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
@@ -1004,8 +1292,7 @@ var init_MorphoClient = __esm({
|
|
|
1004
1292
|
*/
|
|
1005
1293
|
async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
|
|
1006
1294
|
const acctAddr = await this.getAccountAddress();
|
|
1007
|
-
const colInfo =
|
|
1008
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1295
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1009
1296
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1010
1297
|
const colWei = import_ethers.ethers.parseUnits(collateralAmount, colInfo.decimals);
|
|
1011
1298
|
const borrowWei = import_ethers.ethers.parseUnits(borrowUsdcAmount, 6);
|
|
@@ -1173,8 +1460,7 @@ var init_MorphoClient = __esm({
|
|
|
1173
1460
|
*/
|
|
1174
1461
|
async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
|
|
1175
1462
|
const acctAddr = await this.getAccountAddress();
|
|
1176
|
-
const colInfo =
|
|
1177
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1463
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1178
1464
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1179
1465
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1180
1466
|
const usdcAddr = this.config.contracts.usdc;
|
|
@@ -1271,8 +1557,7 @@ var init_MorphoClient = __esm({
|
|
|
1271
1557
|
* (The agent must then supplyCollateral themselves via their own account.)
|
|
1272
1558
|
*/
|
|
1273
1559
|
async sponsor(target, tokenSymbol, amount) {
|
|
1274
|
-
const colInfo =
|
|
1275
|
-
if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
|
|
1560
|
+
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1276
1561
|
let targetAddr;
|
|
1277
1562
|
if (target.address) {
|
|
1278
1563
|
targetAddr = target.address;
|
|
@@ -1501,6 +1786,131 @@ var init_MorphoClient = __esm({
|
|
|
1501
1786
|
const params = await this.findMarketForCollateral("WETH");
|
|
1502
1787
|
return { params, symbol: "WETH" };
|
|
1503
1788
|
}
|
|
1789
|
+
/** Find the first market where the agent has a supply (lending) position. */
|
|
1790
|
+
async _findActiveSupplyMarket() {
|
|
1791
|
+
const acctAddr = await this.getAccountAddress();
|
|
1792
|
+
const markets = await this.getMarkets();
|
|
1793
|
+
for (const m of markets) {
|
|
1794
|
+
if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
|
|
1795
|
+
try {
|
|
1796
|
+
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1797
|
+
if (BigInt(pos.supplyShares) > 0n) {
|
|
1798
|
+
return {
|
|
1799
|
+
params: {
|
|
1800
|
+
loanToken: m.loanAsset.address,
|
|
1801
|
+
collateralToken: m.collateralAsset.address,
|
|
1802
|
+
oracle: m.oracle,
|
|
1803
|
+
irm: m.irm,
|
|
1804
|
+
lltv: m.lltv
|
|
1805
|
+
},
|
|
1806
|
+
symbol: m.collateralAsset.symbol
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
} catch (e) {
|
|
1810
|
+
console.warn("[agether] _findActiveSupplyMarket position check failed:", e instanceof Error ? e.message : e);
|
|
1811
|
+
continue;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
throw new AgetherError("No active supply position found", "NO_SUPPLY");
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
1818
|
+
*
|
|
1819
|
+
* Uses the dynamic `_tokenCache` populated by `getMarkets()` from the
|
|
1820
|
+
* Morpho GraphQL API — no hardcoded token list needed.
|
|
1821
|
+
*
|
|
1822
|
+
* @param symbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
|
|
1823
|
+
*/
|
|
1824
|
+
async _resolveToken(symbolOrAddress) {
|
|
1825
|
+
const key = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
|
|
1826
|
+
const cached = this._tokenCache.get(key);
|
|
1827
|
+
if (cached) return cached;
|
|
1828
|
+
await this.getMarkets();
|
|
1829
|
+
const fromApi = this._tokenCache.get(key);
|
|
1830
|
+
if (fromApi) return fromApi;
|
|
1831
|
+
throw new AgetherError(
|
|
1832
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
|
|
1833
|
+
"UNKNOWN_COLLATERAL"
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Get all discovered collateral tokens (for balance iteration, etc.).
|
|
1838
|
+
* Returns unique tokens from the Morpho API market discovery.
|
|
1839
|
+
*/
|
|
1840
|
+
async _getDiscoveredTokens() {
|
|
1841
|
+
await this.getMarkets();
|
|
1842
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1843
|
+
const tokens = [];
|
|
1844
|
+
for (const [key, info] of this._tokenCache) {
|
|
1845
|
+
if (key.startsWith("0x")) continue;
|
|
1846
|
+
if (seen.has(info.address.toLowerCase())) continue;
|
|
1847
|
+
seen.add(info.address.toLowerCase());
|
|
1848
|
+
if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
|
|
1849
|
+
tokens.push(info);
|
|
1850
|
+
}
|
|
1851
|
+
return tokens;
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Compute net deposited amounts per market using Morpho GraphQL API.
|
|
1855
|
+
*
|
|
1856
|
+
* Fetches all MarketSupply and MarketWithdraw transactions for the account,
|
|
1857
|
+
* then computes: netDeposited[marketId] = Σ Supply.assets − Σ Withdraw.assets
|
|
1858
|
+
*
|
|
1859
|
+
* Returns a Map<marketId (lowercase), bigint>.
|
|
1860
|
+
*
|
|
1861
|
+
* Uses pagination (100 per page) for completeness, though agent accounts
|
|
1862
|
+
* typically have single-digit transaction counts.
|
|
1863
|
+
*/
|
|
1864
|
+
async _computeNetDepositedAll(accountAddr, chainId) {
|
|
1865
|
+
const result = /* @__PURE__ */ new Map();
|
|
1866
|
+
let skip = 0;
|
|
1867
|
+
const pageSize = 100;
|
|
1868
|
+
let hasMore = true;
|
|
1869
|
+
while (hasMore) {
|
|
1870
|
+
const txQuery = `{
|
|
1871
|
+
transactions(
|
|
1872
|
+
first: ${pageSize}
|
|
1873
|
+
skip: ${skip}
|
|
1874
|
+
where: {
|
|
1875
|
+
userAddress_in: ["${accountAddr}"]
|
|
1876
|
+
type_in: [MarketSupply, MarketWithdraw]
|
|
1877
|
+
chainId_in: [${chainId}]
|
|
1878
|
+
}
|
|
1879
|
+
) {
|
|
1880
|
+
pageInfo { count countTotal }
|
|
1881
|
+
items {
|
|
1882
|
+
type
|
|
1883
|
+
data {
|
|
1884
|
+
... on MarketTransferTransactionData {
|
|
1885
|
+
assets
|
|
1886
|
+
market { uniqueKey }
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}`;
|
|
1892
|
+
const resp = await import_axios.default.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
|
|
1893
|
+
const txData = resp.data?.data?.transactions;
|
|
1894
|
+
if (!txData?.items) break;
|
|
1895
|
+
for (const tx of txData.items) {
|
|
1896
|
+
const marketKey = tx.data?.market?.uniqueKey?.toLowerCase();
|
|
1897
|
+
if (!marketKey || !tx.data?.assets) continue;
|
|
1898
|
+
const assets = BigInt(tx.data.assets);
|
|
1899
|
+
const current = result.get(marketKey) ?? 0n;
|
|
1900
|
+
if (tx.type === "MarketSupply") {
|
|
1901
|
+
result.set(marketKey, current + assets);
|
|
1902
|
+
} else if (tx.type === "MarketWithdraw") {
|
|
1903
|
+
const newVal = current - assets;
|
|
1904
|
+
result.set(marketKey, newVal > 0n ? newVal : 0n);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
const fetched = skip + txData.items.length;
|
|
1908
|
+
const total = txData.pageInfo?.countTotal ?? 0;
|
|
1909
|
+
hasMore = fetched < total;
|
|
1910
|
+
skip += pageSize;
|
|
1911
|
+
}
|
|
1912
|
+
return result;
|
|
1913
|
+
}
|
|
1504
1914
|
};
|
|
1505
1915
|
}
|
|
1506
1916
|
});
|