@agether/agether 1.7.3 → 2.0.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/README.md CHANGED
@@ -14,9 +14,41 @@ OpenClaw plugin for **Agether** — onchain credit protocol for AI agents on Bas
14
14
  openclaw plugins install @agether/agether
15
15
  ```
16
16
 
17
- ## Configure
17
+ ## Configure Secrets
18
18
 
19
- Edit `~/.openclaw/openclaw.json` and add the `agether` entry under `plugins.entries`:
19
+ The plugin reads sensitive credentials from **OpenClaw secrets** (environment variables), not from the plugin config. This keeps your private key out of `openclaw.json`.
20
+
21
+ ### 1. Set your private key (required)
22
+
23
+ ```bash
24
+ # Option A: Interactive setup (recommended)
25
+ openclaw secrets configure
26
+ # → source: env → id: AGETHER_PRIVATE_KEY
27
+
28
+ # Option B: Set directly in your shell / .env
29
+ export AGETHER_PRIVATE_KEY=0x...
30
+ ```
31
+
32
+ ### 2. Set an RPC key (optional — better rate limits)
33
+
34
+ The plugin auto-detects RPC keys in this order, falling back to the free public endpoint:
35
+
36
+ | Env Var | Provider | URL pattern |
37
+ |---------|----------|------------|
38
+ | `ALCHEMY_API_KEY` | Alchemy | `https://base-mainnet.g.alchemy.com/v2/<key>` |
39
+ | `ANKR_API_KEY` | Ankr | `https://rpc.ankr.com/base/<key>` |
40
+ | `QUICKNODE_URL` | QuickNode | Used as-is (full URL) |
41
+ | *(none)* | PublicNode | `https://base-rpc.publicnode.com` (free, rate-limited) |
42
+
43
+ ```bash
44
+ # Example: set via OpenClaw secrets
45
+ openclaw secrets configure
46
+ # → source: env → id: ALCHEMY_API_KEY
47
+ ```
48
+
49
+ ### 3. Plugin config (optional)
50
+
51
+ The plugin works with an **empty config**. All fields are optional:
20
52
 
21
53
  ```jsonc
22
54
  // ~/.openclaw/openclaw.json
@@ -26,10 +58,13 @@ Edit `~/.openclaw/openclaw.json` and add the `agether` entry under `plugins.entr
26
58
  "agether": {
27
59
  "enabled": true,
28
60
  "config": {
29
- "privateKey": "0x...", // requiredyour wallet PK
30
- "rpcUrl": "https://base-rpc.publicnode.com", // optional (default)
31
- "backendUrl": "http://95.179.189.214:3001", // optional (default)
32
- "autoDraw": false // optional auto-borrow for x402 payments
61
+ // All fields below are optional plugin works with empty config {}
62
+ "agentId": "17676", // auto-saved after registration
63
+ "autoDraw": false, // auto-borrow for x402 payments
64
+ "dailySpendLimitUsdc": 0, // 0 = unlimited
65
+ "yieldLimitedSpending": false,
66
+ "autoDrawBuffer": 0,
67
+ "healthAlertThreshold": 70 // LTV % to trigger health warnings
33
68
  }
34
69
  }
35
70
  }
@@ -43,7 +78,11 @@ Then restart the gateway:
43
78
  openclaw gateway --force
44
79
  ```
45
80
 
46
- > ⚠️ **Never share your private key.** The `privateKey` field is marked as sensitive in the plugin manifest.
81
+ ### Verify secrets are clean
82
+
83
+ ```bash
84
+ openclaw secrets audit --check
85
+ ```
47
86
 
48
87
  ## Verify
49
88
 
@@ -59,18 +98,23 @@ Once installed, the following tools are available to your AI agent:
59
98
 
60
99
  | Tool | Description |
61
100
  |---|---|
101
+ | `agether_health` | **Start here** — comprehensive check: balances, positions, LTV, liquidation risk, headroom |
102
+ | `agether_preflight` | Setup readiness checklist: key, RPC, registration, balances |
62
103
  | `agether_balance` | Check ETH & USDC balances (EOA + AgentAccount) |
63
104
  | `agether_register` | Mint ERC-8004 identity & create AgentAccount |
64
105
  | `agether_set_agent` | Set a known agentId (from memory) and save to config |
65
106
  | `agether_score` | Get Bayesian credit score with 5-factor breakdown |
66
107
  | `morpho_status` | Show all Morpho credit positions |
67
108
  | `morpho_markets` | List supported Morpho Blue markets |
109
+ | `morpho_rates` | Current market rates — supply APY, borrow APY, utilization |
110
+ | `morpho_max_borrowable` | Calculate max additional USDC borrowable |
111
+ | `morpho_yield_estimate` | Estimate theoretical yield for collateral |
68
112
  | `morpho_deposit` | Deposit collateral (WETH, wstETH, cbETH) |
69
- | `morpho_deposit_and_borrow` | Deposit collateral + borrow USDC in one step |
113
+ | `morpho_deposit_and_borrow` | Deposit collateral + borrow USDC in one batched tx |
70
114
  | `morpho_borrow` | Borrow USDC against deposited collateral |
71
115
  | `morpho_repay` | Repay USDC debt |
72
116
  | `morpho_withdraw` | Withdraw collateral back to EOA |
73
- | `morpho_sponsor` | Human deposits collateral for an agent (by ID or address) |
117
+ | `morpho_sponsor` | Deposit collateral for another agent (by ID or address) |
74
118
  | `wallet_fund` | Transfer USDC from EOA into AgentAccount |
75
119
  | `x402_pay` | Make paid API calls via x402 protocol (supports auto-draw) |
76
120
 
@@ -80,16 +124,25 @@ Quick commands that run without AI reasoning:
80
124
 
81
125
  - `/balance` — wallet balances
82
126
  - `/morpho` — Morpho positions overview
127
+ - `/health` — position health: LTV, liquidation risk, balances
128
+ - `/rates` — current Morpho market rates
129
+
130
+ ## Hooks
131
+
132
+ The plugin registers automatic hooks:
133
+
134
+ - **`command:new`** — logs agent balance context at the start of each new conversation
83
135
 
84
136
  ## Quick Start
85
137
 
86
138
  1. Install: `openclaw plugins install @agether/agether`
87
- 2. Add config to `~/.openclaw/openclaw.json` (see above)
88
- 3. Restart: `openclaw gateway --force`
89
- 4. Open Telegram *"What is my balance?"*
90
- 5. *"Register me as an agent on Agether"*
91
- 6. Fund your wallet with ETH (gas) + WETH/wstETH/cbETH (collateral)
92
- 7. *"Deposit 0.05 WETH and borrow $50 USDC"*
139
+ 2. Set secret: `export AGETHER_PRIVATE_KEY=0x...` (or use `openclaw secrets configure`)
140
+ 3. Enable: add `"agether": { "enabled": true, "config": {} }` to `plugins.entries`
141
+ 4. Restart: `openclaw gateway --force`
142
+ 5. Open Telegram *"What is my balance?"*
143
+ 6. *"Register me as an agent on Agether"*
144
+ 7. Fund your wallet with ETH (gas) + WETH/wstETH/cbETH (collateral)
145
+ 8. *"Deposit 0.05 WETH and borrow $50 USDC"*
93
146
 
94
147
  ## Getting Collateral on Base
95
148
 
@@ -101,11 +154,21 @@ Quick commands that run without AI reasoning:
101
154
 
102
155
  | Option | Required | Default | Description |
103
156
  |---|---|---|---|
104
- | `privateKey` | ✅ | — | Wallet private key for signing transactions |
105
- | `rpcUrl` | — | `https://base-rpc.publicnode.com` | Base RPC endpoint |
106
- | `backendUrl` | — | `http://95.179.189.214:3001` | Agether backend API |
107
- | `autoDraw` | — | `false` | Auto-borrow from credit line when USDC is insufficient for x402 payments |
108
157
  | `agentId` | — | — | Pre-set agent ID (auto-saved after registration) |
158
+ | `autoDraw` | — | `false` | Auto-borrow from credit line when USDC is insufficient for x402 payments |
159
+ | `dailySpendLimitUsdc` | — | `0` | Daily USDC spending cap (0 = unlimited) |
160
+ | `yieldLimitedSpending` | — | `false` | Limit auto-draw to theoretical yield |
161
+ | `autoDrawBuffer` | — | `0` | Extra USDC buffer when auto-drawing |
162
+ | `healthAlertThreshold` | — | `70` | LTV % to trigger health warnings |
163
+
164
+ ### Secrets (env vars)
165
+
166
+ | Env Var | Required | Description |
167
+ |---|---|---|
168
+ | `AGETHER_PRIVATE_KEY` | ✅ | Wallet private key for signing transactions |
169
+ | `ALCHEMY_API_KEY` | — | Alchemy API key for premium Base RPC |
170
+ | `ANKR_API_KEY` | — | Ankr API key for premium Base RPC |
171
+ | `QUICKNODE_URL` | — | Full QuickNode RPC URL |
109
172
 
110
173
  ## Links
111
174
 
@@ -1,44 +1,51 @@
1
1
  {
2
2
  "id": "agether",
3
3
  "name": "Agether Credit",
4
- "description": "Onchain credit protocol for AI agents — Morpho-backed overcollateralized credit, ERC-8004 identity, x402 payments",
5
- "version": "1.2.0",
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.1",
6
6
  "skills": ["skills/agether"],
7
7
  "configSchema": {
8
8
  "type": "object",
9
9
  "additionalProperties": false,
10
10
  "properties": {
11
- "privateKey": {
12
- "type": "string",
13
- "description": "Wallet private key for signing transactions"
14
- },
15
11
  "agentId": {
16
12
  "type": "string",
17
- "description": "ERC-8004 agent ID (set after registration)"
18
- },
19
- "rpcUrl": {
20
- "type": "string",
21
- "description": "Base RPC endpoint",
22
- "default": "https://base-rpc.publicnode.com"
23
- },
24
- "backendUrl": {
25
- "type": "string",
26
- "description": "Agether backend URL",
27
- "default": "http://95.179.189.214:3001"
13
+ "description": "ERC-8004 agent ID (set after registration, auto-saved by tools)"
28
14
  },
29
15
  "autoDraw": {
30
16
  "type": "boolean",
31
17
  "description": "Auto-borrow from Morpho credit line when USDC balance is low for x402 payments",
32
18
  "default": false
19
+ },
20
+ "dailySpendLimitUsdc": {
21
+ "type": "number",
22
+ "description": "Daily USDC spending cap for x402 payments (0 = unlimited)",
23
+ "default": 0
24
+ },
25
+ "yieldLimitedSpending": {
26
+ "type": "boolean",
27
+ "description": "Limit auto-draw spending to theoretical yield on deposited collateral",
28
+ "default": false
29
+ },
30
+ "autoDrawBuffer": {
31
+ "type": "number",
32
+ "description": "Extra USDC buffer when auto-drawing from Morpho (e.g. 0.5 = borrow $0.50 extra)",
33
+ "default": 0
34
+ },
35
+ "healthAlertThreshold": {
36
+ "type": "number",
37
+ "description": "LTV percentage threshold for health warnings (e.g. 70 = warn at 70% LTV, liquidation at 80%)",
38
+ "default": 70
33
39
  }
34
40
  },
35
- "required": ["privateKey"]
41
+ "required": []
36
42
  },
37
43
  "uiHints": {
38
- "privateKey": { "label": "Private Key", "sensitive": true },
39
44
  "agentId": { "label": "Agent ID", "placeholder": "17676" },
40
- "rpcUrl": { "label": "RPC URL", "placeholder": "https://base-rpc.publicnode.com" },
41
- "backendUrl": { "label": "Backend URL", "placeholder": "http://95.179.189.214:3001" },
42
- "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" }
45
+ "autoDraw": { "label": "Auto-Draw Credit", "placeholder": "false" },
46
+ "dailySpendLimitUsdc": { "label": "Daily Spend Limit (USDC)", "placeholder": "0" },
47
+ "yieldLimitedSpending": { "label": "Yield-Limited Spending", "placeholder": "false" },
48
+ "autoDrawBuffer": { "label": "Auto-Draw Buffer (USDC)", "placeholder": "0" },
49
+ "healthAlertThreshold": { "label": "Health Alert Threshold (%)", "placeholder": "70" }
43
50
  }
44
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "1.7.3",
3
+ "version": "2.0.1",
4
4
  "description": "OpenClaw plugin for Agether — onchain credit for AI agents",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
@@ -26,21 +26,23 @@ If you don't paste it, the user sees NOTHING. An empty colon ":" with no data af
26
26
  1. **PASTE tool results into your reply.** Every tool returns JSON. Extract the key fields and write them in your message. The user cannot see tool output directly.
27
27
  2. **ALWAYS paste tx links.** Tools return `tx` field like `https://basescan.org/tx/0x...`. Copy it verbatim into your reply.
28
28
  3. **After on-chain actions, call `agether_balance` and paste the balances.**
29
- 4. **On first message, call `agether_balance`.** If `agentId` is `"?"`, you need to set it up see AGENT ID RESOLUTION below. If not registered at all, call `agether_register`.
29
+ 4. **On first message, call `agether_health`.** This is your single best "context loader" it returns balances, positions, LTV, alerts, and headroom in one call. If `agentId` is `"?"`, see AGENT ID RESOLUTION below.
30
30
  5. **Be proactive** — if the user asks to call a paid API, do the full flow without asking.
31
- 6. **Never ask for private keys** — already configured.
31
+ 6. **Never ask for private keys** — they come from OpenClaw secrets (AGETHER_PRIVATE_KEY env var).
32
32
  7. **Max LTV is 80%** (125% collateral ratio). To borrow $X, you need $X × 1.25 in collateral value.
33
33
  8. **When user says "register" → ALWAYS call `agether_register`.** A wallet CAN have multiple ERC-8004 identities. The tool handles everything. Never refuse to register because the wallet "already has an identity". Just call the tool.
34
34
  9. **Never guess about blockchain state.** If you're unsure, call the tool. Don't tell the user something is impossible — try it first.
35
35
  10. **Your EOA wallet IS the user's wallet.** Same private key, same address. If the user says "I have WETH", call `agether_balance` — you'll see it in the `collateral` field. You already have access to those tokens.
36
36
  11. **`morpho_deposit` and `morpho_deposit_and_borrow` transfer collateral from your EOA automatically.** You do NOT need anyone to "send you" tokens. If your EOA has WETH, just call the deposit tool directly. The SDK handles EOA→Safe account→Morpho in one flow.
37
37
  12. **To get USDC for x402:** call `morpho_deposit_and_borrow` with collateral from your EOA. Do NOT ask the user to send tokens — check your balance first.
38
+ 13. **Check health before large actions.** Before borrowing, depositing, or withdrawing significant amounts, call `agether_health` to see current LTV and headroom. This prevents failed transactions.
39
+ 14. **Batch awareness:** `morpho_deposit_and_borrow` is a batched operation (deposit + borrow in one tx). Always prefer it over separate `morpho_deposit` then `morpho_borrow` when doing both — it saves gas and is atomic.
38
40
 
39
41
  ---
40
42
 
41
43
  ## 🆔 AGENT ID RESOLUTION (CRITICAL)
42
44
 
43
- If `agether_balance` returns `agentId: "?"`, ALL Morpho tools will fail. You MUST resolve this first:
45
+ If `agether_health` or `agether_balance` returns `agentId: "?"`, ALL Morpho tools will fail. You MUST resolve this first:
44
46
 
45
47
  ```
46
48
  1. Check your memory for a previously registered agentId
@@ -50,7 +52,7 @@ If `agether_balance` returns `agentId: "?"`, ALL Morpho tools will fail. You MUS
50
52
  → IF user says no → call `agether_register(name: "<ask name>")`
51
53
  3. IF no agentId in memory:
52
54
  → call `agether_register(name: "<ask name>")`
53
- 4. After either path → call `agether_balance` to confirm agentId is set
55
+ 4. After either path → call `agether_health` to confirm everything is working
54
56
  ```
55
57
 
56
58
  Both `agether_set_agent` and `agether_register` save the agentId to config permanently (survives restarts).
@@ -59,6 +61,12 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
59
61
 
60
62
  ## 🛠 YOUR TOOLS
61
63
 
64
+ ### Health & Diagnostics
65
+ | Tool | What it does |
66
+ |------|-------------|
67
+ | `agether_health` | **START HERE.** Comprehensive check: balances, all positions with LTV & liquidation risk, borrowing headroom, alerts — all in one call. |
68
+ | `agether_preflight` | Setup readiness check: private key present? RPC working? Agent registered? Balances OK? Returns pass/fail checklist. |
69
+
62
70
  ### Identity & Wallet
63
71
  | Tool | What it does |
64
72
  |------|-------------|
@@ -73,70 +81,118 @@ Both `agether_set_agent` and `agether_register` save the agentId to config perma
73
81
  |------|--------|-------------|
74
82
  | `morpho_markets` | none | List supported Morpho markets and parameters |
75
83
  | `morpho_status` | none | Show all Morpho positions (collateral + debt) |
84
+ | `morpho_rates` | `token?` | Current market rates — supply APY, borrow APY, utilization |
85
+ | `morpho_max_borrowable` | none | Calculate max additional USDC borrowable given current collateral/debt |
86
+ | `morpho_yield_estimate` | `token`, `amount`, `periodDays?` | Estimate theoretical yield for collateral |
76
87
  | `morpho_deposit` | `amount`, `token` | Deposit collateral from EOA → Morpho (no borrow) |
77
- | `morpho_deposit_and_borrow` | `collateralAmount`, `token`, `borrowAmount` | Deposit + borrow in one flow. **Best for first-time setup** |
88
+ | `morpho_deposit_and_borrow` | `collateralAmount`, `token`, `borrowAmount` | **PREFERRED** — Deposit + borrow in one batched tx. Best for first-time setup |
78
89
  | `morpho_borrow` | `amount` (USD) | Borrow USDC against existing collateral → lands in Safe account |
79
- | `morpho_repay` | `amount` (USD) | Repay USDC debt from Safe account → Morpho |
90
+ | `morpho_repay` | `amount` (USD or "all") | Repay USDC debt from Safe account → Morpho |
80
91
  | `morpho_withdraw` | `amount` (or "all"), `token` | Withdraw collateral back to EOA wallet |
81
- | `morpho_sponsor` | `agentId`/`agentAddress`, `amount`, `token`, `borrow?` | Deposit collateral for another agent |
92
+ | `morpho_sponsor` | `agentId`/`agentAddress`, `amount`, `token` | Deposit collateral for another agent |
82
93
 
83
94
  ### x402 Payments
84
95
  | Tool | Params | What it does |
85
96
  |------|--------|-------------|
86
97
  | `x402_pay` | `url`, `method?`, `body?`, `autoDraw?` | Make a paid API call. Pays with USDC via EIP-3009 signature |
87
98
 
88
- ### Slash Commands (user types these)
99
+ ### Slash Commands (user types these — no AI invocation)
89
100
  | Command | What it does |
90
101
  |---------|-------------|
91
102
  | `/balance` | Quick balance check |
92
103
  | `/morpho` | Quick Morpho position summary |
104
+ | `/health` | Quick position health: LTV, liquidation risk, balances |
105
+ | `/rates` | Current Morpho market rates |
93
106
 
94
107
  ---
95
108
 
96
109
  ## 📋 DECISION TREES
97
110
 
111
+ ### Session start (first message or new conversation)
112
+ ```
113
+ 1. agether_health ← ONE call for full context
114
+ 2. Check the "alerts" array:
115
+ → If any 🔴 alerts → WARN user immediately (liquidation risk!)
116
+ → If any 🟡 alerts → mention the risk casually
117
+ → If "agentId: ?" → go to AGENT ID RESOLUTION
118
+ 3. Now you have full context to handle any request
119
+ ```
120
+
98
121
  ### User asks to call a paid API (x402)
99
122
  ```
100
- 1. agether_balance check USDC in Safe account AND collateral on EOA
101
- 2. IF Safe account has USDC ≥ $0.01:
123
+ 1. agether_health ← get full picture in one call
124
+ 2. Look at balances.agentAccount.usdc:
125
+ IF USDC ≥ $0.01:
102
126
  → x402_pay(url)
103
- 3. IF no USDC but has collateral deposited in Morpho:
127
+ 3. IF no USDC but positions exist with headroom:
104
128
  → morpho_borrow(amount: "1") ← borrow $1
105
129
  → x402_pay(url)
106
- 4. IF no USDC AND no Morpho collateral BUT EOA has WETH/wstETH/cbETH:
130
+ 4. IF no USDC AND no positions BUT EOA has WETH/wstETH/cbETH:
107
131
  → morpho_deposit_and_borrow(collateralAmount, token, borrowAmount: "1")
108
132
  → x402_pay(url)
109
- 5. IF no USDC AND no collateral anywhere:
110
- → Tell user: "Need collateral (WETH/wstETH/cbETH) on Base to get credit. Send to <EOA address>"
133
+ 5. IF nothing at all:
134
+ → Tell user: "Need collateral (WETH/wstETH/cbETH) on Base. Send to <EOA address>"
111
135
  ```
112
136
 
113
137
  ### User wants to borrow USDC via Morpho
114
138
  ```
115
- 1. agether_balance ← check collateral on EOA
116
- 2. morpho_deposit_and_borrow(collateralAmount, token, borrowAmount)
117
- 3. agether_balance ← show the result with balance changes
118
- 4. Show: tx hash, collateral deposited, USDC borrowed, new balances
139
+ 1. agether_health ← check collateral, headroom, current LTV
140
+ 2. IF headroom allows the borrow amount AND collateral already in Morpho:
141
+ morpho_borrow(amount)
142
+ 3. IF EOA has collateral but nothing in Morpho yet:
143
+ → morpho_deposit_and_borrow(collateralAmount, token, borrowAmount)
144
+ 4. agether_balance ← show the result with balance changes
145
+ 5. Show: tx hash, collateral deposited, USDC borrowed, new balances, new LTV
119
146
  ```
120
147
 
121
148
  ### User wants to repay + withdraw
122
149
  ```
123
- 1. morpho_status ← check current debt and collateral
124
- 2. morpho_repay(amount) ← repay USDC
150
+ 1. agether_health ← check current debt, collateral, LTV
151
+ 2. morpho_repay(amount or "all") ← repay USDC
125
152
  3. morpho_withdraw("all", token) ← get collateral back
126
153
  4. agether_balance ← show final balances
127
154
  ```
128
155
 
156
+ ### User asks about position health or liquidation risk
157
+ ```
158
+ 1. agether_health ← returns LTV, risk level, alerts per position
159
+ 2. Paste the positions table with LTV percentages
160
+ 3. IF any position has LTV > 70%:
161
+ → Suggest: "Consider repaying debt or adding collateral"
162
+ → Show: morpho_max_borrowable for headroom context
163
+ ```
164
+
165
+ ### User wants to optimize rates or compare markets
166
+ ```
167
+ 1. morpho_rates ← get all market rates
168
+ 2. morpho_yield_estimate(token, amount) ← estimate yield
169
+ 3. Present: which market has lowest borrow APY, highest supply APY
170
+ 4. Suggest optimal strategy based on their holdings
171
+ ```
172
+
129
173
  ### User asks to check or refresh credit score
130
174
  ```
131
175
  1. IF user says "refresh" / "update" / "get new score":
132
- agether_score(refresh: true) ← pays via x402, computes fresh score, writes on-chain
176
+ Check USDC balance first (refresh costs x402)
177
+ → agether_score(refresh: true) ← pays via x402, computes fresh score
133
178
  → Paste the score and tx link
134
179
  2. IF user says "check" / "what's my score":
135
- → agether_score(refresh: false) ← free, reads current on-chain score
180
+ → agether_score(refresh: false) ← free read
136
181
  → Paste the score
137
182
  3. NEVER say "not registered" or "no score yet" without calling the tool first!
138
183
  ```
139
184
 
185
+ ### Something seems wrong / setup issues
186
+ ```
187
+ 1. agether_preflight ← run the diagnostic checklist
188
+ 2. Show each check with its status
189
+ 3. Address any ❌ or ⚠️ items:
190
+ - Missing private key → guide to `openclaw secrets configure`
191
+ - Not registered → offer to register
192
+ - No ETH for gas → show EOA address for funding
193
+ - No USDC → suggest deposit_and_borrow flow
194
+ ```
195
+
140
196
  ### User asks "what can I do?" or "what is agether?"
141
197
  ```
142
198
  Explain: You're an AI agent with on-chain credit on Base.
@@ -144,6 +200,7 @@ You can:
144
200
  - Deposit WETH/wstETH/cbETH as collateral
145
201
  - Borrow USDC instantly (125% collateral ratio)
146
202
  - Pay for APIs using x402 protocol
203
+ - Monitor position health and avoid liquidation
147
204
  - All on Base mainnet, all autonomous
148
205
  ```
149
206
 
@@ -157,7 +214,7 @@ Example after repay:
157
214
  ```
158
215
  ✅ Repaid $1.50 USDC
159
216
 
160
- Tx: https://basescan.org/tx/0xabc123def456...
217
+ 🔗 Tx: https://basescan.org/tx/0xabc123def456...
161
218
  💰 Balances:
162
219
  EOA: 0.05 ETH, 0 USDC
163
220
  AgentAccount: 0 ETH, $3.50 USDC
@@ -199,6 +256,7 @@ If something **fails**:
199
256
 
200
257
  | Error | Meaning | What to do |
201
258
  |-------|---------|-----------|
259
+ | `Missing AGETHER_PRIVATE_KEY` | Private key not set in secrets | Run `openclaw secrets configure` → source: env → id: AGETHER_PRIVATE_KEY |
202
260
  | `ExceedsMaxLtv` | Collateral too low for borrow amount | Deposit more collateral or borrow less. LTV must be ≤ 80% |
203
261
  | `Payment rejected (402)` | No USDC for x402 payment | Borrow USDC first: `morpho_borrow` or `morpho_deposit_and_borrow` |
204
262
  | `No collateral deposited` | Trying to borrow without collateral | `morpho_deposit` first |
@@ -208,10 +266,25 @@ If something **fails**:
208
266
 
209
267
  ---
210
268
 
269
+ ## 🔐 SECRETS SETUP (for reference)
270
+
271
+ The plugin reads secrets from environment variables via OpenClaw's secrets system:
272
+
273
+ | Secret | Env Var | Required | Notes |
274
+ |--------|---------|----------|-------|
275
+ | Wallet private key | `AGETHER_PRIVATE_KEY` | Yes | Set via `openclaw secrets configure` → source: env |
276
+ | Alchemy RPC key | `ALCHEMY_API_KEY` | No | Higher rate limits than free RPC |
277
+ | Ankr RPC key | `ANKR_API_KEY` | No | Alternative premium RPC |
278
+ | QuickNode RPC URL | `QUICKNODE_URL` | No | Full URL including API key |
279
+
280
+ If no RPC key is set, the plugin falls back to `https://base-rpc.publicnode.com` (free, rate-limited).
281
+
282
+ ---
283
+
211
284
  ## 🏗 ARCHITECTURE (for context)
212
285
 
213
286
  ```
214
- EOA Wallet (private key)
287
+ EOA Wallet (private key from AGETHER_PRIVATE_KEY)
215
288
  ├── Owns ERC-8004 Identity NFT (agentId) ← can own MULTIPLE
216
289
  └── Owns Safe Account (via Safe7579 + ERC-4337, 1 per agentId)
217
290
  ├── Holds USDC (from borrows)
package/src/index.ts CHANGED
@@ -3,6 +3,16 @@
3
3
  *
4
4
  * Each tool = MorphoClient / X402Client call → format result with txLink.
5
5
  * Market discovery via Morpho GraphQL API (no backend dependency for markets).
6
+ *
7
+ * v2: Secrets-first config
8
+ * ─────────────────────────
9
+ * - privateKey → AGETHER_PRIVATE_KEY env var (set via `openclaw secrets configure` → crypto)
10
+ * - rpcUrl → auto-resolved from ALCHEMY_API_KEY / ANKR_API_KEY / QUICKNODE_URL env vars,
11
+ * falls back to https://base-rpc.publicnode.com
12
+ * - backendUrl → hardcoded (no config needed)
13
+ * - agentId → optional plugin config field (auto-saved by tools)
14
+ *
15
+ * The plugin works with an empty `plugins.entries.agether.config: {}`.
6
16
  */
7
17
 
8
18
  import axios from "axios";
@@ -13,6 +23,12 @@ import {
13
23
  X402Client,
14
24
  } from "@agether/sdk";
15
25
 
26
+ // ─── Constants ────────────────────────────────────────────
27
+
28
+ const BACKEND_URL = "http://95.179.189.214:3001";
29
+ const DEFAULT_RPC = "https://base-rpc.publicnode.com";
30
+ const BASESCAN = "https://basescan.org/tx";
31
+
16
32
  // ─── Helpers ──────────────────────────────────────────────
17
33
 
18
34
  interface PluginConfig {
@@ -22,7 +38,6 @@ interface PluginConfig {
22
38
  backendUrl: string;
23
39
  }
24
40
 
25
- const BASESCAN = "https://basescan.org/tx";
26
41
  function txLink(hash: string): string {
27
42
  return hash ? `${BASESCAN}/${hash}` : "";
28
43
  }
@@ -44,16 +59,48 @@ function fail(err: unknown) {
44
59
  return { content: [{ type: "text" as const, text: `❌ ${msg}` }], isError: true };
45
60
  }
46
61
 
47
- function getConfig(api: any): PluginConfig {
48
- const cfg = api.config?.plugins?.entries?.["agether"]?.config;
49
- if (!cfg?.privateKey) {
50
- throw new Error("Agether plugin not configured: missing privateKey in plugins.entries.agether.config");
62
+ // ─── Secrets-first config resolution ──────────────────────
63
+
64
+ /**
65
+ * Resolve RPC URL from environment secrets.
66
+ * Priority: ALCHEMY_API_KEY → ANKR_API_KEY → QUICKNODE_URL → publicnode fallback.
67
+ */
68
+ function resolveRpcUrl(): string {
69
+ const alchemy = process.env.ALCHEMY_API_KEY;
70
+ if (alchemy) return `https://base-mainnet.g.alchemy.com/v2/${alchemy}`;
71
+
72
+ const ankr = process.env.ANKR_API_KEY;
73
+ if (ankr) return `https://rpc.ankr.com/base/${ankr}`;
74
+
75
+ const quicknode = process.env.QUICKNODE_URL;
76
+ if (quicknode) return quicknode;
77
+
78
+ return DEFAULT_RPC;
79
+ }
80
+
81
+ /**
82
+ * Resolve private key from environment secrets.
83
+ * Set via `openclaw secrets configure` (source: env → AGETHER_PRIVATE_KEY).
84
+ */
85
+ function resolvePrivateKey(): string {
86
+ const key = process.env.AGETHER_PRIVATE_KEY;
87
+ if (!key) {
88
+ throw new Error(
89
+ "Missing AGETHER_PRIVATE_KEY. " +
90
+ "Set it via: openclaw secrets configure → source: env → id: AGETHER_PRIVATE_KEY\n" +
91
+ "Or export AGETHER_PRIVATE_KEY=0x... in your shell / .env file.",
92
+ );
51
93
  }
94
+ return key;
95
+ }
96
+
97
+ function getConfig(api: any): PluginConfig {
98
+ const cfg = api.config?.plugins?.entries?.["agether"]?.config ?? {};
52
99
  return {
53
- privateKey: cfg.privateKey,
100
+ privateKey: resolvePrivateKey(),
54
101
  agentId: cfg.agentId,
55
- rpcUrl: cfg.rpcUrl || "https://base-rpc.publicnode.com",
56
- backendUrl: cfg.backendUrl || "http://95.179.189.214:3001",
102
+ rpcUrl: resolveRpcUrl(),
103
+ backendUrl: BACKEND_URL,
57
104
  };
58
105
  }
59
106
 
@@ -76,13 +123,17 @@ function persistAgentId(agentId: string): string {
76
123
  const home = process.env.HOME || process.env.USERPROFILE || "/root";
77
124
  const cfgPath = path.join(home, ".openclaw", "openclaw.json");
78
125
  const raw = fs.readFileSync(cfgPath, "utf-8");
79
- const cfg = JSON.parse(raw);
80
- if (cfg?.plugins?.entries?.agether?.config) {
81
- cfg.plugins.entries.agether.config.agentId = agentId;
82
- fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
83
- return "saved";
84
- }
85
- return "config structure not found";
126
+ const json = JSON.parse(raw);
127
+
128
+ // Ensure config path exists
129
+ if (!json.plugins) json.plugins = {};
130
+ if (!json.plugins.entries) json.plugins.entries = {};
131
+ if (!json.plugins.entries.agether) json.plugins.entries.agether = {};
132
+ if (!json.plugins.entries.agether.config) json.plugins.entries.agether.config = {};
133
+
134
+ json.plugins.entries.agether.config.agentId = agentId;
135
+ fs.writeFileSync(cfgPath, JSON.stringify(json, null, 2));
136
+ return "saved";
86
137
  } catch (e) {
87
138
  return `write failed: ${e instanceof Error ? e.message : String(e)}`;
88
139
  }
@@ -789,6 +840,163 @@ export default function register(api: any) {
789
840
  },
790
841
  });
791
842
 
843
+ // ═══════════════════════════════════════════════════════
844
+ // TOOL: agether_health (comprehensive position health)
845
+ // ═══════════════════════════════════════════════════════
846
+ api.registerTool({
847
+ name: "agether_health",
848
+ description:
849
+ "Comprehensive health check: balances, all Morpho positions with LTV & liquidation risk, " +
850
+ "borrowing headroom, and current market rates — all in one call. " +
851
+ "Use this as the first call in any session to get full context before making decisions.",
852
+ parameters: { type: "object", properties: {}, required: [] },
853
+ async execute() {
854
+ try {
855
+ const cfg = getConfig(api);
856
+ const agetherCfg = api.config?.plugins?.entries?.agether?.config ?? {};
857
+ const healthThreshold = agetherCfg.healthAlertThreshold ?? 70;
858
+ const client = createClient(cfg);
859
+
860
+ // Gather everything in parallel where possible
861
+ const [balances, status, maxBorrow] = await Promise.all([
862
+ client.getBalances(),
863
+ client.getStatus(),
864
+ client.getMaxBorrowable(),
865
+ ]);
866
+
867
+ // Build per-position health analysis
868
+ const positionHealth = status.positions.map((p: any) => {
869
+ const matchingMarket = maxBorrow.byMarket.find(
870
+ (m: any) => m.collateralToken === p.collateralToken,
871
+ );
872
+ const collateralValue = matchingMarket ? Number(matchingMarket.collateralValue) / 1e6 : 0;
873
+ const debt = parseFloat(p.debt);
874
+ const ltv = collateralValue > 0 ? (debt / collateralValue) * 100 : 0;
875
+ const maxLtv = 80; // Morpho LLTV
876
+ const headroom = matchingMarket ? Number(matchingMarket.maxAdditional) / 1e6 : 0;
877
+
878
+ let risk: string;
879
+ if (debt === 0) risk = "🟢 no debt";
880
+ else if (ltv >= maxLtv) risk = "🔴 LIQUIDATION ZONE";
881
+ else if (ltv >= healthThreshold) risk = "🟡 WARNING — approaching liquidation";
882
+ else risk = "🟢 healthy";
883
+
884
+ return {
885
+ collateralToken: p.collateralToken,
886
+ collateral: p.collateral,
887
+ collateralValueUsd: `$${collateralValue.toFixed(2)}`,
888
+ debt: `$${debt.toFixed(2)}`,
889
+ ltvPercent: `${ltv.toFixed(1)}%`,
890
+ maxLtv: `${maxLtv}%`,
891
+ headroomUsdc: `$${headroom.toFixed(2)}`,
892
+ risk,
893
+ };
894
+ });
895
+
896
+ const alerts: string[] = [];
897
+ for (const ph of positionHealth) {
898
+ if (ph.risk.includes("LIQUIDATION")) {
899
+ alerts.push(`🔴 ${ph.collateralToken}: LTV at ${ph.ltvPercent} — at risk of liquidation!`);
900
+ } else if (ph.risk.includes("WARNING")) {
901
+ alerts.push(`🟡 ${ph.collateralToken}: LTV at ${ph.ltvPercent} — consider adding collateral or repaying debt`);
902
+ }
903
+ }
904
+
905
+ // Check gas (ETH) is sufficient for transactions
906
+ const ethBalance = parseFloat(balances.eth);
907
+ if (ethBalance < 0.0005) {
908
+ alerts.push("⛽ Low ETH for gas — may fail on-chain transactions. Send ETH to EOA.");
909
+ }
910
+
911
+ const result = {
912
+ agentId: balances.agentId,
913
+ walletAddress: balances.address,
914
+ balances: {
915
+ eoa: { eth: balances.eth, usdc: balances.usdc },
916
+ agentAccount: balances.agentAccount
917
+ ? { address: balances.agentAccount.address, usdc: balances.agentAccount.usdc }
918
+ : null,
919
+ },
920
+ totalDebt: `$${status.totalDebt}`,
921
+ totalBorrowingHeadroom: `$${(Number(maxBorrow.total) / 1e6).toFixed(2)}`,
922
+ positions: positionHealth,
923
+ alerts: alerts.length > 0 ? alerts : ["✅ All positions healthy"],
924
+ rpcSource: process.env.ALCHEMY_API_KEY ? "Alchemy" :
925
+ process.env.ANKR_API_KEY ? "Ankr" :
926
+ process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free)",
927
+ };
928
+
929
+ return ok(JSON.stringify(result, null, 2));
930
+ } catch (e) { return fail(e); }
931
+ },
932
+ });
933
+
934
+ // ═══════════════════════════════════════════════════════
935
+ // TOOL: agether_preflight (setup readiness check)
936
+ // ═══════════════════════════════════════════════════════
937
+ api.registerTool({
938
+ name: "agether_preflight",
939
+ description:
940
+ "Check that the agent is fully set up: private key present, RPC working, " +
941
+ "agent registered, balances non-zero. Returns a checklist of pass/fail items. " +
942
+ "Call this if anything seems wrong or on first-time setup.",
943
+ parameters: { type: "object", properties: {}, required: [] },
944
+ async execute() {
945
+ const checks: Array<{ check: string; status: string; detail?: string }> = [];
946
+
947
+ // 1. Private key
948
+ try {
949
+ resolvePrivateKey();
950
+ checks.push({ check: "Private key (AGETHER_PRIVATE_KEY)", status: "✅ set" });
951
+ } catch {
952
+ checks.push({ check: "Private key (AGETHER_PRIVATE_KEY)", status: "❌ missing", detail: "Set via: openclaw secrets configure → source: env → id: AGETHER_PRIVATE_KEY" });
953
+ }
954
+
955
+ // 2. RPC URL
956
+ const rpcUrl = resolveRpcUrl();
957
+ const rpcSource = process.env.ALCHEMY_API_KEY ? "Alchemy" :
958
+ process.env.ANKR_API_KEY ? "Ankr" :
959
+ process.env.QUICKNODE_URL ? "QuickNode" : "PublicNode (free fallback)";
960
+ checks.push({ check: "RPC endpoint", status: `✅ ${rpcSource}`, detail: rpcUrl.replace(/\/[a-zA-Z0-9_-]{20,}$/, '/***') });
961
+
962
+ // 3. Agent registration
963
+ try {
964
+ const cfg = getConfig(api);
965
+ const client = createClient(cfg);
966
+ const balances = await client.getBalances();
967
+
968
+ if (balances.agentId && balances.agentId !== '?') {
969
+ checks.push({ check: "Agent registration", status: `✅ ID: ${balances.agentId}` });
970
+ } else {
971
+ checks.push({ check: "Agent registration", status: "⚠️ not registered", detail: "Call agether_register to create an identity" });
972
+ }
973
+
974
+ // 4. ETH for gas
975
+ const eth = parseFloat(balances.eth);
976
+ if (eth >= 0.001) {
977
+ checks.push({ check: "ETH for gas", status: `✅ ${eth.toFixed(6)} ETH` });
978
+ } else {
979
+ checks.push({ check: "ETH for gas", status: `⚠️ ${eth.toFixed(6)} ETH — low`, detail: `Send ETH to ${balances.address}` });
980
+ }
981
+
982
+ // 5. USDC availability
983
+ const eoaUsdc = parseFloat(balances.usdc);
984
+ const safeUsdc = balances.agentAccount ? parseFloat(balances.agentAccount.usdc) : 0;
985
+ if (safeUsdc > 0) {
986
+ checks.push({ check: "USDC (AgentAccount)", status: `✅ $${safeUsdc.toFixed(2)}` });
987
+ } else if (eoaUsdc > 0) {
988
+ checks.push({ check: "USDC", status: `⚠️ $${eoaUsdc.toFixed(2)} in EOA only`, detail: "Use wallet_fund or morpho_deposit_and_borrow to get USDC into AgentAccount" });
989
+ } else {
990
+ checks.push({ check: "USDC", status: "❌ no USDC anywhere", detail: "Deposit collateral and borrow, or send USDC to EOA" });
991
+ }
992
+ } catch (e: any) {
993
+ checks.push({ check: "Blockchain connectivity", status: "❌ failed", detail: e.message });
994
+ }
995
+
996
+ return ok(JSON.stringify({ preflight: checks }, null, 2));
997
+ },
998
+ });
999
+
792
1000
  // ═══════════════════════════════════════════════════════
793
1001
  // SLASH COMMANDS (no AI needed)
794
1002
  // ═══════════════════════════════════════════════════════
@@ -835,4 +1043,95 @@ export default function register(api: any) {
835
1043
  }
836
1044
  },
837
1045
  });
1046
+
1047
+ api.registerCommand({
1048
+ name: "health",
1049
+ description: "Quick position health check — LTV, liquidation risk, balances (no AI)",
1050
+ handler: async () => {
1051
+ try {
1052
+ const cfg = getConfig(api);
1053
+ const agetherCfg = api.config?.plugins?.entries?.agether?.config ?? {};
1054
+ const healthThreshold = agetherCfg.healthAlertThreshold ?? 70;
1055
+ const client = createClient(cfg);
1056
+
1057
+ const [balances, status, maxBorrow] = await Promise.all([
1058
+ client.getBalances(),
1059
+ client.getStatus(),
1060
+ client.getMaxBorrowable(),
1061
+ ]);
1062
+
1063
+ let text = `🏥 Health — Agent #${balances.agentId}\n`;
1064
+ text += `EOA: ${balances.eth} ETH, $${balances.usdc} USDC\n`;
1065
+ if (balances.agentAccount) {
1066
+ text += `Safe: $${balances.agentAccount.usdc} USDC\n`;
1067
+ }
1068
+ text += `Total debt: $${status.totalDebt}\n`;
1069
+ text += `Headroom: $${(Number(maxBorrow.total) / 1e6).toFixed(2)}\n`;
1070
+
1071
+ for (const p of status.positions) {
1072
+ const mm = maxBorrow.byMarket.find((m: any) => m.collateralToken === p.collateralToken);
1073
+ const cv = mm ? Number(mm.collateralValue) / 1e6 : 0;
1074
+ const debt = parseFloat(p.debt);
1075
+ const ltv = cv > 0 ? (debt / cv) * 100 : 0;
1076
+ const icon = debt === 0 ? "🟢" : ltv >= 80 ? "🔴" : ltv >= healthThreshold ? "🟡" : "🟢";
1077
+ text += `\n${icon} ${p.collateralToken}: ${p.collateral} col, $${p.debt} debt, LTV ${ltv.toFixed(1)}%`;
1078
+ }
1079
+ if (status.positions.length === 0) text += "\nNo active positions.";
1080
+
1081
+ return { text };
1082
+ } catch (e: any) {
1083
+ return { text: `❌ ${e.message}` };
1084
+ }
1085
+ },
1086
+ });
1087
+
1088
+ api.registerCommand({
1089
+ name: "rates",
1090
+ description: "Show current Morpho market rates (no AI)",
1091
+ handler: async () => {
1092
+ try {
1093
+ const cfg = getConfig(api);
1094
+ const client = createClient(cfg);
1095
+ const rates = await client.getMarketRates();
1096
+
1097
+ if (rates.length === 0) return { text: "No markets found." };
1098
+
1099
+ let text = "📈 Morpho Rates\n";
1100
+ for (const r of rates as any[]) {
1101
+ text += `\n${r.collateralToken}/${r.loanToken}: borrow ${(r.borrowApy * 100).toFixed(2)}%, supply ${(r.supplyApy * 100).toFixed(2)}%, util ${(r.utilization * 100).toFixed(1)}%`;
1102
+ }
1103
+ return { text };
1104
+ } catch (e: any) {
1105
+ return { text: `❌ ${e.message}` };
1106
+ }
1107
+ },
1108
+ });
1109
+
1110
+ // ═══════════════════════════════════════════════════════
1111
+ // HOOKS (automatic actions)
1112
+ // ═══════════════════════════════════════════════════════
1113
+
1114
+ api.registerHook(
1115
+ "command:new",
1116
+ async () => {
1117
+ // On new conversation, log a quick context summary so the agent
1118
+ // starts with awareness of the current state.
1119
+ try {
1120
+ const cfg = getConfig(api);
1121
+ const client = createClient(cfg);
1122
+ const balances = await client.getBalances();
1123
+ const agentId = balances.agentId ?? "?";
1124
+ const safeUsdc = balances.agentAccount?.usdc ?? "0";
1125
+ api.logger?.info?.(
1126
+ `[agether] Session start — Agent #${agentId}, EOA: ${balances.eth} ETH / $${balances.usdc} USDC, Safe: $${safeUsdc} USDC`,
1127
+ );
1128
+ } catch {
1129
+ // Silently fail — hook should not block conversations
1130
+ }
1131
+ },
1132
+ {
1133
+ name: "agether.session-context",
1134
+ description: "Logs agent balance context at the start of each new conversation",
1135
+ },
1136
+ );
838
1137
  }
package/test/kya.test.ts DELETED
@@ -1,157 +0,0 @@
1
- /**
2
- * KYA gate feature — unit tests for plugin tools.
3
- *
4
- * Mocks MorphoClient so no blockchain connection is needed.
5
- */
6
-
7
- import { describe, it, expect, vi, beforeEach } from 'vitest';
8
-
9
- // ── Mock state ──
10
-
11
- const mockIsKyaRequired = vi.fn<() => Promise<boolean>>();
12
- const mockRegister = vi.fn();
13
- const mockGetBalances = vi.fn();
14
- const mockGetAgentId = vi.fn().mockReturnValue('12345');
15
- const mockGetAccountAddress = vi.fn().mockResolvedValue('0xAGENTACCOUNT');
16
-
17
- vi.mock('@agether/sdk', () => {
18
- // MorphoClient must work with `new` — use a class
19
- class MockMorphoClient {
20
- isKyaRequired = mockIsKyaRequired;
21
- register = mockRegister;
22
- getBalances = mockGetBalances;
23
- getAgentId = mockGetAgentId;
24
- getAccountAddress = mockGetAccountAddress;
25
- }
26
- class MockX402Client {}
27
- return {
28
- MorphoClient: MockMorphoClient,
29
- X402Client: MockX402Client,
30
- };
31
- });
32
-
33
- // Suppress fs.readFileSync for persistAgentId
34
- vi.mock('fs', async (importOriginal) => {
35
- const orig = await importOriginal<typeof import('fs')>();
36
- return {
37
- ...orig,
38
- default: {
39
- ...orig,
40
- readFileSync: vi.fn().mockReturnValue('{}'),
41
- writeFileSync: vi.fn(),
42
- },
43
- readFileSync: vi.fn().mockReturnValue('{}'),
44
- writeFileSync: vi.fn(),
45
- };
46
- });
47
-
48
- import registerPlugin from '../src/index';
49
-
50
- // ── Helpers ──
51
-
52
- /** Minimal mock for the OpenClaw plugin API */
53
- function createMockApi(config?: Record<string, any>) {
54
- const tools = new Map<string, { execute: Function }>();
55
- return {
56
- config: {
57
- plugins: {
58
- entries: {
59
- agether: {
60
- config: {
61
- privateKey: '0x' + 'ab'.repeat(32),
62
- agentId: '12345',
63
- rpcUrl: 'http://127.0.0.1:8545',
64
- backendUrl: 'http://localhost:3001',
65
- ...config,
66
- },
67
- },
68
- },
69
- },
70
- },
71
- registerTool(def: { name: string; execute: Function }) {
72
- tools.set(def.name, def);
73
- },
74
- registerCommand() { /* noop */ },
75
- getTool(name: string) {
76
- return tools.get(name);
77
- },
78
- };
79
- }
80
-
81
- // ── Tests ──
82
-
83
- describe('agether_kya_status tool', () => {
84
- let api: ReturnType<typeof createMockApi>;
85
-
86
- beforeEach(() => {
87
- vi.clearAllMocks();
88
- api = createMockApi();
89
- registerPlugin(api);
90
- });
91
-
92
- it('returns DISABLED message when isKyaRequired = false', async () => {
93
- mockIsKyaRequired.mockResolvedValue(false);
94
-
95
- const tool = api.getTool('agether_kya_status')!;
96
- const result = await tool.execute();
97
-
98
- expect(result.content[0].text).toContain('DISABLED');
99
- expect(result.isError).toBeUndefined();
100
- });
101
-
102
- it('returns ENABLED message when isKyaRequired = true', async () => {
103
- mockIsKyaRequired.mockResolvedValue(true);
104
-
105
- const tool = api.getTool('agether_kya_status')!;
106
- const result = await tool.execute();
107
-
108
- expect(result.content[0].text).toContain('ENABLED');
109
- expect(result.isError).toBeUndefined();
110
- });
111
- });
112
-
113
- describe('agether_register kyaMessage', () => {
114
- let api: ReturnType<typeof createMockApi>;
115
-
116
- beforeEach(() => {
117
- vi.clearAllMocks();
118
- api = createMockApi();
119
- registerPlugin(api);
120
- });
121
-
122
- it('includes "gate disabled" kyaMessage when kyaRequired = false', async () => {
123
- mockRegister.mockResolvedValue({
124
- agentId: '99999',
125
- address: '0xWALLET',
126
- agentAccount: '0xACCOUNT',
127
- alreadyRegistered: false,
128
- kyaRequired: false,
129
- tx: '0xTXHASH',
130
- });
131
-
132
- const tool = api.getTool('agether_register')!;
133
- const result = await tool.execute('test-id', { name: 'TestAgent' });
134
- const data = JSON.parse(result.content[0].text);
135
-
136
- expect(data.kyaRequired).toBe(false);
137
- expect(data.kyaMessage).toContain('gate disabled');
138
- });
139
-
140
- it('includes "KYA required" kyaMessage when kyaRequired = true', async () => {
141
- mockRegister.mockResolvedValue({
142
- agentId: '99999',
143
- address: '0xWALLET',
144
- agentAccount: '0xACCOUNT',
145
- alreadyRegistered: false,
146
- kyaRequired: true,
147
- tx: '0xTXHASH',
148
- });
149
-
150
- const tool = api.getTool('agether_register')!;
151
- const result = await tool.execute('test-id', { name: 'TestAgent' });
152
- const data = JSON.parse(result.content[0].text);
153
-
154
- expect(data.kyaRequired).toBe(true);
155
- expect(data.kyaMessage).toContain('KYA required');
156
- });
157
- });
package/vitest.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- testTimeout: 10_000,
7
- },
8
- });