@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/cli.js
CHANGED
|
@@ -514,7 +514,7 @@ var init_MorphoClient = __esm({
|
|
|
514
514
|
const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
|
|
515
515
|
loanAddr = resolved.address.toLowerCase();
|
|
516
516
|
} catch {
|
|
517
|
-
loanAddr =
|
|
517
|
+
loanAddr = void 0;
|
|
518
518
|
}
|
|
519
519
|
}
|
|
520
520
|
}
|
|
@@ -522,6 +522,7 @@ var init_MorphoClient = __esm({
|
|
|
522
522
|
for (const m of this._discoveredMarkets ?? []) {
|
|
523
523
|
if (m.collateralAsset.address.toLowerCase() !== colAddr) continue;
|
|
524
524
|
if (loanAddr && m.loanAsset.address.toLowerCase() !== loanAddr) continue;
|
|
525
|
+
if (!loanAddr && loanTokenSymbolOrAddress && m.loanAsset.symbol.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
|
|
525
526
|
return {
|
|
526
527
|
loanToken: m.loanAsset.address,
|
|
527
528
|
collateralToken: m.collateralAsset.address,
|
|
@@ -532,9 +533,22 @@ var init_MorphoClient = __esm({
|
|
|
532
533
|
}
|
|
533
534
|
if (!collateralSymbolOrAddress.startsWith("0x")) {
|
|
534
535
|
const searched = await this.searchMarkets(collateralSymbolOrAddress, { asCollateral: true });
|
|
535
|
-
|
|
536
|
+
const allResults = [...searched];
|
|
537
|
+
if (loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
538
|
+
const loanSearched = await this.searchMarkets(loanTokenSymbolOrAddress, { asLoanToken: true });
|
|
539
|
+
const seen = new Set(allResults.map((r) => r.marketId));
|
|
540
|
+
for (const m of loanSearched) {
|
|
541
|
+
if (!seen.has(m.marketId)) allResults.push(m);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
for (const m of allResults) {
|
|
545
|
+
if (colAddr.startsWith("0x") && m.collateralAddress.toLowerCase() !== colAddr) {
|
|
546
|
+
if (m.collateralToken.toUpperCase() !== collateralSymbolOrAddress.toUpperCase()) continue;
|
|
547
|
+
} else if (m.collateralToken.toUpperCase() !== collateralSymbolOrAddress.toUpperCase()) {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
536
550
|
if (loanAddr && m.loanAddress.toLowerCase() !== loanAddr) continue;
|
|
537
|
-
if (
|
|
551
|
+
if (!loanAddr && loanTokenSymbolOrAddress && m.loanToken.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
|
|
538
552
|
return this.getMarketParams(m.marketId);
|
|
539
553
|
}
|
|
540
554
|
}
|
|
@@ -568,26 +582,54 @@ var init_MorphoClient = __esm({
|
|
|
568
582
|
};
|
|
569
583
|
}
|
|
570
584
|
/**
|
|
571
|
-
* Full status: positions across all
|
|
585
|
+
* Full status: positions across all markets the user has interacted with.
|
|
586
|
+
*
|
|
587
|
+
* Uses Morpho GraphQL `marketPositions` to find ALL positions (not limited
|
|
588
|
+
* to the top-500 markets), then reads onchain data for accurate debt.
|
|
572
589
|
*/
|
|
573
590
|
async getStatus() {
|
|
574
591
|
const acctAddr = await this.getAccountAddress();
|
|
575
|
-
const
|
|
592
|
+
const chainId = this.config.chainId;
|
|
576
593
|
const positions = [];
|
|
577
594
|
let totalDebtFloat = 0;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
595
|
+
try {
|
|
596
|
+
const posQuery = `{
|
|
597
|
+
marketPositions(
|
|
598
|
+
where: {
|
|
599
|
+
userAddress_in: ["${acctAddr}"]
|
|
600
|
+
chainId_in: [${chainId}]
|
|
601
|
+
}
|
|
602
|
+
first: 100
|
|
603
|
+
) {
|
|
604
|
+
items {
|
|
605
|
+
supplyShares
|
|
606
|
+
borrowShares
|
|
607
|
+
collateral
|
|
608
|
+
market {
|
|
609
|
+
uniqueKey
|
|
610
|
+
loanAsset { symbol address decimals }
|
|
611
|
+
collateralAsset { symbol address decimals }
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}`;
|
|
616
|
+
const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 15e3 });
|
|
617
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
618
|
+
for (const item of items) {
|
|
619
|
+
const supplyShares = BigInt(item.supplyShares ?? "0");
|
|
620
|
+
const borrowShares = BigInt(item.borrowShares ?? "0");
|
|
621
|
+
const collateral = BigInt(item.collateral ?? "0");
|
|
622
|
+
if (collateral === 0n && borrowShares === 0n && supplyShares === 0n) continue;
|
|
623
|
+
const m = item.market;
|
|
624
|
+
if (!m?.collateralAsset || !m?.loanAsset) continue;
|
|
583
625
|
const loanDecimals = m.loanAsset.decimals;
|
|
584
626
|
let debt = 0n;
|
|
585
|
-
if (
|
|
627
|
+
if (borrowShares > 0n) {
|
|
586
628
|
try {
|
|
587
629
|
const mkt = await this.morphoBlue.market(m.uniqueKey);
|
|
588
630
|
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
589
631
|
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
590
|
-
debt = totalBorrowShares > 0n ? (
|
|
632
|
+
debt = totalBorrowShares > 0n ? (borrowShares * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
591
633
|
totalDebtFloat += parseFloat(import_ethers.ethers.formatUnits(debt, loanDecimals));
|
|
592
634
|
} catch (e) {
|
|
593
635
|
console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
|
|
@@ -597,14 +639,46 @@ var init_MorphoClient = __esm({
|
|
|
597
639
|
marketId: m.uniqueKey,
|
|
598
640
|
collateralToken: m.collateralAsset.symbol,
|
|
599
641
|
loanToken: m.loanAsset.symbol,
|
|
600
|
-
collateral: import_ethers.ethers.formatUnits(
|
|
601
|
-
borrowShares:
|
|
602
|
-
supplyShares:
|
|
642
|
+
collateral: import_ethers.ethers.formatUnits(collateral, m.collateralAsset.decimals),
|
|
643
|
+
borrowShares: borrowShares.toString(),
|
|
644
|
+
supplyShares: supplyShares.toString(),
|
|
603
645
|
debt: import_ethers.ethers.formatUnits(debt, loanDecimals)
|
|
604
646
|
});
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
647
|
+
}
|
|
648
|
+
} catch (e) {
|
|
649
|
+
console.warn("[agether] marketPositions API failed, falling back to market scan:", e instanceof Error ? e.message : e);
|
|
650
|
+
const markets = await this.getMarkets();
|
|
651
|
+
for (const m of markets) {
|
|
652
|
+
if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
|
|
653
|
+
try {
|
|
654
|
+
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
655
|
+
if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
|
|
656
|
+
const loanDecimals = m.loanAsset.decimals;
|
|
657
|
+
let debt = 0n;
|
|
658
|
+
if (pos.borrowShares > 0n) {
|
|
659
|
+
try {
|
|
660
|
+
const mkt = await this.morphoBlue.market(m.uniqueKey);
|
|
661
|
+
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
662
|
+
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
663
|
+
debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
664
|
+
totalDebtFloat += parseFloat(import_ethers.ethers.formatUnits(debt, loanDecimals));
|
|
665
|
+
} catch (e2) {
|
|
666
|
+
console.warn(`[agether] debt calc failed:`, e2 instanceof Error ? e2.message : e2);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
positions.push({
|
|
670
|
+
marketId: m.uniqueKey,
|
|
671
|
+
collateralToken: m.collateralAsset.symbol,
|
|
672
|
+
loanToken: m.loanAsset.symbol,
|
|
673
|
+
collateral: import_ethers.ethers.formatUnits(pos.collateral, m.collateralAsset.decimals),
|
|
674
|
+
borrowShares: pos.borrowShares.toString(),
|
|
675
|
+
supplyShares: pos.supplyShares.toString(),
|
|
676
|
+
debt: import_ethers.ethers.formatUnits(debt, loanDecimals)
|
|
677
|
+
});
|
|
678
|
+
} catch (e2) {
|
|
679
|
+
console.warn(`[agether] position read failed:`, e2 instanceof Error ? e2.message : e2);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
608
682
|
}
|
|
609
683
|
}
|
|
610
684
|
return {
|
|
@@ -1891,6 +1965,46 @@ var init_MorphoClient = __esm({
|
|
|
1891
1965
|
/** Find the first market where the agent has collateral deposited. */
|
|
1892
1966
|
async _findActiveMarket() {
|
|
1893
1967
|
const acctAddr = await this.getAccountAddress();
|
|
1968
|
+
const chainId = this.config.chainId;
|
|
1969
|
+
try {
|
|
1970
|
+
const posQuery = `{
|
|
1971
|
+
marketPositions(
|
|
1972
|
+
where: { userAddress_in: ["${acctAddr}"], chainId_in: [${chainId}] }
|
|
1973
|
+
first: 50
|
|
1974
|
+
) {
|
|
1975
|
+
items {
|
|
1976
|
+
collateral
|
|
1977
|
+
market {
|
|
1978
|
+
uniqueKey
|
|
1979
|
+
oracleAddress
|
|
1980
|
+
irmAddress
|
|
1981
|
+
lltv
|
|
1982
|
+
loanAsset { address symbol decimals }
|
|
1983
|
+
collateralAsset { address symbol decimals }
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}`;
|
|
1988
|
+
const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
|
|
1989
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
1990
|
+
for (const item of items) {
|
|
1991
|
+
if (BigInt(item.collateral ?? "0") > 0n && item.market?.collateralAsset) {
|
|
1992
|
+
const m = item.market;
|
|
1993
|
+
return {
|
|
1994
|
+
params: {
|
|
1995
|
+
loanToken: m.loanAsset.address,
|
|
1996
|
+
collateralToken: m.collateralAsset.address,
|
|
1997
|
+
oracle: m.oracleAddress,
|
|
1998
|
+
irm: m.irmAddress,
|
|
1999
|
+
lltv: BigInt(m.lltv)
|
|
2000
|
+
},
|
|
2001
|
+
symbol: m.collateralAsset.symbol
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
} catch (e) {
|
|
2006
|
+
console.warn("[agether] _findActiveMarket GraphQL failed, falling back:", e instanceof Error ? e.message : e);
|
|
2007
|
+
}
|
|
1894
2008
|
const markets = await this.getMarkets();
|
|
1895
2009
|
for (const m of markets) {
|
|
1896
2010
|
if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
|
|
@@ -1919,6 +2033,46 @@ var init_MorphoClient = __esm({
|
|
|
1919
2033
|
/** Find the first market where the agent has a supply (lending) position. */
|
|
1920
2034
|
async _findActiveSupplyMarket() {
|
|
1921
2035
|
const acctAddr = await this.getAccountAddress();
|
|
2036
|
+
const chainId = this.config.chainId;
|
|
2037
|
+
try {
|
|
2038
|
+
const posQuery = `{
|
|
2039
|
+
marketPositions(
|
|
2040
|
+
where: { userAddress_in: ["${acctAddr}"], chainId_in: [${chainId}] }
|
|
2041
|
+
first: 50
|
|
2042
|
+
) {
|
|
2043
|
+
items {
|
|
2044
|
+
supplyShares
|
|
2045
|
+
market {
|
|
2046
|
+
uniqueKey
|
|
2047
|
+
oracleAddress
|
|
2048
|
+
irmAddress
|
|
2049
|
+
lltv
|
|
2050
|
+
loanAsset { address symbol decimals }
|
|
2051
|
+
collateralAsset { address symbol decimals }
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}`;
|
|
2056
|
+
const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
|
|
2057
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
2058
|
+
for (const item of items) {
|
|
2059
|
+
if (BigInt(item.supplyShares ?? "0") > 0n && item.market?.collateralAsset) {
|
|
2060
|
+
const m = item.market;
|
|
2061
|
+
return {
|
|
2062
|
+
params: {
|
|
2063
|
+
loanToken: m.loanAsset.address,
|
|
2064
|
+
collateralToken: m.collateralAsset.address,
|
|
2065
|
+
oracle: m.oracleAddress,
|
|
2066
|
+
irm: m.irmAddress,
|
|
2067
|
+
lltv: BigInt(m.lltv)
|
|
2068
|
+
},
|
|
2069
|
+
symbol: m.collateralAsset.symbol
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
} catch (e) {
|
|
2074
|
+
console.warn("[agether] _findActiveSupplyMarket GraphQL failed, falling back:", e instanceof Error ? e.message : e);
|
|
2075
|
+
}
|
|
1922
2076
|
const markets = await this.getMarkets();
|
|
1923
2077
|
for (const m of markets) {
|
|
1924
2078
|
if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
|
|
@@ -2082,16 +2236,18 @@ var AgetherClient_exports = {};
|
|
|
2082
2236
|
__export(AgetherClient_exports, {
|
|
2083
2237
|
AgetherClient: () => AgetherClient
|
|
2084
2238
|
});
|
|
2085
|
-
var import_ethers2, MODE_SINGLE2, erc20Iface2, KNOWN_TOKENS, AgetherClient;
|
|
2239
|
+
var import_ethers2, import_axios2, MODE_SINGLE2, erc20Iface2, MORPHO_API_URL2, KNOWN_TOKENS, AgetherClient;
|
|
2086
2240
|
var init_AgetherClient = __esm({
|
|
2087
2241
|
"src/clients/AgetherClient.ts"() {
|
|
2088
2242
|
"use strict";
|
|
2089
2243
|
import_ethers2 = require("ethers");
|
|
2244
|
+
import_axios2 = __toESM(require("axios"));
|
|
2090
2245
|
init_types();
|
|
2091
2246
|
init_abis();
|
|
2092
2247
|
init_config();
|
|
2093
2248
|
MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
2094
2249
|
erc20Iface2 = new import_ethers2.ethers.Interface(ERC20_ABI);
|
|
2250
|
+
MORPHO_API_URL2 = "https://api.morpho.org/graphql";
|
|
2095
2251
|
KNOWN_TOKENS = {
|
|
2096
2252
|
[8453 /* Base */]: {
|
|
2097
2253
|
WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
|
|
@@ -2106,6 +2262,17 @@ var init_AgetherClient = __esm({
|
|
|
2106
2262
|
};
|
|
2107
2263
|
AgetherClient = class _AgetherClient {
|
|
2108
2264
|
constructor(options) {
|
|
2265
|
+
/**
|
|
2266
|
+
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
2267
|
+
*
|
|
2268
|
+
* Resolution order:
|
|
2269
|
+
* 1. `'USDC'` → from chain config (instant)
|
|
2270
|
+
* 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
|
|
2271
|
+
* 3. Dynamic cache populated by previous Morpho API lookups (instant)
|
|
2272
|
+
* 4. `'0x...'` address → reads decimals and symbol onchain
|
|
2273
|
+
* 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
|
|
2274
|
+
*/
|
|
2275
|
+
this._dynamicTokenCache = /* @__PURE__ */ new Map();
|
|
2109
2276
|
this.config = options.config;
|
|
2110
2277
|
this.signer = options.signer;
|
|
2111
2278
|
this.agentId = options.agentId;
|
|
@@ -2390,21 +2557,10 @@ var init_AgetherClient = __esm({
|
|
|
2390
2557
|
}
|
|
2391
2558
|
/**
|
|
2392
2559
|
* Fund the Safe account with USDC from EOA.
|
|
2393
|
-
*
|
|
2560
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
2394
2561
|
*/
|
|
2395
2562
|
async fundAccount(usdcAmount) {
|
|
2396
|
-
|
|
2397
|
-
const usdc = new import_ethers2.Contract(this.config.contracts.usdc, ERC20_ABI, this.signer);
|
|
2398
|
-
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
2399
|
-
const tx = await usdc.transfer(acctAddr, amount);
|
|
2400
|
-
const receipt = await tx.wait();
|
|
2401
|
-
this._refreshSigner();
|
|
2402
|
-
return {
|
|
2403
|
-
txHash: receipt.hash,
|
|
2404
|
-
blockNumber: receipt.blockNumber,
|
|
2405
|
-
status: receipt.status === 1 ? "success" : "failed",
|
|
2406
|
-
gasUsed: receipt.gasUsed
|
|
2407
|
-
};
|
|
2563
|
+
return this.fundAccountToken("USDC", usdcAmount);
|
|
2408
2564
|
}
|
|
2409
2565
|
// ════════════════════════════════════════════════════════
|
|
2410
2566
|
// Withdrawals (Safe → EOA via UserOps)
|
|
@@ -2456,6 +2612,138 @@ var init_AgetherClient = __esm({
|
|
|
2456
2612
|
return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
|
|
2457
2613
|
}
|
|
2458
2614
|
// ════════════════════════════════════════════════════════
|
|
2615
|
+
// Token Transfers & Approvals (AgentAccount → any address)
|
|
2616
|
+
// ════════════════════════════════════════════════════════
|
|
2617
|
+
/**
|
|
2618
|
+
* Fund the AgentAccount with any ERC-20 token from EOA.
|
|
2619
|
+
* Simple ERC-20 transfer (does NOT require a UserOp).
|
|
2620
|
+
*
|
|
2621
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
2622
|
+
* @param amount - Amount to send (human-readable, e.g. '100')
|
|
2623
|
+
*/
|
|
2624
|
+
async fundAccountToken(tokenSymbol, amount) {
|
|
2625
|
+
const acctAddr = await this.getAccountAddress();
|
|
2626
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
2627
|
+
const tokenContract = new import_ethers2.Contract(tokenInfo.address, ERC20_ABI, this.signer);
|
|
2628
|
+
const weiAmount = import_ethers2.ethers.parseUnits(amount, tokenInfo.decimals);
|
|
2629
|
+
const eoaBalance = await tokenContract.balanceOf(await this._getSignerAddress());
|
|
2630
|
+
if (eoaBalance < weiAmount) {
|
|
2631
|
+
throw new AgetherError(
|
|
2632
|
+
`Insufficient ${tokenInfo.symbol}. EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, tokenInfo.decimals)}, need ${amount}.`,
|
|
2633
|
+
"INSUFFICIENT_BALANCE"
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
const tx = await tokenContract.transfer(acctAddr, weiAmount);
|
|
2637
|
+
const receipt = await tx.wait();
|
|
2638
|
+
this._refreshSigner();
|
|
2639
|
+
return {
|
|
2640
|
+
txHash: receipt.hash,
|
|
2641
|
+
blockNumber: receipt.blockNumber,
|
|
2642
|
+
status: receipt.status === 1 ? "success" : "failed",
|
|
2643
|
+
gasUsed: receipt.gasUsed
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Transfer any ERC-20 token from AgentAccount to any address or agent.
|
|
2648
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
2649
|
+
*
|
|
2650
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
2651
|
+
* @param amount - Amount to send (e.g. '100' or 'all')
|
|
2652
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
2653
|
+
*/
|
|
2654
|
+
async transferToken(tokenSymbol, amount, to) {
|
|
2655
|
+
const acctAddr = await this.getAccountAddress();
|
|
2656
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
2657
|
+
const tokenContract = new import_ethers2.Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
|
|
2658
|
+
let toAddr;
|
|
2659
|
+
if (to.address) {
|
|
2660
|
+
toAddr = to.address;
|
|
2661
|
+
} else if (to.agentId) {
|
|
2662
|
+
toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
|
|
2663
|
+
if (toAddr === import_ethers2.ethers.ZeroAddress) {
|
|
2664
|
+
throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
|
|
2665
|
+
}
|
|
2666
|
+
} else {
|
|
2667
|
+
throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
|
|
2668
|
+
}
|
|
2669
|
+
let weiAmount;
|
|
2670
|
+
if (amount === "all") {
|
|
2671
|
+
weiAmount = await tokenContract.balanceOf(acctAddr);
|
|
2672
|
+
if (weiAmount === 0n) {
|
|
2673
|
+
throw new AgetherError(`No ${tokenInfo.symbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
|
|
2674
|
+
}
|
|
2675
|
+
} else {
|
|
2676
|
+
weiAmount = import_ethers2.ethers.parseUnits(amount, tokenInfo.decimals);
|
|
2677
|
+
}
|
|
2678
|
+
const data = erc20Iface2.encodeFunctionData("transfer", [toAddr, weiAmount]);
|
|
2679
|
+
const receipt = await this._exec(tokenInfo.address, data);
|
|
2680
|
+
const actualAmount = amount === "all" ? import_ethers2.ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
|
|
2681
|
+
return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: toAddr };
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* Transfer ETH from AgentAccount to any address or agent.
|
|
2685
|
+
* Executes via Safe UserOp.
|
|
2686
|
+
*
|
|
2687
|
+
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
2688
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
2689
|
+
*/
|
|
2690
|
+
async transferEth(amount, to) {
|
|
2691
|
+
const acctAddr = await this.getAccountAddress();
|
|
2692
|
+
let toAddr;
|
|
2693
|
+
if (to.address) {
|
|
2694
|
+
toAddr = to.address;
|
|
2695
|
+
} else if (to.agentId) {
|
|
2696
|
+
toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
|
|
2697
|
+
if (toAddr === import_ethers2.ethers.ZeroAddress) {
|
|
2698
|
+
throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
|
|
2699
|
+
}
|
|
2700
|
+
} else {
|
|
2701
|
+
throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
|
|
2702
|
+
}
|
|
2703
|
+
let weiAmount;
|
|
2704
|
+
if (amount === "all") {
|
|
2705
|
+
weiAmount = await this.signer.provider.getBalance(acctAddr);
|
|
2706
|
+
if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
|
|
2707
|
+
} else {
|
|
2708
|
+
weiAmount = import_ethers2.ethers.parseEther(amount);
|
|
2709
|
+
}
|
|
2710
|
+
const receipt = await this._exec(toAddr, "0x", weiAmount);
|
|
2711
|
+
const actualAmount = amount === "all" ? import_ethers2.ethers.formatEther(weiAmount) : amount;
|
|
2712
|
+
return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: toAddr };
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Approve a spender to use ERC-20 tokens from the AgentAccount.
|
|
2716
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
2717
|
+
*
|
|
2718
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
2719
|
+
* @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
|
|
2720
|
+
* @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
2721
|
+
*/
|
|
2722
|
+
async approveToken(tokenSymbol, amount, spender) {
|
|
2723
|
+
const tokenInfo = await this._resolveToken(tokenSymbol);
|
|
2724
|
+
let spenderAddr;
|
|
2725
|
+
if (spender.address) {
|
|
2726
|
+
spenderAddr = spender.address;
|
|
2727
|
+
} else if (spender.agentId) {
|
|
2728
|
+
spenderAddr = await this.agether4337Factory.getAccount(BigInt(spender.agentId));
|
|
2729
|
+
if (spenderAddr === import_ethers2.ethers.ZeroAddress) {
|
|
2730
|
+
throw new AgetherError(`Agent ${spender.agentId} has no account`, "NO_ACCOUNT");
|
|
2731
|
+
}
|
|
2732
|
+
} else {
|
|
2733
|
+
throw new AgetherError("Provide address or agentId as spender", "INVALID_TARGET");
|
|
2734
|
+
}
|
|
2735
|
+
let weiAmount;
|
|
2736
|
+
if (amount === "max") {
|
|
2737
|
+
weiAmount = import_ethers2.ethers.MaxUint256;
|
|
2738
|
+
} else {
|
|
2739
|
+
weiAmount = import_ethers2.ethers.parseUnits(amount, tokenInfo.decimals);
|
|
2740
|
+
}
|
|
2741
|
+
const data = erc20Iface2.encodeFunctionData("approve", [spenderAddr, weiAmount]);
|
|
2742
|
+
const receipt = await this._exec(tokenInfo.address, data);
|
|
2743
|
+
const actualAmount = amount === "max" ? "unlimited" : amount;
|
|
2744
|
+
return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, spender: spenderAddr };
|
|
2745
|
+
}
|
|
2746
|
+
// ════════════════════════════════════════════════════════
|
|
2459
2747
|
// Sponsorship
|
|
2460
2748
|
// ════════════════════════════════════════════════════════
|
|
2461
2749
|
/**
|
|
@@ -2584,14 +2872,6 @@ var init_AgetherClient = __esm({
|
|
|
2584
2872
|
}
|
|
2585
2873
|
return this._eoaAddress;
|
|
2586
2874
|
}
|
|
2587
|
-
/**
|
|
2588
|
-
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
2589
|
-
*
|
|
2590
|
-
* Supports:
|
|
2591
|
-
* - `'USDC'` → from chain config
|
|
2592
|
-
* - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
|
|
2593
|
-
* - `'0x...'` address → reads decimals and symbol onchain
|
|
2594
|
-
*/
|
|
2595
2875
|
async _resolveToken(symbolOrAddress) {
|
|
2596
2876
|
if (symbolOrAddress.toUpperCase() === "USDC") {
|
|
2597
2877
|
return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
|
|
@@ -2599,6 +2879,9 @@ var init_AgetherClient = __esm({
|
|
|
2599
2879
|
const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
|
|
2600
2880
|
const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
|
|
2601
2881
|
if (bySymbol) return bySymbol;
|
|
2882
|
+
const cacheKey = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
|
|
2883
|
+
const cached = this._dynamicTokenCache.get(cacheKey);
|
|
2884
|
+
if (cached) return cached;
|
|
2602
2885
|
if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
|
|
2603
2886
|
try {
|
|
2604
2887
|
const token = new import_ethers2.Contract(
|
|
@@ -2607,7 +2890,10 @@ var init_AgetherClient = __esm({
|
|
|
2607
2890
|
this.signer.provider
|
|
2608
2891
|
);
|
|
2609
2892
|
const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
|
|
2610
|
-
|
|
2893
|
+
const info = { address: symbolOrAddress, symbol, decimals: Number(decimals) };
|
|
2894
|
+
this._dynamicTokenCache.set(symbolOrAddress.toLowerCase(), info);
|
|
2895
|
+
this._dynamicTokenCache.set(symbol.toUpperCase(), info);
|
|
2896
|
+
return info;
|
|
2611
2897
|
} catch (e) {
|
|
2612
2898
|
throw new AgetherError(
|
|
2613
2899
|
`Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
|
|
@@ -2615,8 +2901,53 @@ var init_AgetherClient = __esm({
|
|
|
2615
2901
|
);
|
|
2616
2902
|
}
|
|
2617
2903
|
}
|
|
2904
|
+
try {
|
|
2905
|
+
const chainId = this.config.chainId;
|
|
2906
|
+
const query = `{
|
|
2907
|
+
markets(
|
|
2908
|
+
first: 20
|
|
2909
|
+
where: { chainId_in: [${chainId}], search: "${symbolOrAddress}" }
|
|
2910
|
+
) {
|
|
2911
|
+
items {
|
|
2912
|
+
loanAsset { address symbol decimals }
|
|
2913
|
+
collateralAsset { address symbol decimals }
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
}`;
|
|
2917
|
+
const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
|
|
2918
|
+
const items = resp.data?.data?.markets?.items ?? [];
|
|
2919
|
+
const sym = symbolOrAddress.toUpperCase();
|
|
2920
|
+
for (const m of items) {
|
|
2921
|
+
if (m.loanAsset?.symbol?.toUpperCase() === sym) {
|
|
2922
|
+
const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
|
|
2923
|
+
this._dynamicTokenCache.set(sym, info);
|
|
2924
|
+
this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
|
|
2925
|
+
return info;
|
|
2926
|
+
}
|
|
2927
|
+
if (m.collateralAsset?.symbol?.toUpperCase() === sym) {
|
|
2928
|
+
const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
|
|
2929
|
+
this._dynamicTokenCache.set(sym, info);
|
|
2930
|
+
this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
|
|
2931
|
+
return info;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
for (const m of items) {
|
|
2935
|
+
if (m.loanAsset?.symbol) {
|
|
2936
|
+
const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
|
|
2937
|
+
this._dynamicTokenCache.set(m.loanAsset.symbol.toUpperCase(), info);
|
|
2938
|
+
this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
|
|
2939
|
+
}
|
|
2940
|
+
if (m.collateralAsset?.symbol) {
|
|
2941
|
+
const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
|
|
2942
|
+
this._dynamicTokenCache.set(m.collateralAsset.symbol.toUpperCase(), info);
|
|
2943
|
+
this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
} catch (e) {
|
|
2947
|
+
console.warn("[agether] Morpho token search failed:", e instanceof Error ? e.message : e);
|
|
2948
|
+
}
|
|
2618
2949
|
throw new AgetherError(
|
|
2619
|
-
`Unknown token: ${symbolOrAddress}.
|
|
2950
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
2620
2951
|
"UNKNOWN_TOKEN"
|
|
2621
2952
|
);
|
|
2622
2953
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -252,7 +252,7 @@ declare class AgetherClient {
|
|
|
252
252
|
getBalances(): Promise<BalancesResult>;
|
|
253
253
|
/**
|
|
254
254
|
* Fund the Safe account with USDC from EOA.
|
|
255
|
-
*
|
|
255
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
256
256
|
*/
|
|
257
257
|
fundAccount(usdcAmount: string): Promise<TransactionResult>;
|
|
258
258
|
/**
|
|
@@ -270,6 +270,54 @@ declare class AgetherClient {
|
|
|
270
270
|
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
271
271
|
*/
|
|
272
272
|
withdrawEth(amount: string): Promise<WithdrawFromAccountResult>;
|
|
273
|
+
/**
|
|
274
|
+
* Fund the AgentAccount with any ERC-20 token from EOA.
|
|
275
|
+
* Simple ERC-20 transfer (does NOT require a UserOp).
|
|
276
|
+
*
|
|
277
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
278
|
+
* @param amount - Amount to send (human-readable, e.g. '100')
|
|
279
|
+
*/
|
|
280
|
+
fundAccountToken(tokenSymbol: string, amount: string): Promise<TransactionResult>;
|
|
281
|
+
/**
|
|
282
|
+
* Transfer any ERC-20 token from AgentAccount to any address or agent.
|
|
283
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
284
|
+
*
|
|
285
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
286
|
+
* @param amount - Amount to send (e.g. '100' or 'all')
|
|
287
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
288
|
+
*/
|
|
289
|
+
transferToken(tokenSymbol: string, amount: string, to: {
|
|
290
|
+
address?: string;
|
|
291
|
+
agentId?: string;
|
|
292
|
+
}): Promise<WithdrawFromAccountResult>;
|
|
293
|
+
/**
|
|
294
|
+
* Transfer ETH from AgentAccount to any address or agent.
|
|
295
|
+
* Executes via Safe UserOp.
|
|
296
|
+
*
|
|
297
|
+
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
298
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
299
|
+
*/
|
|
300
|
+
transferEth(amount: string, to: {
|
|
301
|
+
address?: string;
|
|
302
|
+
agentId?: string;
|
|
303
|
+
}): Promise<WithdrawFromAccountResult>;
|
|
304
|
+
/**
|
|
305
|
+
* Approve a spender to use ERC-20 tokens from the AgentAccount.
|
|
306
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
307
|
+
*
|
|
308
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
309
|
+
* @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
|
|
310
|
+
* @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
311
|
+
*/
|
|
312
|
+
approveToken(tokenSymbol: string, amount: string, spender: {
|
|
313
|
+
address?: string;
|
|
314
|
+
agentId?: string;
|
|
315
|
+
}): Promise<{
|
|
316
|
+
tx: string;
|
|
317
|
+
token: string;
|
|
318
|
+
amount: string;
|
|
319
|
+
spender: string;
|
|
320
|
+
}>;
|
|
273
321
|
/**
|
|
274
322
|
* Send tokens to another agent's Safe account (or any address).
|
|
275
323
|
* Transfers from EOA (does NOT require a UserOp).
|
|
@@ -324,11 +372,14 @@ declare class AgetherClient {
|
|
|
324
372
|
/**
|
|
325
373
|
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
326
374
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
375
|
+
* Resolution order:
|
|
376
|
+
* 1. `'USDC'` → from chain config (instant)
|
|
377
|
+
* 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
|
|
378
|
+
* 3. Dynamic cache populated by previous Morpho API lookups (instant)
|
|
379
|
+
* 4. `'0x...'` address → reads decimals and symbol onchain
|
|
380
|
+
* 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
|
|
331
381
|
*/
|
|
382
|
+
private _dynamicTokenCache;
|
|
332
383
|
private _resolveToken;
|
|
333
384
|
/**
|
|
334
385
|
* Refresh signer and rebind contracts for fresh nonce.
|
|
@@ -575,7 +626,10 @@ declare class MorphoClient {
|
|
|
575
626
|
/** Read onchain position for a specific market. */
|
|
576
627
|
getPosition(marketId: string): Promise<MorphoPosition>;
|
|
577
628
|
/**
|
|
578
|
-
* Full status: positions across all
|
|
629
|
+
* Full status: positions across all markets the user has interacted with.
|
|
630
|
+
*
|
|
631
|
+
* Uses Morpho GraphQL `marketPositions` to find ALL positions (not limited
|
|
632
|
+
* to the top-500 markets), then reads onchain data for accurate debt.
|
|
579
633
|
*/
|
|
580
634
|
getStatus(): Promise<StatusResult>;
|
|
581
635
|
/**
|