avee 0.1.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.
- avee-0.1.0/.gitignore +6 -0
- avee-0.1.0/ASTRA.md +149 -0
- avee-0.1.0/CHANGELOG.md +34 -0
- avee-0.1.0/LICENSE +21 -0
- avee-0.1.0/PKG-INFO +239 -0
- avee-0.1.0/README.md +213 -0
- avee-0.1.0/pyproject.toml +44 -0
- avee-0.1.0/src/avee/__init__.py +33 -0
- avee-0.1.0/src/avee/_client.py +214 -0
- avee-0.1.0/src/avee/_decode.py +79 -0
- avee-0.1.0/src/avee/_operations.py +1384 -0
- avee-0.1.0/src/avee/_request.py +232 -0
- avee-0.1.0/src/avee/_transport.py +219 -0
- avee-0.1.0/src/avee/_types.py +30 -0
- avee-0.1.0/src/avee/astra/__init__.py +65 -0
- avee-0.1.0/src/avee/astra/_transport.py +152 -0
- avee-0.1.0/src/avee/astra/client.py +426 -0
- avee-0.1.0/src/avee/astra/errors.py +60 -0
- avee-0.1.0/src/avee/astra/ids.py +37 -0
- avee-0.1.0/src/avee/astra/models.py +354 -0
- avee-0.1.0/src/avee/astra/price.py +47 -0
- avee-0.1.0/src/avee/astra/py.typed +0 -0
- avee-0.1.0/src/avee/astra/stream.py +378 -0
- avee-0.1.0/src/avee/errors.py +87 -0
- avee-0.1.0/src/avee/models.py +3277 -0
- avee-0.1.0/src/avee/py.typed +0 -0
- avee-0.1.0/src/avee/x402.py +167 -0
- avee-0.1.0/tests/__init__.py +0 -0
- avee-0.1.0/tests/astra/__init__.py +0 -0
- avee-0.1.0/tests/astra/conftest.py +131 -0
- avee-0.1.0/tests/astra/spec/astra.yml +1221 -0
- avee-0.1.0/tests/astra/spec/sdk-contract.json +114 -0
- avee-0.1.0/tests/astra/test_chunking.py +133 -0
- avee-0.1.0/tests/astra/test_contract.py +124 -0
- avee-0.1.0/tests/astra/test_feed_ids.py +150 -0
- avee-0.1.0/tests/astra/test_live.py +33 -0
- avee-0.1.0/tests/astra/test_models.py +148 -0
- avee-0.1.0/tests/astra/test_price.py +77 -0
- avee-0.1.0/tests/astra/test_rest.py +260 -0
- avee-0.1.0/tests/astra/test_status_feed.py +100 -0
- avee-0.1.0/tests/astra/test_stream.py +453 -0
- avee-0.1.0/tests/conftest.py +163 -0
- avee-0.1.0/tests/spec/openapi.yml +7196 -0
- avee-0.1.0/tests/spec/sdk-calls.json +1863 -0
- avee-0.1.0/tests/spec/sdk-contract.json +1656 -0
- avee-0.1.0/tests/test_api_surface.py +21 -0
- avee-0.1.0/tests/test_client.py +188 -0
- avee-0.1.0/tests/test_contract.py +53 -0
- avee-0.1.0/tests/test_hardening.py +320 -0
- avee-0.1.0/tests/test_live.py +22 -0
- avee-0.1.0/tests/test_operations.py +85 -0
- avee-0.1.0/tests/test_x402.py +147 -0
avee-0.1.0/.gitignore
ADDED
avee-0.1.0/ASTRA.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Astra oracle client for Python
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
from avee.astra import AstraClient
|
|
5
|
+
[btc] = AstraClient().latest_prices(["0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"])
|
|
6
|
+
print(btc.price.to_decimal()) # 83095.4425
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Astra is avee's composite exchange index served over the routes and JSON shapes of Pyth's Hermes. No
|
|
10
|
+
key is needed. `avee.astra` wraps its REST, SSE and WebSocket surfaces with typed dataclasses,
|
|
11
|
+
retries and reconnects. The `astra` extra adds `websockets` and `certifi` to `httpx`; Python 3.10+.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pip install 'avee[astra]'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The default host is the preview host `https://astra.preview.avee.tech`; pass a base URL to use another
|
|
18
|
+
(`AstraClient(base_url)`). A base URL with a path prefix works.
|
|
19
|
+
|
|
20
|
+
## REST
|
|
21
|
+
|
|
22
|
+
`AstraClient` is synchronous, `AsyncAstraClient` has the same methods as coroutines. Both are context
|
|
23
|
+
managers and accept your own `httpx.Client` / `httpx.AsyncClient`.
|
|
24
|
+
|
|
25
|
+
| Method | Route |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `price_feeds(query=, asset_type=)` | `GET /v2/price_feeds` |
|
|
28
|
+
| `price_feed(id)` | `GET /v2/price_feeds/{id}` |
|
|
29
|
+
| `latest_prices(ids, ignore_invalid=)` | `GET /v2/updates/price/latest`, `MAX_IDS_PER_URL` (200) ids per request, merged in request order |
|
|
30
|
+
| `prices_at(publish_time, ids)` | `GET /v2/updates/price/{publish_time}` |
|
|
31
|
+
| `prices_in_interval(publish_time, seconds, ids, unique=)` | `GET /v2/updates/price/{publish_time}/{interval}`, flattened oldest first |
|
|
32
|
+
| `feeds(category=)` | `GET /v1/feeds`: both ids, category, live value and status |
|
|
33
|
+
| `feed_ids(pyth_ids=, astra_ids=, category=)` | `GET /v1/feed-ids`: which Pyth ids Astra serves, and their Astra ids |
|
|
34
|
+
| `status(feed=)` | `GET /v1/status`; with `feed`, that feed's entry alone, and a `503` (not `trading`, or `stale`) is returned as the report, not raised |
|
|
35
|
+
| `candles(feed, resolution, from_time, to_time)` | `GET /v1/candles` as `list[Candle]` |
|
|
36
|
+
|
|
37
|
+
Ids are accepted with or without `0x`, in any case, are de-duplicated, and come back lower-case
|
|
38
|
+
without `0x` (`normalize_feed_id`). Times are Unix seconds.
|
|
39
|
+
|
|
40
|
+
**Prices are exact.** `Price` keeps `price` and `conf` as integer strings with `expo`, as sent:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
p.to_decimal() # Decimal('83095.442500000'), exact
|
|
44
|
+
p.to_float() # 83095.4425, correctly rounded
|
|
45
|
+
p.scaled(18) # 83095442500000000000000, int, truncated toward zero
|
|
46
|
+
p.conf_to_decimal(); p.conf_scaled(8)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Streaming
|
|
50
|
+
|
|
51
|
+
Streaming is asynchronous:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
async with AsyncAstraClient() as astra, astra.subscribe(ids, channel="fixed_rate@1000ms") as sub:
|
|
55
|
+
async for update in sub:
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Leaving `async for` does not close a subscription: it keeps reconnecting until `async with` exits or
|
|
60
|
+
`await sub.aclose()` is called.
|
|
61
|
+
|
|
62
|
+
- `transport`: `"ws"` (default, the Hermes WebSocket protocol) or `"sse"`
|
|
63
|
+
(`/v2/updates/price/stream`, which also takes `benchmarks_only`). SSE carries the ids in the URL, so
|
|
64
|
+
it takes at most `MAX_IDS_PER_URL` (200) and refuses more with `AstraValidationError`; the WebSocket
|
|
65
|
+
sends them in a message and takes up to 500.
|
|
66
|
+
- **Reconnect** on any drop, 429 or 5xx: full-jitter backoff from 0.5 s, capped at 30 s, reset after a
|
|
67
|
+
connection has been up for 60 s; `Retry-After` is a floor. The same ids are resubscribed.
|
|
68
|
+
- **Keepalive**: WebSocket pings every `idle_timeout / 2` (45 s / 2) and reconnects when a pong is
|
|
69
|
+
late or the subscribe is not answered within `idle_timeout`; SSE reconnects after `idle_timeout`
|
|
70
|
+
without a byte.
|
|
71
|
+
- **Exactly the new values**: an update older than the last one delivered for its feed, or an exact
|
|
72
|
+
repeat of it (the replay sent on reconnect), is dropped.
|
|
73
|
+
- **Bounded memory**: a consumer that falls behind gets the newest update per feed, never a queue;
|
|
74
|
+
`sub.stats.coalesced` counts what it skipped.
|
|
75
|
+
- **Fatal** (the iterator raises, no retry): 400, 404, 422, and a refused WebSocket subscription.
|
|
76
|
+
Everything else, an unexpected exception included, goes to `on_error` as an `AstraError` and is
|
|
77
|
+
retried. `on_state_change` sees
|
|
78
|
+
`connecting → open → reconnecting → … → closed`.
|
|
79
|
+
|
|
80
|
+
## Errors
|
|
81
|
+
|
|
82
|
+
All errors derive from `AstraError`:
|
|
83
|
+
|
|
84
|
+
| Class | When |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `AstraHTTPError` | non-2xx: `status`, `body`, `problem` (`application/problem+json`), `retry_after`, `retryable` |
|
|
87
|
+
| `AstraTimeoutError` | a network operation exceeded `timeout` (10 s), or a stream went idle |
|
|
88
|
+
| `AstraConnectionError` | network failure, abnormal WebSocket close |
|
|
89
|
+
| `AstraValidationError` | bad input, or a response that breaks the contract (also a `ValueError`) |
|
|
90
|
+
| `AstraSubscriptionError` | the WebSocket subscribe was refused |
|
|
91
|
+
|
|
92
|
+
REST calls are GETs and are retried `max_retries` times (2) on 408, 429, 502, 503, 504, timeouts and
|
|
93
|
+
network errors. `Retry-After` is honoured up to `max_retry_delay` (30 s); a longer one is raised.
|
|
94
|
+
|
|
95
|
+
## Find your feeds
|
|
96
|
+
|
|
97
|
+
Paste the Pyth ids your code already uses and see which ones Astra serves:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
m = client.feed_ids(pyth_ids=["0xe62df6c8…", "0xff61491a…"])
|
|
101
|
+
# m.items: [FeedIdEntry(symbol="Crypto.BTC/USD", pyth_id="e62d…", astra_id="1de7…", …)]
|
|
102
|
+
# m.missing: Pyth ids Astra does not serve
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Every id in `items` works as it is on the Hermes routes: nothing in your code changes for those
|
|
106
|
+
feeds. `feed_ids()` without a filter lists every feed Astra serves, and `missing` is then `None`. Up
|
|
107
|
+
to `MAX_IDS_PER_URL` (200) ids go in one request; a longer list is split, so the URL fits the
|
|
108
|
+
request line a proxy accepts, and merged, sorted by symbol.
|
|
109
|
+
|
|
110
|
+
## Moving from Pyth Hermes
|
|
111
|
+
|
|
112
|
+
Code calling Hermes over HTTP only changes its host to `https://astra.preview.avee.tech`; an `Authorization`
|
|
113
|
+
header is accepted and ignored. `pythclient`'s v1 routes (`/api/latest_price_feeds`, `/ws`) are served
|
|
114
|
+
too.
|
|
115
|
+
|
|
116
|
+
| Hermes | this package |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `GET /v2/updates/price/latest?ids[]=…` | `latest_prices(ids)` |
|
|
119
|
+
| `GET /v2/updates/price/stream` | `subscribe(ids, transport="sse")` |
|
|
120
|
+
| `wss://…/ws` subscribe | `subscribe(ids)` |
|
|
121
|
+
| `int(price) * 10 ** expo` | `price.to_float()`, `to_decimal()`, `scaled(n)` |
|
|
122
|
+
| `binary.data` for `updatePriceFeeds` | always empty: Astra is unsigned and cannot be verified on-chain |
|
|
123
|
+
|
|
124
|
+
A reference-status feed is served over Hermes like a trading one; read `status()` or `feeds()` before
|
|
125
|
+
liquidating on it.
|
|
126
|
+
|
|
127
|
+
## Limits
|
|
128
|
+
|
|
129
|
+
| Limit | Value |
|
|
130
|
+
|---|---|
|
|
131
|
+
| Ids per URL | `MAX_IDS_PER_URL`, 200: the edge rejects a longer request line before it reaches Astra. `latest_prices` and `feed_ids` split a longer list and merge the answers, failing whole when any request fails; SSE refuses more |
|
|
132
|
+
| Ids per call / WebSocket subscription | 500 (historical routes: 100 feeds) |
|
|
133
|
+
| Interval window | 60 s |
|
|
134
|
+
| Candles per request | 5000 |
|
|
135
|
+
| Response body | `max_response_bytes`, 8 MiB |
|
|
136
|
+
| Stream message | `max_message_bytes`, 1 MiB; a larger WebSocket message ends the connection, which reconnects |
|
|
137
|
+
| Stream lifetime | 24 h on the server; the client reconnects |
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
python -m venv .venv && .venv/bin/pip install -e '.[test]'
|
|
143
|
+
.venv/bin/pytest # local fake Astra
|
|
144
|
+
ASTRA_LIVE=1 .venv/bin/pytest tests/astra/test_live.py
|
|
145
|
+
.venv/bin/mypy
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The contract test reads `tests/astra/spec/astra.yml`, the synced copy of the Astra OpenAPI file, and fails
|
|
149
|
+
when a route or field this package reads changes type or disappears.
|
avee-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-09-29
|
|
4
|
+
|
|
5
|
+
- First release: every operation of `/api/v1` as a typed method, models generated from the OpenAPI
|
|
6
|
+
document, tolerant of unknown fields and enum values.
|
|
7
|
+
- Lazy cursor iterators for every paged operation.
|
|
8
|
+
- Typed problem+json errors, GET retries honouring `Retry-After`, rate-limit info of the last response,
|
|
9
|
+
input validated against the specification's bounds, bounded response size.
|
|
10
|
+
- Opt-in x402: with a payer, a spent keyless budget is paid for once and the receipt exposed.
|
|
11
|
+
- Redirects are not followed; an unreadable x402 challenge or a malformed offer (amount not a positive
|
|
12
|
+
integer or above `maxAmountRequired`, no asset or payee) is refused before the payer is asked; a
|
|
13
|
+
repeated cursor is detected in constant memory; public-API compatibility gates (`COMPATIBILITY.md`).
|
|
14
|
+
- The timeout bounds the whole attempt, body included.
|
|
15
|
+
- A class of constants for every value set (`SortBy.VOLUME`, `TimeFrame.H24`), and the server's defaults in each method's docstring.
|
|
16
|
+
|
|
17
|
+
### Astra price oracle (`avee.astra`)
|
|
18
|
+
|
|
19
|
+
- `normalize_feed_id` is listed in `__all__`, so a strictly type-checked caller can import it from
|
|
20
|
+
`avee.astra`.
|
|
21
|
+
- `MAX_IDS_PER_URL` (200): the ids that fit one URL past the edge. `latest_prices` splits a longer list
|
|
22
|
+
into requests of 200 and merges them in request order, raising if any of them fails; `feed_ids`
|
|
23
|
+
splits at the same constant, and `FEED_IDS_PER_REQUEST` stays as its alias. An SSE subscription
|
|
24
|
+
over 200 ids raises `AstraValidationError`: subscribe over WebSocket, which takes up to 500.
|
|
25
|
+
- `feed_ids` for `/v1/feed-ids`: which Pyth ids Astra serves and their Astra ids, with the ones it
|
|
26
|
+
does not serve in `missing`.
|
|
27
|
+
- `status(feed=)`: one feed's health; the server's `503` for a feed that is not `trading`, or is
|
|
28
|
+
`stale`, is a normal answer here and comes back as the report. Only an `application/json` body counts: a proxy page or a problem on `503` stays an error and is retried. An empty feed reads the whole report.
|
|
29
|
+
- First release: Hermes v2 REST (`price_feeds`, latest, at a time, over an interval), Astra's native
|
|
30
|
+
`/v1/feeds`, `/v1/status` and `/v1/candles`.
|
|
31
|
+
- Streaming over WebSocket (Hermes protocol) and SSE with reconnect, resubscribe, keepalive, per-feed
|
|
32
|
+
de-duplication and a bounded, coalescing buffer.
|
|
33
|
+
- Exact prices (integer string + `expo`) with decimal, float and fixed-point helpers.
|
|
34
|
+
- Typed errors, retries for GETs honouring `Retry-After`, and validation of every response.
|
avee-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 avee
|
|
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.
|
avee-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: avee
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Clients for avee: the DEX data API (/api/v1) and Astra, the Hermes-compatible price oracle (avee.astra).
|
|
5
|
+
Project-URL: Homepage, https://github.com/aveetechapp/avee-python
|
|
6
|
+
Project-URL: Issues, https://github.com/aveetechapp/avee-python/issues
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: api-client,crypto,defi,dex,hermes,market-data,ohlcv,oracle,price-feed,pyth,sse,websocket,x402
|
|
10
|
+
Classifier: Framework :: AsyncIO
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: httpx<1,>=0.27
|
|
15
|
+
Provides-Extra: astra
|
|
16
|
+
Requires-Dist: certifi; extra == 'astra'
|
|
17
|
+
Requires-Dist: websockets>=13; extra == 'astra'
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: aiohttp>=3.9; extra == 'test'
|
|
20
|
+
Requires-Dist: certifi; extra == 'test'
|
|
21
|
+
Requires-Dist: mypy>=1.10; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
23
|
+
Requires-Dist: pyyaml>=6; extra == 'test'
|
|
24
|
+
Requires-Dist: websockets>=13; extra == 'test'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# avee DEX data API client for Python
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from avee import AveeClient
|
|
31
|
+
page = AveeClient().pairs(chains=["base"], sort="liquidity", limit=10)
|
|
32
|
+
print(page.items[0].pair_address)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Typed access to every operation of the avee client API (`/api/v1`): pairs, tokens, trades, candles,
|
|
36
|
+
wallets, leaderboards, farms, perps and oracle prices across every chain avee indexes. No key is
|
|
37
|
+
needed. Depends on `httpx` only; Python 3.10+.
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pip install avee
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`AveeClient` is synchronous, `AsyncAveeClient` has the same methods as coroutines. Both are context
|
|
44
|
+
managers and accept your own `httpx.Client` / `httpx.AsyncClient` (`http_client=`). The default host
|
|
45
|
+
is the preview host: `DEFAULT_BASE_URL` equals `PREVIEW_BASE_URL`.
|
|
46
|
+
|
|
47
|
+
## Options
|
|
48
|
+
|
|
49
|
+
| Argument | Default | |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `base_url` | `DEFAULT_BASE_URL` | a path prefix works |
|
|
52
|
+
| `api_key` | `None` | sent as `X-API-Key`; only raises the limits |
|
|
53
|
+
| `timeout` | 30 s | per attempt |
|
|
54
|
+
| `max_retries` | 2 | GETs only |
|
|
55
|
+
| `max_retry_delay` | 30 s | a longer `Retry-After` is raised instead |
|
|
56
|
+
| `max_response_bytes` | 16 MiB | a larger body is refused |
|
|
57
|
+
| `headers`, `http_client` | | |
|
|
58
|
+
| `payer` | `None` | opts into x402, see below |
|
|
59
|
+
|
|
60
|
+
## Operations
|
|
61
|
+
|
|
62
|
+
Path parameters are positional (a chain is a slug or an `int` id); everything else is keyword-only
|
|
63
|
+
with the API's names (`from` becomes `from_`). A batch takes its body fields as keywords
|
|
64
|
+
(`pair_batch(items=[{"chain": "base", "address": "0x…"}])`, dataclasses work too). Input is checked
|
|
65
|
+
against the specification's bounds before anything is sent (`AveeValidationError`, a `ValueError`); a
|
|
66
|
+
batch takes up to the `maxItems` of its request schema (pairs 50, tokens and wallet labels 200).
|
|
67
|
+
Responses are frozen dataclasses from `avee.models`.
|
|
68
|
+
|
|
69
|
+
Every value set in the specification has a class of constants in `avee.models`:
|
|
70
|
+
`sort=SortBy.VOLUME`, `timeframe=TimeFrame.H24`, `pair.status == PairStatus.SCAM`. A value that starts
|
|
71
|
+
with a digit leads with its unit (`"24h"` is `TimeFrame.H24`, `"30d"` is `WalletWindow.D30`). Fields
|
|
72
|
+
and parameters stay `str`, so an unknown value from the server decodes as it is. An omitted parameter
|
|
73
|
+
takes the server's default, listed in each method's docstring.
|
|
74
|
+
|
|
75
|
+
<!-- operations:start -->
|
|
76
|
+
| Method | Route | What it answers |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| **meta** | | |
|
|
79
|
+
| `x402_discovery()` | `GET /.well-known/x402` | Operations payable with x402 and their prices |
|
|
80
|
+
| `status()` | `GET /status` | Liveness and upstream health |
|
|
81
|
+
| `key()` | `GET /key` | Plan and limits of the calling key |
|
|
82
|
+
| `config()` | `GET /config` | Enumerations and defaults |
|
|
83
|
+
| `chains()` | `GET /chains` | Chains served right now |
|
|
84
|
+
| **dex** | | |
|
|
85
|
+
| `search(…)` | `GET /search` | Typeahead across tokens and pairs |
|
|
86
|
+
| `pairs(…), iter_pairs` | `GET /pairs` | Liquidity pair screener |
|
|
87
|
+
| `pair(chain, address)` | `GET /chains/{chain}/pairs/{address}` | One liquidity pair |
|
|
88
|
+
| `pair_trades(chain, address, …), iter_pair_trades` | `GET /chains/{chain}/pairs/{address}/trades` | Trade tape of a pair |
|
|
89
|
+
| `pair_candles(chain, address, …)` | `GET /chains/{chain}/pairs/{address}/candles` | OHLCV candles |
|
|
90
|
+
| `trending(…), iter_trending` | `GET /trending` | Trending pairs right now |
|
|
91
|
+
| `pairs_new(…), iter_pairs_new` | `GET /pairs/new` | Newest pairs on one chain |
|
|
92
|
+
| `launchpad_tokens(…), iter_launchpad_tokens` | `GET /launchpads/tokens` | Launchpad launches by stage (new, bonding or graduated) |
|
|
93
|
+
| `pair_batch(…)` | `POST /pairs/batch` | Many pairs in one call |
|
|
94
|
+
| `perps(…), iter_perps` | `GET /perps` | Perpetual markets |
|
|
95
|
+
| `perp_history(market, …)` | `GET /perps/{market}/history` | Open interest, funding and mark history of a perpetual |
|
|
96
|
+
| `perp_liquidations(…)` | `GET /perps/liquidations` | Daily liquidations of a perpetual or a whole venue |
|
|
97
|
+
| `deployer_tokens(address, …), iter_deployer_tokens` | `GET /deployers/{address}/tokens` | Launches of one deployer, with its reputation card |
|
|
98
|
+
| **token** | | |
|
|
99
|
+
| `token_by_address(chain, address, …)` | `GET /chains/{chain}/tokens/{address}` | Canonical token by chain and address |
|
|
100
|
+
| `token_pairs(chain, address, …)` | `GET /chains/{chain}/tokens/{address}/pairs` | Top pairs of a token by 24h volume |
|
|
101
|
+
| `token_verdict(chain, address)` | `GET /chains/{chain}/tokens/{address}/verdict` | Signed token verdict, submittable to the trust oracle |
|
|
102
|
+
| `token_verdict_proof(chain, address)` | `GET /chains/{chain}/tokens/{address}/proof` | Merkle proof of a verdict at the last published epoch |
|
|
103
|
+
| `token_brief(chain, address)` | `GET /chains/{chain}/tokens/{address}/brief` | Everything needed to decide about a token, in one call |
|
|
104
|
+
| `token_by_id(id, …)` | `GET /tokens/{id}` | Canonical token by id |
|
|
105
|
+
| `token_by_slug(slug, …)` | `GET /tokens/by-slug/{slug}` | Canonical token by slug |
|
|
106
|
+
| `tokens(…), iter_tokens` | `GET /tokens` | Token market list, ranked by market cap |
|
|
107
|
+
| `token_batch(…)` | `POST /tokens/batch` | Resolve many tokens at once |
|
|
108
|
+
| `token_holders(chain, address, …), iter_token_holders` | `GET /chains/{chain}/tokens/{address}/holders` | Top holders of a token |
|
|
109
|
+
| `token_traders(chain, address, …), iter_token_traders` | `GET /chains/{chain}/tokens/{address}/traders` | Wallets that traded a token, with their PNL on it |
|
|
110
|
+
| **farm** | | |
|
|
111
|
+
| `farms(…), iter_farms` | `GET /farms` | Yield farm screener |
|
|
112
|
+
| `farm(chain, address)` | `GET /chains/{chain}/farms/{address}` | One yield farm |
|
|
113
|
+
| **wallet** | | |
|
|
114
|
+
| `wallets(…), iter_wallets` | `GET /wallets` | Rank traders on one chain |
|
|
115
|
+
| `wallet_stats(…)` | `GET /wallets/stats` | Trader population per chain |
|
|
116
|
+
| `wallet_labels_batch(…)` | `POST /wallets/labels/batch` | Behaviour labels for many wallets |
|
|
117
|
+
| `wallet_overview(address, …)` | `GET /wallets/{address}/overview` | One wallet across every chain it traded |
|
|
118
|
+
| `leaderboard(…)` | `GET /leaderboard` | Chain and DEX protocol boards |
|
|
119
|
+
| `wallet_profile(chain, address)` | `GET /chains/{chain}/wallets/{address}` | Trader profile with metrics for every window |
|
|
120
|
+
| `wallet_positions(chain, address, …), iter_wallet_positions` | `GET /chains/{chain}/wallets/{address}/positions` | Open and closed positions of a wallet |
|
|
121
|
+
| `wallet_trades(chain, address, …), iter_wallet_trades` | `GET /chains/{chain}/wallets/{address}/trades` | Raw trade history of a wallet |
|
|
122
|
+
| `wallet_chart(chain, address, …)` | `GET /chains/{chain}/wallets/{address}/chart` | Wallet performance series — PNL and ROI |
|
|
123
|
+
| `wallet_best_trades(chain, address, …)` | `GET /chains/{chain}/wallets/{address}/best-trades` | Best and worst closed trades of a wallet |
|
|
124
|
+
| `wallet_rounds(chain, address, …), iter_wallet_rounds` | `GET /chains/{chain}/wallets/{address}/rounds` | Position rounds — one entry and exit cycle per row |
|
|
125
|
+
| `wallet_funding(chain, address)` | `GET /chains/{chain}/wallets/{address}/funding` | Who funded a wallet |
|
|
126
|
+
| **oracle** | | |
|
|
127
|
+
| `oracle_prices(…)` | `GET /prices` | Latest oracle prices by feed id |
|
|
128
|
+
| `oracle_prices_at(…)` | `GET /prices/at` | Oracle prices at a past moment |
|
|
129
|
+
<!-- operations:end -->
|
|
130
|
+
|
|
131
|
+
## Pagination
|
|
132
|
+
|
|
133
|
+
Each paged operation has an `iter_…` twin (an async iterator on `AsyncAveeClient`) that fetches the
|
|
134
|
+
next page only when the previous one is used up; `options=RequestOptions(max_pages=5)` caps it.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
for trade in client.iter_pair_trades("base", pair, tx_type=["buy"]):
|
|
138
|
+
...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A repeated cursor ends the walk with `AveeValidationError` rather than looping. Prefer `pair_batch`,
|
|
142
|
+
`token_batch` and `wallet_labels_batch` to one call per address.
|
|
143
|
+
|
|
144
|
+
## Errors
|
|
145
|
+
|
|
146
|
+
| Class | When |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `AveeAPIError` | non-2xx: `status`, `code` (branch on it), `detail`, `param`, `request_id`, `rate_limit`, `retry_after`, `retryable`, `paid`, `payment_required`, `payment` |
|
|
149
|
+
| `AveeTimeoutError` | an attempt exceeded `timeout` |
|
|
150
|
+
| `AveeConnectionError` | network failure |
|
|
151
|
+
| `AveeValidationError` | bad input, or a response that breaks the contract or is too large |
|
|
152
|
+
| `AveePaymentError` | the x402 flow stopped before paying: an unreadable challenge or offer, no network in common, the payer declined |
|
|
153
|
+
|
|
154
|
+
GETs are retried on 408, 429, 5xx, timeouts and network errors with full-jitter backoff; a
|
|
155
|
+
`Retry-After` (or, on a 429 without one, the `RateLimit` reset) is waited out when it fits
|
|
156
|
+
`max_retry_delay`. POSTs are never retried. Redirects are not followed, even on an `http_client` that
|
|
157
|
+
follows them: a 3xx is an `AveeAPIError`, so the key and a payment signature never leave the host.
|
|
158
|
+
|
|
159
|
+
## Rate limits
|
|
160
|
+
|
|
161
|
+
`client.last_response` holds the latest `status`, `request_id`, `rate_limit` (`limit`, `remaining`,
|
|
162
|
+
`reset_seconds`, `retry_after_seconds`, `policy`) and the x402 `payment` receipt. Errors carry their own
|
|
163
|
+
`rate_limit`.
|
|
164
|
+
|
|
165
|
+
## Paying past the keyless limit (x402)
|
|
166
|
+
|
|
167
|
+
Off by default: without a payer the client never pays and a spent budget is an ordinary 429. With one
|
|
168
|
+
it sends `Accept-Payment: x402`; a spent budget then answers 402, the client picks the first `exact`
|
|
169
|
+
offer on a network from `payer.networks` (in that order), calls `payer.sign(context)` once, and
|
|
170
|
+
repeats the request once with `PAYMENT-SIGNATURE`. A paid request is never retried; the receipt is
|
|
171
|
+
`last_response.payment`. The payer sees the amount, asset, network and `pay_to` in
|
|
172
|
+
`context.requirement` and declines by returning `None`. `sign` may be a coroutine on
|
|
173
|
+
`AsyncAveeClient`. An offer whose amount is not a positive integer (or exceeds
|
|
174
|
+
`max_amount_required`), or that lacks an asset or `pay_to`, is refused before the payer is asked.
|
|
175
|
+
|
|
176
|
+
The SDK holds no key and depends on no wallet library. With eth-account:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
import secrets, time
|
|
180
|
+
from eth_account import Account
|
|
181
|
+
from avee import AveeClient, PaymentSignature
|
|
182
|
+
|
|
183
|
+
class Payer:
|
|
184
|
+
networks = ["eip155:84532"]
|
|
185
|
+
|
|
186
|
+
def __init__(self, key: str) -> None:
|
|
187
|
+
self.account = Account.from_key(key)
|
|
188
|
+
|
|
189
|
+
def sign(self, ctx):
|
|
190
|
+
r = ctx.requirement
|
|
191
|
+
if int(r.amount) > 10_000:
|
|
192
|
+
return None
|
|
193
|
+
now = int(time.time())
|
|
194
|
+
auth = {"from": self.account.address, "to": r.pay_to, "value": r.amount,
|
|
195
|
+
"validAfter": str(now - 5), "validBefore": str(now + r.max_timeout_seconds),
|
|
196
|
+
"nonce": "0x" + secrets.token_hex(32)}
|
|
197
|
+
signed = self.account.sign_typed_data(
|
|
198
|
+
domain_data={"name": r.extra["name"], "version": r.extra["version"], "chainId": 84532, "verifyingContract": r.asset},
|
|
199
|
+
message_types={"TransferWithAuthorization": [
|
|
200
|
+
{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"},
|
|
201
|
+
{"name": "validAfter", "type": "uint256"}, {"name": "validBefore", "type": "uint256"}, {"name": "nonce", "type": "bytes32"}]},
|
|
202
|
+
message_data={**auth, "value": int(auth["value"]), "validAfter": int(auth["validAfter"]),
|
|
203
|
+
"validBefore": int(auth["validBefore"]), "nonce": bytes.fromhex(auth["nonce"][2:])},
|
|
204
|
+
)
|
|
205
|
+
return PaymentSignature(payload={"signature": "0x" + signed.signature.hex(), "authorization": auth})
|
|
206
|
+
|
|
207
|
+
client = AveeClient(payer=Payer(KEY))
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`sign` may instead return `PaymentSignature(header=...)`, a finished `PAYMENT-SIGNATURE` from any x402
|
|
211
|
+
client library.
|
|
212
|
+
|
|
213
|
+
## Astra price oracle
|
|
214
|
+
|
|
215
|
+
The package also carries the client for Astra, avee's Hermes-compatible price oracle, as
|
|
216
|
+
`avee.astra`, which needs the `astra` extra (`websockets`, `certifi`); `import avee` never loads it.
|
|
217
|
+
|
|
218
|
+
```sh
|
|
219
|
+
pip install 'avee[astra]'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from avee.astra import AstraClient
|
|
224
|
+
[btc] = AstraClient().latest_prices(["0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"])
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
REST, SSE and WebSocket, exact prices, reconnecting streams: [ASTRA.md](ASTRA.md).
|
|
228
|
+
|
|
229
|
+
## Compatibility
|
|
230
|
+
|
|
231
|
+
Models are generated from the OpenAPI document: unknown fields are ignored, enum fields are plain
|
|
232
|
+
`str` and keep unknown values, and the one polymorphic field (`Transaction.block_info`, `event_data`)
|
|
233
|
+
falls back to a `dict` for an unrecognised branch. Inside a major version the package is additive
|
|
234
|
+
only.
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
`pytest` runs against a local fake server; `AVEE_LIVE=1 pytest tests/test_live.py` checks the preview
|
|
239
|
+
host; `mypy` checks `src` in strict mode.
|