@agether/agether 2.6.0 → 2.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/agether",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "OpenClaw plugin for Agether — onchain credit for AI agents on Ethereum & Base",
5
5
  "main": "src/index.ts",
6
6
  "openclaw": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agether
3
- description: On-chain credit protocol for AI agents on Base. Morpho credit & lending, x402 payments, ERC-8004 identity.
3
+ description: On-chain credit protocol for AI agents. Morpho credit & lending, x402 payments, ERC-8004 identity.
4
4
  ---
5
5
 
6
6
  # Agether — On-Chain Credit for AI Agents
@@ -26,11 +26,11 @@ 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_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.
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 `chain` is `"?"`, follow the MANDATORY BOOT SEQUENCE above — set chain FIRST, then agent ID.
30
30
  5. **Be proactive** — if the user asks to call a paid API, do the full flow without asking.
31
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
- 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.
33
+ 8. **When user says "register" → ALWAYS call `agether_register`.** But ONLY after chain is set (see MANDATORY BOOT SEQUENCE). 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.
@@ -43,39 +43,63 @@ If you don't paste it, the user sees NOTHING. An empty colon ":" with no data af
43
43
 
44
44
  ---
45
45
 
46
- ## 🆔 AGENT ID RESOLUTION (CRITICAL)
46
+ ## 🚫🚫🚫 MANDATORY BOOT SEQUENCE — READ THIS FIRST 🚫🚫🚫
47
47
 
48
- If `agether_health` or `agether_balance` returns `agentId: "?"`, ALL Morpho tools will fail. You MUST resolve this first:
48
+ There is a **strict ordering requirement**. Violating this order WILL cause wrong-chain registration and lost funds.
49
+
50
+ ### STEP 1 → CHAIN (must be first, ALWAYS, no exceptions)
51
+
52
+ **You MUST set the chain BEFORE you do ANYTHING else — before `agether_set_agent`, before `agether_register`, before ANY on-chain tool call.**
53
+
54
+ An `agentId` is **chain-specific**. Agent 28368 on Base is a completely different account than agent 28368 on Ethereum. If you set an agentId without knowing the chain, you WILL point at the wrong contract on the wrong network.
55
+
56
+ **NEVER call `agether_set_agent` before the chain is confirmed. NEVER.**
49
57
 
50
58
  ```
51
- 1. Check your memory for a previously registered agentId
52
- 2. IF you find an agentId in memory:
53
- Ask user: "I remember agent ID <X> from before. Should I use it, or register a new one?"
54
- IF user says yes call `agether_set_agent(agentId: "<X>")`
55
- → IF user says no → call `agether_register(name: "<ask name>")`
56
- 3. IF no agentId in memory:
57
- → call `agether_register(name: "<ask name>")`
58
- 4. After either path → call `agether_health` to confirm everything is working
59
+ IF agether_health returns chain: "?"
60
+ Ask user: "Which chain Ethereum or Base?"
61
+ Call agether_set_chain(chain: "<ethereum or base>")
62
+ Call agether_health again to confirm chain is set (not "?")
59
63
  ```
60
64
 
61
- Both `agether_set_agent` and `agether_register` save the agentId to config permanently (survives restarts).
65
+ `agether_set_chain` accepts: `ethereum`, `eth`, `1`, `base`, `8453`.
62
66
 
63
- ---
67
+ ### STEP 2 → AGENT ID (ONLY after chain is confirmed, not "?")
64
68
 
65
- ## CHAIN RESOLUTION (CRITICAL)
69
+ **NEVER call `agether_set_agent` or `agether_register` until the chain is confirmed (chain ≠ "?").**
66
70
 
67
- If `agether_health` returns `chain: "?"`, the chain is NOT configured. You MUST resolve this before proceeding with any on-chain action:
71
+ Even if you have an agentId cached in memory or config — that agentId belongs to a **specific chain**. Do NOT blindly reuse it. A config value like `"agentId": "28368"` might be from the old deployment or a different chain.
68
72
 
69
73
  ```
70
- 1. Ask user: "Which blockchain would you like to use? **Ethereum** or **Base**?"
71
- 2. User chooses a chain
72
- 3. Call `agether_set_chain(chain: "<ethereum or base>")`
73
- 4. After set call `agether_health` to confirm everything is working on that chain
74
+ IF chain is confirmed AND agentId is "?"
75
+ 1. IF you remember an agentId AND know which chain it was for:
76
+ Ask: "I remember agent <X> on <CHAIN>. Is this still correct?"
77
+ IF yes AND chain matches agether_set_agent(agentId: "<X>")
78
+ → IF no → agether_register(name: "<ask>")
79
+ 2. IF no memory of agentId:
80
+ → agether_register(name: "<ask>")
81
+ 3. Call agether_health to confirm
74
82
  ```
75
83
 
76
- `agether_set_chain` saves the chain to config permanently (survives restarts). Accepts: `ethereum`, `eth`, `1`, `base`, `8453`.
84
+ ### FORBIDDEN (will cause bugs)
77
85
 
78
- **IMPORTANT:** Resolve chain BEFORE resolving agentIdthe agent registration happens on a specific chain.
86
+ - `agether_set_agent` then `agether_set_chain`**WRONG ORDER, agent points at wrong chain**
87
+ - ❌ `agether_register` before chain is set — **WILL FAIL**
88
+ - ❌ Reusing a cached agentId without confirming which chain it belongs to — **WRONG ACCOUNT**
89
+ - ❌ Seeing `"agentId": "28368"` in config and assuming it's valid — it might be stale or from a different chain
90
+ - ❌ Calling `agether_set_agent` just because config has an agentId — **ASK THE USER FIRST**
91
+
92
+ ### ✅ CORRECT SEQUENCE (always follow this)
93
+
94
+ ```
95
+ 1. agether_health → check chain and agentId status
96
+ 2. agether_set_chain → if chain is "?", ask user and set it FIRST
97
+ 3. agether_health → confirm chain is now set
98
+ 4. agether_set_agent OR agether_register → only NOW, after chain is confirmed
99
+ 5. agether_health → final confirmation that both chain + agentId are set
100
+ ```
101
+
102
+ Both `agether_set_agent` and `agether_register` save the agentId to config permanently (survives restarts).
79
103
 
80
104
  ---
81
105
 
@@ -176,11 +200,11 @@ When `autoYield` and/or `autoDraw` are enabled, `x402_pay` automatically sources
176
200
  ```
177
201
  1. agether_health ← ONE call for full context
178
202
  2. Check the "chain" field:
179
- → If chain is "?" → go to CHAIN RESOLUTION (must resolve before anything else)
203
+ → If chain is "?" → STOP. Set chain first (MANDATORY BOOT SEQUENCE). Do NOT proceed until chain is confirmed.
180
204
  3. Check the "alerts" array:
181
205
  → If any 🔴 alerts → WARN user immediately (liquidation risk!)
182
206
  → If any 🟡 alerts → mention the risk casually
183
- → If "agentId: ?" → go to AGENT ID RESOLUTION
207
+ → If "agentId: ?" → set agent ID (but only AFTER chain is confirmed — see MANDATORY BOOT SEQUENCE)
184
208
  4. Now you have full context to handle any request
185
209
  ```
186
210
 
package/src/index.ts CHANGED
@@ -175,6 +175,20 @@ function getConfig(api: any): PluginConfig {
175
175
  let cachedAgentId: string | undefined;
176
176
  let activeChainId: ChainId = ChainId.Ethereum;
177
177
 
178
+ /**
179
+ * Hard guardrail: refuse to proceed if chain was never explicitly configured.
180
+ * Prevents silent default-to-Ethereum when user hasn't chosen a chain.
181
+ * Throws an error that the LLM sees, forcing it to call agether_set_chain first.
182
+ */
183
+ function requireChain(cfg: PluginConfig): void {
184
+ if (!cfg.chainConfigured) {
185
+ throw new Error(
186
+ "Chain not configured. Ask the user which chain to use (Ethereum or Base), " +
187
+ "then call agether_set_chain before proceeding."
188
+ );
189
+ }
190
+ }
191
+
178
192
  function createClient(cfg: PluginConfig): MorphoClient {
179
193
  const agentId = cachedAgentId || cfg.agentId;
180
194
  return new MorphoClient({
@@ -258,7 +272,8 @@ export default function register(api: any) {
258
272
  api.registerTool({
259
273
  name: "agether_register",
260
274
  description:
261
- "Register a new ERC-8004 agent identity and create a Safe smart account (via Safe7579). Returns the new agentId.",
275
+ "Register a new ERC-8004 agent identity and create a Safe smart account (via Safe7579). " +
276
+ "REQUIRES chain to be configured first — call agether_set_chain before this tool. Returns the new agentId.",
262
277
  parameters: {
263
278
  type: "object",
264
279
  properties: {
@@ -269,6 +284,7 @@ export default function register(api: any) {
269
284
  async execute(_id: string, params: { name: string }) {
270
285
  try {
271
286
  const cfg = getConfig(api);
287
+ requireChain(cfg);
272
288
  const client = createClient(cfg);
273
289
  const result = await client.register(params.name);
274
290
  const persistStatus = persistAgentId(result.agentId);
@@ -417,6 +433,7 @@ export default function register(api: any) {
417
433
  async execute(_id: string, params: { registryAddress: string }) {
418
434
  try {
419
435
  const cfg = getConfig(api);
436
+ requireChain(cfg);
420
437
  const client = createClient(cfg);
421
438
 
422
439
  // Build the calldata for ERC8004ValidationModule.setValidationRegistry — the owner (Timelock) must execute it
@@ -526,6 +543,7 @@ export default function register(api: any) {
526
543
  async execute(_id: string, params: { amount: string; token: string }) {
527
544
  try {
528
545
  const cfg = getConfig(api);
546
+ requireChain(cfg);
529
547
  const client = createClient(cfg);
530
548
  const result = await client.supplyCollateral(params.token, params.amount);
531
549
  return ok(JSON.stringify({
@@ -558,6 +576,7 @@ export default function register(api: any) {
558
576
  async execute(_id: string, params: { collateralAmount: string; token: string; borrowAmount: string }) {
559
577
  try {
560
578
  const cfg = getConfig(api);
579
+ requireChain(cfg);
561
580
  const client = createClient(cfg);
562
581
  const result = await client.depositAndBorrow(
563
582
  params.token,
@@ -597,6 +616,7 @@ export default function register(api: any) {
597
616
  try {
598
617
  if (!params.agentId && !params.agentAddress) return fail("Provide either agentId or agentAddress");
599
618
  const cfg = getConfig(api);
619
+ requireChain(cfg);
600
620
  const client = createClient(cfg);
601
621
 
602
622
  const target = params.agentId
@@ -634,6 +654,7 @@ export default function register(api: any) {
634
654
  async execute(_id: string, params: { amount: string; token?: string }) {
635
655
  try {
636
656
  const cfg = getConfig(api);
657
+ requireChain(cfg);
637
658
  const client = createClient(cfg);
638
659
  const result = await client.borrow(params.amount, params.token);
639
660
  return ok(JSON.stringify({
@@ -666,6 +687,7 @@ export default function register(api: any) {
666
687
  async execute(_id: string, params: { amount: string; token?: string }) {
667
688
  try {
668
689
  const cfg = getConfig(api);
690
+ requireChain(cfg);
669
691
  const client = createClient(cfg);
670
692
  const result = await client.repay(params.amount, params.token);
671
693
  return ok(JSON.stringify({
@@ -699,6 +721,7 @@ export default function register(api: any) {
699
721
  async execute(_id: string, params: { amount: string; token: string; toEoa?: boolean }) {
700
722
  try {
701
723
  const cfg = getConfig(api);
724
+ requireChain(cfg);
702
725
  const client = createClient(cfg);
703
726
  // Default: keep in AgentAccount. toEoa=true → send to EOA.
704
727
  const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
@@ -739,6 +762,7 @@ export default function register(api: any) {
739
762
  async execute(_id: string, params: { amount: string; market?: string }) {
740
763
  try {
741
764
  const cfg = getConfig(api);
765
+ requireChain(cfg);
742
766
  const client = createClient(cfg);
743
767
  const result = await client.supplyAsset(params.amount, params.market);
744
768
  return ok(JSON.stringify({
@@ -831,6 +855,7 @@ export default function register(api: any) {
831
855
  async execute(_id: string, params: { amount: string; market?: string; toEoa?: boolean }) {
832
856
  try {
833
857
  const cfg = getConfig(api);
858
+ requireChain(cfg);
834
859
  const client = createClient(cfg);
835
860
  // Default: keep in AgentAccount. toEoa=true → send to EOA.
836
861
  const receiver = params.toEoa ? await client.getSignerAddress() : await client.getAccountAddress();
@@ -903,6 +928,7 @@ export default function register(api: any) {
903
928
  async execute(_id: string, params: { refresh?: boolean }) {
904
929
  try {
905
930
  const cfg = getConfig(api);
931
+ requireChain(cfg);
906
932
  const client = createClient(cfg);
907
933
  const agentId = client.getAgentId();
908
934
 
@@ -952,6 +978,7 @@ export default function register(api: any) {
952
978
  async execute(_id: string, params: { amount: string }) {
953
979
  try {
954
980
  const cfg = getConfig(api);
981
+ requireChain(cfg);
955
982
  const client = createClient(cfg);
956
983
  const result = await client.fundAccount(params.amount);
957
984
  return ok(JSON.stringify({
@@ -984,6 +1011,7 @@ export default function register(api: any) {
984
1011
  async execute(_id: string, params: { token: string; amount: string }) {
985
1012
  try {
986
1013
  const cfg = getConfig(api);
1014
+ requireChain(cfg);
987
1015
  const client = createClient(cfg);
988
1016
  const result = await client.withdrawToken(params.token, params.amount);
989
1017
  return ok(JSON.stringify({
@@ -1015,6 +1043,7 @@ export default function register(api: any) {
1015
1043
  async execute(_id: string, params: { amount: string }) {
1016
1044
  try {
1017
1045
  const cfg = getConfig(api);
1046
+ requireChain(cfg);
1018
1047
  const client = createClient(cfg);
1019
1048
  const result = await client.withdrawEth(params.amount);
1020
1049
  return ok(JSON.stringify({
@@ -1049,6 +1078,7 @@ export default function register(api: any) {
1049
1078
  async execute(_id: string, params: { url: string; method?: string; body?: string }) {
1050
1079
  try {
1051
1080
  const cfg = getConfig(api);
1081
+ requireChain(cfg);
1052
1082
  const agetherCfg = api.config?.plugins?.entries?.agether?.config || {};
1053
1083
  const client = createClient(cfg);
1054
1084