Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c734de2
docs(spec): base58 signers + delegate support + onboarding design
0xtristan Jun 18, 2026
50f70b4
chore: gitignore docs/superpowers working docs
0xtristan Jun 18, 2026
2b56bb3
feat(bullet): parse signer keys from hex or base58
0xtristan Jun 18, 2026
99e3981
fix(bullet): silence clippy format_collect in key tests; tidy dep order
0xtristan Jun 18, 2026
07af5d4
feat(bullet): accept base58 keys via private_key / BB_BULLET_PRIVATE_KEY
0xtristan Jun 18, 2026
a582adb
docs(bullet): update key-precedence doc comments for private_key/base58
0xtristan Jun 18, 2026
b1ecad7
feat(bullet): resolve delegate signer to master via delegateOf
0xtristan Jun 18, 2026
a70ecb6
fix(bullet): propagate delegateOf body-read errors as BotError
0xtristan Jun 18, 2026
9195a59
feat(bullet): read account data + subscribe via resolved master address
0xtristan Jun 18, 2026
d9d0c10
docs: base58 keys, delegate wallets, from-scratch onboarding
0xtristan Jun 18, 2026
3217556
docs(agents): use canonical BB_BULLET_PRIVATE_KEY in setup example
0xtristan Jun 18, 2026
5fde9b1
docs: use canonical BB_BULLET_PRIVATE_KEY in example configs; fix env…
0xtristan Jun 18, 2026
5524e26
fix(review): robust env key resolution, retryable delegateOf 5xx/429,…
0xtristan Jun 18, 2026
9265192
feat(hyperliquid): support API/agent wallets via account_address
0xtristan Jun 18, 2026
dd6998b
feat(bullet): log a hint when a signer resolves to its own account
0xtristan Jun 19, 2026
1cd97a6
docs(config): document BB_HYPERLIQUID_ACCOUNT_ADDRESS + network-match…
0xtristan Jun 19, 2026
8432e99
docs(config): clarify network matches the delegate's network (testnet…
0xtristan Jun 19, 2026
375c0e1
feat(hyperliquid): log whether reads use the signer or a master account
0xtristan Jun 19, 2026
e5d4f34
fix(bullet): treat delegateOf 400 'is not a delegate' as self
0xtristan Jun 19, 2026
064fbce
feat!: standardize on base58 for Bullet, hex for Hyperliquid
0xtristan Jun 19, 2026
34882df
feat: auto-load .env, unify key resolution, add Hyperliquid key_file
0xtristan Jun 19, 2026
cefd022
feat(hyperliquid): read collateral from spot balance for unified acco…
0xtristan Jun 21, 2026
e7e7e2c
fix(hyperliquid): round order prices to 5 sig figs; flatten orphaned …
0xtristan Jun 22, 2026
b68afa7
feat(cli): flatten closes all configured venues (Bullet + Hyperliquid)
0xtristan Jun 22, 2026
f4630e1
fix(hyperliquid): normalize cloid format so own fills are recognized
0xtristan Jun 22, 2026
34f0878
fix(harness): flatten on SIGTERM, not just Ctrl-C
0xtristan Jun 22, 2026
2dcaeeb
fix(review): address bot findings on flatten + funding-arb cleanup
0xtristan Jun 22, 2026
8695a00
fix(review): treat empty BB_*_KEY_FILE as absent in load_config merges
0xtristan Jun 22, 2026
669c47f
fix(keygen): faucet curl needs a browser User-Agent; restructure READ…
0xtristan Jun 22, 2026
3b077da
feat(cli): add 'bb-bot faucet' command + web-faucet fallback
0xtristan Jun 23, 2026
76154b8
test(funding-arb): cover incomplete-entry cleanup (filled leg closed,…
0xtristan Jun 23, 2026
0c15873
chore(deps): bump quinn-proto 0.11.14 → 0.11.15 (RUSTSEC-2026-0185)
0xtristan Jun 23, 2026
369ff5b
fix(review): address remaining bot findings
0xtristan Jun 23, 2026
74a9f58
fix(funding-arb): don't treat a capped partial cleanup as flat
0xtristan Jun 23, 2026
0f03852
fix(review): delegateOf retries; emergency_flatten sizes from live po…
0xtristan Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Reference for the environment variables the bot reads. Never commit secrets
# into config/*.toml.
#
# NOTE: the bot does NOT auto-load this file. Copy it to .env (gitignored) and
# either `export` the vars or load them yourself before running, e.g.:
# set -a && source .env && set +a
# For Bullet, a key file (BB_BULLET_KEY_FILE) keeps the key off the
# environment entirely and is preferred.

# --- Bullet (base58) ---
# Signer secret as base58 (Phantom / delegation export) — hex also accepted.
# A delegate can only trade (no deposit/withdraw) and is revocable from the
# webapp. The bot resolves a delegate to its master account automatically.
BB_BULLET_PRIVATE_KEY=

# Alternatively (preferred), point at a file containing the key string, as
# written by `bb-bot keygen`. Takes precedence over BB_BULLET_PRIVATE_KEY.
# BB_BULLET_KEY_FILE=/path/to/id.key

# --- Hyperliquid (hex) ---
# API-wallet key (secp256k1 hex) created at https://app.hyperliquid.xyz/API.
BB_HYPERLIQUID_PRIVATE_KEY=
# Your MAIN account address (0x… as shown in the HL UI). Required when the key
# above is an API wallet — positions/balances/fills live on the main account,
# not the API wallet. Leave unset if the key is your main wallet itself.
BB_HYPERLIQUID_ACCOUNT_ADDRESS=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@

# Local/experimental configs — put personal configs here
config/local/

# Superpowers brainstorming/specs (local working docs)
docs/superpowers/
40 changes: 28 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Generate a keypair (first time):

```sh
cargo run --bin bb-bot -- keygen --network testnet
# → writes ~/.config/bullet/id.json (0600), prints address + faucet curl
# → writes ~/.config/bullet/id.key (base58 secret, 0600), prints address + faucet curl
```

Fund and onboard the account (first time). The faucet credits the on-chain
Expand All @@ -49,18 +49,18 @@ order placement fail with `user_variants not found`:
cargo run --bin bb-bot -- deposit --network testnet --asset USDC --amount 5000
```

Run a bot (default: reads `~/.config/bullet/id.json`):
Run a bot (default: reads `~/.config/bullet/id.key`):

```sh
cargo run --bin bb-bot -- run --config config/simple-mm-example.toml
```

Or point at an explicit keystore / use hex for CI:
Or point at an explicit key file / pass a key string for CI:

```sh
export BB_BULLET_KEY_FILE="/path/to/keystore.json" # preferred
# OR
export BB_BULLET_PRIVATE_KEY_HEX="0x..." # fallback
export BB_BULLET_KEY_FILE="/path/to/id.key" # preferred
# OR (base58 from Phantom/delegation export, or hex)
export BB_BULLET_PRIVATE_KEY="<base58-or-hex>" # fallback
```

## Architecture — the harness, feeds, and actors
Expand Down Expand Up @@ -294,6 +294,13 @@ the full walkthrough including reconnect patterns and the `Trade` /
`InfoClient::with_reconnect` handles reconnection. Symbol mapping: Bullet
`"BTC-USD"` ↔ HL `"BTC"`. `ActiveAssetCtx` provides real funding rates;
`AllMids` remains a mark-price fallback when no funding field is present.
**API/agent wallets**: set `account_address` (env `BB_HYPERLIQUID_ACCOUNT_ADDRESS`)
to the master account. The agent key signs (orders are attributed to the master
on-chain, `vault_address: None` per the SDK's `approve_agent` pattern); reads
(`user_state` / `open_orders` / `user_fills`) and the `UserFills` / `OrderUpdates`
subscriptions use `account_address`. Unset → reads default to the signer's own
address (main-wallet-key case). HL has no on-chain delegate lookup, so unlike
Bullet the master must be given explicitly.

## Config Format

Expand All @@ -303,15 +310,24 @@ TOML. Top-level sections: `[engine]`, `[exchanges.<name>]`, `[strategy]`,
- `[engine]` — `tick_interval_ms`, `status_port` (optional), or
`status_bind = "host:port"` for explicit bind. `symbol` lives inside each
`[strategy.<name>]` section so multi-symbol setups are explicit.
- Exchange configs: `type = "<name>"` + adapter-specific fields. Bullet
- Exchange configs: `type = "<name>"` + adapter-specific fields. Keys use each
venue's native format: **base58 for Bullet**, **hex for Hyperliquid**. Bullet
resolves key material in this order (explicit config wins; env fills a
field the config omits, so an ambient env var can't silently switch
wallets): `key_file` (in config) → env `BB_BULLET_KEY_FILE` →
`private_key_hex` (in config) → env `BB_BULLET_PRIVATE_KEY_HEX`. File-based
keystore is preferred — see `bb-bot keygen`. Hyperliquid keys via
`BB_HYPERLIQUID_PRIVATE_KEY_HEX`. (Standalone `deposit`/`flatten`/`observe`
take no config, so there env is the source: `BB_BULLET_KEY_FILE` → env hex
→ default keystore.)
`private_key` (in config) → env `BB_BULLET_PRIVATE_KEY`. The key string is
base58 (Phantom / delegation export); hex is also accepted. A `key_file` is a
file containing that key string (as written by `bb-bot keygen`), not a JSON
keystore. If the signer is a **delegate** key, the adapter resolves it to its
master account (via the `delegateOf` endpoint) for all reads and the
user-orders subscription; signing uses the delegate key directly. File-based
key is preferred — see `bb-bot keygen`. Hyperliquid keys via
`BB_HYPERLIQUID_PRIVATE_KEY` (hex), plus `BB_HYPERLIQUID_ACCOUNT_ADDRESS` (or
`account_address` in config) for the master account when using an API/agent
wallet. Env vars are read from the process environment; the bot does not
auto-load `.env`. (Standalone `deposit`/`flatten`/`observe` take no config, so
there env is the source: `BB_BULLET_KEY_FILE` → `BB_BULLET_PRIVATE_KEY` →
default key file.)
- Strategy configs: `type = "<name>"` with sub-table `[strategy.<name>]`.

## Code Style
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ toml = "0.8"
rust_decimal = { version = "1", features = ["serde-with-str"] }
thiserror = "2"
secrecy = { version = "0.8", features = ["serde"] }
bs58 = "0.5"
getrandom = "0.2"
reqwest = { version = "0.13", features = ["json"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
async-trait = "0.1"
Expand Down
6 changes: 3 additions & 3 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ status_port = 3030
type = "bullet"
network = "testnet"
# Key material — do NOT put private keys in this file.
# Option 1 (preferred): key_file = "/path/to/id.json" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY_HEX="0x..."
# Option 1 (preferred): key_file = "/path/to/id.key" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY="<base58 key>"

[strategy]
type = "dip-buyer"
Expand All @@ -297,7 +297,7 @@ max_position = "0.01"
Run:

```sh
export BB_BULLET_PRIVATE_KEY_HEX="0x..."
export BB_BULLET_PRIVATE_KEY="<base58 key>"
cargo run --bin bb-bot -- run --config config/dip-buyer-example.toml
```

Expand Down
53 changes: 48 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,56 @@ Recommended first path:
6. `run` — start tiny, watch logs plus `GET /status`.
7. `flatten` — cancel and close manually if you need to clean up.

## Key management
## Trading from your own wallet (recommended)

For real usage, trade with a **delegate key** rather than your main wallet key:

1. Sign in at [app.bullet.xyz](https://app.bullet.xyz) (or
[app.testnet.bullet.xyz](https://app.testnet.bullet.xyz)) with your wallet
(e.g. Phantom). This creates the embedded wallet that is your Bullet trading
account.
2. Deposit collateral through the webapp UI — this initializes the trading
account.
3. Create a delegate (see Bullet's
[delegate setup guide](https://docs.bullet.xyz/bulletx-exchange/how-to-guide/delegate-account-setup)),
then put the delegate signer key in `.env` as `BB_BULLET_PRIVATE_KEY` — the
**base58** string from the delegate UI (hex also works). Alternatively, save
that string to a file and point `BB_BULLET_KEY_FILE` at it (takes precedence).
4. For Hyperliquid, create an API wallet at
[app.hyperliquid.xyz/API](https://app.hyperliquid.xyz/API). Set
`BB_HYPERLIQUID_PRIVATE_KEY` to the **API-wallet key** (hex), and
`BB_HYPERLIQUID_ACCOUNT_ADDRESS` to your **main account address** (the
`0x…` address shown in the HL UI). The API wallet signs orders; positions,
balances, and fills are read from the main account. (If you instead use your
main wallet's own key, leave `BB_HYPERLIQUID_ACCOUNT_ADDRESS` unset.)

> **What is a delegate / API wallet?** A separate keypair authorized to trade on
> behalf of your account. It can place and cancel orders but **cannot deposit or
> withdraw**, and you can revoke it from the webapp at any time — so you trade
> without exposing your main wallet's private key. On both venues the bot signs
> with this key but reads account state from the **main account** — Bullet
> resolves the master automatically via `delegateOf`; on Hyperliquid you supply
> it via `BB_HYPERLIQUID_ACCOUNT_ADDRESS`.

These are environment variables — the bot does **not** auto-load a `.env` file.
Either `export` them in your shell, or load `.env.example` (copied to `.env`)
yourself before running, e.g. `set -a && source .env && set +a`. For Bullet, a
**key file** (`BB_BULLET_KEY_FILE`) avoids putting the key in the environment at
all and is preferred.

Private keys are passed via environment variables or keystore files, not copied
into example configs. Two options:
## Key management

- **Bullet key file (recommended):** generate once with `cargo run --bin bb-bot -- keygen`, then set `BB_BULLET_KEY_FILE` or add `key_file = "/path/to/id.json"` under `[exchanges.bullet]`.
- **Hex key:** set `BB_BULLET_PRIVATE_KEY_HEX` / `BB_HYPERLIQUID_PRIVATE_KEY_HEX`, e.g. via a `.env` file (already gitignored).
Keys use each venue's native format: **base58 for Bullet** (what Phantom and the
delegation UI export), **hex for Hyperliquid** (what the HL API page gives you).
Pass them via environment variables or a key file — never in example configs.

- **Bullet key file (recommended):** generate once with `cargo run --bin bb-bot -- keygen`, then set `BB_BULLET_KEY_FILE` or add `key_file = "/path/to/id.key"` under `[exchanges.bullet]`. Keeps the key on disk instead of in the environment.
- **Key string (env):** set `BB_BULLET_PRIVATE_KEY` (base58) and
`BB_HYPERLIQUID_PRIVATE_KEY` (hex). The bot reads process environment
variables — it does not auto-load `.env`, so export them (or
`set -a && source .env && set +a`) before running. When the Hyperliquid key is
an API wallet, also set `BB_HYPERLIQUID_ACCOUNT_ADDRESS` to your main account
address.

## Strategies

Expand Down
4 changes: 2 additions & 2 deletions config/avellaneda-stoikov-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ status_port = 3034 # must be unique per running bot — collides if two bots sh
type = "bullet"
network = "testnet"
# Key material — do NOT put private keys in this file.
# Option 1 (preferred): key_file = "/path/to/id.json" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY_HEX="0x..."
# Option 1 (preferred): key_file = "/path/to/id.key" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY="..." (base58 or hex)

[strategy]
type = "avellaneda-stoikov"
Expand Down
13 changes: 8 additions & 5 deletions config/funding-arb-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ status_port = 3031

[exchanges.bullet]
type = "bullet"
network = "testnet"
network = "testnet" # match where your delegate was created: testnet (app.testnet.bullet.xyz) or mainnet (app.bullet.xyz)
# Key material — do NOT put private keys in this file.
# Option 1 (preferred): key_file = "/path/to/id.json" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY_HEX="0x..."
# Option 1 (preferred): key_file = "/path/to/id.key" (run `bb-bot keygen`)
# Option 2: export BB_BULLET_PRIVATE_KEY="..." (base58 from the delegate UI, or hex)

[exchanges.hyperliquid]
type = "hyperliquid"
network = "testnet"
network = "testnet" # match where your API wallet was created: testnet or mainnet (app.hyperliquid.xyz)
# Key material — do NOT put private keys in this file.
# export BB_HYPERLIQUID_PRIVATE_KEY_HEX="0x..."
# export BB_HYPERLIQUID_PRIVATE_KEY="0x..." # API-wallet key (signs orders)
# export BB_HYPERLIQUID_ACCOUNT_ADDRESS="0x..." # your MAIN account (reads
# positions/balances/fills). Required for an API wallet; omit only if the
# key above is your main wallet's own key.

[strategy]
type = "funding-arb"
Expand Down
4 changes: 2 additions & 2 deletions config/grid-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ status_port = 3033 # must be unique per running bot — collides if two bots sh
type = "bullet"
network = "testnet"
# Key material — do NOT put private keys in this file.
# Option 1 (preferred): key_file = "/path/to/id.json" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY_HEX="0x..."
# Option 1 (preferred): key_file = "/path/to/id.key" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY="..." (base58 or hex)

[strategy]
type = "grid"
Expand Down
2 changes: 1 addition & 1 deletion config/reference-arb-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ status_port = 3032
[exchanges.bullet]
type = "bullet"
network = "testnet"
# Set via: export BB_BULLET_PRIVATE_KEY_HEX="0x..." or BB_BULLET_KEY_FILE=/path/to/key.json
# Set via: export BB_BULLET_PRIVATE_KEY="..." (base58 or hex) or BB_BULLET_KEY_FILE=/path/to/key.json

[strategy]
type = "reference-arb"
Expand Down
4 changes: 2 additions & 2 deletions config/simple-mm-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ status_port = 3030 # must be unique per running bot — collides if two bots sh
type = "bullet"
network = "testnet"
# Key material — do NOT put private keys in this file.
# Option 1 (preferred): key_file = "/path/to/id.json" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY_HEX="0x..."
# Option 1 (preferred): key_file = "/path/to/id.key" (run `bb-bot keygen`)
# Option 2 (CI/ephemeral): export BB_BULLET_PRIVATE_KEY="..." (base58 or hex)

[strategy]
type = "simple-mm"
Expand Down
Loading