@agether/sdk 1.6.1 → 1.6.3

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/index.js CHANGED
@@ -199,9 +199,9 @@ var CONTRACT_ADDRESSES = {
199
199
  morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"
200
200
  },
201
201
  [8453 /* Base */]: {
202
- accountFactory: "0x7D5D56416bAEA06a9DCBe3092eF335724C6320a0",
203
- validationRegistry: "0xd196C32D2149270F56E209ba7aEE67CE9ceD2001",
204
- agentReputation: "0x65c9cA1211809D3CF3A2707558198eb2b2bE623c",
202
+ accountFactory: "0x871eb6b07964bc308bf68b18ca5824AFE5Cb0c8b",
203
+ validationRegistry: "0x5a2FF014C68a2498554B5786ED92483E6d56D06f",
204
+ agentReputation: "0x47adEA82a8975a60D81483CD39C377F905988DF1",
205
205
  usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
206
206
  identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
207
207
  morphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"
@@ -760,6 +760,179 @@ var MorphoClient = class {
760
760
  };
761
761
  }
762
762
  // ════════════════════════════════════════════════════════
763
+ // Balance & Borrowing Capacity
764
+ // ════════════════════════════════════════════════════════
765
+ /**
766
+ * Get the USDC balance of the AgentAccount.
767
+ * @returns USDC balance in raw units (6 decimals)
768
+ */
769
+ async getUsdcBalance() {
770
+ const acctAddr = await this.getAccountAddress();
771
+ const usdc = new import_ethers2.Contract(this.config.contracts.usdc, ERC20_ABI, this.provider);
772
+ return usdc.balanceOf(acctAddr);
773
+ }
774
+ /**
775
+ * Calculate the maximum additional USDC that can be borrowed
776
+ * given the agent's current collateral and debt across all markets.
777
+ *
778
+ * For each market with collateral deposited:
779
+ * maxBorrow = (collateralValue * LLTV) - currentDebt
780
+ *
781
+ * Uses the Morpho oracle to price collateral → loan token.
782
+ *
783
+ * @returns Maximum additional USDC borrowable (6 decimals)
784
+ */
785
+ async getMaxBorrowable() {
786
+ const acctAddr = await this.getAccountAddress();
787
+ const markets = await this.getMarkets();
788
+ let totalAdditional = 0n;
789
+ const byMarket = [];
790
+ for (const m of markets) {
791
+ if (!m.collateralAsset || m.collateralAsset.address === import_ethers2.ethers.ZeroAddress) continue;
792
+ try {
793
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
794
+ if (pos.collateral === 0n) continue;
795
+ const mktState = await this.morphoBlue.market(m.uniqueKey);
796
+ const totalBorrowShares = BigInt(mktState.totalBorrowShares);
797
+ const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
798
+ const currentDebt = totalBorrowShares > 0n ? BigInt(pos.borrowShares) * totalBorrowAssets / totalBorrowShares : 0n;
799
+ let collateralValueInLoan;
800
+ try {
801
+ const oracleContract = new import_ethers2.Contract(m.oracle, [
802
+ "function price() view returns (uint256)"
803
+ ], this.provider);
804
+ const oraclePrice = await oracleContract.price();
805
+ const ORACLE_PRICE_SCALE = 10n ** 36n;
806
+ collateralValueInLoan = BigInt(pos.collateral) * oraclePrice / ORACLE_PRICE_SCALE;
807
+ } catch {
808
+ continue;
809
+ }
810
+ const maxBorrowTotal = collateralValueInLoan * m.lltv / 10n ** 18n;
811
+ const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
812
+ totalAdditional += maxAdditional;
813
+ byMarket.push({
814
+ collateralToken: m.collateralAsset.symbol,
815
+ maxAdditional,
816
+ currentDebt,
817
+ collateralValue: collateralValueInLoan
818
+ });
819
+ } catch {
820
+ continue;
821
+ }
822
+ }
823
+ return { total: totalAdditional, byMarket };
824
+ }
825
+ // ════════════════════════════════════════════════════════
826
+ // Market Rates & Yield Estimation
827
+ // ════════════════════════════════════════════════════════
828
+ /**
829
+ * Fetch current supply/borrow APY for a collateral market from Morpho GraphQL API.
830
+ *
831
+ * Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
832
+ * is what lenders earn; borrow APY is what borrowers pay.
833
+ */
834
+ async getMarketRates(collateralSymbolOrAddress) {
835
+ const chainId = this.config.chainId;
836
+ const usdcAddr = this.config.contracts.usdc.toLowerCase();
837
+ let collateralFilter = "";
838
+ if (collateralSymbolOrAddress) {
839
+ const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
840
+ const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
841
+ collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
842
+ }
843
+ const query = `{
844
+ markets(
845
+ first: 50
846
+ orderBy: SupplyAssetsUsd
847
+ orderDirection: Desc
848
+ where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"]${collateralFilter} }
849
+ ) {
850
+ items {
851
+ uniqueKey
852
+ lltv
853
+ loanAsset { address symbol decimals }
854
+ collateralAsset { address symbol decimals }
855
+ state {
856
+ borrowAssets
857
+ supplyAssets
858
+ utilization
859
+ supplyApy
860
+ borrowApy
861
+ }
862
+ }
863
+ }
864
+ }`;
865
+ try {
866
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
867
+ const items = resp.data?.data?.markets?.items ?? [];
868
+ return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress).map((m) => ({
869
+ collateralToken: m.collateralAsset.symbol,
870
+ loanToken: m.loanAsset.symbol,
871
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
872
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
873
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
874
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 1e6 : 0,
875
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 1e6 : 0,
876
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
877
+ marketId: m.uniqueKey
878
+ }));
879
+ } catch {
880
+ return [];
881
+ }
882
+ }
883
+ /**
884
+ * Estimate theoretical yield for a given collateral amount over a period.
885
+ *
886
+ * ⚠️ IMPORTANT: On Morpho Blue, collateral does NOT earn yield directly.
887
+ * This estimates what the collateral WOULD earn if it were instead supplied
888
+ * as a lender (not used as collateral). This is a theoretical upper bound
889
+ * useful for setting spending caps.
890
+ *
891
+ * @param collateralSymbol - e.g. 'WETH'
892
+ * @param amount - collateral amount in human-readable (e.g. '1.5')
893
+ * @param periodDays - estimation period in days (default: 1)
894
+ * @param ethPriceUsd - ETH price in USD for value conversion (if not provided, uses oracle)
895
+ * @returns Estimated yield in USD for the period
896
+ */
897
+ async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
898
+ const colInfo = BASE_COLLATERALS[collateralSymbol];
899
+ if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
900
+ const rates = await this.getMarketRates(collateralSymbol);
901
+ if (rates.length === 0) {
902
+ throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
903
+ }
904
+ const market = rates[0];
905
+ const supplyApy = market.supplyApy;
906
+ let collateralValueUsd;
907
+ if (ethPriceUsd) {
908
+ collateralValueUsd = parseFloat(amount) * ethPriceUsd;
909
+ } else {
910
+ try {
911
+ const params = await this.findMarketForCollateral(collateralSymbol);
912
+ const oracleContract = new import_ethers2.Contract(params.oracle, [
913
+ "function price() view returns (uint256)"
914
+ ], this.provider);
915
+ const oraclePrice = await oracleContract.price();
916
+ const ORACLE_PRICE_SCALE = 10n ** 36n;
917
+ const amountWei = import_ethers2.ethers.parseUnits(amount, colInfo.decimals);
918
+ const valueInUsdc = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
919
+ collateralValueUsd = Number(valueInUsdc) / 1e6;
920
+ } catch {
921
+ throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
922
+ }
923
+ }
924
+ const estimatedYieldUsd = collateralValueUsd * supplyApy * (periodDays / 365);
925
+ return {
926
+ collateralToken: collateralSymbol,
927
+ amount,
928
+ periodDays,
929
+ theoreticalSupplyApy: supplyApy,
930
+ estimatedYieldUsd,
931
+ collateralValueUsd,
932
+ disclaimer: "Collateral on Morpho Blue does NOT earn yield directly. This estimates what it WOULD earn if supplied as a lender instead. Use as a theoretical spending cap."
933
+ };
934
+ }
935
+ // ════════════════════════════════════════════════════════
763
936
  // Lending Operations (all via AgentAccount.executeBatch)
764
937
  // ════════════════════════════════════════════════════════
765
938
  /**
@@ -1152,6 +1325,9 @@ var X402Client = class {
1152
1325
  const client = new import_client.x402Client();
1153
1326
  (0, import_client2.registerExactEvmScheme)(client, { signer });
1154
1327
  this.paidFetch = (0, import_fetch.wrapFetchWithPayment)(fetch, client);
1328
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1329
+ const dailyLimit = config.dailySpendLimitUsdc ? BigInt(Math.round(parseFloat(config.dailySpendLimitUsdc) * 1e6)) : 0n;
1330
+ this._spendingTracker = { date: today, totalBorrowed: 0n, dailyLimit };
1155
1331
  }
1156
1332
  async get(url, opts) {
1157
1333
  return this.request(url, { ...opts, method: "GET" });
@@ -1167,6 +1343,82 @@ var X402Client = class {
1167
1343
  getAddress() {
1168
1344
  return this.address;
1169
1345
  }
1346
+ /** Get the current spending tracker state */
1347
+ getSpendingTracker() {
1348
+ this._resetTrackerIfNewDay();
1349
+ return { ...this._spendingTracker };
1350
+ }
1351
+ /** Get remaining daily spending allowance in USDC (human-readable) */
1352
+ getRemainingDailyAllowance() {
1353
+ this._resetTrackerIfNewDay();
1354
+ if (this._spendingTracker.dailyLimit === 0n) return "unlimited";
1355
+ const remaining = this._spendingTracker.dailyLimit - this._spendingTracker.totalBorrowed;
1356
+ return (Number(remaining > 0n ? remaining : 0n) / 1e6).toFixed(2);
1357
+ }
1358
+ /**
1359
+ * Pay with auto-draw: Make an x402 request with automatic Morpho borrowing.
1360
+ *
1361
+ * Flow:
1362
+ * 1. Check USDC balance on AgentAccount
1363
+ * 2. Probe the URL to discover payment amount (if 402)
1364
+ * 3. If insufficient USDC, calculate deficit
1365
+ * 4. Check spending limit
1366
+ * 5. Borrow from Morpho via MorphoClient
1367
+ * 6. Proceed with x402 payment
1368
+ */
1369
+ async payWithAutoDraw(url, opts) {
1370
+ const { morphoClient, ...fetchOpts } = opts || {};
1371
+ if (!this.config.autoDraw || !morphoClient) {
1372
+ return this.request(url, fetchOpts);
1373
+ }
1374
+ try {
1375
+ const usdcBalance = await morphoClient.getUsdcBalance();
1376
+ console.log(` [auto-draw] AgentAccount USDC balance: ${(Number(usdcBalance) / 1e6).toFixed(2)}`);
1377
+ const paymentAmount = await this._probePaymentAmount(url, fetchOpts);
1378
+ if (paymentAmount !== null) {
1379
+ console.log(` [auto-draw] Payment required: ${(Number(paymentAmount) / 1e6).toFixed(6)} USDC`);
1380
+ const bufferStr = this.config.autoDrawBuffer || "0.5";
1381
+ const buffer = BigInt(Math.round(parseFloat(bufferStr) * 1e6));
1382
+ const needed = paymentAmount + buffer;
1383
+ if (usdcBalance < needed) {
1384
+ const deficit = needed - usdcBalance;
1385
+ console.log(` [auto-draw] Insufficient balance. Need to borrow ${(Number(deficit) / 1e6).toFixed(2)} USDC`);
1386
+ const limitCheck = await this._checkSpendingLimit(deficit, morphoClient);
1387
+ if (!limitCheck.allowed) {
1388
+ return {
1389
+ success: false,
1390
+ error: `Auto-draw blocked: ${limitCheck.reason}`
1391
+ };
1392
+ }
1393
+ const maxBorrowable = await morphoClient.getMaxBorrowable();
1394
+ if (maxBorrowable.total < deficit) {
1395
+ return {
1396
+ success: false,
1397
+ error: `Auto-draw failed: insufficient collateral. Need ${(Number(deficit) / 1e6).toFixed(2)} USDC but can only borrow ${(Number(maxBorrowable.total) / 1e6).toFixed(2)} USDC more.`
1398
+ };
1399
+ }
1400
+ const borrowAmount = (Number(deficit) / 1e6).toFixed(6);
1401
+ console.log(` [auto-draw] Borrowing ${borrowAmount} USDC from Morpho...`);
1402
+ const borrowResult = await morphoClient.borrow(borrowAmount);
1403
+ console.log(` [auto-draw] Borrow tx: ${borrowResult.tx}`);
1404
+ this._trackSpending(deficit);
1405
+ const result = await this.request(url, fetchOpts);
1406
+ return {
1407
+ ...result,
1408
+ autoDrawInfo: {
1409
+ borrowed: borrowAmount,
1410
+ borrowTx: borrowResult.tx,
1411
+ reason: `USDC balance insufficient (had ${(Number(usdcBalance) / 1e6).toFixed(2)}, needed ${(Number(needed) / 1e6).toFixed(2)})`
1412
+ }
1413
+ };
1414
+ }
1415
+ }
1416
+ return this.request(url, fetchOpts);
1417
+ } catch (error) {
1418
+ console.log(` [auto-draw] Auto-draw check failed: ${error instanceof Error ? error.message : String(error)}. Proceeding with normal request.`);
1419
+ return this.request(url, fetchOpts);
1420
+ }
1421
+ }
1170
1422
  // ──────────── Core request — @x402/fetch handles 402 automatically ────────────
1171
1423
  async request(url, options) {
1172
1424
  try {
@@ -1216,6 +1468,102 @@ var X402Client = class {
1216
1468
  };
1217
1469
  }
1218
1470
  }
1471
+ // ──────────── Auto-Draw Helpers ────────────
1472
+ /**
1473
+ * Probe a URL to discover payment requirements without paying.
1474
+ * Makes a request and parses the 402 PAYMENT-REQUIRED header.
1475
+ * @returns Payment amount in raw USDC units (6 decimals), or null if not a 402.
1476
+ */
1477
+ async _probePaymentAmount(url, options) {
1478
+ try {
1479
+ const response = await fetch(url, {
1480
+ ...options,
1481
+ headers: {
1482
+ ...options?.headers,
1483
+ "X-Agent-Id": this.config.agentId || ""
1484
+ }
1485
+ });
1486
+ if (response.status !== 402) return null;
1487
+ const paymentHeader = response.headers.get("X-PAYMENT") || response.headers.get("PAYMENT-REQUIRED");
1488
+ if (!paymentHeader) return null;
1489
+ try {
1490
+ const decoded = JSON.parse(Buffer.from(paymentHeader, "base64").toString("utf-8"));
1491
+ const requirements = Array.isArray(decoded) ? decoded : decoded.accepts || [decoded];
1492
+ if (requirements.length > 0) {
1493
+ const amount = requirements[0].maxAmountRequired || requirements[0].amount;
1494
+ if (amount) {
1495
+ return BigInt(amount);
1496
+ }
1497
+ }
1498
+ } catch {
1499
+ }
1500
+ return null;
1501
+ } catch {
1502
+ return null;
1503
+ }
1504
+ }
1505
+ /** Reset spending tracker if it's a new day */
1506
+ _resetTrackerIfNewDay() {
1507
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1508
+ if (this._spendingTracker.date !== today) {
1509
+ this._spendingTracker = {
1510
+ date: today,
1511
+ totalBorrowed: 0n,
1512
+ dailyLimit: this._spendingTracker.dailyLimit
1513
+ };
1514
+ }
1515
+ }
1516
+ /** Track a new spending amount */
1517
+ _trackSpending(amount) {
1518
+ this._resetTrackerIfNewDay();
1519
+ this._spendingTracker.totalBorrowed += amount;
1520
+ }
1521
+ /**
1522
+ * Check if a borrow amount is within spending limits.
1523
+ * Considers both fixed daily limits and yield-limited spending.
1524
+ */
1525
+ async _checkSpendingLimit(amount, morphoClient) {
1526
+ this._resetTrackerIfNewDay();
1527
+ if (this.config.yieldLimitedSpending) {
1528
+ try {
1529
+ const status = await morphoClient.getStatus();
1530
+ let totalDailyYieldUsdc = 0;
1531
+ for (const pos of status.positions) {
1532
+ if (parseFloat(pos.collateral) > 0) {
1533
+ try {
1534
+ const estimate = await morphoClient.getYieldEstimate(
1535
+ pos.collateralToken,
1536
+ pos.collateral,
1537
+ 1
1538
+ // 1 day
1539
+ );
1540
+ totalDailyYieldUsdc += estimate.estimatedYieldUsd;
1541
+ } catch {
1542
+ }
1543
+ }
1544
+ }
1545
+ const yieldLimit = BigInt(Math.round(totalDailyYieldUsdc * 1e6));
1546
+ const newTotal = this._spendingTracker.totalBorrowed + amount;
1547
+ if (yieldLimit > 0n && newTotal > yieldLimit) {
1548
+ return {
1549
+ allowed: false,
1550
+ reason: `Yield-limited spending exceeded. Daily yield cap: $${(Number(yieldLimit) / 1e6).toFixed(2)}, already spent: $${(Number(this._spendingTracker.totalBorrowed) / 1e6).toFixed(2)}, requested: $${(Number(amount) / 1e6).toFixed(2)}`
1551
+ };
1552
+ }
1553
+ } catch {
1554
+ }
1555
+ }
1556
+ if (this._spendingTracker.dailyLimit > 0n) {
1557
+ const newTotal = this._spendingTracker.totalBorrowed + amount;
1558
+ if (newTotal > this._spendingTracker.dailyLimit) {
1559
+ return {
1560
+ allowed: false,
1561
+ reason: `Daily spending limit exceeded. Limit: $${(Number(this._spendingTracker.dailyLimit) / 1e6).toFixed(2)}, already spent: $${(Number(this._spendingTracker.totalBorrowed) / 1e6).toFixed(2)}, requested: $${(Number(amount) / 1e6).toFixed(2)}`
1562
+ };
1563
+ }
1564
+ }
1565
+ return { allowed: true };
1566
+ }
1219
1567
  };
1220
1568
 
1221
1569
  // src/clients/ScoringClient.ts