orblivion 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.
- orblivion-0.1.0/LICENSE +21 -0
- orblivion-0.1.0/MANIFEST.in +4 -0
- orblivion-0.1.0/PKG-INFO +235 -0
- orblivion-0.1.0/README.md +217 -0
- orblivion-0.1.0/orblivion.egg-info/PKG-INFO +235 -0
- orblivion-0.1.0/orblivion.egg-info/SOURCES.txt +34 -0
- orblivion-0.1.0/orblivion.egg-info/dependency_links.txt +1 -0
- orblivion-0.1.0/orblivion.egg-info/requires.txt +2 -0
- orblivion-0.1.0/orblivion.egg-info/top_level.txt +1 -0
- orblivion-0.1.0/pit_sdk/__init__.py +70 -0
- orblivion-0.1.0/pit_sdk/_util.py +100 -0
- orblivion-0.1.0/pit_sdk/account.py +428 -0
- orblivion-0.1.0/pit_sdk/auth.py +70 -0
- orblivion-0.1.0/pit_sdk/client.py +2322 -0
- orblivion-0.1.0/pit_sdk/constants.py +257 -0
- orblivion-0.1.0/pit_sdk/curve_session.py +517 -0
- orblivion-0.1.0/pit_sdk/delegation.py +239 -0
- orblivion-0.1.0/pit_sdk/eip712.py +129 -0
- orblivion-0.1.0/pit_sdk/errors.py +139 -0
- orblivion-0.1.0/pit_sdk/execution.py +172 -0
- orblivion-0.1.0/pit_sdk/fast.py +1204 -0
- orblivion-0.1.0/pit_sdk/hosted.py +410 -0
- orblivion-0.1.0/pit_sdk/http.py +360 -0
- orblivion-0.1.0/pit_sdk/league.py +588 -0
- orblivion-0.1.0/pit_sdk/money.py +109 -0
- orblivion-0.1.0/pit_sdk/onchain.py +326 -0
- orblivion-0.1.0/pit_sdk/plans.py +455 -0
- orblivion-0.1.0/pit_sdk/pricing.py +260 -0
- orblivion-0.1.0/pit_sdk/risks.py +332 -0
- orblivion-0.1.0/pit_sdk/router.py +454 -0
- orblivion-0.1.0/pit_sdk/rpc.py +217 -0
- orblivion-0.1.0/pit_sdk/signer.py +179 -0
- orblivion-0.1.0/pit_sdk/tokens.py +235 -0
- orblivion-0.1.0/pit_sdk/verify.py +1849 -0
- orblivion-0.1.0/pyproject.toml +33 -0
- orblivion-0.1.0/setup.cfg +4 -0
orblivion-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Orblivion
|
|
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.
|
orblivion-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orblivion
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Trade on Robinhood Chain through Orblivion from an Orbio agent: every transaction is decoded and checked locally before the agent's key signs it.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Documentation, https://orblivion.com/docs/
|
|
7
|
+
Keywords: orblivion,orbio,robinhood-chain,trading,agents,uniswap,sdk
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: eth-account>=0.13.6
|
|
16
|
+
Requires-Dist: eth-abi>=5
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# orblivion
|
|
20
|
+
|
|
21
|
+
Trade on Robinhood Chain (chain 4663) through Orblivion, the trading layer for Orbio agents, from Python. Orblivion
|
|
22
|
+
finds the route, builds the transaction and simulates it. Your agent's own key signs it, and Orblivion's relay sends it.
|
|
23
|
+
|
|
24
|
+
**Nothing the server returns is signed until this SDK has decoded and checked it locally**, and every mined transaction
|
|
25
|
+
is checked again against what was signed. The decoder and the checks are this package's own: the same rules, rule names
|
|
26
|
+
and error codes as the TypeScript SDK (`@orblivion/sdk`), proven against the same recorded answers.
|
|
27
|
+
|
|
28
|
+
Python 3.10 or later. Depends on `eth-account` and `eth-abi` only; HTTP uses the standard library.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install orblivion
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quickstart
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import os
|
|
40
|
+
from eth_account import Account
|
|
41
|
+
from orblivion import OrblivionClient
|
|
42
|
+
|
|
43
|
+
client = OrblivionClient(account=Account.from_key(os.environ["AGENT_KEY"]),
|
|
44
|
+
max_notional_usd=25) # the client refuses anything bigger, whatever the server says
|
|
45
|
+
|
|
46
|
+
client.sign_in() # optional: the first call signs in for you
|
|
47
|
+
quote = client.quote(sell="ETH", buy="USDG", amount_usd=10)
|
|
48
|
+
plan = client.plan(sell="ETH", buy="USDG", amount_usd=10) # every check, nothing signed
|
|
49
|
+
dry = client.trade(sell="ETH", buy="USDG", amount_usd=10, dry_run=True)
|
|
50
|
+
sent = client.trade(sell="ETH", buy="USDG", amount_usd=10) # checked, signed, sent, checked again
|
|
51
|
+
print(sent.tx_hash, sent.amount_out, sent.min_out, sent.fee)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The client talks to `https://api.orblivion.com` unless told otherwise: pass another server first, or leave it out
|
|
55
|
+
(`OrblivionClient(account=...)`) and set `ORBLIVION_API_URL`. Plain `http` is accepted only for `127.0.0.1`,
|
|
56
|
+
`localhost` and `::1`; anything else must be `https`.
|
|
57
|
+
|
|
58
|
+
`headers={...}` adds headers to every request to the Orblivion API (never to the RPC or the sequencer); their values
|
|
59
|
+
are never shown in an error or a repr.
|
|
60
|
+
|
|
61
|
+
## Signing in
|
|
62
|
+
|
|
63
|
+
`sign_in()` asks the server for an EIP-4361 (Sign-In with Ethereum) message, checks it names this server's host, chain
|
|
64
|
+
4663, this wallet and a short expiry, signs it, and receives an API key bound to that wallet, kept in memory only.
|
|
65
|
+
Pass `api_key=` to reuse one; the client checks it is bound to its wallet first.
|
|
66
|
+
|
|
67
|
+
## Quote, plan, trade
|
|
68
|
+
|
|
69
|
+
| Call | What it does |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `quote(sell=, buy=, amount_usd= \| amount_in=, slippage_bps=None, shield=False)` | The server's best route and what it discloses. Read-only; the server's word. |
|
|
72
|
+
| `plan(sell=, buy=, amount_usd= \| amount_in=, ...)` | The trade's plan, checked exactly as `trade` checks it, with **nothing signed**. A sale of an ERC-20 token whose swap is built only around a Permit2 signature stops after the permit's checks (`complete=False`). |
|
|
73
|
+
| `trade(sell=, buy=, amount_usd= \| amount_in=, slippage_bps=None, approval="budget", dry_run=False, accept_risks=None, shield=False, policies=None)` | Prepare, check, sign, send through the relay, wait for the receipt, check what was mined. With `dry_run=True` it stops after signing and sends nothing. |
|
|
74
|
+
| `search_tokens(q, tier=None, kind=None, limit=20, shield=False)` | Search the token registry. Symbols are copied by impersonators: pick a token by its address. |
|
|
75
|
+
| `inspect_token(token, shield=False)` | One token as a trade would see it, checked against the SDK's own reads of the chain, refusing nothing. |
|
|
76
|
+
| `wallet.balances(tokens=None, who=None)` | ETH and token balances, read from the chain. |
|
|
77
|
+
| `approvals.status()`, `approvals.revoke()` | The wallet's Permit2 approvals; take them back. |
|
|
78
|
+
|
|
79
|
+
Core tokens are named by symbol (`ETH`, `WETH`, `USDG`, `cbBTC`, `ORBIO`, `NVDA`, `SPY`); every other token by its
|
|
80
|
+
contract address. Amounts are `int` in the token's smallest unit (`parse_token("1.5", "USDG")`), or `amount_usd`.
|
|
81
|
+
|
|
82
|
+
## Fast mode
|
|
83
|
+
|
|
84
|
+
For an agent that holds its own key and trades a major pair, `fast()` cuts the path from decision to relay to one call:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
fast = client.fast().start() # keeps its Chainlink marks, nonce, base fee, allowances and fee tier warm
|
|
88
|
+
r = fast.trade("ETH", "USDG", amount_usd=10) # or client.fast_trade(...)
|
|
89
|
+
print(r.path, r.tx_hash, r.timings["decision_to_accepted_ms"])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The SDK builds the transaction itself on a pinned route (`FAST_ROUTES`: ETH, WETH, USDG, NVDA and SPY; never cbBTC,
|
|
93
|
+
which takes the normal path before anything is signed), with `min_out` from its own quote of that route (kept warm; the
|
|
94
|
+
tolerance at most the server's pair default), your fee tier in ETH, WETH or USDG by the fee currency rule, and the
|
|
95
|
+
output to your wallet; checks it with `verify_fast` (the server's own rules), signs it and sends it to
|
|
96
|
+
`POST /api/v1/live/fast`, which rebuilds the calldata and relays only an exact match.
|
|
97
|
+
|
|
98
|
+
- Anything stale, an ineligible pair (ORBIO, launchpad and long-tail tokens, a stock token after hours), a token you
|
|
99
|
+
haven't approved to Permit2 yet, a pool further under the Chainlink marks than its fee and the pair's band (75 bps
|
|
100
|
+
against USDG, 125 for a cross pair), or marks from your RPC more than 3% from the server's falls back to `trade()`
|
|
101
|
+
before anything is signed (`r.path == "fallback"`, `r.fallback_reason`); `fallback=False` raises instead.
|
|
102
|
+
- One `trade()` never lets two transactions land. After a refusal the server answers before relaying, the SDK asks your
|
|
103
|
+
own RPC about the signed transaction first: on chain, it is the result (`r.note`); its nonce taken by another
|
|
104
|
+
transaction, the next one takes a fresh nonce; otherwise the retry or the fallback signs at the same nonce. A refusal
|
|
105
|
+
that may follow a relay (`NONCE` without that proof, `IN_FLIGHT`, `SUBMIT_REJECTED`, `SEQUENCER_UNREACHABLE`, no
|
|
106
|
+
answer) raises with the hash. `trade(nonce=n)` signs the normal path's first transaction at `n` (and refuses when the
|
|
107
|
+
wallet's pending nonce isn't `n`).
|
|
108
|
+
- `tolerance_bps` tightens the minimum (it can't exceed the server's cap); `min_out` sets your own when it is higher.
|
|
109
|
+
The slippage table is pinned in the SDK (`FAST_TOLERANCES`); the server's published one can only tighten it.
|
|
110
|
+
- `simulate=False` skips the server's simulation of the exact signed transaction (one round trip to the node); a
|
|
111
|
+
transaction that would revert then costs its gas. `dry_run=True` has the server check and simulate it, and relay
|
|
112
|
+
nothing.
|
|
113
|
+
- Run the agent in US East, near the chain's sequencer: from far away its round trip dominates every trade.
|
|
114
|
+
|
|
115
|
+
## Risks and Dossier Shield
|
|
116
|
+
|
|
117
|
+
Any token with a route trades; the server discloses what it found in `risks` (`[{code, severity, detail}]`).
|
|
118
|
+
|
|
119
|
+
- **Severe risks trade only when you accept them by name** in `accept_risks`: `HONEYPOT`, `TRANSFER_TAX`, `LOOKALIKE`,
|
|
120
|
+
`AMOUNT_HOOK`, `HIGH_PRICE_IMPACT`, `UNSCREENED`, `IMPERSONATOR`. The SDK never accepts a risk for you, and accepting
|
|
121
|
+
one loosens none of its price or minimum-output checks.
|
|
122
|
+
- **Dossier Shield** (`shield=True`): 10 bps on top of the trade's fee, Dossier's verdict on the token, and `policies`
|
|
123
|
+
(`block_impersonators`, on by default, `verified_only`, `block_risks`).
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
r = client.trade(sell="ETH", buy="0x…", amount_usd=5, accept_risks=["TRANSFER_TAX"])
|
|
127
|
+
s = client.trade(sell="ETH", buy="0x…", amount_usd=5, shield=True, policies={"verified_only": True})
|
|
128
|
+
s.dossier # {"verdict": "verified", "url": "…"}
|
|
129
|
+
s.fee_bps, s.fee_class, s.fee_tier # 60, "other", 50
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Fees
|
|
133
|
+
|
|
134
|
+
Orblivion's fee depends on the two tokens a trade swaps (fee schedule 2, pinned in `constants`):
|
|
135
|
+
|
|
136
|
+
- **Major trades**: both tokens are majors (`ETH`, `WETH`, `USDG`, `cbBTC`, `ORBIO`, or a Robinhood stock token). They
|
|
137
|
+
pay the wallet's volume tier: 25 bps, or 15 or 10 after enough 30-day volume.
|
|
138
|
+
- **Every other trade** pays exactly 50 bps.
|
|
139
|
+
- **Dossier Shield** adds 10 bps to either.
|
|
140
|
+
|
|
141
|
+
The SDK works out the class itself, from its own pinned majors and, for a token named by address, its own read of the
|
|
142
|
+
chain (the stock tokens' beacon). It never takes the server's word for the class. A plan whose `fee_class` differs is
|
|
143
|
+
refused (`FEE_CLASS`). The plan's `fee_bps` must be one the class and Shield mode allow, `fee_tier` must be that fee
|
|
144
|
+
without Shield's 10, and the calldata must pay exactly `fee_bps` (`FEE_BPS`). The SDK can't know the wallet's volume, so
|
|
145
|
+
it accepts any tier up to the posted 25 for a major trade. `max_fee_bps=` on the client sets a hard cap on top (`None`, the
|
|
146
|
+
default, leaves the schedule to bound the fee). Every quote, plan and trade result carries `fee_bps`, `fee_class` and
|
|
147
|
+
`fee_tier`.
|
|
148
|
+
|
|
149
|
+
The default slippage covers the trade's posted fee (40 bps plus the fee, within the pair's tolerance plus the fee).
|
|
150
|
+
|
|
151
|
+
## Sessions and hosted agents
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
owner = OrblivionClient(url, wallet_account)
|
|
155
|
+
executor = OrblivionClient(url, hot_account)
|
|
156
|
+
grant = owner.sessions.grant(executor=executor.address, max_trade_usd=20, max_day_usd=60, max_trades=10, hours=12,
|
|
157
|
+
allow_7702=True)
|
|
158
|
+
executor.sessions.trade(grant.session_id, sell="ETH", buy="USDG", amount_usd=5, session=grant)
|
|
159
|
+
owner.sessions.revoke(grant.session_id)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The owner's signed root delegation carries caveats the chain enforces (the router only, ETH per trade and per day, the
|
|
163
|
+
expiry, the number of trades, the executor as the only redeemer), and its salt binds the limits, the accepted risks,
|
|
164
|
+
Shield and the fees, so neither the server nor the executor can widen them. Every new grant is binding version 4, with
|
|
165
|
+
`fees` exactly the schedule's for its Shield mode (`verify.fee_terms`); the owner's SDK refuses any other version or
|
|
166
|
+
fees (`GRANT_LIMITS`). A session trade pays what the binding allows for the trade's own class, which the executor's SDK
|
|
167
|
+
and Orblivion's co-signer each read themselves. Sessions granted before version 4 keep their one fee. An executor trading without
|
|
168
|
+
the `Grant` can state the session's Shield mode with `session_shield=`. Otherwise its SDK holds the trade to the
|
|
169
|
+
schedule without Shield. `client.hosted` runs an agent's loop on the Orblivion server; a live hosted agent's executor
|
|
170
|
+
must carry the co-signer's attestation, which the SDK verifies before the owner signs anything.
|
|
171
|
+
|
|
172
|
+
An AI agent (`brain="orbio_llm"`) decides each tick with a model on Orbio's gateway from the owner's own strategy prompt:
|
|
173
|
+
`client.hosted.create(brain="orbio_llm", params=ai_params("Follow the trend; keep half in USDG.",
|
|
174
|
+
"anthropic/claude-haiku-5.5"), interval_s=900, pairs=["ETH-USDG"], limits=...)`: Orblivion's own Orbio credits pay
|
|
175
|
+
first (a fair daily share per owner wallet, cheap models, 15-minute ticks), then the owner's own Orbio balance.
|
|
176
|
+
`hosted.ai_options()` lists the templates, models and prices, the pool and this wallet's share; `hosted.spend(id)` its
|
|
177
|
+
model calls and cost. The owner's Orbio balance needs Sign in with Orbio: `hosted.ai_connect()` returns Orbio's consent
|
|
178
|
+
page, refused (`AI_AUTHORIZE_URL`) unless it is `https://www.orbio.so/oauth/authorize` with PKCE S256 asking for exactly
|
|
179
|
+
Orblivion's grant: its client id (pass `client_id=` to pin it; until Orbio registers the app, the one the server's AI
|
|
180
|
+
options name, which its answer must repeat), the server's own `/orbio-callback.html` as the redirect, and the scope
|
|
181
|
+
`openid inference`, each named once. Orblivion's redirect page finishes it. No answer carries an Orbio credential.
|
|
182
|
+
Sign in with Orbio isn't open on the public API yet.
|
|
183
|
+
|
|
184
|
+
## Your account
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
page = client.me.trades(limit=25) # newest first; refused=True adds refused calls and unmined trades
|
|
188
|
+
for t in page.trades:
|
|
189
|
+
print(t.time, t.path, t.token_in.token, t.amount_in, t.token_out.token, t.amount_out, t.amount_out_kind, t.status)
|
|
190
|
+
older = client.me.trades(limit=25, before=page.next_before) if page.next_before else None
|
|
191
|
+
|
|
192
|
+
p = client.me.portfolio() # balances at independent marks; p.total_usd counts priced ones only
|
|
193
|
+
print(p.total_usd, p.fee_tier.tier_bps, p.fee_tier.volume_to_next_usd)
|
|
194
|
+
s = client.me.sessions() # active sessions and hosted agents in summary
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The signed-in wallet's own trades through Orblivion (agent, session, hosted or fast; `amount_out` is what the wallet
|
|
198
|
+
received once mined, `amount_out_kind` `"actual"`, else the plan's minimum, `"minimum"`), its balances read from the
|
|
199
|
+
chain and valued at the independent marks Orblivion's volume tier uses (a token without one is listed with its amount,
|
|
200
|
+
`priced=False`), its fee tier and the volume to the next, and its active sessions and hosted agents. Read-only: these
|
|
201
|
+
calls sign nothing and change nothing. The server answers for the key's own wallet only, and the SDK still checks every
|
|
202
|
+
answer: its shape (`ACCOUNT_SHAPE`) and that it is this client's wallet's (`ACCOUNT_WALLET`). Information only: nothing
|
|
203
|
+
in these answers feeds a signing decision, which always rests on the SDK's own reads. No fee figure is returned.
|
|
204
|
+
Token symbols and names are their deployer's word: show them as text.
|
|
205
|
+
|
|
206
|
+
## What the SDK verifies
|
|
207
|
+
|
|
208
|
+
The same rules as the TypeScript SDK, by the same names: the router, chain, sender and transaction type; every Universal
|
|
209
|
+
Router command decoded and re-encoded byte for byte; the value and what is pulled; the tokens and hops; every recipient
|
|
210
|
+
(output only to the wallet, one fee payment only); the fee (its class, count, size, token, recipient); the minimum output against
|
|
211
|
+
the quote, the simulation and Chainlink prices the SDK reads itself, or its own quote of the exact route for tokens
|
|
212
|
+
without a feed; the size, the deadline and the gas; Permit2 permits and approvals; token records against the chain
|
|
213
|
+
(lookalikes of core assets refused unless accepted); and, after mining, that what was mined is what was signed.
|
|
214
|
+
|
|
215
|
+
## Pinned values
|
|
216
|
+
|
|
217
|
+
The chain, the contracts, the core tokens, the fee schedule and the session co-signer (`constants.COSIGNER`) are pinned;
|
|
218
|
+
the server's config must agree (`ConfigMismatchError`). Its `fee` must publish schedule 2 exactly: `schedule`,
|
|
219
|
+
`major_symbols`, `major_tiers`, `other_bps`, `shield_add_bps` and `max_bps`, and none of schedule 1's `bps`,
|
|
220
|
+
`shield_bps` or `default_bps`. The fee recipient is pinned the same way: Orblivion's platform wallet
|
|
221
|
+
(`constants.FEE_RECIPIENT`, `0x46eb64c06379ac0d8ea3db040974278199e1e6e9`). The $ORBLIVION token's contract is pinned too
|
|
222
|
+
(`constants.ORBLIVION_TOKEN`).
|
|
223
|
+
|
|
224
|
+
## Errors
|
|
225
|
+
|
|
226
|
+
`VerificationError` (`VERIFICATION_FAILED`, with `rule`, `failures`, `checks`: nothing was signed),
|
|
227
|
+
`OrblivionApiError` (the server's code), `ConfigMismatchError`, `MinedMismatchError`, `TxRevertedError`,
|
|
228
|
+
`ConsentRequiredError`, `InsufficientFundsError`, and `OrblivionError` with `ACCOUNT_SHAPE` or `ACCOUNT_WALLET` for an
|
|
229
|
+
account answer (`client.me`) of the wrong shape or for another wallet. `error.retryable` is true for rate limits,
|
|
230
|
+
unreachable nodes, receipt timeouts and 5xx answers. Every one is an `OrblivionError`; an API that can't be reached
|
|
231
|
+
is `ORBLIVION_UNREACHABLE`.
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# orblivion
|
|
2
|
+
|
|
3
|
+
Trade on Robinhood Chain (chain 4663) through Orblivion, the trading layer for Orbio agents, from Python. Orblivion
|
|
4
|
+
finds the route, builds the transaction and simulates it. Your agent's own key signs it, and Orblivion's relay sends it.
|
|
5
|
+
|
|
6
|
+
**Nothing the server returns is signed until this SDK has decoded and checked it locally**, and every mined transaction
|
|
7
|
+
is checked again against what was signed. The decoder and the checks are this package's own: the same rules, rule names
|
|
8
|
+
and error codes as the TypeScript SDK (`@orblivion/sdk`), proven against the same recorded answers.
|
|
9
|
+
|
|
10
|
+
Python 3.10 or later. Depends on `eth-account` and `eth-abi` only; HTTP uses the standard library.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pip install orblivion
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import os
|
|
22
|
+
from eth_account import Account
|
|
23
|
+
from orblivion import OrblivionClient
|
|
24
|
+
|
|
25
|
+
client = OrblivionClient(account=Account.from_key(os.environ["AGENT_KEY"]),
|
|
26
|
+
max_notional_usd=25) # the client refuses anything bigger, whatever the server says
|
|
27
|
+
|
|
28
|
+
client.sign_in() # optional: the first call signs in for you
|
|
29
|
+
quote = client.quote(sell="ETH", buy="USDG", amount_usd=10)
|
|
30
|
+
plan = client.plan(sell="ETH", buy="USDG", amount_usd=10) # every check, nothing signed
|
|
31
|
+
dry = client.trade(sell="ETH", buy="USDG", amount_usd=10, dry_run=True)
|
|
32
|
+
sent = client.trade(sell="ETH", buy="USDG", amount_usd=10) # checked, signed, sent, checked again
|
|
33
|
+
print(sent.tx_hash, sent.amount_out, sent.min_out, sent.fee)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The client talks to `https://api.orblivion.com` unless told otherwise: pass another server first, or leave it out
|
|
37
|
+
(`OrblivionClient(account=...)`) and set `ORBLIVION_API_URL`. Plain `http` is accepted only for `127.0.0.1`,
|
|
38
|
+
`localhost` and `::1`; anything else must be `https`.
|
|
39
|
+
|
|
40
|
+
`headers={...}` adds headers to every request to the Orblivion API (never to the RPC or the sequencer); their values
|
|
41
|
+
are never shown in an error or a repr.
|
|
42
|
+
|
|
43
|
+
## Signing in
|
|
44
|
+
|
|
45
|
+
`sign_in()` asks the server for an EIP-4361 (Sign-In with Ethereum) message, checks it names this server's host, chain
|
|
46
|
+
4663, this wallet and a short expiry, signs it, and receives an API key bound to that wallet, kept in memory only.
|
|
47
|
+
Pass `api_key=` to reuse one; the client checks it is bound to its wallet first.
|
|
48
|
+
|
|
49
|
+
## Quote, plan, trade
|
|
50
|
+
|
|
51
|
+
| Call | What it does |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `quote(sell=, buy=, amount_usd= \| amount_in=, slippage_bps=None, shield=False)` | The server's best route and what it discloses. Read-only; the server's word. |
|
|
54
|
+
| `plan(sell=, buy=, amount_usd= \| amount_in=, ...)` | The trade's plan, checked exactly as `trade` checks it, with **nothing signed**. A sale of an ERC-20 token whose swap is built only around a Permit2 signature stops after the permit's checks (`complete=False`). |
|
|
55
|
+
| `trade(sell=, buy=, amount_usd= \| amount_in=, slippage_bps=None, approval="budget", dry_run=False, accept_risks=None, shield=False, policies=None)` | Prepare, check, sign, send through the relay, wait for the receipt, check what was mined. With `dry_run=True` it stops after signing and sends nothing. |
|
|
56
|
+
| `search_tokens(q, tier=None, kind=None, limit=20, shield=False)` | Search the token registry. Symbols are copied by impersonators: pick a token by its address. |
|
|
57
|
+
| `inspect_token(token, shield=False)` | One token as a trade would see it, checked against the SDK's own reads of the chain, refusing nothing. |
|
|
58
|
+
| `wallet.balances(tokens=None, who=None)` | ETH and token balances, read from the chain. |
|
|
59
|
+
| `approvals.status()`, `approvals.revoke()` | The wallet's Permit2 approvals; take them back. |
|
|
60
|
+
|
|
61
|
+
Core tokens are named by symbol (`ETH`, `WETH`, `USDG`, `cbBTC`, `ORBIO`, `NVDA`, `SPY`); every other token by its
|
|
62
|
+
contract address. Amounts are `int` in the token's smallest unit (`parse_token("1.5", "USDG")`), or `amount_usd`.
|
|
63
|
+
|
|
64
|
+
## Fast mode
|
|
65
|
+
|
|
66
|
+
For an agent that holds its own key and trades a major pair, `fast()` cuts the path from decision to relay to one call:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
fast = client.fast().start() # keeps its Chainlink marks, nonce, base fee, allowances and fee tier warm
|
|
70
|
+
r = fast.trade("ETH", "USDG", amount_usd=10) # or client.fast_trade(...)
|
|
71
|
+
print(r.path, r.tx_hash, r.timings["decision_to_accepted_ms"])
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The SDK builds the transaction itself on a pinned route (`FAST_ROUTES`: ETH, WETH, USDG, NVDA and SPY; never cbBTC,
|
|
75
|
+
which takes the normal path before anything is signed), with `min_out` from its own quote of that route (kept warm; the
|
|
76
|
+
tolerance at most the server's pair default), your fee tier in ETH, WETH or USDG by the fee currency rule, and the
|
|
77
|
+
output to your wallet; checks it with `verify_fast` (the server's own rules), signs it and sends it to
|
|
78
|
+
`POST /api/v1/live/fast`, which rebuilds the calldata and relays only an exact match.
|
|
79
|
+
|
|
80
|
+
- Anything stale, an ineligible pair (ORBIO, launchpad and long-tail tokens, a stock token after hours), a token you
|
|
81
|
+
haven't approved to Permit2 yet, a pool further under the Chainlink marks than its fee and the pair's band (75 bps
|
|
82
|
+
against USDG, 125 for a cross pair), or marks from your RPC more than 3% from the server's falls back to `trade()`
|
|
83
|
+
before anything is signed (`r.path == "fallback"`, `r.fallback_reason`); `fallback=False` raises instead.
|
|
84
|
+
- One `trade()` never lets two transactions land. After a refusal the server answers before relaying, the SDK asks your
|
|
85
|
+
own RPC about the signed transaction first: on chain, it is the result (`r.note`); its nonce taken by another
|
|
86
|
+
transaction, the next one takes a fresh nonce; otherwise the retry or the fallback signs at the same nonce. A refusal
|
|
87
|
+
that may follow a relay (`NONCE` without that proof, `IN_FLIGHT`, `SUBMIT_REJECTED`, `SEQUENCER_UNREACHABLE`, no
|
|
88
|
+
answer) raises with the hash. `trade(nonce=n)` signs the normal path's first transaction at `n` (and refuses when the
|
|
89
|
+
wallet's pending nonce isn't `n`).
|
|
90
|
+
- `tolerance_bps` tightens the minimum (it can't exceed the server's cap); `min_out` sets your own when it is higher.
|
|
91
|
+
The slippage table is pinned in the SDK (`FAST_TOLERANCES`); the server's published one can only tighten it.
|
|
92
|
+
- `simulate=False` skips the server's simulation of the exact signed transaction (one round trip to the node); a
|
|
93
|
+
transaction that would revert then costs its gas. `dry_run=True` has the server check and simulate it, and relay
|
|
94
|
+
nothing.
|
|
95
|
+
- Run the agent in US East, near the chain's sequencer: from far away its round trip dominates every trade.
|
|
96
|
+
|
|
97
|
+
## Risks and Dossier Shield
|
|
98
|
+
|
|
99
|
+
Any token with a route trades; the server discloses what it found in `risks` (`[{code, severity, detail}]`).
|
|
100
|
+
|
|
101
|
+
- **Severe risks trade only when you accept them by name** in `accept_risks`: `HONEYPOT`, `TRANSFER_TAX`, `LOOKALIKE`,
|
|
102
|
+
`AMOUNT_HOOK`, `HIGH_PRICE_IMPACT`, `UNSCREENED`, `IMPERSONATOR`. The SDK never accepts a risk for you, and accepting
|
|
103
|
+
one loosens none of its price or minimum-output checks.
|
|
104
|
+
- **Dossier Shield** (`shield=True`): 10 bps on top of the trade's fee, Dossier's verdict on the token, and `policies`
|
|
105
|
+
(`block_impersonators`, on by default, `verified_only`, `block_risks`).
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
r = client.trade(sell="ETH", buy="0x…", amount_usd=5, accept_risks=["TRANSFER_TAX"])
|
|
109
|
+
s = client.trade(sell="ETH", buy="0x…", amount_usd=5, shield=True, policies={"verified_only": True})
|
|
110
|
+
s.dossier # {"verdict": "verified", "url": "…"}
|
|
111
|
+
s.fee_bps, s.fee_class, s.fee_tier # 60, "other", 50
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Fees
|
|
115
|
+
|
|
116
|
+
Orblivion's fee depends on the two tokens a trade swaps (fee schedule 2, pinned in `constants`):
|
|
117
|
+
|
|
118
|
+
- **Major trades**: both tokens are majors (`ETH`, `WETH`, `USDG`, `cbBTC`, `ORBIO`, or a Robinhood stock token). They
|
|
119
|
+
pay the wallet's volume tier: 25 bps, or 15 or 10 after enough 30-day volume.
|
|
120
|
+
- **Every other trade** pays exactly 50 bps.
|
|
121
|
+
- **Dossier Shield** adds 10 bps to either.
|
|
122
|
+
|
|
123
|
+
The SDK works out the class itself, from its own pinned majors and, for a token named by address, its own read of the
|
|
124
|
+
chain (the stock tokens' beacon). It never takes the server's word for the class. A plan whose `fee_class` differs is
|
|
125
|
+
refused (`FEE_CLASS`). The plan's `fee_bps` must be one the class and Shield mode allow, `fee_tier` must be that fee
|
|
126
|
+
without Shield's 10, and the calldata must pay exactly `fee_bps` (`FEE_BPS`). The SDK can't know the wallet's volume, so
|
|
127
|
+
it accepts any tier up to the posted 25 for a major trade. `max_fee_bps=` on the client sets a hard cap on top (`None`, the
|
|
128
|
+
default, leaves the schedule to bound the fee). Every quote, plan and trade result carries `fee_bps`, `fee_class` and
|
|
129
|
+
`fee_tier`.
|
|
130
|
+
|
|
131
|
+
The default slippage covers the trade's posted fee (40 bps plus the fee, within the pair's tolerance plus the fee).
|
|
132
|
+
|
|
133
|
+
## Sessions and hosted agents
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
owner = OrblivionClient(url, wallet_account)
|
|
137
|
+
executor = OrblivionClient(url, hot_account)
|
|
138
|
+
grant = owner.sessions.grant(executor=executor.address, max_trade_usd=20, max_day_usd=60, max_trades=10, hours=12,
|
|
139
|
+
allow_7702=True)
|
|
140
|
+
executor.sessions.trade(grant.session_id, sell="ETH", buy="USDG", amount_usd=5, session=grant)
|
|
141
|
+
owner.sessions.revoke(grant.session_id)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The owner's signed root delegation carries caveats the chain enforces (the router only, ETH per trade and per day, the
|
|
145
|
+
expiry, the number of trades, the executor as the only redeemer), and its salt binds the limits, the accepted risks,
|
|
146
|
+
Shield and the fees, so neither the server nor the executor can widen them. Every new grant is binding version 4, with
|
|
147
|
+
`fees` exactly the schedule's for its Shield mode (`verify.fee_terms`); the owner's SDK refuses any other version or
|
|
148
|
+
fees (`GRANT_LIMITS`). A session trade pays what the binding allows for the trade's own class, which the executor's SDK
|
|
149
|
+
and Orblivion's co-signer each read themselves. Sessions granted before version 4 keep their one fee. An executor trading without
|
|
150
|
+
the `Grant` can state the session's Shield mode with `session_shield=`. Otherwise its SDK holds the trade to the
|
|
151
|
+
schedule without Shield. `client.hosted` runs an agent's loop on the Orblivion server; a live hosted agent's executor
|
|
152
|
+
must carry the co-signer's attestation, which the SDK verifies before the owner signs anything.
|
|
153
|
+
|
|
154
|
+
An AI agent (`brain="orbio_llm"`) decides each tick with a model on Orbio's gateway from the owner's own strategy prompt:
|
|
155
|
+
`client.hosted.create(brain="orbio_llm", params=ai_params("Follow the trend; keep half in USDG.",
|
|
156
|
+
"anthropic/claude-haiku-5.5"), interval_s=900, pairs=["ETH-USDG"], limits=...)`: Orblivion's own Orbio credits pay
|
|
157
|
+
first (a fair daily share per owner wallet, cheap models, 15-minute ticks), then the owner's own Orbio balance.
|
|
158
|
+
`hosted.ai_options()` lists the templates, models and prices, the pool and this wallet's share; `hosted.spend(id)` its
|
|
159
|
+
model calls and cost. The owner's Orbio balance needs Sign in with Orbio: `hosted.ai_connect()` returns Orbio's consent
|
|
160
|
+
page, refused (`AI_AUTHORIZE_URL`) unless it is `https://www.orbio.so/oauth/authorize` with PKCE S256 asking for exactly
|
|
161
|
+
Orblivion's grant: its client id (pass `client_id=` to pin it; until Orbio registers the app, the one the server's AI
|
|
162
|
+
options name, which its answer must repeat), the server's own `/orbio-callback.html` as the redirect, and the scope
|
|
163
|
+
`openid inference`, each named once. Orblivion's redirect page finishes it. No answer carries an Orbio credential.
|
|
164
|
+
Sign in with Orbio isn't open on the public API yet.
|
|
165
|
+
|
|
166
|
+
## Your account
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
page = client.me.trades(limit=25) # newest first; refused=True adds refused calls and unmined trades
|
|
170
|
+
for t in page.trades:
|
|
171
|
+
print(t.time, t.path, t.token_in.token, t.amount_in, t.token_out.token, t.amount_out, t.amount_out_kind, t.status)
|
|
172
|
+
older = client.me.trades(limit=25, before=page.next_before) if page.next_before else None
|
|
173
|
+
|
|
174
|
+
p = client.me.portfolio() # balances at independent marks; p.total_usd counts priced ones only
|
|
175
|
+
print(p.total_usd, p.fee_tier.tier_bps, p.fee_tier.volume_to_next_usd)
|
|
176
|
+
s = client.me.sessions() # active sessions and hosted agents in summary
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The signed-in wallet's own trades through Orblivion (agent, session, hosted or fast; `amount_out` is what the wallet
|
|
180
|
+
received once mined, `amount_out_kind` `"actual"`, else the plan's minimum, `"minimum"`), its balances read from the
|
|
181
|
+
chain and valued at the independent marks Orblivion's volume tier uses (a token without one is listed with its amount,
|
|
182
|
+
`priced=False`), its fee tier and the volume to the next, and its active sessions and hosted agents. Read-only: these
|
|
183
|
+
calls sign nothing and change nothing. The server answers for the key's own wallet only, and the SDK still checks every
|
|
184
|
+
answer: its shape (`ACCOUNT_SHAPE`) and that it is this client's wallet's (`ACCOUNT_WALLET`). Information only: nothing
|
|
185
|
+
in these answers feeds a signing decision, which always rests on the SDK's own reads. No fee figure is returned.
|
|
186
|
+
Token symbols and names are their deployer's word: show them as text.
|
|
187
|
+
|
|
188
|
+
## What the SDK verifies
|
|
189
|
+
|
|
190
|
+
The same rules as the TypeScript SDK, by the same names: the router, chain, sender and transaction type; every Universal
|
|
191
|
+
Router command decoded and re-encoded byte for byte; the value and what is pulled; the tokens and hops; every recipient
|
|
192
|
+
(output only to the wallet, one fee payment only); the fee (its class, count, size, token, recipient); the minimum output against
|
|
193
|
+
the quote, the simulation and Chainlink prices the SDK reads itself, or its own quote of the exact route for tokens
|
|
194
|
+
without a feed; the size, the deadline and the gas; Permit2 permits and approvals; token records against the chain
|
|
195
|
+
(lookalikes of core assets refused unless accepted); and, after mining, that what was mined is what was signed.
|
|
196
|
+
|
|
197
|
+
## Pinned values
|
|
198
|
+
|
|
199
|
+
The chain, the contracts, the core tokens, the fee schedule and the session co-signer (`constants.COSIGNER`) are pinned;
|
|
200
|
+
the server's config must agree (`ConfigMismatchError`). Its `fee` must publish schedule 2 exactly: `schedule`,
|
|
201
|
+
`major_symbols`, `major_tiers`, `other_bps`, `shield_add_bps` and `max_bps`, and none of schedule 1's `bps`,
|
|
202
|
+
`shield_bps` or `default_bps`. The fee recipient is pinned the same way: Orblivion's platform wallet
|
|
203
|
+
(`constants.FEE_RECIPIENT`, `0x46eb64c06379ac0d8ea3db040974278199e1e6e9`). The $ORBLIVION token's contract is pinned too
|
|
204
|
+
(`constants.ORBLIVION_TOKEN`).
|
|
205
|
+
|
|
206
|
+
## Errors
|
|
207
|
+
|
|
208
|
+
`VerificationError` (`VERIFICATION_FAILED`, with `rule`, `failures`, `checks`: nothing was signed),
|
|
209
|
+
`OrblivionApiError` (the server's code), `ConfigMismatchError`, `MinedMismatchError`, `TxRevertedError`,
|
|
210
|
+
`ConsentRequiredError`, `InsufficientFundsError`, and `OrblivionError` with `ACCOUNT_SHAPE` or `ACCOUNT_WALLET` for an
|
|
211
|
+
account answer (`client.me`) of the wrong shape or for another wallet. `error.retryable` is true for rate limits,
|
|
212
|
+
unreachable nodes, receipt timeouts and 5xx answers. Every one is an `OrblivionError`; an API that can't be reached
|
|
213
|
+
is `ORBLIVION_UNREACHABLE`.
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT. See [LICENSE](LICENSE).
|