Appearance
Configuration
Zephyr persists state across a few files in the working directory:
config.toml: your preferences. RPC endpoints, processor toggles, buy/sell transaction params, and the[trade]defaults used to seed new mints.positions.json: runtime state. The currently-active mint, per-mint trade params (entry %, exit %, target change delay) that override[trade]defaults once a mint has a record, and per-mint trade stats / PnL history.wallet.enc+wallet.key: your trading wallet, encrypted. Created on first start from theprivate_keyyou put inconfig.toml(which is then removed). Back up both together — one without the other is useless.
When you change settings from the UI:
- Processor toggles, tips, delays, and buy/sell tx params → saved to
config.toml. - Active mint → saved to
positions.json(top-levelactive_mint), never touchesconfig.toml. - Entry %, exit %, or target change delay with an active mint → saved to that mint's record in
positions.json. Without an active mint → saved toconfig.toml[trade]defaults.
Buy amount is not persisted. Each UI client tracks its own last-used amount and sends it with every buy command.
You edit config.toml manually for initial setup and fields that aren't exposed in the UI (private_key, rpc_url, optionally grpc_url).
Full config.toml
This is the same config shipped in Getting Started. Copy this as your starting point and fill in your details.
toml
[trade]
# Defaults used to seed new mints. Per-mint overrides live in positions.json.
entry_pct = -10.0 # Entry target as % from reference price (negative = wait for dip)
exit_pct = 2.0 # Take-profit target as % above buy price
target_change_delay = 1000 # Ms between reference price updates (auto-rebuy)
[buy]
slippage = 150 # Slippage in bps (150 = 1.5%)
max_block_difference = 3 # Transaction freshness window in slots
nonce_delay = 25 # Nonce rotation interval in ms
panic_slippage = 1000 # Slippage for panic buy in bps
[buy.dynamic_fee]
percentile = 7500 # Fee percentile (×100, so 7500 = p75)
fee_multiplier = 1.0 # Multiply raw fee by this factor
fee_cap = 500000 # Max fee in microlamports (0 = no cap)
[buy.dynamic_tip]
percentile = 8500 # Must be in [general].tip_percentiles
tip_multiplier = 1.0 # Multiply raw tip by this factor
tip_floor = 0.003 # Minimum SOL tip (stream-fallback)
tip_cap = 0.005 # Maximum SOL tip; set == tip_floor to pin
adaptive = true # Drop to lower percentile when congestion forces cap
[sell]
slippage = 200
max_block_difference = 5
nonce_delay = 25
panic_slippage = 2000
[sell.dynamic_fee]
percentile = 8500
fee_multiplier = 1.0
fee_cap = 800000
[sell.dynamic_tip]
percentile = 8500
tip_multiplier = 1.0
tip_floor = 0.003
tip_cap = 0.007
adaptive = true
[network]
rpc_url = "<your-rpc-url>" # Solana RPC endpoint (read-only) — leave empty to use the Zephyr-provided default
grpc_url = "" # gRPC endpoint for real-time streaming (leave empty to use the Zephyr-provided default; Helius LaserStream NY/Frankfurt auto-picked by region)
grpc_token = "" # Auth token for the gRPC endpoint (ignored when grpc_url is empty)
processor = "all" # "all" or a specific one: jito, helius, etc.
[network.node]
enabled = true # Enable anonymous RPC sender
urls = [
"http://rpc-slc1.thornode.io",
"http://rpc-va-zesty.thornode.io",
"http://rpc-ny.thornode.io",
"https://rpc.shyft.to?api_key=<your-shyft-key>",
] # Send endpoints. MUST NOT reuse network.rpc_url
buy_delay = 50
sell_delay = 50
[network.jito]
enabled = true
buy_delay = 100
sell_delay = 100
# Optional per-processor override. Empty = use Zephyr's default fan-out.
# Non-empty = exact list that fires (good for non-NY VPS — restrict to
# nearby regions). Available on every processor and each bifrost subprovider.
# endpoints = [
# "https://frankfurt.mainnet.block-engine.jito.wtf",
# "https://amsterdam.mainnet.block-engine.jito.wtf",
# ]
[network.nextblock]
enabled = true
buy_delay = 25
sell_delay = 25
[network.helius]
enabled = true
buy_delay = 25
sell_delay = 25
[network.zeroslot]
enabled = true
buy_delay = 25
sell_delay = 25
[network.nozomi]
enabled = true
buy_delay = 25
sell_delay = 25
[network.lunar]
enabled = true
buy_delay = 25
sell_delay = 25
[network.astralane]
enabled = true
buy_delay = 25
sell_delay = 25
[network.stellium]
enabled = true
buy_delay = 25
sell_delay = 25
[network.falcon]
enabled = true
buy_delay = 25
sell_delay = 25
[network.bifrost_bloxroute]
enabled = true
buy_delay = 50
sell_delay = 50
[network.bifrost_astralane]
enabled = false
buy_delay = 50
sell_delay = 50
[network.bifrost_blockrazor]
enabled = true
buy_delay = 100
sell_delay = 100
[network.bifrost_lunarlander]
enabled = false
buy_delay = 100
sell_delay = 100
# API keys live as array-of-tables. Each [[...api_keys]] adds one key with
# its own TPS budget. First-fit rotation; aggregate processor TPS = sum of
# per-key `tps` values. A send must clear BOTH the processor's
# buy_delay/sell_delay AND the selected key's `1000/tps`-ms interval.
[[network.jito.api_keys]]
api_key = "<your-jito-key>"
tps = 10
[[network.nextblock.api_keys]]
api_key = "<your-nextblock-key>"
tps = 50
[[network.helius.api_keys]]
api_key = "<your-helius-key>"
tps = 50
# Optional — Zephyr provides 40 TPS on 0slot by default, no api key
# required. Add your own here only if you need more capacity (it's used
# as spillover).
# [[network.zeroslot.api_keys]]
# api_key = "<your-zeroslot-key>"
# tps = 50
# Optional: Zephyr provides 40 TPS on Nozomi by default, no api key
# required. Add your own here only if you need more capacity (it's used
# as spillover).
# [[network.nozomi.api_keys]]
# api_key = "<your-nozomi-key>"
# tps = 50
# Optional — Zephyr provides a default Lunar key (TPS assigned per IP),
# no api key required. Add your own here only if you need more capacity
# (it's used as spillover).
# [[network.lunar.api_keys]]
# api_key = "<your-lunar-key>"
# tps = 50
[[network.astralane.api_keys]]
api_key = "<your-astralane-key>"
tps = 50
[[network.stellium.api_keys]]
api_key = "<your-stellium-key>"
tps = 50
# Optional — Zephyr provides 40 TPS on Falcon by default, no api key
# required. Add your own here only if you need more capacity (it's used
# as spillover).
# [[network.falcon.api_keys]]
# api_key = "<your-falcon-key>"
# tps = 50
[general]
private_key = "<your-base58-private-key>" # Set once; encrypted to wallet.enc + wallet.key on first start, then removed from this file
access_code = "<your-access-code>" # Required: bot refuses to start if empty. Use a long random string
fee_percentiles = [5500, 6500, 7000, 7500, 8000, 8500, 9000] # Fee percentiles surfaced in the fee bar
tip_percentiles = [7000, 7500, 8000, 8500, 9000, 9800] # Tip percentiles surfaced in the tip bar
debug = false # Verbose logging (set false in production if logs are noisy)
nonces = [] # Nonce pubkeys (populated by --generate-nonces)[general]
| Field | Description |
|---|---|
private_key | Your wallet's base58-encoded private key. Set it once to load (or replace) your wallet: on the next start it's encrypted into wallet.enc + wallet.key, removed from config.toml, and read from the encrypted files on every startup after. Replacing it backs up the previous wallet first. Back up both files together. |
fee_percentiles | Priority fee percentiles (×100) the backend subscribes to, displayed in the fee bar |
tip_percentiles | Tip percentiles (×100) the backend subscribes to, displayed in the tip bar |
access_code | Password the extension uses to authenticate with the backend. Required — the bot refuses to start if it's empty, since the web interface is reachable from your machine's public IP. Use a long random string. |
debug | Verbose logging |
nonces | Durable nonce account pubkeys (populated by --generate-nonces) |
[network]
| Field | Description |
|---|---|
rpc_url | Solana RPC endpoint for read-only calls (account lookups, nonce recovery). Leave empty to use the Zephyr-provided default. |
grpc_url | gRPC endpoint for real-time streaming. Leave empty to use the Zephyr-provided default (Helius LaserStream — NY or Frankfurt region auto-selected at startup based on your machine's location) |
grpc_token | Auth token for the gRPC endpoint. Ignored when grpc_url is empty; an empty token is honored for providers that don't authenticate |
processor | Which processors to use: all or a specific one (jito, helius, etc.) |
WARNING
Never duplicate rpc_url in network.node.urls. rpc_url is the read lane, node.urls is the send lane for the Node processor. Pointing them at the same endpoint turns your read RPC into a hot send target and burns rate limit quickly.
[trade]
These three values act as defaults used to seed each new mint's record in positions.json the first time Zephyr fetches its MintCache. Once a mint has a record, its per-mint values override these defaults, and any change made in the UI while that mint is active is saved to that record, not back to config.toml.
| Field | Description |
|---|---|
entry_pct | Default entry target as % from reference price. Negative = wait for dip (e.g., -12.0 = buy when price drops 12%). |
exit_pct | Default take-profit target as % above buy price (e.g., 5.0 = sell at +5%). Triggers when net holdings (after protocol fees) would be worth amount × (1 + exit_pct/100) SOL. |
target_change_delay | Default ms between reference price updates in auto-rebuy mode. |
Session state not in [trade]:
- Active mint: stored in
positions.jsontop-levelactive_mint, so Zephyr resumes on the same mint after a restart. - Buy amount: kept in UI memory only. Flows to the backend with every
bot_buy/bot_auto_rebuycommand. A backend restart re-seeds amount fromopen_cost_solof the active mint so the take-profit target stays correct mid-Hold.
Buy / sell params
[buy] and [sell] share the same structure.
| Field | Description |
|---|---|
slippage | Slippage tolerance in bps (100 = 1%, 200 = 2%) |
max_block_difference | Transaction freshness window in slots |
nonce_delay | Nonce rotation interval in ms |
panic_slippage | Slippage for panic sell (- key) in bps. Default 1000 = 10% |
Compute unit limit is set per-AMM in
src/bot/execute.rs::cu_limit(PumpFun ~80k, PumpSwap ~120k, Raydium CPMM ~140k, Meteora DAMM ~160k). Values were sized from observed consumption + ~15% headroom so priority fee paid isn't wasted on unused CU budget.
Dynamic fee
| Field | Description |
|---|---|
percentile | Fee percentile (×100). Example: 7500 = p75. Should be one of [general].fee_percentiles; if not, it is snapped to the nearest configured value. |
fee_multiplier | Multiply the fee by this factor. 1.0 = as-is |
fee_cap | Max fee in microlamports. 0 = no cap |
Dynamic tip
Every tip-paying processor (Jito, Nextblock, Helius, Zeroslot, Nozomi, Lunar, Astralane, Stellium, Falcon, and all four Bifrost subproviders) draws its tip from a single dynamic value sourced from sol-fee-tracker's aggregate tip pool. The legacy per-processor tip = 0.005 field has been removed.
| Field | Description |
|---|---|
percentile | Tip percentile (×100). Should be one of [general].tip_percentiles (snapped to the nearest if not). Serves as a ceiling on the adaptive scan |
tip_multiplier | Multiply the raw tip by this factor |
tip_floor | Minimum SOL tip (also stream-fallback when pool has fewer than 20 non-dust samples) |
tip_cap | Maximum SOL tip. Set equal to tip_floor to pin a constant |
adaptive | Congestion behaviour (see below) |
Adaptive mode only diverges in congested markets where the configured percentile's value exceeds tip_cap:
true(default): scan downward frompercentile, pick the highest subscribed percentile whose value still fits in[tip_floor, tip_cap]. Pays slightly less than cap with minimal landing-rate cost.false: pay exactlytip_cap(flat-cap). Deterministic "highest I can afford" tipping, useful for bundle-auction routing.
Processors
Each processor sends your transaction through a different block builder or RPC provider. Enable the ones you have access to.
Two rate-limiting gates
Every send passes through two gates in series:
- Processor-level delay (
buy_delay/sell_delayin ms): minimum interval between consecutive sends from this processor, regardless of which key is used. - Per-key TPS (keyed processors only): each
[[network.<proc>.api_keys]]has its owntpsbudget; the throttle walks the list first-fit and picks the next key whose1000/tps-ms interval has elapsed. Aggregate processor TPS = sum of per-key values, so adding more keys scales throughput horizontally.
Both gates must clear per send. On Too Many Requests / 429 errors, either lower the key's tps or increase the processor's buy_delay / sell_delay.
Direct processors
| Processor | Recommended TPS | Notes |
|---|---|---|
node | n/a | Anonymous RPC sender. Multi-URL via urls = [...], no api_keys block |
jito | 10 | |
nextblock | 50 | |
helius | 50 | |
zeroslot | 40 | 0slot. Zephyr provides 40 TPS by default — no api key required. Adding your own [[network.zeroslot.api_keys]] extends capacity (it's used as spillover) |
nozomi | 40 | Temporal (nozomi.temporal.xyz). Zephyr provides 40 TPS by default (no api key required). Adding your own [[network.nozomi.api_keys]] extends capacity (it's used as spillover) |
lunar | 50 | Lunar Lander. Zephyr provides a default key (TPS assigned per IP) — no api key required. Adding your own [[network.lunar.api_keys]] extends capacity (it's used as spillover) |
astralane | 50 | Iris2 gateway. Plain-text body (base64 of signed tx, no JSON-RPC envelope); api-key and method go in the URI query string. Shares astraXXX tip accounts with the Bifrost /astralane subroute |
stellium | 50 | Per-customer regional endpoints; api key is the trailing path component. Min tip 0.001 SOL (silently dropped below — tip_floor covers it) |
falcon | 40 | corvus-labs.io / falcon.wtf. NYC region only. Min tip 0.001 SOL (tip_floor covers it). Zephyr provides 40 TPS by default — no api key required. Adding your own [[network.falcon.api_keys]] extends capacity (it's used as spillover) |
Standard shape for keyed processors:
toml
[network.<name>]
enabled = true
buy_delay = 100
sell_delay = 100
[[network.<name>.api_keys]]
api_key = "your-api-key"
tps = <see table>Node is slightly different (multi-URL, no api_keys):
toml
[network.node]
enabled = true
urls = ["https://your-rpc-endpoint"]
buy_delay = 100
sell_delay = 100Multiple keys per processor
Each processor accepts any number of [[network.<proc>.api_keys]] blocks. Four 5-TPS Nextblock keys combine to a 20-TPS fleet:
toml
[[network.nextblock.api_keys]]
api_key = "key1"
tps = 5
[[network.nextblock.api_keys]]
api_key = "key2"
tps = 5
[[network.nextblock.api_keys]]
api_key = "key3"
tps = 5
[[network.nextblock.api_keys]]
api_key = "key4"
tps = 5With buy_delay = 50 (matching the 20 TPS total) the processor sends every 50 ms, rotating keys so no single key exceeds its 5 TPS budget.
Bifrost proxies
Bifrost routes through underlying providers. IP-allowlisted, no api_keys needed.
toml
[network.bifrost_bloxroute]
enabled = true
buy_delay = 50
sell_delay = 50
# Same shape for bifrost_astralane, bifrost_blockrazor, bifrost_lunarlanderEndpoint override
Every processor (including each Bifrost subprovider) accepts an optional endpoints list to override the baked-in regional fan-out. Empty / missing = use Zephyr's defaults (today's behavior). Non-empty = exact list that fires.
The audience is operators running a non-NY VPS — by default Zephyr fans out to NY-biased endpoints, which adds 80–150 ms WAN latency from Frankfurt or Tokyo. Set endpoints to nearby regions only:
toml
[network.jito]
enabled = true
buy_delay = 100
sell_delay = 100
endpoints = [
"https://frankfurt.mainnet.block-engine.jito.wtf",
"https://amsterdam.mainnet.block-engine.jito.wtf",
"https://london.mainnet.block-engine.jito.wtf",
]Entries are origins only — Zephyr appends each processor's auth scheme (path component, query param, header) the same way it does for the defaults.
Default endpoints
The lists Zephyr fans out to when endpoints is empty / missing. NY-biased by default. Override per processor as shown above for non-NY VPS deployments.
| Processor | Default origins |
|---|---|
jito | https://{ny,slc,amsterdam,frankfurt,london,singapore,tokyo}.mainnet.block-engine.jito.wtf |
helius | http://{ewr,slc,fra,ams,lon,sg,tyo}-sender.helius-rpc.com |
nextblock | http://{ny,slc,fra,tokyo,london}.nextblock.io |
nozomi | https://{pit1,ewr1,ash1,lax1,ams1,lon1,sgp1}.nozomi.temporal.xyz (tyo1 disabled — unstable) |
zeroslot | http://ny1.0slot.trade |
lunar | http://nyc.lunar-lander.hellomoon.io (NYC only — Lunar concentrates landing there) |
astralane | https://edge.astralane.io, http://ny.gateway.astralane.io, http://la.gateway.astralane.io |
stellium | http://ewr1.flashrpc.com |
falcon | http://nyc.falcon.wtf (NYC region only — additional regions added as Falcon expands) |
bifrost_* | https://rpc-ny.thornode.io (single Thor deployment; each subprovider — bloxroute, astralane, blockrazor, lunarlander — is a path on this origin) |
If a region you'd expect (Lunar EU/Asia, Bifrost AMS/DE/AP, Astralane FR/AMS/JP) isn't in this list, it's off by default — either underperforming today or requiring per-customer allowlisting. Set endpoints = [...] to opt in.
Nonce pool
Required when multiple processors are enabled. Reduces wasted transaction fees.
Create nonce accounts:
bash
./zephyr --generate-nonces 64Pubkeys are appended to config.toml automatically. Run again to add more. Rent is reclaimable. If you see Nonce pool exhausted warnings, add more.
nonce_delay in [buy] / [sell] controls rotation interval (ms). Nonces are automatically refilled when transactions land on-chain.
