allium-cli 0.2.0__py3-none-any.whl
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.
- allium_cli-0.2.0.dist-info/METADATA +306 -0
- allium_cli-0.2.0.dist-info/RECORD +44 -0
- allium_cli-0.2.0.dist-info/WHEEL +4 -0
- allium_cli-0.2.0.dist-info/entry_points.txt +2 -0
- allium_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
- cli/__init__.py +0 -0
- cli/auth/__init__.py +0 -0
- cli/auth/api_key.py +7 -0
- cli/auth/tempo.py +103 -0
- cli/auth/x402_key.py +31 -0
- cli/auth/x402_privy.py +27 -0
- cli/clients/__init__.py +4 -0
- cli/clients/factory.py +89 -0
- cli/clients/http.py +94 -0
- cli/clients/protocol.py +18 -0
- cli/clients/tempo.py +54 -0
- cli/clients/x402.py +185 -0
- cli/commands/__init__.py +16 -0
- cli/commands/auth.py +397 -0
- cli/commands/explorer.py +304 -0
- cli/commands/mp.py +159 -0
- cli/commands/realtime.py +445 -0
- cli/constants/__init__.py +28 -0
- cli/constants/api.py +4 -0
- cli/constants/config.py +23 -0
- cli/constants/ui.py +27 -0
- cli/main.py +108 -0
- cli/types/__init__.py +38 -0
- cli/types/config.py +33 -0
- cli/types/context.py +14 -0
- cli/types/enums.py +62 -0
- cli/types/labels.py +26 -0
- cli/types/profiles.py +45 -0
- cli/utils/__init__.py +0 -0
- cli/utils/async_cmd.py +15 -0
- cli/utils/body.py +40 -0
- cli/utils/config.py +90 -0
- cli/utils/console.py +6 -0
- cli/utils/cost_log.py +101 -0
- cli/utils/errors.py +63 -0
- cli/utils/options.py +44 -0
- cli/utils/output.py +103 -0
- cli/utils/payment.py +28 -0
- cli/utils/version.py +46 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: allium-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI for Allium blockchain data APIs
|
|
5
|
+
Project-URL: Documentation, https://docs.allium.so
|
|
6
|
+
Project-URL: Homepage, https://allium.so
|
|
7
|
+
Project-URL: Repository, https://github.com/Allium-Science/allium-cli
|
|
8
|
+
Author-email: Allium <support@allium.so>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Framework :: Pydantic :: 2
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: click>=8.1
|
|
19
|
+
Requires-Dist: eth-account~=0.13
|
|
20
|
+
Requires-Dist: httpx~=0.27
|
|
21
|
+
Requires-Dist: privy-client~=0.5
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: pympp[tempo]==0.3
|
|
24
|
+
Requires-Dist: questionary>=2.0
|
|
25
|
+
Requires-Dist: rich-click>=1.9.7
|
|
26
|
+
Requires-Dist: rich>=13.0
|
|
27
|
+
Requires-Dist: tomli-w>=1.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# Allium CLI
|
|
31
|
+
|
|
32
|
+
Command-line interface for querying blockchain data across 80+ chains via the [Allium](https://allium.so) platform. Supports realtime token prices, wallet balances, transaction history, and SQL queries against Allium's data warehouse.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
curl -sSL https://raw.githubusercontent.com/Allium-Science/allium-cli/main/install.sh | sh
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or install directly with your preferred package manager:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv tool install allium-cli # recommended
|
|
44
|
+
pipx install allium-cli
|
|
45
|
+
pip install allium-cli
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This installs the `allium` command. Run `allium auth setup` to configure authentication.
|
|
49
|
+
|
|
50
|
+
## Authentication
|
|
51
|
+
|
|
52
|
+
The CLI supports four authentication methods. Run the interactive wizard, or pass arguments directly for scripted/CI setups:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Interactive wizard (arrow-key selection)
|
|
56
|
+
allium auth setup
|
|
57
|
+
|
|
58
|
+
# Non-interactive one-liners
|
|
59
|
+
allium auth setup --method api_key --api-key sk-...
|
|
60
|
+
allium auth setup --method x402_key --private-key 0x... --network eip155:8453
|
|
61
|
+
allium auth setup --method x402_privy \
|
|
62
|
+
--privy-app-id ... --privy-app-secret ... \
|
|
63
|
+
--privy-wallet-id ... --network eip155:8453
|
|
64
|
+
allium auth setup --method tempo --private-key 0x... --chain-id 42431
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Method | Description |
|
|
68
|
+
|---|---|
|
|
69
|
+
| **API Key** | Standard key from [app.allium.so/settings/api-keys](https://app.allium.so/settings/api-keys) |
|
|
70
|
+
| **x402 Private Key** | Pay-per-call with USDC on Base -- no API key needed |
|
|
71
|
+
| **x402 Privy** | x402 via Privy server wallets -- no private key handling |
|
|
72
|
+
| **Tempo MPP** | Tempo micropayment protocol |
|
|
73
|
+
|
|
74
|
+
Optional flags: `--name <profile-name>` (defaults to the method name), `--no-active` (skip setting as active profile).
|
|
75
|
+
|
|
76
|
+
Credentials are stored in `~/.config/allium/credentials.toml` (file permissions restricted to owner).
|
|
77
|
+
|
|
78
|
+
### Profile management
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
allium auth list # Show all profiles
|
|
82
|
+
allium auth use <name> # Switch active profile
|
|
83
|
+
allium auth remove <name> # Delete a profile
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Global Options
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
--profile TEXT Override the active auth profile for this command
|
|
90
|
+
--format [json|table|csv] Output format (default: json)
|
|
91
|
+
-v, --verbose Show progress details (run IDs, spinners, status)
|
|
92
|
+
--help Show help and exit
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Commands
|
|
96
|
+
|
|
97
|
+
### `allium realtime` -- Realtime Blockchain Data
|
|
98
|
+
|
|
99
|
+
Query realtime blockchain data with 3-5s freshness across 20+ chains.
|
|
100
|
+
|
|
101
|
+
#### Prices
|
|
102
|
+
|
|
103
|
+
Token prices derived from on-chain DEX trades with VWAP calculation and outlier detection.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Latest minute-level price and OHLC values
|
|
107
|
+
allium realtime prices latest \
|
|
108
|
+
--chain solana --token-address So11111111111111111111111111111111111111112
|
|
109
|
+
|
|
110
|
+
# Price at a specific timestamp
|
|
111
|
+
allium realtime prices at-timestamp \
|
|
112
|
+
--chain ethereum --token-address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
|
|
113
|
+
--timestamp 2026-01-15T12:00:00Z --time-granularity 1h
|
|
114
|
+
|
|
115
|
+
# Historical price series
|
|
116
|
+
allium realtime prices history \
|
|
117
|
+
--chain ethereum --token-address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
|
|
118
|
+
--start-timestamp 2026-01-01T00:00:00Z --end-timestamp 2026-01-07T00:00:00Z \
|
|
119
|
+
--time-granularity 1d
|
|
120
|
+
|
|
121
|
+
# 24h/1h price stats (high, low, volume, trade count, percent change)
|
|
122
|
+
allium realtime prices stats \
|
|
123
|
+
--chain solana --token-address So11111111111111111111111111111111111111112
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Options:** `--chain`, `--token-address` (repeatable, paired in order), `--body` (JSON override), `--timestamp`, `--start-timestamp`, `--end-timestamp`, `--time-granularity [15s|1m|5m|1h|1d]`
|
|
127
|
+
|
|
128
|
+
#### Tokens
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# List top tokens by volume
|
|
132
|
+
allium realtime tokens list --chain ethereum --sort volume --limit 10
|
|
133
|
+
|
|
134
|
+
# Fuzzy search by name or symbol
|
|
135
|
+
allium realtime tokens search -q "USDC"
|
|
136
|
+
|
|
137
|
+
# Exact lookup by chain + contract address
|
|
138
|
+
allium realtime tokens chain-address \
|
|
139
|
+
--chain ethereum --token-address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Options:** `--chain`, `--token-address` (repeatable), `--sort [volume|trade_count|fully_diluted_valuation|address|name]`, `--order [asc|desc]`, `--limit`, `-q/--query`
|
|
143
|
+
|
|
144
|
+
#### Balances
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Current token balances for a wallet
|
|
148
|
+
allium realtime balances latest \
|
|
149
|
+
--chain ethereum --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
150
|
+
|
|
151
|
+
# Historical balance snapshots
|
|
152
|
+
allium realtime balances history \
|
|
153
|
+
--chain ethereum --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \
|
|
154
|
+
--start-timestamp 2026-01-01T00:00:00Z --limit 100
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Options:** `--chain`, `--address` (repeatable, paired), `--start-timestamp`, `--end-timestamp`, `--limit`, `--body`
|
|
158
|
+
|
|
159
|
+
#### Transactions
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Wallet transaction activity with decoded activities and labels
|
|
163
|
+
allium realtime transactions \
|
|
164
|
+
--chain ethereum --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \
|
|
165
|
+
--activity-type dex_trade --lookback-days 7 --limit 50
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Options:** `--chain`, `--address` (repeatable), `--activity-type`, `--lookback-days`, `--limit`, `--body`
|
|
169
|
+
|
|
170
|
+
#### PnL
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Wallet profit and loss
|
|
174
|
+
allium realtime pnl \
|
|
175
|
+
--chain ethereum --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
|
176
|
+
|
|
177
|
+
# With historical breakdown
|
|
178
|
+
allium realtime pnl \
|
|
179
|
+
--chain ethereum --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 \
|
|
180
|
+
--with-historical-breakdown
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Options:** `--chain`, `--address` (repeatable), `--with-historical-breakdown`, `--body`
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### `allium explorer` -- SQL Query Execution
|
|
188
|
+
|
|
189
|
+
Run SQL queries on Allium's data warehouse. By default, the CLI polls silently and prints results. Use `-v` for progress details.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Execute ad-hoc SQL (requires x402 or Tempo auth)
|
|
193
|
+
allium explorer run-sql "SELECT chain, COUNT(*) FROM crosschain.dex.trades GROUP BY chain LIMIT 10"
|
|
194
|
+
|
|
195
|
+
# Execute SQL from a file
|
|
196
|
+
allium explorer run-sql query.sql --limit 1000
|
|
197
|
+
|
|
198
|
+
# Run a saved query by ID with parameters
|
|
199
|
+
allium explorer run abc123 --param start_date=2026-01-01 --param chain=ethereum
|
|
200
|
+
|
|
201
|
+
# Just get the run ID without waiting
|
|
202
|
+
allium explorer run-sql "SELECT 1" --no-wait
|
|
203
|
+
|
|
204
|
+
# Check status of a query run
|
|
205
|
+
allium explorer status <run_id>
|
|
206
|
+
|
|
207
|
+
# Fetch results of a completed run
|
|
208
|
+
allium explorer results <run_id>
|
|
209
|
+
|
|
210
|
+
# Pipe CSV output directly
|
|
211
|
+
allium --format csv explorer run-sql "SELECT 1" > output.csv
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
| Command | Description |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `run-sql <SQL_OR_FILE>` | Execute ad-hoc SQL (x402/Tempo auth required) |
|
|
217
|
+
| `run <QUERY_ID>` | Execute a saved Explorer query by ID |
|
|
218
|
+
| `status <RUN_ID>` | Check query run status (created, running, success, failed, canceled) |
|
|
219
|
+
| `results <RUN_ID>` | Download results of a completed run |
|
|
220
|
+
|
|
221
|
+
**Options:** `--limit`, `--no-wait`, `--param key=value` (repeatable), `--compute-profile`
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### `allium mp` -- Machine Payment Tracking
|
|
226
|
+
|
|
227
|
+
Track costs for x402 and Tempo micropayment API calls. Payments are logged automatically to `~/.config/allium/cost_log.csv`.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Total spend summary grouped by method and network
|
|
231
|
+
allium mp cost
|
|
232
|
+
|
|
233
|
+
# Full itemized payment history
|
|
234
|
+
allium mp cost list
|
|
235
|
+
|
|
236
|
+
# Export as CSV
|
|
237
|
+
allium --format csv mp cost list
|
|
238
|
+
|
|
239
|
+
# Clear the cost log
|
|
240
|
+
allium mp cost clear
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| Command | Description |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `mp cost` | Total spend summary (grouped by method/network with call counts) |
|
|
246
|
+
| `mp cost list` | Full itemized history with per-row details |
|
|
247
|
+
| `mp cost clear` | Delete the cost log (with confirmation prompt) |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### `allium auth` -- Authentication Management
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Interactive setup wizard (arrow-key selection)
|
|
255
|
+
allium auth setup
|
|
256
|
+
|
|
257
|
+
# Non-interactive setup (for scripts/CI)
|
|
258
|
+
allium auth setup --method api_key --api-key sk-...
|
|
259
|
+
allium auth setup --method tempo --private-key 0x... --chain-id 42431
|
|
260
|
+
|
|
261
|
+
# List all configured profiles
|
|
262
|
+
allium auth list
|
|
263
|
+
|
|
264
|
+
# Switch active profile
|
|
265
|
+
allium auth use <name>
|
|
266
|
+
|
|
267
|
+
# Delete a profile
|
|
268
|
+
allium auth remove <name>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## JSON Body Override
|
|
272
|
+
|
|
273
|
+
All realtime commands support a `--body` flag that accepts either inline JSON or a path to a `.json` file. When provided, it overrides all other options:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Inline JSON
|
|
277
|
+
allium realtime prices latest --body '[{"chain":"solana","token_address":"So111..."}]'
|
|
278
|
+
|
|
279
|
+
# From file
|
|
280
|
+
allium realtime prices latest --body tokens.json
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Shell Completions
|
|
284
|
+
|
|
285
|
+
Tab-completion is available for all commands, subcommands, and options. Add one of the following to your shell config:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Bash — add to ~/.bashrc
|
|
289
|
+
eval "$(_ALLIUM_COMPLETE=bash_source allium)"
|
|
290
|
+
|
|
291
|
+
# Zsh — add to ~/.zshrc
|
|
292
|
+
eval "$(_ALLIUM_COMPLETE=zsh_source allium)"
|
|
293
|
+
|
|
294
|
+
# Fish — add to ~/.config/fish/config.fish
|
|
295
|
+
_ALLIUM_COMPLETE=fish_source allium | source
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Reload your shell to activate completions.
|
|
299
|
+
|
|
300
|
+
## Documentation
|
|
301
|
+
|
|
302
|
+
Full API documentation: [docs.allium.so](https://docs.allium.so)
|
|
303
|
+
|
|
304
|
+
## Contributing
|
|
305
|
+
|
|
306
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and release instructions.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
cli/main.py,sha256=9eV1TaJhIbVyp6B6i4mfc82dpfgNavwWleGnJYk1DnM,3042
|
|
3
|
+
cli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
cli/auth/api_key.py,sha256=Md52mwytMhBfJieNnsqtw8DfDt4N7ayXM4SLFXeu1Rs,184
|
|
5
|
+
cli/auth/tempo.py,sha256=bY-Do6zWTZqZE177ynfS7xz-E3VpjyZymw1dxIOT91U,3309
|
|
6
|
+
cli/auth/x402_key.py,sha256=61BGrcwX-GNSvZgr_C--gWn0mhvUgCNNysfgCHGqetI,1047
|
|
7
|
+
cli/auth/x402_privy.py,sha256=GsBImZk_qGliILA6ITZ8kp6OeJ-YaZs_iZ6BXb1kBHk,832
|
|
8
|
+
cli/clients/__init__.py,sha256=Y0lLc0ZL4-Y24sbul6_G_Tc0N0ygnHDaDRKRs-el7Ps,135
|
|
9
|
+
cli/clients/factory.py,sha256=dvq5r2KuDN9sECVq2AyowgmxUTfVPiJQ7NgJ9jCKOr4,2630
|
|
10
|
+
cli/clients/http.py,sha256=d33N3AnVToHNOgJM3BRat732T-CqpoN1kl5A2vv_GR8,2877
|
|
11
|
+
cli/clients/protocol.py,sha256=Z-SrpRuMulcfPxgb3EDADYwHoxfchch9cKbTl1qsd2I,518
|
|
12
|
+
cli/clients/tempo.py,sha256=mtkjjZFoG8SLxNUp0bwvq28oXwJHHAIff4JGlw12NOM,1606
|
|
13
|
+
cli/clients/x402.py,sha256=_RyvjAvKLcM2esP12pobGeasMxjfx4NDbow0i40B5lY,5929
|
|
14
|
+
cli/commands/__init__.py,sha256=QcGvn8b-5lURzbZh-6RJndY1cbaNqQlBDVNW3XJ9Ywc,448
|
|
15
|
+
cli/commands/auth.py,sha256=39gD2z7EdhyuWgiCC7FCBvK3OLTjUFLnFUib-wQGoFg,12256
|
|
16
|
+
cli/commands/explorer.py,sha256=JX0dlzUYWcH4BszdxTHm5yxpg6mQFth9YDdnsy_BbHQ,8930
|
|
17
|
+
cli/commands/mp.py,sha256=se26EafW2o4gTGURIWfEiF_3pjnE-qgFFWFwGscD4-M,4954
|
|
18
|
+
cli/commands/realtime.py,sha256=VQQaq5g_Tkfwvt35xOwbC_3J28tcST8gDoe5w_Bujus,12312
|
|
19
|
+
cli/constants/__init__.py,sha256=FEM6EUJ2FPh_7s0sdO8ejyz4tpQjzBflAibDXgwrHKU,589
|
|
20
|
+
cli/constants/api.py,sha256=ge9bm1FDueq6IEsugZyQwtsmJiCmlWpc7TWqacwiCR4,126
|
|
21
|
+
cli/constants/config.py,sha256=dUTqHRRZ2ZHfIVYxkd4VuQpnp82AbEKJAykEP4mB5-Q,421
|
|
22
|
+
cli/constants/ui.py,sha256=FDJ-zlo4m_OpYImsC_DcRUkjeE7UD2VtSc_0ww287sU,1371
|
|
23
|
+
cli/types/__init__.py,sha256=WHsiIfqi42_J3bXvQwTrqE7zWI_aToYc0Qwc9Fn0YIw,769
|
|
24
|
+
cli/types/config.py,sha256=ZATqu0LdSlc3myBFljQ7Aduqu7dMbG5noWD0rnZkW2w,885
|
|
25
|
+
cli/types/context.py,sha256=YIhRgSajB4Th1L_qme1Wzn3uj79zQPwu3xS4Lem5o84,347
|
|
26
|
+
cli/types/enums.py,sha256=Czw5kId_mu198T_S0AX3-CyaOfM-zAZ-ePH21mSA4Tw,1196
|
|
27
|
+
cli/types/labels.py,sha256=Ye2FdzvoFT8FWiHHKhqiYJcCzn6LNX5u3Y5tm_G0ssk,693
|
|
28
|
+
cli/types/profiles.py,sha256=abgoV-XFauYn1BdtzXBPelra5YVXz5MlYcrTs77zp2I,1142
|
|
29
|
+
cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
cli/utils/async_cmd.py,sha256=kq-A4pccLChOTYBJ-er9PnQgtGBfUoX5R4YLK6mI39g,339
|
|
31
|
+
cli/utils/body.py,sha256=c9kXdbovnD7efivYxMDnPVtgjE5bqvRAB1mVPOYPTF8,1287
|
|
32
|
+
cli/utils/config.py,sha256=Uaaa7eHvxYQatrUMjeF0x04XzD0fp9al82XSd7L_KSQ,2753
|
|
33
|
+
cli/utils/console.py,sha256=vTMOYCq6FEhOeTxrePCHIzg_goyTI25JWFYUIcRYR08,129
|
|
34
|
+
cli/utils/cost_log.py,sha256=tej5nFR1MyKHGBziQEUNR71z001ioW8gx1FM4XFtJSk,3186
|
|
35
|
+
cli/utils/errors.py,sha256=KZ1sjKRmyixIxASG_ZdilD7toFiknwXZmxYutEiRhJI,2062
|
|
36
|
+
cli/utils/options.py,sha256=tvzSxSObkD-7ycg0HlZKsCGzDZ3tpqcy6oTA6HoHaaU,1220
|
|
37
|
+
cli/utils/output.py,sha256=YvqlPu6dwT3VXQtQqYzgFg8xy1dqTHqdZH23ygGy_Xg,3141
|
|
38
|
+
cli/utils/payment.py,sha256=P5SoOuqvwi5U4eEt4QfvKvY1oErvjLxUqNf01jiSVxU,644
|
|
39
|
+
cli/utils/version.py,sha256=pVT9UaRZfREUOA1FwgJGcjYxnjvhNJaKyrNB6B-7uXc,1580
|
|
40
|
+
allium_cli-0.2.0.dist-info/METADATA,sha256=yexBYxaBDAePDv8xDq5_DubQz924-JnAshbvMZCikD8,9562
|
|
41
|
+
allium_cli-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
42
|
+
allium_cli-0.2.0.dist-info/entry_points.txt,sha256=L7gMV98VY361HCDk8oWm0JfSMNmW9Gvoi6R0WNISIZs,41
|
|
43
|
+
allium_cli-0.2.0.dist-info/licenses/LICENSE,sha256=A0_y9c4gKSgfkof9sHKFRbnaT7euA2hl0J1Ucqde5DA,1063
|
|
44
|
+
allium_cli-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Allium
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cli/__init__.py
ADDED
|
File without changes
|
cli/auth/__init__.py
ADDED
|
File without changes
|
cli/auth/api_key.py
ADDED
cli/auth/tempo.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from mpp import Challenge
|
|
9
|
+
from mpp.client.transport import PaymentTransport
|
|
10
|
+
from mpp.methods.tempo import ChargeIntent, TempoAccount, tempo
|
|
11
|
+
from mpp.methods.tempo._defaults import CHAIN_RPC_URLS
|
|
12
|
+
|
|
13
|
+
from cli.types.profiles import TempoProfile
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class TempoPaymentInfo:
|
|
20
|
+
"""cost details from the most recent 402 challenge."""
|
|
21
|
+
|
|
22
|
+
amount: str = "0"
|
|
23
|
+
currency: str = ""
|
|
24
|
+
recipient: str = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _CostCapturingTransport(httpx.AsyncBaseTransport):
|
|
28
|
+
"""captures payment amounts from 402 challenges during transport."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, inner: httpx.AsyncBaseTransport | None = None) -> None:
|
|
31
|
+
self._inner = inner or httpx.AsyncHTTPTransport()
|
|
32
|
+
self.last_payment = TempoPaymentInfo()
|
|
33
|
+
|
|
34
|
+
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
35
|
+
response = await self._inner.handle_async_request(request)
|
|
36
|
+
if response.status_code == 402:
|
|
37
|
+
self._capture_challenge(response)
|
|
38
|
+
return response
|
|
39
|
+
|
|
40
|
+
def _capture_challenge(self, response: httpx.Response) -> None:
|
|
41
|
+
for header in response.headers.get_list("www-authenticate"):
|
|
42
|
+
if not header.lower().startswith("payment "):
|
|
43
|
+
continue
|
|
44
|
+
try:
|
|
45
|
+
challenge = Challenge.from_www_authenticate(header)
|
|
46
|
+
req = challenge.request
|
|
47
|
+
self.last_payment = TempoPaymentInfo(
|
|
48
|
+
amount=str(req.get("amount", "0")),
|
|
49
|
+
currency=str(req.get("currency", "")),
|
|
50
|
+
recipient=str(req.get("recipient", "")),
|
|
51
|
+
)
|
|
52
|
+
return
|
|
53
|
+
except Exception:
|
|
54
|
+
logger.debug("Failed to parse challenge header", exc_info=True)
|
|
55
|
+
|
|
56
|
+
async def aclose(self) -> None:
|
|
57
|
+
await self._inner.aclose()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class TempoResult:
|
|
62
|
+
"""bundles HTTP response with payment metadata."""
|
|
63
|
+
|
|
64
|
+
response: httpx.Response
|
|
65
|
+
payment: TempoPaymentInfo
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_tempo_config(profile: TempoProfile) -> tuple[TempoAccount, str, int]:
|
|
69
|
+
chain_id = int(profile.chain_id)
|
|
70
|
+
rpc_url = CHAIN_RPC_URLS.get(chain_id)
|
|
71
|
+
if rpc_url is None:
|
|
72
|
+
supported = ", ".join(str(cid) for cid in sorted(CHAIN_RPC_URLS))
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Unsupported Tempo chain ID: {chain_id}. Supported chain IDs: {supported}"
|
|
75
|
+
)
|
|
76
|
+
account = TempoAccount.from_key(profile.private_key)
|
|
77
|
+
return account, rpc_url, chain_id
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def tempo_request(
|
|
81
|
+
profile: TempoProfile,
|
|
82
|
+
method: str,
|
|
83
|
+
url: str,
|
|
84
|
+
**kwargs: Any,
|
|
85
|
+
) -> TempoResult:
|
|
86
|
+
account, rpc_url, _ = _get_tempo_config(profile)
|
|
87
|
+
kwargs.setdefault("timeout", 30.0)
|
|
88
|
+
|
|
89
|
+
cost_transport = _CostCapturingTransport()
|
|
90
|
+
payment_transport = PaymentTransport(
|
|
91
|
+
methods=[
|
|
92
|
+
tempo(
|
|
93
|
+
account=account,
|
|
94
|
+
rpc_url=rpc_url,
|
|
95
|
+
intents={"charge": ChargeIntent()},
|
|
96
|
+
)
|
|
97
|
+
],
|
|
98
|
+
inner=cost_transport,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
async with httpx.AsyncClient(transport=payment_transport) as client:
|
|
102
|
+
response = await client.request(method, url, **kwargs)
|
|
103
|
+
return TempoResult(response=response, payment=cost_transport.last_payment)
|
cli/auth/x402_key.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from eth_account import Account
|
|
7
|
+
from eth_account.messages import encode_typed_data
|
|
8
|
+
|
|
9
|
+
from cli.types.profiles import X402KeyProfile
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_signer(profile: X402KeyProfile) -> tuple[str, Callable[[dict[str, Any]], str]]:
|
|
13
|
+
"""return (wallet_address, sign_fn) for x402 using a raw private key."""
|
|
14
|
+
account = Account.from_key(profile.private_key)
|
|
15
|
+
address: str = account.address
|
|
16
|
+
|
|
17
|
+
def sign(typed_data: dict[str, Any]) -> str:
|
|
18
|
+
domain = typed_data["domain"]
|
|
19
|
+
msg = typed_data["message"]
|
|
20
|
+
primary = typed_data.get("primary_type", typed_data.get("primaryType"))
|
|
21
|
+
types = {k: v for k, v in typed_data["types"].items() if k != "EIP712Domain"}
|
|
22
|
+
signable = encode_typed_data(
|
|
23
|
+
primaryType=primary,
|
|
24
|
+
domain_data=domain,
|
|
25
|
+
types=types,
|
|
26
|
+
message=msg,
|
|
27
|
+
)
|
|
28
|
+
signed = account.sign_message(signable)
|
|
29
|
+
return signed.signature.hex()
|
|
30
|
+
|
|
31
|
+
return address, sign
|
cli/auth/x402_privy.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from privy import PrivyAPI
|
|
7
|
+
|
|
8
|
+
from cli.types.profiles import X402PrivyProfile
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_signer(
|
|
12
|
+
profile: X402PrivyProfile,
|
|
13
|
+
) -> tuple[str, Callable[[dict[str, Any]], str]]:
|
|
14
|
+
"""return (wallet_address, sign_fn) for x402 using privy wallet RPC."""
|
|
15
|
+
privy = PrivyAPI(app_id=profile.privy_app_id, app_secret=profile.privy_app_secret)
|
|
16
|
+
wallet = privy.wallets.get(wallet_id=profile.privy_wallet_id)
|
|
17
|
+
address: str = wallet.address
|
|
18
|
+
|
|
19
|
+
def sign(typed_data: dict[str, Any]) -> str:
|
|
20
|
+
result = privy.wallets.rpc(
|
|
21
|
+
wallet_id=profile.privy_wallet_id,
|
|
22
|
+
method="eth_signTypedData_v4",
|
|
23
|
+
params={"typed_data": typed_data},
|
|
24
|
+
)
|
|
25
|
+
return result.data.signature
|
|
26
|
+
|
|
27
|
+
return address, sign
|
cli/clients/__init__.py
ADDED
cli/clients/factory.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from cli.clients.http import AlliumHTTPClient
|
|
8
|
+
from cli.clients.protocol import ClientProtocol
|
|
9
|
+
from cli.clients.x402 import X402Client
|
|
10
|
+
from cli.constants.config import EXIT_AUTH
|
|
11
|
+
from cli.types.profiles import (
|
|
12
|
+
ApiKeyProfile,
|
|
13
|
+
ProfileUnion,
|
|
14
|
+
TempoProfile,
|
|
15
|
+
X402KeyProfile,
|
|
16
|
+
X402PrivyProfile,
|
|
17
|
+
)
|
|
18
|
+
from cli.utils.console import err_console
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _X402SignerAdapter:
|
|
22
|
+
"""adapts auth provider to X402Signer protocol."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
wallet_address: str,
|
|
27
|
+
sign_fn: Callable[[dict[str, Any]], str],
|
|
28
|
+
target_network: str,
|
|
29
|
+
) -> None:
|
|
30
|
+
self._address = wallet_address
|
|
31
|
+
self._sign_fn = sign_fn
|
|
32
|
+
self._target_network = target_network
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def address(self) -> str:
|
|
36
|
+
return self._address
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def target_network(self) -> str:
|
|
40
|
+
return self._target_network
|
|
41
|
+
|
|
42
|
+
def sign(self, typed_data: dict[str, Any]) -> str:
|
|
43
|
+
return self._sign_fn(typed_data)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _make_x402_client(
|
|
47
|
+
base_url: str,
|
|
48
|
+
make_signer: Callable[..., tuple[str, Callable[[dict[str, Any]], str]]],
|
|
49
|
+
profile: X402KeyProfile | X402PrivyProfile,
|
|
50
|
+
) -> X402Client:
|
|
51
|
+
try:
|
|
52
|
+
address, sign_fn = make_signer(profile)
|
|
53
|
+
except Exception as exc:
|
|
54
|
+
err_console.print(f"[red]Failed to initialize wallet signer:[/red] {exc}")
|
|
55
|
+
sys.exit(EXIT_AUTH)
|
|
56
|
+
signer = _X402SignerAdapter(address, sign_fn, str(profile.target_network))
|
|
57
|
+
http = AlliumHTTPClient(base_url=base_url)
|
|
58
|
+
return X402Client(http, signer)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_client(profile: ProfileUnion) -> ClientProtocol:
|
|
62
|
+
"""create an authenticated client from a profile."""
|
|
63
|
+
base_url = profile.base_url
|
|
64
|
+
|
|
65
|
+
if isinstance(profile, ApiKeyProfile):
|
|
66
|
+
from cli.auth.api_key import get_headers
|
|
67
|
+
|
|
68
|
+
return AlliumHTTPClient(base_url=base_url, headers=get_headers(profile))
|
|
69
|
+
|
|
70
|
+
if isinstance(profile, X402KeyProfile):
|
|
71
|
+
from cli.auth.x402_key import make_signer
|
|
72
|
+
|
|
73
|
+
return _make_x402_client(base_url, make_signer, profile)
|
|
74
|
+
|
|
75
|
+
if isinstance(profile, X402PrivyProfile):
|
|
76
|
+
from cli.auth.x402_privy import make_signer
|
|
77
|
+
|
|
78
|
+
return _make_x402_client(base_url, make_signer, profile)
|
|
79
|
+
|
|
80
|
+
if isinstance(profile, TempoProfile):
|
|
81
|
+
from cli.clients.tempo import TempoClient
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
return TempoClient(profile)
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
err_console.print(f"[red]Failed to initialize Tempo client:[/red] {exc}")
|
|
87
|
+
sys.exit(EXIT_AUTH)
|
|
88
|
+
|
|
89
|
+
raise ValueError(f"Unknown profile type: {type(profile)}")
|