@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 +200 -30
- package/dist/index.d.mts +55 -9
- package/dist/index.d.ts +55 -9
- package/dist/index.js +210 -40
- package/dist/index.mjs +210 -40
- 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;
|
|
@@ -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
|
-
|
|
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}.
|
|
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.
|
|
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
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
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
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
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
|
-
|
|
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}.
|
|
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
|
|
1033
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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}.
|
|
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
|
|
958
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
3437
|
+
this.client = axios3.create({
|
|
3268
3438
|
baseURL: config.endpoint,
|
|
3269
3439
|
headers: { "Content-Type": "application/json" },
|
|
3270
3440
|
timeout: 3e4
|