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.
- {primecli-0.5.3 → primecli-0.5.4}/PKG-INFO +1 -1
- {primecli-0.5.3 → primecli-0.5.4}/primecli/arbprime.py +25 -25
- {primecli-0.5.3 → primecli-0.5.4}/primecli/degenprime.py +70 -5
- {primecli-0.5.3 → primecli-0.5.4}/primecli/deltaprime.py +25 -25
- {primecli-0.5.3 → primecli-0.5.4}/primecli/health_monitor.py +21 -21
- {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/PKG-INFO +1 -1
- {primecli-0.5.3 → primecli-0.5.4}/pyproject.toml +1 -1
- {primecli-0.5.3 → primecli-0.5.4}/LICENSE +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/README.md +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/primecli/__init__.py +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/SOURCES.txt +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/dependency_links.txt +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/entry_points.txt +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/requires.txt +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/primecli.egg-info/top_level.txt +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/setup.cfg +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/tests/test_cross_file_identity.py +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/tests/test_gas_pricing.py +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/tests/test_health_monitor.py +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/tests/test_paraswap_validator.py +0 -0
- {primecli-0.5.3 → primecli-0.5.4}/tests/test_redstone_encoding.py +0 -0
- {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
|
+
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 (
|
|
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
|
|
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
|
|
321
|
-
#
|
|
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
|
|
1972
|
-
"""Compute
|
|
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.
|
|
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
|
-
|
|
1985
|
+
health_pct = (max_debt - debt_usd) / max_debt * 100
|
|
1986
1986
|
|
|
1987
|
-
Returns dict with keys:
|
|
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 {"
|
|
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
|
-
|
|
2005
|
-
|
|
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
|
-
|
|
2007
|
+
health_pct = 100.0
|
|
2008
2008
|
|
|
2009
|
-
return {"
|
|
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
|
-
# ───
|
|
2059
|
-
# Different from health_ratio! See
|
|
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
|
-
|
|
2067
|
-
if "error" not in
|
|
2068
|
-
print(f" Health (
|
|
2069
|
-
print(f" (supplied=${
|
|
2070
|
-
f" equity=${
|
|
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 (
|
|
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
|
|
5075
|
-
result["
|
|
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
|
-
"
|
|
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
|
-
"
|
|
1465
|
-
"
|
|
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":
|
|
1586
|
-
"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:
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1999
|
-
"""Compute
|
|
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.
|
|
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
|
-
|
|
2012
|
+
health_pct = (max_debt - debt_usd) / max_debt * 100
|
|
2013
2013
|
|
|
2014
|
-
Returns dict with keys:
|
|
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 {"
|
|
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
|
-
|
|
2032
|
-
|
|
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
|
-
|
|
2034
|
+
health_pct = 100.0
|
|
2035
2035
|
|
|
2036
|
-
return {"
|
|
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
|
-
# ───
|
|
2086
|
-
# Different from health_ratio! See
|
|
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
|
-
|
|
2094
|
-
if "error" not in
|
|
2095
|
-
print(f" Health (
|
|
2096
|
-
print(f" (supplied=${
|
|
2097
|
-
f" equity=${
|
|
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 (
|
|
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
|
|
5007
|
-
result["
|
|
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
|
-
"
|
|
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
|
|
40
|
+
"""Compute equity-based health (0-100%) from defi --json data.
|
|
41
41
|
|
|
42
|
-
PREFERS the precomputed ``
|
|
43
|
-
includes
|
|
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 ``
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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["
|
|
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["
|
|
340
|
-
diff = abs(health["
|
|
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["
|
|
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["
|
|
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["
|
|
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["
|
|
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["
|
|
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
|
+
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|