@agether/sdk 2.14.1 → 2.15.1

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;
@@ -2542,24 +2555,6 @@ var init_AgetherClient = __esm({
2542
2555
  }
2543
2556
  return result;
2544
2557
  }
2545
- /**
2546
- * Fund the Safe account with USDC from EOA.
2547
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
2548
- */
2549
- 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
- };
2562
- }
2563
2558
  // ════════════════════════════════════════════════════════
2564
2559
  // Withdrawals (Safe → EOA via UserOps)
2565
2560
  // ════════════════════════════════════════════════════════
@@ -2610,6 +2605,138 @@ var init_AgetherClient = __esm({
2610
2605
  return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
2611
2606
  }
2612
2607
  // ════════════════════════════════════════════════════════
2608
+ // Token Transfers & Approvals (AgentAccount → any address)
2609
+ // ════════════════════════════════════════════════════════
2610
+ /**
2611
+ * Fund the AgentAccount with any ERC-20 token from EOA.
2612
+ * Simple ERC-20 transfer (does NOT require a UserOp).
2613
+ *
2614
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
2615
+ * @param amount - Amount to send (human-readable, e.g. '100')
2616
+ */
2617
+ async fundAccountToken(tokenSymbol, amount) {
2618
+ const acctAddr = await this.getAccountAddress();
2619
+ const tokenInfo = await this._resolveToken(tokenSymbol);
2620
+ const tokenContract = new import_ethers2.Contract(tokenInfo.address, ERC20_ABI, this.signer);
2621
+ const weiAmount = import_ethers2.ethers.parseUnits(amount, tokenInfo.decimals);
2622
+ const eoaBalance = await tokenContract.balanceOf(await this._getSignerAddress());
2623
+ if (eoaBalance < weiAmount) {
2624
+ throw new AgetherError(
2625
+ `Insufficient ${tokenInfo.symbol}. EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, tokenInfo.decimals)}, need ${amount}.`,
2626
+ "INSUFFICIENT_BALANCE"
2627
+ );
2628
+ }
2629
+ const tx = await tokenContract.transfer(acctAddr, weiAmount);
2630
+ const receipt = await tx.wait();
2631
+ this._refreshSigner();
2632
+ return {
2633
+ txHash: receipt.hash,
2634
+ blockNumber: receipt.blockNumber,
2635
+ status: receipt.status === 1 ? "success" : "failed",
2636
+ gasUsed: receipt.gasUsed
2637
+ };
2638
+ }
2639
+ /**
2640
+ * Transfer any ERC-20 token from AgentAccount to any address or agent.
2641
+ * Executes via Safe UserOp (ERC-7579 single execution).
2642
+ *
2643
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
2644
+ * @param amount - Amount to send (e.g. '100' or 'all')
2645
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
2646
+ */
2647
+ async transferToken(tokenSymbol, amount, to) {
2648
+ const acctAddr = await this.getAccountAddress();
2649
+ const tokenInfo = await this._resolveToken(tokenSymbol);
2650
+ const tokenContract = new import_ethers2.Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
2651
+ let toAddr;
2652
+ if (to.address) {
2653
+ toAddr = to.address;
2654
+ } else if (to.agentId) {
2655
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
2656
+ if (toAddr === import_ethers2.ethers.ZeroAddress) {
2657
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
2658
+ }
2659
+ } else {
2660
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
2661
+ }
2662
+ let weiAmount;
2663
+ if (amount === "all") {
2664
+ weiAmount = await tokenContract.balanceOf(acctAddr);
2665
+ if (weiAmount === 0n) {
2666
+ throw new AgetherError(`No ${tokenInfo.symbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
2667
+ }
2668
+ } else {
2669
+ weiAmount = import_ethers2.ethers.parseUnits(amount, tokenInfo.decimals);
2670
+ }
2671
+ const data = erc20Iface2.encodeFunctionData("transfer", [toAddr, weiAmount]);
2672
+ const receipt = await this._exec(tokenInfo.address, data);
2673
+ const actualAmount = amount === "all" ? import_ethers2.ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
2674
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: toAddr };
2675
+ }
2676
+ /**
2677
+ * Transfer ETH from AgentAccount to any address or agent.
2678
+ * Executes via Safe UserOp.
2679
+ *
2680
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
2681
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
2682
+ */
2683
+ async transferEth(amount, to) {
2684
+ const acctAddr = await this.getAccountAddress();
2685
+ let toAddr;
2686
+ if (to.address) {
2687
+ toAddr = to.address;
2688
+ } else if (to.agentId) {
2689
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
2690
+ if (toAddr === import_ethers2.ethers.ZeroAddress) {
2691
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
2692
+ }
2693
+ } else {
2694
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
2695
+ }
2696
+ let weiAmount;
2697
+ if (amount === "all") {
2698
+ weiAmount = await this.signer.provider.getBalance(acctAddr);
2699
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
2700
+ } else {
2701
+ weiAmount = import_ethers2.ethers.parseEther(amount);
2702
+ }
2703
+ const receipt = await this._exec(toAddr, "0x", weiAmount);
2704
+ const actualAmount = amount === "all" ? import_ethers2.ethers.formatEther(weiAmount) : amount;
2705
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: toAddr };
2706
+ }
2707
+ /**
2708
+ * Approve a spender to use ERC-20 tokens from the AgentAccount.
2709
+ * Executes via Safe UserOp (ERC-7579 single execution).
2710
+ *
2711
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
2712
+ * @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
2713
+ * @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
2714
+ */
2715
+ async approveToken(tokenSymbol, amount, spender) {
2716
+ const tokenInfo = await this._resolveToken(tokenSymbol);
2717
+ let spenderAddr;
2718
+ if (spender.address) {
2719
+ spenderAddr = spender.address;
2720
+ } else if (spender.agentId) {
2721
+ spenderAddr = await this.agether4337Factory.getAccount(BigInt(spender.agentId));
2722
+ if (spenderAddr === import_ethers2.ethers.ZeroAddress) {
2723
+ throw new AgetherError(`Agent ${spender.agentId} has no account`, "NO_ACCOUNT");
2724
+ }
2725
+ } else {
2726
+ throw new AgetherError("Provide address or agentId as spender", "INVALID_TARGET");
2727
+ }
2728
+ let weiAmount;
2729
+ if (amount === "max") {
2730
+ weiAmount = import_ethers2.ethers.MaxUint256;
2731
+ } else {
2732
+ weiAmount = import_ethers2.ethers.parseUnits(amount, tokenInfo.decimals);
2733
+ }
2734
+ const data = erc20Iface2.encodeFunctionData("approve", [spenderAddr, weiAmount]);
2735
+ const receipt = await this._exec(tokenInfo.address, data);
2736
+ const actualAmount = amount === "max" ? "unlimited" : amount;
2737
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, spender: spenderAddr };
2738
+ }
2739
+ // ════════════════════════════════════════════════════════
2613
2740
  // Sponsorship
2614
2741
  // ════════════════════════════════════════════════════════
2615
2742
  /**
@@ -2738,14 +2865,6 @@ var init_AgetherClient = __esm({
2738
2865
  }
2739
2866
  return this._eoaAddress;
2740
2867
  }
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
2868
  async _resolveToken(symbolOrAddress) {
2750
2869
  if (symbolOrAddress.toUpperCase() === "USDC") {
2751
2870
  return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
@@ -2753,6 +2872,9 @@ var init_AgetherClient = __esm({
2753
2872
  const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
2754
2873
  const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
2755
2874
  if (bySymbol) return bySymbol;
2875
+ const cacheKey = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
2876
+ const cached = this._dynamicTokenCache.get(cacheKey);
2877
+ if (cached) return cached;
2756
2878
  if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
2757
2879
  try {
2758
2880
  const token = new import_ethers2.Contract(
@@ -2761,7 +2883,10 @@ var init_AgetherClient = __esm({
2761
2883
  this.signer.provider
2762
2884
  );
2763
2885
  const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
2764
- return { address: symbolOrAddress, symbol, decimals: Number(decimals) };
2886
+ const info = { address: symbolOrAddress, symbol, decimals: Number(decimals) };
2887
+ this._dynamicTokenCache.set(symbolOrAddress.toLowerCase(), info);
2888
+ this._dynamicTokenCache.set(symbol.toUpperCase(), info);
2889
+ return info;
2765
2890
  } catch (e) {
2766
2891
  throw new AgetherError(
2767
2892
  `Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
@@ -2769,8 +2894,53 @@ var init_AgetherClient = __esm({
2769
2894
  );
2770
2895
  }
2771
2896
  }
2897
+ try {
2898
+ const chainId = this.config.chainId;
2899
+ const query = `{
2900
+ markets(
2901
+ first: 20
2902
+ where: { chainId_in: [${chainId}], search: "${symbolOrAddress}" }
2903
+ ) {
2904
+ items {
2905
+ loanAsset { address symbol decimals }
2906
+ collateralAsset { address symbol decimals }
2907
+ }
2908
+ }
2909
+ }`;
2910
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
2911
+ const items = resp.data?.data?.markets?.items ?? [];
2912
+ const sym = symbolOrAddress.toUpperCase();
2913
+ for (const m of items) {
2914
+ if (m.loanAsset?.symbol?.toUpperCase() === sym) {
2915
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
2916
+ this._dynamicTokenCache.set(sym, info);
2917
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
2918
+ return info;
2919
+ }
2920
+ if (m.collateralAsset?.symbol?.toUpperCase() === sym) {
2921
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
2922
+ this._dynamicTokenCache.set(sym, info);
2923
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
2924
+ return info;
2925
+ }
2926
+ }
2927
+ for (const m of items) {
2928
+ if (m.loanAsset?.symbol) {
2929
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
2930
+ this._dynamicTokenCache.set(m.loanAsset.symbol.toUpperCase(), info);
2931
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
2932
+ }
2933
+ if (m.collateralAsset?.symbol) {
2934
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
2935
+ this._dynamicTokenCache.set(m.collateralAsset.symbol.toUpperCase(), info);
2936
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
2937
+ }
2938
+ }
2939
+ } catch (e) {
2940
+ console.warn("[agether] Morpho token search failed:", e instanceof Error ? e.message : e);
2941
+ }
2772
2942
  throw new AgetherError(
2773
- `Unknown token: ${symbolOrAddress}. Use a known symbol (USDC, WETH, wstETH, cbETH) or a 0x address.`,
2943
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
2774
2944
  "UNKNOWN_TOKEN"
2775
2945
  );
2776
2946
  }
@@ -3745,7 +3915,7 @@ async function cmdFund(amount) {
3745
3915
  \u{1F4B5} Funding AgentAccount with $${amount} USDC...
3746
3916
  `);
3747
3917
  try {
3748
- const result = await ac.fundAccount(amount);
3918
+ const result = await ac.fundAccountToken("USDC", amount);
3749
3919
  console.log(`\u2705 Funded $${amount} USDC`);
3750
3920
  console.log(` TX: ${result.txHash}`);
3751
3921
  } catch (e) {
package/dist/index.d.mts CHANGED
@@ -250,11 +250,6 @@ declare class AgetherClient {
250
250
  * tokens per chain (WETH, wstETH, cbETH).
251
251
  */
252
252
  getBalances(): Promise<BalancesResult>;
253
- /**
254
- * Fund the Safe account with USDC from EOA.
255
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
256
- */
257
- fundAccount(usdcAmount: string): Promise<TransactionResult>;
258
253
  /**
259
254
  * Withdraw an ERC-20 token from Safe account to EOA.
260
255
  * Executes a transfer via Safe UserOp.
@@ -270,6 +265,54 @@ declare class AgetherClient {
270
265
  * @param amount - ETH amount (e.g. '0.01' or 'all')
271
266
  */
272
267
  withdrawEth(amount: string): Promise<WithdrawFromAccountResult>;
268
+ /**
269
+ * Fund the AgentAccount with any ERC-20 token from EOA.
270
+ * Simple ERC-20 transfer (does NOT require a UserOp).
271
+ *
272
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
273
+ * @param amount - Amount to send (human-readable, e.g. '100')
274
+ */
275
+ fundAccountToken(tokenSymbol: string, amount: string): Promise<TransactionResult>;
276
+ /**
277
+ * Transfer any ERC-20 token from AgentAccount to any address or agent.
278
+ * Executes via Safe UserOp (ERC-7579 single execution).
279
+ *
280
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
281
+ * @param amount - Amount to send (e.g. '100' or 'all')
282
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
283
+ */
284
+ transferToken(tokenSymbol: string, amount: string, to: {
285
+ address?: string;
286
+ agentId?: string;
287
+ }): Promise<WithdrawFromAccountResult>;
288
+ /**
289
+ * Transfer ETH from AgentAccount to any address or agent.
290
+ * Executes via Safe UserOp.
291
+ *
292
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
293
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
294
+ */
295
+ transferEth(amount: string, to: {
296
+ address?: string;
297
+ agentId?: string;
298
+ }): Promise<WithdrawFromAccountResult>;
299
+ /**
300
+ * Approve a spender to use ERC-20 tokens from the AgentAccount.
301
+ * Executes via Safe UserOp (ERC-7579 single execution).
302
+ *
303
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
304
+ * @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
305
+ * @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
306
+ */
307
+ approveToken(tokenSymbol: string, amount: string, spender: {
308
+ address?: string;
309
+ agentId?: string;
310
+ }): Promise<{
311
+ tx: string;
312
+ token: string;
313
+ amount: string;
314
+ spender: string;
315
+ }>;
273
316
  /**
274
317
  * Send tokens to another agent's Safe account (or any address).
275
318
  * Transfers from EOA (does NOT require a UserOp).
@@ -324,11 +367,14 @@ declare class AgetherClient {
324
367
  /**
325
368
  * Resolve a token symbol or address to { address, symbol, decimals }.
326
369
  *
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
370
+ * Resolution order:
371
+ * 1. `'USDC'` → from chain config (instant)
372
+ * 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
373
+ * 3. Dynamic cache populated by previous Morpho API lookups (instant)
374
+ * 4. `'0x...'` address → reads decimals and symbol onchain
375
+ * 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
331
376
  */
377
+ private _dynamicTokenCache;
332
378
  private _resolveToken;
333
379
  /**
334
380
  * Refresh signer and rebind contracts for fresh nonce.
package/dist/index.d.ts CHANGED
@@ -250,11 +250,6 @@ declare class AgetherClient {
250
250
  * tokens per chain (WETH, wstETH, cbETH).
251
251
  */
252
252
  getBalances(): Promise<BalancesResult>;
253
- /**
254
- * Fund the Safe account with USDC from EOA.
255
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
256
- */
257
- fundAccount(usdcAmount: string): Promise<TransactionResult>;
258
253
  /**
259
254
  * Withdraw an ERC-20 token from Safe account to EOA.
260
255
  * Executes a transfer via Safe UserOp.
@@ -270,6 +265,54 @@ declare class AgetherClient {
270
265
  * @param amount - ETH amount (e.g. '0.01' or 'all')
271
266
  */
272
267
  withdrawEth(amount: string): Promise<WithdrawFromAccountResult>;
268
+ /**
269
+ * Fund the AgentAccount with any ERC-20 token from EOA.
270
+ * Simple ERC-20 transfer (does NOT require a UserOp).
271
+ *
272
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
273
+ * @param amount - Amount to send (human-readable, e.g. '100')
274
+ */
275
+ fundAccountToken(tokenSymbol: string, amount: string): Promise<TransactionResult>;
276
+ /**
277
+ * Transfer any ERC-20 token from AgentAccount to any address or agent.
278
+ * Executes via Safe UserOp (ERC-7579 single execution).
279
+ *
280
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
281
+ * @param amount - Amount to send (e.g. '100' or 'all')
282
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
283
+ */
284
+ transferToken(tokenSymbol: string, amount: string, to: {
285
+ address?: string;
286
+ agentId?: string;
287
+ }): Promise<WithdrawFromAccountResult>;
288
+ /**
289
+ * Transfer ETH from AgentAccount to any address or agent.
290
+ * Executes via Safe UserOp.
291
+ *
292
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
293
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
294
+ */
295
+ transferEth(amount: string, to: {
296
+ address?: string;
297
+ agentId?: string;
298
+ }): Promise<WithdrawFromAccountResult>;
299
+ /**
300
+ * Approve a spender to use ERC-20 tokens from the AgentAccount.
301
+ * Executes via Safe UserOp (ERC-7579 single execution).
302
+ *
303
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
304
+ * @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
305
+ * @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
306
+ */
307
+ approveToken(tokenSymbol: string, amount: string, spender: {
308
+ address?: string;
309
+ agentId?: string;
310
+ }): Promise<{
311
+ tx: string;
312
+ token: string;
313
+ amount: string;
314
+ spender: string;
315
+ }>;
273
316
  /**
274
317
  * Send tokens to another agent's Safe account (or any address).
275
318
  * Transfers from EOA (does NOT require a UserOp).
@@ -324,11 +367,14 @@ declare class AgetherClient {
324
367
  /**
325
368
  * Resolve a token symbol or address to { address, symbol, decimals }.
326
369
  *
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
370
+ * Resolution order:
371
+ * 1. `'USDC'` → from chain config (instant)
372
+ * 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
373
+ * 3. Dynamic cache populated by previous Morpho API lookups (instant)
374
+ * 4. `'0x...'` address → reads decimals and symbol onchain
375
+ * 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
331
376
  */
377
+ private _dynamicTokenCache;
332
378
  private _resolveToken;
333
379
  /**
334
380
  * 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;
@@ -667,24 +680,6 @@ var AgetherClient = class _AgetherClient {
667
680
  }
668
681
  return result;
669
682
  }
670
- /**
671
- * Fund the Safe account with USDC from EOA.
672
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
673
- */
674
- 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
- };
687
- }
688
683
  // ════════════════════════════════════════════════════════
689
684
  // Withdrawals (Safe → EOA via UserOps)
690
685
  // ════════════════════════════════════════════════════════
@@ -735,6 +730,138 @@ var AgetherClient = class _AgetherClient {
735
730
  return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
736
731
  }
737
732
  // ════════════════════════════════════════════════════════
733
+ // Token Transfers & Approvals (AgentAccount → any address)
734
+ // ════════════════════════════════════════════════════════
735
+ /**
736
+ * Fund the AgentAccount with any ERC-20 token from EOA.
737
+ * Simple ERC-20 transfer (does NOT require a UserOp).
738
+ *
739
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
740
+ * @param amount - Amount to send (human-readable, e.g. '100')
741
+ */
742
+ async fundAccountToken(tokenSymbol, amount) {
743
+ const acctAddr = await this.getAccountAddress();
744
+ const tokenInfo = await this._resolveToken(tokenSymbol);
745
+ const tokenContract = new import_ethers.Contract(tokenInfo.address, ERC20_ABI, this.signer);
746
+ const weiAmount = import_ethers.ethers.parseUnits(amount, tokenInfo.decimals);
747
+ const eoaBalance = await tokenContract.balanceOf(await this._getSignerAddress());
748
+ if (eoaBalance < weiAmount) {
749
+ throw new AgetherError(
750
+ `Insufficient ${tokenInfo.symbol}. EOA has ${import_ethers.ethers.formatUnits(eoaBalance, tokenInfo.decimals)}, need ${amount}.`,
751
+ "INSUFFICIENT_BALANCE"
752
+ );
753
+ }
754
+ const tx = await tokenContract.transfer(acctAddr, weiAmount);
755
+ const receipt = await tx.wait();
756
+ this._refreshSigner();
757
+ return {
758
+ txHash: receipt.hash,
759
+ blockNumber: receipt.blockNumber,
760
+ status: receipt.status === 1 ? "success" : "failed",
761
+ gasUsed: receipt.gasUsed
762
+ };
763
+ }
764
+ /**
765
+ * Transfer any ERC-20 token from AgentAccount to any address or agent.
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 - Amount to send (e.g. '100' or 'all')
770
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
771
+ */
772
+ async transferToken(tokenSymbol, amount, to) {
773
+ const acctAddr = await this.getAccountAddress();
774
+ const tokenInfo = await this._resolveToken(tokenSymbol);
775
+ const tokenContract = new import_ethers.Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
776
+ let toAddr;
777
+ if (to.address) {
778
+ toAddr = to.address;
779
+ } else if (to.agentId) {
780
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
781
+ if (toAddr === import_ethers.ethers.ZeroAddress) {
782
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
783
+ }
784
+ } else {
785
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
786
+ }
787
+ let weiAmount;
788
+ if (amount === "all") {
789
+ weiAmount = await tokenContract.balanceOf(acctAddr);
790
+ if (weiAmount === 0n) {
791
+ throw new AgetherError(`No ${tokenInfo.symbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
792
+ }
793
+ } else {
794
+ weiAmount = import_ethers.ethers.parseUnits(amount, tokenInfo.decimals);
795
+ }
796
+ const data = erc20Iface.encodeFunctionData("transfer", [toAddr, weiAmount]);
797
+ const receipt = await this._exec(tokenInfo.address, data);
798
+ const actualAmount = amount === "all" ? import_ethers.ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
799
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: toAddr };
800
+ }
801
+ /**
802
+ * Transfer ETH from AgentAccount to any address or agent.
803
+ * Executes via Safe UserOp.
804
+ *
805
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
806
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
807
+ */
808
+ async transferEth(amount, to) {
809
+ const acctAddr = await this.getAccountAddress();
810
+ let toAddr;
811
+ if (to.address) {
812
+ toAddr = to.address;
813
+ } else if (to.agentId) {
814
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
815
+ if (toAddr === import_ethers.ethers.ZeroAddress) {
816
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
817
+ }
818
+ } else {
819
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
820
+ }
821
+ let weiAmount;
822
+ if (amount === "all") {
823
+ weiAmount = await this.signer.provider.getBalance(acctAddr);
824
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
825
+ } else {
826
+ weiAmount = import_ethers.ethers.parseEther(amount);
827
+ }
828
+ const receipt = await this._exec(toAddr, "0x", weiAmount);
829
+ const actualAmount = amount === "all" ? import_ethers.ethers.formatEther(weiAmount) : amount;
830
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: toAddr };
831
+ }
832
+ /**
833
+ * Approve a spender to use ERC-20 tokens from the AgentAccount.
834
+ * Executes via Safe UserOp (ERC-7579 single execution).
835
+ *
836
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
837
+ * @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
838
+ * @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
839
+ */
840
+ async approveToken(tokenSymbol, amount, spender) {
841
+ const tokenInfo = await this._resolveToken(tokenSymbol);
842
+ let spenderAddr;
843
+ if (spender.address) {
844
+ spenderAddr = spender.address;
845
+ } else if (spender.agentId) {
846
+ spenderAddr = await this.agether4337Factory.getAccount(BigInt(spender.agentId));
847
+ if (spenderAddr === import_ethers.ethers.ZeroAddress) {
848
+ throw new AgetherError(`Agent ${spender.agentId} has no account`, "NO_ACCOUNT");
849
+ }
850
+ } else {
851
+ throw new AgetherError("Provide address or agentId as spender", "INVALID_TARGET");
852
+ }
853
+ let weiAmount;
854
+ if (amount === "max") {
855
+ weiAmount = import_ethers.ethers.MaxUint256;
856
+ } else {
857
+ weiAmount = import_ethers.ethers.parseUnits(amount, tokenInfo.decimals);
858
+ }
859
+ const data = erc20Iface.encodeFunctionData("approve", [spenderAddr, weiAmount]);
860
+ const receipt = await this._exec(tokenInfo.address, data);
861
+ const actualAmount = amount === "max" ? "unlimited" : amount;
862
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, spender: spenderAddr };
863
+ }
864
+ // ════════════════════════════════════════════════════════
738
865
  // Sponsorship
739
866
  // ════════════════════════════════════════════════════════
740
867
  /**
@@ -863,14 +990,6 @@ var AgetherClient = class _AgetherClient {
863
990
  }
864
991
  return this._eoaAddress;
865
992
  }
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
993
  async _resolveToken(symbolOrAddress) {
875
994
  if (symbolOrAddress.toUpperCase() === "USDC") {
876
995
  return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
@@ -878,6 +997,9 @@ var AgetherClient = class _AgetherClient {
878
997
  const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
879
998
  const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
880
999
  if (bySymbol) return bySymbol;
1000
+ const cacheKey = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
1001
+ const cached = this._dynamicTokenCache.get(cacheKey);
1002
+ if (cached) return cached;
881
1003
  if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
882
1004
  try {
883
1005
  const token = new import_ethers.Contract(
@@ -886,7 +1008,10 @@ var AgetherClient = class _AgetherClient {
886
1008
  this.signer.provider
887
1009
  );
888
1010
  const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
889
- return { address: symbolOrAddress, symbol, decimals: Number(decimals) };
1011
+ const info = { address: symbolOrAddress, symbol, decimals: Number(decimals) };
1012
+ this._dynamicTokenCache.set(symbolOrAddress.toLowerCase(), info);
1013
+ this._dynamicTokenCache.set(symbol.toUpperCase(), info);
1014
+ return info;
890
1015
  } catch (e) {
891
1016
  throw new AgetherError(
892
1017
  `Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
@@ -894,8 +1019,53 @@ var AgetherClient = class _AgetherClient {
894
1019
  );
895
1020
  }
896
1021
  }
1022
+ try {
1023
+ const chainId = this.config.chainId;
1024
+ const query = `{
1025
+ markets(
1026
+ first: 20
1027
+ where: { chainId_in: [${chainId}], search: "${symbolOrAddress}" }
1028
+ ) {
1029
+ items {
1030
+ loanAsset { address symbol decimals }
1031
+ collateralAsset { address symbol decimals }
1032
+ }
1033
+ }
1034
+ }`;
1035
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1036
+ const items = resp.data?.data?.markets?.items ?? [];
1037
+ const sym = symbolOrAddress.toUpperCase();
1038
+ for (const m of items) {
1039
+ if (m.loanAsset?.symbol?.toUpperCase() === sym) {
1040
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
1041
+ this._dynamicTokenCache.set(sym, info);
1042
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
1043
+ return info;
1044
+ }
1045
+ if (m.collateralAsset?.symbol?.toUpperCase() === sym) {
1046
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
1047
+ this._dynamicTokenCache.set(sym, info);
1048
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
1049
+ return info;
1050
+ }
1051
+ }
1052
+ for (const m of items) {
1053
+ if (m.loanAsset?.symbol) {
1054
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
1055
+ this._dynamicTokenCache.set(m.loanAsset.symbol.toUpperCase(), info);
1056
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
1057
+ }
1058
+ if (m.collateralAsset?.symbol) {
1059
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
1060
+ this._dynamicTokenCache.set(m.collateralAsset.symbol.toUpperCase(), info);
1061
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
1062
+ }
1063
+ }
1064
+ } catch (e) {
1065
+ console.warn("[agether] Morpho token search failed:", e instanceof Error ? e.message : e);
1066
+ }
897
1067
  throw new AgetherError(
898
- `Unknown token: ${symbolOrAddress}. Use a known symbol (USDC, WETH, wstETH, cbETH) or a 0x address.`,
1068
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
899
1069
  "UNKNOWN_TOKEN"
900
1070
  );
901
1071
  }
@@ -1029,8 +1199,8 @@ var AgetherClient = class _AgetherClient {
1029
1199
 
1030
1200
  // src/clients/MorphoClient.ts
1031
1201
  var import_ethers2 = require("ethers");
1032
- var import_axios = __toESM(require("axios"));
1033
- var MORPHO_API_URL = "https://api.morpho.org/graphql";
1202
+ var import_axios2 = __toESM(require("axios"));
1203
+ var MORPHO_API_URL2 = "https://api.morpho.org/graphql";
1034
1204
  var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1035
1205
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
1036
1206
  var morphoIface = new import_ethers2.ethers.Interface(MORPHO_BLUE_ABI);
@@ -1176,7 +1346,7 @@ var MorphoClient = class {
1176
1346
  }
1177
1347
  }`;
1178
1348
  try {
1179
- const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1349
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1180
1350
  const items = resp.data?.data?.markets?.items ?? [];
1181
1351
  this._discoveredMarkets = items.map((m) => ({
1182
1352
  uniqueKey: m.uniqueKey,
@@ -1360,7 +1530,7 @@ var MorphoClient = class {
1360
1530
  }
1361
1531
  }
1362
1532
  }`;
1363
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 15e3 });
1533
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 15e3 });
1364
1534
  const items = resp.data?.data?.marketPositions?.items ?? [];
1365
1535
  for (const item of items) {
1366
1536
  const supplyShares = BigInt(item.supplyShares ?? "0");
@@ -1579,7 +1749,7 @@ var MorphoClient = class {
1579
1749
  }
1580
1750
  }`;
1581
1751
  try {
1582
- const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1752
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1583
1753
  let items = resp.data?.data?.markets?.items ?? [];
1584
1754
  if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
1585
1755
  const sym = collateralSymbolOrAddress.toUpperCase();
@@ -1645,7 +1815,7 @@ var MorphoClient = class {
1645
1815
  }
1646
1816
  }`;
1647
1817
  try {
1648
- const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1818
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1649
1819
  let items = resp.data?.data?.markets?.items ?? [];
1650
1820
  items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress);
1651
1821
  const searchUpper = search.toUpperCase();
@@ -2038,7 +2208,7 @@ var MorphoClient = class {
2038
2208
  }
2039
2209
  }
2040
2210
  }`;
2041
- const posResp = await import_axios.default.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
2211
+ const posResp = await import_axios2.default.post(MORPHO_API_URL2, { query: positionsQuery }, { timeout: 15e3 });
2042
2212
  const user = posResp.data?.data?.userByAddress;
2043
2213
  if (!user?.marketPositions) return [];
2044
2214
  const activePositions = user.marketPositions.filter(
@@ -2732,7 +2902,7 @@ var MorphoClient = class {
2732
2902
  }
2733
2903
  }
2734
2904
  }`;
2735
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2905
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2736
2906
  const items = resp.data?.data?.marketPositions?.items ?? [];
2737
2907
  for (const item of items) {
2738
2908
  if (BigInt(item.collateral ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2800,7 +2970,7 @@ var MorphoClient = class {
2800
2970
  }
2801
2971
  }
2802
2972
  }`;
2803
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2973
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2804
2974
  const items = resp.data?.data?.marketPositions?.items ?? [];
2805
2975
  for (const item of items) {
2806
2976
  if (BigInt(item.supplyShares ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2952,7 +3122,7 @@ var MorphoClient = class {
2952
3122
  }
2953
3123
  }
2954
3124
  }`;
2955
- const resp = await import_axios.default.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
3125
+ const resp = await import_axios2.default.post(MORPHO_API_URL2, { query: txQuery }, { timeout: 15e3 });
2956
3126
  const txData = resp.data?.data?.transactions;
2957
3127
  if (!txData?.items) break;
2958
3128
  for (const tx of txData.items) {
@@ -2977,7 +3147,7 @@ var MorphoClient = class {
2977
3147
  };
2978
3148
 
2979
3149
  // src/clients/ScoringClient.ts
2980
- var import_axios2 = __toESM(require("axios"));
3150
+ var import_axios3 = __toESM(require("axios"));
2981
3151
 
2982
3152
  // src/clients/X402Client.ts
2983
3153
  var import_fetch = require("@x402/fetch");
@@ -3339,7 +3509,7 @@ var ScoringClient = class {
3339
3509
  constructor(config) {
3340
3510
  this.endpoint = config.endpoint;
3341
3511
  this.defaultChainId = config.chainId;
3342
- this.client = import_axios2.default.create({
3512
+ this.client = import_axios3.default.create({
3343
3513
  baseURL: config.endpoint,
3344
3514
  headers: { "Content-Type": "application/json" },
3345
3515
  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;
@@ -592,24 +605,6 @@ var AgetherClient = class _AgetherClient {
592
605
  }
593
606
  return result;
594
607
  }
595
- /**
596
- * Fund the Safe account with USDC from EOA.
597
- * This is a simple ERC-20 transfer (does NOT require a UserOp).
598
- */
599
- 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
- };
612
- }
613
608
  // ════════════════════════════════════════════════════════
614
609
  // Withdrawals (Safe → EOA via UserOps)
615
610
  // ════════════════════════════════════════════════════════
@@ -660,6 +655,138 @@ var AgetherClient = class _AgetherClient {
660
655
  return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: eoaAddr };
661
656
  }
662
657
  // ════════════════════════════════════════════════════════
658
+ // Token Transfers & Approvals (AgentAccount → any address)
659
+ // ════════════════════════════════════════════════════════
660
+ /**
661
+ * Fund the AgentAccount with any ERC-20 token from EOA.
662
+ * Simple ERC-20 transfer (does NOT require a UserOp).
663
+ *
664
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
665
+ * @param amount - Amount to send (human-readable, e.g. '100')
666
+ */
667
+ async fundAccountToken(tokenSymbol, amount) {
668
+ const acctAddr = await this.getAccountAddress();
669
+ const tokenInfo = await this._resolveToken(tokenSymbol);
670
+ const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer);
671
+ const weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
672
+ const eoaBalance = await tokenContract.balanceOf(await this._getSignerAddress());
673
+ if (eoaBalance < weiAmount) {
674
+ throw new AgetherError(
675
+ `Insufficient ${tokenInfo.symbol}. EOA has ${ethers.formatUnits(eoaBalance, tokenInfo.decimals)}, need ${amount}.`,
676
+ "INSUFFICIENT_BALANCE"
677
+ );
678
+ }
679
+ const tx = await tokenContract.transfer(acctAddr, weiAmount);
680
+ const receipt = await tx.wait();
681
+ this._refreshSigner();
682
+ return {
683
+ txHash: receipt.hash,
684
+ blockNumber: receipt.blockNumber,
685
+ status: receipt.status === 1 ? "success" : "failed",
686
+ gasUsed: receipt.gasUsed
687
+ };
688
+ }
689
+ /**
690
+ * Transfer any ERC-20 token from AgentAccount to any address or agent.
691
+ * Executes via Safe UserOp (ERC-7579 single execution).
692
+ *
693
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
694
+ * @param amount - Amount to send (e.g. '100' or 'all')
695
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
696
+ */
697
+ async transferToken(tokenSymbol, amount, to) {
698
+ const acctAddr = await this.getAccountAddress();
699
+ const tokenInfo = await this._resolveToken(tokenSymbol);
700
+ const tokenContract = new Contract(tokenInfo.address, ERC20_ABI, this.signer.provider);
701
+ let toAddr;
702
+ if (to.address) {
703
+ toAddr = to.address;
704
+ } else if (to.agentId) {
705
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
706
+ if (toAddr === ethers.ZeroAddress) {
707
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
708
+ }
709
+ } else {
710
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
711
+ }
712
+ let weiAmount;
713
+ if (amount === "all") {
714
+ weiAmount = await tokenContract.balanceOf(acctAddr);
715
+ if (weiAmount === 0n) {
716
+ throw new AgetherError(`No ${tokenInfo.symbol} in AgentAccount`, "INSUFFICIENT_BALANCE");
717
+ }
718
+ } else {
719
+ weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
720
+ }
721
+ const data = erc20Iface.encodeFunctionData("transfer", [toAddr, weiAmount]);
722
+ const receipt = await this._exec(tokenInfo.address, data);
723
+ const actualAmount = amount === "all" ? ethers.formatUnits(weiAmount, tokenInfo.decimals) : amount;
724
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, destination: toAddr };
725
+ }
726
+ /**
727
+ * Transfer ETH from AgentAccount to any address or agent.
728
+ * Executes via Safe UserOp.
729
+ *
730
+ * @param amount - ETH amount (e.g. '0.01' or 'all')
731
+ * @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
732
+ */
733
+ async transferEth(amount, to) {
734
+ const acctAddr = await this.getAccountAddress();
735
+ let toAddr;
736
+ if (to.address) {
737
+ toAddr = to.address;
738
+ } else if (to.agentId) {
739
+ toAddr = await this.agether4337Factory.getAccount(BigInt(to.agentId));
740
+ if (toAddr === ethers.ZeroAddress) {
741
+ throw new AgetherError(`Agent ${to.agentId} has no account`, "NO_ACCOUNT");
742
+ }
743
+ } else {
744
+ throw new AgetherError("Provide address or agentId as destination", "INVALID_TARGET");
745
+ }
746
+ let weiAmount;
747
+ if (amount === "all") {
748
+ weiAmount = await this.signer.provider.getBalance(acctAddr);
749
+ if (weiAmount === 0n) throw new AgetherError("No ETH in AgentAccount", "INSUFFICIENT_BALANCE");
750
+ } else {
751
+ weiAmount = ethers.parseEther(amount);
752
+ }
753
+ const receipt = await this._exec(toAddr, "0x", weiAmount);
754
+ const actualAmount = amount === "all" ? ethers.formatEther(weiAmount) : amount;
755
+ return { tx: receipt.hash, token: "ETH", amount: actualAmount, destination: toAddr };
756
+ }
757
+ /**
758
+ * Approve a spender to use ERC-20 tokens from the AgentAccount.
759
+ * Executes via Safe UserOp (ERC-7579 single execution).
760
+ *
761
+ * @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
762
+ * @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
763
+ * @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
764
+ */
765
+ async approveToken(tokenSymbol, amount, spender) {
766
+ const tokenInfo = await this._resolveToken(tokenSymbol);
767
+ let spenderAddr;
768
+ if (spender.address) {
769
+ spenderAddr = spender.address;
770
+ } else if (spender.agentId) {
771
+ spenderAddr = await this.agether4337Factory.getAccount(BigInt(spender.agentId));
772
+ if (spenderAddr === ethers.ZeroAddress) {
773
+ throw new AgetherError(`Agent ${spender.agentId} has no account`, "NO_ACCOUNT");
774
+ }
775
+ } else {
776
+ throw new AgetherError("Provide address or agentId as spender", "INVALID_TARGET");
777
+ }
778
+ let weiAmount;
779
+ if (amount === "max") {
780
+ weiAmount = ethers.MaxUint256;
781
+ } else {
782
+ weiAmount = ethers.parseUnits(amount, tokenInfo.decimals);
783
+ }
784
+ const data = erc20Iface.encodeFunctionData("approve", [spenderAddr, weiAmount]);
785
+ const receipt = await this._exec(tokenInfo.address, data);
786
+ const actualAmount = amount === "max" ? "unlimited" : amount;
787
+ return { tx: receipt.hash, token: tokenInfo.symbol, amount: actualAmount, spender: spenderAddr };
788
+ }
789
+ // ════════════════════════════════════════════════════════
663
790
  // Sponsorship
664
791
  // ════════════════════════════════════════════════════════
665
792
  /**
@@ -788,14 +915,6 @@ var AgetherClient = class _AgetherClient {
788
915
  }
789
916
  return this._eoaAddress;
790
917
  }
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
918
  async _resolveToken(symbolOrAddress) {
800
919
  if (symbolOrAddress.toUpperCase() === "USDC") {
801
920
  return { address: this.config.contracts.usdc, symbol: "USDC", decimals: 6 };
@@ -803,6 +922,9 @@ var AgetherClient = class _AgetherClient {
803
922
  const chainTokens = KNOWN_TOKENS[this.config.chainId] ?? {};
804
923
  const bySymbol = chainTokens[symbolOrAddress] || chainTokens[symbolOrAddress.toUpperCase()];
805
924
  if (bySymbol) return bySymbol;
925
+ const cacheKey = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
926
+ const cached = this._dynamicTokenCache.get(cacheKey);
927
+ if (cached) return cached;
806
928
  if (symbolOrAddress.startsWith("0x") && symbolOrAddress.length === 42) {
807
929
  try {
808
930
  const token = new Contract(
@@ -811,7 +933,10 @@ var AgetherClient = class _AgetherClient {
811
933
  this.signer.provider
812
934
  );
813
935
  const [decimals, symbol] = await Promise.all([token.decimals(), token.symbol()]);
814
- return { address: symbolOrAddress, symbol, decimals: Number(decimals) };
936
+ const info = { address: symbolOrAddress, symbol, decimals: Number(decimals) };
937
+ this._dynamicTokenCache.set(symbolOrAddress.toLowerCase(), info);
938
+ this._dynamicTokenCache.set(symbol.toUpperCase(), info);
939
+ return info;
815
940
  } catch (e) {
816
941
  throw new AgetherError(
817
942
  `Failed to read token at ${symbolOrAddress}: ${e instanceof Error ? e.message : e}`,
@@ -819,8 +944,53 @@ var AgetherClient = class _AgetherClient {
819
944
  );
820
945
  }
821
946
  }
947
+ try {
948
+ const chainId = this.config.chainId;
949
+ const query = `{
950
+ markets(
951
+ first: 20
952
+ where: { chainId_in: [${chainId}], search: "${symbolOrAddress}" }
953
+ ) {
954
+ items {
955
+ loanAsset { address symbol decimals }
956
+ collateralAsset { address symbol decimals }
957
+ }
958
+ }
959
+ }`;
960
+ const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
961
+ const items = resp.data?.data?.markets?.items ?? [];
962
+ const sym = symbolOrAddress.toUpperCase();
963
+ for (const m of items) {
964
+ if (m.loanAsset?.symbol?.toUpperCase() === sym) {
965
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
966
+ this._dynamicTokenCache.set(sym, info);
967
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
968
+ return info;
969
+ }
970
+ if (m.collateralAsset?.symbol?.toUpperCase() === sym) {
971
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
972
+ this._dynamicTokenCache.set(sym, info);
973
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
974
+ return info;
975
+ }
976
+ }
977
+ for (const m of items) {
978
+ if (m.loanAsset?.symbol) {
979
+ const info = { address: m.loanAsset.address, symbol: m.loanAsset.symbol, decimals: m.loanAsset.decimals };
980
+ this._dynamicTokenCache.set(m.loanAsset.symbol.toUpperCase(), info);
981
+ this._dynamicTokenCache.set(m.loanAsset.address.toLowerCase(), info);
982
+ }
983
+ if (m.collateralAsset?.symbol) {
984
+ const info = { address: m.collateralAsset.address, symbol: m.collateralAsset.symbol, decimals: m.collateralAsset.decimals };
985
+ this._dynamicTokenCache.set(m.collateralAsset.symbol.toUpperCase(), info);
986
+ this._dynamicTokenCache.set(m.collateralAsset.address.toLowerCase(), info);
987
+ }
988
+ }
989
+ } catch (e) {
990
+ console.warn("[agether] Morpho token search failed:", e instanceof Error ? e.message : e);
991
+ }
822
992
  throw new AgetherError(
823
- `Unknown token: ${symbolOrAddress}. Use a known symbol (USDC, WETH, wstETH, cbETH) or a 0x address.`,
993
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
824
994
  "UNKNOWN_TOKEN"
825
995
  );
826
996
  }
@@ -954,8 +1124,8 @@ var AgetherClient = class _AgetherClient {
954
1124
 
955
1125
  // src/clients/MorphoClient.ts
956
1126
  import { ethers as ethers2, Contract as Contract2 } from "ethers";
957
- import axios from "axios";
958
- var MORPHO_API_URL = "https://api.morpho.org/graphql";
1127
+ import axios2 from "axios";
1128
+ var MORPHO_API_URL2 = "https://api.morpho.org/graphql";
959
1129
  var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000000000";
960
1130
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
961
1131
  var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
@@ -1101,7 +1271,7 @@ var MorphoClient = class {
1101
1271
  }
1102
1272
  }`;
1103
1273
  try {
1104
- const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1274
+ const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1105
1275
  const items = resp.data?.data?.markets?.items ?? [];
1106
1276
  this._discoveredMarkets = items.map((m) => ({
1107
1277
  uniqueKey: m.uniqueKey,
@@ -1285,7 +1455,7 @@ var MorphoClient = class {
1285
1455
  }
1286
1456
  }
1287
1457
  }`;
1288
- const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 15e3 });
1458
+ const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 15e3 });
1289
1459
  const items = resp.data?.data?.marketPositions?.items ?? [];
1290
1460
  for (const item of items) {
1291
1461
  const supplyShares = BigInt(item.supplyShares ?? "0");
@@ -1504,7 +1674,7 @@ var MorphoClient = class {
1504
1674
  }
1505
1675
  }`;
1506
1676
  try {
1507
- const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1677
+ const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1508
1678
  let items = resp.data?.data?.markets?.items ?? [];
1509
1679
  if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
1510
1680
  const sym = collateralSymbolOrAddress.toUpperCase();
@@ -1570,7 +1740,7 @@ var MorphoClient = class {
1570
1740
  }
1571
1741
  }`;
1572
1742
  try {
1573
- const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1743
+ const resp = await axios2.post(MORPHO_API_URL2, { query }, { timeout: 1e4 });
1574
1744
  let items = resp.data?.data?.markets?.items ?? [];
1575
1745
  items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress);
1576
1746
  const searchUpper = search.toUpperCase();
@@ -1963,7 +2133,7 @@ var MorphoClient = class {
1963
2133
  }
1964
2134
  }
1965
2135
  }`;
1966
- const posResp = await axios.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
2136
+ const posResp = await axios2.post(MORPHO_API_URL2, { query: positionsQuery }, { timeout: 15e3 });
1967
2137
  const user = posResp.data?.data?.userByAddress;
1968
2138
  if (!user?.marketPositions) return [];
1969
2139
  const activePositions = user.marketPositions.filter(
@@ -2657,7 +2827,7 @@ var MorphoClient = class {
2657
2827
  }
2658
2828
  }
2659
2829
  }`;
2660
- const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2830
+ const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2661
2831
  const items = resp.data?.data?.marketPositions?.items ?? [];
2662
2832
  for (const item of items) {
2663
2833
  if (BigInt(item.collateral ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2725,7 +2895,7 @@ var MorphoClient = class {
2725
2895
  }
2726
2896
  }
2727
2897
  }`;
2728
- const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
2898
+ const resp = await axios2.post(MORPHO_API_URL2, { query: posQuery }, { timeout: 1e4 });
2729
2899
  const items = resp.data?.data?.marketPositions?.items ?? [];
2730
2900
  for (const item of items) {
2731
2901
  if (BigInt(item.supplyShares ?? "0") > 0n && item.market?.collateralAsset) {
@@ -2877,7 +3047,7 @@ var MorphoClient = class {
2877
3047
  }
2878
3048
  }
2879
3049
  }`;
2880
- const resp = await axios.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
3050
+ const resp = await axios2.post(MORPHO_API_URL2, { query: txQuery }, { timeout: 15e3 });
2881
3051
  const txData = resp.data?.data?.transactions;
2882
3052
  if (!txData?.items) break;
2883
3053
  for (const tx of txData.items) {
@@ -2902,7 +3072,7 @@ var MorphoClient = class {
2902
3072
  };
2903
3073
 
2904
3074
  // src/clients/ScoringClient.ts
2905
- import axios2 from "axios";
3075
+ import axios3 from "axios";
2906
3076
 
2907
3077
  // src/clients/X402Client.ts
2908
3078
  import { wrapFetchWithPayment } from "@x402/fetch";
@@ -3264,7 +3434,7 @@ var ScoringClient = class {
3264
3434
  constructor(config) {
3265
3435
  this.endpoint = config.endpoint;
3266
3436
  this.defaultChainId = config.chainId;
3267
- this.client = axios2.create({
3437
+ this.client = axios3.create({
3268
3438
  baseURL: config.endpoint,
3269
3439
  headers: { "Content-Type": "application/json" },
3270
3440
  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.1",
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",