agentic-aqua 0.3.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.
@@ -0,0 +1,327 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentic-aqua
3
+ Version: 0.3.0
4
+ Summary: Agentic AQUA — MCP server for Liquid Network and Bitcoin wallet operations
5
+ Project-URL: Homepage, https://github.com/jan3dev/agentic-aqua
6
+ Project-URL: Repository, https://github.com/jan3dev/agentic-aqua
7
+ Project-URL: Documentation, https://github.com/jan3dev/agentic-aqua#readme
8
+ Author-email: Andy <andy@example.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,bitcoin,blockstream,liquid,lwk,mcp,wallet
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: bdkpython>=2.2.0
19
+ Requires-Dist: click>=8.1
20
+ Requires-Dist: coincurve>=21.0.0
21
+ Requires-Dist: cryptography>=42.0.0
22
+ Requires-Dist: lwk>=0.8.0
23
+ Requires-Dist: mcp>=1.0.0
24
+ Requires-Dist: python-dotenv>=1.0.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: black>=24.0.0; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
29
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.2.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Agentic AQUA
34
+
35
+ MCP server and CLI for managing **Liquid Network** and **Bitcoin** wallets through AI assistants like Claude. One mnemonic backs both networks (unified wallet). Also can operates on Lightning network via Boltz swaps.
36
+
37
+ ## Features
38
+
39
+ - **Generate & Import** - Create new wallets or import existing mnemonics
40
+ - **Unified Wallet** - One mnemonic for Liquid and Bitcoin; `unified_balance` shows both
41
+ - **Bitcoin (onchain)** - BIP84 wallets, balance and send via `btc_*` tools (BDK)
42
+ - **Watch-Only** - Import CT descriptors for balance monitoring
43
+ - **Send & Receive** - Full transaction support (L-BTC, BTC, and Liquid assets)
44
+ - **Lightning** - Send and receive via Lightning using L-BTC (via Boltz & Ankara)
45
+ - **Assets** - Native support for L-BTC, USDT, and all Liquid assets
46
+ - **Secure** - Encrypted storage, no remote servers for keys
47
+
48
+ ## Installation
49
+
50
+ > **Quickest way:** just ask your AI agent directly:
51
+ >
52
+ > ```
53
+ > Install this MCP server: https://github.com/jan3dev/agentic-aqua
54
+ > ```
55
+
56
+ ### Recommended (uvx)
57
+
58
+ If you don't have `uvx` installed:
59
+
60
+ ```bash
61
+ # macOS/Linux
62
+ curl -LsSf https://astral.sh/uv/install.sh | sh
63
+
64
+ # Windows
65
+ powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
66
+ ```
67
+
68
+ Configure Claude Desktop (`~/.claude/claude_desktop_config.json`):
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "agentic-aqua": {
74
+ "command": "/full/path/to/uvx",
75
+ "args": ["agentic-aqua"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ Find the full path to `uvx` with:
82
+
83
+ ```bash
84
+ which uvx
85
+ # Example: /Users/yourname/.local/bin/uvx
86
+ ```
87
+
88
+ Restart Claude Desktop and you're ready to use Bitcoin and Liquid wallets.
89
+
90
+ ### For Developers
91
+
92
+ Clone and install from source:
93
+
94
+ ```bash
95
+ git clone https://github.com/jan3dev/agentic-aqua.git
96
+ cd agentic-aqua
97
+ uv sync
98
+ ```
99
+
100
+ Configure Claude Desktop using the full path to `uv` (find with `which uv`):
101
+
102
+ ```json
103
+ {
104
+ "mcpServers": {
105
+ "agentic-aqua": {
106
+ "command": "/full/path/to/uv",
107
+ "args": ["run", "--directory", "/absolute/path/to/agentic-aqua", "python", "-m", "aqua.server"]
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ## Quick Start
114
+
115
+ Once connected, you can ask Claude to:
116
+
117
+ - "Create a new wallet" (creates both Bitcoin and Liquid wallets from one mnemonic)
118
+ - "Show my balance" / "What's my Bitcoin balance?"
119
+ - "Generate a receive address" (Liquid or Bitcoin)
120
+ - "Send 10,000 sats to bc1..." / "Send 0.001 L-BTC to lq1..."
121
+ - "Pay this Lightning invoice: lnbc..."
122
+ - "Receive 50,000 sats via Lightning"
123
+ - "Delete my wallet"
124
+
125
+ ## Available Tools
126
+
127
+ **Wallet Management**
128
+
129
+ | Tool | Description |
130
+ |------|-------------|
131
+ | `lw_generate_mnemonic` | Generate new BIP39 mnemonic |
132
+ | `lw_import_mnemonic` | Import wallet from mnemonic (also creates Bitcoin wallet) |
133
+ | `lw_import_descriptor` | Import watch-only wallet from CT descriptor |
134
+ | `lw_export_descriptor` | Export CT descriptor for watch-only use |
135
+ | `lw_list_wallets` | List all wallets |
136
+ | `delete_wallet` | Delete a wallet and all its cached data |
137
+
138
+ **Liquid (lw_*)**
139
+
140
+ | Tool | Description |
141
+ |------|-------------|
142
+ | `lw_balance` | Get wallet balances (all assets) |
143
+ | `lw_address` | Generate Liquid receive address (lq1...) |
144
+ | `lw_send` | Send L-BTC |
145
+ | `lw_send_asset` | Send any Liquid asset (USDT, etc.) |
146
+ | `lw_transactions` | Transaction history |
147
+ | `lw_tx_status` | Get transaction status (txid or explorer URL) |
148
+
149
+ **Bitcoin (btc_*)**
150
+
151
+ | Tool | Description |
152
+ |------|-------------|
153
+ | `btc_balance` | Get Bitcoin balance (sats) |
154
+ | `btc_address` | Generate Bitcoin receive address (bc1...) |
155
+ | `btc_transactions` | Bitcoin transaction history |
156
+ | `btc_send` | Send BTC |
157
+
158
+ **Unified**
159
+
160
+ | Tool | Description |
161
+ |------|-------------|
162
+ | `unified_balance` | Get balance for both Bitcoin and Liquid |
163
+
164
+ **Lightning**
165
+
166
+ | Tool | Description |
167
+ |------|-------------|
168
+ | `lightning_receive` | Generate a Lightning invoice to receive L-BTC (100–25,000,000 sats) |
169
+ | `lightning_send` | Pay a Lightning invoice using L-BTC via Boltz (~0.1% fee) |
170
+ | `lightning_transaction_status` | Check status of a Lightning swap (send or receive) |
171
+
172
+ ## CLI
173
+
174
+ Agentic AQUA also ships with a Click-based CLI (`aqua`) for direct, scriptable wallet operations. It exposes the same operations as the MCP tools.
175
+
176
+ ```bash
177
+ # Discover commands
178
+ aqua --help
179
+ aqua wallet --help
180
+ aqua btc --help
181
+ aqua liquid --help
182
+ aqua lightning --help
183
+
184
+ # Wallet management
185
+ aqua wallet generate-mnemonic
186
+ aqua wallet import-mnemonic --wallet-name default --network mainnet
187
+ aqua wallet list
188
+ aqua wallet export-descriptor --wallet-name default
189
+ aqua wallet delete --wallet-name old
190
+
191
+ # Balances
192
+ aqua balance # unified (BTC + Liquid)
193
+ aqua btc balance --wallet-name default
194
+ aqua liquid balance --wallet-name default
195
+
196
+ # Receive addresses
197
+ aqua btc address
198
+ aqua liquid address
199
+
200
+ # Send (--wallet-name is required for on-chain sends)
201
+ aqua btc send --wallet-name default --address bc1... --amount 10000
202
+ aqua liquid send --wallet-name default --address lq1... --amount 50000
203
+ aqua liquid send-asset --wallet-name default --address lq1... --amount 1000000 --asset-id <asset_id>
204
+ # (or use --asset-ticker USDt instead of --asset-id)
205
+
206
+ # Transaction history & status
207
+ aqua btc transactions
208
+ aqua liquid transactions
209
+ aqua liquid tx-status --tx <txid|explorer_url>
210
+
211
+ # Lightning (L-BTC via Boltz / Ankara)
212
+ aqua lightning receive --amount 50000
213
+ aqua lightning send --invoice lnbc...
214
+ aqua lightning status --swap-id <id>
215
+
216
+ # Run as MCP stdio server
217
+ aqua serve # recommended
218
+ aqua-mcp # direct MCP entrypoint
219
+ ```
220
+
221
+ Output defaults to a human-readable table on the terminal and JSON when piped. Force a format with `--format json` or `--format pretty`.
222
+
223
+ ### Loading mnemonics safely (env vars from a text file)
224
+
225
+ Avoid pasting mnemonics into shell prompts or chat with an AI agent — both shell history and agent transcripts may persist them. The recommended workflow is to keep secrets in a local text file with restricted permissions and load them as environment variables.
226
+
227
+ 1. Create `~/.aqua/secrets.env` (or any path you prefer) and lock it down:
228
+
229
+ ```bash
230
+ mkdir -p ~/.aqua
231
+ cat > ~/.aqua/secrets.env <<'EOF'
232
+ AQUA_MNEMONIC="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
233
+ AQUA_PASSWORD="Wild-red-dolphin-386"
234
+ EOF
235
+ chmod 600 ~/.aqua/secrets.env
236
+ ```
237
+
238
+ 2. Source it before running CLI commands and clear it afterwards:
239
+
240
+ ```bash
241
+ set -a; . ~/.aqua/secrets.env; set +a
242
+ aqua-cli wallet import-mnemonic --wallet-name default --network mainnet
243
+ unset AQUA_MNEMONIC AQUA_PASSWORD
244
+ ```
245
+
246
+ `aqua-cli` also auto-loads a `.env` file from the project root via `python-dotenv` if you prefer a per-project file.
247
+
248
+ The CLI honors these variables out of the box:
249
+
250
+ | Variable | Used by |
251
+ |----------|---------|
252
+ | `AQUA_MNEMONIC` | `wallet import-mnemonic` |
253
+ | `AQUA_PASSWORD` | `wallet import-mnemonic`, `btc send`, `liquid send`, `liquid send-asset`, `lightning send`, `lightning receive` |
254
+ | `AQUA_<OPTION>` | Any CLI option (Click `auto_envvar_prefix="AQUA"`) — e.g. `AQUA_WALLET_NAME=default` |
255
+
256
+ If you would rather pipe secrets from a password manager, every secret-bearing command also accepts `--mnemonic-stdin` / `--password-stdin`:
257
+
258
+ ```bash
259
+ pass show crypto/aqua-mnemonic | aqua-cli wallet import-mnemonic --mnemonic-stdin
260
+ ```
261
+
262
+ Tips:
263
+ - Never commit `.env` or `secrets.env` files (the project's `.gitignore` already excludes them).
264
+ - Prefer `set -a; . file; set +a` over `export $(cat file)` — the former tolerates spaces and quotes inside values.
265
+ - After importing a wallet, the mnemonic is no longer needed for day-to-day operations; only `AQUA_PASSWORD` is used to sign transactions.
266
+
267
+ ## Configuration
268
+
269
+ Default config location: `~/.aqua/config.json`
270
+
271
+ > **Migrating from `aqua-mcp`?** The config dir moved from `~/.aqua-mcp` to `~/.aqua`. There is no automatic migration. To carry over your wallets, run once:
272
+ >
273
+ > ```bash
274
+ > mv ~/.aqua-mcp ~/.aqua
275
+ > ```
276
+
277
+ ```json
278
+ {
279
+ "network": "mainnet",
280
+ "default_wallet": "default",
281
+ "electrum_url": null,
282
+ "auto_sync": true
283
+ }
284
+ ```
285
+
286
+ ## Security
287
+
288
+ Mnemonics are encrypted at rest using a password (PBKDF2 + Fernet). Without a password, the mnemonic is stored base64-encoded only — use a password for real funds. **Note:** this password is NOT a BIP39 passphrase; the derived Liquid/Bitcoin keys depend solely on the mnemonic, so the same mnemonic restores identical descriptors in any BIP39-compliant wallet (AQUA, Blockstream Green, Jade, etc.).
289
+
290
+ For maximum security you can:
291
+ 1. Generate wallet on an air-gapped device
292
+ 2. Export the CT descriptor
293
+ 3. Import as watch-only on your daily machine
294
+
295
+ All private key operations happen locally. Only blockchain sync uses Blockstream's public servers.
296
+
297
+ ## Development
298
+
299
+ ```bash
300
+ # Install with dev dependencies
301
+ uv sync --all-extras
302
+
303
+ # Run tests
304
+ uv run python -m pytest tests/
305
+
306
+ # Format code
307
+ uv run black src/
308
+ uv run ruff check src/
309
+ ```
310
+
311
+ ## Architecture
312
+
313
+ ```
314
+ AI Assistant ←→ MCP Server (Python) ←→ LWK (Liquid) ──→ Electrum/Esplora
315
+
316
+ ├──→ BDK (Bitcoin) ──→ Esplora (Blockstream)
317
+
318
+ └──→ Boltz / Ankara ──→ Lightning
319
+ ```
320
+
321
+ ## Credits
322
+
323
+ Built with:
324
+ - [LWK](https://github.com/Blockstream/lwk) - Liquid Wallet Kit by Blockstream
325
+ - [BDK](https://github.com/bitcoindevkit/bdk-python) - Bitcoin Development Kit
326
+ - [MCP](https://modelcontextprotocol.io/) - Model Context Protocol
327
+ - [Boltz](https://boltz.exchange/) - Submarine swaps for Lightning
@@ -0,0 +1,25 @@
1
+ aqua/__init__.py,sha256=FjAAbyjKfcG8H4gclAuCM68qVcFKD-P5O36HclYx51o,109
2
+ aqua/ankara.py,sha256=AUXPzobm_LhOLlJCJjkq1jBs1e-kCN-DF1XIu0kCtSQ,2972
3
+ aqua/assets.py,sha256=UnN-5CZc_Bj3QyCs_dsHMmsXKh0moD10DZjj4GNo32s,3138
4
+ aqua/bitcoin.py,sha256=uqiicA4Sc-b-oi5Gwl6z3KPp-eZg12xjNm-WRrHAQ4M,13836
5
+ aqua/boltz.py,sha256=oomJVqYjTPWAqKTaT-Lpx5gTHoQ6par5Mb6RBw3aa68,5259
6
+ aqua/lightning.py,sha256=KCxR14EEr1z5On7xo6dbpIdi5r-Mv88STVtHtHU93e0,14115
7
+ aqua/server.py,sha256=pPmFU2tjnYxNeIw-tjFPRh89xnLGi1Gv9RKJQFEmCBA,42587
8
+ aqua/storage.py,sha256=yGAcYQKPK79vRfnnGGh12-xWaSZdNoTy330ePBQOazc,12238
9
+ aqua/tools.py,sha256=GSFweNv8V3nta6Vf5k0tGm0QWppZZt2RBaWsp6Q6IKw,22133
10
+ aqua/wallet.py,sha256=nVUjRckY8V_J4qEt8H85KAK8gF8DPSZEvepwKjiJ_ZU,11409
11
+ aqua/cli/__init__.py,sha256=iDaGtdJuhg5j7eV4H_EARyjSIUNWM-uWx0ZpcCE9XUU,90
12
+ aqua/cli/btc.py,sha256=yMSkiR2MprUnnCRBhCzDFupPLzpsfh5EmcIehKffWwA,2605
13
+ aqua/cli/commands.py,sha256=CSzabNlYzXGJqzc8W1lJJNlPY4VtzXOwP1An6r51ts8,857
14
+ aqua/cli/lightning.py,sha256=dMJZ5JkPtSEMMcH8Idm-uYIpkeJEIQygYI_j-JOC-xc,2641
15
+ aqua/cli/liquid.py,sha256=Zs32RdDnN8zQ8FpYfNwBhQwhqT0DfS5blL_8BpAcKtE,6072
16
+ aqua/cli/main.py,sha256=pP1pbJRUwtkO3_oE9vza7uBWb0BZ2fUvZnFfQGTkwt0,1360
17
+ aqua/cli/output.py,sha256=FWa-5SUsxkJ8or7GqMDx1BZEH0mL64Y_SN_dC20kU58,2167
18
+ aqua/cli/password.py,sha256=JiSZAhqEUHuoaRzA-_TY3MT7A1l3FujZnm46SDl00S4,1556
19
+ aqua/cli/serve.py,sha256=v6dX5D5gg61orH_1hOHqo7EGVuieC7a3G1QR06LIvFI,261
20
+ aqua/cli/wallet.py,sha256=xA4MwyCt9ynABBTgFxORThZktc1YgKTJGIsic1_8hnA,4478
21
+ agentic_aqua-0.3.0.dist-info/METADATA,sha256=pneU99x5bYMMFRyexJZG5MkRQ-0E2PdIYwNDqECYIWw,10694
22
+ agentic_aqua-0.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
23
+ agentic_aqua-0.3.0.dist-info/entry_points.txt,sha256=daLwm0HZ381CB4DGSxTYMOAd6LEeVjXOds9jeRk8Ntg,71
24
+ agentic_aqua-0.3.0.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
25
+ agentic_aqua-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ aqua = aqua.cli.main:cli
3
+ aqua-mcp = aqua.server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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.
aqua/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Agentic AQUA - Manage Liquid Network and Bitcoin wallets through AI assistants."""
2
+
3
+ __version__ = "0.3.0"
aqua/ankara.py ADDED
@@ -0,0 +1,90 @@
1
+ """Ankara backend integration for Lightning → L-BTC swaps."""
2
+
3
+ import json
4
+ import os
5
+ import urllib.error
6
+ import urllib.request
7
+ from dataclasses import asdict, dataclass
8
+ from typing import Optional
9
+
10
+ # API URL with environment variable override
11
+ ANKARA_API_URL = os.environ.get("ANKARA_API_URL", "https://ankara.aquabtc.com")
12
+
13
+
14
+ @dataclass
15
+ class AnkaraSwapInfo:
16
+ """Holds all data for an active/completed Ankara Lightning swap."""
17
+
18
+ swap_id: str
19
+ boltz_swap_id: str
20
+ invoice: str
21
+ address: str
22
+ amount: int
23
+ wallet_name: str
24
+ status: str # "pending" | "settled" | "failed"
25
+ created_at: str
26
+ preimage: Optional[str] = None
27
+
28
+ def to_dict(self) -> dict:
29
+ return asdict(self)
30
+
31
+ @classmethod
32
+ def from_dict(cls, data: dict) -> "AnkaraSwapInfo":
33
+ return cls(**data)
34
+
35
+
36
+ class AnkaraClient:
37
+ """HTTP client for Ankara backend API."""
38
+
39
+ def __init__(self):
40
+ self.base_url = ANKARA_API_URL
41
+
42
+ def _api_request(self, method: str, path: str, body: dict | None = None) -> dict:
43
+ """Make HTTP request to Ankara API."""
44
+ url = f"{self.base_url}{path}"
45
+ data = json.dumps(body).encode() if body else None
46
+ req = urllib.request.Request(
47
+ url,
48
+ data=data,
49
+ method=method,
50
+ headers={
51
+ "Content-Type": "application/json",
52
+ "User-Agent": "agentic-aqua",
53
+ },
54
+ )
55
+ try:
56
+ with urllib.request.urlopen(req, timeout=30) as resp:
57
+ return json.loads(resp.read().decode())
58
+ except urllib.error.HTTPError as e:
59
+ # Try to extract error message from response body
60
+ detail = ""
61
+ try:
62
+ err_body = json.loads(e.read().decode())
63
+ detail = err_body.get("error", err_body.get("message", ""))
64
+ except Exception:
65
+ pass
66
+ msg = f"Ankara API error ({e.code} {method} {path})"
67
+ if detail:
68
+ msg += f": {detail}"
69
+ raise RuntimeError(msg) from e
70
+ except urllib.error.URLError as e:
71
+ raise RuntimeError(f"Ankara API unreachable ({method} {path}): {e.reason}") from e
72
+
73
+ def create_swap(self, amount: int, address: str) -> dict:
74
+ """POST /api/v1/lightning/swaps/create/ - create a new swap."""
75
+ return self._api_request(
76
+ "POST",
77
+ "/api/v1/lightning/swaps/create/",
78
+ {
79
+ "amount": amount,
80
+ "address": address,
81
+ },
82
+ )
83
+
84
+ def claim_swap(self, swap_id: str) -> dict:
85
+ """POST /api/v1/lightning/swaps/{swap_id}/claim/ - claim a swap."""
86
+ return self._api_request("POST", f"/api/v1/lightning/swaps/{swap_id}/claim/")
87
+
88
+ def verify_swap(self, swap_id: str) -> dict:
89
+ """GET /api/v1/lightning/lnurlp/verify/{swap_id} - verify swap status."""
90
+ return self._api_request("GET", f"/api/v1/lightning/lnurlp/verify/{swap_id}")
aqua/assets.py ADDED
@@ -0,0 +1,92 @@
1
+ """Known Liquid Network asset registry."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+
7
+ @dataclass
8
+ class AssetInfo:
9
+ """Metadata for a known Liquid asset."""
10
+
11
+ asset_id: str
12
+ name: str
13
+ ticker: str
14
+ logo: str
15
+ precision: int # Number of decimal places (e.g. 8 means divide by 10^8)
16
+
17
+
18
+ # Mainnet known assets
19
+ MAINNET_ASSETS: dict[str, AssetInfo] = {
20
+ info.asset_id: info
21
+ for info in [
22
+ AssetInfo(
23
+ asset_id="6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d",
24
+ name="Liquid Bitcoin",
25
+ ticker="L-BTC",
26
+ logo="https://aqua-asset-logos.s3.us-west-2.amazonaws.com/L-BTC.svg",
27
+ precision=8,
28
+ ),
29
+ AssetInfo(
30
+ asset_id="ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2",
31
+ name="Tether USDt",
32
+ ticker="USDt",
33
+ logo="https://aqua-asset-logos.s3.us-west-2.amazonaws.com/USDt.svg",
34
+ precision=8,
35
+ ),
36
+ AssetInfo(
37
+ asset_id="3438ecb49fc45c08e687de4749ed628c511e326460ea4336794e1cf02741329e",
38
+ name="JPY Stablecoin",
39
+ ticker="JPYS",
40
+ logo="https://aqua-asset-logos.s3.us-west-2.amazonaws.com/JPYS.svg",
41
+ precision=8,
42
+ ),
43
+ AssetInfo(
44
+ asset_id="18729918ab4bca843656f08d4dd877bed6641fbd596a0a963abbf199cfeb3cec",
45
+ name="PEGx EURx",
46
+ ticker="EURx",
47
+ logo="https://aqua-asset-logos.s3.us-west-2.amazonaws.com/EURx.svg",
48
+ precision=8,
49
+ ),
50
+ AssetInfo(
51
+ asset_id="26ac924263ba547b706251635550a8649545ee5c074fe5db8d7140557baaf32e",
52
+ name="Mexas",
53
+ ticker="MEX",
54
+ logo="https://aqua-asset-logos.s3.us-west-2.amazonaws.com/MEX.svg",
55
+ precision=8,
56
+ ),
57
+ AssetInfo(
58
+ asset_id="02f22f8d9c76ab41661a2729e4752e2c5d1a263012141b86ea98af5472df5189",
59
+ name="DePix",
60
+ ticker="DePix",
61
+ logo="https://aqua-asset-logos.s3.us-west-2.amazonaws.com/DePix.svg",
62
+ precision=8,
63
+ ),
64
+ ]
65
+ }
66
+
67
+ # Testnet known assets (policy asset only for now)
68
+ TESTNET_ASSETS: dict[str, AssetInfo] = {}
69
+
70
+
71
+ def lookup_asset(asset_id: str, network: str = "mainnet") -> Optional[AssetInfo]:
72
+ """Look up asset metadata by ID. Returns None if unknown."""
73
+ registry = MAINNET_ASSETS if network == "mainnet" else TESTNET_ASSETS
74
+ return registry.get(asset_id)
75
+
76
+
77
+ def resolve_asset_name(asset_id: str, network: str = "mainnet") -> str:
78
+ """Return ticker if known, otherwise truncated asset ID."""
79
+ info = lookup_asset(asset_id, network)
80
+ if info:
81
+ return info.ticker
82
+ return asset_id[:8] + "..."
83
+
84
+
85
+ def lookup_asset_by_ticker(ticker: str, network: str = "mainnet") -> Optional[AssetInfo]:
86
+ """Look up asset metadata by ticker (case-insensitive). Returns None if unknown."""
87
+ registry = MAINNET_ASSETS if network == "mainnet" else TESTNET_ASSETS
88
+ target = ticker.lower()
89
+ for info in registry.values():
90
+ if info.ticker.lower() == target:
91
+ return info
92
+ return None