@agether/sdk 2.14.0 → 2.15.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.js +373 -42
- package/dist/index.d.mts +60 -6
- package/dist/index.d.ts +60 -6
- package/dist/index.js +381 -50
- package/dist/index.mjs +381 -50
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/clients/AgetherClient.ts
|
|
2
2
|
import { ethers, Contract } from "ethers";
|
|
3
|
+
import axios from "axios";
|
|
3
4
|
|
|
4
5
|
// src/types/index.ts
|
|
5
6
|
var ChainId = /* @__PURE__ */ ((ChainId3) => {
|
|
@@ -296,6 +297,7 @@ function getContractAddresses(chainId) {
|
|
|
296
297
|
// src/clients/AgetherClient.ts
|
|
297
298
|
var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
298
299
|
var erc20Iface = new ethers.Interface(ERC20_ABI);
|
|
300
|
+
var MORPHO_API_URL = "https://api.morpho.org/graphql";
|
|
299
301
|
var KNOWN_TOKENS = {
|
|
300
302
|
[8453 /* Base */]: {
|
|
301
303
|
WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
|
|
@@ -310,6 +312,17 @@ var KNOWN_TOKENS = {
|
|
|
310
312
|
};
|
|
311
313
|
var AgetherClient = class _AgetherClient {
|
|
312
314
|
constructor(options) {
|
|
315
|
+
/**
|
|
316
|
+
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
317
|
+
*
|
|
318
|
+
* Resolution order:
|
|
319
|
+
* 1. `'USDC'` → from chain config (instant)
|
|
320
|
+
* 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
|
|
321
|
+
* 3. Dynamic cache populated by previous Morpho API lookups (instant)
|
|
322
|
+
* 4. `'0x...'` address → reads decimals and symbol onchain
|
|
323
|
+
* 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
|
|
324
|
+
*/
|
|
325
|
+
this._dynamicTokenCache = /* @__PURE__ */ new Map();
|
|
313
326
|
this.config = options.config;
|
|
314
327
|
this.signer = options.signer;
|
|
315
328
|
this.agentId = options.agentId;
|
|
@@ -594,21 +607,10 @@ var AgetherClient = class _AgetherClient {
|
|
|
594
607
|
}
|
|
595
608
|
/**
|
|
596
609
|
* Fund the Safe account with USDC from EOA.
|
|
597
|
-
*
|
|
610
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
598
611
|
*/
|
|
599
612
|
async fundAccount(usdcAmount) {
|
|
600
|
-
|
|
601
|
-
const usdc = new Contract(this.config.contracts.usdc, ERC20_ABI, this.signer);
|
|
602
|
-
const amount = ethers.parseUnits(usdcAmount, 6);
|
|
603
|
-
const tx = await usdc.transfer(acctAddr, amount);
|
|
604
|
-
const receipt = await tx.wait();
|
|
605
|
-
this._refreshSigner();
|
|
606
|
-
return {
|
|
607
|
-
txHash: receipt.hash,
|
|
608
|
-
blockNumber: receipt.blockNumber,
|
|
609
|
-
status: receipt.status === 1 ? "success" : "failed",
|
|
610
|
-
gasUsed: receipt.gasUsed
|
|
611
|
-
};
|
|
613
|
+
return this.fundAccountToken("USDC", usdcAmount);
|
|
612
614
|
}
|
|
613
615
|
// ════════════════════════════════════════════════════════
|
|
614
616
|
// Withdrawals (Safe → EOA via UserOps)
|
|
@@ -660,6 +662,138 @@ var AgetherClient = class _AgetherClient {
|
|
|
660
662
|
return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
|
|
661
663
|
}
|
|
662
664
|
// ════════════════════════════════════════════════════════
|
|
665
|
+
// Token Transfers & Approvals (AgentAccount → any address)
|
|
666
|
+
// ════════════════════════════════════════════════════════
|
|
667
|
+
/**
|
|
668
|
+
* Fund the AgentAccount with any ERC-20 token from EOA.
|
|
669
|
+
* Simple ERC-20 transfer (does NOT require a UserOp).
|
|
670
|
+
*
|
|
671
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
672
|
+
* @param amount - Amount to send (human-readable, e.g. '100')
|
|
673
|
+
*/
|
|
674
|
+
async fundAccountToken(tokenSymbol, amount) {
|
|
675
|
+
const acctAddr = await this.getAccountAddress();
|
|
676
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
677
|
+
const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer);
|
|
678
|
+
const weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
|
|
679
|
+
const eoaBalance = await tokenContract.balanceOf(await this._getSignerAddress());
|
|
680
|
+
if (eoaBalance < weiAmount) {
|
|
681
|
+
throw new AgetherError(
|
|
682
|
+
`Insufficient ${tokenInfo.symbol}. EOA has ${ethers.formatUnits(eoaBalance, tokenInfo.decimals)}, need ${amount}.`,
|
|
683
|
+
"INSUFFICIENT_BALANCE"
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
const tx = await tokenContract.transfer(acctAddr, weiAmount);
|
|
687
|
+
const receipt = await tx.wait();
|
|
688
|
+
this._refreshSigner();
|
|
689
|
+
return {
|
|
690
|
+
txHash: receipt.hash,
|
|
691
|
+
blockNumber: receipt.blockNumber,
|
|
692
|
+
status: receipt.status === 1 ? "success" : "failed",
|
|
693
|
+
gasUsed: receipt.gasUsed
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Transfer any ERC-20 token from AgentAccount to any address or agent.
|
|
698
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
699
|
+
*
|
|
700
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
701
|
+
* @param amount - Amount to send (e.g. '100' or 'all')
|
|
702
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
703
|
+
*/
|
|
704
|
+
async transferToken(tokenSymbol, amount, to) {
|
|
705
|
+
const acctAddr = await this.getAccountAddress();
|
|
706
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
707
|
+
const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
|
|
708
|
+
let toAddr;
|
|
709
|
+
if (to.address) {
|
|
710
|
+
toAddr = to.address;
|
|
711
|
+
} else if (to.agentId) {
|
|
712
|
+
toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
|
|
713
|
+
if (toAddr === ethers.ZeroAddress) {
|
|
714
|
+
throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
|
|
718
|
+
}
|
|
719
|
+
let weiAmount;
|
|
720
|
+
if (amount === "all") {
|
|
721
|
+
weiAmount = await tokenContract.balanceOf(acctAddr);
|
|
722
|
+
if (weiAmount === 0n) {
|
|
723
|
+
throw new AgetherError(`No ${tokenInfo.symbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
|
|
727
|
+
}
|
|
728
|
+
const data = erc20Iface.encodeFunctionData("transfer", [toAddr, weiAmount]);
|
|
729
|
+
const receipt = await this._exec(tokenInfo.address, data);
|
|
730
|
+
const actualAmount = amount === "all" ? ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
|
|
731
|
+
return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: toAddr };
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Transfer ETH from AgentAccount to any address or agent.
|
|
735
|
+
* Executes via Safe UserOp.
|
|
736
|
+
*
|
|
737
|
+
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
738
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
739
|
+
*/
|
|
740
|
+
async transferEth(amount, to) {
|
|
741
|
+
const acctAddr = await this.getAccountAddress();
|
|
742
|
+
let toAddr;
|
|
743
|
+
if (to.address) {
|
|
744
|
+
toAddr = to.address;
|
|
745
|
+
} else if (to.agentId) {
|
|
746
|
+
toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
|
|
747
|
+
if (toAddr === ethers.ZeroAddress) {
|
|
748
|
+
throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
|
|
749
|
+
}
|
|
750
|
+
} else {
|
|
751
|
+
throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
|
|
752
|
+
}
|
|
753
|
+
let weiAmount;
|
|
754
|
+
if (amount === "all") {
|
|
755
|
+
weiAmount = await this.signer.provider.getBalance(acctAddr);
|
|
756
|
+
if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
|
|
757
|
+
} else {
|
|
758
|
+
weiAmount = ethers.parseEther(amount);
|
|
759
|
+
}
|
|
760
|
+
const receipt = await this._exec(toAddr, "0x", weiAmount);
|
|
761
|
+
const actualAmount = amount === "all" ? ethers.formatEther(weiAmount) : amount;
|
|
762
|
+
return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: toAddr };
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Approve a spender to use ERC-20 tokens from the AgentAccount.
|
|
766
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
767
|
+
*
|
|
768
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
769
|
+
* @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
|
|
770
|
+
* @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
771
|
+
*/
|
|
772
|
+
async approveToken(tokenSymbol, amount, spender) {
|
|
773
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
774
|
+
let spenderAddr;
|
|
775
|
+
if (spender.address) {
|
|
776
|
+
spenderAddr = spender.address;
|
|
777
|
+
} else if (spender.agentId) {
|
|
778
|
+
spenderAddr = await this.agether4337Factory.getAccount(BigInt(spender.agentId));
|
|
779
|
+
if (spenderAddr === ethers.ZeroAddress) {
|
|
780
|
+
throw new AgetherError(`Agent ${spender.agentId} has no account`, "NO_ACCOUNT");
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
throw new AgetherError("Provide address or agentId as spender", "INVALID_TARGET");
|
|
784
|
+
}
|
|
785
|
+
let weiAmount;
|
|
786
|
+
if (amount === "max") {
|
|
787
|
+
weiAmount = ethers.MaxUint256;
|
|
788
|
+
} else {
|
|
789
|
+
weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
|
|
790
|
+
}
|
|
791
|
+
const data = erc20Iface.encodeFunctionData("approve", [spenderAddr, weiAmount]);
|
|
792
|
+
const receipt = await this._exec(tokenInfo.address, data);
|
|
793
|
+
const actualAmount = amount === "max" ? "unlimited" : amount;
|
|
794
|
+
return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, spender: spenderAddr };
|
|
795
|
+
}
|
|
796
|
+
// ════════════════════════════════════════════════════════
|
|
663
797
|
// Sponsorship
|
|
664
798
|
// ════════════════════════════════════════════════════════
|
|
665
799
|
/**
|
|
@@ -788,14 +922,6 @@ var AgetherClient = class _AgetherClient {
|
|
|
788
922
|
}
|
|
789
923
|
return this._eoaAddress;
|
|
790
924
|
}
|
|
791
|
-
/**
|
|
792
|
-
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
793
|
-
*
|
|
794
|
-
* Supports:
|
|
795
|
-
* - `'USDC'` → from chain config
|
|
796
|
-
* - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
|
|
797
|
-
* - `'0x...'` address → reads decimals and symbol onchain
|
|
798
|
-
*/
|
|
799
925
|
async _resolveToken(symbolOrAddress) {
|
|
800
926
|
if (symbolOrAddress.toUpperCase() === "USDC") {
|
|
801
927
|
return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
|
|
@@ -803,6 +929,9 @@ var AgetherClient = class _AgetherClient {
|
|
|
803
929
|
const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
|
|
804
930
|
const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
|
|
805
931
|
if (bySymbol) return bySymbol;
|
|
932
|
+
const cacheKey = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
|
|
933
|
+
const cached = this._dynamicTokenCache.get(cacheKey);
|
|
934
|
+
if (cached) return cached;
|
|
806
935
|
if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
|
|
807
936
|
try {
|
|
808
937
|
const token = new Contract(
|
|
@@ -811,7 +940,10 @@ var AgetherClient = class _AgetherClient {
|
|
|
811
940
|
this.signer.provider
|
|
812
941
|
);
|
|
813
942
|
const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
|
|
814
|
-
|
|
943
|
+
const info = { address: symbolOrAddress, symbol, decimals: Number(decimals) };
|
|
944
|
+
this._dynamicTokenCache.set(symbolOrAddress.toLowerCase(), info);
|
|
945
|
+
this._dynamicTokenCache.set(symbol.toUpperCase(), info);
|
|
946
|
+
return info;
|
|
815
947
|
} catch (e) {
|
|
816
948
|
throw new AgetherError(
|
|
817
949
|
`Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
|
|
@@ -819,8 +951,53 @@ var AgetherClient = class _AgetherClient {
|
|
|
819
951
|
);
|
|
820
952
|
}
|
|
821
953
|
}
|
|
954
|
+
try {
|
|
955
|
+
const chainId = this.config.chainId;
|
|
956
|
+
const query = `{
|
|
957
|
+
markets(
|
|
958
|
+
first: 20
|
|
959
|
+
where: { chainId_in: [${chainId}], search: "${symbolOrAddress}" }
|
|
960
|
+
) {
|
|
961
|
+
items {
|
|
962
|
+
loanAsset { address symbol decimals }
|
|
963
|
+
collateralAsset { address symbol decimals }
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}`;
|
|
967
|
+
const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
|
|
968
|
+
const items = resp.data?.data?.markets?.items ?? [];
|
|
969
|
+
const sym = symbolOrAddress.toUpperCase();
|
|
970
|
+
for (const m of items) {
|
|
971
|
+
if (m.loanAsset?.symbol?.toUpperCase() === sym) {
|
|
972
|
+
const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
|
|
973
|
+
this._dynamicTokenCache.set(sym, info);
|
|
974
|
+
this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
|
|
975
|
+
return info;
|
|
976
|
+
}
|
|
977
|
+
if (m.collateralAsset?.symbol?.toUpperCase() === sym) {
|
|
978
|
+
const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
|
|
979
|
+
this._dynamicTokenCache.set(sym, info);
|
|
980
|
+
this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
|
|
981
|
+
return info;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
for (const m of items) {
|
|
985
|
+
if (m.loanAsset?.symbol) {
|
|
986
|
+
const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
|
|
987
|
+
this._dynamicTokenCache.set(m.loanAsset.symbol.toUpperCase(), info);
|
|
988
|
+
this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
|
|
989
|
+
}
|
|
990
|
+
if (m.collateralAsset?.symbol) {
|
|
991
|
+
const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
|
|
992
|
+
this._dynamicTokenCache.set(m.collateralAsset.symbol.toUpperCase(), info);
|
|
993
|
+
this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
} catch (e) {
|
|
997
|
+
console.warn("[agether] Morpho token search failed:", e instanceof Error ? e.message : e);
|
|
998
|
+
}
|
|
822
999
|
throw new AgetherError(
|
|
823
|
-
`Unknown token: ${symbolOrAddress}.
|
|
1000
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
824
1001
|
"UNKNOWN_TOKEN"
|
|
825
1002
|
);
|
|
826
1003
|
}
|
|
@@ -954,8 +1131,8 @@ var AgetherClient = class _AgetherClient {
|
|
|
954
1131
|
|
|
955
1132
|
// src/clients/MorphoClient.ts
|
|
956
1133
|
import { ethers as ethers2, Contract as Contract2 } from "ethers";
|
|
957
|
-
import
|
|
958
|
-
var
|
|
1134
|
+
import axios2 from "axios";
|
|
1135
|
+
var MORPHO_API_URL2 = "https://api.morpho.org/graphql";
|
|
959
1136
|
var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
960
1137
|
var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
|
|
961
1138
|
var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
|
|
@@ -1101,7 +1278,7 @@ var MorphoClient = class {
|
|
|
1101
1278
|
}
|
|
1102
1279
|
}`;
|
|
1103
1280
|
try {
|
|
1104
|
-
const resp = await
|
|
1281
|
+
const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
|
|
1105
1282
|
const items = resp.data?.data?.markets?.items ?? [];
|
|
1106
1283
|
this._discoveredMarkets = items.map((m) => ({
|
|
1107
1284
|
uniqueKey: m.uniqueKey,
|
|
@@ -1186,7 +1363,7 @@ var MorphoClient = class {
|
|
|
1186
1363
|
const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
|
|
1187
1364
|
loanAddr = resolved.address.toLowerCase();
|
|
1188
1365
|
} catch {
|
|
1189
|
-
loanAddr =
|
|
1366
|
+
loanAddr = void 0;
|
|
1190
1367
|
}
|
|
1191
1368
|
}
|
|
1192
1369
|
}
|
|
@@ -1194,6 +1371,7 @@ var MorphoClient = class {
|
|
|
1194
1371
|
for (const m of this._discoveredMarkets ?? []) {
|
|
1195
1372
|
if (m.collateralAsset.address.toLowerCase() !== colAddr) continue;
|
|
1196
1373
|
if (loanAddr && m.loanAsset.address.toLowerCase() !== loanAddr) continue;
|
|
1374
|
+
if (!loanAddr && loanTokenSymbolOrAddress && m.loanAsset.symbol.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
|
|
1197
1375
|
return {
|
|
1198
1376
|
loanToken: m.loanAsset.address,
|
|
1199
1377
|
collateralToken: m.collateralAsset.address,
|
|
@@ -1204,9 +1382,22 @@ var MorphoClient = class {
|
|
|
1204
1382
|
}
|
|
1205
1383
|
if (!collateralSymbolOrAddress.startsWith("0x")) {
|
|
1206
1384
|
const searched = await this.searchMarkets(collateralSymbolOrAddress, { asCollateral: true });
|
|
1207
|
-
|
|
1385
|
+
const allResults = [...searched];
|
|
1386
|
+
if (loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
1387
|
+
const loanSearched = await this.searchMarkets(loanTokenSymbolOrAddress, { asLoanToken: true });
|
|
1388
|
+
const seen = new Set(allResults.map((r) => r.marketId));
|
|
1389
|
+
for (const m of loanSearched) {
|
|
1390
|
+
if (!seen.has(m.marketId)) allResults.push(m);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
for (const m of allResults) {
|
|
1394
|
+
if (colAddr.startsWith("0x") && m.collateralAddress.toLowerCase() !== colAddr) {
|
|
1395
|
+
if (m.collateralToken.toUpperCase() !== collateralSymbolOrAddress.toUpperCase()) continue;
|
|
1396
|
+
} else if (m.collateralToken.toUpperCase() !== collateralSymbolOrAddress.toUpperCase()) {
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1208
1399
|
if (loanAddr && m.loanAddress.toLowerCase() !== loanAddr) continue;
|
|
1209
|
-
if (
|
|
1400
|
+
if (!loanAddr && loanTokenSymbolOrAddress && m.loanToken.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
|
|
1210
1401
|
return this.getMarketParams(m.marketId);
|
|
1211
1402
|
}
|
|
1212
1403
|
}
|
|
@@ -1240,26 +1431,54 @@ var MorphoClient = class {
|
|
|
1240
1431
|
};
|
|
1241
1432
|
}
|
|
1242
1433
|
/**
|
|
1243
|
-
* Full status: positions across all
|
|
1434
|
+
* Full status: positions across all markets the user has interacted with.
|
|
1435
|
+
*
|
|
1436
|
+
* Uses Morpho GraphQL `marketPositions` to find ALL positions (not limited
|
|
1437
|
+
* to the top-500 markets), then reads onchain data for accurate debt.
|
|
1244
1438
|
*/
|
|
1245
1439
|
async getStatus() {
|
|
1246
1440
|
const acctAddr = await this.getAccountAddress();
|
|
1247
|
-
const
|
|
1441
|
+
const chainId = this.config.chainId;
|
|
1248
1442
|
const positions = [];
|
|
1249
1443
|
let totalDebtFloat = 0;
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1444
|
+
try {
|
|
1445
|
+
const posQuery = `{
|
|
1446
|
+
marketPositions(
|
|
1447
|
+
where: {
|
|
1448
|
+
userAddress_in: ["${acctAddr}"]
|
|
1449
|
+
chainId_in: [${chainId}]
|
|
1450
|
+
}
|
|
1451
|
+
first: 100
|
|
1452
|
+
) {
|
|
1453
|
+
items {
|
|
1454
|
+
supplyShares
|
|
1455
|
+
borrowShares
|
|
1456
|
+
collateral
|
|
1457
|
+
market {
|
|
1458
|
+
uniqueKey
|
|
1459
|
+
loanAsset { symbol address decimals }
|
|
1460
|
+
collateralAsset { symbol address decimals }
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}`;
|
|
1465
|
+
const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 15e3 });
|
|
1466
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
1467
|
+
for (const item of items) {
|
|
1468
|
+
const supplyShares = BigInt(item.supplyShares ?? "0");
|
|
1469
|
+
const borrowShares = BigInt(item.borrowShares ?? "0");
|
|
1470
|
+
const collateral = BigInt(item.collateral ?? "0");
|
|
1471
|
+
if (collateral === 0n && borrowShares === 0n && supplyShares === 0n) continue;
|
|
1472
|
+
const m = item.market;
|
|
1473
|
+
if (!m?.collateralAsset || !m?.loanAsset) continue;
|
|
1255
1474
|
const loanDecimals = m.loanAsset.decimals;
|
|
1256
1475
|
let debt = 0n;
|
|
1257
|
-
if (
|
|
1476
|
+
if (borrowShares > 0n) {
|
|
1258
1477
|
try {
|
|
1259
1478
|
const mkt = await this.morphoBlue.market(m.uniqueKey);
|
|
1260
1479
|
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1261
1480
|
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1262
|
-
debt = totalBorrowShares > 0n ? (
|
|
1481
|
+
debt = totalBorrowShares > 0n ? (borrowShares * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1263
1482
|
totalDebtFloat += parseFloat(ethers2.formatUnits(debt, loanDecimals));
|
|
1264
1483
|
} catch (e) {
|
|
1265
1484
|
console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
|
|
@@ -1269,14 +1488,46 @@ var MorphoClient = class {
|
|
|
1269
1488
|
marketId: m.uniqueKey,
|
|
1270
1489
|
collateralToken: m.collateralAsset.symbol,
|
|
1271
1490
|
loanToken: m.loanAsset.symbol,
|
|
1272
|
-
collateral: ethers2.formatUnits(
|
|
1273
|
-
borrowShares:
|
|
1274
|
-
supplyShares:
|
|
1491
|
+
collateral: ethers2.formatUnits(collateral, m.collateralAsset.decimals),
|
|
1492
|
+
borrowShares: borrowShares.toString(),
|
|
1493
|
+
supplyShares: supplyShares.toString(),
|
|
1275
1494
|
debt: ethers2.formatUnits(debt, loanDecimals)
|
|
1276
1495
|
});
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1496
|
+
}
|
|
1497
|
+
} catch (e) {
|
|
1498
|
+
console.warn("[agether] marketPositions API failed, falling back to market scan:", e instanceof Error ? e.message : e);
|
|
1499
|
+
const markets = await this.getMarkets();
|
|
1500
|
+
for (const m of markets) {
|
|
1501
|
+
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
1502
|
+
try {
|
|
1503
|
+
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1504
|
+
if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
|
|
1505
|
+
const loanDecimals = m.loanAsset.decimals;
|
|
1506
|
+
let debt = 0n;
|
|
1507
|
+
if (pos.borrowShares > 0n) {
|
|
1508
|
+
try {
|
|
1509
|
+
const mkt = await this.morphoBlue.market(m.uniqueKey);
|
|
1510
|
+
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1511
|
+
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1512
|
+
debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1513
|
+
totalDebtFloat += parseFloat(ethers2.formatUnits(debt, loanDecimals));
|
|
1514
|
+
} catch (e2) {
|
|
1515
|
+
console.warn(`[agether] debt calc failed:`, e2 instanceof Error ? e2.message : e2);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
positions.push({
|
|
1519
|
+
marketId: m.uniqueKey,
|
|
1520
|
+
collateralToken: m.collateralAsset.symbol,
|
|
1521
|
+
loanToken: m.loanAsset.symbol,
|
|
1522
|
+
collateral: ethers2.formatUnits(pos.collateral, m.collateralAsset.decimals),
|
|
1523
|
+
borrowShares: pos.borrowShares.toString(),
|
|
1524
|
+
supplyShares: pos.supplyShares.toString(),
|
|
1525
|
+
debt: ethers2.formatUnits(debt, loanDecimals)
|
|
1526
|
+
});
|
|
1527
|
+
} catch (e2) {
|
|
1528
|
+
console.warn(`[agether] position read failed:`, e2 instanceof Error ? e2.message : e2);
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1280
1531
|
}
|
|
1281
1532
|
}
|
|
1282
1533
|
return {
|
|
@@ -1430,7 +1681,7 @@ var MorphoClient = class {
|
|
|
1430
1681
|
}
|
|
1431
1682
|
}`;
|
|
1432
1683
|
try {
|
|
1433
|
-
const resp = await
|
|
1684
|
+
const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
|
|
1434
1685
|
let items = resp.data?.data?.markets?.items ?? [];
|
|
1435
1686
|
if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
|
|
1436
1687
|
const sym = collateralSymbolOrAddress.toUpperCase();
|
|
@@ -1496,7 +1747,7 @@ var MorphoClient = class {
|
|
|
1496
1747
|
}
|
|
1497
1748
|
}`;
|
|
1498
1749
|
try {
|
|
1499
|
-
const resp = await
|
|
1750
|
+
const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
|
|
1500
1751
|
let items = resp.data?.data?.markets?.items ?? [];
|
|
1501
1752
|
items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress);
|
|
1502
1753
|
const searchUpper = search.toUpperCase();
|
|
@@ -1889,7 +2140,7 @@ var MorphoClient = class {
|
|
|
1889
2140
|
}
|
|
1890
2141
|
}
|
|
1891
2142
|
}`;
|
|
1892
|
-
const posResp = await
|
|
2143
|
+
const posResp = await axios2.post(MORPHO_API_URL2, { query: positionsQuery }, { timeout: 15e3 });
|
|
1893
2144
|
const user = posResp.data?.data?.userByAddress;
|
|
1894
2145
|
if (!user?.marketPositions) return [];
|
|
1895
2146
|
const activePositions = user.marketPositions.filter(
|
|
@@ -2563,6 +2814,46 @@ var MorphoClient = class {
|
|
|
2563
2814
|
/** Find the first market where the agent has collateral deposited. */
|
|
2564
2815
|
async _findActiveMarket() {
|
|
2565
2816
|
const acctAddr = await this.getAccountAddress();
|
|
2817
|
+
const chainId = this.config.chainId;
|
|
2818
|
+
try {
|
|
2819
|
+
const posQuery = `{
|
|
2820
|
+
marketPositions(
|
|
2821
|
+
where: { userAddress_in: ["${acctAddr}"], chainId_in: [${chainId}] }
|
|
2822
|
+
first: 50
|
|
2823
|
+
) {
|
|
2824
|
+
items {
|
|
2825
|
+
collateral
|
|
2826
|
+
market {
|
|
2827
|
+
uniqueKey
|
|
2828
|
+
oracleAddress
|
|
2829
|
+
irmAddress
|
|
2830
|
+
lltv
|
|
2831
|
+
loanAsset { address symbol decimals }
|
|
2832
|
+
collateralAsset { address symbol decimals }
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}`;
|
|
2837
|
+
const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
|
|
2838
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
2839
|
+
for (const item of items) {
|
|
2840
|
+
if (BigInt(item.collateral ?? "0") > 0n && item.market?.collateralAsset) {
|
|
2841
|
+
const m = item.market;
|
|
2842
|
+
return {
|
|
2843
|
+
params: {
|
|
2844
|
+
loanToken: m.loanAsset.address,
|
|
2845
|
+
collateralToken: m.collateralAsset.address,
|
|
2846
|
+
oracle: m.oracleAddress,
|
|
2847
|
+
irm: m.irmAddress,
|
|
2848
|
+
lltv: BigInt(m.lltv)
|
|
2849
|
+
},
|
|
2850
|
+
symbol: m.collateralAsset.symbol
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
} catch (e) {
|
|
2855
|
+
console.warn("[agether] _findActiveMarket GraphQL failed, falling back:", e instanceof Error ? e.message : e);
|
|
2856
|
+
}
|
|
2566
2857
|
const markets = await this.getMarkets();
|
|
2567
2858
|
for (const m of markets) {
|
|
2568
2859
|
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
@@ -2591,6 +2882,46 @@ var MorphoClient = class {
|
|
|
2591
2882
|
/** Find the first market where the agent has a supply (lending) position. */
|
|
2592
2883
|
async _findActiveSupplyMarket() {
|
|
2593
2884
|
const acctAddr = await this.getAccountAddress();
|
|
2885
|
+
const chainId = this.config.chainId;
|
|
2886
|
+
try {
|
|
2887
|
+
const posQuery = `{
|
|
2888
|
+
marketPositions(
|
|
2889
|
+
where: { userAddress_in: ["${acctAddr}"], chainId_in: [${chainId}] }
|
|
2890
|
+
first: 50
|
|
2891
|
+
) {
|
|
2892
|
+
items {
|
|
2893
|
+
supplyShares
|
|
2894
|
+
market {
|
|
2895
|
+
uniqueKey
|
|
2896
|
+
oracleAddress
|
|
2897
|
+
irmAddress
|
|
2898
|
+
lltv
|
|
2899
|
+
loanAsset { address symbol decimals }
|
|
2900
|
+
collateralAsset { address symbol decimals }
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
}`;
|
|
2905
|
+
const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
|
|
2906
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
2907
|
+
for (const item of items) {
|
|
2908
|
+
if (BigInt(item.supplyShares ?? "0") > 0n && item.market?.collateralAsset) {
|
|
2909
|
+
const m = item.market;
|
|
2910
|
+
return {
|
|
2911
|
+
params: {
|
|
2912
|
+
loanToken: m.loanAsset.address,
|
|
2913
|
+
collateralToken: m.collateralAsset.address,
|
|
2914
|
+
oracle: m.oracleAddress,
|
|
2915
|
+
irm: m.irmAddress,
|
|
2916
|
+
lltv: BigInt(m.lltv)
|
|
2917
|
+
},
|
|
2918
|
+
symbol: m.collateralAsset.symbol
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
} catch (e) {
|
|
2923
|
+
console.warn("[agether] _findActiveSupplyMarket GraphQL failed, falling back:", e instanceof Error ? e.message : e);
|
|
2924
|
+
}
|
|
2594
2925
|
const markets = await this.getMarkets();
|
|
2595
2926
|
for (const m of markets) {
|
|
2596
2927
|
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
@@ -2723,7 +3054,7 @@ var MorphoClient = class {
|
|
|
2723
3054
|
}
|
|
2724
3055
|
}
|
|
2725
3056
|
}`;
|
|
2726
|
-
const resp = await
|
|
3057
|
+
const resp = await axios2.post(MORPHO_API_URL2, { query: txQuery }, { timeout: 15e3 });
|
|
2727
3058
|
const txData = resp.data?.data?.transactions;
|
|
2728
3059
|
if (!txData?.items) break;
|
|
2729
3060
|
for (const tx of txData.items) {
|
|
@@ -2748,7 +3079,7 @@ var MorphoClient = class {
|
|
|
2748
3079
|
};
|
|
2749
3080
|
|
|
2750
3081
|
// src/clients/ScoringClient.ts
|
|
2751
|
-
import
|
|
3082
|
+
import axios3 from "axios";
|
|
2752
3083
|
|
|
2753
3084
|
// src/clients/X402Client.ts
|
|
2754
3085
|
import { wrapFetchWithPayment } from "@x402/fetch";
|
|
@@ -3110,7 +3441,7 @@ var ScoringClient = class {
|
|
|
3110
3441
|
constructor(config) {
|
|
3111
3442
|
this.endpoint = config.endpoint;
|
|
3112
3443
|
this.defaultChainId = config.chainId;
|
|
3113
|
-
this.client =
|
|
3444
|
+
this.client = axios3.create({
|
|
3114
3445
|
baseURL: config.endpoint,
|
|
3115
3446
|
headers: { "Content-Type": "application/json" },
|
|
3116
3447
|
timeout: 3e4
|