"""A hosted rebalance agent on Orblivion: it keeps the wallet half in ETH and half in USDG. The script from Orblivion's build-along film, where it runs $ORBLIVION's own agent. It runs on Orblivion's servers, so this PC can be off. It trades only inside the session the wallet signs here, and its key never leaves Orblivion's signing service. python house_agent.py create make the agent and sign its session (one setup transaction) python house_agent.py gas how much ETH its executor holds for gas python house_agent.py start switch it on python house_agent.py watch follow what it decides and trades from now on ("watch all": history too) python house_agent.py every 300 check every 300 seconds python house_agent.py off the off switch: end its session, at Orblivion and on chain python house_agent.py renew sign a fresh session after the off switch """ import json import os import sys import time from orblivion import OrblivionClient from common import confirm, load, tx STATE = "agent.json" LIMITS = {"max_trade_usd": 10, "max_day_usd": 40, "max_position_usd": 120} SESSION = {"max_trade_usd": 10, "max_day_usd": 40, "max_trades": 400, "hours": 30 * 24} # the longest a session lasts wallet = OrblivionClient(account=load("wallet")) def saved(): with open(STATE, encoding="utf-8") as f: return json.load(f) def save(state): with open(STATE, "w", encoding="utf-8") as f: json.dump(state, f, indent=1) def setup(grant): """The session may spend at most one day's cap of USDG; the wallet allows it on chain. On a plain wallet the SDK first sets the wallet's smart-account code (EIP-7702), the change its session runs on.""" if grant.setup: confirm(f"One transaction from the wallet lets the session spend at most ${SESSION['max_day_usd']} of USDG.") wallet.sessions.setup(grant) def show(grant): lim = grant.limits print(f"Session {grant.session_id}") print(f"Caps ${lim.max_trade_usd:g} a trade, ${lim.max_day_usd:g} a day, {lim.max_trades} trades, " f"{', '.join(lim.tokens)} only") print(f"Checked {sum(c.ok for c in grant.checks)} of {len(grant.checks)} checks passed before signing") def create(): if os.path.exists(STATE): sys.exit(f"{STATE} already names an agent: use renew for a new session") made = wallet.hosted.create( brain="rebalance", params={"targets": {"ETH": 0.5, "USDG": 0.5}, "threshold_bps": 200, "max_trade_usd": 10}, interval_s=60, pairs=["ETH-USDG"], limits=LIMITS, name="rebalance agent", mode="live", session=SESSION, allow_7702=True, ) agent, grant = made["agent"], made["grant"] print(f"Agent {agent['id']} (rebalance: half ETH, half USDG; trades when either drifts 2 points)") print(f"Executor {agent['executor']} (made by Orblivion's signing service; attestation checked)") show(grant) setup(grant) save({"agent_id": agent["id"], "session_id": grant.session_id, "wallet": wallet.address, "executor": agent["executor"]}) print(f"\nSaved to {STATE}. Next: send a little ETH to the executor for gas (python house_agent.py gas).") def gas(): g = wallet.hosted.executor(saved()["agent_id"]) print(f"Executor {g['executor']}") print(f"Holds ${g.get('balance_usd') or 0:.2f} of ETH; needs ${g.get('min_balance_usd') or 0:.2f}" f" ({'enough' if g.get('enough') else 'top it up'})") def start(): s = saved() confirm("Switch the agent on: from now it trades the wallet's real money, inside its session.") wallet.hosted.update(s["agent_id"], live=True) a = wallet.hosted.resume(s["agent_id"]) print(f"On: {a.get('trading', a.get('status'))}") def watch(history=None): """What the agent does from now on; `watch all` starts with its last 20 entries.""" s, seen = saved(), 0 if history != "all": seen = max((r["id"] for r in wallet.hosted.logs(s["agent_id"], limit=1)["rows"]), default=0) print(f"Watching {s['agent_id']} (Ctrl+C to stop watching; the agent keeps running)\n") while True: rows = wallet.hosted.logs(s["agent_id"], limit=20)["rows"] for r in reversed([r for r in rows if r["id"] > seen]): when, d = time.strftime("%H:%M:%S", time.localtime(r["ts"])), r["data"] if r["kind"] == "decision": print(f"{when} decides {d.get('reasoning')}") elif r["kind"] == "trade": print(f"{when} TRADE sold {d['amount_in']} {d['sell']} for {d['amount_out']} {d['buy']}" f"\n{' ' * 18}{tx(d['tx_hash'])}") else: print(f"{when} {r['kind']:8} {d.get('code', '')} {d.get('detail', '')}".rstrip()) seen = max(seen, r["id"]) time.sleep(5) def every(seconds): wallet.hosted.update(saved()["agent_id"], interval_s=int(seconds)) print(f"It checks every {int(seconds)} seconds now.") def off(): s = saved() confirm(f"End session {s['session_id']} now: Orblivion stops co-signing at once, then the chain ends it too " "(transactions from the wallet).") r = wallet.sessions.revoke(s["session_id"]) print(f"Revoked: {r.effective}") for step in r.steps: print(f" {step.kind}: {tx(step.tx_hash) if step.sent else 'signed, not sent'}") def renew(): s = saved() executor = wallet.hosted.attested_executor(s["agent_id"]) # the same executor, its attestation checked again grant = wallet.sessions.grant( executor=executor, tokens=["ETH", "USDG"], max_trade_usd=SESSION["max_trade_usd"], max_day_usd=SESSION["max_day_usd"], max_trades=SESSION["max_trades"], hours=SESSION["hours"], setup_budget_usd=SESSION["max_day_usd"], require_day_cap=True, allow_7702=True, ) show(grant) setup(grant) wallet.hosted.attach_session(s["agent_id"], grant.session_id) save(dict(s, session_id=grant.session_id)) print("\nNew session attached. Switch it back on with: python house_agent.py start") if __name__ == "__main__": cmd, *rest = sys.argv[1:] or ["help"] acts = {"create": create, "gas": gas, "start": start, "watch": watch, "every": every, "off": off, "renew": renew} if cmd not in acts: sys.exit(__doc__) try: acts[cmd](*rest) except KeyboardInterrupt: pass