@agether/sdk 2.14.1 → 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 CHANGED
@@ -2236,16 +2236,18 @@ var AgetherClient_exports = {};
2236
2236
  __export(AgetherClient_exports, {
2237
2237
  AgetherClient: () => AgetherClient
2238
2238
  });
2239
- var import_ethers2, MODE_SINGLE2, erc20Iface2, KNOWN_TOKENS, AgetherClient;
2239
+ var import_ethers2, import_axios2, MODE_SINGLE2, erc20Iface2, MORPHO_API_URL2, KNOWN_TOKENS, AgetherClient;
2240
2240
  var init_AgetherClient = __esm({
2241
2241
  "src/clients/AgetherClient.ts"() {
2242
2242
  "use strict";
2243
2243
  import_ethers2 = require("ethers");
2244
+ import_axios2 = __toESM(require("axios"));
2244
2245
  init_types();
2245
2246
  init_abis();
2246
2247
  init_config();
2247
2248
  MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
2248
2249
  erc20Iface2 = new import_ethers2.ethers.Interface(ERC20_ABI);
2250
+ MORPHO_API_URL2 = "https://api.morpho.org/graphql";
2249
2251
  KNOWN_TOKENS = {
2250
2252
  [8453 /* Base */]: {
2251
2253
  WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
@@ -2260,6 +2262,17 @@ var init_AgetherClient = __esm({
2260
2262
  };
2261
2263
  AgetherClient = class _AgetherClient {
2262
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();
2263
2276
  this.config = options.config;
2264
2277
  this.signer = options.signer;
2265
2278
  this.agentId = options.agentId;
@@ -2544,21 +2557,10 @@ var init_AgetherClient = __esm({
2544
2557
  }
2545
2558
  /**
2546
2559
  * Fund the Safe account with USDC from EOA.
2547
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
2560
+ * @deprecated Use `fundAccountToken('USDC', amount)` instead.
2548
2561
  */
2549
2562
  async fundAccount(usdcAmount) {
2550
- const acctAddr = await this.getAccountAddress();
2551
- const usdc = new import_ethers2.Contract(this.config.contracts.usdc, ERC20_ABI, this.signer);
2552
- const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
2553
- const tx = await usdc.transfer(acctAddr, amount);
2554
- const receipt = await tx.wait();
2555
- this._refreshSigner();
2556
- return {
2557
- txHash: receipt.hash,
2558
- blockNumber: receipt.blockNumber,
2559
- status: receipt.status === 1 ? "success" : "failed",
2560
- gasUsed: receipt.gasUsed
2561
- };
2563
+ return this.fundAccountToken("USDC", usdcAmount);
2562
2564
  }
2563
2565
  // ════════════════════════════════════════════════════════
2564
2566
  // Withdrawals (Safe → EOA via UserOps)
@@ -2610,6 +2612,138 @@ var init_AgetherClient = __esm({
2610
2612
  return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
2611
2613
  }
2612
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
+ // ════════════════════════════════════════════════════════
2613
2747
  // Sponsorship
2614
2748
  // ════════════════════════════════════════════════════════
2615
2749
  /**
@@ -2738,14 +2872,6 @@ var init_AgetherClient = __esm({
2738
2872
  }
2739
2873
  return this._eoaAddress;
2740
2874
  }
2741
- /**
2742
- * Resolve a token symbol or address to { address, symbol, decimals }.
2743
- *
2744
- * Supports:
2745
- * - `'USDC'` → from chain config
2746
- * - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
2747
- * - `'0x...'` address → reads decimals and symbol onchain
2748
- */
2749
2875
  async _resolveToken(symbolOrAddress) {
2750
2876
  if (symbolOrAddress.toUpperCase() === "USDC") {
2751
2877
  return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
@@ -2753,6 +2879,9 @@ var init_AgetherClient = __esm({
2753
2879
  const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
2754
2880
  const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
2755
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;
2756
2885
  if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
2757
2886
  try {
2758
2887
  const token = new import_ethers2.Contract(
@@ -2761,7 +2890,10 @@ var init_AgetherClient = __esm({
2761
2890
  this.signer.provider
2762
2891
  );
2763
2892
  const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
2764
- return { address: symbolOrAddress, symbol, decimals: Number(decimals) };
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;
2765
2897
  } catch (e) {
2766
2898
  throw new AgetherError(
2767
2899
  `Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
@@ -2769,8 +2901,53 @@ var init_AgetherClient = __esm({
2769
2901
  );
2770
2902
  }
2771
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
+ }
2772
2949
  throw new AgetherError(
2773
- `Unknown token: ${symbolOrAddress}. Use a known symbol (USDC, WETH, wstETH, cbETH) or a 0x address.`,
2950
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
2774
2951
  "UNKNOWN_TOKEN"
2775
2952
  );
2776
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
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
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
- * Supports:
328
- * - `'USDC'` → from chain config
329
- * - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
330
- * - `'0x...'` address reads decimals and symbol onchain
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.
package/dist/index.d.ts 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
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
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
- * Supports:
328
- * - `'USDC'` → from chain config
329
- * - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
330
- * - `'0x...'` address reads decimals and symbol onchain
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.
package/dist/index.js CHANGED
@@ -75,6 +75,7 @@ module.exports = __toCommonJS(index_exports);
75
75
 
76
76
  // src/clients/AgetherClient.ts
77
77
  var import_ethers = require("ethers");
78
+ var import_axios = __toESM(require("axios"));
78
79
 
79
80
  // src/types/index.ts
80
81
  var ChainId = /* @__PURE__ */ ((ChainId3) => {
@@ -371,6 +372,7 @@ function getContractAddresses(chainId) {
371
372
  // src/clients/AgetherClient.ts
372
373
  var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
373
374
  var erc20Iface = new import_ethers.ethers.Interface(ERC20_ABI);
375
+ var MORPHO_API_URL = "https://api.morpho.org/graphql";
374
376
  var KNOWN_TOKENS = {
375
377
  [8453 /* Base */]: {
376
378
  WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
@@ -385,6 +387,17 @@ var KNOWN_TOKENS = {
385
387
  };
386
388
  var AgetherClient = class _AgetherClient {
387
389
  constructor(options) {
390
+ /**
391
+ * Resolve a token symbol or address to { address, symbol, decimals }.
392
+ *
393
+ * Resolution order:
394
+ * 1. `'USDC'` → from chain config (instant)
395
+ * 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
396
+ * 3. Dynamic cache populated by previous Morpho API lookups (instant)
397
+ * 4. `'0x...'` address → reads decimals and symbol onchain
398
+ * 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
399
+ */
400
+ this._dynamicTokenCache = /* @__PURE__ */ new Map();
388
401
  this.config = options.config;
389
402
  this.signer = options.signer;
390
403
  this.agentId = options.agentId;
@@ -669,21 +682,10 @@ var AgetherClient = class _AgetherClient {
669
682
  }
670
683
  /**
671
684
  * Fund the Safe account with USDC from EOA.
672
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
685
+ * @deprecated Use `fundAccountToken('USDC', amount)` instead.
673
686
  */
674
687
  async fundAccount(usdcAmount) {
675
- const acctAddr = await this.getAccountAddress();
676
- const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, this.signer);
677
- const amount = import_ethers.ethers.parseUnits(usdcAmount, 6);
678
- const tx = await usdc.transfer(acctAddr, amount);
679
- const receipt = await tx.wait();
680
- this._refreshSigner();
681
- return {
682
- txHash: receipt.hash,
683
- blockNumber: receipt.blockNumber,
684
- status: receipt.status === 1 ? "success" : "failed",
685
- gasUsed: receipt.gasUsed
686
- };
688
+ return this.fundAccountToken("USDC", usdcAmount);
687
689
  }
688
690
  // ════════════════════════════════════════════════════════
689
691
  // Withdrawals (Safe → EOA via UserOps)
@@ -735,6 +737,138 @@ var AgetherClient = class _AgetherClient {
735
737
  return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
736
738
  }
737
739
  // ════════════════════════════════════════════════════════
740
+ // Token Transfers & Approvals (AgentAccount → any address)
741
+ // ════════════════════════════════════════════════════════
742
+ /**
743
+ * Fund the AgentAccount with any ERC-20 token from EOA.
744
+ * Simple ERC-20 transfer (does NOT require a UserOp).
745
+ *
746
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
747
+ * @param amount - Amount to send (human-readable, e.g. '100')
748
+ */
749
+ async fundAccountToken(tokenSymbol, amount) {
750
+ const acctAddr = await this.getAccountAddress();
751
+ const tokenInfo = await this._resolveToken(tokenSymbol);
752
+ const tokenContract = new import_ethers.Contract(tokenInfo.address, ERC20_ABI, this.signer);
753
+ const weiAmount = import_ethers.ethers.parseUnits(amount, tokenInfo.decimals);
754
+ const eoaBalance = await tokenContract.balanceOf(await this._getSignerAddress());
755
+ if (eoaBalance < weiAmount) {
756
+ throw new AgetherError(
757
+ `Insufficient ${tokenInfo.symbol}. EOA has ${import_ethers.ethers.formatUnits(eoaBalance, tokenInfo.decimals)}, need ${amount}.`,
758
+ "INSUFFICIENT_BALANCE"
759
+ );
760
+ }
761
+ const tx = await tokenContract.transfer(acctAddr, weiAmount);
762
+ const receipt = await tx.wait();
763
+ this._refreshSigner();
764
+ return {
765
+ txHash: receipt.hash,
766
+ blockNumber: receipt.blockNumber,
767
+ status: receipt.status === 1 ? "success" : "failed",
768
+ gasUsed: receipt.gasUsed
769
+ };
770
+ }
771
+ /**
772
+ * Transfer any ERC-20 token from AgentAccount to any address or agent.
773
+ * Executes via Safe UserOp (ERC-7579 single execution).
774
+ *
775
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
776
+ * @param amount - Amount to send (e.g. '100' or 'all')
777
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
778
+ */
779
+ async transferToken(tokenSymbol, amount, to) {
780
+ const acctAddr = await this.getAccountAddress();
781
+ const tokenInfo = await this._resolveToken(tokenSymbol);
782
+ const tokenContract = new import_ethers.Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
783
+ let toAddr;
784
+ if (to.address) {
785
+ toAddr = to.address;
786
+ } else if (to.agentId) {
787
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
788
+ if (toAddr === import_ethers.ethers.ZeroAddress) {
789
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
790
+ }
791
+ } else {
792
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
793
+ }
794
+ let weiAmount;
795
+ if (amount === "all") {
796
+ weiAmount = await tokenContract.balanceOf(acctAddr);
797
+ if (weiAmount === 0n) {
798
+ throw new AgetherError(`No ${tokenInfo.symbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
799
+ }
800
+ } else {
801
+ weiAmount = import_ethers.ethers.parseUnits(amount, tokenInfo.decimals);
802
+ }
803
+ const data = erc20Iface.encodeFunctionData("transfer", [toAddr, weiAmount]);
804
+ const receipt = await this._exec(tokenInfo.address, data);
805
+ const actualAmount = amount === "all" ? import_ethers.ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
806
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: toAddr };
807
+ }
808
+ /**
809
+ * Transfer ETH from AgentAccount to any address or agent.
810
+ * Executes via Safe UserOp.
811
+ *
812
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
813
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
814
+ */
815
+ async transferEth(amount, to) {
816
+ const acctAddr = await this.getAccountAddress();
817
+ let toAddr;
818
+ if (to.address) {
819
+ toAddr = to.address;
820
+ } else if (to.agentId) {
821
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
822
+ if (toAddr === import_ethers.ethers.ZeroAddress) {
823
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
824
+ }
825
+ } else {
826
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
827
+ }
828
+ let weiAmount;
829
+ if (amount === "all") {
830
+ weiAmount = await this.signer.provider.getBalance(acctAddr);
831
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
832
+ } else {
833
+ weiAmount = import_ethers.ethers.parseEther(amount);
834
+ }
835
+ const receipt = await this._exec(toAddr, "0x", weiAmount);
836
+ const actualAmount = amount === "all" ? import_ethers.ethers.formatEther(weiAmount) : amount;
837
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: toAddr };
838
+ }
839
+ /**
840
+ * Approve a spender to use ERC-20 tokens from the AgentAccount.
841
+ * Executes via Safe UserOp (ERC-7579 single execution).
842
+ *
843
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
844
+ * @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
845
+ * @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
846
+ */
847
+ async approveToken(tokenSymbol, amount, spender) {
848
+ const tokenInfo = await this._resolveToken(tokenSymbol);
849
+ let spenderAddr;
850
+ if (spender.address) {
851
+ spenderAddr = spender.address;
852
+ } else if (spender.agentId) {
853
+ spenderAddr = await this.agether4337Factory.getAccount(BigInt(spender.agentId));
854
+ if (spenderAddr === import_ethers.ethers.ZeroAddress) {
855
+ throw new AgetherError(`Agent ${spender.agentId} has no account`, "NO_ACCOUNT");
856
+ }
857
+ } else {
858
+ throw new AgetherError("Provide address or agentId as spender", "INVALID_TARGET");
859
+ }
860
+ let weiAmount;
861
+ if (amount === "max") {
862
+ weiAmount = import_ethers.ethers.MaxUint256;
863
+ } else {
864
+ weiAmount = import_ethers.ethers.parseUnits(amount, tokenInfo.decimals);
865
+ }
866
+ const data = erc20Iface.encodeFunctionData("approve", [spenderAddr, weiAmount]);
867
+ const receipt = await this._exec(tokenInfo.address, data);
868
+ const actualAmount = amount === "max" ? "unlimited" : amount;
869
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, spender: spenderAddr };
870
+ }
871
+ // ════════════════════════════════════════════════════════
738
872
  // Sponsorship
739
873
  // ════════════════════════════════════════════════════════
740
874
  /**
@@ -863,14 +997,6 @@ var AgetherClient = class _AgetherClient {
863
997
  }
864
998
  return this._eoaAddress;
865
999
  }
866
- /**
867
- * Resolve a token symbol or address to { address, symbol, decimals }.
868
- *
869
- * Supports:
870
- * - `'USDC'` → from chain config
871
- * - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
872
- * - `'0x...'` address → reads decimals and symbol onchain
873
- */
874
1000
  async _resolveToken(symbolOrAddress) {
875
1001
  if (symbolOrAddress.toUpperCase() === "USDC") {
876
1002
  return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
@@ -878,6 +1004,9 @@ var AgetherClient = class _AgetherClient {
878
1004
  const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
879
1005
  const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
880
1006
  if (bySymbol) return bySymbol;
1007
+ const cacheKey = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
1008
+ const cached = this._dynamicTokenCache.get(cacheKey);
1009
+ if (cached) return cached;
881
1010
  if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
882
1011
  try {
883
1012
  const token = new import_ethers.Contract(
@@ -886,7 +1015,10 @@ var AgetherClient = class _AgetherClient {
886
1015
  this.signer.provider
887
1016
  );
888
1017
  const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
889
- return { address: symbolOrAddress, symbol, decimals: Number(decimals) };
1018
+ const info = { address: symbolOrAddress, symbol, decimals: Number(decimals) };
1019
+ this._dynamicTokenCache.set(symbolOrAddress.toLowerCase(), info);
1020
+ this._dynamicTokenCache.set(symbol.toUpperCase(), info);
1021
+ return info;
890
1022
  } catch (e) {
891
1023
  throw new AgetherError(
892
1024
  `Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
@@ -894,8 +1026,53 @@ var AgetherClient = class _AgetherClient {
894
1026
  );
895
1027
  }
896
1028
  }
1029
+ try {
1030
+ const chainId = this.config.chainId;
1031
+ const query = `{
1032
+ markets(
1033
+ first: 20
1034
+ where: { chainId_in: [${chainId}], search: "${symbolOrAddress}" }
1035
+ ) {
1036
+ items {
1037
+ loanAsset { address symbol decimals }
1038
+ collateralAsset { address symbol decimals }
1039
+ }
1040
+ }
1041
+ }`;
1042
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1043
+ const items = resp.data?.data?.markets?.items ?? [];
1044
+ const sym = symbolOrAddress.toUpperCase();
1045
+ for (const m of items) {
1046
+ if (m.loanAsset?.symbol?.toUpperCase() === sym) {
1047
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
1048
+ this._dynamicTokenCache.set(sym, info);
1049
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
1050
+ return info;
1051
+ }
1052
+ if (m.collateralAsset?.symbol?.toUpperCase() === sym) {
1053
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
1054
+ this._dynamicTokenCache.set(sym, info);
1055
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
1056
+ return info;
1057
+ }
1058
+ }
1059
+ for (const m of items) {
1060
+ if (m.loanAsset?.symbol) {
1061
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
1062
+ this._dynamicTokenCache.set(m.loanAsset.symbol.toUpperCase(), info);
1063
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
1064
+ }
1065
+ if (m.collateralAsset?.symbol) {
1066
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
1067
+ this._dynamicTokenCache.set(m.collateralAsset.symbol.toUpperCase(), info);
1068
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
1069
+ }
1070
+ }
1071
+ } catch (e) {
1072
+ console.warn("[agether] Morpho token search failed:", e instanceof Error ? e.message : e);
1073
+ }
897
1074
  throw new AgetherError(
898
- `Unknown token: ${symbolOrAddress}. Use a known symbol (USDC, WETH, wstETH, cbETH) or a 0x address.`,
1075
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
899
1076
  "UNKNOWN_TOKEN"
900
1077
  );
901
1078
  }
@@ -1029,8 +1206,8 @@ var AgetherClient = class _AgetherClient {
1029
1206
 
1030
1207
  // src/clients/MorphoClient.ts
1031
1208
  var import_ethers2 = require("ethers");
1032
- var import_axios = __toESM(require("axios"));
1033
- var MORPHO_API_URL = "https://api.morpho.org/graphql";
1209
+ var import_axios2 = __toESM(require("axios"));
1210
+ var MORPHO_API_URL2 = "https://api.morpho.org/graphql";
1034
1211
  var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1035
1212
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
1036
1213
  var morphoIface = new import_ethers2.ethers.Interface(MORPHO_BLUE_ABI);
@@ -1176,7 +1353,7 @@ var MorphoClient = class {
1176
1353
  }
1177
1354
  }`;
1178
1355
  try {
1179
- const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1356
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1180
1357
  const items = resp.data?.data?.markets?.items ?? [];
1181
1358
  this._discoveredMarkets = items.map((m) => ({
1182
1359
  uniqueKey: m.uniqueKey,
@@ -1360,7 +1537,7 @@ var MorphoClient = class {
1360
1537
  }
1361
1538
  }
1362
1539
  }`;
1363
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 15e3 });
1540
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 15e3 });
1364
1541
  const items = resp.data?.data?.marketPositions?.items ?? [];
1365
1542
  for (const item of items) {
1366
1543
  const supplyShares = BigInt(item.supplyShares ?? "0");
@@ -1579,7 +1756,7 @@ var MorphoClient = class {
1579
1756
  }
1580
1757
  }`;
1581
1758
  try {
1582
- const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1759
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1583
1760
  let items = resp.data?.data?.markets?.items ?? [];
1584
1761
  if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
1585
1762
  const sym = collateralSymbolOrAddress.toUpperCase();
@@ -1645,7 +1822,7 @@ var MorphoClient = class {
1645
1822
  }
1646
1823
  }`;
1647
1824
  try {
1648
- const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1825
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1649
1826
  let items = resp.data?.data?.markets?.items ?? [];
1650
1827
  items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress);
1651
1828
  const searchUpper = search.toUpperCase();
@@ -2038,7 +2215,7 @@ var MorphoClient = class {
2038
2215
  }
2039
2216
  }
2040
2217
  }`;
2041
- const posResp = await import_axios.default.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
2218
+ const posResp = await import_axios2.default.post(MORPHO_API_URL2, { query: positionsQuery }, { timeout: 15e3 });
2042
2219
  const user = posResp.data?.data?.userByAddress;
2043
2220
  if (!user?.marketPositions) return [];
2044
2221
  const activePositions = user.marketPositions.filter(
@@ -2732,7 +2909,7 @@ var MorphoClient = class {
2732
2909
  }
2733
2910
  }
2734
2911
  }`;
2735
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2912
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2736
2913
  const items = resp.data?.data?.marketPositions?.items ?? [];
2737
2914
  for (const item of items) {
2738
2915
  if (BigInt(item.collateral ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2800,7 +2977,7 @@ var MorphoClient = class {
2800
2977
  }
2801
2978
  }
2802
2979
  }`;
2803
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2980
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2804
2981
  const items = resp.data?.data?.marketPositions?.items ?? [];
2805
2982
  for (const item of items) {
2806
2983
  if (BigInt(item.supplyShares ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2952,7 +3129,7 @@ var MorphoClient = class {
2952
3129
  }
2953
3130
  }
2954
3131
  }`;
2955
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
3132
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: txQuery }, { timeout: 15e3 });
2956
3133
  const txData = resp.data?.data?.transactions;
2957
3134
  if (!txData?.items) break;
2958
3135
  for (const tx of txData.items) {
@@ -2977,7 +3154,7 @@ var MorphoClient = class {
2977
3154
  };
2978
3155
 
2979
3156
  // src/clients/ScoringClient.ts
2980
- var import_axios2 = __toESM(require("axios"));
3157
+ var import_axios3 = __toESM(require("axios"));
2981
3158
 
2982
3159
  // src/clients/X402Client.ts
2983
3160
  var import_fetch = require("@x402/fetch");
@@ -3339,7 +3516,7 @@ var ScoringClient = class {
3339
3516
  constructor(config) {
3340
3517
  this.endpoint = config.endpoint;
3341
3518
  this.defaultChainId = config.chainId;
3342
- this.client = import_axios2.default.create({
3519
+ this.client = import_axios3.default.create({
3343
3520
  baseURL: config.endpoint,
3344
3521
  headers: { "Content-Type": "application/json" },
3345
3522
  timeout: 3e4
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/clients/AgetherClient.ts
2
2
  import { ethers, Contract } from "ethers";
3
+ import axios from "axios";
3
4
 
4
5
  // src/types/index.ts
5
6
  var ChainId = /* @__PURE__ */ ((ChainId3) => {
@@ -296,6 +297,7 @@ function getContractAddresses(chainId) {
296
297
  // src/clients/AgetherClient.ts
297
298
  var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
298
299
  var erc20Iface = new ethers.Interface(ERC20_ABI);
300
+ var MORPHO_API_URL = "https://api.morpho.org/graphql";
299
301
  var KNOWN_TOKENS = {
300
302
  [8453 /* Base */]: {
301
303
  WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
@@ -310,6 +312,17 @@ var KNOWN_TOKENS = {
310
312
  };
311
313
  var AgetherClient = class _AgetherClient {
312
314
  constructor(options) {
315
+ /**
316
+ * Resolve a token symbol or address to { address, symbol, decimals }.
317
+ *
318
+ * Resolution order:
319
+ * 1. `'USDC'` → from chain config (instant)
320
+ * 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
321
+ * 3. Dynamic cache populated by previous Morpho API lookups (instant)
322
+ * 4. `'0x...'` address → reads decimals and symbol onchain
323
+ * 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
324
+ */
325
+ this._dynamicTokenCache = /* @__PURE__ */ new Map();
313
326
  this.config = options.config;
314
327
  this.signer = options.signer;
315
328
  this.agentId = options.agentId;
@@ -594,21 +607,10 @@ var AgetherClient = class _AgetherClient {
594
607
  }
595
608
  /**
596
609
  * Fund the Safe account with USDC from EOA.
597
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
610
+ * @deprecated Use `fundAccountToken('USDC', amount)` instead.
598
611
  */
599
612
  async fundAccount(usdcAmount) {
600
- const acctAddr = await this.getAccountAddress();
601
- const usdc = new Contract(this.config.contracts.usdc, ERC20_ABI, this.signer);
602
- const amount = ethers.parseUnits(usdcAmount, 6);
603
- const tx = await usdc.transfer(acctAddr, amount);
604
- const receipt = await tx.wait();
605
- this._refreshSigner();
606
- return {
607
- txHash: receipt.hash,
608
- blockNumber: receipt.blockNumber,
609
- status: receipt.status === 1 ? "success" : "failed",
610
- gasUsed: receipt.gasUsed
611
- };
613
+ return this.fundAccountToken("USDC", usdcAmount);
612
614
  }
613
615
  // ════════════════════════════════════════════════════════
614
616
  // Withdrawals (Safe → EOA via UserOps)
@@ -660,6 +662,138 @@ var AgetherClient = class _AgetherClient {
660
662
  return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
661
663
  }
662
664
  // ════════════════════════════════════════════════════════
665
+ // Token Transfers & Approvals (AgentAccount → any address)
666
+ // ════════════════════════════════════════════════════════
667
+ /**
668
+ * Fund the AgentAccount with any ERC-20 token from EOA.
669
+ * Simple ERC-20 transfer (does NOT require a UserOp).
670
+ *
671
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
672
+ * @param amount - Amount to send (human-readable, e.g. '100')
673
+ */
674
+ async fundAccountToken(tokenSymbol, amount) {
675
+ const acctAddr = await this.getAccountAddress();
676
+ const tokenInfo = await this._resolveToken(tokenSymbol);
677
+ const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer);
678
+ const weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
679
+ const eoaBalance = await tokenContract.balanceOf(await this._getSignerAddress());
680
+ if (eoaBalance < weiAmount) {
681
+ throw new AgetherError(
682
+ `Insufficient ${tokenInfo.symbol}. EOA has ${ethers.formatUnits(eoaBalance, tokenInfo.decimals)}, need ${amount}.`,
683
+ "INSUFFICIENT_BALANCE"
684
+ );
685
+ }
686
+ const tx = await tokenContract.transfer(acctAddr, weiAmount);
687
+ const receipt = await tx.wait();
688
+ this._refreshSigner();
689
+ return {
690
+ txHash: receipt.hash,
691
+ blockNumber: receipt.blockNumber,
692
+ status: receipt.status === 1 ? "success" : "failed",
693
+ gasUsed: receipt.gasUsed
694
+ };
695
+ }
696
+ /**
697
+ * Transfer any ERC-20 token from AgentAccount to any address or agent.
698
+ * Executes via Safe UserOp (ERC-7579 single execution).
699
+ *
700
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
701
+ * @param amount - Amount to send (e.g. '100' or 'all')
702
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
703
+ */
704
+ async transferToken(tokenSymbol, amount, to) {
705
+ const acctAddr = await this.getAccountAddress();
706
+ const tokenInfo = await this._resolveToken(tokenSymbol);
707
+ const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
708
+ let toAddr;
709
+ if (to.address) {
710
+ toAddr = to.address;
711
+ } else if (to.agentId) {
712
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
713
+ if (toAddr === ethers.ZeroAddress) {
714
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
715
+ }
716
+ } else {
717
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
718
+ }
719
+ let weiAmount;
720
+ if (amount === "all") {
721
+ weiAmount = await tokenContract.balanceOf(acctAddr);
722
+ if (weiAmount === 0n) {
723
+ throw new AgetherError(`No ${tokenInfo.symbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
724
+ }
725
+ } else {
726
+ weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
727
+ }
728
+ const data = erc20Iface.encodeFunctionData("transfer", [toAddr, weiAmount]);
729
+ const receipt = await this._exec(tokenInfo.address, data);
730
+ const actualAmount = amount === "all" ? ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
731
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: toAddr };
732
+ }
733
+ /**
734
+ * Transfer ETH from AgentAccount to any address or agent.
735
+ * Executes via Safe UserOp.
736
+ *
737
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
738
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
739
+ */
740
+ async transferEth(amount, to) {
741
+ const acctAddr = await this.getAccountAddress();
742
+ let toAddr;
743
+ if (to.address) {
744
+ toAddr = to.address;
745
+ } else if (to.agentId) {
746
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
747
+ if (toAddr === ethers.ZeroAddress) {
748
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
749
+ }
750
+ } else {
751
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
752
+ }
753
+ let weiAmount;
754
+ if (amount === "all") {
755
+ weiAmount = await this.signer.provider.getBalance(acctAddr);
756
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
757
+ } else {
758
+ weiAmount = ethers.parseEther(amount);
759
+ }
760
+ const receipt = await this._exec(toAddr, "0x", weiAmount);
761
+ const actualAmount = amount === "all" ? ethers.formatEther(weiAmount) : amount;
762
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: toAddr };
763
+ }
764
+ /**
765
+ * Approve a spender to use ERC-20 tokens from the AgentAccount.
766
+ * Executes via Safe UserOp (ERC-7579 single execution).
767
+ *
768
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
769
+ * @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
770
+ * @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
771
+ */
772
+ async approveToken(tokenSymbol, amount, spender) {
773
+ const tokenInfo = await this._resolveToken(tokenSymbol);
774
+ let spenderAddr;
775
+ if (spender.address) {
776
+ spenderAddr = spender.address;
777
+ } else if (spender.agentId) {
778
+ spenderAddr = await this.agether4337Factory.getAccount(BigInt(spender.agentId));
779
+ if (spenderAddr === ethers.ZeroAddress) {
780
+ throw new AgetherError(`Agent ${spender.agentId} has no account`, "NO_ACCOUNT");
781
+ }
782
+ } else {
783
+ throw new AgetherError("Provide address or agentId as spender", "INVALID_TARGET");
784
+ }
785
+ let weiAmount;
786
+ if (amount === "max") {
787
+ weiAmount = ethers.MaxUint256;
788
+ } else {
789
+ weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
790
+ }
791
+ const data = erc20Iface.encodeFunctionData("approve", [spenderAddr, weiAmount]);
792
+ const receipt = await this._exec(tokenInfo.address, data);
793
+ const actualAmount = amount === "max" ? "unlimited" : amount;
794
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, spender: spenderAddr };
795
+ }
796
+ // ════════════════════════════════════════════════════════
663
797
  // Sponsorship
664
798
  // ════════════════════════════════════════════════════════
665
799
  /**
@@ -788,14 +922,6 @@ var AgetherClient = class _AgetherClient {
788
922
  }
789
923
  return this._eoaAddress;
790
924
  }
791
- /**
792
- * Resolve a token symbol or address to { address, symbol, decimals }.
793
- *
794
- * Supports:
795
- * - `'USDC'` → from chain config
796
- * - Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry
797
- * - `'0x...'` address → reads decimals and symbol onchain
798
- */
799
925
  async _resolveToken(symbolOrAddress) {
800
926
  if (symbolOrAddress.toUpperCase() === "USDC") {
801
927
  return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
@@ -803,6 +929,9 @@ var AgetherClient = class _AgetherClient {
803
929
  const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
804
930
  const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
805
931
  if (bySymbol) return bySymbol;
932
+ const cacheKey = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
933
+ const cached = this._dynamicTokenCache.get(cacheKey);
934
+ if (cached) return cached;
806
935
  if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
807
936
  try {
808
937
  const token = new Contract(
@@ -811,7 +940,10 @@ var AgetherClient = class _AgetherClient {
811
940
  this.signer.provider
812
941
  );
813
942
  const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
814
- return { address: symbolOrAddress, symbol, decimals: Number(decimals) };
943
+ const info = { address: symbolOrAddress, symbol, decimals: Number(decimals) };
944
+ this._dynamicTokenCache.set(symbolOrAddress.toLowerCase(), info);
945
+ this._dynamicTokenCache.set(symbol.toUpperCase(), info);
946
+ return info;
815
947
  } catch (e) {
816
948
  throw new AgetherError(
817
949
  `Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
@@ -819,8 +951,53 @@ var AgetherClient = class _AgetherClient {
819
951
  );
820
952
  }
821
953
  }
954
+ try {
955
+ const chainId = this.config.chainId;
956
+ const query = `{
957
+ markets(
958
+ first: 20
959
+ where: { chainId_in: [${chainId}], search: "${symbolOrAddress}" }
960
+ ) {
961
+ items {
962
+ loanAsset { address symbol decimals }
963
+ collateralAsset { address symbol decimals }
964
+ }
965
+ }
966
+ }`;
967
+ const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
968
+ const items = resp.data?.data?.markets?.items ?? [];
969
+ const sym = symbolOrAddress.toUpperCase();
970
+ for (const m of items) {
971
+ if (m.loanAsset?.symbol?.toUpperCase() === sym) {
972
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
973
+ this._dynamicTokenCache.set(sym, info);
974
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
975
+ return info;
976
+ }
977
+ if (m.collateralAsset?.symbol?.toUpperCase() === sym) {
978
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
979
+ this._dynamicTokenCache.set(sym, info);
980
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
981
+ return info;
982
+ }
983
+ }
984
+ for (const m of items) {
985
+ if (m.loanAsset?.symbol) {
986
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
987
+ this._dynamicTokenCache.set(m.loanAsset.symbol.toUpperCase(), info);
988
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
989
+ }
990
+ if (m.collateralAsset?.symbol) {
991
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
992
+ this._dynamicTokenCache.set(m.collateralAsset.symbol.toUpperCase(), info);
993
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
994
+ }
995
+ }
996
+ } catch (e) {
997
+ console.warn("[agether] Morpho token search failed:", e instanceof Error ? e.message : e);
998
+ }
822
999
  throw new AgetherError(
823
- `Unknown token: ${symbolOrAddress}. Use a known symbol (USDC, WETH, wstETH, cbETH) or a 0x address.`,
1000
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
824
1001
  "UNKNOWN_TOKEN"
825
1002
  );
826
1003
  }
@@ -954,8 +1131,8 @@ var AgetherClient = class _AgetherClient {
954
1131
 
955
1132
  // src/clients/MorphoClient.ts
956
1133
  import { ethers as ethers2, Contract as Contract2 } from "ethers";
957
- import axios from "axios";
958
- var MORPHO_API_URL = "https://api.morpho.org/graphql";
1134
+ import axios2 from "axios";
1135
+ var MORPHO_API_URL2 = "https://api.morpho.org/graphql";
959
1136
  var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
960
1137
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
961
1138
  var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
@@ -1101,7 +1278,7 @@ var MorphoClient = class {
1101
1278
  }
1102
1279
  }`;
1103
1280
  try {
1104
- const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1281
+ const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1105
1282
  const items = resp.data?.data?.markets?.items ?? [];
1106
1283
  this._discoveredMarkets = items.map((m) => ({
1107
1284
  uniqueKey: m.uniqueKey,
@@ -1285,7 +1462,7 @@ var MorphoClient = class {
1285
1462
  }
1286
1463
  }
1287
1464
  }`;
1288
- const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 15e3 });
1465
+ const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 15e3 });
1289
1466
  const items = resp.data?.data?.marketPositions?.items ?? [];
1290
1467
  for (const item of items) {
1291
1468
  const supplyShares = BigInt(item.supplyShares ?? "0");
@@ -1504,7 +1681,7 @@ var MorphoClient = class {
1504
1681
  }
1505
1682
  }`;
1506
1683
  try {
1507
- const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1684
+ const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1508
1685
  let items = resp.data?.data?.markets?.items ?? [];
1509
1686
  if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
1510
1687
  const sym = collateralSymbolOrAddress.toUpperCase();
@@ -1570,7 +1747,7 @@ var MorphoClient = class {
1570
1747
  }
1571
1748
  }`;
1572
1749
  try {
1573
- const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1750
+ const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1574
1751
  let items = resp.data?.data?.markets?.items ?? [];
1575
1752
  items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress);
1576
1753
  const searchUpper = search.toUpperCase();
@@ -1963,7 +2140,7 @@ var MorphoClient = class {
1963
2140
  }
1964
2141
  }
1965
2142
  }`;
1966
- const posResp = await axios.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
2143
+ const posResp = await axios2.post(MORPHO_API_URL2, { query: positionsQuery }, { timeout: 15e3 });
1967
2144
  const user = posResp.data?.data?.userByAddress;
1968
2145
  if (!user?.marketPositions) return [];
1969
2146
  const activePositions = user.marketPositions.filter(
@@ -2657,7 +2834,7 @@ var MorphoClient = class {
2657
2834
  }
2658
2835
  }
2659
2836
  }`;
2660
- const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2837
+ const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2661
2838
  const items = resp.data?.data?.marketPositions?.items ?? [];
2662
2839
  for (const item of items) {
2663
2840
  if (BigInt(item.collateral ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2725,7 +2902,7 @@ var MorphoClient = class {
2725
2902
  }
2726
2903
  }
2727
2904
  }`;
2728
- const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2905
+ const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2729
2906
  const items = resp.data?.data?.marketPositions?.items ?? [];
2730
2907
  for (const item of items) {
2731
2908
  if (BigInt(item.supplyShares ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2877,7 +3054,7 @@ var MorphoClient = class {
2877
3054
  }
2878
3055
  }
2879
3056
  }`;
2880
- const resp = await axios.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
3057
+ const resp = await axios2.post(MORPHO_API_URL2, { query: txQuery }, { timeout: 15e3 });
2881
3058
  const txData = resp.data?.data?.transactions;
2882
3059
  if (!txData?.items) break;
2883
3060
  for (const tx of txData.items) {
@@ -2902,7 +3079,7 @@ var MorphoClient = class {
2902
3079
  };
2903
3080
 
2904
3081
  // src/clients/ScoringClient.ts
2905
- import axios2 from "axios";
3082
+ import axios3 from "axios";
2906
3083
 
2907
3084
  // src/clients/X402Client.ts
2908
3085
  import { wrapFetchWithPayment } from "@x402/fetch";
@@ -3264,7 +3441,7 @@ var ScoringClient = class {
3264
3441
  constructor(config) {
3265
3442
  this.endpoint = config.endpoint;
3266
3443
  this.defaultChainId = config.chainId;
3267
- this.client = axios2.create({
3444
+ this.client = axios3.create({
3268
3445
  baseURL: config.endpoint,
3269
3446
  headers: { "Content-Type": "application/json" },
3270
3447
  timeout: 3e4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/sdk",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "TypeScript SDK for Agether - autonomous credit for AI agents on Ethereum & Base",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",