primecli 0.4.0__tar.gz → 0.5.0__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.4.0 → primecli-0.5.0}/PKG-INFO +25 -7
- {primecli-0.4.0 → primecli-0.5.0}/README.md +23 -5
- {primecli-0.4.0 → primecli-0.5.0}/primecli/arbprime.py +86 -40
- {primecli-0.4.0 → primecli-0.5.0}/primecli/degenprime.py +14 -10
- {primecli-0.4.0 → primecli-0.5.0}/primecli/deltaprime.py +86 -42
- {primecli-0.4.0 → primecli-0.5.0}/primecli/health_monitor.py +49 -0
- {primecli-0.4.0 → primecli-0.5.0}/primecli.egg-info/PKG-INFO +25 -7
- {primecli-0.4.0 → primecli-0.5.0}/primecli.egg-info/SOURCES.txt +6 -2
- {primecli-0.4.0 → primecli-0.5.0}/primecli.egg-info/requires.txt +1 -1
- {primecli-0.4.0 → primecli-0.5.0}/pyproject.toml +2 -2
- primecli-0.5.0/tests/test_cross_file_identity.py +183 -0
- primecli-0.5.0/tests/test_gas_pricing.py +105 -0
- primecli-0.5.0/tests/test_health_monitor.py +340 -0
- primecli-0.5.0/tests/test_paraswap_validator.py +160 -0
- primecli-0.5.0/tests/test_redstone_encoding.py +71 -0
- primecli-0.5.0/tests/test_to_wei_units.py +40 -0
- primecli-0.4.0/primecli/prime-bridge.py +0 -299
- primecli-0.4.0/tests/test_redstone_encoding.py +0 -110
- {primecli-0.4.0 → primecli-0.5.0}/LICENSE +0 -0
- {primecli-0.4.0 → primecli-0.5.0}/primecli/__init__.py +0 -0
- {primecli-0.4.0 → primecli-0.5.0}/primecli.egg-info/dependency_links.txt +0 -0
- {primecli-0.4.0 → primecli-0.5.0}/primecli.egg-info/entry_points.txt +0 -0
- {primecli-0.4.0 → primecli-0.5.0}/primecli.egg-info/top_level.txt +0 -0
- {primecli-0.4.0 → primecli-0.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: primecli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
|
@@ -26,7 +26,7 @@ License-File: LICENSE
|
|
|
26
26
|
Requires-Dist: web3<8,>=7.0
|
|
27
27
|
Requires-Dist: eth-account>=0.13
|
|
28
28
|
Requires-Dist: eth-keys>=0.5
|
|
29
|
-
Requires-Dist: eth-abi
|
|
29
|
+
Requires-Dist: eth-abi<7,>=5.0
|
|
30
30
|
Requires-Dist: requests>=2.31
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
@@ -48,13 +48,15 @@ Built for agent use:
|
|
|
48
48
|
- RedStone-signed solvency math handled internally, with a regression test pinning the half-boundary `toFixed(8)` encoding.
|
|
49
49
|
- ParaSwap calldata validated client-side against the on-chain executor allowlist before broadcast.
|
|
50
50
|
|
|
51
|
-
**Current version:** 0.
|
|
51
|
+
**Current version:** 0.5.0 The 0.x line is pre-1.0, so breaking changes are possible. See [Releases](https://github.com/Mnemosyne-quest/primecli/releases).
|
|
52
|
+
|
|
53
|
+
> **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)).
|
|
52
54
|
|
|
53
55
|
## Security and trust
|
|
54
56
|
|
|
55
57
|
**This tool moves real on-chain funds.** Read before using.
|
|
56
58
|
|
|
57
|
-
- You manage your own private key. The tool reads it from `
|
|
59
|
+
- You manage your own private key. The tool reads it from `<TOOL>_PRIVATE_KEY` (e.g. `DELTAPRIME_PRIVATE_KEY`), or a file path you point at via `<TOOL>_KEY_FILE`, or a one-shot `--key` flag. It never writes the key anywhere. There is no default key: with nothing configured, commands fail closed.
|
|
58
60
|
- Every state-changing command **previews by default**. You must pass `--execute` to broadcast. Don't pass `--execute` until you have read the preview and understand what it is about to do.
|
|
59
61
|
- The RedStone payload, ParaSwap executor allowlist, and facet ABIs are pinned to specific on-chain state at the dates noted in the source. If DeltaPrime or DegenPrime upgrade their diamonds, the tool may need updating. Open an issue.
|
|
60
62
|
- The DeltaPrimeLabs team is not affiliated with this project. This is community-maintained tooling.
|
|
@@ -123,7 +125,7 @@ State-changing commands preview by default. Add `--execute` to broadcast.
|
|
|
123
125
|
|-------|----------|
|
|
124
126
|
| Lending core | `pool-info [--json]`, `my-positions`, `deposit`, `withdraw` (24h delayed lender flow, step 1), `withdrawal-requests`, `execute-withdrawal-request --pool X [--index N]`, `cancel-withdrawal-request --pool X --index N`, `borrow`, `repay`, `fund` |
|
|
125
127
|
| Prime Account | `create-prime-account` (alias `create-account`), `prime-summary`, `defi --json`, `withdraw-collateral`, `withdrawal-intents`, `execute-withdrawal` |
|
|
126
|
-
| Swaps | `swap --from S --to S --amount N [--via yak\|paraswap] [--slippage P]` (
|
|
128
|
+
| Swaps | `swap --from S --to S --amount N [--via yak\|paraswap] [--slippage P]` (`--via yak` default; `--via paraswap` validates the API calldata and patches a non-whitelisted executor to a known-good one before broadcast), `swap-debt --from S --to S --amount N [--slippage P]` (same ParaSwap executor handling) |
|
|
127
129
|
| GMX V2 LP (async, keeper-executed) | `gmx-positions`, `gmx-deposit --market M --amount N [--side long\|short]`, `gmx-withdraw --market M --amount N` |
|
|
128
130
|
| TraderJoe V2 LB | `lb-positions`, `lb-add --pair P --amount-x N --amount-y N [--shape spot\|curve\|bidask] [--range R]`, `lb-remove --pair P` |
|
|
129
131
|
| sJOE staking | `sjoe-position`, `sjoe-stake --amount N`, `sjoe-unstake --amount N`, `sjoe-claim` |
|
|
@@ -181,11 +183,14 @@ Note: DeltaPrime has TWO deployments on Arbitrum; `arbprime` targets the live on
|
|
|
181
183
|
| `DEGENPRIME_KEY_FILE` | falls back to `DELTAPRIME_KEY_FILE` | Path to key file for Base. |
|
|
182
184
|
| `DEGENPRIME_RPC` | `https://base.publicnode.com` | Base RPC. |
|
|
183
185
|
| `ARBPRIME_PRIVATE_KEY` | falls back to `DELTAPRIME_PRIVATE_KEY` | Your Arbitrum signing key. Same EVM key works on all three chains. |
|
|
186
|
+
| `ARBPRIME_KEY_FILE` | falls back to `DELTAPRIME_KEY_FILE` | Path to key file for Arbitrum. |
|
|
184
187
|
| `ARBPRIME_AGENT` | falls back to `DELTAPRIME_AGENT` | Named-agent key selection (multi-wallet setups). |
|
|
185
188
|
| `ARBPRIME_RPC` | `https://arb1.arbitrum.io/rpc` | Arbitrum One RPC. |
|
|
186
189
|
|
|
187
190
|
The CLI also accepts a per-command `--key <0xhex>` override that takes precedence over all env vars. Handy for one-off operations from a shell where you don't want to persist the key.
|
|
188
191
|
|
|
192
|
+
**Key resolution.** `deltaprime` and `arbprime` resolve the signing key in this order (first hit wins): `--key` > `--as <agent>` > `<TOOL>_PRIVATE_KEY` > `<TOOL>_KEY_FILE` > `<TOOL>_ENV_FILE` + `<TOOL>_KEY_VAR` > `<TOOL>_AGENT` env > error. `arbprime`'s `ARBPRIME_*` vars each fall back to the `DELTAPRIME_*` equivalent. `degenprime` is simpler — `--key` > `DEGENPRIME_PRIVATE_KEY` > `DEGENPRIME_KEY_FILE`, with `DELTAPRIME_PRIVATE_KEY` / `DELTAPRIME_KEY_FILE` as fallbacks (it has no `--as` / agent-table mechanism). If none resolve, the command exits 1 with `No signing key found...`.
|
|
193
|
+
|
|
189
194
|
A copy-paste template is at [examples/env.example](examples/env.example).
|
|
190
195
|
|
|
191
196
|
## What's covered
|
|
@@ -239,6 +244,19 @@ A copy-paste template is at [examples/env.example](examples/env.example).
|
|
|
239
244
|
| Leveraged-long zap macro | full (GM-terminal) |
|
|
240
245
|
| Penpie / Beefy / Sushi facets | not yet (live on-chain; deferred by scope) |
|
|
241
246
|
|
|
247
|
+
### PRIME bridge (`deltaprime` and `arbprime`)
|
|
248
|
+
|
|
249
|
+
Both tools expose a `prime-bridge` subcommand that moves the PRIME token between Avalanche and Arbitrum over LayerZero's OFT:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
deltaprime prime-bridge --from arb --amount 100 [--execute] # Arbitrum -> Avalanche
|
|
253
|
+
arbprime prime-bridge --from avax --amount 100 [--execute] # Avalanche -> Arbitrum
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`--from` takes `avax` or `arb` (the source chain; destination is the other one). The default `--from` differs per tool — `arb` for `deltaprime`, `avax` for `arbprime` — so always pass it explicitly. Without `--execute` it previews the LayerZero native fee, balance, and the two steps (ERC-20 approve, then `sendFrom()`); with `--execute` it broadcasts both. Gas price and `chainId` are set per the source chain.
|
|
257
|
+
|
|
258
|
+
The standalone `prime-bridge.py` script that shipped earlier was removed in 0.5.0 — the subcommand is the only supported entry point.
|
|
259
|
+
|
|
242
260
|
## Documentation
|
|
243
261
|
|
|
244
262
|
- [DeltaPrime reference](docs/deltaprime-reference.md): protocol model, addresses, facet map, RedStone integration, full command table, GMX / LB / PRIME flows.
|
|
@@ -253,7 +271,7 @@ A copy-paste template is at [examples/env.example](examples/env.example).
|
|
|
253
271
|
|
|
254
272
|
1. **Preview by default.** Every state-changing command prints a structured preview and stops unless `--execute` is passed. An agent can call any command speculatively, parse the preview, decide whether to broadcast, then re-run with `--execute`.
|
|
255
273
|
2. **Predictable, parseable stdout.** Read-only commands (`pool-info` (also `--json`), `my-positions`, `prime-summary`, `summary`, `withdrawal-intents`, `lb-positions`, `gmx-positions`, `aerodrome-positions`, `sjoe-position`, `prime-tier`, `defi --json`) emit fixed-format tables or JSON. `defi --json` is a one-shot full positions snapshot.
|
|
256
|
-
3. **Clean failure modes.** Configuration errors do not print stack traces. A missing key prints `deltaprime: No signing key found.
|
|
274
|
+
3. **Clean failure modes.** Configuration errors do not print stack traces. A missing key prints `deltaprime: No signing key found. Pass --key <0xhex> or --as <agent>, or set DELTAPRIME_PRIVATE_KEY ...` to stderr and exits 1.
|
|
257
275
|
|
|
258
276
|
Full agent integration guide (Claude Code skill template, MCP notes, recommended guardrails): [docs/agent-integration.md](docs/agent-integration.md).
|
|
259
277
|
|
|
@@ -280,7 +298,7 @@ Common failure modes and their fixes:
|
|
|
280
298
|
- **`RedStone gateway unreachable` on a read.** `prime-summary` / `summary` fall back to balances-only when the RedStone gateway is down. On `--execute` of a solvency-gated write, the call cannot proceed without a payload; wait and retry, or try the alternate gateway via the env override.
|
|
281
299
|
- **Swap fails on-chain with `InvalidExecutor`.** ParaSwap rotated an executor that is not in the tool's mirror of the on-chain allowlist. The tool patches to a known-good fallback automatically; if reverts persist, the on-chain allowlist itself has likely rotated. Open an issue.
|
|
282
300
|
- **GMX deposit reverts `InsufficientNumberOfUniqueSigners(0,3)`.** A required RedStone feed was missing from the appended payload. This was the load-bearing fix on the GMX path (24-05-2026). If you hit it on a current build, capture the tx hash and open an issue.
|
|
283
|
-
- **GMX deposit accepted but no GM minted.** The execution fee was below the keeper's threshold and the request expired (refund without mint). Re-run; the tool floors gas at
|
|
301
|
+
- **GMX deposit accepted but no GM minted.** The execution fee was below the keeper's threshold and the request expired (refund without mint). Re-run; the tool floors gas at 1 gwei (and pads 2×) in the fee estimator to clear the keeper's bar.
|
|
284
302
|
- **`createLoan` succeeded but `getLoansForOwner` returns empty.** The factory's owner→loans map lags a beat behind the receipt. The tool polls for up to 12s; rerun `my-positions` shortly after if it timed out.
|
|
285
303
|
|
|
286
304
|
If your failure is not on this list and the on-chain revert reason is opaque, capture the tx hash, the exact CLI invocation, and the preview output, and file an issue.
|
|
@@ -16,13 +16,15 @@ 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.
|
|
19
|
+
**Current version:** 0.5.0 The 0.x line is pre-1.0, so breaking changes are possible. See [Releases](https://github.com/Mnemosyne-quest/primecli/releases).
|
|
20
|
+
|
|
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)).
|
|
20
22
|
|
|
21
23
|
## Security and trust
|
|
22
24
|
|
|
23
25
|
**This tool moves real on-chain funds.** Read before using.
|
|
24
26
|
|
|
25
|
-
- You manage your own private key. The tool reads it from `
|
|
27
|
+
- You manage your own private key. The tool reads it from `<TOOL>_PRIVATE_KEY` (e.g. `DELTAPRIME_PRIVATE_KEY`), or a file path you point at via `<TOOL>_KEY_FILE`, or a one-shot `--key` flag. It never writes the key anywhere. There is no default key: with nothing configured, commands fail closed.
|
|
26
28
|
- Every state-changing command **previews by default**. You must pass `--execute` to broadcast. Don't pass `--execute` until you have read the preview and understand what it is about to do.
|
|
27
29
|
- The RedStone payload, ParaSwap executor allowlist, and facet ABIs are pinned to specific on-chain state at the dates noted in the source. If DeltaPrime or DegenPrime upgrade their diamonds, the tool may need updating. Open an issue.
|
|
28
30
|
- The DeltaPrimeLabs team is not affiliated with this project. This is community-maintained tooling.
|
|
@@ -91,7 +93,7 @@ State-changing commands preview by default. Add `--execute` to broadcast.
|
|
|
91
93
|
|-------|----------|
|
|
92
94
|
| Lending core | `pool-info [--json]`, `my-positions`, `deposit`, `withdraw` (24h delayed lender flow, step 1), `withdrawal-requests`, `execute-withdrawal-request --pool X [--index N]`, `cancel-withdrawal-request --pool X --index N`, `borrow`, `repay`, `fund` |
|
|
93
95
|
| Prime Account | `create-prime-account` (alias `create-account`), `prime-summary`, `defi --json`, `withdraw-collateral`, `withdrawal-intents`, `execute-withdrawal` |
|
|
94
|
-
| Swaps | `swap --from S --to S --amount N [--via yak\|paraswap] [--slippage P]` (
|
|
96
|
+
| Swaps | `swap --from S --to S --amount N [--via yak\|paraswap] [--slippage P]` (`--via yak` default; `--via paraswap` validates the API calldata and patches a non-whitelisted executor to a known-good one before broadcast), `swap-debt --from S --to S --amount N [--slippage P]` (same ParaSwap executor handling) |
|
|
95
97
|
| GMX V2 LP (async, keeper-executed) | `gmx-positions`, `gmx-deposit --market M --amount N [--side long\|short]`, `gmx-withdraw --market M --amount N` |
|
|
96
98
|
| TraderJoe V2 LB | `lb-positions`, `lb-add --pair P --amount-x N --amount-y N [--shape spot\|curve\|bidask] [--range R]`, `lb-remove --pair P` |
|
|
97
99
|
| sJOE staking | `sjoe-position`, `sjoe-stake --amount N`, `sjoe-unstake --amount N`, `sjoe-claim` |
|
|
@@ -149,11 +151,14 @@ Note: DeltaPrime has TWO deployments on Arbitrum; `arbprime` targets the live on
|
|
|
149
151
|
| `DEGENPRIME_KEY_FILE` | falls back to `DELTAPRIME_KEY_FILE` | Path to key file for Base. |
|
|
150
152
|
| `DEGENPRIME_RPC` | `https://base.publicnode.com` | Base RPC. |
|
|
151
153
|
| `ARBPRIME_PRIVATE_KEY` | falls back to `DELTAPRIME_PRIVATE_KEY` | Your Arbitrum signing key. Same EVM key works on all three chains. |
|
|
154
|
+
| `ARBPRIME_KEY_FILE` | falls back to `DELTAPRIME_KEY_FILE` | Path to key file for Arbitrum. |
|
|
152
155
|
| `ARBPRIME_AGENT` | falls back to `DELTAPRIME_AGENT` | Named-agent key selection (multi-wallet setups). |
|
|
153
156
|
| `ARBPRIME_RPC` | `https://arb1.arbitrum.io/rpc` | Arbitrum One RPC. |
|
|
154
157
|
|
|
155
158
|
The CLI also accepts a per-command `--key <0xhex>` override that takes precedence over all env vars. Handy for one-off operations from a shell where you don't want to persist the key.
|
|
156
159
|
|
|
160
|
+
**Key resolution.** `deltaprime` and `arbprime` resolve the signing key in this order (first hit wins): `--key` > `--as <agent>` > `<TOOL>_PRIVATE_KEY` > `<TOOL>_KEY_FILE` > `<TOOL>_ENV_FILE` + `<TOOL>_KEY_VAR` > `<TOOL>_AGENT` env > error. `arbprime`'s `ARBPRIME_*` vars each fall back to the `DELTAPRIME_*` equivalent. `degenprime` is simpler — `--key` > `DEGENPRIME_PRIVATE_KEY` > `DEGENPRIME_KEY_FILE`, with `DELTAPRIME_PRIVATE_KEY` / `DELTAPRIME_KEY_FILE` as fallbacks (it has no `--as` / agent-table mechanism). If none resolve, the command exits 1 with `No signing key found...`.
|
|
161
|
+
|
|
157
162
|
A copy-paste template is at [examples/env.example](examples/env.example).
|
|
158
163
|
|
|
159
164
|
## What's covered
|
|
@@ -207,6 +212,19 @@ A copy-paste template is at [examples/env.example](examples/env.example).
|
|
|
207
212
|
| Leveraged-long zap macro | full (GM-terminal) |
|
|
208
213
|
| Penpie / Beefy / Sushi facets | not yet (live on-chain; deferred by scope) |
|
|
209
214
|
|
|
215
|
+
### PRIME bridge (`deltaprime` and `arbprime`)
|
|
216
|
+
|
|
217
|
+
Both tools expose a `prime-bridge` subcommand that moves the PRIME token between Avalanche and Arbitrum over LayerZero's OFT:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
deltaprime prime-bridge --from arb --amount 100 [--execute] # Arbitrum -> Avalanche
|
|
221
|
+
arbprime prime-bridge --from avax --amount 100 [--execute] # Avalanche -> Arbitrum
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`--from` takes `avax` or `arb` (the source chain; destination is the other one). The default `--from` differs per tool — `arb` for `deltaprime`, `avax` for `arbprime` — so always pass it explicitly. Without `--execute` it previews the LayerZero native fee, balance, and the two steps (ERC-20 approve, then `sendFrom()`); with `--execute` it broadcasts both. Gas price and `chainId` are set per the source chain.
|
|
225
|
+
|
|
226
|
+
The standalone `prime-bridge.py` script that shipped earlier was removed in 0.5.0 — the subcommand is the only supported entry point.
|
|
227
|
+
|
|
210
228
|
## Documentation
|
|
211
229
|
|
|
212
230
|
- [DeltaPrime reference](docs/deltaprime-reference.md): protocol model, addresses, facet map, RedStone integration, full command table, GMX / LB / PRIME flows.
|
|
@@ -221,7 +239,7 @@ A copy-paste template is at [examples/env.example](examples/env.example).
|
|
|
221
239
|
|
|
222
240
|
1. **Preview by default.** Every state-changing command prints a structured preview and stops unless `--execute` is passed. An agent can call any command speculatively, parse the preview, decide whether to broadcast, then re-run with `--execute`.
|
|
223
241
|
2. **Predictable, parseable stdout.** Read-only commands (`pool-info` (also `--json`), `my-positions`, `prime-summary`, `summary`, `withdrawal-intents`, `lb-positions`, `gmx-positions`, `aerodrome-positions`, `sjoe-position`, `prime-tier`, `defi --json`) emit fixed-format tables or JSON. `defi --json` is a one-shot full positions snapshot.
|
|
224
|
-
3. **Clean failure modes.** Configuration errors do not print stack traces. A missing key prints `deltaprime: No signing key found.
|
|
242
|
+
3. **Clean failure modes.** Configuration errors do not print stack traces. A missing key prints `deltaprime: No signing key found. Pass --key <0xhex> or --as <agent>, or set DELTAPRIME_PRIVATE_KEY ...` to stderr and exits 1.
|
|
225
243
|
|
|
226
244
|
Full agent integration guide (Claude Code skill template, MCP notes, recommended guardrails): [docs/agent-integration.md](docs/agent-integration.md).
|
|
227
245
|
|
|
@@ -248,7 +266,7 @@ Common failure modes and their fixes:
|
|
|
248
266
|
- **`RedStone gateway unreachable` on a read.** `prime-summary` / `summary` fall back to balances-only when the RedStone gateway is down. On `--execute` of a solvency-gated write, the call cannot proceed without a payload; wait and retry, or try the alternate gateway via the env override.
|
|
249
267
|
- **Swap fails on-chain with `InvalidExecutor`.** ParaSwap rotated an executor that is not in the tool's mirror of the on-chain allowlist. The tool patches to a known-good fallback automatically; if reverts persist, the on-chain allowlist itself has likely rotated. Open an issue.
|
|
250
268
|
- **GMX deposit reverts `InsufficientNumberOfUniqueSigners(0,3)`.** A required RedStone feed was missing from the appended payload. This was the load-bearing fix on the GMX path (24-05-2026). If you hit it on a current build, capture the tx hash and open an issue.
|
|
251
|
-
- **GMX deposit accepted but no GM minted.** The execution fee was below the keeper's threshold and the request expired (refund without mint). Re-run; the tool floors gas at
|
|
269
|
+
- **GMX deposit accepted but no GM minted.** The execution fee was below the keeper's threshold and the request expired (refund without mint). Re-run; the tool floors gas at 1 gwei (and pads 2×) in the fee estimator to clear the keeper's bar.
|
|
252
270
|
- **`createLoan` succeeded but `getLoansForOwner` returns empty.** The factory's owner→loans map lags a beat behind the receipt. The tool polls for up to 12s; rerun `my-positions` shortly after if it timed out.
|
|
253
271
|
|
|
254
272
|
If your failure is not on this list and the on-chain revert reason is opaque, capture the tx hash, the exact CLI invocation, and the preview output, and file an issue.
|
|
@@ -234,19 +234,21 @@ ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
|
|
|
234
234
|
# every chain, so the ARBPRIME_ env vars fall back to the DELTAPRIME_ equivalents.
|
|
235
235
|
#
|
|
236
236
|
# Key resolution order (first hit wins; see resolve_private_key):
|
|
237
|
-
# 1. --
|
|
238
|
-
# 2.
|
|
239
|
-
# 3.
|
|
240
|
-
# 4.
|
|
241
|
-
# 5.
|
|
237
|
+
# 1. --key <0xhex> CLI flag -> raw 0x… key (one-off)
|
|
238
|
+
# 2. --as <agent> CLI flag -> AGENTS[<agent>]
|
|
239
|
+
# 3. ARBPRIME_PRIVATE_KEY / DELTAPRIME_PRIVATE_KEY env var -> raw 0x… key
|
|
240
|
+
# 4. ARBPRIME_KEY_FILE / DELTAPRIME_KEY_FILE env var -> path to a file with the 0x key
|
|
241
|
+
# 5. ARBPRIME_ENV_FILE+ARBPRIME_KEY_VAR / DELTAPRIME_* equivalent -> read <var> from <file>
|
|
242
|
+
# 6. ARBPRIME_AGENT / DELTAPRIME_AGENT env var -> AGENTS[<agent>]
|
|
243
|
+
# If none resolve, fail closed (no silent default key).
|
|
242
244
|
#
|
|
243
|
-
# To add another wallet: add a row to AGENTS, or
|
|
245
|
+
# To add another wallet: add a row to AGENTS, export ARBPRIME_PRIVATE_KEY, or pass --key.
|
|
244
246
|
AGENTS = {
|
|
245
247
|
"parakletos": ("/root/.openclaw/.env", "PARAKLETOS_EVM_PRIVATE_KEY"),
|
|
246
248
|
"paraklaudios": ("/root/paraklaudios/.credentials.env", "PARAKLAUDIOS_EVM_PRIVATE_KEY"),
|
|
247
249
|
}
|
|
248
|
-
DEFAULT_AGENT = "parakletos" # preserves original behavior when nothing else is set
|
|
249
250
|
_SELECTED_AGENT = None # set by the --as CLI flag in main()
|
|
251
|
+
_CLI_KEY = None # set by the --key CLI flag in main()
|
|
250
252
|
# Core protocol addresses — the LIVE Arbitrum deployment (DeploymentConstants.sol),
|
|
251
253
|
# on-chain verified 2026-06-03. The stale *TUP.json deployment (factory 0x97f4C81…)
|
|
252
254
|
# has only ETH+USDC pools — NOT used here.
|
|
@@ -667,6 +669,21 @@ def _set_gas_price(w3, tx_dict):
|
|
|
667
669
|
tx_dict["maxPriorityFeePerGas"] = prio
|
|
668
670
|
else: # Avalanche (43114) — legacy gasPrice
|
|
669
671
|
tx_dict["gasPrice"] = max(int(w3.eth.gas_price * 2), 25 * 10**9)
|
|
672
|
+
|
|
673
|
+
def _set_gas_price_for(chain_id, w3, tx_dict):
|
|
674
|
+
"""Set gas fields for an EXPLICIT chain_id rather than the module CHAIN_ID. Needed by
|
|
675
|
+
cross-chain flows (prime-bridge) where a tx may target Avalanche or Arbitrum regardless
|
|
676
|
+
of which tool built it. Arbitrum/Base (EIP-1559): maxFeePerGas + maxPriorityFeePerGas;
|
|
677
|
+
Avalanche (legacy): gasPrice with a 25 gwei floor."""
|
|
678
|
+
tx_dict.pop("gasPrice", None)
|
|
679
|
+
if chain_id in (42161, 8453): # Arbitrum, Base — EIP-1559
|
|
680
|
+
base = w3.eth.gas_price
|
|
681
|
+
prio = w3.eth.max_priority_fee
|
|
682
|
+
tx_dict["maxFeePerGas"] = max(int(base * 2), base + prio + 10**9)
|
|
683
|
+
tx_dict["maxPriorityFeePerGas"] = prio
|
|
684
|
+
else: # Avalanche (43114) — legacy gasPrice
|
|
685
|
+
tx_dict["gasPrice"] = max(int(w3.eth.gas_price * 2), 25 * 10**9)
|
|
686
|
+
|
|
670
687
|
def _read_env_var(path, var):
|
|
671
688
|
"""Return the value of `var` from a KEY=VALUE env file, or None if absent."""
|
|
672
689
|
try:
|
|
@@ -693,15 +710,26 @@ def _agent_key(agent):
|
|
|
693
710
|
def resolve_private_key():
|
|
694
711
|
# The same EVM key works on every chain, so each ARBPRIME_ var falls back to its
|
|
695
712
|
# DELTAPRIME_ equivalent (exactly how degenprime falls back to DELTAPRIME_*).
|
|
696
|
-
# 1. --
|
|
713
|
+
# 1. --key <0xhex> CLI flag (set in main)
|
|
714
|
+
if _CLI_KEY:
|
|
715
|
+
return _CLI_KEY.strip()
|
|
716
|
+
# 2. --as <agent> CLI flag (set in main)
|
|
697
717
|
if _SELECTED_AGENT:
|
|
698
718
|
return _agent_key(_SELECTED_AGENT)
|
|
699
|
-
#
|
|
719
|
+
# 3. raw key directly in the environment
|
|
700
720
|
for env_var in ("ARBPRIME_PRIVATE_KEY", "DELTAPRIME_PRIVATE_KEY"):
|
|
701
721
|
raw = os.environ.get(env_var)
|
|
702
722
|
if raw:
|
|
703
723
|
return raw.strip()
|
|
704
|
-
#
|
|
724
|
+
# 4. path to a file containing the 0x key
|
|
725
|
+
for path_var in ("ARBPRIME_KEY_FILE", "DELTAPRIME_KEY_FILE"):
|
|
726
|
+
key_file = os.environ.get(path_var)
|
|
727
|
+
if key_file:
|
|
728
|
+
try:
|
|
729
|
+
return Path(key_file).read_text().strip()
|
|
730
|
+
except FileNotFoundError:
|
|
731
|
+
raise RuntimeError(f"{path_var} points at {key_file} but the file does not exist.")
|
|
732
|
+
# 5. explicit env-file + var-name
|
|
705
733
|
env_file = os.environ.get("ARBPRIME_ENV_FILE") or os.environ.get("DELTAPRIME_ENV_FILE")
|
|
706
734
|
key_var = os.environ.get("ARBPRIME_KEY_VAR") or os.environ.get("DELTAPRIME_KEY_VAR")
|
|
707
735
|
if env_file and key_var:
|
|
@@ -709,16 +737,25 @@ def resolve_private_key():
|
|
|
709
737
|
if not key:
|
|
710
738
|
raise RuntimeError(f"{key_var} not found in {env_file}.")
|
|
711
739
|
return key
|
|
712
|
-
#
|
|
740
|
+
# 6. named agent in the environment
|
|
713
741
|
agent = os.environ.get("ARBPRIME_AGENT") or os.environ.get("DELTAPRIME_AGENT")
|
|
714
742
|
if agent:
|
|
715
743
|
return _agent_key(agent)
|
|
716
|
-
#
|
|
717
|
-
|
|
744
|
+
# No silent default — fail closed.
|
|
745
|
+
raise RuntimeError(
|
|
746
|
+
"No signing key found. Pass --key <0xhex> or --as <agent>, or set "
|
|
747
|
+
"ARBPRIME_PRIVATE_KEY (raw 0x... key), ARBPRIME_KEY_FILE (path to a file with "
|
|
748
|
+
"the key), or ARBPRIME_ENV_FILE + ARBPRIME_KEY_VAR. DELTAPRIME_* equivalents "
|
|
749
|
+
"also work (same key, all chains)."
|
|
750
|
+
)
|
|
718
751
|
|
|
719
752
|
def get_account() -> Account:
|
|
720
753
|
return Account.from_key(resolve_private_key())
|
|
721
754
|
|
|
755
|
+
def to_wei_units(amount, decimals):
|
|
756
|
+
"""Convert a human amount to integer base units without float drift."""
|
|
757
|
+
return int(Decimal(str(amount)) * (10 ** int(decimals)))
|
|
758
|
+
|
|
722
759
|
def get_pool_contract(pool_name: str):
|
|
723
760
|
"""Pool proxy contract bound directly to the hand-curated POOL_ABI (no block-explorer
|
|
724
761
|
ABI fetch — Arbiscan is display-only here; the hand-curated subset covers every
|
|
@@ -1376,7 +1413,7 @@ def cmd_my_positions():
|
|
|
1376
1413
|
def cmd_deposit(pool_name: str, amount: float, execute: bool = False):
|
|
1377
1414
|
contract, cfg, w3 = get_pool_contract(pool_name)
|
|
1378
1415
|
acct = get_account()
|
|
1379
|
-
amount_wei =
|
|
1416
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
1380
1417
|
|
|
1381
1418
|
if not execute:
|
|
1382
1419
|
print(f"Preview: Deposit {amount} {cfg['symbol']} into {pool_name.upper()} pool")
|
|
@@ -1433,7 +1470,7 @@ def cmd_withdraw(pool_name: str, amount: float, execute: bool = False):
|
|
|
1433
1470
|
"""
|
|
1434
1471
|
contract, cfg, w3 = get_pool_contract(pool_name)
|
|
1435
1472
|
acct = get_account()
|
|
1436
|
-
amount_wei =
|
|
1473
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
1437
1474
|
|
|
1438
1475
|
# getBalanceOf is the lender's current deposit balance — sane upper bound for
|
|
1439
1476
|
# the intent amount. The pool reverts "Amount must be greater than zero" on 0,
|
|
@@ -1670,7 +1707,7 @@ def cmd_create_prime_account(execute: bool = False, fund_pool: str = None, fund_
|
|
|
1670
1707
|
print(f"Preview: Create a new Prime Account for {acct.address}")
|
|
1671
1708
|
if funding:
|
|
1672
1709
|
symbol = cfg["symbol"]
|
|
1673
|
-
amount_wei =
|
|
1710
|
+
amount_wei = to_wei_units(fund_amount, cfg["decimals"])
|
|
1674
1711
|
print(f" Factory: {FACTORY_PROXY} (SmartLoansFactory.createAndFundLoan())")
|
|
1675
1712
|
print(f" Approves the factory to spend {fund_amount} {symbol}, then")
|
|
1676
1713
|
print(f" calls createAndFundLoan(bytes32 '{symbol}', {amount_wei}) — creates + funds in one go.")
|
|
@@ -1683,7 +1720,7 @@ def cmd_create_prime_account(execute: bool = False, fund_pool: str = None, fund_
|
|
|
1683
1720
|
|
|
1684
1721
|
if funding:
|
|
1685
1722
|
symbol = cfg["symbol"]
|
|
1686
|
-
amount_wei =
|
|
1723
|
+
amount_wei = to_wei_units(fund_amount, cfg["decimals"])
|
|
1687
1724
|
# createAndFundLoan does token.transferFrom(msg.sender, factory, amount),
|
|
1688
1725
|
# so approve the factory first.
|
|
1689
1726
|
token = w3.eth.contract(address=Web3.to_checksum_address(cfg["token"]),
|
|
@@ -1746,7 +1783,7 @@ def cmd_fund(pool_name: str, amount: float, execute: bool = False):
|
|
|
1746
1783
|
return
|
|
1747
1784
|
|
|
1748
1785
|
symbol = pool_to_asset_symbol(pool_name)
|
|
1749
|
-
amount_wei =
|
|
1786
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
1750
1787
|
pa_cs = Web3.to_checksum_address(pa)
|
|
1751
1788
|
|
|
1752
1789
|
if not execute:
|
|
@@ -1980,7 +2017,7 @@ def cmd_borrow(pool_name: str, amount: float, execute: bool = False):
|
|
|
1980
2017
|
return
|
|
1981
2018
|
|
|
1982
2019
|
symbol = pool_to_asset_symbol(pool_name)
|
|
1983
|
-
amount_wei =
|
|
2020
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
1984
2021
|
if not execute:
|
|
1985
2022
|
print(f"Preview: Borrow {amount} {symbol} into Prime Account {pa}")
|
|
1986
2023
|
print(f" Calls borrow(bytes32 '{symbol}', {amount_wei}) on the Prime Account")
|
|
@@ -2027,7 +2064,7 @@ def cmd_repay(pool_name: str, amount: float, execute: bool = False):
|
|
|
2027
2064
|
# The facet's repay reverts if amount > debt OR amount > in-account balance.
|
|
2028
2065
|
# Cap to min(requested, debt, in_account) so callers don't need to know either
|
|
2029
2066
|
# exact figure — pass an overshoot like 9999 and it clips cleanly.
|
|
2030
|
-
requested_wei =
|
|
2067
|
+
requested_wei = to_wei_units(amount, cfg["decimals"])
|
|
2031
2068
|
debt_wei = pool.functions.getBorrowed(pa_cs).call()
|
|
2032
2069
|
in_acct_wei = account.functions.getBalance(asset_b32(symbol)).call()
|
|
2033
2070
|
if debt_wei == 0:
|
|
@@ -2297,7 +2334,7 @@ def cmd_swap(from_sym: str, to_sym: str, amount: float, slippage_pct: float = 1.
|
|
|
2297
2334
|
account = w3.eth.contract(address=pa_cs, abi=PRIME_ACCOUNT_ABI)
|
|
2298
2335
|
|
|
2299
2336
|
from_cfg, to_cfg = SWAP_ASSETS[from_sym], SWAP_ASSETS[to_sym]
|
|
2300
|
-
amount_in =
|
|
2337
|
+
amount_in = to_wei_units(amount, from_cfg["decimals"])
|
|
2301
2338
|
|
|
2302
2339
|
# In-account balance check (oracle-free view).
|
|
2303
2340
|
in_balance = account.functions.getBalance(asset_b32(from_sym)).call()
|
|
@@ -2395,7 +2432,7 @@ def _calc_swap_debt_amounts(w3, account, from_sym, to_sym, amount):
|
|
|
2395
2432
|
borrowed = from_pool.functions.getBorrowed(pa_cs).call()
|
|
2396
2433
|
if borrowed == 0:
|
|
2397
2434
|
raise ValueError("zero_old_debt")
|
|
2398
|
-
repay_amount = min(
|
|
2435
|
+
repay_amount = min(to_wei_units(amount, from_cfg["decimals"]), borrowed)
|
|
2399
2436
|
|
|
2400
2437
|
# Value-match the new borrow to the repay using the facet's own RedStone prices.
|
|
2401
2438
|
feeds = prime_account_price_feeds(account)
|
|
@@ -2749,7 +2786,7 @@ def cmd_withdraw_collateral(pool_name: str, amount: float, execute: bool = False
|
|
|
2749
2786
|
return
|
|
2750
2787
|
|
|
2751
2788
|
symbol = pool_to_asset_symbol(pool_name)
|
|
2752
|
-
amount_wei =
|
|
2789
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
2753
2790
|
pa_cs = Web3.to_checksum_address(pa)
|
|
2754
2791
|
account = w3.eth.contract(address=pa_cs, abi=PRIME_ACCOUNT_ABI)
|
|
2755
2792
|
|
|
@@ -3244,7 +3281,7 @@ def cmd_gmx_deposit(market: str, amount: float, is_long: bool | None = None,
|
|
|
3244
3281
|
pa_cs = Web3.to_checksum_address(pa)
|
|
3245
3282
|
account = w3.eth.contract(address=pa_cs, abi=PRIME_ACCOUNT_ABI)
|
|
3246
3283
|
|
|
3247
|
-
amount_wei =
|
|
3284
|
+
amount_wei = to_wei_units(amount, dep_cfg["decimals"])
|
|
3248
3285
|
in_balance = account.functions.getBalance(asset_b32(dep_sym)).call()
|
|
3249
3286
|
if amount_wei > in_balance:
|
|
3250
3287
|
print(f"Prime Account holds only {in_balance / 10**dep_cfg['decimals']:.6f} {dep_sym} "
|
|
@@ -3354,7 +3391,7 @@ def cmd_gmx_withdraw(market: str, amount: float, slippage_pct: float = 1.0,
|
|
|
3354
3391
|
gm_cs = Web3.to_checksum_address(mkt["gm_token"])
|
|
3355
3392
|
erc = json.loads('[{"inputs":[{"name":"a","type":"address"}],"name":"balanceOf","outputs":[{"type":"uint256"}],"stateMutability":"view","type":"function"}]')
|
|
3356
3393
|
gm_bal = w3.eth.contract(address=gm_cs, abi=erc).functions.balanceOf(pa_cs).call()
|
|
3357
|
-
gm_amount =
|
|
3394
|
+
gm_amount = to_wei_units(amount, GM_TOKEN_DECIMALS)
|
|
3358
3395
|
if gm_bal == 0:
|
|
3359
3396
|
print(f"Prime Account holds no {mkt['gm_feed']} GM tokens — nothing to withdraw.")
|
|
3360
3397
|
return
|
|
@@ -3600,7 +3637,7 @@ def cmd_glv_deposit(vault_key: str, amount: float, is_long: bool | None = None,
|
|
|
3600
3637
|
dep_meta = long_meta if is_long_eff else short_meta
|
|
3601
3638
|
dep_sym = dep_meta["symbol"]
|
|
3602
3639
|
|
|
3603
|
-
amount_wei =
|
|
3640
|
+
amount_wei = to_wei_units(amount, dep_meta["decimals"])
|
|
3604
3641
|
in_balance = account.functions.getBalance(asset_b32(dep_sym)).call()
|
|
3605
3642
|
if amount_wei > in_balance:
|
|
3606
3643
|
print(f"Prime Account holds only {in_balance / 10**dep_meta['decimals']:.6f} {dep_sym} "
|
|
@@ -3717,7 +3754,7 @@ def cmd_glv_withdraw(vault_key: str, amount: float, target_market: str = None,
|
|
|
3717
3754
|
glv_cs = Web3.to_checksum_address(vault["glv_token"])
|
|
3718
3755
|
erc = json.loads('[{"inputs":[{"name":"a","type":"address"}],"name":"balanceOf","outputs":[{"type":"uint256"}],"stateMutability":"view","type":"function"}]')
|
|
3719
3756
|
glv_bal = w3.eth.contract(address=glv_cs, abi=erc).functions.balanceOf(pa_cs).call()
|
|
3720
|
-
glv_amount =
|
|
3757
|
+
glv_amount = to_wei_units(amount, GLV_TOKEN_DECIMALS)
|
|
3721
3758
|
if glv_bal == 0:
|
|
3722
3759
|
print(f"Prime Account holds no [{vault_key}] GLV tokens — nothing to withdraw.")
|
|
3723
3760
|
return
|
|
@@ -4055,8 +4092,8 @@ def cmd_lb_add(pair_key: str, amount_x: float, amount_y: float, shape: str = "sp
|
|
|
4055
4092
|
pa_cs = Web3.to_checksum_address(pa)
|
|
4056
4093
|
account = w3.eth.contract(address=pa_cs, abi=PRIME_ACCOUNT_ABI)
|
|
4057
4094
|
|
|
4058
|
-
amount_x_wei =
|
|
4059
|
-
amount_y_wei =
|
|
4095
|
+
amount_x_wei = to_wei_units(amount_x, x_cfg["decimals"]) if has_x else 0
|
|
4096
|
+
amount_y_wei = to_wei_units(amount_y, y_cfg["decimals"]) if has_y else 0
|
|
4060
4097
|
|
|
4061
4098
|
# In-account balances (oracle-free), keyed by the TokenManager symbol the facet uses.
|
|
4062
4099
|
bal_x = account.functions.getBalance(asset_b32(x_cfg["symbol"])).call()
|
|
@@ -4382,7 +4419,7 @@ def cmd_prime_activate(amount: float = None, execute: bool = False):
|
|
|
4382
4419
|
print(f"Prime Account {pa} is already in PREMIUM tier — nothing to do.")
|
|
4383
4420
|
return
|
|
4384
4421
|
|
|
4385
|
-
deposit_wei =
|
|
4422
|
+
deposit_wei = to_wei_units(amount, PRIME_TOKEN["decimals"]) if amount else 0
|
|
4386
4423
|
eoa_prime = prime.functions.balanceOf(acct.address).call()
|
|
4387
4424
|
in_acct_prime = account.functions.getBalance(asset_b32(PRIME_TOKEN["symbol"])).call()
|
|
4388
4425
|
# depositPrime caps to the EOA balance on-chain; mirror that for an honest preview.
|
|
@@ -4490,7 +4527,7 @@ def cmd_prime_deposit(amount: float, execute: bool = False):
|
|
|
4490
4527
|
|
|
4491
4528
|
eoa_prime = prime.functions.balanceOf(acct.address).call()
|
|
4492
4529
|
in_acct_prime = account.functions.getBalance(asset_b32(PRIME_TOKEN["symbol"])).call()
|
|
4493
|
-
deposit_wei =
|
|
4530
|
+
deposit_wei = to_wei_units(amount, PRIME_TOKEN["decimals"])
|
|
4494
4531
|
deposit_wei = min(deposit_wei, eoa_prime) # depositPrime caps to the EOA balance on-chain
|
|
4495
4532
|
|
|
4496
4533
|
print(f"PRIME deposit into Prime Account {pa}")
|
|
@@ -4602,7 +4639,7 @@ def cmd_prime_unstake(amount: float, execute: bool = False):
|
|
|
4602
4639
|
if staked == 0:
|
|
4603
4640
|
print(f"Prime Account {pa} has no staked PRIME — nothing to unstake.")
|
|
4604
4641
|
return
|
|
4605
|
-
amount_wei =
|
|
4642
|
+
amount_wei = to_wei_units(amount, PRIME_TOKEN["decimals"])
|
|
4606
4643
|
if amount_wei > staked:
|
|
4607
4644
|
print(f"Staked PRIME is {staked / 10**PRIME_TOKEN['decimals']:,.6f}; clamping unstake to that.")
|
|
4608
4645
|
amount_wei = staked
|
|
@@ -4649,7 +4686,7 @@ def cmd_prime_repay(amount: float, execute: bool = False):
|
|
|
4649
4686
|
|
|
4650
4687
|
_tier, _staked, recorded_debt = account.functions.getLeverageTierFullInfo().call()
|
|
4651
4688
|
in_acct_prime = account.functions.getBalance(asset_b32(PRIME_TOKEN["symbol"])).call()
|
|
4652
|
-
amount_wei =
|
|
4689
|
+
amount_wei = to_wei_units(amount, PRIME_TOKEN["decimals"])
|
|
4653
4690
|
|
|
4654
4691
|
print(f"PRIME repay debt: {amount} PRIME on Prime Account {pa}")
|
|
4655
4692
|
print(f" Recorded PRIME debt: {recorded_debt / 10**PRIME_TOKEN['decimals']:,.6f} "
|
|
@@ -5141,7 +5178,7 @@ def _bridge_estimate_lz_fee(w3, src_cfg: dict, dst_lz_chain_id: int, amount_wei:
|
|
|
5141
5178
|
"stateMutability": "view", "type": "function"}])))
|
|
5142
5179
|
native, zro = ep.functions.estimateFees(
|
|
5143
5180
|
dst_lz_chain_id, Web3.to_checksum_address(src_cfg["bridge_target"]),
|
|
5144
|
-
_bridge_lz_payload(
|
|
5181
|
+
_bridge_lz_payload(get_account().address, amount_wei),
|
|
5145
5182
|
False, BRIDGE_ADAPTER_PARAMS).call()
|
|
5146
5183
|
return native, zro
|
|
5147
5184
|
|
|
@@ -5164,7 +5201,8 @@ def cmd_prime_bridge(from_chain: str = "avax", amount: float = None, execute: bo
|
|
|
5164
5201
|
dst_key = "arb" if src_key == "avax" else "avax"
|
|
5165
5202
|
src_cfg = BRIDGE_CHAIN[src_key]
|
|
5166
5203
|
dst_cfg = BRIDGE_CHAIN[dst_key]
|
|
5167
|
-
|
|
5204
|
+
src_chain_id = src_cfg["chain_id"]
|
|
5205
|
+
amount_wei = to_wei_units(amount, 18)
|
|
5168
5206
|
|
|
5169
5207
|
w3 = _bridge_w3(src_key)
|
|
5170
5208
|
acct = get_account()
|
|
@@ -5218,8 +5256,8 @@ def cmd_prime_bridge(from_chain: str = "avax", amount: float = None, execute: bo
|
|
|
5218
5256
|
"name": "approve", "outputs": [{"name": "", "type": "bool"}], "type": "function"}])))
|
|
5219
5257
|
atx = app_c.functions.approve(bridge_target, amount_wei).build_transaction(
|
|
5220
5258
|
{"from": wallet, "nonce": w3.eth.get_transaction_count(wallet),
|
|
5221
|
-
"gas": 100000, "chainId":
|
|
5222
|
-
|
|
5259
|
+
"gas": 100000, "chainId": src_chain_id})
|
|
5260
|
+
_set_gas_price_for(src_chain_id, w3, atx)
|
|
5223
5261
|
signed = acct.sign_transaction(atx)
|
|
5224
5262
|
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
5225
5263
|
_ = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
|
|
@@ -5240,8 +5278,8 @@ def cmd_prime_bridge(from_chain: str = "avax", amount: float = None, execute: bo
|
|
|
5240
5278
|
print(f" sendFrom() via LayerZero (LZ {src_cfg['lz_chain_id']} → {dst_cfg['lz_chain_id']})...")
|
|
5241
5279
|
tx = {"from": wallet, "to": bridge_target, "data": bytes.fromhex(calldata_hex),
|
|
5242
5280
|
"nonce": w3.eth.get_transaction_count(wallet), "gas": 500000,
|
|
5243
|
-
"value": native_fee, "chainId":
|
|
5244
|
-
|
|
5281
|
+
"value": native_fee, "chainId": src_chain_id}
|
|
5282
|
+
_set_gas_price_for(src_chain_id, w3, tx)
|
|
5245
5283
|
signed = acct.sign_transaction(tx)
|
|
5246
5284
|
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
|
|
5247
5285
|
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=300)
|
|
@@ -5252,7 +5290,7 @@ def cmd_prime_bridge(from_chain: str = "avax", amount: float = None, execute: bo
|
|
|
5252
5290
|
def main():
|
|
5253
5291
|
args = sys.argv[1:] if len(sys.argv) > 1 else []
|
|
5254
5292
|
# Global wallet selector: --as <agent>, stripped before command dispatch.
|
|
5255
|
-
global _SELECTED_AGENT
|
|
5293
|
+
global _SELECTED_AGENT, _CLI_KEY
|
|
5256
5294
|
if "--as" in args:
|
|
5257
5295
|
i = args.index("--as")
|
|
5258
5296
|
if i + 1 >= len(args):
|
|
@@ -5260,6 +5298,14 @@ def main():
|
|
|
5260
5298
|
return
|
|
5261
5299
|
_SELECTED_AGENT = args[i + 1]
|
|
5262
5300
|
del args[i:i + 2]
|
|
5301
|
+
# Global signing-key override: --key <0xhex>, stripped before command dispatch.
|
|
5302
|
+
if "--key" in args:
|
|
5303
|
+
i = args.index("--key")
|
|
5304
|
+
if i + 1 >= len(args):
|
|
5305
|
+
print("--key requires a hex key. Example: --key 0xabc...")
|
|
5306
|
+
return
|
|
5307
|
+
_CLI_KEY = args[i + 1]
|
|
5308
|
+
del args[i:i + 2]
|
|
5263
5309
|
if not args or args[0] in ("-h", "--help"):
|
|
5264
5310
|
print(__doc__)
|
|
5265
5311
|
return
|
|
@@ -274,6 +274,10 @@ def resolve_private_key():
|
|
|
274
274
|
def get_account() -> Account:
|
|
275
275
|
return Account.from_key(resolve_private_key())
|
|
276
276
|
|
|
277
|
+
def to_wei_units(amount, decimals):
|
|
278
|
+
"""Convert a human amount to integer base units without float drift."""
|
|
279
|
+
return int(Decimal(str(amount)) * (10 ** int(decimals)))
|
|
280
|
+
|
|
277
281
|
# Basescan's v1 API is deprecated (returns "switch to Etherscan API V2" since 2026),
|
|
278
282
|
# and the v2 multichain endpoint (api.etherscan.io/v2/api?chainid=8453) requires an API
|
|
279
283
|
# key with no anonymous reads. Rather than depend on an API key for what is a tiny,
|
|
@@ -928,7 +932,7 @@ def cmd_my_positions():
|
|
|
928
932
|
def cmd_deposit(pool_name: str, amount: float, execute: bool = False):
|
|
929
933
|
contract, cfg, w3 = get_pool_contract(pool_name)
|
|
930
934
|
acct = get_account()
|
|
931
|
-
amount_wei =
|
|
935
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
932
936
|
print(f"Wallet: {acct.address}")
|
|
933
937
|
|
|
934
938
|
if not execute:
|
|
@@ -984,7 +988,7 @@ def cmd_withdraw(pool_name: str, amount: float, execute: bool = False):
|
|
|
984
988
|
The pool also exposes withdrawNativeToken — future --native flag could opt in."""
|
|
985
989
|
contract, cfg, w3 = get_pool_contract(pool_name)
|
|
986
990
|
acct = get_account()
|
|
987
|
-
amount_wei =
|
|
991
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
988
992
|
print(f"Wallet: {acct.address}")
|
|
989
993
|
|
|
990
994
|
if not execute:
|
|
@@ -1149,7 +1153,7 @@ def cmd_create_account(execute: bool = False, fund_pool: str = None, fund_amount
|
|
|
1149
1153
|
print(f"Preview: Create a new Degen Account for {acct.address}")
|
|
1150
1154
|
if funding:
|
|
1151
1155
|
symbol = cfg["symbol"]
|
|
1152
|
-
amount_wei =
|
|
1156
|
+
amount_wei = to_wei_units(fund_amount, cfg["decimals"])
|
|
1153
1157
|
print(f" Factory: {FACTORY_PROXY} (SmartLoansFactory.createAndFundLoan())")
|
|
1154
1158
|
print(f" Approves the factory to spend {fund_amount} {symbol}, then")
|
|
1155
1159
|
print(f" calls createAndFundLoan(bytes32 '{symbol}', {amount_wei}) - creates + funds in one go.")
|
|
@@ -1162,7 +1166,7 @@ def cmd_create_account(execute: bool = False, fund_pool: str = None, fund_amount
|
|
|
1162
1166
|
|
|
1163
1167
|
if funding:
|
|
1164
1168
|
symbol = cfg["symbol"]
|
|
1165
|
-
amount_wei =
|
|
1169
|
+
amount_wei = to_wei_units(fund_amount, cfg["decimals"])
|
|
1166
1170
|
token = w3.eth.contract(address=Web3.to_checksum_address(cfg["token"]), abi=ERC20_ABI)
|
|
1167
1171
|
app_tx = token.functions.approve(factory_cs, amount_wei).build_transaction({
|
|
1168
1172
|
"from": acct.address, "nonce": w3.eth.get_transaction_count(acct.address),
|
|
@@ -1223,7 +1227,7 @@ def cmd_fund(pool_name: str, amount: float, execute: bool = False):
|
|
|
1223
1227
|
return
|
|
1224
1228
|
|
|
1225
1229
|
symbol = pool_to_asset_symbol(pool_name)
|
|
1226
|
-
amount_wei =
|
|
1230
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
1227
1231
|
pa_cs = Web3.to_checksum_address(pa)
|
|
1228
1232
|
|
|
1229
1233
|
if not execute:
|
|
@@ -1515,7 +1519,7 @@ def cmd_borrow(pool_name: str, amount: float, execute: bool = False):
|
|
|
1515
1519
|
return
|
|
1516
1520
|
|
|
1517
1521
|
symbol = pool_to_asset_symbol(pool_name)
|
|
1518
|
-
amount_wei =
|
|
1522
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
1519
1523
|
if not execute:
|
|
1520
1524
|
print(f"Preview: Borrow {amount} {symbol} into Degen Account {pa}")
|
|
1521
1525
|
print(f" Calls borrow(bytes32 '{symbol}', {amount_wei}) on the Degen Account")
|
|
@@ -1569,7 +1573,7 @@ def cmd_repay(pool_name: str, amount: float, execute: bool = False):
|
|
|
1569
1573
|
pa_cs = Web3.to_checksum_address(pa)
|
|
1570
1574
|
account = w3.eth.contract(address=pa_cs, abi=PRIME_ACCOUNT_ABI)
|
|
1571
1575
|
pool, _, _ = get_pool_contract(pool_name)
|
|
1572
|
-
requested_wei =
|
|
1576
|
+
requested_wei = to_wei_units(amount, cfg["decimals"])
|
|
1573
1577
|
debt_wei = pool.functions.getBorrowed(pa_cs).call()
|
|
1574
1578
|
in_acct_wei = account.functions.getBalance(asset_b32(symbol)).call()
|
|
1575
1579
|
if debt_wei == 0:
|
|
@@ -1767,7 +1771,7 @@ def cmd_swap(from_sym: str, to_sym: str, amount: float, slippage_pct: float = 1.
|
|
|
1767
1771
|
"or any TokenManager-listed collateral symbol.")
|
|
1768
1772
|
return
|
|
1769
1773
|
|
|
1770
|
-
amount_in =
|
|
1774
|
+
amount_in = to_wei_units(amount, from_cfg["decimals"])
|
|
1771
1775
|
in_balance = account.functions.getBalance(asset_b32(from_sym)).call()
|
|
1772
1776
|
if amount_in > in_balance:
|
|
1773
1777
|
print(f"Degen Account holds only {in_balance / 10**from_cfg['decimals']:.6f} {from_sym} "
|
|
@@ -1884,7 +1888,7 @@ def cmd_swap_debt(from_sym: str, to_sym: str, amount: float, slippage_pct: float
|
|
|
1884
1888
|
if borrowed == 0:
|
|
1885
1889
|
print(f"Degen Account has no {from_sym} debt to refinance.")
|
|
1886
1890
|
return
|
|
1887
|
-
repay_amount = min(
|
|
1891
|
+
repay_amount = min(to_wei_units(amount, from_cfg["decimals"]), borrowed)
|
|
1888
1892
|
|
|
1889
1893
|
feeds = degen_account_price_feeds(account)
|
|
1890
1894
|
for s in (from_sym, to_sym):
|
|
@@ -2003,7 +2007,7 @@ def cmd_withdraw_collateral(pool_name: str, amount: float, execute: bool = False
|
|
|
2003
2007
|
return
|
|
2004
2008
|
|
|
2005
2009
|
symbol = pool_to_asset_symbol(pool_name)
|
|
2006
|
-
amount_wei =
|
|
2010
|
+
amount_wei = to_wei_units(amount, cfg["decimals"])
|
|
2007
2011
|
pa_cs = Web3.to_checksum_address(pa)
|
|
2008
2012
|
account = w3.eth.contract(address=pa_cs, abi=PRIME_ACCOUNT_ABI)
|
|
2009
2013
|
|