If you run an arbitrage or sniper bot on Solana, you almost certainly fan the same opportunity out to several landing services at once. BoltTx, Helius Sender, Jito, 0slot. Whoever reaches the leader first wins the race.
Here is the part most people get wrong: each of those copies is a separate transaction, and every one that lands costs you a base fee.
This post explains why that happens and how durable nonce turns the losing copies into free no-ops. There is complete, runnable code near the end.
Why three providers means three transactions
Every landing service requires its own tip instruction, paid to its own address. Change the tip recipient and you change the instruction. Change an instruction and you change the message. Change the message and you get a different signature.
So you are not sending "one transaction to three places." You are sending three distinct transactions that happen to perform the same trade:
Trade opportunity
├── tx #1 tip → BoltTx address signature A
├── tx #2 tip → Helius address signature B
└── tx #3 tip → Jito address signature C
A Solana leader has no idea these are related. It will happily execute all three.
If your arbitrage is real, that is a disaster — you execute the same trade three times. If your program aborts on zero profit (as it should), all three abort, and you pay three base fees for nothing.
What a reverted transaction actually costs
This is a real transaction from our production logs. The bot's program checked for profit, found none, and aborted:
err : InstructionError [5, Custom(1)]
program log : profit=0
tip address : +0 lamports ← tip fully reverted
fee payer : -135,000 lamports ← base fee still charged
The tip reverts atomically. The base fee does not. That asymmetry is the whole problem. A reverted arbitrage is cheap, but it is not free. When you race three providers, you pay it three times per opportunity.
At 135,000 lamports per attempt across three providers, 1,000 opportunities a day burns roughly 0.4 SOL/day on transactions that produced nothing.
Durable nonce: how it removes the waste
A normal transaction carries a recentBlockhash, which expires in about 90 seconds. A durable-nonce transaction replaces that with a value stored in an on-chain nonce account.
The runtime enforces three rules:
- The transaction's first instruction must be
AdvanceNonceAccount. - The blockhash field must match the nonce account's current stored value.
- When the transaction executes, the nonce advances to a new value.
Now watch what happens when all three copies share one nonce account:
nonce = X
tx #1 arrives first → X matches → executes → nonce advances to Y
tx #2 arrives later → carries X, nonce is now Y → REJECTED
tx #3 arrives later → carries X, nonce is now Y → REJECTED
Here is the part that matters for your wallet:
That rejection happens during transaction validation, before execution. The transaction never enters a block. No compute is consumed. No fee is charged.
This is categorically different from "the transaction failed." A failed transaction, like the profit=0 example above, already made it into a block and paid its base fee. A nonce mismatch never gets that far.
| Outcome | Enters a block? | Fee charged? |
|---|---|---|
| Executed, succeeded | Yes | Yes |
Executed, reverted (profit=0) |
Yes | Yes |
| Nonce mismatch (lost the race) | No | No |
You get the full benefit of racing every provider you can reach, and you pay exactly once, for the copy that actually won.
What it costs you
AdvanceNonceAccount consumes roughly 150 compute units. A typical arbitrage transaction burns 150,000 to 250,000 CU, so the overhead is around 0.1%.
You also fund the nonce account with rent (~0.0015 SOL, one time, recoverable when you close the account).
There is no added latency. The instruction is processed inline with the rest of your transaction.
The one real constraint: one in-flight transaction per nonce account
A nonce account holds exactly one value. While a transaction using nonce X is in flight, you cannot send an unrelated transaction on that same account. You would be creating two competitors for one slot, and only one could ever land.
This trips people up, so to be explicit:
- Same opportunity, N providers → one nonce account. This is the intended pattern.
- N different opportunities → N nonce accounts.
High-frequency bots therefore run a nonce pool: a set of accounts cycled as opportunities arrive. The two highest-volume arbitrage bots on our network do exactly this. It is visible in their on-chain instruction data.
Working code
Create and fund the nonce account once:
solana-keygen new -o nonce-account.json
solana create-nonce-account nonce-account.json 0.0015
solana nonce-account nonce-account.json # prints the current nonce value
Then race providers with it:
import base64
import requests
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.hash import Hash
from solders.message import Message
from solders.transaction import Transaction
from solders.system_program import (
advance_nonce_account, AdvanceNonceAccountParams,
transfer, TransferParams,
)
payer = Keypair.from_base58_string("YOUR_PAYER_SECRET")
nonce_account = Pubkey.from_string("YOUR_NONCE_ACCOUNT")
nonce_authority = payer.pubkey()
# Each provider needs its own tip address and tip amount.
PROVIDERS = [
{
"name": "bolttx",
"url": "https://fr.bolttx.io/v1/send",
"tip": Pubkey.from_string("BoLt1A77XnXgmLPTWFXztQ9oZRrTPuQHV7cChFCt35g6"),
"lamports": 1_000_000,
"key": "YOUR_BOLTTX_KEY",
},
# add your other providers here
]
def current_nonce(rpc_url: str) -> Hash:
"""Read the nonce account's stored value. This replaces getLatestBlockhash."""
r = requests.post(rpc_url, json={
"jsonrpc": "2.0", "id": 1, "method": "getAccountInfo",
"params": [str(nonce_account), {"encoding": "base64"}],
}, timeout=10).json()
raw = base64.b64decode(r["result"]["value"]["data"][0])
# Layout: 4-byte version + 4-byte state + 32-byte authority + 32-byte nonce
return Hash.from_bytes(raw[40:72])
def build(nonce: Hash, provider: dict, trade_instructions: list) -> str:
"""Build one provider-specific copy of the same trade."""
ixs = [
# MUST be instruction 0 — the runtime rejects it otherwise.
advance_nonce_account(AdvanceNonceAccountParams(
nonce_pubkey=nonce_account,
authorized_pubkey=nonce_authority,
)),
*trade_instructions,
transfer(TransferParams(
from_pubkey=payer.pubkey(),
to_pubkey=provider["tip"],
lamports=provider["lamports"],
)),
]
msg = Message(ixs, payer.pubkey())
# The nonce value goes where the blockhash normally would.
tx = Transaction([payer], msg, nonce)
return base64.b64encode(bytes(tx)).decode()
def race(trade_instructions: list, rpc_url: str) -> None:
"""Fan one opportunity out to every provider. Only the winner pays a fee."""
nonce = current_nonce(rpc_url)
for p in PROVIDERS:
payload = build(nonce, p, trade_instructions)
try:
requests.post(
p["url"],
json={"transaction": payload},
headers={"Authorization": f"Bearer {p['key']}"},
timeout=5,
)
except requests.RequestException:
pass # a slow provider must never block the others
Two details worth calling out:
advance_nonce_account must be instruction index 0. Put it anywhere else and the runtime will not treat this as a durable-nonce transaction. You lose the deduplication and go back to paying every provider.
Do not call getLatestBlockhash. The nonce value replaces it. Fetching a real blockhash and passing it here produces an ordinary transaction with none of the benefits.
How to verify it is working
After a race, look up the losing signatures on-chain. If durable nonce is doing its job, they will not exist. Not "failed," but absent entirely. Only the winner appears in a block.
# Winner: returns a transaction
solana confirm -v <WINNING_SIGNATURE>
# Losers: "Transaction not found" — this is the result you want.
# It means they were rejected at validation and cost you nothing.
solana confirm -v <LOSING_SIGNATURE>
If the losing signatures do appear on-chain carrying an error, durable nonce is not engaged. Check that AdvanceNonceAccount really is instruction 0, and that you passed the nonce value rather than a blockhash.
Where this leaves you
Racing multiple providers is the right strategy on Solana. Landing is probabilistic, and more independent paths to the leader means better odds. The mistake is paying for every path.
With durable nonce you can:
- Race as many providers as you want
- Pay the base fee exactly once per opportunity
- Accept ~0.1% compute overhead and zero added latency
If you want a fast path to race against, BoltTx runs direct-connect endpoints in Frankfurt, New Jersey, Los Angeles, and Singapore. Our Frankfurt node measures a 133 ms median with roughly 1 ms jitter, and you only pay a tip when your transaction actually lands.