primecli 0.5.1__tar.gz → 0.5.3__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.1 → primecli-0.5.3}/PKG-INFO +3 -4
- {primecli-0.5.1 → primecli-0.5.3}/README.md +1 -1
- {primecli-0.5.1 → primecli-0.5.3}/primecli/arbprime.py +70 -1
- {primecli-0.5.1 → primecli-0.5.3}/primecli/degenprime.py +173 -51
- {primecli-0.5.1 → primecli-0.5.3}/primecli/deltaprime.py +91 -13
- {primecli-0.5.1 → primecli-0.5.3}/primecli/health_monitor.py +33 -0
- {primecli-0.5.1 → primecli-0.5.3}/primecli.egg-info/PKG-INFO +3 -4
- {primecli-0.5.1 → primecli-0.5.3}/pyproject.toml +2 -2
- {primecli-0.5.1 → primecli-0.5.3}/LICENSE +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/primecli/__init__.py +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/primecli.egg-info/SOURCES.txt +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/primecli.egg-info/dependency_links.txt +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/primecli.egg-info/entry_points.txt +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/primecli.egg-info/requires.txt +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/primecli.egg-info/top_level.txt +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/setup.cfg +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/tests/test_cross_file_identity.py +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/tests/test_gas_pricing.py +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/tests/test_health_monitor.py +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/tests/test_paraswap_validator.py +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/tests/test_redstone_encoding.py +0 -0
- {primecli-0.5.1 → primecli-0.5.3}/tests/test_to_wei_units.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: primecli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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
|
|
@@ -28,7 +28,6 @@ Requires-Dist: eth-account>=0.13
|
|
|
28
28
|
Requires-Dist: eth-keys>=0.5
|
|
29
29
|
Requires-Dist: eth-abi<7,>=5.0
|
|
30
30
|
Requires-Dist: requests>=2.31
|
|
31
|
-
Dynamic: license-file
|
|
32
31
|
|
|
33
32
|
# primecli
|
|
34
33
|
|
|
@@ -48,7 +47,7 @@ Built for agent use:
|
|
|
48
47
|
- RedStone-signed solvency math handled internally, with a regression test pinning the half-boundary `toFixed(8)` encoding.
|
|
49
48
|
- ParaSwap calldata validated client-side against the on-chain executor allowlist before broadcast.
|
|
50
49
|
|
|
51
|
-
**Current version:** 0.5.
|
|
50
|
+
**Current version:** 0.5.2 The 0.x line is pre-1.0, so breaking changes are possible. See [Releases](https://github.com/Mnemosyne-quest/primecli/releases).
|
|
52
51
|
|
|
53
52
|
> **Breaking change in 0.5.0:** there is no longer a default signing key. Earlier versions silently fell back to a baked-in agent when no key was configured; that fallback has been removed. With no key configured, every command now fails closed with `No signing key found...`. Set a key explicitly (see [Configuration](#configuration)).
|
|
54
53
|
|
|
@@ -16,7 +16,7 @@ Built for agent use:
|
|
|
16
16
|
- RedStone-signed solvency math handled internally, with a regression test pinning the half-boundary `toFixed(8)` encoding.
|
|
17
17
|
- ParaSwap calldata validated client-side against the on-chain executor allowlist before broadcast.
|
|
18
18
|
|
|
19
|
-
**Current version:** 0.5.
|
|
19
|
+
**Current version:** 0.5.2 The 0.x line is pre-1.0, so breaking changes are possible. See [Releases](https://github.com/Mnemosyne-quest/primecli/releases).
|
|
20
20
|
|
|
21
21
|
> **Breaking change in 0.5.0:** there is no longer a default signing key. Earlier versions silently fell back to a baked-in agent when no key was configured; that fallback has been removed. With no key configured, every command now fails closed with `No signing key found...`. Set a key explicitly (see [Configuration](#configuration)).
|
|
22
22
|
|
|
@@ -57,6 +57,13 @@ prime-summary reports live solvency (health ratio, total value, debt, solvent fl
|
|
|
57
57
|
SolvencyFacetProdArbitrum, read via eth_call with a RedStone price payload appended (falls
|
|
58
58
|
back to balances-only if the gateway is unreachable).
|
|
59
59
|
|
|
60
|
+
NOTE: prime-summary shows TWO health metrics — don't confuse them:
|
|
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
|
|
63
|
+
borrowing power used, 100% = no debt.
|
|
64
|
+
Formula for the latter: equity=supplied-debt, max_debt=equity*(tier-1),
|
|
65
|
+
pct=(max_debt-debt)/max_debt*100. tier=5 (BASIC) or 10 (PREMIUM).
|
|
66
|
+
|
|
60
67
|
Collateral withdrawal is a two-step, time-delayed flow on the Prime Account (there is NO
|
|
61
68
|
instant withdraw of in-account collateral). The savings-pool `withdraw` above is a separate
|
|
62
69
|
two-step intent flow on the pool itself, not the Prime Account. Step 1: `withdraw --pool X
|
|
@@ -1961,6 +1968,49 @@ def gather_lending(w3, account):
|
|
|
1961
1968
|
r["usd"] = None
|
|
1962
1969
|
return out
|
|
1963
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.
|
|
1973
|
+
|
|
1974
|
+
DeltaPrime has *two* health metrics that agents must not confuse:
|
|
1975
|
+
|
|
1976
|
+
1. health_ratio (on-chain, getHealthRatio): 1.0 = liquidation, >1.0 = solvent.
|
|
1977
|
+
This is the raw weighted-collateral / debt ratio from the SolvencyFacet.
|
|
1978
|
+
|
|
1979
|
+
2. bruno_pct (equity-based, 0-100%): the scale used in the DeltaPrime frontend
|
|
1980
|
+
and the account-health-monitor cron. 0% = liquidation, 100% = no debt.
|
|
1981
|
+
Formula:
|
|
1982
|
+
equity = supplied_usd - debt_usd
|
|
1983
|
+
max_mult = 10 if PREMIUM tier else 5 if BASIC
|
|
1984
|
+
max_debt = equity * (max_mult - 1)
|
|
1985
|
+
bruno_pct = (max_debt - debt_usd) / max_debt * 100
|
|
1986
|
+
|
|
1987
|
+
Returns dict with keys: bruno_pct, supplied_usd, debt_usd, equity, max_debt,
|
|
1988
|
+
tier_label, or error.
|
|
1989
|
+
"""
|
|
1990
|
+
supplied_usd = sum(r.get("usd", 0) or 0 for r in data.get("supplied", []))
|
|
1991
|
+
debt_usd = sum(r.get("usd", 0) or 0 for r in data.get("borrowed", []))
|
|
1992
|
+
equity = supplied_usd - debt_usd
|
|
1993
|
+
tier_labels = {0: "BASIC", 1: "PREMIUM", 2: "_NON_EXISTENT"}
|
|
1994
|
+
tier_label = tier_labels.get(tier_code, str(tier_code))
|
|
1995
|
+
max_mult = {0: 5, 1: 10}.get(tier_code, 5)
|
|
1996
|
+
|
|
1997
|
+
if equity <= 0.01:
|
|
1998
|
+
return {"bruno_pct": 0.0, "supplied_usd": round(supplied_usd, 2),
|
|
1999
|
+
"debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
|
|
2000
|
+
"max_debt": 0.0, "tier": tier_label, "error": "equity near zero"}
|
|
2001
|
+
|
|
2002
|
+
max_debt = equity * (max_mult - 1)
|
|
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))
|
|
2006
|
+
else:
|
|
2007
|
+
bruno_pct = 100.0
|
|
2008
|
+
|
|
2009
|
+
return {"bruno_pct": round(bruno_pct, 1), "supplied_usd": round(supplied_usd, 2),
|
|
2010
|
+
"debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
|
|
2011
|
+
"max_debt": round(max_debt, 2), "tier": tier_label}
|
|
2012
|
+
|
|
2013
|
+
|
|
1964
2014
|
def cmd_prime_summary():
|
|
1965
2015
|
w3 = get_w3()
|
|
1966
2016
|
acct = get_account()
|
|
@@ -2004,7 +2054,23 @@ def cmd_prime_summary():
|
|
|
2004
2054
|
# gather_lending nulls the ratio when debt is negligible (the raw value is
|
|
2005
2055
|
# astronomically large there); render that as ">1000" rather than a junk number.
|
|
2006
2056
|
ratio_str = ">1000.00 (negligible debt)" if ratio is None else f"{ratio:.4f}"
|
|
2007
|
-
print(f" Health ratio:
|
|
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.
|
|
2060
|
+
# Get tier from the Prime Account (oracle-free view)
|
|
2061
|
+
try:
|
|
2062
|
+
tier_info = gather_prime_tier(w3, acct, account)
|
|
2063
|
+
tier_code = tier_info.get("tier_code", 0)
|
|
2064
|
+
except Exception:
|
|
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']})")
|
|
2071
|
+
print(f" 0%=liquidation 50%=half borrowing power used 100%=no debt")
|
|
2072
|
+
else:
|
|
2073
|
+
print(f" Health (Bruno 0-100%): N/A ({bh['error']})")
|
|
2008
2074
|
print(f" Solvent: {'yes' if data['solvent'] else 'NO — liquidatable'}")
|
|
2009
2075
|
else:
|
|
2010
2076
|
print(f" Health/solvency: RedStone fetch/call failed ({data.get('solvency_error', 'error')}); "
|
|
@@ -5005,9 +5071,12 @@ def gather_defi() -> dict:
|
|
|
5005
5071
|
result["total_usd"] = lending["total_value_usd"]
|
|
5006
5072
|
result["health_ratio"] = lending["health_ratio"]
|
|
5007
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")
|
|
5008
5076
|
if lending["supplied"] or lending["borrowed"]:
|
|
5009
5077
|
result["groups"].append({
|
|
5010
5078
|
"type": "Lending / Leverage", "health_ratio": lending["health_ratio"],
|
|
5079
|
+
"bruno_pct": result["bruno_pct"],
|
|
5011
5080
|
"supplied": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
|
|
5012
5081
|
for r in lending["supplied"]],
|
|
5013
5082
|
"borrowed": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
|
|
@@ -15,6 +15,7 @@ Usage:
|
|
|
15
15
|
degenprime create-account [--execute]
|
|
16
16
|
degenprime create-account --fund-pool usdc --fund-amount 100 [--execute]
|
|
17
17
|
degenprime summary
|
|
18
|
+
degenprime defi --json (aggregate ALL positions as DeBank-style JSON; read-only)
|
|
18
19
|
degenprime fund --pool usdc --amount 100 [--execute]
|
|
19
20
|
degenprime borrow --pool usdc --amount 100 [--execute]
|
|
20
21
|
degenprime repay --pool usdc --amount 100 [--execute]
|
|
@@ -1293,34 +1294,10 @@ def _prices_usd(w3, account, symbols: list, payload: bytes) -> dict:
|
|
|
1293
1294
|
except Exception:
|
|
1294
1295
|
return {}
|
|
1295
1296
|
|
|
1296
|
-
def
|
|
1297
|
-
"""
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
Note: per-asset USD is best-effort - only symbols with a RedStone primary-prod
|
|
1301
|
-
feed are priced here. Symbols sourced on-chain from BaseOracle TWAP show as
|
|
1302
|
-
balance-only (the SolvencyFacet still values them for the total/debt figures).
|
|
1303
|
-
|
|
1304
|
-
With --json: emits a single JSON object covering wallet, account, native
|
|
1305
|
-
balance, per-asset supplied/borrowed with optional USD, poolDeposits (the EOA's
|
|
1306
|
-
'Diamond Hands' lending-pool balances, emitted even with no Degen Account),
|
|
1307
|
-
total/debt/health-ratio/solvent flags. Null fields, empty lists, and empty dicts are dropped (same
|
|
1308
|
-
trim contract as `deltaprime defi --json`). Numeric 0 and boolean false are
|
|
1309
|
-
preserved.
|
|
1310
|
-
|
|
1311
|
-
Multicall: stage A batches getAllOwnedAssets + getDebts (2 -> 1 RPC). Stage B
|
|
1312
|
-
batches one getBalance per owned asset (N -> 1 RPC). Stage C batches the four
|
|
1313
|
-
RedStone-gated solvency views + getPrices (4-5 -> 1 RPC), each leg carrying the
|
|
1314
|
-
same RedStone payload appended."""
|
|
1315
|
-
w3 = get_w3()
|
|
1316
|
-
acct = get_account()
|
|
1317
|
-
pa = get_prime_account(w3, acct.address)
|
|
1318
|
-
if not as_json:
|
|
1319
|
-
print(f"Wallet: {acct.address}")
|
|
1320
|
-
|
|
1321
|
-
# Pool deposits ("Diamond Hands") are EOA balances independent of the Degen Account,
|
|
1322
|
-
# so read them up front via one Multicall3 — they must surface even for a wallet with
|
|
1323
|
-
# no Degen Account (e.g. a deposit made before creating one).
|
|
1297
|
+
def _gather_pool_deposits(w3, owner: str) -> list:
|
|
1298
|
+
"""The EOA's 'Diamond Hands' lending-pool balances, read independently of the Degen
|
|
1299
|
+
Account via one Multicall3 (one balanceOf per pool). Surfaces even for a wallet with
|
|
1300
|
+
no Degen Account. Returns [{symbol, raw, decimals}, ...] for non-zero balances."""
|
|
1324
1301
|
pool_deposits = []
|
|
1325
1302
|
dep_legs, dep_meta = [], []
|
|
1326
1303
|
for _pname, _pcfg in POOLS.items():
|
|
@@ -1329,7 +1306,7 @@ def cmd_summary(as_json: bool = False):
|
|
|
1329
1306
|
except Exception:
|
|
1330
1307
|
continue
|
|
1331
1308
|
_proxy_cs = Web3.to_checksum_address(_pcfg["proxy"])
|
|
1332
|
-
dep_legs.append((_proxy_cs, bytes.fromhex(_pc.encode_abi("balanceOf", args=[
|
|
1309
|
+
dep_legs.append((_proxy_cs, bytes.fromhex(_pc.encode_abi("balanceOf", args=[owner])[2:])))
|
|
1333
1310
|
dep_meta.append(_pcfg)
|
|
1334
1311
|
if dep_legs:
|
|
1335
1312
|
try:
|
|
@@ -1340,32 +1317,21 @@ def cmd_summary(as_json: bool = False):
|
|
|
1340
1317
|
_bal = w3.codec.decode(["uint256"], _rd)[0] if _ok and _rd else 0
|
|
1341
1318
|
if _bal > 0:
|
|
1342
1319
|
pool_deposits.append({"symbol": _pcfg["symbol"], "raw": _bal, "decimals": _pcfg["decimals"]})
|
|
1320
|
+
return pool_deposits
|
|
1343
1321
|
|
|
1344
|
-
if not pa:
|
|
1345
|
-
# No Degen Account: still surface Diamond Hands deposits (balance-only — the
|
|
1346
|
-
# RedStone getPrices view lives on the Degen Account, absent here).
|
|
1347
|
-
if as_json:
|
|
1348
|
-
out = {"wallet": acct.address, "account": None}
|
|
1349
|
-
if pool_deposits:
|
|
1350
|
-
out["poolDeposits"] = [{"symbol": r["symbol"], "amount": r["raw"] / 10**r["decimals"]}
|
|
1351
|
-
for r in pool_deposits]
|
|
1352
|
-
print(json.dumps(out, indent=2))
|
|
1353
|
-
else:
|
|
1354
|
-
print("No Degen Account yet. Create one with: degenprime create-account --execute")
|
|
1355
|
-
if pool_deposits:
|
|
1356
|
-
print(" Pool Deposits (Diamond Hands):")
|
|
1357
|
-
for r in pool_deposits:
|
|
1358
|
-
print(f" {r['symbol']:<8} {r['raw'] / 10**r['decimals']:,.6f}")
|
|
1359
|
-
return
|
|
1360
1322
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1323
|
+
def _gather_account_state(w3, account, pool_deposits: list):
|
|
1324
|
+
"""Read-only collateral / debt / RedStone-gated solvency for an existing Degen Account.
|
|
1325
|
+
Shared by `summary` and `defi`. Returns (pa_eth, supplied, borrowed, solvency) where
|
|
1326
|
+
supplied/borrowed are [{symbol, raw, decimals}, ...] and solvency carries
|
|
1327
|
+
total/debt/ratio/solvent/error/prices. pool_deposits is taken so their feeds get folded
|
|
1328
|
+
into the RedStone payload (else getPrices reverts on a deposit-only symbol).
|
|
1366
1329
|
|
|
1367
|
-
|
|
1330
|
+
Multicall: stage A batches getAllOwnedAssets + getDebts (2 -> 1 RPC). Stage B batches
|
|
1331
|
+
one getBalance per owned asset (N -> 1 RPC). Stage C batches the four RedStone-gated
|
|
1332
|
+
solvency views + getPrices (4-5 -> 1 RPC), each leg carrying the same payload appended."""
|
|
1368
1333
|
pa_cs = account.address
|
|
1334
|
+
pa_eth = w3.eth.get_balance(pa_cs) / 1e18
|
|
1369
1335
|
stage_a_legs = [
|
|
1370
1336
|
("getAllOwnedAssets", ["bytes32[]"], account.encode_abi("getAllOwnedAssets", args=[])),
|
|
1371
1337
|
("getDebts", ["(bytes32,uint256)[]"], account.encode_abi("getDebts", args=[])),
|
|
@@ -1449,6 +1415,160 @@ def cmd_summary(as_json: bool = False):
|
|
|
1449
1415
|
solvency["prices"] = prices
|
|
1450
1416
|
except Exception as e:
|
|
1451
1417
|
solvency["error"] = type(e).__name__
|
|
1418
|
+
return pa_eth, supplied, borrowed, solvency
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
def gather_defi() -> dict:
|
|
1422
|
+
"""Aggregate ALL DegenPrime positions for the selected wallet into one DeBank-style dict,
|
|
1423
|
+
matching the cross-tool shape `deltaprime defi --json` emits. Read-only: reuses the same
|
|
1424
|
+
gather helpers as `summary` (lending/solvency via the RedStone-gated views, plus the EOA's
|
|
1425
|
+
own pool deposits surfaced as a Savings group). Empty groups are omitted. total_usd /
|
|
1426
|
+
health_ratio / solvent come from the RedStone-gated solvency views; per-asset USD is
|
|
1427
|
+
best-effort (omitted where a RedStone feed is missing). Never broadcasts."""
|
|
1428
|
+
w3 = get_w3()
|
|
1429
|
+
acct = get_account()
|
|
1430
|
+
pa = get_prime_account(w3, acct.address)
|
|
1431
|
+
result = {
|
|
1432
|
+
"protocol": "DegenPrime", "url": "https://degenprime.io", "chain": "base",
|
|
1433
|
+
"wallet": acct.address, "prime_account": pa,
|
|
1434
|
+
"total_usd": None, "health_ratio": None, "solvent": None,
|
|
1435
|
+
"groups": [], "status": "ok",
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
pool_deposits = _gather_pool_deposits(w3, acct.address)
|
|
1439
|
+
# _gather_account_state folds pool-deposit symbols into getPrices, so this map covers
|
|
1440
|
+
# both in-account assets and Diamond-Hands deposits. Empty with no Degen Account.
|
|
1441
|
+
prices = {}
|
|
1442
|
+
|
|
1443
|
+
if pa:
|
|
1444
|
+
account = w3.eth.contract(address=Web3.to_checksum_address(pa), abi=PRIME_ACCOUNT_ABI)
|
|
1445
|
+
_pa_eth, supplied, borrowed, solvency = _gather_account_state(w3, account, pool_deposits)
|
|
1446
|
+
prices = solvency["prices"]
|
|
1447
|
+
result["total_usd"] = solvency["total"]
|
|
1448
|
+
result["health_ratio"] = solvency["ratio"]
|
|
1449
|
+
result["solvent"] = solvency["solvent"]
|
|
1450
|
+
if solvency["error"]:
|
|
1451
|
+
result["solvency_error"] = solvency["error"]
|
|
1452
|
+
|
|
1453
|
+
def _row(r):
|
|
1454
|
+
amt = r["raw"] / 10**r["decimals"]
|
|
1455
|
+
row = {"symbol": r["symbol"], "balance": f"{amt:.6f}"}
|
|
1456
|
+
usd = prices.get(r["symbol"])
|
|
1457
|
+
if usd is not None:
|
|
1458
|
+
row["usd"] = round(amt * usd, 2)
|
|
1459
|
+
return row
|
|
1460
|
+
|
|
1461
|
+
if supplied or borrowed:
|
|
1462
|
+
result["groups"].append({
|
|
1463
|
+
"type": "Lending / Leverage", "health_ratio": solvency["ratio"],
|
|
1464
|
+
"supplied": [_row(r) for r in supplied],
|
|
1465
|
+
"borrowed": [_row(r) for r in borrowed],
|
|
1466
|
+
})
|
|
1467
|
+
|
|
1468
|
+
# Savings: the EOA's own pool deposits ("Diamond Hands"), independent of the Degen
|
|
1469
|
+
# Account (so NOT in getTotalValue) — surfaced as their own group and added on top.
|
|
1470
|
+
# Priced from the same RedStone read used for the account (no extra RPC).
|
|
1471
|
+
if pool_deposits:
|
|
1472
|
+
sav_rows, sav_usd_total = [], 0.0
|
|
1473
|
+
for r in pool_deposits:
|
|
1474
|
+
amt = r["raw"] / 10**r["decimals"]
|
|
1475
|
+
row = {"symbol": r["symbol"], "balance": f"{amt:.6f}"}
|
|
1476
|
+
usd = prices.get(r["symbol"])
|
|
1477
|
+
if usd is not None:
|
|
1478
|
+
row["usd"] = round(amt * usd, 2)
|
|
1479
|
+
sav_usd_total += amt * usd
|
|
1480
|
+
sav_rows.append(row)
|
|
1481
|
+
result["groups"].append({"type": "Savings", "label": "Savings", "supplied": sav_rows})
|
|
1482
|
+
if sav_usd_total:
|
|
1483
|
+
result["total_usd"] = (result["total_usd"] or 0) + sav_usd_total
|
|
1484
|
+
|
|
1485
|
+
return result
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
_DEFI_DECORATIVE_KEYS = {"url"}
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
def _trim_defi_json(value):
|
|
1492
|
+
"""Recursively strip noise from `defi --json` output so an LLM consumer doesn't pay
|
|
1493
|
+
context for fields that carry no information: drops dict keys whose value is exactly
|
|
1494
|
+
None, drops keys whose value is an empty list or empty dict, drops the decorative
|
|
1495
|
+
top-level `url` key, but PRESERVES numeric 0 and boolean False (zero balance,
|
|
1496
|
+
explicitly-not-solvent, etc.) and keeps the top-level structure so a consumer can tell
|
|
1497
|
+
what's missing from what shape the response took. Same contract as `deltaprime`'s."""
|
|
1498
|
+
if isinstance(value, dict):
|
|
1499
|
+
out = {}
|
|
1500
|
+
for k, v in value.items():
|
|
1501
|
+
if k in _DEFI_DECORATIVE_KEYS:
|
|
1502
|
+
continue
|
|
1503
|
+
trimmed = _trim_defi_json(v)
|
|
1504
|
+
if trimmed is None:
|
|
1505
|
+
continue
|
|
1506
|
+
if isinstance(trimmed, (list, dict)) and len(trimmed) == 0:
|
|
1507
|
+
continue
|
|
1508
|
+
out[k] = trimmed
|
|
1509
|
+
return out
|
|
1510
|
+
if isinstance(value, list):
|
|
1511
|
+
return [_trim_defi_json(v) for v in value]
|
|
1512
|
+
return value
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
def cmd_defi(as_json: bool = True):
|
|
1516
|
+
"""Aggregate all DegenPrime positions for the wallet. Default output is the DeBank-style
|
|
1517
|
+
JSON (the cross-tool shape the health monitor consumes). On error, emits
|
|
1518
|
+
{"status":"error", ...} rather than raising, so the caller always gets parseable JSON."""
|
|
1519
|
+
try:
|
|
1520
|
+
data = gather_defi()
|
|
1521
|
+
except Exception as e:
|
|
1522
|
+
data = {"protocol": "DegenPrime", "chain": "base",
|
|
1523
|
+
"status": "error", "error": f"{type(e).__name__}: {e}"}
|
|
1524
|
+
print(json.dumps(_trim_defi_json(data), indent=2))
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
def cmd_summary(as_json: bool = False):
|
|
1528
|
+
"""Read-only Degen Account view: in-account collateral, debts, and live
|
|
1529
|
+
RedStone-gated solvency (getTotalValue/getDebt/getHealthRatio/isSolvent). Falls
|
|
1530
|
+
back to balances-only if the RedStone gateway is unreachable or a view reverts.
|
|
1531
|
+
Note: per-asset USD is best-effort - only symbols with a RedStone primary-prod
|
|
1532
|
+
feed are priced here. Symbols sourced on-chain from BaseOracle TWAP show as
|
|
1533
|
+
balance-only (the SolvencyFacet still values them for the total/debt figures).
|
|
1534
|
+
|
|
1535
|
+
With --json: emits a single JSON object covering wallet, account, native
|
|
1536
|
+
balance, per-asset supplied/borrowed with optional USD, poolDeposits (the EOA's
|
|
1537
|
+
'Diamond Hands' lending-pool balances, emitted even with no Degen Account),
|
|
1538
|
+
total/debt/health-ratio/solvent flags. Null fields, empty lists, and empty dicts are dropped (same
|
|
1539
|
+
trim contract as `deltaprime defi --json`). Numeric 0 and boolean false are
|
|
1540
|
+
preserved."""
|
|
1541
|
+
w3 = get_w3()
|
|
1542
|
+
acct = get_account()
|
|
1543
|
+
pa = get_prime_account(w3, acct.address)
|
|
1544
|
+
if not as_json:
|
|
1545
|
+
print(f"Wallet: {acct.address}")
|
|
1546
|
+
|
|
1547
|
+
pool_deposits = _gather_pool_deposits(w3, acct.address)
|
|
1548
|
+
|
|
1549
|
+
if not pa:
|
|
1550
|
+
# No Degen Account: still surface Diamond Hands deposits (balance-only — the
|
|
1551
|
+
# RedStone getPrices view lives on the Degen Account, absent here).
|
|
1552
|
+
if as_json:
|
|
1553
|
+
out = {"wallet": acct.address, "account": None}
|
|
1554
|
+
if pool_deposits:
|
|
1555
|
+
out["poolDeposits"] = [{"symbol": r["symbol"], "amount": r["raw"] / 10**r["decimals"]}
|
|
1556
|
+
for r in pool_deposits]
|
|
1557
|
+
print(json.dumps(out, indent=2))
|
|
1558
|
+
else:
|
|
1559
|
+
print("No Degen Account yet. Create one with: degenprime create-account --execute")
|
|
1560
|
+
if pool_deposits:
|
|
1561
|
+
print(" Pool Deposits (Diamond Hands):")
|
|
1562
|
+
for r in pool_deposits:
|
|
1563
|
+
print(f" {r['symbol']:<8} {r['raw'] / 10**r['decimals']:,.6f}")
|
|
1564
|
+
return
|
|
1565
|
+
|
|
1566
|
+
account = w3.eth.contract(address=Web3.to_checksum_address(pa), abi=PRIME_ACCOUNT_ABI)
|
|
1567
|
+
pa_eth, supplied, borrowed, solvency = _gather_account_state(w3, account, pool_deposits)
|
|
1568
|
+
if not as_json:
|
|
1569
|
+
print(f"Degen Account: {pa}")
|
|
1570
|
+
if pa_eth >= 1e-9:
|
|
1571
|
+
print(f" Native ETH (gas): {pa_eth:.6f}")
|
|
1452
1572
|
|
|
1453
1573
|
if as_json:
|
|
1454
1574
|
def _asset_row(r):
|
|
@@ -2315,6 +2435,8 @@ def _dispatch():
|
|
|
2315
2435
|
cmd_create_account("--execute" in args, fund_pool, fund_amount)
|
|
2316
2436
|
elif cmd == "summary":
|
|
2317
2437
|
cmd_summary(as_json="--json" in args)
|
|
2438
|
+
elif cmd == "defi":
|
|
2439
|
+
cmd_defi("--json" in args)
|
|
2318
2440
|
elif cmd == "fund":
|
|
2319
2441
|
pool, amount = None, None
|
|
2320
2442
|
execute = "--execute" in args
|
|
@@ -50,6 +50,13 @@ prime-summary reports live solvency (health ratio, total value, debt, solvent fl
|
|
|
50
50
|
SolvencyFacetProdAvalanche, read via eth_call with a RedStone price payload appended (falls
|
|
51
51
|
back to balances-only if the gateway is unreachable).
|
|
52
52
|
|
|
53
|
+
NOTE: prime-summary shows TWO health metrics — don't confuse them:
|
|
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
|
|
56
|
+
borrowing power used, 100% = no debt.
|
|
57
|
+
Formula for the latter: equity=supplied-debt, max_debt=equity*(tier-1),
|
|
58
|
+
pct=(max_debt-debt)/max_debt*100. tier=5 (BASIC) or 10 (PREMIUM).
|
|
59
|
+
|
|
53
60
|
Collateral withdrawal is a two-step, time-delayed flow on the Prime Account (there is NO
|
|
54
61
|
instant withdraw of in-account collateral). The savings-pool `withdraw` above is a separate
|
|
55
62
|
two-step intent flow on the pool itself, not the Prime Account. Step 1: `withdraw --pool X
|
|
@@ -658,33 +665,42 @@ def _tx_gas_price(w3) -> int:
|
|
|
658
665
|
|
|
659
666
|
def _set_gas_price(w3, tx_dict):
|
|
660
667
|
"""Set appropriate gas price fields for the chain, replacing the legacy gasPrice approach.
|
|
661
|
-
On EIP-1559 chains (Arbitrum, Base): sets maxFeePerGas +
|
|
662
|
-
base-fee hedge (base + prio + 1 gwei buffer).
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
668
|
+
On EIP-1559 chains (Arbitrum, Base, Avalanche post-Etna): sets maxFeePerGas +
|
|
669
|
+
maxPriorityFeePerGas with a 2x base-fee hedge (base + prio + 1 gwei buffer).
|
|
670
|
+
Falls back to legacy gasPrice only if the tx dict already lacks EIP-1559 fields
|
|
671
|
+
and the chain doesn't support max_priority_fee.
|
|
672
|
+
(25 gwei was the pre-Etna C-chain minimum; ACP-125 (Dec 2024) lowered the min base
|
|
673
|
+
fee to 1 nAVAX — base now sits at ~0.01 nAVAX, so a 25 gwei floor overpaid ~2500x
|
|
674
|
+
and inflated the upfront balance requirement past small EOAs.)"""
|
|
675
|
+
# If build_transaction already set EIP-1559 fields, don't touch them
|
|
676
|
+
if "maxFeePerGas" in tx_dict or "maxPriorityFeePerGas" in tx_dict:
|
|
677
|
+
tx_dict.pop("gasPrice", None)
|
|
678
|
+
return
|
|
667
679
|
tx_dict.pop("gasPrice", None)
|
|
668
|
-
|
|
680
|
+
try:
|
|
669
681
|
base = w3.eth.gas_price
|
|
670
682
|
prio = w3.eth.max_priority_fee
|
|
671
683
|
tx_dict["maxFeePerGas"] = max(int(base * 2), base + prio + 10**9)
|
|
672
684
|
tx_dict["maxPriorityFeePerGas"] = prio
|
|
673
|
-
|
|
685
|
+
except Exception:
|
|
686
|
+
# Legacy chain — use gasPrice instead
|
|
674
687
|
tx_dict["gasPrice"] = max(int(w3.eth.gas_price * 2), 1 * 10**9)
|
|
675
688
|
|
|
676
689
|
def _set_gas_price_for(chain_id, w3, tx_dict):
|
|
677
690
|
"""Set gas fields for an EXPLICIT chain_id rather than the module CHAIN_ID. Needed by
|
|
678
691
|
cross-chain flows (prime-bridge) where a tx may target Avalanche or Arbitrum regardless
|
|
679
|
-
of which tool built it.
|
|
680
|
-
|
|
692
|
+
of which tool built it."""
|
|
693
|
+
# If build_transaction already set EIP-1559 fields, don't touch them
|
|
694
|
+
if "maxFeePerGas" in tx_dict or "maxPriorityFeePerGas" in tx_dict:
|
|
695
|
+
tx_dict.pop("gasPrice", None)
|
|
696
|
+
return
|
|
681
697
|
tx_dict.pop("gasPrice", None)
|
|
682
|
-
|
|
698
|
+
try:
|
|
683
699
|
base = w3.eth.gas_price
|
|
684
700
|
prio = w3.eth.max_priority_fee
|
|
685
701
|
tx_dict["maxFeePerGas"] = max(int(base * 2), base + prio + 10**9)
|
|
686
702
|
tx_dict["maxPriorityFeePerGas"] = prio
|
|
687
|
-
|
|
703
|
+
except Exception:
|
|
688
704
|
tx_dict["gasPrice"] = max(int(w3.eth.gas_price * 2), 1 * 10**9)
|
|
689
705
|
|
|
690
706
|
def _read_env_var(path, var):
|
|
@@ -1979,6 +1995,49 @@ def gather_lending(w3, account):
|
|
|
1979
1995
|
r["usd"] = None
|
|
1980
1996
|
return out
|
|
1981
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.
|
|
2000
|
+
|
|
2001
|
+
DeltaPrime has *two* health metrics that agents must not confuse:
|
|
2002
|
+
|
|
2003
|
+
1. health_ratio (on-chain, getHealthRatio): 1.0 = liquidation, >1.0 = solvent.
|
|
2004
|
+
This is the raw weighted-collateral / debt ratio from the SolvencyFacet.
|
|
2005
|
+
|
|
2006
|
+
2. bruno_pct (equity-based, 0-100%): the scale used in the DeltaPrime frontend
|
|
2007
|
+
and the account-health-monitor cron. 0% = liquidation, 100% = no debt.
|
|
2008
|
+
Formula:
|
|
2009
|
+
equity = supplied_usd - debt_usd
|
|
2010
|
+
max_mult = 10 if PREMIUM tier else 5 if BASIC
|
|
2011
|
+
max_debt = equity * (max_mult - 1)
|
|
2012
|
+
bruno_pct = (max_debt - debt_usd) / max_debt * 100
|
|
2013
|
+
|
|
2014
|
+
Returns dict with keys: bruno_pct, supplied_usd, debt_usd, equity, max_debt,
|
|
2015
|
+
tier_label, or error.
|
|
2016
|
+
"""
|
|
2017
|
+
supplied_usd = sum(r.get("usd", 0) or 0 for r in data.get("supplied", []))
|
|
2018
|
+
debt_usd = sum(r.get("usd", 0) or 0 for r in data.get("borrowed", []))
|
|
2019
|
+
equity = supplied_usd - debt_usd
|
|
2020
|
+
tier_labels = {0: "BASIC", 1: "PREMIUM", 2: "_NON_EXISTENT"}
|
|
2021
|
+
tier_label = tier_labels.get(tier_code, str(tier_code))
|
|
2022
|
+
max_mult = {0: 5, 1: 10}.get(tier_code, 5)
|
|
2023
|
+
|
|
2024
|
+
if equity <= 0.01:
|
|
2025
|
+
return {"bruno_pct": 0.0, "supplied_usd": round(supplied_usd, 2),
|
|
2026
|
+
"debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
|
|
2027
|
+
"max_debt": 0.0, "tier": tier_label, "error": "equity near zero"}
|
|
2028
|
+
|
|
2029
|
+
max_debt = equity * (max_mult - 1)
|
|
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))
|
|
2033
|
+
else:
|
|
2034
|
+
bruno_pct = 100.0
|
|
2035
|
+
|
|
2036
|
+
return {"bruno_pct": round(bruno_pct, 1), "supplied_usd": round(supplied_usd, 2),
|
|
2037
|
+
"debt_usd": round(debt_usd, 2), "equity": round(equity, 2),
|
|
2038
|
+
"max_debt": round(max_debt, 2), "tier": tier_label}
|
|
2039
|
+
|
|
2040
|
+
|
|
1982
2041
|
def cmd_prime_summary():
|
|
1983
2042
|
w3 = get_w3()
|
|
1984
2043
|
acct = get_account()
|
|
@@ -2022,7 +2081,23 @@ def cmd_prime_summary():
|
|
|
2022
2081
|
# gather_lending nulls the ratio when debt is negligible (the raw value is
|
|
2023
2082
|
# astronomically large there); render that as ">1000" rather than a junk number.
|
|
2024
2083
|
ratio_str = ">1000.00 (negligible debt)" if ratio is None else f"{ratio:.4f}"
|
|
2025
|
-
print(f" Health ratio:
|
|
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.
|
|
2087
|
+
# Get tier from the Prime Account (oracle-free view)
|
|
2088
|
+
try:
|
|
2089
|
+
tier_info = gather_prime_tier(w3, acct, account)
|
|
2090
|
+
tier_code = tier_info.get("tier_code", 0)
|
|
2091
|
+
except Exception:
|
|
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']})")
|
|
2098
|
+
print(f" 0%=liquidation 50%=half borrowing power used 100%=no debt")
|
|
2099
|
+
else:
|
|
2100
|
+
print(f" Health (Bruno 0-100%): N/A ({bh['error']})")
|
|
2026
2101
|
print(f" Solvent: {'yes' if data['solvent'] else 'NO — liquidatable'}")
|
|
2027
2102
|
else:
|
|
2028
2103
|
print(f" Health/solvency: RedStone fetch/call failed ({data.get('solvency_error', 'error')}); "
|
|
@@ -4928,9 +5003,12 @@ def gather_defi() -> dict:
|
|
|
4928
5003
|
result["total_usd"] = lending["total_value_usd"]
|
|
4929
5004
|
result["health_ratio"] = lending["health_ratio"]
|
|
4930
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")
|
|
4931
5008
|
if lending["supplied"] or lending["borrowed"]:
|
|
4932
5009
|
result["groups"].append({
|
|
4933
5010
|
"type": "Lending / Leverage", "health_ratio": lending["health_ratio"],
|
|
5011
|
+
"bruno_pct": result["bruno_pct"],
|
|
4934
5012
|
"supplied": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
|
|
4935
5013
|
for r in lending["supplied"]],
|
|
4936
5014
|
"borrowed": [{"symbol": r["symbol"], "balance": r["balance"], "usd": r.get("usd")}
|
|
@@ -39,11 +39,19 @@ TIER_MAX = {"basic": 5, "premium": 10}
|
|
|
39
39
|
def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
|
|
40
40
|
"""Compute Bruno's 0-100% health scale 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.
|
|
44
|
+
|
|
42
45
|
Formula:
|
|
43
46
|
equity = total_supplied_usd - total_debt_usd
|
|
44
47
|
max_debt = equity * (tier - 1) # PREMIUM=10, BASIC=5
|
|
45
48
|
health% = (max_debt - debt) / max_debt * 100
|
|
46
49
|
|
|
50
|
+
This is DIFFERENT from getHealthRatio (the on-chain ratio where 1.0 = liquidation).
|
|
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
|
|
53
|
+
measurement (0%=liquidation, 50%=half borrowing power used, 100%=no debt).
|
|
54
|
+
|
|
47
55
|
Returns dict with health metrics or error.
|
|
48
56
|
"""
|
|
49
57
|
# Parse groups (DeltaPrime format) or flat format (DegenPrime)
|
|
@@ -53,6 +61,31 @@ def compute_health(defi_data: dict, max_mult: int = 10) -> dict:
|
|
|
53
61
|
supplied = g.get("supplied", [])
|
|
54
62
|
borrowed = g.get("borrowed", [])
|
|
55
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")
|
|
66
|
+
if precomputed is not None:
|
|
67
|
+
# Early return: precomputed value exists, enrich with detail fields
|
|
68
|
+
supplied_usd = sum(s.get("usd", 0) or 0 for s in supplied)
|
|
69
|
+
debt_usd = sum(b.get("usd", 0) or 0 for b in borrowed)
|
|
70
|
+
equity = supplied_usd - debt_usd
|
|
71
|
+
raw_usdc = sum(s.get("usd", 0) for s in supplied if s.get("symbol") == "USDC")
|
|
72
|
+
symbols = [s.get("symbol", "") for s in supplied]
|
|
73
|
+
has_gmx = any("GM_" in sym for sym in symbols)
|
|
74
|
+
has_lb = any(sym in ("LB_AVAX_USDC", "LB_WAVAX_USDC", "JOE") or "TRADERJOE" in sym.upper() for sym in symbols)
|
|
75
|
+
has_aero = any("AERO" in sym.upper() or "CL_POSITION" in sym.upper() for sym in symbols)
|
|
76
|
+
return {
|
|
77
|
+
"bruno_pct": float(precomputed),
|
|
78
|
+
"health_ratio": round(health_ratio, 4),
|
|
79
|
+
"supplied_usd": round(supplied_usd, 2),
|
|
80
|
+
"debt_usd": round(debt_usd, 2),
|
|
81
|
+
"equity": round(equity, 2),
|
|
82
|
+
"max_debt": round(max(0, equity * (max_mult - 1)), 2),
|
|
83
|
+
"raw_usdc": round(raw_usdc, 2),
|
|
84
|
+
"has_gmx": has_gmx,
|
|
85
|
+
"has_lb": has_lb,
|
|
86
|
+
"has_aero": has_aero,
|
|
87
|
+
"action": "computed from defi --json bruno_pct",
|
|
88
|
+
}
|
|
56
89
|
else:
|
|
57
90
|
supplied = defi_data.get("supplied", [])
|
|
58
91
|
borrowed = defi_data.get("borrowed", [])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: primecli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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
|
|
@@ -28,7 +28,6 @@ Requires-Dist: eth-account>=0.13
|
|
|
28
28
|
Requires-Dist: eth-keys>=0.5
|
|
29
29
|
Requires-Dist: eth-abi<7,>=5.0
|
|
30
30
|
Requires-Dist: requests>=2.31
|
|
31
|
-
Dynamic: license-file
|
|
32
31
|
|
|
33
32
|
# primecli
|
|
34
33
|
|
|
@@ -48,7 +47,7 @@ Built for agent use:
|
|
|
48
47
|
- RedStone-signed solvency math handled internally, with a regression test pinning the half-boundary `toFixed(8)` encoding.
|
|
49
48
|
- ParaSwap calldata validated client-side against the on-chain executor allowlist before broadcast.
|
|
50
49
|
|
|
51
|
-
**Current version:** 0.5.
|
|
50
|
+
**Current version:** 0.5.2 The 0.x line is pre-1.0, so breaking changes are possible. See [Releases](https://github.com/Mnemosyne-quest/primecli/releases).
|
|
52
51
|
|
|
53
52
|
> **Breaking change in 0.5.0:** there is no longer a default signing key. Earlier versions silently fell back to a baked-in agent when no key was configured; that fallback has been removed. With no key configured, every command now fails closed with `No signing key found...`. Set a key explicitly (see [Configuration](#configuration)).
|
|
54
53
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=68", "wheel"]
|
|
2
|
+
requires = ["setuptools>=68,<73", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "primecli"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.3"
|
|
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
|