@agether/sdk 2.12.2 → 2.14.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 CHANGED
@@ -290,8 +290,9 @@ var init_MorphoClient = __esm({
290
290
  erc20Iface = new import_ethers.ethers.Interface(ERC20_ABI);
291
291
  MorphoClient = class {
292
292
  constructor(config) {
293
+ /** Market params cache: keyed by market uniqueKey (bytes32 hash) */
293
294
  this._marketCache = /* @__PURE__ */ new Map();
294
- /** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
295
+ /** Dynamic token registry: symbol (uppercase) or address (lowercase) → { address, symbol, decimals } */
295
296
  this._tokenCache = /* @__PURE__ */ new Map();
296
297
  this._discoveredAt = 0;
297
298
  if (!config.agentId) {
@@ -394,21 +395,23 @@ var init_MorphoClient = __esm({
394
395
  // Market Discovery (Morpho GraphQL API)
395
396
  // ════════════════════════════════════════════════════════
396
397
  /**
397
- * Fetch USDC borrow markets on Base from Morpho API.
398
- * Caches results for 5 minutes.
398
+ * Fetch available markets on the current chain from Morpho API.
399
+ * Caches results for 5 minutes. Supports all loan tokens (not just USDC).
400
+ *
401
+ * @param forceRefresh - bypass cache TTL
402
+ * @param filter - optional filter by loan token and/or collateral token
399
403
  */
400
- async getMarkets(forceRefresh = false) {
404
+ async getMarkets(forceRefresh = false, filter) {
401
405
  if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
402
- return this._discoveredMarkets;
406
+ return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
403
407
  }
404
408
  const chainId = this.config.chainId;
405
- const usdcAddr = this.config.contracts.usdc.toLowerCase();
406
409
  const query = `{
407
410
  markets(
408
- first: 50
411
+ first: 500
409
412
  orderBy: SupplyAssetsUsd
410
413
  orderDirection: Desc
411
- where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"] }
414
+ where: { chainId_in: [${chainId}] }
412
415
  ) {
413
416
  items {
414
417
  uniqueKey
@@ -441,14 +444,14 @@ var init_MorphoClient = __esm({
441
444
  }));
442
445
  this._discoveredAt = Date.now();
443
446
  for (const mi of this._discoveredMarkets) {
447
+ this._marketCache.set(mi.uniqueKey.toLowerCase(), {
448
+ loanToken: mi.loanAsset.address,
449
+ collateralToken: mi.collateralAsset.address,
450
+ oracle: mi.oracle,
451
+ irm: mi.irm,
452
+ lltv: mi.lltv
453
+ });
444
454
  if (mi.collateralAsset.address !== import_ethers.ethers.ZeroAddress) {
445
- this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
446
- loanToken: mi.loanAsset.address,
447
- collateralToken: mi.collateralAsset.address,
448
- oracle: mi.oracle,
449
- irm: mi.irm,
450
- lltv: mi.lltv
451
- });
452
455
  this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
453
456
  address: mi.collateralAsset.address,
454
457
  symbol: mi.collateralAsset.symbol,
@@ -473,17 +476,24 @@ var init_MorphoClient = __esm({
473
476
  });
474
477
  }
475
478
  }
476
- return this._discoveredMarkets;
479
+ return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
477
480
  } catch (e) {
478
481
  console.warn("[agether] getMarkets failed, using cache:", e instanceof Error ? e.message : e);
479
- return this._discoveredMarkets ?? [];
482
+ const cached = this._discoveredMarkets ?? [];
483
+ return filter ? this._applyMarketFilter(cached, filter) : cached;
480
484
  }
481
485
  }
482
486
  /**
483
- * Get MarketParams for a collateral token.
484
- * Tries cache → API → onchain idToMarketParams.
487
+ * Get MarketParams for a collateral token (and optionally a specific loan token).
488
+ * Tries cache → API discovery.
489
+ *
490
+ * When `loanTokenSymbolOrAddress` is omitted, returns the most liquid market
491
+ * for that collateral (sorted by supply, typically the USDC market).
492
+ *
493
+ * @param collateralSymbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
494
+ * @param loanTokenSymbolOrAddress - e.g. 'USDC', 'WETH', or '0x833589...' (optional)
485
495
  */
486
- async findMarketForCollateral(collateralSymbolOrAddress) {
496
+ async findMarketForCollateral(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
487
497
  let colAddr;
488
498
  if (collateralSymbolOrAddress.startsWith("0x")) {
489
499
  colAddr = collateralSymbolOrAddress.toLowerCase();
@@ -495,13 +505,41 @@ var init_MorphoClient = __esm({
495
505
  colAddr = collateralSymbolOrAddress.toLowerCase();
496
506
  }
497
507
  }
498
- const cached = this._marketCache.get(colAddr);
499
- if (cached) return cached;
500
- await this.getMarkets();
501
- const fromApi = this._marketCache.get(colAddr);
502
- if (fromApi) return fromApi;
508
+ let loanAddr;
509
+ if (loanTokenSymbolOrAddress) {
510
+ if (loanTokenSymbolOrAddress.startsWith("0x")) {
511
+ loanAddr = loanTokenSymbolOrAddress.toLowerCase();
512
+ } else {
513
+ try {
514
+ const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
515
+ loanAddr = resolved.address.toLowerCase();
516
+ } catch {
517
+ loanAddr = loanTokenSymbolOrAddress.toLowerCase();
518
+ }
519
+ }
520
+ }
521
+ if (!this._discoveredMarkets) await this.getMarkets();
522
+ for (const m of this._discoveredMarkets ?? []) {
523
+ if (m.collateralAsset.address.toLowerCase() !== colAddr) continue;
524
+ if (loanAddr && m.loanAsset.address.toLowerCase() !== loanAddr) continue;
525
+ return {
526
+ loanToken: m.loanAsset.address,
527
+ collateralToken: m.collateralAsset.address,
528
+ oracle: m.oracle,
529
+ irm: m.irm,
530
+ lltv: m.lltv
531
+ };
532
+ }
533
+ if (!collateralSymbolOrAddress.startsWith("0x")) {
534
+ const searched = await this.searchMarkets(collateralSymbolOrAddress, { asCollateral: true });
535
+ for (const m of searched) {
536
+ if (loanAddr && m.loanAddress.toLowerCase() !== loanAddr) continue;
537
+ if (loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x") && m.loanToken.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
538
+ return this.getMarketParams(m.marketId);
539
+ }
540
+ }
503
541
  throw new AgetherError(
504
- `No Morpho market found for collateral ${collateralSymbolOrAddress}`,
542
+ `No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
505
543
  "MARKET_NOT_FOUND"
506
544
  );
507
545
  }
@@ -536,12 +574,13 @@ var init_MorphoClient = __esm({
536
574
  const acctAddr = await this.getAccountAddress();
537
575
  const markets = await this.getMarkets();
538
576
  const positions = [];
539
- let totalDebt = 0n;
577
+ let totalDebtFloat = 0;
540
578
  for (const m of markets) {
541
579
  if (!m.collateralAsset || m.collateralAsset.address === import_ethers.ethers.ZeroAddress) continue;
542
580
  try {
543
581
  const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
544
582
  if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
583
+ const loanDecimals = m.loanAsset.decimals;
545
584
  let debt = 0n;
546
585
  if (pos.borrowShares > 0n) {
547
586
  try {
@@ -549,7 +588,7 @@ var init_MorphoClient = __esm({
549
588
  const totalBorrowShares = BigInt(mkt.totalBorrowShares);
550
589
  const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
551
590
  debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
552
- totalDebt += debt;
591
+ totalDebtFloat += parseFloat(import_ethers.ethers.formatUnits(debt, loanDecimals));
553
592
  } catch (e) {
554
593
  console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
555
594
  }
@@ -557,10 +596,11 @@ var init_MorphoClient = __esm({
557
596
  positions.push({
558
597
  marketId: m.uniqueKey,
559
598
  collateralToken: m.collateralAsset.symbol,
599
+ loanToken: m.loanAsset.symbol,
560
600
  collateral: import_ethers.ethers.formatUnits(pos.collateral, m.collateralAsset.decimals),
561
601
  borrowShares: pos.borrowShares.toString(),
562
602
  supplyShares: pos.supplyShares.toString(),
563
- debt: import_ethers.ethers.formatUnits(debt, 6)
603
+ debt: import_ethers.ethers.formatUnits(debt, loanDecimals)
564
604
  });
565
605
  } catch (e) {
566
606
  console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
@@ -570,16 +610,28 @@ var init_MorphoClient = __esm({
570
610
  return {
571
611
  agentId: this.agentId,
572
612
  agentAccount: acctAddr,
573
- totalDebt: import_ethers.ethers.formatUnits(totalDebt, 6),
613
+ totalDebt: totalDebtFloat.toFixed(6),
574
614
  positions
575
615
  };
576
616
  }
577
617
  // ════════════════════════════════════════════════════════
578
618
  // Balance & Borrowing Capacity
579
619
  // ════════════════════════════════════════════════════════
620
+ /**
621
+ * Get the balance of any ERC-20 token in the AgentAccount.
622
+ * @param symbolOrAddress - token symbol (e.g. 'USDC', 'WETH') or address
623
+ * @returns balance in raw units
624
+ */
625
+ async getTokenBalance(symbolOrAddress) {
626
+ const acctAddr = await this.getAccountAddress();
627
+ const tokenInfo = await this._resolveToken(symbolOrAddress);
628
+ const token = new import_ethers.Contract(tokenInfo.address, ERC20_ABI, this.provider);
629
+ return token.balanceOf(acctAddr);
630
+ }
580
631
  /**
581
632
  * Get the USDC balance of the AgentAccount.
582
633
  * @returns USDC balance in raw units (6 decimals)
634
+ * @deprecated Use `getTokenBalance('USDC')` instead.
583
635
  */
584
636
  async getUsdcBalance() {
585
637
  const acctAddr = await this.getAccountAddress();
@@ -587,7 +639,7 @@ var init_MorphoClient = __esm({
587
639
  return usdc.balanceOf(acctAddr);
588
640
  }
589
641
  /**
590
- * Calculate the maximum additional USDC that can be borrowed
642
+ * Calculate the maximum additional loan token that can be borrowed
591
643
  * given the agent's current collateral and debt across all markets.
592
644
  *
593
645
  * For each market with collateral deposited:
@@ -595,7 +647,7 @@ var init_MorphoClient = __esm({
595
647
  *
596
648
  * Uses the Morpho oracle to price collateral → loan token.
597
649
  *
598
- * @returns Maximum additional USDC borrowable (6 decimals)
650
+ * @returns Maximum additional borrowable per market (raw units in each market's loan token)
599
651
  */
600
652
  async getMaxBorrowable() {
601
653
  const acctAddr = await this.getAccountAddress();
@@ -628,6 +680,8 @@ var init_MorphoClient = __esm({
628
680
  totalAdditional += maxAdditional;
629
681
  byMarket.push({
630
682
  collateralToken: m.collateralAsset.symbol,
683
+ loanToken: m.loanAsset.symbol,
684
+ loanDecimals: m.loanAsset.decimals,
631
685
  maxAdditional,
632
686
  currentDebt,
633
687
  collateralValue: collateralValueInLoan
@@ -643,35 +697,50 @@ var init_MorphoClient = __esm({
643
697
  // Market Rates & Yield Estimation
644
698
  // ════════════════════════════════════════════════════════
645
699
  /**
646
- * Fetch current supply/borrow APY for a collateral market from Morpho GraphQL API.
700
+ * Fetch current supply/borrow APY for markets from Morpho GraphQL API.
647
701
  *
648
702
  * Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
649
703
  * is what lenders earn; borrow APY is what borrowers pay.
704
+ *
705
+ * @param collateralSymbolOrAddress - filter by collateral token (optional)
706
+ * @param loanTokenSymbolOrAddress - filter by loan token (optional). Omit for all loan tokens.
650
707
  */
651
- async getMarketRates(collateralSymbolOrAddress) {
708
+ async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
652
709
  const chainId = this.config.chainId;
653
- const usdcAddr = this.config.contracts.usdc.toLowerCase();
654
710
  let collateralFilter = "";
711
+ let loanFilter = "";
712
+ let searchTerm = "";
655
713
  if (collateralSymbolOrAddress) {
656
- let colAddr;
657
714
  if (collateralSymbolOrAddress.startsWith("0x")) {
658
- colAddr = collateralSymbolOrAddress.toLowerCase();
715
+ collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
659
716
  } else {
660
- try {
661
- const resolved = await this._resolveToken(collateralSymbolOrAddress);
662
- colAddr = resolved.address.toLowerCase();
663
- } catch {
664
- colAddr = collateralSymbolOrAddress.toLowerCase();
717
+ const cached = this._tokenCache.get(collateralSymbolOrAddress.toUpperCase());
718
+ if (cached) {
719
+ collateralFilter = `, collateralAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
720
+ } else {
721
+ searchTerm = collateralSymbolOrAddress;
665
722
  }
666
723
  }
667
- collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
668
724
  }
725
+ if (loanTokenSymbolOrAddress) {
726
+ if (loanTokenSymbolOrAddress.startsWith("0x")) {
727
+ loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
728
+ } else {
729
+ const cached = this._tokenCache.get(loanTokenSymbolOrAddress.toUpperCase());
730
+ if (cached) {
731
+ loanFilter = `, loanAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
732
+ } else {
733
+ searchTerm = searchTerm || loanTokenSymbolOrAddress;
734
+ }
735
+ }
736
+ }
737
+ const searchClause = searchTerm ? `, search: "${searchTerm}"` : "";
669
738
  const query = `{
670
739
  markets(
671
- first: 50
740
+ first: 100
672
741
  orderBy: SupplyAssetsUsd
673
742
  orderDirection: Desc
674
- where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"]${collateralFilter} }
743
+ where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter}${searchClause} }
675
744
  ) {
676
745
  items {
677
746
  uniqueKey
@@ -690,23 +759,234 @@ var init_MorphoClient = __esm({
690
759
  }`;
691
760
  try {
692
761
  const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
693
- const items = resp.data?.data?.markets?.items ?? [];
694
- return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers.ethers.ZeroAddress).map((m) => ({
695
- collateralToken: m.collateralAsset.symbol,
696
- loanToken: m.loanAsset.symbol,
697
- supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
698
- borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
699
- utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
700
- totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 1e6 : 0,
701
- totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 1e6 : 0,
702
- lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
703
- marketId: m.uniqueKey
704
- }));
762
+ let items = resp.data?.data?.markets?.items ?? [];
763
+ if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
764
+ const sym = collateralSymbolOrAddress.toUpperCase();
765
+ items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === sym);
766
+ }
767
+ if (searchTerm && loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x")) {
768
+ const sym = loanTokenSymbolOrAddress.toUpperCase();
769
+ items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === sym);
770
+ }
771
+ return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers.ethers.ZeroAddress).map((m) => {
772
+ const loanDecimals = m.loanAsset?.decimals ?? 18;
773
+ return {
774
+ collateralToken: m.collateralAsset.symbol,
775
+ loanToken: m.loanAsset.symbol,
776
+ loanDecimals,
777
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
778
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
779
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
780
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
781
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
782
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
783
+ marketId: m.uniqueKey
784
+ };
785
+ });
705
786
  } catch (e) {
706
787
  console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
707
788
  return [];
708
789
  }
709
790
  }
791
+ // ════════════════════════════════════════════════════════
792
+ // Market Search & Wallet Discovery
793
+ // ════════════════════════════════════════════════════════
794
+ /**
795
+ * Search Morpho markets by token name using the Morpho GraphQL API `search` field.
796
+ * No hardcoded token lists — uses Morpho's built-in fuzzy search.
797
+ *
798
+ * @param search - token name or symbol (e.g. 'WETH', 'staked ETH', 'ezETH')
799
+ * @param options.asCollateral - only return markets where the searched token is collateral
800
+ * @param options.asLoanToken - only return markets where the searched token is the loan asset
801
+ */
802
+ async searchMarkets(search, options) {
803
+ const chainId = this.config.chainId;
804
+ const query = `{
805
+ markets(
806
+ first: 100
807
+ orderBy: SupplyAssetsUsd
808
+ orderDirection: Desc
809
+ where: { chainId_in: [${chainId}], search: "${search}" }
810
+ ) {
811
+ items {
812
+ uniqueKey
813
+ lltv
814
+ loanAsset { address symbol decimals }
815
+ collateralAsset { address symbol decimals }
816
+ state {
817
+ borrowAssets
818
+ supplyAssets
819
+ utilization
820
+ supplyApy
821
+ borrowApy
822
+ }
823
+ }
824
+ }
825
+ }`;
826
+ try {
827
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
828
+ let items = resp.data?.data?.markets?.items ?? [];
829
+ items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers.ethers.ZeroAddress);
830
+ const searchUpper = search.toUpperCase();
831
+ if (options?.asCollateral) {
832
+ items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === searchUpper);
833
+ }
834
+ if (options?.asLoanToken) {
835
+ items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === searchUpper);
836
+ }
837
+ return items.map((m) => {
838
+ const loanDecimals = m.loanAsset?.decimals ?? 18;
839
+ const collateralDecimals = m.collateralAsset?.decimals ?? 18;
840
+ return {
841
+ collateralToken: m.collateralAsset.symbol,
842
+ loanToken: m.loanAsset.symbol,
843
+ loanDecimals,
844
+ collateralDecimals,
845
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
846
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
847
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
848
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
849
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
850
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
851
+ marketId: m.uniqueKey,
852
+ collateralAddress: m.collateralAsset.address,
853
+ loanAddress: m.loanAsset.address
854
+ };
855
+ });
856
+ } catch (e) {
857
+ console.warn("[agether] searchMarkets failed:", e instanceof Error ? e.message : e);
858
+ return [];
859
+ }
860
+ }
861
+ /**
862
+ * Scan the AgentAccount wallet for all ERC-20 tokens that appear in Morpho
863
+ * markets on the current chain. Returns tokens where balance > 0.
864
+ *
865
+ * Uses the full market list (500 markets) to discover all relevant tokens,
866
+ * then checks on-chain balance for each unique token address.
867
+ *
868
+ * @returns Array of tokens with non-zero balance, sorted by balance descending.
869
+ */
870
+ async getWalletTokenBalances() {
871
+ const acctAddr = await this.getAccountAddress();
872
+ await this.getMarkets();
873
+ const uniqueTokens = /* @__PURE__ */ new Map();
874
+ for (const [key, info] of this._tokenCache.entries()) {
875
+ if (key.startsWith("0x") && !uniqueTokens.has(key)) {
876
+ uniqueTokens.set(key, info);
877
+ }
878
+ }
879
+ const results = [];
880
+ try {
881
+ const ethBalance = await this.provider.getBalance(acctAddr);
882
+ if (ethBalance > 0n) {
883
+ results.push({
884
+ symbol: "ETH",
885
+ address: import_ethers.ethers.ZeroAddress,
886
+ decimals: 18,
887
+ balance: ethBalance,
888
+ balanceFormatted: import_ethers.ethers.formatEther(ethBalance)
889
+ });
890
+ }
891
+ } catch {
892
+ }
893
+ const tokenEntries = Array.from(uniqueTokens.values());
894
+ const batchSize = 20;
895
+ for (let i = 0; i < tokenEntries.length; i += batchSize) {
896
+ const batch = tokenEntries.slice(i, i + batchSize);
897
+ const checks = batch.map(async (info) => {
898
+ try {
899
+ const token = new import_ethers.Contract(info.address, ERC20_ABI, this.provider);
900
+ const balance = await token.balanceOf(acctAddr);
901
+ if (balance > 0n) {
902
+ return {
903
+ symbol: info.symbol,
904
+ address: info.address,
905
+ decimals: info.decimals,
906
+ balance,
907
+ balanceFormatted: import_ethers.ethers.formatUnits(balance, info.decimals)
908
+ };
909
+ }
910
+ } catch {
911
+ }
912
+ return null;
913
+ });
914
+ const batchResults = await Promise.all(checks);
915
+ for (const r of batchResults) {
916
+ if (r) results.push(r);
917
+ }
918
+ }
919
+ results.sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
920
+ return results;
921
+ }
922
+ /**
923
+ * Find borrowing opportunities for the agent.
924
+ *
925
+ * - If `collateralSymbol` is provided: find all markets where that token is collateral.
926
+ * - If omitted: scan wallet balances and find markets for each token the agent holds.
927
+ *
928
+ * Returns markets grouped by collateral token with APY, liquidity, and balance info.
929
+ *
930
+ * @param collateralSymbol - optional, e.g. 'WETH'. If omitted, scans wallet.
931
+ */
932
+ async findBorrowingOptions(collateralSymbol) {
933
+ let tokensToCheck;
934
+ if (collateralSymbol) {
935
+ tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: "N/A" }];
936
+ try {
937
+ const balance = await this.getTokenBalance(collateralSymbol);
938
+ const info = await this._resolveToken(collateralSymbol);
939
+ tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: import_ethers.ethers.formatUnits(balance, info.decimals) }];
940
+ } catch {
941
+ }
942
+ } else {
943
+ const walletTokens = await this.getWalletTokenBalances();
944
+ tokensToCheck = walletTokens.filter((t) => t.symbol !== "ETH").map((t) => ({ symbol: t.symbol, balanceFormatted: t.balanceFormatted }));
945
+ if (tokensToCheck.length === 0) {
946
+ return [];
947
+ }
948
+ }
949
+ const results = [];
950
+ for (const token of tokensToCheck) {
951
+ const markets = await this.searchMarkets(token.symbol, { asCollateral: true });
952
+ if (markets.length === 0) continue;
953
+ results.push({
954
+ collateralToken: token.symbol,
955
+ collateralBalance: token.balanceFormatted,
956
+ markets: markets.map((m) => ({
957
+ loanToken: m.loanToken,
958
+ borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
959
+ supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
960
+ lltv: m.lltv,
961
+ utilization: `${(m.utilization * 100).toFixed(1)}%`,
962
+ availableLiquidity: `$${(m.totalSupplyUsd - m.totalBorrowUsd).toFixed(0)}`,
963
+ marketId: m.marketId
964
+ }))
965
+ });
966
+ }
967
+ return results;
968
+ }
969
+ /**
970
+ * Find supply/lending opportunities for a specific loan token.
971
+ *
972
+ * "What can I supply to earn WETH?" → shows all markets where WETH is the loan token
973
+ * (user supplies WETH to earn yield from borrowers).
974
+ *
975
+ * @param loanTokenSymbol - e.g. 'USDC', 'WETH'
976
+ */
977
+ async findSupplyOptions(loanTokenSymbol) {
978
+ const markets = await this.searchMarkets(loanTokenSymbol, { asLoanToken: true });
979
+ return markets.map((m) => ({
980
+ collateralToken: m.collateralToken,
981
+ loanToken: m.loanToken,
982
+ supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
983
+ borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
984
+ lltv: m.lltv,
985
+ utilization: `${(m.utilization * 100).toFixed(1)}%`,
986
+ totalSupply: `$${m.totalSupplyUsd.toFixed(0)}`,
987
+ marketId: m.marketId
988
+ }));
989
+ }
710
990
  /**
711
991
  * Estimate theoretical yield for a given collateral amount over a period.
712
992
  *
@@ -735,14 +1015,15 @@ var init_MorphoClient = __esm({
735
1015
  } else {
736
1016
  try {
737
1017
  const params = await this.findMarketForCollateral(collateralSymbol);
1018
+ const loanDecimals = await this._getLoanTokenDecimals(params);
738
1019
  const oracleContract = new import_ethers.Contract(params.oracle, [
739
1020
  "function price() view returns (uint256)"
740
1021
  ], this.provider);
741
1022
  const oraclePrice = await oracleContract.price();
742
1023
  const ORACLE_PRICE_SCALE = 10n ** 36n;
743
1024
  const amountWei = import_ethers.ethers.parseUnits(amount, colInfo.decimals);
744
- const valueInUsdc = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
745
- collateralValueUsd = Number(valueInUsdc) / 1e6;
1025
+ const valueInLoan = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
1026
+ collateralValueUsd = Number(valueInLoan) / 10 ** loanDecimals;
746
1027
  } catch (e) {
747
1028
  console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
748
1029
  throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
@@ -763,61 +1044,65 @@ var init_MorphoClient = __esm({
763
1044
  // Supply-Side (Lending) — earn yield by supplying USDC
764
1045
  // ════════════════════════════════════════════════════════
765
1046
  /**
766
- * Supply USDC to a Morpho Blue market as a lender (earn yield).
1047
+ * Supply loan token to a Morpho Blue market as a lender (earn yield).
767
1048
  *
768
1049
  * Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
769
- * you deposit the loanToken (USDC) into the market's supply pool and earn
1050
+ * you deposit the loanToken into the market's supply pool and earn
770
1051
  * interest paid by borrowers.
771
1052
  *
772
- * @param usdcAmount - Amount of USDC to supply (e.g. '500')
1053
+ * @param amount - Amount of loan token to supply (e.g. '500' for 500 USDC, '0.5' for 0.5 WETH)
773
1054
  * @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
774
1055
  * Optional — defaults to highest-APY market
1056
+ * @param loanTokenSymbol - Loan token to filter market (e.g. 'USDC', 'WETH'). Optional.
775
1057
  */
776
- async supplyAsset(usdcAmount, collateralSymbol) {
1058
+ async supplyAsset(amount, collateralSymbol, loanTokenSymbol) {
777
1059
  const acctAddr = await this.getAccountAddress();
778
- const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
779
1060
  const morphoAddr = this.config.contracts.morphoBlue;
780
- const usdcAddr = this.config.contracts.usdc;
781
1061
  let params;
782
1062
  let usedCollateral;
783
1063
  if (collateralSymbol) {
784
- params = await this.findMarketForCollateral(collateralSymbol);
1064
+ params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
785
1065
  usedCollateral = collateralSymbol;
786
1066
  } else {
787
- const rates = await this.getMarketRates();
1067
+ const rates = await this.getMarketRates(void 0, loanTokenSymbol);
788
1068
  if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
789
1069
  const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
790
- params = await this.findMarketForCollateral(best.collateralToken);
1070
+ params = await this.findMarketForCollateral(best.collateralToken, loanTokenSymbol);
791
1071
  usedCollateral = best.collateralToken;
792
1072
  }
1073
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1074
+ const loanTokenAddr = params.loanToken;
1075
+ const parsedAmount = import_ethers.ethers.parseUnits(amount, loanDecimals);
793
1076
  const marketId = import_ethers.ethers.keccak256(
794
1077
  import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
795
1078
  ["address", "address", "address", "address", "uint256"],
796
1079
  [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
797
1080
  )
798
1081
  );
799
- const usdcContract = new import_ethers.Contract(usdcAddr, ERC20_ABI, this._signer);
800
- const acctBalance = await usdcContract.balanceOf(acctAddr);
801
- if (acctBalance < amount) {
802
- const shortfall = amount - acctBalance;
803
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1082
+ const loanContract = new import_ethers.Contract(loanTokenAddr, ERC20_ABI, this._signer);
1083
+ const acctBalance = await loanContract.balanceOf(acctAddr);
1084
+ if (acctBalance < parsedAmount) {
1085
+ const shortfall = parsedAmount - acctBalance;
1086
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
804
1087
  if (eoaBalance < shortfall) {
1088
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1089
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
805
1090
  throw new AgetherError(
806
- `Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, 6)}.`,
1091
+ `Insufficient ${loanSymbol}. Need ${amount}, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
807
1092
  "INSUFFICIENT_BALANCE"
808
1093
  );
809
1094
  }
810
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1095
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
811
1096
  await transferTx.wait();
812
1097
  this._refreshSigner();
813
1098
  }
814
- const targets = [usdcAddr, morphoAddr];
1099
+ const targets = [loanTokenAddr, morphoAddr];
815
1100
  const values = [0n, 0n];
816
1101
  const datas = [
817
- erc20Iface.encodeFunctionData("approve", [morphoAddr, amount]),
1102
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
818
1103
  morphoIface.encodeFunctionData("supply", [
819
1104
  this._toTuple(params),
820
- amount,
1105
+ parsedAmount,
821
1106
  0n,
822
1107
  acctAddr,
823
1108
  "0x"
@@ -826,7 +1111,7 @@ var init_MorphoClient = __esm({
826
1111
  const receipt = await this.batch(targets, values, datas);
827
1112
  return {
828
1113
  tx: receipt.hash,
829
- amount: usdcAmount,
1114
+ amount,
830
1115
  marketId,
831
1116
  collateralToken: usedCollateral,
832
1117
  agentAccount: acctAddr
@@ -839,17 +1124,26 @@ var init_MorphoClient = __esm({
839
1124
  * @param collateralSymbol - Market collateral to identify which market
840
1125
  * @param receiver - Destination address (defaults to EOA)
841
1126
  */
842
- async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
1127
+ /**
1128
+ * Withdraw supplied loan token (+ earned interest) from a Morpho Blue market.
1129
+ *
1130
+ * @param amount - Amount to withdraw (e.g. '100' or 'all' for full position)
1131
+ * @param collateralSymbol - Market collateral to identify which market
1132
+ * @param receiver - Destination address (defaults to EOA)
1133
+ * @param loanTokenSymbol - Loan token to filter market (optional)
1134
+ */
1135
+ async withdrawSupply(amount, collateralSymbol, receiver, loanTokenSymbol) {
843
1136
  const acctAddr = await this.getAccountAddress();
844
1137
  const morphoAddr = this.config.contracts.morphoBlue;
845
1138
  const dest = receiver || await this.getSignerAddress();
846
1139
  let params;
847
1140
  if (collateralSymbol) {
848
- params = await this.findMarketForCollateral(collateralSymbol);
1141
+ params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
849
1142
  } else {
850
1143
  const { params: p } = await this._findActiveSupplyMarket();
851
1144
  params = p;
852
1145
  }
1146
+ const loanDecimals = await this._getLoanTokenDecimals(params);
853
1147
  const marketId = import_ethers.ethers.keccak256(
854
1148
  import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
855
1149
  ["address", "address", "address", "address", "uint256"],
@@ -858,13 +1152,13 @@ var init_MorphoClient = __esm({
858
1152
  );
859
1153
  let withdrawAssets;
860
1154
  let withdrawShares;
861
- if (usdcAmount === "all") {
1155
+ if (amount === "all") {
862
1156
  const pos = await this.morphoBlue.position(marketId, acctAddr);
863
1157
  withdrawShares = BigInt(pos.supplyShares);
864
1158
  withdrawAssets = 0n;
865
1159
  if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
866
1160
  } else {
867
- withdrawAssets = import_ethers.ethers.parseUnits(usdcAmount, 6);
1161
+ withdrawAssets = import_ethers.ethers.parseUnits(amount, loanDecimals);
868
1162
  withdrawShares = 0n;
869
1163
  }
870
1164
  const data = morphoIface.encodeFunctionData("withdraw", [
@@ -882,13 +1176,13 @@ var init_MorphoClient = __esm({
882
1176
  const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
883
1177
  const totalSupplyShares = BigInt(mkt.totalSupplyShares);
884
1178
  const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
885
- remainingSupply = import_ethers.ethers.formatUnits(currentAssets, 6);
1179
+ remainingSupply = import_ethers.ethers.formatUnits(currentAssets, loanDecimals);
886
1180
  } catch (e) {
887
1181
  console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
888
1182
  }
889
1183
  return {
890
1184
  tx: receipt.hash,
891
- amount: usdcAmount,
1185
+ amount,
892
1186
  remainingSupply,
893
1187
  destination: dest
894
1188
  };
@@ -961,14 +1255,13 @@ var init_MorphoClient = __esm({
961
1255
  * Computes available yield, verifies the requested amount doesn't exceed it,
962
1256
  * then withdraws from the supply position and sends directly to the recipient.
963
1257
  *
964
- * @param recipient - Address to receive the USDC
965
- * @param usdcAmount - Amount to pay from yield (e.g. '5.50')
1258
+ * @param recipient - Address to receive the loan token
1259
+ * @param amount - Amount to pay from yield (e.g. '5.50')
966
1260
  * @param collateralSymbol - Market collateral to identify which supply position
967
1261
  */
968
- async payFromYield(recipient, usdcAmount, collateralSymbol) {
1262
+ async payFromYield(recipient, amount, collateralSymbol) {
969
1263
  const acctAddr = await this.getAccountAddress();
970
1264
  const morphoAddr = this.config.contracts.morphoBlue;
971
- const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
972
1265
  const positions = await this.getSupplyPositions(collateralSymbol);
973
1266
  if (positions.length === 0) {
974
1267
  throw new AgetherError("No supply position found", "NO_SUPPLY");
@@ -976,17 +1269,20 @@ var init_MorphoClient = __esm({
976
1269
  const pos = positions.reduce(
977
1270
  (a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
978
1271
  );
979
- const availableYield = import_ethers.ethers.parseUnits(pos.earnedYield, 6);
980
- if (amount > availableYield) {
1272
+ const params = await this.findMarketForCollateral(pos.collateralToken, pos.loanToken);
1273
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1274
+ const parsedAmount = import_ethers.ethers.parseUnits(amount, loanDecimals);
1275
+ const availableYield = import_ethers.ethers.parseUnits(pos.earnedYield, loanDecimals);
1276
+ if (parsedAmount > availableYield) {
1277
+ const loanSymbol = pos.loanToken;
981
1278
  throw new AgetherError(
982
- `Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
1279
+ `Requested ${amount} ${loanSymbol} exceeds available yield of ${pos.earnedYield} ${loanSymbol}. Use withdrawSupply to withdraw principal.`,
983
1280
  "EXCEEDS_YIELD"
984
1281
  );
985
1282
  }
986
- const params = await this.findMarketForCollateral(pos.collateralToken);
987
1283
  const data = morphoIface.encodeFunctionData("withdraw", [
988
1284
  this._toTuple(params),
989
- amount,
1285
+ parsedAmount,
990
1286
  0n,
991
1287
  acctAddr,
992
1288
  recipient
@@ -1005,7 +1301,7 @@ var init_MorphoClient = __esm({
1005
1301
  }
1006
1302
  return {
1007
1303
  tx: receipt.hash,
1008
- yieldWithdrawn: usdcAmount,
1304
+ yieldWithdrawn: amount,
1009
1305
  recipient,
1010
1306
  remainingYield,
1011
1307
  remainingSupply
@@ -1063,28 +1359,31 @@ var init_MorphoClient = __esm({
1063
1359
  };
1064
1360
  }
1065
1361
  /**
1066
- * Borrow USDC against existing collateral.
1362
+ * Borrow loan token against existing collateral.
1067
1363
  *
1068
1364
  * AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
1069
1365
  *
1070
- * @param usdcAmount - USDC amount (e.g. '100')
1366
+ * @param amount - Loan token amount (e.g. '100' for 100 USDC, '0.5' for 0.5 WETH)
1071
1367
  * @param tokenSymbol - collateral symbol to identify which market (default: first with collateral)
1368
+ * @param marketParams - explicit market params (optional)
1369
+ * @param loanTokenSymbol - loan token to filter market (optional, e.g. 'USDC', 'WETH')
1072
1370
  */
1073
- async borrow(usdcAmount, tokenSymbol, marketParams) {
1371
+ async borrow(amount, tokenSymbol, marketParams, loanTokenSymbol) {
1074
1372
  const acctAddr = await this.getAccountAddress();
1075
- const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
1076
1373
  const morphoAddr = this.config.contracts.morphoBlue;
1077
1374
  let params;
1078
1375
  let usedToken = tokenSymbol || "WETH";
1079
1376
  if (marketParams) {
1080
1377
  params = marketParams;
1081
1378
  } else if (tokenSymbol) {
1082
- params = await this.findMarketForCollateral(tokenSymbol);
1379
+ params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
1083
1380
  } else {
1084
1381
  const { params: p, symbol } = await this._findActiveMarket();
1085
1382
  params = p;
1086
1383
  usedToken = symbol;
1087
1384
  }
1385
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1386
+ const parsedAmount = import_ethers.ethers.parseUnits(amount, loanDecimals);
1088
1387
  try {
1089
1388
  const marketId = import_ethers.ethers.keccak256(
1090
1389
  import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
@@ -1108,11 +1407,14 @@ var init_MorphoClient = __esm({
1108
1407
  const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
1109
1408
  const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1110
1409
  const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
1111
- if (amount > maxAdditional) {
1112
- const maxUsd = import_ethers.ethers.formatUnits(maxAdditional, 6);
1113
- const colFormatted = import_ethers.ethers.formatUnits(pos.collateral, 18);
1410
+ if (parsedAmount > maxAdditional) {
1411
+ const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
1412
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1413
+ const colInfo = await this._resolveToken(usedToken);
1414
+ const maxFormatted = import_ethers.ethers.formatUnits(maxAdditional, loanDecimals);
1415
+ const colFormatted = import_ethers.ethers.formatUnits(pos.collateral, colInfo.decimals);
1114
1416
  throw new AgetherError(
1115
- `Borrow of $${usdcAmount} USDC exceeds max borrowable $${maxUsd} USDC (collateral: ${colFormatted} ${usedToken}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Deposit more collateral or reduce borrow amount.`,
1417
+ `Borrow of ${amount} ${loanSymbol} exceeds max borrowable ${maxFormatted} ${loanSymbol} (collateral: ${colFormatted} ${usedToken}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Deposit more collateral or reduce borrow amount.`,
1116
1418
  "EXCEEDS_MAX_LTV"
1117
1419
  );
1118
1420
  }
@@ -1122,7 +1424,7 @@ var init_MorphoClient = __esm({
1122
1424
  }
1123
1425
  const data = morphoIface.encodeFunctionData("borrow", [
1124
1426
  this._toTuple(params),
1125
- amount,
1427
+ parsedAmount,
1126
1428
  0n,
1127
1429
  acctAddr,
1128
1430
  acctAddr
@@ -1130,7 +1432,7 @@ var init_MorphoClient = __esm({
1130
1432
  const receipt = await this.exec(morphoAddr, data);
1131
1433
  return {
1132
1434
  tx: receipt.hash,
1133
- amount: usdcAmount,
1435
+ amount,
1134
1436
  collateralToken: usedToken,
1135
1437
  agentAccount: acctAddr
1136
1438
  };
@@ -1138,17 +1440,27 @@ var init_MorphoClient = __esm({
1138
1440
  /**
1139
1441
  * Deposit collateral AND borrow USDC in one batched transaction.
1140
1442
  *
1443
+ /**
1444
+ * Deposit collateral AND borrow loan token in one batched transaction.
1445
+ *
1141
1446
  * AgentAccount.executeBatch:
1142
1447
  * [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
1143
1448
  *
1144
1449
  * The collateral must be transferred to AgentAccount first.
1450
+ *
1451
+ * @param tokenSymbol - collateral token symbol (e.g. 'WETH')
1452
+ * @param collateralAmount - amount of collateral (e.g. '0.05')
1453
+ * @param borrowAmount - amount of loan token to borrow (e.g. '100')
1454
+ * @param marketParams - explicit market params (optional)
1455
+ * @param loanTokenSymbol - loan token to filter market (optional)
1145
1456
  */
1146
- async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
1457
+ async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount, marketParams, loanTokenSymbol) {
1147
1458
  const acctAddr = await this.getAccountAddress();
1148
1459
  const colInfo = await this._resolveToken(tokenSymbol);
1149
- const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1460
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
1461
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1150
1462
  const colWei = import_ethers.ethers.parseUnits(collateralAmount, colInfo.decimals);
1151
- const borrowWei = import_ethers.ethers.parseUnits(borrowUsdcAmount, 6);
1463
+ const borrowWei = import_ethers.ethers.parseUnits(borrowAmount, loanDecimals);
1152
1464
  const morphoAddr = this.config.contracts.morphoBlue;
1153
1465
  try {
1154
1466
  const marketId = import_ethers.ethers.keccak256(
@@ -1169,9 +1481,11 @@ var init_MorphoClient = __esm({
1169
1481
  const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1170
1482
  const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
1171
1483
  if (borrowWei > maxAdditional) {
1172
- const maxUsd = import_ethers.ethers.formatUnits(maxAdditional, 6);
1484
+ const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
1485
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1486
+ const maxFormatted = import_ethers.ethers.formatUnits(maxAdditional, loanDecimals);
1173
1487
  throw new AgetherError(
1174
- `Borrow of $${borrowUsdcAmount} USDC exceeds max borrowable $${maxUsd} USDC (total collateral: ${import_ethers.ethers.formatUnits(totalCollateral, colInfo.decimals)} ${tokenSymbol}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Reduce borrow or increase collateral.`,
1488
+ `Borrow of ${borrowAmount} ${loanSymbol} exceeds max borrowable ${maxFormatted} ${loanSymbol} (total collateral: ${import_ethers.ethers.formatUnits(totalCollateral, colInfo.decimals)} ${tokenSymbol}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Reduce borrow or increase collateral.`,
1175
1489
  "EXCEEDS_MAX_LTV"
1176
1490
  );
1177
1491
  }
@@ -1217,36 +1531,42 @@ var init_MorphoClient = __esm({
1217
1531
  tx: receipt.hash,
1218
1532
  collateralToken: tokenSymbol,
1219
1533
  collateralAmount,
1220
- borrowAmount: borrowUsdcAmount,
1534
+ borrowAmount,
1221
1535
  agentAccount: acctAddr
1222
1536
  };
1223
1537
  }
1224
1538
  /**
1225
- * Repay borrowed USDC from AgentAccount.
1539
+ * Repay borrowed loan token from AgentAccount.
1226
1540
  *
1227
1541
  * AgentAccount.executeBatch:
1228
- * [USDC.approve(MorphoBlue), Morpho.repay(params)]
1542
+ * [loanToken.approve(MorphoBlue), Morpho.repay(params)]
1543
+ *
1544
+ * @param amount - loan token amount to repay (e.g. '50' or 'all' for full repayment)
1545
+ * @param tokenSymbol - collateral symbol to identify which market (optional)
1546
+ * @param marketParams - explicit market params (optional)
1547
+ * @param loanTokenSymbol - loan token to filter market (optional)
1229
1548
  */
1230
- async repay(usdcAmount, tokenSymbol, marketParams) {
1549
+ async repay(amount, tokenSymbol, marketParams, loanTokenSymbol) {
1231
1550
  const acctAddr = await this.getAccountAddress();
1232
1551
  const morphoAddr = this.config.contracts.morphoBlue;
1233
- const usdcAddr = this.config.contracts.usdc;
1234
1552
  let params;
1235
1553
  if (marketParams) {
1236
1554
  params = marketParams;
1237
1555
  } else if (tokenSymbol) {
1238
- params = await this.findMarketForCollateral(tokenSymbol);
1556
+ params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
1239
1557
  } else {
1240
1558
  const { params: p } = await this._findActiveMarket();
1241
1559
  params = p;
1242
1560
  }
1561
+ const loanTokenAddr = params.loanToken;
1562
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1243
1563
  let repayAssets;
1244
1564
  let repayShares;
1245
1565
  let approveAmount;
1246
- if (usdcAmount === "all") {
1566
+ if (amount === "all") {
1247
1567
  const markets = await this.getMarkets();
1248
1568
  const mkt = markets.find(
1249
- (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
1569
+ (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
1250
1570
  );
1251
1571
  if (mkt) {
1252
1572
  const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
@@ -1256,33 +1576,35 @@ var init_MorphoClient = __esm({
1256
1576
  const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
1257
1577
  const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
1258
1578
  const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
1259
- approveAmount = estimated > 0n ? estimated : import_ethers.ethers.parseUnits("1", 6);
1579
+ approveAmount = estimated > 0n ? estimated : import_ethers.ethers.parseUnits("1", loanDecimals);
1260
1580
  } else {
1261
- repayAssets = import_ethers.ethers.parseUnits("999999", 6);
1581
+ repayAssets = import_ethers.ethers.parseUnits("999999", loanDecimals);
1262
1582
  repayShares = 0n;
1263
1583
  approveAmount = repayAssets;
1264
1584
  }
1265
1585
  } else {
1266
- repayAssets = import_ethers.ethers.parseUnits(usdcAmount, 6);
1586
+ repayAssets = import_ethers.ethers.parseUnits(amount, loanDecimals);
1267
1587
  repayShares = 0n;
1268
1588
  approveAmount = repayAssets;
1269
1589
  }
1270
- const usdcContract = new import_ethers.Contract(usdcAddr, ERC20_ABI, this._signer);
1271
- const acctBalance = await usdcContract.balanceOf(acctAddr);
1590
+ const loanContract = new import_ethers.Contract(loanTokenAddr, ERC20_ABI, this._signer);
1591
+ const acctBalance = await loanContract.balanceOf(acctAddr);
1272
1592
  if (acctBalance < approveAmount) {
1273
1593
  const shortfall = approveAmount - acctBalance;
1274
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1594
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
1275
1595
  if (eoaBalance < shortfall) {
1596
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1597
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1276
1598
  throw new AgetherError(
1277
- `Insufficient USDC for repay. Need ${import_ethers.ethers.formatUnits(approveAmount, 6)} USDC, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, 6)}.`,
1599
+ `Insufficient ${loanSymbol} for repay. Need ${import_ethers.ethers.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
1278
1600
  "INSUFFICIENT_BALANCE"
1279
1601
  );
1280
1602
  }
1281
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1603
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
1282
1604
  await transferTx.wait();
1283
1605
  this._refreshSigner();
1284
1606
  }
1285
- const targets = [usdcAddr, morphoAddr];
1607
+ const targets = [loanTokenAddr, morphoAddr];
1286
1608
  const values = [0n, 0n];
1287
1609
  const datas = [
1288
1610
  erc20Iface.encodeFunctionData("approve", [morphoAddr, approveAmount]),
@@ -1302,7 +1624,7 @@ var init_MorphoClient = __esm({
1302
1624
  } catch (e) {
1303
1625
  console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
1304
1626
  }
1305
- return { tx: receipt.hash, amount: usdcAmount, remainingDebt };
1627
+ return { tx: receipt.hash, amount, remainingDebt };
1306
1628
  }
1307
1629
  /**
1308
1630
  * Withdraw collateral from Morpho Blue.
@@ -1316,12 +1638,13 @@ var init_MorphoClient = __esm({
1316
1638
  const colInfo = await this._resolveToken(tokenSymbol);
1317
1639
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1318
1640
  const morphoAddr = this.config.contracts.morphoBlue;
1319
- const usdcAddr = this.config.contracts.usdc;
1641
+ const loanTokenAddr = params.loanToken;
1642
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1320
1643
  const dest = receiver || await this.getSignerAddress();
1321
1644
  let weiAmount;
1322
1645
  const markets = await this.getMarkets();
1323
1646
  const market = markets.find(
1324
- (m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
1647
+ (m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase() && m.loanAsset?.address.toLowerCase() === loanTokenAddr.toLowerCase()
1325
1648
  );
1326
1649
  if (amount === "all") {
1327
1650
  if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
@@ -1344,8 +1667,10 @@ var init_MorphoClient = __esm({
1344
1667
  const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
1345
1668
  const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
1346
1669
  const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
1347
- dustApproveAmount = estimated > 0n ? estimated : import_ethers.ethers.parseUnits("1", 6);
1348
- console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${import_ethers.ethers.formatUnits(dustApproveAmount, 6)} USDC \u2014 auto-repaying before withdraw`);
1670
+ dustApproveAmount = estimated > 0n ? estimated : import_ethers.ethers.parseUnits("1", loanDecimals);
1671
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1672
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1673
+ console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${import_ethers.ethers.formatUnits(dustApproveAmount, loanDecimals)} ${loanSymbol} \u2014 auto-repaying before withdraw`);
1349
1674
  }
1350
1675
  } catch (e) {
1351
1676
  console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
@@ -1359,19 +1684,21 @@ var init_MorphoClient = __esm({
1359
1684
  ]);
1360
1685
  let receipt;
1361
1686
  if (hasDustDebt) {
1362
- const usdcContract = new import_ethers.Contract(usdcAddr, ERC20_ABI, this._signer);
1363
- const acctBalance = await usdcContract.balanceOf(acctAddr);
1687
+ const loanContract = new import_ethers.Contract(loanTokenAddr, ERC20_ABI, this._signer);
1688
+ const acctBalance = await loanContract.balanceOf(acctAddr);
1364
1689
  if (acctBalance < dustApproveAmount) {
1365
1690
  const shortfall = dustApproveAmount - acctBalance;
1366
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1691
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
1367
1692
  if (eoaBalance >= shortfall) {
1368
- console.log(`[agether] transferring ${import_ethers.ethers.formatUnits(shortfall, 6)} USDC from EOA \u2192 AgentAccount for dust repay`);
1369
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1693
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1694
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1695
+ console.log(`[agether] transferring ${import_ethers.ethers.formatUnits(shortfall, loanDecimals)} ${loanSymbol} from EOA \u2192 AgentAccount for dust repay`);
1696
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
1370
1697
  await transferTx.wait();
1371
1698
  this._refreshSigner();
1372
1699
  }
1373
1700
  }
1374
- const targets = [usdcAddr, morphoAddr, morphoAddr];
1701
+ const targets = [loanTokenAddr, morphoAddr, morphoAddr];
1375
1702
  const values = [0n, 0n, 0n];
1376
1703
  const datas = [
1377
1704
  erc20Iface.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
@@ -1616,6 +1943,37 @@ var init_MorphoClient = __esm({
1616
1943
  }
1617
1944
  throw new AgetherError("No active supply position found", "NO_SUPPLY");
1618
1945
  }
1946
+ /**
1947
+ * Resolve loan token decimals from market params.
1948
+ * Uses the `_tokenCache` populated by `getMarkets()`.
1949
+ */
1950
+ async _getLoanTokenDecimals(params) {
1951
+ const tokenInfo = this._tokenCache.get(params.loanToken.toLowerCase());
1952
+ if (tokenInfo) return tokenInfo.decimals;
1953
+ await this.getMarkets();
1954
+ const fromApi = this._tokenCache.get(params.loanToken.toLowerCase());
1955
+ return fromApi?.decimals ?? 18;
1956
+ }
1957
+ /**
1958
+ * Apply client-side filter to discovered markets.
1959
+ */
1960
+ _applyMarketFilter(markets, filter) {
1961
+ return markets.filter((m) => {
1962
+ if (filter.loanToken) {
1963
+ const loanAddr = filter.loanToken.toLowerCase();
1964
+ if (m.loanAsset.address.toLowerCase() !== loanAddr && m.loanAsset.symbol.toUpperCase() !== filter.loanToken.toUpperCase()) {
1965
+ return false;
1966
+ }
1967
+ }
1968
+ if (filter.collateralToken) {
1969
+ const colAddr = filter.collateralToken.toLowerCase();
1970
+ if (m.collateralAsset.address.toLowerCase() !== colAddr && m.collateralAsset.symbol.toUpperCase() !== filter.collateralToken.toUpperCase()) {
1971
+ return false;
1972
+ }
1973
+ }
1974
+ return true;
1975
+ });
1976
+ }
1619
1977
  /**
1620
1978
  * Resolve a token symbol or address to { address, symbol, decimals }.
1621
1979
  *
@@ -1631,9 +1989,27 @@ var init_MorphoClient = __esm({
1631
1989
  await this.getMarkets();
1632
1990
  const fromApi = this._tokenCache.get(key);
1633
1991
  if (fromApi) return fromApi;
1992
+ if (!symbolOrAddress.startsWith("0x")) {
1993
+ const searchResults = await this.searchMarkets(symbolOrAddress);
1994
+ const sym = symbolOrAddress.toUpperCase();
1995
+ for (const m of searchResults) {
1996
+ if (m.collateralToken.toUpperCase() === sym) {
1997
+ const info = { address: m.collateralAddress, symbol: m.collateralToken, decimals: m.collateralDecimals };
1998
+ this._tokenCache.set(sym, info);
1999
+ this._tokenCache.set(m.collateralAddress.toLowerCase(), info);
2000
+ return info;
2001
+ }
2002
+ if (m.loanToken.toUpperCase() === sym) {
2003
+ const info = { address: m.loanAddress, symbol: m.loanToken, decimals: m.loanDecimals };
2004
+ this._tokenCache.set(sym, info);
2005
+ this._tokenCache.set(m.loanAddress.toLowerCase(), info);
2006
+ return info;
2007
+ }
2008
+ }
2009
+ }
1634
2010
  throw new AgetherError(
1635
- `Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
1636
- "UNKNOWN_COLLATERAL"
2011
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
2012
+ "UNKNOWN_TOKEN"
1637
2013
  );
1638
2014
  }
1639
2015
  /**
@@ -1839,25 +2215,21 @@ var init_AgetherClient = __esm({
1839
2215
  const acctAddr = await this.agether4337Factory.getAccount(agentId);
1840
2216
  this.accountAddress = acctAddr;
1841
2217
  if (!acctExists) {
1842
- try {
1843
- const updatedMeta = JSON.stringify({
1844
- type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
1845
- name: options?.name || "Unnamed Agent",
1846
- description: options?.description || "AI agent registered via @agether/sdk",
1847
- active: true,
1848
- wallet: `eip155:${this.config.chainId}:${acctAddr}`,
1849
- registrations: [{
1850
- agentId: Number(agentId),
1851
- agentRegistry: `eip155:${this.config.chainId}:${this.config.contracts.identityRegistry}`
1852
- }]
1853
- });
1854
- const finalURI = `data:application/json;base64,${Buffer.from(updatedMeta).toString("base64")}`;
1855
- const uriTx = await this.identityRegistry.setAgentURI(agentId, finalURI);
1856
- await uriTx.wait();
1857
- this._refreshSigner();
1858
- } catch (e) {
1859
- console.warn("[agether] setAgentURI failed (non-fatal):", e instanceof Error ? e.message : e);
1860
- }
2218
+ const updatedMeta = JSON.stringify({
2219
+ type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
2220
+ name: options?.name || "Unnamed Agent",
2221
+ description: options?.description || "AI agent registered via @agether/sdk",
2222
+ active: true,
2223
+ wallet: `eip155:${this.config.chainId}:${acctAddr}`,
2224
+ registrations: [{
2225
+ agentId: Number(agentId),
2226
+ agentRegistry: `eip155:${this.config.chainId}:${this.config.contracts.identityRegistry}`
2227
+ }]
2228
+ });
2229
+ const finalURI = `data:application/json;base64,${Buffer.from(updatedMeta).toString("base64")}`;
2230
+ const uriTx = await this.identityRegistry.setAgentURI(agentId, finalURI);
2231
+ await uriTx.wait();
2232
+ this._refreshSigner();
1861
2233
  }
1862
2234
  const kyaRequired = await this.isKyaRequired();
1863
2235
  return {
@@ -1875,7 +2247,7 @@ var init_AgetherClient = __esm({
1875
2247
  async _mintNewIdentity(name, description) {
1876
2248
  const registrationFile = JSON.stringify({
1877
2249
  type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
1878
- name: name || "Unnamed Agent",
2250
+ name: name || "Agether Agent",
1879
2251
  description: description || "AI agent registered via @agether/sdk",
1880
2252
  active: true,
1881
2253
  registrations: [{