@agether/agether 2.1.0 → 2.3.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/openclaw.plugin.json +8 -8
- package/package.json +2 -2
- package/src/index.ts +191 -165
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "agether",
|
|
3
3
|
"name": "Agether Credit",
|
|
4
4
|
"description": "Onchain credit protocol for AI agents — Morpho-backed overcollateralized credit, ERC-8004 identity, x402 payments. Private key and RPC keys are read from OpenClaw secrets (env vars); no plaintext config needed.",
|
|
5
|
-
"version": "2.0
|
|
5
|
+
"version": "2.3.0",
|
|
6
6
|
"skills": ["skills/agether"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
"description": "Auto-borrow from Morpho credit line when USDC balance is low for x402 payments",
|
|
18
18
|
"default": false
|
|
19
19
|
},
|
|
20
|
+
"autoYield": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"description": "Auto-withdraw earned yield from supply positions before borrowing. Waterfall: balance → yield → borrow",
|
|
23
|
+
"default": false
|
|
24
|
+
},
|
|
20
25
|
"dailySpendLimitUsdc": {
|
|
21
26
|
"type": "number",
|
|
22
|
-
"description": "Daily USDC spending cap for x402 payments (0 = unlimited)",
|
|
27
|
+
"description": "Daily USDC spending cap for x402 auto-draw payments (0 = unlimited). Persisted to cache file so it survives restarts.",
|
|
23
28
|
"default": 0
|
|
24
29
|
},
|
|
25
|
-
"yieldLimitedSpending": {
|
|
26
|
-
"type": "boolean",
|
|
27
|
-
"description": "Limit auto-draw spending to theoretical yield on deposited collateral",
|
|
28
|
-
"default": false
|
|
29
|
-
},
|
|
30
30
|
"autoDrawBuffer": {
|
|
31
31
|
"type": "number",
|
|
32
32
|
"description": "Extra USDC buffer when auto-drawing from Morpho (e.g. 0.5 = borrow $0.50 extra)",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"uiHints": {
|
|
44
44
|
"agentId": { "label": "Agent ID", "placeholder": "17676" },
|
|
45
45
|
"autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" },
|
|
46
|
+
"autoYield": { "label": "Auto-Yield (use yield before borrowing)", "placeholder": "false" },
|
|
46
47
|
"dailySpendLimitUsdc": { "label": "Daily Spend Limit (USDC)", "placeholder": "0" },
|
|
47
|
-
"yieldLimitedSpending": { "label": "Yield-Limited Spending", "placeholder": "false" },
|
|
48
48
|
"autoDrawBuffer": { "label": "Auto-Draw Buffer (USDC)", "placeholder": "0" },
|
|
49
49
|
"healthAlertThreshold": { "label": "Health Alert Threshold (%)", "placeholder": "70" }
|
|
50
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agether/agether",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "OpenClaw plugin for Agether — onchain credit for AI agents",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"openclaw": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
]
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@agether/sdk": "^2.
|
|
12
|
+
"@agether/sdk": "^2.6.0",
|
|
13
13
|
"axios": "^1.6.0",
|
|
14
14
|
"ethers": "^6.9.0"
|
|
15
15
|
},
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,44 @@ const BACKEND_URL = "http://95.179.189.214:3001";
|
|
|
29
29
|
const DEFAULT_RPC = "https://base-rpc.publicnode.com";
|
|
30
30
|
const BASESCAN = "https://basescan.org/tx";
|
|
31
31
|
|
|
32
|
+
// ─── Spending Cache (persists dailySpendLimit tracker across restarts) ────
|
|
33
|
+
|
|
34
|
+
const SPEND_CACHE_FILE = path.join(
|
|
35
|
+
process.env.HOME || process.env.USERPROFILE || "/root",
|
|
36
|
+
".openclaw",
|
|
37
|
+
".agether-spend-cache.json",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
interface SpendCacheData {
|
|
41
|
+
date: string; // YYYY-MM-DD
|
|
42
|
+
totalBorrowed: string; // raw bigint string (6-decimal USDC units)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function loadSpendCache(): SpendCacheData | undefined {
|
|
46
|
+
try {
|
|
47
|
+
if (fs.existsSync(SPEND_CACHE_FILE)) {
|
|
48
|
+
const raw = fs.readFileSync(SPEND_CACHE_FILE, "utf-8");
|
|
49
|
+
const data: SpendCacheData = JSON.parse(raw);
|
|
50
|
+
// Only return if it's today's data
|
|
51
|
+
const today = new Date().toISOString().split("T")[0];
|
|
52
|
+
if (data.date === today && data.totalBorrowed) return data;
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.warn("[agether] failed to load spend cache:", e instanceof Error ? e.message : String(e));
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function saveSpendCache(state: SpendCacheData): void {
|
|
61
|
+
try {
|
|
62
|
+
const dir = path.dirname(SPEND_CACHE_FILE);
|
|
63
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
fs.writeFileSync(SPEND_CACHE_FILE, JSON.stringify(state, null, 2));
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.warn("[agether] failed to save spend cache:", e instanceof Error ? e.message : String(e));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
32
70
|
// ─── Helpers ──────────────────────────────────────────────
|
|
33
71
|
|
|
34
72
|
interface PluginConfig {
|
|
@@ -339,12 +377,12 @@ export default function register(api: any) {
|
|
|
339
377
|
api.registerTool({
|
|
340
378
|
name: "morpho_deposit",
|
|
341
379
|
description:
|
|
342
|
-
"Deposit collateral
|
|
380
|
+
"Deposit collateral into Morpho Blue via AgentAccount. Enables borrowing USDC. Token is auto-discovered from available markets.",
|
|
343
381
|
parameters: {
|
|
344
382
|
type: "object",
|
|
345
383
|
properties: {
|
|
346
384
|
amount: { type: "string", description: "Amount of collateral tokens (e.g. '0.05')" },
|
|
347
|
-
token: { type: "string",
|
|
385
|
+
token: { type: "string", description: "Collateral token" },
|
|
348
386
|
},
|
|
349
387
|
required: ["amount", "token"],
|
|
350
388
|
},
|
|
@@ -375,7 +413,7 @@ export default function register(api: any) {
|
|
|
375
413
|
type: "object",
|
|
376
414
|
properties: {
|
|
377
415
|
collateralAmount: { type: "string", description: "Amount of collateral (e.g. '0.05')" },
|
|
378
|
-
token: { type: "string",
|
|
416
|
+
token: { type: "string", description: "Collateral token" },
|
|
379
417
|
borrowAmount: { type: "string", description: "USDC amount to borrow (e.g. '50')" },
|
|
380
418
|
},
|
|
381
419
|
required: ["collateralAmount", "token", "borrowAmount"],
|
|
@@ -414,7 +452,7 @@ export default function register(api: any) {
|
|
|
414
452
|
agentId: { type: "string", description: "Target agent's ERC-8004 ID (e.g. '17676')" },
|
|
415
453
|
agentAddress: { type: "string", description: "Target AgentAccount address (alternative to agentId)" },
|
|
416
454
|
amount: { type: "string", description: "Collateral amount (e.g. '0.05')" },
|
|
417
|
-
token: { type: "string",
|
|
455
|
+
token: { type: "string", description: "Collateral token" },
|
|
418
456
|
},
|
|
419
457
|
required: ["amount", "token"],
|
|
420
458
|
},
|
|
@@ -452,7 +490,7 @@ export default function register(api: any) {
|
|
|
452
490
|
type: "object",
|
|
453
491
|
properties: {
|
|
454
492
|
amount: { type: "string", description: "Amount in USD to borrow (e.g. '100')" },
|
|
455
|
-
token: { type: "string",
|
|
493
|
+
token: { type: "string", description: "Collateral token (optional, auto-detected)" },
|
|
456
494
|
},
|
|
457
495
|
required: ["amount"],
|
|
458
496
|
},
|
|
@@ -484,7 +522,7 @@ export default function register(api: any) {
|
|
|
484
522
|
type: "object",
|
|
485
523
|
properties: {
|
|
486
524
|
amount: { type: "string", description: "Amount in USD to repay (e.g. '50' or 'all' for full repayment)" },
|
|
487
|
-
token: { type: "string",
|
|
525
|
+
token: { type: "string", description: "Collateral token (optional, auto-detected)" },
|
|
488
526
|
},
|
|
489
527
|
required: ["amount"],
|
|
490
528
|
},
|
|
@@ -509,21 +547,25 @@ export default function register(api: any) {
|
|
|
509
547
|
api.registerTool({
|
|
510
548
|
name: "morpho_withdraw",
|
|
511
549
|
description:
|
|
512
|
-
"Withdraw collateral from Morpho Blue
|
|
550
|
+
"Withdraw collateral from Morpho Blue. " +
|
|
551
|
+
"By default keeps collateral in AgentAccount. Set toEoa=true to send to EOA wallet. " +
|
|
513
552
|
"Use amount 'all' to withdraw maximum. Must maintain collateral ratio if debt remains.",
|
|
514
553
|
parameters: {
|
|
515
554
|
type: "object",
|
|
516
555
|
properties: {
|
|
517
556
|
amount: { type: "string", description: "Amount to withdraw (e.g. '0.05' or 'all')" },
|
|
518
|
-
token: { type: "string",
|
|
557
|
+
token: { type: "string", description: "Collateral token" },
|
|
558
|
+
toEoa: { type: "boolean", description: "Send to EOA wallet instead of keeping in AgentAccount (default: false)" },
|
|
519
559
|
},
|
|
520
560
|
required: ["amount", "token"],
|
|
521
561
|
},
|
|
522
|
-
async execute(_id: string, params: { amount: string; token: string }) {
|
|
562
|
+
async execute(_id: string, params: { amount: string; token: string; toEoa?: boolean }) {
|
|
523
563
|
try {
|
|
524
564
|
const cfg = getConfig(api);
|
|
525
565
|
const client = createClient(cfg);
|
|
526
|
-
|
|
566
|
+
// Default: keep in AgentAccount. toEoa=true → send to EOA.
|
|
567
|
+
const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
|
|
568
|
+
const result = await client.withdrawCollateral(params.token, params.amount, undefined, receiver);
|
|
527
569
|
return ok(JSON.stringify({
|
|
528
570
|
status: "withdrawn",
|
|
529
571
|
amount: result.amount,
|
|
@@ -551,7 +593,7 @@ export default function register(api: any) {
|
|
|
551
593
|
amount: { type: "string", description: "USDC amount to supply (e.g. '500')" },
|
|
552
594
|
market: {
|
|
553
595
|
type: "string",
|
|
554
|
-
|
|
596
|
+
|
|
555
597
|
description: "Which market to supply to, identified by collateral token (optional — auto-picks highest APY)",
|
|
556
598
|
},
|
|
557
599
|
},
|
|
@@ -588,7 +630,7 @@ export default function register(api: any) {
|
|
|
588
630
|
properties: {
|
|
589
631
|
market: {
|
|
590
632
|
type: "string",
|
|
591
|
-
|
|
633
|
+
|
|
592
634
|
description: "Filter by market collateral token (optional)",
|
|
593
635
|
},
|
|
594
636
|
},
|
|
@@ -634,6 +676,7 @@ export default function register(api: any) {
|
|
|
634
676
|
name: "morpho_withdraw_supply",
|
|
635
677
|
description:
|
|
636
678
|
"Withdraw supplied USDC (principal + earned interest) from a Morpho Blue lending position. " +
|
|
679
|
+
"By default keeps USDC in AgentAccount. Set toEoa=true to send to EOA wallet. " +
|
|
637
680
|
"Use amount 'all' to withdraw the full position. Different from morpho_withdraw which withdraws collateral.",
|
|
638
681
|
parameters: {
|
|
639
682
|
type: "object",
|
|
@@ -641,17 +684,20 @@ export default function register(api: any) {
|
|
|
641
684
|
amount: { type: "string", description: "USDC amount to withdraw (e.g. '100' or 'all')" },
|
|
642
685
|
market: {
|
|
643
686
|
type: "string",
|
|
644
|
-
|
|
687
|
+
|
|
645
688
|
description: "Market collateral token (optional — auto-detects)",
|
|
646
689
|
},
|
|
690
|
+
toEoa: { type: "boolean", description: "Send to EOA wallet instead of keeping in AgentAccount (default: false)" },
|
|
647
691
|
},
|
|
648
692
|
required: ["amount"],
|
|
649
693
|
},
|
|
650
|
-
async execute(_id: string, params: { amount: string; market?: string }) {
|
|
694
|
+
async execute(_id: string, params: { amount: string; market?: string; toEoa?: boolean }) {
|
|
651
695
|
try {
|
|
652
696
|
const cfg = getConfig(api);
|
|
653
697
|
const client = createClient(cfg);
|
|
654
|
-
|
|
698
|
+
// Default: keep in AgentAccount. toEoa=true → send to EOA.
|
|
699
|
+
const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
|
|
700
|
+
const result = await client.withdrawSupply(params.amount, params.market, receiver);
|
|
655
701
|
return ok(JSON.stringify({
|
|
656
702
|
status: "withdrawn",
|
|
657
703
|
amount: params.amount === "all" ? "all (full position)" : `$${params.amount} USDC`,
|
|
@@ -664,70 +710,38 @@ export default function register(api: any) {
|
|
|
664
710
|
});
|
|
665
711
|
|
|
666
712
|
// ═══════════════════════════════════════════════════════
|
|
667
|
-
// TOOL:
|
|
713
|
+
// TOOL: morpho_markets (Morpho GraphQL API — no backend)
|
|
668
714
|
// ═══════════════════════════════════════════════════════
|
|
669
715
|
api.registerTool({
|
|
670
|
-
name: "
|
|
716
|
+
name: "morpho_markets",
|
|
671
717
|
description:
|
|
672
|
-
"
|
|
673
|
-
"
|
|
674
|
-
"Fails if requested amount exceeds available yield.",
|
|
718
|
+
"List available Morpho Blue USDC markets on Base — liquidity, supply/borrow APY, utilization, LLTV. " +
|
|
719
|
+
"Optionally filter by collateral token.",
|
|
675
720
|
parameters: {
|
|
676
721
|
type: "object",
|
|
677
722
|
properties: {
|
|
678
|
-
|
|
679
|
-
amount: { type: "string", description: "USDC amount to pay from yield (e.g. '2.50')" },
|
|
680
|
-
market: {
|
|
681
|
-
type: "string",
|
|
682
|
-
enum: ["WETH", "wstETH", "cbETH"],
|
|
683
|
-
description: "Which supply position to use (optional — auto-picks highest yield)",
|
|
684
|
-
},
|
|
723
|
+
token: { type: "string", description: "Filter by collateral token (optional)" },
|
|
685
724
|
},
|
|
686
|
-
required: [
|
|
725
|
+
required: [],
|
|
687
726
|
},
|
|
688
|
-
async execute(_id: string, params: {
|
|
727
|
+
async execute(_id: string, params: { token?: string }) {
|
|
689
728
|
try {
|
|
690
729
|
const cfg = getConfig(api);
|
|
691
730
|
const client = createClient(cfg);
|
|
692
|
-
const
|
|
693
|
-
return ok(JSON.stringify({
|
|
694
|
-
status: "paid_from_yield",
|
|
695
|
-
amount: `$${params.amount} USDC`,
|
|
696
|
-
recipient: params.recipient,
|
|
697
|
-
remainingYield: `$${result.remainingYield} USDC`,
|
|
698
|
-
remainingSupply: `$${result.remainingSupply} USDC`,
|
|
699
|
-
note: "Paid from earned interest only — principal untouched",
|
|
700
|
-
tx: txLink(result.tx),
|
|
701
|
-
}));
|
|
702
|
-
} catch (e) { return fail(e); }
|
|
703
|
-
},
|
|
704
|
-
});
|
|
731
|
+
const rates = await client.getMarketRates(params.token);
|
|
705
732
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const markets = await client.getMarkets(true);
|
|
719
|
-
|
|
720
|
-
const formatted = markets
|
|
721
|
-
.filter((m) => m.collateralAsset.address !== "0x0000000000000000000000000000000000000000")
|
|
722
|
-
.map((m) => ({
|
|
723
|
-
collateral: m.collateralAsset.symbol,
|
|
724
|
-
loan: m.loanAsset.symbol,
|
|
725
|
-
lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
|
|
726
|
-
totalSupply: `$${(Number(m.totalSupplyAssets) / 1e6).toFixed(0)}`,
|
|
727
|
-
totalBorrow: `$${(Number(m.totalBorrowAssets) / 1e6).toFixed(0)}`,
|
|
728
|
-
utilization: `${(m.utilization * 100).toFixed(1)}%`,
|
|
729
|
-
marketId: m.uniqueKey.slice(0, 18) + "…",
|
|
730
|
-
}));
|
|
733
|
+
if (rates.length === 0) return ok("No markets found.");
|
|
734
|
+
|
|
735
|
+
const formatted = rates.map((r: any) => ({
|
|
736
|
+
collateral: r.collateralToken,
|
|
737
|
+
loan: r.loanToken,
|
|
738
|
+
lltv: r.lltv,
|
|
739
|
+
supplyApy: `${(r.supplyApy * 100).toFixed(2)}%`,
|
|
740
|
+
borrowApy: `${(r.borrowApy * 100).toFixed(2)}%`,
|
|
741
|
+
utilization: `${(r.utilization * 100).toFixed(1)}%`,
|
|
742
|
+
totalSupply: `$${r.totalSupplyUsd.toFixed(0)}`,
|
|
743
|
+
totalBorrow: `$${r.totalBorrowUsd.toFixed(0)}`,
|
|
744
|
+
}));
|
|
731
745
|
|
|
732
746
|
return ok(JSON.stringify(formatted, null, 2));
|
|
733
747
|
} catch (e) { return fail(e); }
|
|
@@ -808,24 +822,88 @@ export default function register(api: any) {
|
|
|
808
822
|
});
|
|
809
823
|
|
|
810
824
|
// ═══════════════════════════════════════════════════════
|
|
811
|
-
// TOOL:
|
|
825
|
+
// TOOL: wallet_withdraw_token
|
|
826
|
+
// ═══════════════════════════════════════════════════════
|
|
827
|
+
api.registerTool({
|
|
828
|
+
name: "wallet_withdraw_token",
|
|
829
|
+
description:
|
|
830
|
+
"Withdraw (transfer) a token from AgentAccount to EOA wallet. " +
|
|
831
|
+
"Works with any ERC-20 token (USDC, WETH, wstETH, cbBTC, etc.). " +
|
|
832
|
+
"Use amount 'all' to withdraw the full balance.",
|
|
833
|
+
parameters: {
|
|
834
|
+
type: "object",
|
|
835
|
+
properties: {
|
|
836
|
+
token: { type: "string", description: "Token to withdraw (e.g. 'USDC', 'WETH')" },
|
|
837
|
+
amount: { type: "string", description: "Amount to withdraw (e.g. '100' or 'all')" },
|
|
838
|
+
},
|
|
839
|
+
required: ["token", "amount"],
|
|
840
|
+
},
|
|
841
|
+
async execute(_id: string, params: { token: string; amount: string }) {
|
|
842
|
+
try {
|
|
843
|
+
const cfg = getConfig(api);
|
|
844
|
+
const client = createClient(cfg);
|
|
845
|
+
const result = await client.withdrawToken(params.token, params.amount);
|
|
846
|
+
return ok(JSON.stringify({
|
|
847
|
+
status: "withdrawn",
|
|
848
|
+
token: result.token,
|
|
849
|
+
amount: result.amount,
|
|
850
|
+
destination: result.destination,
|
|
851
|
+
tx: txLink(result.tx),
|
|
852
|
+
}));
|
|
853
|
+
} catch (e) { return fail(e); }
|
|
854
|
+
},
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// ═══════════════════════════════════════════════════════
|
|
858
|
+
// TOOL: wallet_withdraw_eth
|
|
859
|
+
// ═══════════════════════════════════════════════════════
|
|
860
|
+
api.registerTool({
|
|
861
|
+
name: "wallet_withdraw_eth",
|
|
862
|
+
description:
|
|
863
|
+
"Withdraw ETH from AgentAccount to EOA wallet. " +
|
|
864
|
+
"Use amount 'all' to withdraw the full ETH balance.",
|
|
865
|
+
parameters: {
|
|
866
|
+
type: "object",
|
|
867
|
+
properties: {
|
|
868
|
+
amount: { type: "string", description: "ETH amount to withdraw (e.g. '0.01' or 'all')" },
|
|
869
|
+
},
|
|
870
|
+
required: ["amount"],
|
|
871
|
+
},
|
|
872
|
+
async execute(_id: string, params: { amount: string }) {
|
|
873
|
+
try {
|
|
874
|
+
const cfg = getConfig(api);
|
|
875
|
+
const client = createClient(cfg);
|
|
876
|
+
const result = await client.withdrawEth(params.amount);
|
|
877
|
+
return ok(JSON.stringify({
|
|
878
|
+
status: "withdrawn",
|
|
879
|
+
token: "ETH",
|
|
880
|
+
amount: `${result.amount} ETH`,
|
|
881
|
+
destination: result.destination,
|
|
882
|
+
tx: txLink(result.tx),
|
|
883
|
+
}));
|
|
884
|
+
} catch (e) { return fail(e); }
|
|
885
|
+
},
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// ═══════════════════════════════════════════════════════
|
|
889
|
+
// TOOL: x402_pay (with auto-yield + auto-draw waterfall)
|
|
812
890
|
// ═══════════════════════════════════════════════════════
|
|
813
891
|
api.registerTool({
|
|
814
892
|
name: "x402_pay",
|
|
815
893
|
description:
|
|
816
894
|
"Make a paid API call using x402 protocol. Pays with USDC from AgentAccount via EIP-3009 signature. " +
|
|
817
|
-
"
|
|
895
|
+
"If USDC is insufficient, auto-sources funds: yield first (autoYield), then borrow (autoDraw). " +
|
|
896
|
+
"Both are configured in plugin settings.",
|
|
818
897
|
parameters: {
|
|
819
898
|
type: "object",
|
|
820
899
|
properties: {
|
|
821
900
|
url: { type: "string", description: "API endpoint URL" },
|
|
822
901
|
method: { type: "string", enum: ["GET", "POST"], description: "HTTP method (default: GET)" },
|
|
823
902
|
body: { type: "string", description: "JSON body for POST requests" },
|
|
824
|
-
autoDraw: { type: "boolean", description: "Auto-borrow from Morpho if USDC insufficient (default: false)" },
|
|
825
903
|
},
|
|
826
904
|
required: ["url"],
|
|
827
905
|
},
|
|
828
|
-
async execute(_id: string, params: { url: string; method?: string; body?: string
|
|
906
|
+
async execute(_id: string, params: { url: string; method?: string; body?: string }) {
|
|
829
907
|
try {
|
|
830
908
|
const cfg = getConfig(api);
|
|
831
909
|
const agetherCfg = api.config?.plugins?.entries?.agether?.config || {};
|
|
@@ -840,7 +918,8 @@ export default function register(api: any) {
|
|
|
840
918
|
console.warn('[agether] x402_pay: getAccountAddress failed, paying from EOA:', e instanceof Error ? e.message : e);
|
|
841
919
|
}
|
|
842
920
|
|
|
843
|
-
const autoDrawEnabled =
|
|
921
|
+
const autoDrawEnabled = agetherCfg.autoDraw === true;
|
|
922
|
+
const autoYieldEnabled = agetherCfg.autoYield === true;
|
|
844
923
|
const x402 = new X402Client({
|
|
845
924
|
privateKey: cfg.privateKey,
|
|
846
925
|
rpcUrl: cfg.rpcUrl,
|
|
@@ -848,16 +927,20 @@ export default function register(api: any) {
|
|
|
848
927
|
agentId,
|
|
849
928
|
accountAddress,
|
|
850
929
|
autoDraw: autoDrawEnabled,
|
|
930
|
+
autoYield: autoYieldEnabled,
|
|
851
931
|
dailySpendLimitUsdc: agetherCfg.dailySpendLimitUsdc,
|
|
852
|
-
yieldLimitedSpending: agetherCfg.yieldLimitedSpending,
|
|
853
932
|
autoDrawBuffer: agetherCfg.autoDrawBuffer,
|
|
933
|
+
// Restore spending state from cache so limit survives restarts
|
|
934
|
+
initialSpendingState: loadSpendCache(),
|
|
935
|
+
// Persist every spending update to cache file
|
|
936
|
+
onSpendingUpdate: (state: any) => saveSpendCache(state),
|
|
854
937
|
// Safe7579 needs validator prefix for ERC-1271 isValidSignature routing
|
|
855
938
|
validatorModule: "0xde896C58163b5f6cAC5B16C1b0109843f26106F6",
|
|
856
939
|
});
|
|
857
940
|
|
|
858
941
|
let result;
|
|
859
|
-
if (autoDrawEnabled && agentId) {
|
|
860
|
-
// Use payWithAutoDraw which handles balance
|
|
942
|
+
if ((autoDrawEnabled || autoYieldEnabled) && agentId) {
|
|
943
|
+
// Use payWithAutoDraw which handles waterfall: balance → yield → borrow
|
|
861
944
|
const fetchOpts: any = {
|
|
862
945
|
morphoClient: client,
|
|
863
946
|
};
|
|
@@ -885,98 +968,22 @@ export default function register(api: any) {
|
|
|
885
968
|
};
|
|
886
969
|
}
|
|
887
970
|
if (result.autoDrawInfo) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
971
|
+
const info: any = { reason: result.autoDrawInfo.reason };
|
|
972
|
+
if (result.autoDrawInfo.yieldWithdrawn) {
|
|
973
|
+
info.yieldWithdrawn = `$${result.autoDrawInfo.yieldWithdrawn} USDC`;
|
|
974
|
+
if (result.autoDrawInfo.yieldTx) info.yieldTx = txLink(result.autoDrawInfo.yieldTx);
|
|
975
|
+
}
|
|
976
|
+
if (result.autoDrawInfo.borrowed) {
|
|
977
|
+
info.borrowed = `$${result.autoDrawInfo.borrowed} USDC`;
|
|
978
|
+
if (result.autoDrawInfo.borrowTx) info.borrowTx = txLink(result.autoDrawInfo.borrowTx);
|
|
979
|
+
}
|
|
980
|
+
response.autoFund = info;
|
|
892
981
|
}
|
|
893
982
|
return ok(JSON.stringify(response, null, 2));
|
|
894
983
|
} catch (e) { return fail(e); }
|
|
895
984
|
},
|
|
896
985
|
});
|
|
897
986
|
|
|
898
|
-
// ═══════════════════════════════════════════════════════
|
|
899
|
-
// TOOL: morpho_rates
|
|
900
|
-
// ═══════════════════════════════════════════════════════
|
|
901
|
-
api.registerTool({
|
|
902
|
-
name: "morpho_rates",
|
|
903
|
-
description:
|
|
904
|
-
"Show current Morpho Blue market rates — supply APY, borrow APY, utilization for USDC markets. " +
|
|
905
|
-
"Note: collateral does NOT earn yield on Morpho; supply APY is what lenders earn.",
|
|
906
|
-
parameters: {
|
|
907
|
-
type: "object",
|
|
908
|
-
properties: {
|
|
909
|
-
token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Filter by collateral token (optional)" },
|
|
910
|
-
},
|
|
911
|
-
required: [],
|
|
912
|
-
},
|
|
913
|
-
async execute(_id: string, params: { token?: string }) {
|
|
914
|
-
try {
|
|
915
|
-
const cfg = getConfig(api);
|
|
916
|
-
const client = createClient(cfg);
|
|
917
|
-
const rates = await client.getMarketRates(params.token);
|
|
918
|
-
|
|
919
|
-
if (rates.length === 0) return ok("No markets found.");
|
|
920
|
-
|
|
921
|
-
const formatted = rates.map((r: any) => ({
|
|
922
|
-
collateral: r.collateralToken,
|
|
923
|
-
loan: r.loanToken,
|
|
924
|
-
supplyApy: `${(r.supplyApy * 100).toFixed(2)}%`,
|
|
925
|
-
borrowApy: `${(r.borrowApy * 100).toFixed(2)}%`,
|
|
926
|
-
utilization: `${(r.utilization * 100).toFixed(1)}%`,
|
|
927
|
-
totalSupply: `$${r.totalSupplyUsd.toFixed(0)}`,
|
|
928
|
-
totalBorrow: `$${r.totalBorrowUsd.toFixed(0)}`,
|
|
929
|
-
lltv: r.lltv,
|
|
930
|
-
}));
|
|
931
|
-
|
|
932
|
-
return ok(JSON.stringify(formatted, null, 2));
|
|
933
|
-
} catch (e) { return fail(e); }
|
|
934
|
-
},
|
|
935
|
-
});
|
|
936
|
-
|
|
937
|
-
// ═══════════════════════════════════════════════════════
|
|
938
|
-
// TOOL: morpho_yield_estimate
|
|
939
|
-
// ═══════════════════════════════════════════════════════
|
|
940
|
-
api.registerTool({
|
|
941
|
-
name: "morpho_yield_estimate",
|
|
942
|
-
description:
|
|
943
|
-
"Estimate theoretical yield for collateral deposited in Morpho Blue. " +
|
|
944
|
-
"⚠️ Collateral does NOT actually earn yield on Morpho. This estimates " +
|
|
945
|
-
"what it WOULD earn if it were supplied as a lender instead. " +
|
|
946
|
-
"Useful for setting spending caps based on theoretical earnings.",
|
|
947
|
-
parameters: {
|
|
948
|
-
type: "object",
|
|
949
|
-
properties: {
|
|
950
|
-
token: { type: "string", enum: ["WETH", "wstETH", "cbETH"], description: "Collateral token" },
|
|
951
|
-
amount: { type: "string", description: "Collateral amount (e.g. '1.5')" },
|
|
952
|
-
periodDays: { type: "number", description: "Period in days (default: 1)" },
|
|
953
|
-
ethPriceUsd: { type: "number", description: "ETH price in USD (optional, uses oracle if not provided)" },
|
|
954
|
-
},
|
|
955
|
-
required: ["token", "amount"],
|
|
956
|
-
},
|
|
957
|
-
async execute(_id: string, params: { token: string; amount: string; periodDays?: number; ethPriceUsd?: number }) {
|
|
958
|
-
try {
|
|
959
|
-
const cfg = getConfig(api);
|
|
960
|
-
const client = createClient(cfg);
|
|
961
|
-
const result = await client.getYieldEstimate(
|
|
962
|
-
params.token,
|
|
963
|
-
params.amount,
|
|
964
|
-
params.periodDays || 1,
|
|
965
|
-
params.ethPriceUsd,
|
|
966
|
-
);
|
|
967
|
-
|
|
968
|
-
return ok(JSON.stringify({
|
|
969
|
-
collateral: `${result.amount} ${result.collateralToken}`,
|
|
970
|
-
collateralValueUsd: `$${result.collateralValueUsd.toFixed(2)}`,
|
|
971
|
-
theoreticalSupplyApy: `${(result.theoreticalSupplyApy * 100).toFixed(2)}%`,
|
|
972
|
-
estimatedYieldUsd: `$${result.estimatedYieldUsd.toFixed(4)}`,
|
|
973
|
-
period: `${result.periodDays} day(s)`,
|
|
974
|
-
disclaimer: result.disclaimer,
|
|
975
|
-
}, null, 2));
|
|
976
|
-
} catch (e) { return fail(e); }
|
|
977
|
-
},
|
|
978
|
-
});
|
|
979
|
-
|
|
980
987
|
// ═══════════════════════════════════════════════════════
|
|
981
988
|
// TOOL: morpho_max_borrowable
|
|
982
989
|
// ═══════════════════════════════════════════════════════
|
|
@@ -1177,13 +1184,32 @@ export default function register(api: any) {
|
|
|
1177
1184
|
const client = createClient(cfg);
|
|
1178
1185
|
const b = await client.getBalances();
|
|
1179
1186
|
|
|
1187
|
+
const nz = (v: string) => parseFloat(v) > 0;
|
|
1188
|
+
|
|
1180
1189
|
let text = `💰 Agent #${b.agentId}\n`;
|
|
1181
|
-
text += `Address: ${b.address}
|
|
1182
|
-
text +=
|
|
1183
|
-
text +=
|
|
1190
|
+
text += `Address: ${b.address}`;
|
|
1191
|
+
if (nz(b.eth)) text += `\nETH: ${parseFloat(b.eth).toFixed(6)}`;
|
|
1192
|
+
if (nz(b.usdc)) text += `\nUSDC: $${b.usdc}`;
|
|
1193
|
+
for (const [sym, val] of Object.entries(b.collateral ?? {})) {
|
|
1194
|
+
if (nz(val)) text += `\n${sym}: ${parseFloat(val).toFixed(6)}`;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1184
1197
|
if (b.agentAccount) {
|
|
1185
|
-
|
|
1198
|
+
const a = b.agentAccount;
|
|
1199
|
+
const hasAny = nz(a.eth) || nz(a.usdc) ||
|
|
1200
|
+
Object.values(a.collateral ?? {}).some(nz);
|
|
1201
|
+
if (hasAny) {
|
|
1202
|
+
text += `\n\n🏦 AgentAccount: ${a.address}`;
|
|
1203
|
+
if (nz(a.eth)) text += `\nETH: ${parseFloat(a.eth).toFixed(6)}`;
|
|
1204
|
+
if (nz(a.usdc)) text += `\nUSDC: $${a.usdc}`;
|
|
1205
|
+
for (const [sym, val] of Object.entries(a.collateral ?? {})) {
|
|
1206
|
+
if (nz(val)) text += `\n${sym}: ${parseFloat(val).toFixed(6)}`;
|
|
1207
|
+
}
|
|
1208
|
+
} else {
|
|
1209
|
+
text += `\n\n🏦 AgentAccount: ${a.address}\n(empty)`;
|
|
1210
|
+
}
|
|
1186
1211
|
}
|
|
1212
|
+
|
|
1187
1213
|
return { text };
|
|
1188
1214
|
} catch (e: any) {
|
|
1189
1215
|
return { text: `❌ ${e.message}` };
|