Skip to content

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 the private_key you put in config.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-level active_mint), never touches config.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 to config.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]

FieldDescription
private_keyYour 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_percentilesPriority fee percentiles (×100) the backend subscribes to, displayed in the fee bar
tip_percentilesTip percentiles (×100) the backend subscribes to, displayed in the tip bar
access_codePassword 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.
debugVerbose logging
noncesDurable nonce account pubkeys (populated by --generate-nonces)

[network]

FieldDescription
rpc_urlSolana RPC endpoint for read-only calls (account lookups, nonce recovery). Leave empty to use the Zephyr-provided default.
grpc_urlgRPC 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_tokenAuth token for the gRPC endpoint. Ignored when grpc_url is empty; an empty token is honored for providers that don't authenticate
processorWhich 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.

FieldDescription
entry_pctDefault entry target as % from reference price. Negative = wait for dip (e.g., -12.0 = buy when price drops 12%).
exit_pctDefault 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_delayDefault ms between reference price updates in auto-rebuy mode.

Session state not in [trade]:

  • Active mint: stored in positions.json top-level active_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_rebuy command. A backend restart re-seeds amount from open_cost_sol of the active mint so the take-profit target stays correct mid-Hold.

Buy / sell params

[buy] and [sell] share the same structure.

FieldDescription
slippageSlippage tolerance in bps (100 = 1%, 200 = 2%)
max_block_differenceTransaction freshness window in slots
nonce_delayNonce rotation interval in ms
panic_slippageSlippage 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

FieldDescription
percentileFee percentile (×100). Example: 7500 = p75. Should be one of [general].fee_percentiles; if not, it is snapped to the nearest configured value.
fee_multiplierMultiply the fee by this factor. 1.0 = as-is
fee_capMax 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.

FieldDescription
percentileTip percentile (×100). Should be one of [general].tip_percentiles (snapped to the nearest if not). Serves as a ceiling on the adaptive scan
tip_multiplierMultiply the raw tip by this factor
tip_floorMinimum SOL tip (also stream-fallback when pool has fewer than 20 non-dust samples)
tip_capMaximum SOL tip. Set equal to tip_floor to pin a constant
adaptiveCongestion behaviour (see below)

Adaptive mode only diverges in congested markets where the configured percentile's value exceeds tip_cap:

  • true (default): scan downward from percentile, 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 exactly tip_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:

  1. Processor-level delay (buy_delay / sell_delay in ms): minimum interval between consecutive sends from this processor, regardless of which key is used.
  2. Per-key TPS (keyed processors only): each [[network.<proc>.api_keys]] has its own tps budget; the throttle walks the list first-fit and picks the next key whose 1000/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

ProcessorRecommended TPSNotes
noden/aAnonymous RPC sender. Multi-URL via urls = [...], no api_keys block
jito10
nextblock50
helius50
zeroslot400slot. Zephyr provides 40 TPS by default — no api key required. Adding your own [[network.zeroslot.api_keys]] extends capacity (it's used as spillover)
nozomi40Temporal (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)
lunar50Lunar 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)
astralane50Iris2 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
stellium50Per-customer regional endpoints; api key is the trailing path component. Min tip 0.001 SOL (silently dropped below — tip_floor covers it)
falcon40corvus-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 = 100

Multiple 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 = 5

With 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_lunarlander

Endpoint 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.

ProcessorDefault origins
jitohttps://{ny,slc,amsterdam,frankfurt,london,singapore,tokyo}.mainnet.block-engine.jito.wtf
heliushttp://{ewr,slc,fra,ams,lon,sg,tyo}-sender.helius-rpc.com
nextblockhttp://{ny,slc,fra,tokyo,london}.nextblock.io
nozomihttps://{pit1,ewr1,ash1,lax1,ams1,lon1,sgp1}.nozomi.temporal.xyz (tyo1 disabled — unstable)
zeroslothttp://ny1.0slot.trade
lunarhttp://nyc.lunar-lander.hellomoon.io (NYC only — Lunar concentrates landing there)
astralanehttps://edge.astralane.io, http://ny.gateway.astralane.io, http://la.gateway.astralane.io
stelliumhttp://ewr1.flashrpc.com
falconhttp://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 64

Pubkeys 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.

Zephyr Docs

Enter the access code to continue.