@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 +201 -24
- package/dist/index.d.mts +56 -5
- package/dist/index.d.ts +56 -5
- package/dist/index.js +212 -35
- package/dist/index.mjs +212 -35
- package/package.json +1 -1
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
|
-
*
|
|
2560
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
2548
2561
|
*/
|
|
2549
2562
|
async fundAccount(usdcAmount) {
|
|
2550
|
-
|
|
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
|
-
|
|
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}.
|
|
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
|
-
*
|
|
255
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
256
256
|
*/
|
|
257
257
|
fundAccount(usdcAmount: string): Promise<TransactionResult>;
|
|
258
258
|
/**
|
|
@@ -270,6 +270,54 @@ declare class AgetherClient {
|
|
|
270
270
|
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
271
271
|
*/
|
|
272
272
|
withdrawEth(amount: string): Promise<WithdrawFromAccountResult>;
|
|
273
|
+
/**
|
|
274
|
+
* Fund the AgentAccount with any ERC-20 token from EOA.
|
|
275
|
+
* Simple ERC-20 transfer (does NOT require a UserOp).
|
|
276
|
+
*
|
|
277
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
278
|
+
* @param amount - Amount to send (human-readable, e.g. '100')
|
|
279
|
+
*/
|
|
280
|
+
fundAccountToken(tokenSymbol: string, amount: string): Promise<TransactionResult>;
|
|
281
|
+
/**
|
|
282
|
+
* Transfer any ERC-20 token from AgentAccount to any address or agent.
|
|
283
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
284
|
+
*
|
|
285
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
286
|
+
* @param amount - Amount to send (e.g. '100' or 'all')
|
|
287
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
288
|
+
*/
|
|
289
|
+
transferToken(tokenSymbol: string, amount: string, to: {
|
|
290
|
+
address?: string;
|
|
291
|
+
agentId?: string;
|
|
292
|
+
}): Promise<WithdrawFromAccountResult>;
|
|
293
|
+
/**
|
|
294
|
+
* Transfer ETH from AgentAccount to any address or agent.
|
|
295
|
+
* Executes via Safe UserOp.
|
|
296
|
+
*
|
|
297
|
+
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
298
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
299
|
+
*/
|
|
300
|
+
transferEth(amount: string, to: {
|
|
301
|
+
address?: string;
|
|
302
|
+
agentId?: string;
|
|
303
|
+
}): Promise<WithdrawFromAccountResult>;
|
|
304
|
+
/**
|
|
305
|
+
* Approve a spender to use ERC-20 tokens from the AgentAccount.
|
|
306
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
307
|
+
*
|
|
308
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
309
|
+
* @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
|
|
310
|
+
* @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
311
|
+
*/
|
|
312
|
+
approveToken(tokenSymbol: string, amount: string, spender: {
|
|
313
|
+
address?: string;
|
|
314
|
+
agentId?: string;
|
|
315
|
+
}): Promise<{
|
|
316
|
+
tx: string;
|
|
317
|
+
token: string;
|
|
318
|
+
amount: string;
|
|
319
|
+
spender: string;
|
|
320
|
+
}>;
|
|
273
321
|
/**
|
|
274
322
|
* Send tokens to another agent's Safe account (or any address).
|
|
275
323
|
* Transfers from EOA (does NOT require a UserOp).
|
|
@@ -324,11 +372,14 @@ declare class AgetherClient {
|
|
|
324
372
|
/**
|
|
325
373
|
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
326
374
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
375
|
+
* Resolution order:
|
|
376
|
+
* 1. `'USDC'` → from chain config (instant)
|
|
377
|
+
* 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
|
|
378
|
+
* 3. Dynamic cache populated by previous Morpho API lookups (instant)
|
|
379
|
+
* 4. `'0x...'` address → reads decimals and symbol onchain
|
|
380
|
+
* 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
|
|
331
381
|
*/
|
|
382
|
+
private _dynamicTokenCache;
|
|
332
383
|
private _resolveToken;
|
|
333
384
|
/**
|
|
334
385
|
* Refresh signer and rebind contracts for fresh nonce.
|
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
|
-
*
|
|
255
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
256
256
|
*/
|
|
257
257
|
fundAccount(usdcAmount: string): Promise<TransactionResult>;
|
|
258
258
|
/**
|
|
@@ -270,6 +270,54 @@ declare class AgetherClient {
|
|
|
270
270
|
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
271
271
|
*/
|
|
272
272
|
withdrawEth(amount: string): Promise<WithdrawFromAccountResult>;
|
|
273
|
+
/**
|
|
274
|
+
* Fund the AgentAccount with any ERC-20 token from EOA.
|
|
275
|
+
* Simple ERC-20 transfer (does NOT require a UserOp).
|
|
276
|
+
*
|
|
277
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
278
|
+
* @param amount - Amount to send (human-readable, e.g. '100')
|
|
279
|
+
*/
|
|
280
|
+
fundAccountToken(tokenSymbol: string, amount: string): Promise<TransactionResult>;
|
|
281
|
+
/**
|
|
282
|
+
* Transfer any ERC-20 token from AgentAccount to any address or agent.
|
|
283
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
284
|
+
*
|
|
285
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
286
|
+
* @param amount - Amount to send (e.g. '100' or 'all')
|
|
287
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
288
|
+
*/
|
|
289
|
+
transferToken(tokenSymbol: string, amount: string, to: {
|
|
290
|
+
address?: string;
|
|
291
|
+
agentId?: string;
|
|
292
|
+
}): Promise<WithdrawFromAccountResult>;
|
|
293
|
+
/**
|
|
294
|
+
* Transfer ETH from AgentAccount to any address or agent.
|
|
295
|
+
* Executes via Safe UserOp.
|
|
296
|
+
*
|
|
297
|
+
* @param amount - ETH amount (e.g. '0.01' or 'all')
|
|
298
|
+
* @param to - Destination: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
299
|
+
*/
|
|
300
|
+
transferEth(amount: string, to: {
|
|
301
|
+
address?: string;
|
|
302
|
+
agentId?: string;
|
|
303
|
+
}): Promise<WithdrawFromAccountResult>;
|
|
304
|
+
/**
|
|
305
|
+
* Approve a spender to use ERC-20 tokens from the AgentAccount.
|
|
306
|
+
* Executes via Safe UserOp (ERC-7579 single execution).
|
|
307
|
+
*
|
|
308
|
+
* @param tokenSymbol - Token symbol (e.g. 'USDC', 'WETH') or 0x address
|
|
309
|
+
* @param amount - Allowance amount (e.g. '1000' or 'max' for uint256 max)
|
|
310
|
+
* @param spender - Spender: `{ address: '0x...' }` or `{ agentId: '42' }`
|
|
311
|
+
*/
|
|
312
|
+
approveToken(tokenSymbol: string, amount: string, spender: {
|
|
313
|
+
address?: string;
|
|
314
|
+
agentId?: string;
|
|
315
|
+
}): Promise<{
|
|
316
|
+
tx: string;
|
|
317
|
+
token: string;
|
|
318
|
+
amount: string;
|
|
319
|
+
spender: string;
|
|
320
|
+
}>;
|
|
273
321
|
/**
|
|
274
322
|
* Send tokens to another agent's Safe account (or any address).
|
|
275
323
|
* Transfers from EOA (does NOT require a UserOp).
|
|
@@ -324,11 +372,14 @@ declare class AgetherClient {
|
|
|
324
372
|
/**
|
|
325
373
|
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
326
374
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
375
|
+
* Resolution order:
|
|
376
|
+
* 1. `'USDC'` → from chain config (instant)
|
|
377
|
+
* 2. Well-known symbols (`'WETH'`, `'wstETH'`, `'cbETH'`) → built-in per-chain registry (instant)
|
|
378
|
+
* 3. Dynamic cache populated by previous Morpho API lookups (instant)
|
|
379
|
+
* 4. `'0x...'` address → reads decimals and symbol onchain
|
|
380
|
+
* 5. Morpho GraphQL `search` API → discovers any token across all Morpho markets
|
|
331
381
|
*/
|
|
382
|
+
private _dynamicTokenCache;
|
|
332
383
|
private _resolveToken;
|
|
333
384
|
/**
|
|
334
385
|
* Refresh signer and rebind contracts for fresh nonce.
|
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
|
-
*
|
|
685
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
673
686
|
*/
|
|
674
687
|
async fundAccount(usdcAmount) {
|
|
675
|
-
|
|
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
|
-
|
|
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}.
|
|
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
|
|
1033
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
*
|
|
610
|
+
* @deprecated Use `fundAccountToken('USDC', amount)` instead.
|
|
598
611
|
*/
|
|
599
612
|
async fundAccount(usdcAmount) {
|
|
600
|
-
|
|
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
|
-
|
|
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}.
|
|
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
|
|
958
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
3444
|
+
this.client = axios3.create({
|
|
3268
3445
|
baseURL: config.endpoint,
|
|
3269
3446
|
headers: { "Content-Type": "application/json" },
|
|
3270
3447
|
timeout: 3e4
|