primecli 0.5.7__tar.gz → 0.5.9__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: primecli
3
- Version: 0.5.7
3
+ Version: 0.5.9
4
4
  Summary: Agent-friendly CLI tools for the DeltaPrime (Avalanche + Arbitrum) and DegenPrime (Base) lending and leverage protocols. Preview-by-default; no Etherscan key required.
5
5
  Author: Mnemosyne-quest contributors
6
6
  License: MIT
@@ -310,6 +310,8 @@ def resolve_private_key():
310
310
  Raises with a clear message if none resolve."""
311
311
  if _CLI_KEY:
312
312
  return _CLI_KEY.strip()
313
+ if _SELECTED_AGENT:
314
+ return _agent_key(_SELECTED_AGENT)
313
315
  for env_var in ("DEGENPRIME_PRIVATE_KEY", "DELTAPRIME_PRIVATE_KEY"):
314
316
  raw = os.environ.get(env_var)
315
317
  if raw:
@@ -2548,7 +2550,7 @@ def main():
2548
2550
  def _dispatch():
2549
2551
  args = sys.argv[1:] if len(sys.argv) > 1 else []
2550
2552
  # Global signing-key override: --key <0xhex>, stripped before command dispatch.
2551
- global _CLI_KEY
2553
+ global _SELECTED_AGENT, _CLI_KEY
2552
2554
  if "--key" in args:
2553
2555
  i = args.index("--key")
2554
2556
  if i + 1 >= len(args):
@@ -2556,6 +2558,13 @@ def _dispatch():
2556
2558
  return
2557
2559
  _CLI_KEY = args[i + 1]
2558
2560
  del args[i:i + 2]
2561
+ if "--as" in args:
2562
+ i = args.index("--as")
2563
+ if i + 1 >= len(args):
2564
+ print("--as requires an agent name. Example: --as parakletos")
2565
+ return
2566
+ _SELECTED_AGENT = args[i + 1]
2567
+ del args[i:i + 2]
2559
2568
  if not args or args[0] in ("-h", "--help"):
2560
2569
  print(__doc__)
2561
2570
  return
@@ -22,6 +22,7 @@ Strategy config (JSON):
22
22
 
23
23
  import json
24
24
  import os
25
+ import re
25
26
  import subprocess
26
27
  import sys
27
28
  import time
@@ -36,18 +37,22 @@ TIER_MAX = {"basic": 5, "premium": 10}
36
37
  # Health computation
37
38
  # ════════════════════════════════════════════════════════════════════
38
39
 
39
- def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
40
- """Compute health (0-100%) using the frontend formula from DeltaPrime docs.
40
+ def compute_health(
41
+ defi_data: dict,
42
+ max_mult: int = 10,
43
+ per_asset_powers: dict[str, int] | None = None,
44
+ ) -> dict:
45
+ """Compute health (0-100%) using the cross-margin formula from DeltaPrime docs.
41
46
 
42
- Uses the cross-margin health formula (all assets assumed same borrowing power):
43
- equity = total_supplied_usd - total_debt_usd
44
- health_pct = 100 * (1 - debt / (max_mult * equity))
45
- (0% = liquidation, 100% = no debt)
47
+ Cross-margin formula (when per_asset_powers is provided):
48
+ Pr_i = power_i / (power_i + 1) # borrowing power ratio per asset
49
+ Cw_i = supplied_usd_i x Pr_i # weighted collateral per asset
50
+ Bw_i = borrowed_usd_i x Pr_i # weighted borrows per asset
51
+ H = (SigmaCw + SigmaBw - B) / SigmaCw x 100
46
52
 
47
- Background:
48
- The frontend uses Pr = tier / (tier + 1) and computes:
49
- health_pct = (Pr * supplied - debt) / (Pr * equity) * 100
50
- which simplifies to: 100 * (1 - debt / (max_mult * equity)).
53
+ Falls back to the simplified uniform formula when per_asset_powers is None
54
+ (all assets assumed at max_mult borrowing power):
55
+ health_pct = 100 * (1 - debt / (max_mult * equity))
51
56
 
52
57
  DIFFERENT from the equity-based "health_pct" in defi --json / prime-summary
53
58
  (which uses max_debt = equity * (tier - 1)).
@@ -60,34 +65,6 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
60
65
  supplied = g.get("supplied", [])
61
66
  borrowed = g.get("borrowed", [])
62
67
  health_ratio = g.get("health_ratio", 0) or 0
63
- # Use precomputed health_pct from defi --json if available (primecli >= 0.5.4)
64
- precomputed = g.get("health_pct")
65
- if precomputed is not None:
66
- # Override health_pct with frontend formula (ignores precomputed value)
67
- supplied_usd = sum(s.get("usd", 0) or 0 for s in supplied)
68
- debt_usd = sum(b.get("usd", 0) or 0 for b in borrowed)
69
- equity = max(supplied_usd - debt_usd, 0.01)
70
- raw_usdc = sum(s.get("usd", 0) for s in supplied if s.get("symbol") == "USDC")
71
- symbols = [s.get("symbol", "") for s in supplied]
72
- has_gmx = sum(s.get("usd", 0) for s in supplied if "GM_" in s.get("symbol", "")) > 1.0
73
- has_lb = any(sym in ("LB_AVAX_USDC", "LB_WAVAX_USDC", "JOE") or "TRADERJOE" in sym.upper() for sym in symbols)
74
- has_aero = any("AERO" in sym.upper() or "CL_POSITION" in sym.upper() for sym in symbols)
75
- # Frontend formula: health_pct = 100 * (1 - debt / (max_mult * equity))
76
- fe_health = max(0.0, 100.0 * (1.0 - round(debt_usd, 2) / (max_mult * equity)))
77
- fe_max_debt = round(max_mult * equity, 2)
78
- return {
79
- "health_pct": round(fe_health, 1),
80
- "health_ratio": round(health_ratio, 4),
81
- "supplied_usd": round(supplied_usd, 2),
82
- "debt_usd": round(debt_usd, 2),
83
- "equity": round(equity, 2),
84
- "max_debt": round(max(0, max_mult * equity), 2),
85
- "raw_usdc": round(raw_usdc, 2),
86
- "has_gmx": has_gmx,
87
- "has_lb": has_lb,
88
- "has_aero": has_aero,
89
- "action": "computed from defi --json health_pct",
90
- }
91
68
  else:
92
69
  supplied = defi_data.get("supplied", [])
93
70
  borrowed = defi_data.get("borrowed", [])
@@ -107,12 +84,48 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
107
84
  "error": "equity near zero",
108
85
  }
109
86
 
110
- max_debt = round(max_mult * equity, 2) # frontend formula: max debt before liquidation
87
+ # ── Cross-margin formula (per-asset borrowing powers) ─────────────
88
+ if per_asset_powers is not None:
89
+ powers: dict[str, int] = per_asset_powers
90
+ sum_cw = 0.0 # SigmaCw
91
+ sum_bw = 0.0 # SigmaBw
92
+ total_debt = 0.0
93
+
94
+ for s in supplied:
95
+ sym = s.get("symbol", "")
96
+ usd_val = s.get("usd", 0) or 0
97
+ p = powers.get(sym, max_mult)
98
+ pr = p / (p + 1)
99
+ sum_cw += usd_val * pr
100
+
101
+ for b in borrowed:
102
+ sym = b.get("symbol", "")
103
+ usd_val = b.get("usd", 0) or 0
104
+ p = powers.get(sym, max_mult)
105
+ pr = p / (p + 1)
106
+ sum_bw += usd_val * pr
107
+ total_debt += usd_val
108
+
109
+ if sum_cw > 0.01:
110
+ # H = (SigmaCw + SigmaBw - B) / SigmaCw * 100
111
+ health_pct = max(0.0, (sum_cw + sum_bw - total_debt) / sum_cw * 100.0)
112
+ else:
113
+ health_pct = 0.0
114
+
115
+ max_debt = round(max_mult * equity, 2)
116
+
117
+ # ── Simplified formula fallback (uniform borrowing power) ─────────
118
+ else:
119
+ max_debt = round(max_mult * equity, 2)
120
+
121
+ if max_debt > 0.01 and debt_usd >= 0:
122
+ health_pct = max(0.0, 100.0 * (1.0 - round(debt_usd, 2) / max_debt))
123
+ else:
124
+ health_pct = 100.0
111
125
 
112
- # Raw USDC in account
126
+ # Common features regardless of formula variant
113
127
  raw_usdc = sum(s.get("usd", 0) for s in supplied if s.get("symbol") == "USDC")
114
128
 
115
- # Position type detection
116
129
  symbols = [s.get("symbol", "") for s in supplied]
117
130
  has_gmx = sum(s.get("usd", 0) for s in supplied if "GM_" in s.get("symbol", "")) > 1.0
118
131
  has_lb = any(
@@ -122,11 +135,6 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
122
135
  )
123
136
  has_aero = any("AERO" in sym.upper() or "CL_POSITION" in sym.upper() for sym in symbols)
124
137
 
125
- if max_debt > 0.01 and debt_usd >= 0:
126
- health_pct = max(0.0, 100.0 * (1.0 - round(debt_usd, 2) / max_debt))
127
- else:
128
- health_pct = 100.0
129
-
130
138
  # Center target (50% health): target_debt = max_debt * 0.5
131
139
  delta_debt = (max_debt * 0.5) - debt_usd
132
140
 
@@ -560,39 +568,52 @@ def run_tick(
560
568
  deployed_fail += 1
561
569
 
562
570
  elif pos_type == "lb":
563
- # LB deposits: use the pool from strategy hint, default to AVAX/USDC
564
- lb_pool = strategy.get("lb_pool", "AVAX/USDC")
565
- try:
566
- r = subprocess.run(
567
- [sys.executable, tool_path, "lb-deposit",
568
- "--pool", lb_pool, "--amount", f"{split_amt:.2f}",
569
- "--execute"],
570
- capture_output=True, text=True, timeout=120,
571
- )
572
- if r.returncode == 0:
573
- deployed_ok += 1
574
- else:
575
- result["warning"] = f"lb deposit failed: {r.stderr[:200]}"
576
- deployed_fail += 1
577
- except Exception as e:
578
- result["error"] = f"lb deposit error: {e}"
571
+ # Detect LB pair from defi data (look for "TraderJoe V2 LB" group)
572
+ lb_pairs = []
573
+ for g in defi_data.get("groups", []):
574
+ if g.get("type") == "TraderJoe V2 LB":
575
+ for item in g.get("items", []):
576
+ label = item.get("label", "")
577
+ m = re.match(r'\[([^\]]+)\]', label)
578
+ if m:
579
+ lb_pairs.append(m.group(1))
580
+
581
+ # Skip if tool doesn't support lb-add (degenprime)
582
+ tool_bn = os.path.basename(tool_path) if tool_path else ""
583
+ if "degenprime" in tool_bn:
584
+ result["action"] = f"lb-add not available on degenprime — leaving ${split_amt:.2f} as USDC"
579
585
  deployed_fail += 1
586
+ elif not lb_pairs:
587
+ result["warning"] = f"has_lb=True but no LB pair found in defi data — leaving ${split_amt:.2f} as USDC"
588
+ deployed_fail += 1
589
+ else:
590
+ pair_key = lb_pairs[0]
591
+ try:
592
+ r = subprocess.run(
593
+ [sys.executable, tool_path, "lb-add",
594
+ "--pair", pair_key,
595
+ "--amount-x", "0",
596
+ "--amount-y", f"{split_amt:.2f}",
597
+ "--shape", "spot",
598
+ "--range", "15",
599
+ "--execute"],
600
+ capture_output=True, text=True, timeout=120,
601
+ )
602
+ if r.returncode == 0:
603
+ deployed_ok += 1
604
+ else:
605
+ result["warning"] = f"lb-add failed: {r.stderr[:200]}"
606
+ deployed_fail += 1
607
+ except Exception as e:
608
+ result["error"] = f"lb-add error: {e}"
609
+ deployed_fail += 1
580
610
 
581
611
  elif pos_type == "aero":
582
- try:
583
- r = subprocess.run(
584
- [sys.executable, tool_path, "aerodrome-deposit",
585
- "--amount", f"{split_amt:.2f}", "--execute"],
586
- capture_output=True, text=True, timeout=120,
587
- )
588
- if r.returncode == 0:
589
- deployed_ok += 1
590
- else:
591
- result["warning"] = f"aerodrome deposit failed: {r.stderr[:200]}"
592
- deployed_fail += 1
593
- except Exception as e:
594
- result["error"] = f"aerodrome deposit error: {e}"
595
- deployed_fail += 1
612
+ # Aerodrome CL: degenprime has read-only aerodrome-positions,
613
+ # but no deposit/withdraw commands yet (write paths deferred to
614
+ # v2 — on-chain signatures vary by Aerodrome version).
615
+ # Use `degenprime aerodrome-positions` to list your NFT tokenIds.
616
+ result["action"] = f"aero deposit not yet supported (read-only via aerodrome-positions, writes deferred to v2) — leaving ${split_amt:.2f} as USDC"
596
617
 
597
618
  if deployed_ok > 0:
598
619
  cooldown_file.write_text(str(int(time.time())))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: primecli
3
- Version: 0.5.7
3
+ Version: 0.5.9
4
4
  Summary: Agent-friendly CLI tools for the DeltaPrime (Avalanche + Arbitrum) and DegenPrime (Base) lending and leverage protocols. Preview-by-default; no Etherscan key required.
5
5
  Author: Mnemosyne-quest contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "primecli"
7
- version = "0.5.7"
7
+ version = "0.5.9"
8
8
  description = "Agent-friendly CLI tools for the DeltaPrime (Avalanche + Arbitrum) and DegenPrime (Base) lending and leverage protocols. Preview-by-default; no Etherscan key required."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes