A single binary that quotes and builds Solana swap transactions across 20 venues — Pump.fun / PumpSwap, Raydium AMM V4 / CPMM / CLMM / LaunchLab, Meteora DLMM / DAMM v1+v2 / DBC, Orca Whirlpool, Heaven, Moonit and seven dark-AMMs (full list) — routed through the on-chain RAZEX9 CPI router. You run it on your own machine, against your own RPC and your own Yellowstone gRPC endpoint.
It builds transactions. It never holds a private key and never sends anything on-chain — you sign and submit.
raze-router |
the binary, plus raze-router.service and selfhost.example.env |
program/ |
the on-chain CPI router this binary calls (Apache-2.0), source and all. Read it to see what your transactions execute — or deploy your own copy and point the binary at it with RAZE_ROUTER_PROGRAM. |
sdks/ |
Go, Python, Rust and TypeScript clients for the routes this binary serves |
Our instance is live at RAZEX9pxDuRCrtwR5wxUPAX3pWwAkBzvM8hF2fKaRE9 and the
binary uses it by default, so nothing needs configuring. It is a default, not a
binding: the router derives the config PDA, the swap-authority pool and the
invoked program from whichever id it is given, so a second deployment is a line
of env, not a fork. See program/README.md.
The mainnet instance was built from commit 01bbff2; program/ is ahead of it
by work that is not deployed yet (route_unified, and the wire types that go
with it). Everything the binary calls today is in both.
A license key from Raze (rzl_…) |
issued manually; it is shown once at creation |
| Your own Solana RPC endpoint | a real paid one — see Sizing your RPC |
| Your own Yellowstone gRPC endpoint | Helius, Triton, erpc, or your own Geyser plugin |
| Linux x86-64, glibc ≥ 2.34 | Debian 12+, Ubuntu 22.04+, RHEL 9+ — all fine |
| 4 cores, 8 GB RAM, 2 GB disk | a small VPS is enough — see Sizing the box |
You do not need to give us a wallet, and you do not configure one here.
There is no payer keypair, and no environment variable for one. This binary
is a transaction builder: POST /swap/sol/buy, POST /swap/sol/sell and
POST /swap/sol/instructions take a wallet address in the request body and
hand back unsigned V0 transactions with that address as fee payer. Signing
and sending are entirely on your side. You still need a funded wallet — it pays
for the swap, the network fees and any token-account rent — but you keep it, and
the server never sees its key.
(The only private key this binary can load at all is an optional ALT signer, which never signs a trade — see Reusing Raze's lookup tables.)
# 1. verify the binary you downloaded (see checksums.txt)
sha256sum -c checksums.txt
# 2. install
sudo install -m 755 raze-router /usr/local/bin/raze-router
sudo mkdir -p /var/lib/raze-router && sudo chown $USER /var/lib/raze-router
# 3. configure
cp selfhost.example.env raze.env
$EDITOR raze.env # license key, RPC_URL, GRPC_ENDPOINT
# 4. run
set -a && . ./raze.env && set +a
raze-router
# 5. first call (the license key is the credential)
curl -H "X-Api-Key: $RAZE_SELF_HOST_LICENSE_KEY" \
'http://127.0.0.1:8082/swap/sol/quote/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v?amount=1000000000&slippageBps=500'The port stays closed for the first minute or so. By design: the binary does
not bind until it has loaded a pool snapshot and built its first route index
(RAZE_READY_BIND_TIMEOUT_SECS, default 120 s). A connection refused right after
start is normal, not a fault. RAZE_SERVE_BEFORE_READY=1 binds immediately if
you would rather see the port up.
A ready node answers:
curl -s http://127.0.0.1:8082/health
# {"success":true,"data":{"status":"ok","slot":362…,"accounts":280000,"feed_stale_secs":0}}accounts climbing into the hundreds of thousands and feed_stale_secs at 0–3
means your gRPC feed is healthy. accounts stuck near 0 means it is not.
Full annotated template: selfhost.example.env.
RAZE_SELF_HOST_LICENSE_KEY=rzl_... # activates self-host mode; also your API credential
RPC_URL=https://your-rpc-endpoint # YOUR RPC
GRPC_ENDPOINT=https://your-yellowstone # YOUR Yellowstone gRPC
GRPC_TOKEN=... # only if your gRPC provider requires oneIf RPC_URL or GRPC_ENDPOINT is missing or empty, the binary prints what is
missing and exits 78 rather than booting into a router that answers
/health and knows nothing. This is a presence check, not a reachability check:
a wrong-but-present URL still starts.
RAZE_SELF_HOST_LICENSE_KEY is read once at startup — after a key change or
renewal, restart the process. Setting it to the empty string is the same as not
setting it: the binary reverts to fleet mode, with no license gate.
RAZE_CACHE_DIR=/var/lib/raze-router # absolute; MUST exist and be writable
BIND_ADDR=127.0.0.1:8082 # see "Network exposure" below
RAZE_TICKET_SECRET=<any random string> # enables route tickets; stays on your box| variable | default | meaning |
|---|---|---|
RAZE_HUB_URL |
https://hub.raze.bot |
where the license is verified. You do not need to set it. Empty string = detach, which fails closed (permanent 403). |
RAZE_SELF_HOST_VERIFY_INTERVAL_SECS |
300 |
license re-check interval. 0/non-numeric ⇒ back to 300. Read once at startup. |
WRAPPER_API_KEY |
(unset) | your own API credential. If set, it becomes the only accepted credential and the license key stops working as a header. |
PROMETHEUS_PORT |
9093 |
metrics port, bound to 127.0.0.1 in self-host mode. |
SNAPSHOT_INTERVAL_SECS |
60 |
how often the pool snapshot is written to disk. |
Variables that do nothing here: API_BACKEND_URL, STREAMING_HTTP_URL,
MIGRATION_WS_URL, INTERNAL_SERVICE_SECRET — their consumers (auth-key sync,
usage emitter, buyback poller, migration WS, /internal/*) are not started in
self-host mode.
Two you should leave alone: RAZE_SNAPSHOT_SEED_URL (the boot seeder does
run here, and points at a fleet-only endpoint you cannot reach — it will block
startup up to 120 s before giving up), and JWT_SECRET (it makes the auth layer
accept any HS256 bearer signed with it — a second door you did not ask for).
# /etc/systemd/system/raze-router.service
[Unit]
Description=RAZEX9 self-hosted swap router
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=raze
EnvironmentFile=/etc/raze/raze.env
WorkingDirectory=/var/lib/raze-router
ExecStart=/usr/local/bin/raze-router
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
# The router keeps the live pool state in RAM — expect 1-2 GB resident once
# warm. This cap is a backstop, not the working set. Do NOT add MemoryHigh:
# see "Sizing the box".
MemoryMax=8G
[Install]
WantedBy=multi-user.targetSet
RAZE_CACHE_DIRto an absolute path. Under systemd the default working directory is/, and without it the process tries to create/cache— which either fails withEACCESfor an unprivilegedUser=, or (worse, because it goes unnoticed) succeeds and plants a root-owned/cache. If you useProtectSystem=/ReadOnlyPaths=, add the directory toReadWritePaths=.
Everything is served on BIND_ADDR (default 0.0.0.0:8082).
| Method | Path | Needs license | Needs credential |
|---|---|---|---|
| GET | /swap/sol/quote/{mint} |
yes | yes |
| GET | /swap/sol/quote/stream (WebSocket) |
yes | yes (also ?apiKey=) |
| POST | /swap/sol/instructions |
yes | yes |
| POST | /swap/sol/buy-sell, /swap/sol/sell-buy |
yes | yes |
| POST | /swap/sol/buy |
yes | no — falls back to the public fee tier |
| POST | /swap/sol/sell |
yes | no — falls back to the public fee tier |
| GET | /health, /ready (+ /swap/sol/ aliases) |
no | no |
Not mounted in self-host mode: /perp/*, /internal/*, /public/alt/known,
and the product feed (WebSocket tape, execution lanes) — the latter is not even
compiled into this binary.
Your license key is the credential. Three transports, tried in this order:
# 1) X-Api-Key — checked first. Prefer this.
curl -H 'X-Api-Key: rzl_...' 'http://127.0.0.1:8082/swap/sol/quote/<mint>?amount=1000000000'
# 2) Authorization: Bearer
curl -H 'Authorization: Bearer rzl_...' 'http://127.0.0.1:8082/swap/sol/quote/<mint>?amount=1000000000'
# 3) ?apiKey= — a FALLBACK, read only when no Authorization header is present
curl 'http://127.0.0.1:8082/swap/sol/quote/<mint>?amount=1000000000&apiKey=rzl_...'The query form exists for the WebSocket: browsers cannot set headers on a WebSocket handshake.
new WebSocket('ws://127.0.0.1:8082/swap/sol/quote/stream?apiKey=rzl_...')Everywhere else use a header — a credential in a query string ends up in the access log of every proxy in front of you. The value is compared byte-for-byte and is not URL-decoded.
Precedence: CORS/OPTIONS → license gate (403) → auth (401) → routing (404).
403 SELF_HOST_UNLICENSED— the process is not licensed (not yet verified, revoked, orRAZE_HUB_URL=""). Every/swap/*path answers this, known or not.401— licensed, but your credential is missing (AUTH_REQUIRED) or rejected (INVALID_KEY).404— licensed and authenticated, path does not exist.
So a 401 or 403 tells you nothing about whether an endpoint exists, and
OPTIONS answers 200 on any path. Probe with an authenticated request.
It accepts amount (raw units; omitted ⇒ 1 SOL), slippageBps (default 500),
inputMint, outputMint, maxHops (1–3), includeDex, excludeDex, wallet
— and silently ignores every other query parameter, including swapMode.
For exact-out, use POST /swap/sol/instructions with "swapMode": "exactOut".
20 venues, up to 3 hops. The middle column is exactly what a quote returns in
route[].dex and exactly what includeDex / excludeDex match on. Matching
ignores case, spaces and punctuation — Raydium AMM V4, raydium-amm-v4 and
raydiumammv4 are the same token — and excludeDex wins over includeDex.
| venue | route[].dex |
also accepted | program |
|---|---|---|---|
| Pump.fun (bonding curve) | PumpFun |
pump |
6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P |
| PumpSwap (pAMM) | PumpSwap |
pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA |
|
| Raydium AMM V4 | Raydium AMM V4 |
v4, ammv4, raydiumv4, raydiumamm |
675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 |
| Raydium CPMM | Raydium CPMM |
cpmm, raydiumcpswap |
CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C |
| Raydium CLMM | Raydium CLMM |
clmm |
CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK |
| Raydium LaunchLab | RaydiumLaunchpad |
launchpad, launchlab, raydiumlaunchlab |
LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj |
| Meteora DLMM | Meteora DLMM |
dlmm |
LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo |
| Meteora DAMM v1 | Meteora DAMM V1 |
dammv1, damm1, meteoradamm |
Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB |
| Meteora DAMM v2 (cp-amm) | Meteora DAMM V2 |
dammv2, damm2, cpamm |
cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG |
| Meteora DBC | Meteora DBC |
dbc, meteoradynamicbondingcurve |
dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN |
| Orca Whirlpool | Orca Whirlpool |
orca, whirlpool |
whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc |
| Heaven | Heaven |
HEAVENoP2qxoeuF8Dj2oT1GHEnu49U5mJYkdeC8BAX2o |
|
| Moonit (Moonshot) | Moonit |
MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG |
|
| SolFi V2 † | SolFi V2 |
SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF |
|
| HumidiFi † ‡ | HumidiFi |
9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp |
|
| ZeroFi † | ZeroFi |
ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY |
|
| Tessera V † | Tessera V |
TessVdML9pBGgG9yGks7o4HewRaXVAMuoVj4x83GLQH |
|
| Obric V2 † | Obric V2 |
obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y |
|
| GoonFi † | GoonFi |
goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE |
|
| Alpha † | Alpha |
ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA |
pump resolves to the bonding curve, not PumpSwap. To include both, pass
both: includeDex=pumpfun,pumpswap.
An unknown token in includeDex is not an error — it simply matches nothing,
which on an include-list means no route. Check your spelling against the middle
column before concluding a pair is unroutable.
† Dark-AMMs. These seven price each fill off-chain and keep their on-chain
state obfuscated, so there are no reserves to read and no curve to evaluate.
Instead the router learns a swap instruction per market and direction from your
Yellowstone stream and replays it with the amount spliced in; the on-chain router
measures the real delta, and your slippageBps is the guard. What that means for
you: a dark-AMM market becomes routable only after your router has seen a swap
on it. On a cold start includeDex=humidifi legitimately returns no route for a
minute or two while the stream fills in. Captured instructions survive restarts in
pool_snapshot.pmm (see What it writes to disk); the
observed prices do not, so every restart re-warms.
‡ HumidiFi is first-hop only. Its amount_in is bound to an off-chain quote
handle captured for one specific input amount, and every hop after the first is
executed with the measured output of the hop before it — an amount that handle
never priced. A HumidiFi hop in position 2 or 3 is declined and the router takes
the next-best route.
Two further dark-AMM programs are watched but never routed — the router records
their fills and prices nothing through them, because a CPI from RAZEX9 reverts:
Quant (QuaNtZsgYRe5Z9Bk4LZ4cTD9tbkVoyCNf1R2BN9bBDv, Custom 0x9 — its
caller allowlist does not include the router) and BisonFi
(BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi, MissingRequiredSignature).
transactions[] is index-aligned with the wallets you sent, and entries the
router could not build come back as empty strings — no route, build failure,
nothing to sell, an invalid pubkey in your list, or a transaction that exceeded
Solana's 1232-byte wire limit. amountsOut[] is null at exactly those indexes.
Skip empty entries; never base64-decode them. If every wallet fails, the
response flips to success: false. All of this is HTTP 200 — check the field,
not the status code.
Those two routes are the only ones that answer without a credential. When they do, they fall back to the compiled public fee tier, which pins a hardcoded Raze wallet as the recipient of:
- a 0.001 SOL tip per built transaction (a plain SOL transfer out of the trading wallet).
feeTipLamportscan only raise it, never lower it.- a 0.5% (50 bps) platform fee, taken in-kind on-chain by the RAZEX9 router.
Neither the address nor the rate is configurable — the recipient is a compile-time constant. On that tier
tipWallet,tipLamports,feeWallet,feeBpsandfeeOnInputare parsed and then silently ignored.Send your license key on every call — buy and sell included. An authenticated request takes its fee settings from the request body, and a body with no
tipWallet/feeWalletproduces a transaction with no tip and no percentage fee at all.
On authenticated calls, tipWallet / tipLamports / feeWallet / feeBps /
feeOnInput are honoured as given. Three things to know before you price a
business model on it:
- The percentage fee is charged on-chain, in kind, by the RAZEX9 router — and only on routes the CPI router actually builds. A route that only the local assembler can build carries the tip alone, and your percentage fee is silently zero on it. Clamped to 1000 bps (10%).
- A SOL-denominated fee arrives as wSOL, credited to your fee wallet's associated token account — nothing in the transaction unwraps it. (The one exception: a fee on the native side of a PumpFun bonding-curve trade is paid in native lamports.) The trader's wallet pays the ~0.00204 SOL rent to create your fee ATA the first time.
/swap/sol/buy-selland/swap/sol/sell-buywork differently: there the fee is an off-chain native-SOL transfer sized on the combined notional, the 10% clamp does not apply, and every transfer is floored up to 100,000 lamports per destination — so on small round-trips your wallet receives more thanfeeBps.
A malformed pubkey in any of those fields is discarded silently rather than rejected — which disables that fee leg instead of returning an error. Check your own input.
The Solana signature fee, rent for any token account the build has to open
(~0.00204 SOL per new ATA, recoverable when closed), and a compute budget:
every transaction is prefixed with SetComputeUnitLimit + SetComputeUnitPrice
priced to target a 0.0001 SOL priority fee. That goes to the validator, not to
Raze. Override per request with transactionsFeeLamports; 0 removes both
instructions.
BIND_ADDR defaults to 0.0.0.0:8082 — every interface. That default
assumes a firewall in front of it; on your machine it is a decision.
Anyone who can reach that port can make your node build buy/sell transactions without knowing anything, because those two routes need no credential. The transactions come back unsigned, so nobody can move your funds this way — what is exposed is your RPC bill, your licensed capacity, and fee revenue going to Raze instead of you.
Set BIND_ADDR=127.0.0.1:8082 and put your own reverse proxy in front, or
firewall the port down to hosts you trust. There is no built-in rate limiter.
Metrics (/metrics, port 9093) are bound to 127.0.0.1 in self-host mode and
cannot be moved — they sit outside the license gate, so they are never put on
the network for you.
All state lives under RAZE_CACHE_DIR (default ./cache, relative to the
working directory — this binary has no absolute path compiled in).
| file | when | if the write fails |
|---|---|---|
pool_snapshot.bin |
every SNAPSHOT_INTERVAL_SECS (60 s) |
[persist] save failed in the log; every restart is a cold start |
pool_snapshot.pmm |
every 60 s (dark-AMM templates) | templates lost on restart |
managed_alts*.json |
only with ALT peer sync | silently — no log at all |
Saves are write-temp-then-rename, so the process needs write permission on the directory, not just the files. A missing or unwritable directory does not stop the router: it just cold-starts forever, which with an undersized RPC shows up as missing routes until the state is rebuilt.
/health— unconditional 200 while the process lives. It does not reflect the license state./ready— 200 only when snapshot loaded and first route index built and the Yellowstone feed is fresh; 503 otherwise.
Neither passes through the license gate, so a monitor watching only health stays green on a router whose trading routes are all 403. Probe with a real quote if you want to know that the product works.
A small VPS is enough. The binary is a stateless builder — the only state it holds is a cache of live pool accounts.
| CPU | 4 cores — quoting is cheap; the route index rebuild is the only burst |
| RAM | 8 GB — expect 1–2 GB resident once warm, with room to absorb spikes |
| disk | 2 GB — the pool snapshot is a few hundred MB and grows with coverage |
| network | a continuous gRPC stream, on the order of 10–20 GB/day |
The sample unit sets MemoryMax=8G as a backstop, not as a target.
Do not add
MemoryHigh. It looks like the gentler of the two caps and it is the more dangerous one: crossing it puts the process into cgroup reclaim throttling, which parks threads in D-state and degrades quoting and stream ingest silently — no error, no restart,/healthstill green.MemoryMaxfails loudly instead: the kernel kills the process and systemd restarts it cold, which is both recoverable and visible.
If resident memory climbs well past a couple of GB and stays there,
MALLOC_ARENA_MAX=2 in the environment file is the cheap first thing to try —
glibc's per-thread arenas fragment under a stream this busy. To hold less
history, lower POOL_CACHE_MAX_SLOT_AGE_SECS (seconds, default 24 h); it trims
how long a quiet pool stays quotable, so it trades coverage for memory.
What you should not size down is either paid feed. The router subscribes to every account owned by the venue programs, with no server-side filtering and no data slicing, so ask your Yellowstone provider how they bill that shape of subscription before you pick a plan. The RPC matters just as much, for a different reason:
The router does a large cold-hydration sweep at boot and keeps warming CLMM
tick-arrays and DLMM bin-arrays continuously — tens of thousands of accounts per
sweep via getMultipleAccounts. Measured against the free public endpoint:
requested=46726 fetched=376, i.e. ~99% rate-limited away.
Prices come from your gRPC feed, not from RPC — so an undersized RPC shows up as
missing routes, not wrong prices: concentrated-liquidity pools whose satellite
accounts never arrive are declined rather than mispriced. That is the safe
direction, but it costs you coverage. Use an endpoint with real
getMultipleAccounts throughput.
Serialized transactions embed a recentBlockhash fetched from your RPC on a
2-second loop. Nothing validates it before serializing and /ready does not look
at it, so a healthy-looking node can hand you an unusable transaction:
- RPC never answered since boot → the cache still holds the all-zero hash;
you get
200 success:trueand your submit fails withBlockhash not found. Reject any transaction whoserecentBlockhashis all zeros. - RPC answered once then stopped → the last good hash is served indefinitely; transactions land for ~60–90 s, then fail the same way with nothing else changing.
Only transport-level failures are logged; HTTP error pages and JSON-RPC error
bodies (401, 429) are discarded silently. Poll getLatestBlockhash against your
own RPC as the real signal. Sign and submit promptly — the hash is already up to
2 s old and expires in ~60–90 s.
- It starts closed. Until the first successful verification, every
/swap/*route answers403 SELF_HOST_UNLICENSED. In normal operation you never see this: the port does not open until after the index is built, by which time the license is verified. - The binary calls
POST {RAZE_HUB_URL}/api/v1/licenses/verifyat boot, then everyRAZE_SELF_HOST_VERIFY_INTERVAL_SECS(default 300 s), with a 5 s timeout. - A revocation (hub answers 2xx with
valid:false) closes trading routes within one interval. - An unreachable hub does not — timeouts, 502s, malformed bodies are treated as transient and keep the previous state through 5 consecutive failures; the 6th closes the gate anyway. At the default interval that is roughly 30 minutes of autonomy. Lowering the interval shortens that window.
- Fail-closed is not terminal: the loop keeps polling and routes reopen by themselves on the first good answer, no restart needed.
- There is no fast retry. If you start while the hub is unreachable, expect one full interval of 403 before trading routes open.
- The gate never gives you access to Raze's ALT authority or payer wallets.
Diagnose with the logs (target=selfhost): hub verify call failed,
hub unreachable too long — failing closed, license verified — trading routes enabled.
Your builds always use the two static lookup tables — nothing to configure. On top of that, Raze maintains a rotating pool of tables that make transactions smaller (more of them fit under the 1232-byte wire limit). You can consume that pool read-only, without ever creating or paying for a table.
Only do this if Raze gives you the URL of the box that IS the ALT source of truth. Use exactly that URL, never a geo-routed alias: reconciliation is peer-authoritative, so a URL that lands on a different box between polls discards everything it learned from the previous one.
RAZE_ALT_AUTOEXTEND=0 # REQUIRED — absent means ON, not off
RAZE_ALT_SYNC_PEER_URL=<the exact URL Raze gives you>
RAZE_ALT_SYNC_INTERVAL_SECS=60 # defaultRAZE_ALT_AUTOEXTEND=0 is load-bearing, not a restatement of the default. Absent
or empty means ON. It is inert today only because you have no ALT signer —
but the moment ALT_PRIVATE_KEY or ALT_KEYPAIR_PATH is set, an unset
RAZE_ALT_AUTOEXTEND means the router starts creating and extending tables with
your SOL, and the read-only sync never starts at all.
The poller calls {peer}/public/alt/known with your license key as
x-license-key, then re-reads every newly learned table from your own RPC
before trusting it. It never asks you for a signer: creating, extending or
closing a lookup table needs its authority, but referencing one does not.
sha256sum -c checksums.txtThe binary is built from a private source tree with:
- no default features (
--no-default-features) — the product feed and all fleet-only paths are not compiled in; - build paths remapped and the build-id stripped;
- a portability floor of glibc 2.34, so it runs on Debian 12+ / Ubuntu 22.04+ / RHEL 9+ regardless of what it was built on;
- OpenSSL statically linked — no
libsslneeded on your machine; - automated guards that fail the build if the artifact contains a local build path, a fleet IP or hostname, or anything shaped like a credential.
raze-router --version 2>/dev/null || true # see VERSION for the release identity
ldd --version | head -1 # your glibc must be >= 2.34Contact Raze with your license label. When reporting a problem, include:
- the output of
curl -s localhost:8082/health, - whether
/swap/sol/quote/<a known mint>returns 200, 401 or 403, - the last 50 log lines,
- your RPC and gRPC providers (not the keys).
Pump fee recipients rotate, and both pump venues read the currently authorized set straight from their own on-chain global account on every build — through your RPC, on this build. A rotation is picked up on the next quote and needs no new binary from us.