primecli 0.5.3__tar.gz → 0.5.4__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.
Files changed (22) hide show
  1. {primecli-0.5.3 → primecli-0.5.4}/PKG-INFO +1 -1
  2. {primecli-0.5.3 → primecli-0.5.4}/primecli/arbprime.py +25 -25
  3. {primecli-0.5.3 → primecli-0.5.4}/primecli/degenprime.py +70 -5
  4. {primecli-0.5.3 → primecli-0.5.4}/primecli/deltaprime.py +25 -25
  5. {primecli-0.5.3 → primecli-0.5.4}/primecli/health_monitor.py +21 -21
  6. {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/PKG-INFO +1 -1
  7. {primecli-0.5.3 → primecli-0.5.4}/pyproject.toml +1 -1
  8. {primecli-0.5.3 → primecli-0.5.4}/LICENSE +0 -0
  9. {primecli-0.5.3 → primecli-0.5.4}/README.md +0 -0
  10. {primecli-0.5.3 → primecli-0.5.4}/primecli/__init__.py +0 -0
  11. {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/SOURCES.txt +0 -0
  12. {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/dependency_links.txt +0 -0
  13. {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/entry_points.txt +0 -0
  14. {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/requires.txt +0 -0
  15. {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/top_level.txt +0 -0
  16. {primecli-0.5.3 → primecli-0.5.4}/setup.cfg +0 -0
  17. {primecli-0.5.3 → primecli-0.5.4}/tests/test_cross_file_identity.py +0 -0
  18. {primecli-0.5.3 → primecli-0.5.4}/tests/test_gas_pricing.py +0 -0
  19. {primecli-0.5.3 → primecli-0.5.4}/tests/test_health_monitor.py +0 -0
  20. {primecli-0.5.3 → primecli-0.5.4}/tests/test_paraswap_validator.py +0 -0
  21. {primecli-0.5.3 → primecli-0.5.4}/tests/test_redstone_encoding.py +0 -0
  22. {primecli-0.5.3 → primecli-0.5.4}/tests/test_to_wei_units.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: primecli
3
- Version: 0.5.3
3
+ Version: 0.5.4
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
@@ -59,7 +59,7 @@ back to balances-only if the gateway is unreachable).
59
59
 
60
60
  NOTE: prime-summary shows TWO health metrics — don't confuse them:
61
61
  - "Health ratio (chain)": on-chain getHealthRatio. 1.0 = liquidation, >1.0 = solvent.
62
- - "Health (Bruno 0-100%)": equity-based, frontend-style. 0% = liquidation, 50% = half
62
+ - "Health (0-100%)": equity-based, frontend-style. 0% = liquidation, 50% = half
63
63
  borrowing power used, 100% = no debt.
64
64
  Formula for the latter: equity=supplied-debt, max_debt=equity*(tier-1),
65
65
  pct=(max_debt-debt)/max_debt*100. tier=5 (BASIC) or 10 (PREMIUM).
@@ -234,7 +234,7 @@ EXPLORER = "https://arbiscan.io" # display/links only — never used for ABI fe
234
234
  CHAIN_ID = 42161
235
235
  ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
236
236
  # ── Agent / wallet selection ─────────────────────────────────────────────────
237
- # Agent-agnostic: any agent (Parakletos, Paraklaudios, or another of Bruno's) runs
237
+ # Agent-agnostic: any agent (Parakletos, Paraklaudios, or another authorized agent) runs
238
238
  # this same tool with its OWN wallet. The Prime Account is derived on-chain from the
239
239
  # wallet owner (getLoanForOwner), so each agent automatically operates on its own
240
240
  # Prime Account — no per-agent addresses are hardcoded. The same EVM key works on
@@ -317,8 +317,8 @@ REDSTONE_GATEWAYS = [
317
317
  ]
318
318
 
319
319
  # Active pool proxies — LIVE Arbitrum deployment, on-chain verified 2026-06-03.
320
- # getAllPoolAssets live = [USDC, DAI, BTC, ARB, ETH]; the DAI pool is DROPPED per
321
- # Bruno, leaving 4. The native-wrapped pool is `eth` (underlying WETH, account symbol
320
+ # getAllPoolAssets live = [USDC, DAI, BTC, ARB, ETH]; the DAI pool is DROPPED,
321
+ # leaving 4. The native-wrapped pool is `eth` (underlying WETH, account symbol
322
322
  # "ETH"): its native deposit path uses depositNativeToken() (wraps ETH -> WETH).
323
323
  POOLS = {
324
324
  "eth": {
@@ -1968,23 +1968,23 @@ def gather_lending(w3, account):
1968
1968
  r["usd"] = None
1969
1969
  return out
1970
1970
 
1971
- def _compute_bruno_health(data: dict, tier_code: int = 0) -> dict:
1972
- """Compute Bruno's 0-100% health from gather_lending data + tier.
1971
+ def _compute_health_pct(data: dict, tier_code: int = 0) -> dict:
1972
+ """Compute equity-based health (0-100%) from gather_lending data + tier.
1973
1973
 
1974
1974
  DeltaPrime has *two* health metrics that agents must not confuse:
1975
1975
 
1976
1976
  1. health_ratio (on-chain, getHealthRatio): 1.0 = liquidation, >1.0 = solvent.
1977
1977
  This is the raw weighted-collateral / debt ratio from the SolvencyFacet.
1978
1978
 
1979
- 2. bruno_pct (equity-based, 0-100%): the scale used in the DeltaPrime frontend
1979
+ 2. health_pct (equity-based, 0-100%): the scale used in the DeltaPrime frontend
1980
1980
  and the account-health-monitor cron. 0% = liquidation, 100% = no debt.
1981
1981
  Formula:
1982
1982
  equity = supplied_usd - debt_usd
1983
1983
  max_mult = 10 if PREMIUM tier else 5 if BASIC
1984
1984
  max_debt = equity * (max_mult - 1)
1985
- bruno_pct = (max_debt - debt_usd) / max_debt * 100
1985
+ health_pct = (max_debt - debt_usd) / max_debt * 100
1986
1986
 
1987
- Returns dict with keys: bruno_pct, supplied_usd, debt_usd, equity, max_debt,
1987
+ Returns dict with keys: health_pct, supplied_usd, debt_usd, equity, max_debt,
1988
1988
  tier_label, or error.
1989
1989
  """
1990
1990
  supplied_usd = sum(r.get("usd", 0) or 0 for r in data.get("supplied", []))
@@ -1995,18 +1995,18 @@ def _compute_bruno_health(data: dict, tier_code: int = 0) -> dict:
1995
1995
  max_mult = {0: 5, 1: 10}.get(tier_code, 5)
1996
1996
 
1997
1997
  if equity <= 0.01:
1998
- return {"bruno_pct": 0.0, "supplied_usd": round(supplied_usd, 2),
1998
+ return {"health_pct": 0.0, "supplied_usd": round(supplied_usd, 2),
1999
1999
  "debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
2000
2000
  "max_debt": 0.0, "tier": tier_label, "error": "equity near zero"}
2001
2001
 
2002
2002
  max_debt = equity * (max_mult - 1)
2003
2003
  if max_debt > 0 and debt_usd >= 0:
2004
- bruno_pct = (max_debt - min(debt_usd, max_debt)) / max_debt * 100
2005
- bruno_pct = max(0.0, min(100.0, bruno_pct))
2004
+ health_pct = (max_debt - min(debt_usd, max_debt)) / max_debt * 100
2005
+ health_pct = max(0.0, min(100.0, health_pct))
2006
2006
  else:
2007
- bruno_pct = 100.0
2007
+ health_pct = 100.0
2008
2008
 
2009
- return {"bruno_pct": round(bruno_pct, 1), "supplied_usd": round(supplied_usd, 2),
2009
+ return {"health_pct": round(health_pct, 1), "supplied_usd": round(supplied_usd, 2),
2010
2010
  "debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
2011
2011
  "max_debt": round(max_debt, 2), "tier": tier_label}
2012
2012
 
@@ -2055,22 +2055,22 @@ def cmd_prime_summary():
2055
2055
  # astronomically large there); render that as ">1000" rather than a junk number.
2056
2056
  ratio_str = ">1000.00 (negligible debt)" if ratio is None else f"{ratio:.4f}"
2057
2057
  print(f" Health ratio (chain): {ratio_str} (>1.0 = solvent, 1.0 = liquidation)")
2058
- # ─── Bruno's 0-100% health (equity-based, uses tier multiplier) ───
2059
- # Different from health_ratio! See _compute_bruno_health docstring.
2058
+ # ─── Equity-based health (0-100%) ───
2059
+ # Different from health_ratio! See _compute_health_pct docstring.
2060
2060
  # Get tier from the Prime Account (oracle-free view)
2061
2061
  try:
2062
2062
  tier_info = gather_prime_tier(w3, acct, account)
2063
2063
  tier_code = tier_info.get("tier_code", 0)
2064
2064
  except Exception:
2065
2065
  tier_code = 0
2066
- bh = _compute_bruno_health(data, tier_code)
2067
- if "error" not in bh:
2068
- print(f" Health (Bruno 0-100%): {bh['bruno_pct']:.1f}%")
2069
- print(f" (supplied=${bh['supplied_usd']:.2f}, debt=${bh['debt_usd']:.2f},"
2070
- f" equity=${bh['equity']:.2f}, max_debt=${bh['max_debt']:.2f}, {bh['tier']})")
2066
+ hp = _compute_health_pct(data, tier_code)
2067
+ if "error" not in hp:
2068
+ print(f" Health (0-100%): {hp['health_pct']:.1f}%")
2069
+ print(f" (supplied=${hp['supplied_usd']:.2f}, debt=${hp['debt_usd']:.2f},"
2070
+ f" equity=${hp['equity']:.2f}, max_debt=${hp['max_debt']:.2f}, {hp['tier']})")
2071
2071
  print(f" 0%=liquidation 50%=half borrowing power used 100%=no debt")
2072
2072
  else:
2073
- print(f" Health (Bruno 0-100%): N/A ({bh['error']})")
2073
+ print(f" Health (0-100%): N/A ({hp['error']})")
2074
2074
  print(f" Solvent: {'yes' if data['solvent'] else 'NO — liquidatable'}")
2075
2075
  else:
2076
2076
  print(f" Health/solvency: RedStone fetch/call failed ({data.get('solvency_error', 'error')}); "
@@ -5071,12 +5071,12 @@ def gather_defi() -> dict:
5071
5071
  result["total_usd"] = lending["total_value_usd"]
5072
5072
  result["health_ratio"] = lending["health_ratio"]
5073
5073
  result["solvent"] = lending["solvent"]
5074
- # Compute Bruno's 0-100% health from lending data + tier
5075
- result["bruno_pct"] = _compute_bruno_health(lending, tier.get("tier_code", 0)).get("bruno_pct")
5074
+ # Compute equity-based health (0-100%) from lending data + tier
5075
+ result["health_pct"] = _compute_health_pct(lending, tier.get("tier_code", 0)).get("health_pct")
5076
5076
  if lending["supplied"] or lending["borrowed"]:
5077
5077
  result["groups"].append({
5078
5078
  "type": "Lending / Leverage", "health_ratio": lending["health_ratio"],
5079
- "bruno_pct": result["bruno_pct"],
5079
+ "health_pct": result["health_pct"],
5080
5080
  "supplied": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
5081
5081
  for r in lending["supplied"]],
5082
5082
  "borrowed": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
@@ -112,6 +112,7 @@ health_monitor = _hm
112
112
  BASE_RPC = os.environ.get("DEGENPRIME_RPC", "https://base.publicnode.com")
113
113
  EXPLORER = "https://basescan.org"
114
114
  CHAIN_ID = 8453
115
+ DEGEN_MAX_MULT = 5 # Fixed max leverage multiplier (no tier system on DegenPrime)
115
116
  ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
116
117
 
117
118
  # ── Signing key resolution ──────────────────────────────────────────────────
@@ -570,6 +571,41 @@ def _asset_meta(w3, symbol: str):
570
571
  # gateway once instead of per-feed-symbol. Cleared implicitly when the process exits.
571
572
  _redstone_gateway_cache = None
572
573
 
574
+ def _compute_health_pct(supplied: list, borrowed: list, max_mult: int = None) -> dict:
575
+ """Compute equity-based health (0-100%) from per-asset supplied/borrowed data.
576
+
577
+ 0% = liquidation, 50% = half borrowing power used, 100% = no debt.
578
+ Formula:
579
+ equity = supplied_usd - debt_usd
580
+ max_mult = 5 (fixed for DegenPrime; no tier system)
581
+ max_debt = equity * (max_mult - 1)
582
+ health_pct = (max_debt - debt_usd) / max_debt * 100
583
+
584
+ Returns dict with keys: health_pct, supplied_usd, debt_usd, equity, max_debt, or error.
585
+ """
586
+ if max_mult is None:
587
+ max_mult = DEGEN_MAX_MULT
588
+ supplied_usd = sum(r.get("usd", 0) or 0 for r in supplied)
589
+ debt_usd = sum(r.get("usd", 0) or 0 for r in borrowed)
590
+ equity = supplied_usd - debt_usd
591
+
592
+ if equity <= 0.01:
593
+ return {"health_pct": 0.0, "supplied_usd": round(supplied_usd, 2),
594
+ "debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
595
+ "max_debt": 0.0, "error": "equity near zero"}
596
+
597
+ max_debt = equity * (max_mult - 1)
598
+ if max_debt > 0 and debt_usd >= 0:
599
+ health_pct = (max_debt - min(debt_usd, max_debt)) / max_debt * 100
600
+ health_pct = max(0.0, min(100.0, health_pct))
601
+ else:
602
+ health_pct = 100.0
603
+
604
+ return {"health_pct": round(health_pct, 1), "supplied_usd": round(supplied_usd, 2),
605
+ "debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
606
+ "max_debt": round(max_debt, 2), "tier": "FIXED_5X"}
607
+
608
+
573
609
  def _redstone_fetch_packages(use_cache: bool = True) -> dict:
574
610
  """Fetch the latest signed price packages from the RedStone gateway. Returns the
575
611
  per-feed map: {feedSymbol: [package, ...]} with one package per signer. The per-run
@@ -1458,11 +1494,19 @@ def gather_defi() -> dict:
1458
1494
  row["usd"] = round(amt * usd, 2)
1459
1495
  return row
1460
1496
 
1497
+ _rows_supplied = [_row(r) for r in supplied]
1498
+ _rows_borrowed = [_row(r) for r in borrowed]
1499
+
1500
+ # Compute equity-based health (0-100%)
1501
+ _hp = _compute_health_pct(_rows_supplied, _rows_borrowed)
1502
+ result["health_pct"] = _hp.get("health_pct")
1503
+
1461
1504
  if supplied or borrowed:
1462
1505
  result["groups"].append({
1463
1506
  "type": "Lending / Leverage", "health_ratio": solvency["ratio"],
1464
- "supplied": [_row(r) for r in supplied],
1465
- "borrowed": [_row(r) for r in borrowed],
1507
+ "health_pct": result["health_pct"],
1508
+ "supplied": _rows_supplied,
1509
+ "borrowed": _rows_borrowed,
1466
1510
  })
1467
1511
 
1468
1512
  # Savings: the EOA's own pool deposits ("Diamond Hands"), independent of the Degen
@@ -1578,16 +1622,22 @@ def cmd_summary(as_json: bool = False):
1578
1622
  row["usd"] = round(row["amount"] * usd, 2)
1579
1623
  return row
1580
1624
 
1625
+ # Compute equity-based health (0-100%)
1626
+ _hp_rows = [_asset_row(r) for r in supplied]
1627
+ _hp_borrowed = [_asset_row(r) for r in borrowed]
1628
+ _hp = _compute_health_pct(_hp_rows, _hp_borrowed)
1629
+
1581
1630
  out = {
1582
1631
  "wallet": acct.address,
1583
1632
  "account": pa,
1584
1633
  "nativeBalance": pa_eth if pa_eth >= 1e-9 else None,
1585
- "supplied": [_asset_row(r) for r in supplied],
1586
- "borrowed": [_asset_row(r) for r in borrowed],
1634
+ "supplied": _hp_rows,
1635
+ "borrowed": _hp_borrowed,
1587
1636
  "poolDeposits": [_asset_row(r) for r in pool_deposits],
1588
1637
  "totalValueUsd": solvency["total"],
1589
1638
  "debtUsd": solvency["debt"],
1590
1639
  "healthRatio": solvency["ratio"],
1640
+ "healthPct": _hp.get("health_pct"),
1591
1641
  "solvent": solvency["solvent"],
1592
1642
  "solvencyError": solvency["error"],
1593
1643
  }
@@ -1626,7 +1676,22 @@ def cmd_summary(as_json: bool = False):
1626
1676
  print(f" Total value: ${solvency['total']:,.2f}")
1627
1677
  print(f" Debt: ${solvency['debt']:,.2f}")
1628
1678
  ratio_str = ">1000.00 (negligible debt)" if solvency["ratio"] is None else f"{solvency['ratio']:.4f}"
1629
- print(f" Health ratio: {ratio_str} (>1.0 = solvent)")
1679
+ print(f" Health ratio (chain): {ratio_str} (>1.0 = solvent, 1.0 = liquidation)")
1680
+ # ─── Equity-based health (0-100%) ───
1681
+ # Different from health_ratio!
1682
+ hp = _compute_health_pct(
1683
+ [{"symbol": r["symbol"], "balance": "0", "usd": (r["raw"] / 10**r["decimals"]) * solvency["prices"].get(r["symbol"], 0)}
1684
+ for r in supplied if solvency["prices"].get(r["symbol"])],
1685
+ [{"symbol": r["symbol"], "balance": "0", "usd": (r["raw"] / 10**r["decimals"]) * solvency["prices"].get(r["symbol"], 0)}
1686
+ for r in borrowed if solvency["prices"].get(r["symbol"])],
1687
+ )
1688
+ if "error" not in hp:
1689
+ print(f" Health (0-100%): {hp['health_pct']:.1f}%")
1690
+ print(f" (supplied=${hp['supplied_usd']:.2f}, debt=${hp['debt_usd']:.2f},"
1691
+ f" equity=${hp['equity']:.2f}, max_debt=${hp['max_debt']:.2f}, {hp.get('tier','')})")
1692
+ print(f" 0%=liquidation 50%=half borrowing power used 100%=no debt")
1693
+ else:
1694
+ print(f" Health (0-100%): N/A ({hp['error']})")
1630
1695
  print(f" Solvent: {'yes' if solvency['solvent'] else 'NO - liquidatable'}")
1631
1696
  else:
1632
1697
  print(f" Health/solvency: RedStone fetch/call failed ({solvency['error']}); showing balances only")
@@ -52,7 +52,7 @@ back to balances-only if the gateway is unreachable).
52
52
 
53
53
  NOTE: prime-summary shows TWO health metrics — don't confuse them:
54
54
  - "Health ratio (chain)": on-chain getHealthRatio. 1.0 = liquidation, >1.0 = solvent.
55
- - "Health (Bruno 0-100%)": equity-based, frontend-style. 0% = liquidation, 50% = half
55
+ - "Health (0-100%)": equity-based, frontend-style. 0% = liquidation, 50% = half
56
56
  borrowing power used, 100% = no debt.
57
57
  Formula for the latter: equity=supplied-debt, max_debt=equity*(tier-1),
58
58
  pct=(max_debt-debt)/max_debt*100. tier=5 (BASIC) or 10 (PREMIUM).
@@ -242,7 +242,7 @@ EXPLORER = "https://snowtrace.io"
242
242
  CHAIN_ID = 43114
243
243
  ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
244
244
  # ── Agent / wallet selection ─────────────────────────────────────────────────
245
- # Agent-agnostic: any agent (Parakletos, Paraklaudios, or another of Bruno's) runs
245
+ # Agent-agnostic: any agent (Parakletos, Paraklaudios, or another authorized agent) runs
246
246
  # this same tool with its OWN wallet. The Prime Account is derived on-chain from the
247
247
  # wallet owner (getLoanForOwner), so each agent automatically operates on its own
248
248
  # Prime Account — no per-agent addresses are hardcoded.
@@ -441,12 +441,12 @@ TJ_ROUTER_V21 = "0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30"
441
441
  TJ_ROUTER_V22 = "0x18556DA13313f3532c54711497A8FedAC273220E"
442
442
  TJ_MAX_BINS = 80
443
443
 
444
- # Whitelisted LB pairs exposed as tool keys, matching Bruno's live frontend (bin step in
444
+ # Whitelisted LB pairs exposed as tool keys, matching the live frontend (bin step in
445
445
  # the key suffix where a pair exists at two steps). For each: the LBPair address, the
446
446
  # router version the pair belongs to, the canonical (tokenX, tokenY) order read on-chain,
447
447
  # and the binStep. tokenX/tokenY carry the ERC20 address, the account bytes32 symbol (for
448
448
  # the in-account balance read + RedStone feed), and decimals. The 13 source-whitelisted
449
- # pairs include 4 aUSD pairs not on Bruno's frontend; those are omitted (no clean
449
+ # pairs include 4 aUSD pairs not on the live frontend; those are omitted (no clean
450
450
  # symbol/decimals + out of scope).
451
451
  _WAVAX = {"addr": "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7", "symbol": "AVAX", "decimals": 18}
452
452
  _USDC = {"addr": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", "symbol": "USDC", "decimals": 6}
@@ -1995,23 +1995,23 @@ def gather_lending(w3, account):
1995
1995
  r["usd"] = None
1996
1996
  return out
1997
1997
 
1998
- def _compute_bruno_health(data: dict, tier_code: int = 0) -> dict:
1999
- """Compute Bruno's 0-100% health from gather_lending data + tier.
1998
+ def _compute_health_pct(data: dict, tier_code: int = 0) -> dict:
1999
+ """Compute equity-based health (0-100%) from gather_lending data + tier.
2000
2000
 
2001
2001
  DeltaPrime has *two* health metrics that agents must not confuse:
2002
2002
 
2003
2003
  1. health_ratio (on-chain, getHealthRatio): 1.0 = liquidation, >1.0 = solvent.
2004
2004
  This is the raw weighted-collateral / debt ratio from the SolvencyFacet.
2005
2005
 
2006
- 2. bruno_pct (equity-based, 0-100%): the scale used in the DeltaPrime frontend
2006
+ 2. health_pct (equity-based, 0-100%): the scale used in the DeltaPrime frontend
2007
2007
  and the account-health-monitor cron. 0% = liquidation, 100% = no debt.
2008
2008
  Formula:
2009
2009
  equity = supplied_usd - debt_usd
2010
2010
  max_mult = 10 if PREMIUM tier else 5 if BASIC
2011
2011
  max_debt = equity * (max_mult - 1)
2012
- bruno_pct = (max_debt - debt_usd) / max_debt * 100
2012
+ health_pct = (max_debt - debt_usd) / max_debt * 100
2013
2013
 
2014
- Returns dict with keys: bruno_pct, supplied_usd, debt_usd, equity, max_debt,
2014
+ Returns dict with keys: health_pct, supplied_usd, debt_usd, equity, max_debt,
2015
2015
  tier_label, or error.
2016
2016
  """
2017
2017
  supplied_usd = sum(r.get("usd", 0) or 0 for r in data.get("supplied", []))
@@ -2022,18 +2022,18 @@ def _compute_bruno_health(data: dict, tier_code: int = 0) -> dict:
2022
2022
  max_mult = {0: 5, 1: 10}.get(tier_code, 5)
2023
2023
 
2024
2024
  if equity <= 0.01:
2025
- return {"bruno_pct": 0.0, "supplied_usd": round(supplied_usd, 2),
2025
+ return {"health_pct": 0.0, "supplied_usd": round(supplied_usd, 2),
2026
2026
  "debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
2027
2027
  "max_debt": 0.0, "tier": tier_label, "error": "equity near zero"}
2028
2028
 
2029
2029
  max_debt = equity * (max_mult - 1)
2030
2030
  if max_debt > 0 and debt_usd >= 0:
2031
- bruno_pct = (max_debt - min(debt_usd, max_debt)) / max_debt * 100
2032
- bruno_pct = max(0.0, min(100.0, bruno_pct))
2031
+ health_pct = (max_debt - min(debt_usd, max_debt)) / max_debt * 100
2032
+ health_pct = max(0.0, min(100.0, health_pct))
2033
2033
  else:
2034
- bruno_pct = 100.0
2034
+ health_pct = 100.0
2035
2035
 
2036
- return {"bruno_pct": round(bruno_pct, 1), "supplied_usd": round(supplied_usd, 2),
2036
+ return {"health_pct": round(health_pct, 1), "supplied_usd": round(supplied_usd, 2),
2037
2037
  "debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
2038
2038
  "max_debt": round(max_debt, 2), "tier": tier_label}
2039
2039
 
@@ -2082,22 +2082,22 @@ def cmd_prime_summary():
2082
2082
  # astronomically large there); render that as ">1000" rather than a junk number.
2083
2083
  ratio_str = ">1000.00 (negligible debt)" if ratio is None else f"{ratio:.4f}"
2084
2084
  print(f" Health ratio (chain): {ratio_str} (>1.0 = solvent, 1.0 = liquidation)")
2085
- # ─── Bruno's 0-100% health (equity-based, uses tier multiplier) ───
2086
- # Different from health_ratio! See _compute_bruno_health docstring.
2085
+ # ─── Equity-based health (0-100%) ───
2086
+ # Different from health_ratio! See _compute_health_pct docstring.
2087
2087
  # Get tier from the Prime Account (oracle-free view)
2088
2088
  try:
2089
2089
  tier_info = gather_prime_tier(w3, acct, account)
2090
2090
  tier_code = tier_info.get("tier_code", 0)
2091
2091
  except Exception:
2092
2092
  tier_code = 0
2093
- bh = _compute_bruno_health(data, tier_code)
2094
- if "error" not in bh:
2095
- print(f" Health (Bruno 0-100%): {bh['bruno_pct']:.1f}%")
2096
- print(f" (supplied=${bh['supplied_usd']:.2f}, debt=${bh['debt_usd']:.2f},"
2097
- f" equity=${bh['equity']:.2f}, max_debt=${bh['max_debt']:.2f}, {bh['tier']})")
2093
+ hp = _compute_health_pct(data, tier_code)
2094
+ if "error" not in hp:
2095
+ print(f" Health (0-100%): {hp['health_pct']:.1f}%")
2096
+ print(f" (supplied=${hp['supplied_usd']:.2f}, debt=${hp['debt_usd']:.2f},"
2097
+ f" equity=${hp['equity']:.2f}, max_debt=${hp['max_debt']:.2f}, {hp['tier']})")
2098
2098
  print(f" 0%=liquidation 50%=half borrowing power used 100%=no debt")
2099
2099
  else:
2100
- print(f" Health (Bruno 0-100%): N/A ({bh['error']})")
2100
+ print(f" Health (0-100%): N/A ({hp['error']})")
2101
2101
  print(f" Solvent: {'yes' if data['solvent'] else 'NO — liquidatable'}")
2102
2102
  else:
2103
2103
  print(f" Health/solvency: RedStone fetch/call failed ({data.get('solvency_error', 'error')}); "
@@ -5003,12 +5003,12 @@ def gather_defi() -> dict:
5003
5003
  result["total_usd"] = lending["total_value_usd"]
5004
5004
  result["health_ratio"] = lending["health_ratio"]
5005
5005
  result["solvent"] = lending["solvent"]
5006
- # Compute Bruno's 0-100% health from lending data + tier
5007
- result["bruno_pct"] = _compute_bruno_health(lending, tier.get("tier_code", 0)).get("bruno_pct")
5006
+ # Compute equity-based health (0-100%) from lending data + tier
5007
+ result["health_pct"] = _compute_health_pct(lending, tier.get("tier_code", 0)).get("health_pct")
5008
5008
  if lending["supplied"] or lending["borrowed"]:
5009
5009
  result["groups"].append({
5010
5010
  "type": "Lending / Leverage", "health_ratio": lending["health_ratio"],
5011
- "bruno_pct": result["bruno_pct"],
5011
+ "health_pct": result["health_pct"],
5012
5012
  "supplied": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
5013
5013
  for r in lending["supplied"]],
5014
5014
  "borrowed": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
@@ -37,10 +37,10 @@ TIER_MAX = {"basic": 5, "premium": 10}
37
37
  # ════════════════════════════════════════════════════════════════════
38
38
 
39
39
  def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
40
- """Compute Bruno's 0-100% health scale from defi --json data.
40
+ """Compute equity-based health (0-100%) from defi --json data.
41
41
 
42
- PREFERS the precomputed ``bruno_pct`` from defi --json (which primecli now
43
- includes as of 2026-06-04), falling back to manual calculation when absent.
42
+ PREFERS the precomputed ``health_pct`` from defi --json (which primecli >= 0.5.4
43
+ includes), falling back to manual calculation when absent.
44
44
 
45
45
  Formula:
46
46
  equity = total_supplied_usd - total_debt_usd
@@ -49,7 +49,7 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
49
49
 
50
50
  This is DIFFERENT from getHealthRatio (the on-chain ratio where 1.0 = liquidation).
51
51
  Do NOT convert between the two. The ``health_ratio`` metric is the on-chain value
52
- (1.0=liquidation, >1.0=solvent). The ``bruno_pct`` is the equity-based frontend
52
+ (1.0=liquidation, >1.0=solvent). The ``health_pct`` is the equity-based frontend
53
53
  measurement (0%=liquidation, 50%=half borrowing power used, 100%=no debt).
54
54
 
55
55
  Returns dict with health metrics or error.
@@ -61,8 +61,8 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
61
61
  supplied = g.get("supplied", [])
62
62
  borrowed = g.get("borrowed", [])
63
63
  health_ratio = g.get("health_ratio", 0) or 0
64
- # Use precomputed bruno_pct from defi --json if available (primecli >= 0.5.0)
65
- precomputed = g.get("bruno_pct")
64
+ # Use precomputed health_pct from defi --json if available (primecli >= 0.5.4)
65
+ precomputed = g.get("health_pct") or g.get("bruno_pct") # bruno_pct for backward compat
66
66
  if precomputed is not None:
67
67
  # Early return: precomputed value exists, enrich with detail fields
68
68
  supplied_usd = sum(s.get("usd", 0) or 0 for s in supplied)
@@ -74,7 +74,7 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
74
74
  has_lb = any(sym in ("LB_AVAX_USDC", "LB_WAVAX_USDC", "JOE") or "TRADERJOE" in sym.upper() for sym in symbols)
75
75
  has_aero = any("AERO" in sym.upper() or "CL_POSITION" in sym.upper() for sym in symbols)
76
76
  return {
77
- "bruno_pct": float(precomputed),
77
+ "health_pct": float(precomputed),
78
78
  "health_ratio": round(health_ratio, 4),
79
79
  "supplied_usd": round(supplied_usd, 2),
80
80
  "debt_usd": round(debt_usd, 2),
@@ -84,7 +84,7 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
84
84
  "has_gmx": has_gmx,
85
85
  "has_lb": has_lb,
86
86
  "has_aero": has_aero,
87
- "action": "computed from defi --json bruno_pct",
87
+ "action": "computed from defi --json health_pct",
88
88
  }
89
89
  else:
90
90
  supplied = defi_data.get("supplied", [])
@@ -97,7 +97,7 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
97
97
 
98
98
  if equity <= 0.01:
99
99
  return {
100
- "bruno_pct": 0.0,
100
+ "health_pct": 0.0,
101
101
  "health_ratio": round(health_ratio, 4),
102
102
  "supplied_usd": round(supplied_usd, 2),
103
103
  "debt_usd": round(debt_usd, 2),
@@ -121,15 +121,15 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
121
121
  has_aero = any("AERO" in sym.upper() or "CL_POSITION" in sym.upper() for sym in symbols)
122
122
 
123
123
  if max_debt > 0 and debt_usd >= 0:
124
- bruno_pct = (max_debt - min(debt_usd, max_debt)) / max_debt * 100
125
- bruno_pct = max(0.0, min(100.0, bruno_pct))
124
+ health_pct = (max_debt - min(debt_usd, max_debt)) / max_debt * 100
125
+ health_pct = max(0.0, min(100.0, health_pct))
126
126
  else:
127
- bruno_pct = 100.0
127
+ health_pct = 100.0
128
128
 
129
129
  delta_debt = (max_debt * 0.5) - debt_usd # center target = 50%
130
130
 
131
131
  return {
132
- "bruno_pct": round(bruno_pct, 1),
132
+ "health_pct": round(health_pct, 1),
133
133
  "health_ratio": round(health_ratio, 4),
134
134
  "supplied_usd": round(supplied_usd, 2),
135
135
  "debt_usd": round(debt_usd, 2),
@@ -320,7 +320,7 @@ def run_tick(
320
320
  "reason": "equity_near_zero",
321
321
  "equity": health["equity"],
322
322
  "debt": health["debt_usd"],
323
- "health_pct": health["bruno_pct"],
323
+ "health_pct": health["health_pct"],
324
324
  "label": label,
325
325
  })
326
326
  result.update(health)
@@ -336,23 +336,23 @@ def run_tick(
336
336
 
337
337
  # 5. Health swing detection (always)
338
338
  last_pct = load_last_health(state_dir)
339
- if last_pct is not None and health["bruno_pct"] is not None:
340
- diff = abs(health["bruno_pct"] - last_pct)
339
+ if last_pct is not None and health["health_pct"] is not None:
340
+ diff = abs(health["health_pct"] - last_pct)
341
341
  if diff > 10:
342
342
  write_escalation(state_dir, "health-swing", {
343
343
  "reason": "health_swing",
344
344
  "from_pct": last_pct,
345
- "to_pct": health["bruno_pct"],
345
+ "to_pct": health["health_pct"],
346
346
  "delta": diff,
347
347
  "label": label,
348
348
  })
349
349
  result["escalation"] = "health_swing"
350
- save_last_health(state_dir, health["bruno_pct"] or 0.0)
350
+ save_last_health(state_dir, health["health_pct"] or 0.0)
351
351
 
352
352
  # 6. Append to history
353
353
  entry = {
354
354
  "ts": now_iso, "mode": mode,
355
- "pct": health["bruno_pct"],
355
+ "pct": health["health_pct"],
356
356
  "equity": health["equity"],
357
357
  "debt": health["debt_usd"],
358
358
  "hr": health["health_ratio"],
@@ -369,7 +369,7 @@ def run_tick(
369
369
  write_escalation(state_dir, "incomplete-valuation", {
370
370
  "reason": "incomplete_valuation",
371
371
  "detail": val_reason,
372
- "health_pct": health["bruno_pct"],
372
+ "health_pct": health["health_pct"],
373
373
  "equity": health["equity"],
374
374
  "debt": health["debt_usd"],
375
375
  "label": label,
@@ -387,7 +387,7 @@ def run_tick(
387
387
  side = strategy.get("side", "short")
388
388
  low, high = target_range[0], target_range[1]
389
389
 
390
- pct = health["bruno_pct"]
390
+ pct = health["health_pct"]
391
391
  equity = health["equity"]
392
392
  debt = health["debt_usd"]
393
393
  raw_usdc = health["raw_usdc"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: primecli
3
- Version: 0.5.3
3
+ Version: 0.5.4
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.3"
7
+ version = "0.5.4"
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