Why an On-Chain Program Is Necessary
Imagine you're buying a house. In a normal real estate closing, three things happen in sequence: the buyer wires money to escrow, the title transfers from seller to buyer, and escrow releases the funds to the seller. All three steps are bound together in a single closing. If the title transfer fails for any reason — a lien surfaces, a signature is missing, the notary finds a discrepancy — the entire transaction unwinds. The money goes back to escrow. Nobody loses anything. The deal simply doesn't happen.
Now imagine a version of that closing where each step is independent. The buyer wires the money. Separately, the title company attempts the transfer. Separately, the escrow company releases the funds. No coordination. No binding. If the title transfer fails after the money is already wired, the buyer has to chase down the funds through a separate process. If the escrow releases before the title transfers, the seller has the money and the house. Every permutation of partial completion creates a mess that takes weeks or months to untangle.
Nobody would buy a house this way. The entire real estate closing process exists because people figured out centuries ago that multi-step financial transactions must be atomic — all steps complete, or none do. There is no acceptable middle ground.
Cyclic arbitrage has the same problem.
The Three-Swap Problem
A cyclic arbitrage opportunity is a price discrepancy across a loop of trading pairs. Start with SOL, swap it for Token A on one DEX, swap Token A for Token B on another DEX, swap Token B back to SOL on a third DEX. If the final SOL amount is greater than the initial SOL amount, profit. If not, loss.
The simplest approach is to execute each swap as a separate transaction. Three swaps, three transactions. Send them to the network in sequence. Wait for each to confirm before sending the next.
This approach has a fatal flaw, and it's the same flaw as the uncoordinated house closing: partial execution.
Transaction 1 sends. SOL becomes Token A. Confirmed. Transaction 2 sends. Token A becomes Token B. Confirmed. Transaction 3 sends. Token B should become SOL. It fails. Maybe the pool's price moved in the milliseconds between when the opportunity was detected and when the third transaction landed. Maybe another trader hit the same pool first. Maybe the slippage tolerance was too tight. The reason doesn't matter. What matters is the outcome.
I now hold Token B. I don't want Token B. I have no use for Token B. Token B might be a low-liquidity meme token that's dropping in value every second I hold it. The original opportunity was to start with SOL and end with more SOL. Instead I start with SOL and end with a bag of Token B and a prayer.
This is not a theoretical risk. It's the default outcome of running multi-hop swaps as independent transactions. The first swap changes my token holdings irrevocably. The second swap changes them again. If the third swap fails, I'm stranded mid-route with whatever intermediate token I happen to be holding. There is no undo button. There is no escrow. There is no closing coordinator who unwinds the whole thing.
The situation is analogous to a wire transfer that gets stuck in limbo. You send money from Bank A to Bank B, intending for Bank B to forward it to Bank C. The first wire goes through. The second wire bounces. Your money is now sitting in Bank B's holding account, and extracting it requires phone calls, forms, and waiting periods. Except in crypto, there is no Bank B customer service line. There is no form. There is just a token sitting in a wallet, losing value.
What "Atomic" Actually Means
The word "atomic" gets thrown around in software engineering until it loses its meaning. In this context, it means something very specific: either all three swaps execute successfully within a single transaction, or none of them execute at all. There is no state where swap 1 completed but swap 2 didn't. There is no state where the intermediate token exists in my wallet. The transaction either completes the full cycle — SOL to Token A to Token B back to SOL — or the entire thing reverts and I still have my original SOL.
This is the same guarantee that a real estate simultaneous closing provides. The buyer doesn't wire money and hope the title transfers. The title doesn't transfer and hope the money arrives. Everything happens within a single coordinated event. If any piece fails, the entire closing is voided.
On Solana, this atomic guarantee is achievable because a single transaction can contain multiple instructions, and those instructions either all succeed or all fail. The runtime processes them sequentially within the transaction. If instruction 3 fails, the state changes from instructions 1 and 2 are rolled back. The accounts revert to their pre-transaction state. It's as if the transaction never happened.
But there's a catch: simply putting three separate swap instructions into one transaction doesn't give me what I need. Each DEX swap instruction operates independently. Swap instruction 1 sends SOL and receives Token A. Swap instruction 2 needs Token A as input. But these instructions don't coordinate with each other. They don't share state. They don't know about each other. Getting the token flow right — making sure the output of swap 1 is the input of swap 2, that accounts are set up correctly, that the intermediate token accounts exist and are writable — requires orchestration. And that orchestration needs to happen on-chain.
Enter the Custom On-Chain Program
This is why a custom on-chain program is necessary. Not optional. Not a nice-to-have. Necessary.
The custom program acts as the orchestrator — the closing agent in the real estate analogy. It receives a single instruction: "execute this three-hop cycle." Inside that single instruction, the program makes three sequential Cross-Program Invocations (CPIs). It calls DEX 1's swap program, then DEX 2's swap program, then DEX 3's swap program. All within one transaction.
CPI is the mechanism by which one Solana program calls another. Program A invokes Program B, passing the required accounts and instruction data. Program B executes and returns control to Program A. Program A then invokes Program C. Then Program D. The calling program maintains control throughout. If any CPI call fails — if any of the three DEX swap programs returns an error — the entire transaction reverts.
Think of it like a general contractor managing subcontractors on a construction project. The general contractor (custom program) hires the electrician (DEX 1), the plumber (DEX 2), and the HVAC installer (DEX 3). The general contractor coordinates the sequence, makes sure each subcontractor has what they need, and is responsible for the overall outcome. If the plumber discovers a problem that makes the project infeasible, the general contractor stops everything. The electrician's work gets torn out. The project reverts to its pre-construction state. Nobody gets paid for partial work.
Without the general contractor — without the custom program — each subcontractor works independently. The electrician finishes and leaves. The plumber shows up, discovers a conflict, and walks off the job. The HVAC installer never even starts. The homeowner is left with half-finished electrical work, no plumbing, and no HVAC. Partial completion. The worst possible outcome.
How CPI Chaining Works
The mechanics of CPI chaining are straightforward in concept, even if the implementation details require care.
My custom program exposes a single instruction for executing arbitrage cycles. This instruction receives all the accounts needed for all three swaps: the pool accounts for each DEX, the token accounts for each intermediate token, the relevant program IDs, and any protocol-specific accounts like fee receivers or authority PDAs.
When this instruction is invoked, the program:
- Constructs the first swap instruction using the appropriate accounts and data for DEX 1.
- Calls
invokeorinvoke_signedto execute that instruction via CPI. - The Solana runtime transfers control to DEX 1's program. The swap executes. SOL leaves the input account, Token A arrives in the intermediate account.
- Control returns to my program.
- Constructs the second swap instruction for DEX 2.
- Calls
invokeagain. Token A leaves, Token B arrives. - Control returns.
- Constructs the third swap instruction for DEX 3.
- Calls
invoke. Token B leaves, SOL returns to the output account. - Control returns.
If step 2 fails, the transaction reverts. Nothing happened. If step 6 fails, steps 2-3 are undone. Nothing happened. If step 9 fails, steps 2-7 are undone. Nothing happened. At every point, either the full cycle completes or the state is exactly what it was before the transaction started.
The sequential nature is critical. Each CPI call depends on the output of the previous one. DEX 2 can't swap Token A for Token B unless the first CPI actually deposited Token A into the correct account. The program doesn't need to pass token amounts between calls explicitly — the token balances in the accounts change as a result of each CPI, and the next CPI reads those updated balances. The on-chain state is the communication channel.
This is fundamentally different from off-chain orchestration, where I would need to query token balances between swaps, adjust the next swap's parameters based on the actual output of the previous one, and handle the case where the balance check itself takes time during which the balance might change again. On-chain, within a single transaction, the state is consistent. The balance that DEX 1's swap deposited is exactly the balance that DEX 2's swap reads. No race conditions. No stale data. No TOCTOU (time-of-check-time-of-use) bugs.
The Profit Check: Revert If Unprofitable
Atomic execution solves the partial-completion problem. But there's a second problem that the custom program solves: profit verification.
Even if all three swaps execute successfully, the cycle might not be profitable. Prices move. Slippage happens. The amount of SOL that comes back at the end might be less than the amount that went in at the beginning. Without profit verification, a "successful" execution could still lose money.
The custom program adds a check after the final swap: compare the ending SOL balance to the starting SOL balance. If the ending balance is not greater than the starting balance by at least a specified minimum, the program returns an error. The transaction reverts. All three swaps are undone. My SOL is back where it started.
This is the equivalent of a contingency clause in a real estate contract. "This purchase is contingent on the home inspection passing." If the inspection fails, the deal is off. The buyer doesn't buy a house with a cracked foundation just because the paperwork was already in progress. The contingency protects against a technically-complete-but-substantively-bad outcome.
In the arbitrage context, the profit check is the contingency clause. The program doesn't just ask "did all the swaps complete?" It asks "did all the swaps complete AND did the result produce profit?" Only if both answers are yes does the transaction finalize. If the swaps complete but produce a loss, the transaction reverts just as surely as if a swap had failed.
This creates a powerful asymmetry. If the cycle is profitable, I capture the profit. If the cycle is unprofitable, I lose nothing — my token balances are exactly what they were before the attempt. The only cost of a failed attempt is the compute units consumed by the transaction, which on Solana amounts to a fraction of a cent. And when submitting via Jito bundles, even that cost disappears: if the bundle fails, no tip is paid. The validator doesn't charge for work that didn't finalize.
This asymmetry — unlimited upside, near-zero downside — is the fundamental reason why on-chain arbitrage bots can afford to attempt hundreds or thousands of cycles per hour. Each attempt is like a poker player who gets to see the river card and then decide whether to stay in or fold, keeping their ante either way. The fold costs nothing. The stay-in captures the pot. You would play every hand if folding were free.
Why Not Just Use Multiple Instructions in One Transaction?
A reasonable question: why build a custom program at all? Solana transactions can contain multiple instructions. Why not just put three raw swap instructions into a single transaction and submit it?
The answer is twofold.
First, coordination. Three independent swap instructions in a transaction don't share context. My custom program knows the full cycle topology — which token comes out of swap 1, which goes into swap 2, which comes out of swap 2, which goes into swap 3. It manages the intermediate token accounts. It handles the account setup for each DEX's specific requirements. Without the custom program, I need to set up all of this off-chain, and any mistake in account ordering, token account ownership, or instruction data causes the transaction to fail.
Second, and more importantly: the profit check. Three raw swap instructions have no way to perform a post-execution profit comparison. There is no built-in Solana instruction that says "compare these two token balances and revert if the difference is negative." That logic must live somewhere. It lives in the custom program. The program reads the starting balance before the first CPI, reads the ending balance after the last CPI, performs the comparison, and either succeeds or reverts. Without a custom program, there is no place for this logic to execute.
Some arbitrageurs try to work around this by computing expected outputs off-chain and setting tight slippage limits on each swap. If the actual output deviates from the expected output, the swap instruction itself will fail due to slippage, and the transaction reverts. This approach is fragile. Slippage limits that are too tight cause transactions to fail even when the cycle is profitable. Slippage limits that are too loose allow unprofitable cycles to execute. The right slippage limit is unknowable in advance because it depends on the exact state of three different pools at the exact moment of execution. The profit check avoids this entire problem. It doesn't care about individual swap slippage. It cares about the bottom line: is there more SOL at the end than at the beginning?
The Cost of Trying
The economics of on-chain arbitrage with a custom program are remarkably favorable. Consider the possible outcomes of any single attempt:
Cycle is profitable and executes successfully. Profit is captured. Transaction fees are paid from the profit. Net positive.
Cycle is unprofitable and reverts. No token movement occurs. No profit, but no loss of tokens either. The only cost is the compute units consumed by the transaction — roughly 0.000005 SOL for a typical three-hop cycle.
Cycle fails due to a swap error (pool state changed, slippage, etc.). Same as above. Revert. Minimal compute cost.
In all failure cases, the cost is negligible. There is no scenario where a failed cycle results in holding an unwanted intermediate token. There is no scenario where a failed cycle moves tokens irreversibly. The worst case is spending a tiny fraction of a cent on compute.
With Jito bundles, the economics improve further. Bundles that fail to execute are not included in the block. If the bundle is not included, no tip is paid. The cost of a failed attempt drops to literally zero. Not "approximately zero" — actually zero. No inclusion, no payment.
This changes the calculus of attempt frequency. If trying is effectively free, the rational strategy is to try everything that has a nonzero probability of profiting. Cast the widest possible net. Submit every cycle that looks even marginally promising. The ones that don't work out cost nothing. The ones that do work out generate revenue. It's a pure volume game with no downside risk per attempt.
Compare this to the off-chain multi-transaction approach, where each failed attempt potentially costs the intermediate tokens, the gas fees for the successful early transactions, and the time and additional gas fees required to swap the stranded tokens back. In that world, each attempt carries real risk, and the rational strategy is to be highly selective — only attempt cycles with very high confidence of profitability. Fewer attempts, higher bar, lower total revenue.
The Boundary Between Off-Chain and On-Chain
The custom on-chain program doesn't replace off-chain computation. It complements it. The division of labor is clear:
Off-chain code is responsible for identifying opportunities. It monitors pool states, calculates potential profits, selects the best cycles, and constructs the transaction parameters. This is the intelligence layer — fast, flexible, able to process enormous amounts of data, free to use whatever algorithms and heuristics are appropriate.
The on-chain program is responsible for execution and verification. It performs the actual swaps via CPI and verifies that the result is profitable. This is the safety layer — atomic, deterministic, incapable of partial failure, and armed with the profit check that ensures no unprofitable cycle ever finalizes.
The off-chain code says "I think this cycle will be profitable." The on-chain program says "let's find out, and if it's not, we'll pretend it never happened."
This separation is not just a software architecture pattern. It reflects a fundamental constraint of blockchain execution. On-chain computation is expensive, sequential, and limited by the transaction's compute budget. Complex analysis — scanning hundreds of pools, running optimization algorithms, evaluating thousands of potential cycles — must happen off-chain where compute is cheap and fast. But the actual execution — the part where real tokens move between real accounts — must happen on-chain where atomicity is guaranteed.
The custom program is the bridge. It takes the off-chain decision and executes it with on-chain guarantees. If the off-chain analysis was right, profit. If the off-chain analysis was wrong, revert. Either way, the on-chain program ensures that the outcome is binary: profit or nothing. Never loss.
The Non-Negotiable Foundation
Every arbitrage bot needs speed. Every arbitrage bot needs good pricing models. Every arbitrage bot needs reliable data feeds and smart cycle selection and efficient transaction construction. These are all important, and they are all areas where marginal improvements translate to marginal gains.
But the custom on-chain program is not a marginal improvement. It is a binary prerequisite. Without it, every failed cycle is a potential loss. With it, every failed cycle is a non-event. Without it, the bot must be conservative — only attempting high-confidence opportunities. With it, the bot can be aggressive — attempting everything, knowing that failure is free.
The on-chain program is the escrow agent that guarantees the closing. It is the simultaneous settlement that prevents half-executed deals. It is the contingency clause that kills bad deals before they finalize. It is the foundation that everything else is built on — not because it's the most complex component, but because without it, nothing else matters. The fastest pricing model in the world is worthless if a failed swap leaves you holding a worthless token. The most sophisticated cycle detection is irrelevant if partial execution turns opportunity into liability.
Atomic execution isn't a feature. It's the reason this entire approach works.
Disclaimer
This article is for informational and educational purposes only and does not constitute financial, investment, legal, or professional advice. Content is produced independently and supported by advertising revenue. While we strive for accuracy, this article may contain unintentional errors or outdated information. Readers should independently verify all facts and data before making decisions. Company names and trademarks are referenced for analysis purposes under fair use principles. Always consult qualified professionals before making financial or legal decisions.