Trading & Bots
Python is a common choice for Solana trading automation: polling pools, building swap transactions, signing with hot wallets, and submitting with priority fees. Reliable bots simulate every send, refresh blockhashes, and handle Agave 4.1.1 fee markets explicitly.
Recipe
Quick-reference recipe card - copy-paste ready.
import asyncio
from solana.rpc.async_api import AsyncClient
async def bot_loop(rpc: str):
async with AsyncClient(rpc) as client:
while True:
slot = (await client.get_slot()).value
# 1. read signals 2. build ix 3. simulate 4. send 5. confirm
await asyncio.sleep(0.4) # respect rate limitsWhen to reach for this:
- Market-making or arbitrage between on-chain liquidity venues.
- Scheduled rebalancing and limit-order style automation.
- Monitoring mempools/RPC for trigger conditions (with provider support).
- Internal ops bots (treasury sweeps, reward distribution).
Working Example
import asyncio
import logging
from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.hash import Hash
from solders.message import Message
from solders.transaction import VersionedTransaction
from solana.rpc.async_api import AsyncClient
from solana.rpc.types import TxOpts
log = logging.getLogger("mm-bot")
RPC = "https://api.mainnet-beta.solana.com" # use private RPC in production
class SimpleSender:
def __init__(self, client: AsyncClient, payer: Keypair):
self.client = client
self.payer = payer
async def send(self, instructions, micro_lamports: int):
bh = await self.client.get_latest_blockhash()
blockhash = Hash.from_string(str(bh.value.blockhash))
ixs = [
set_compute_unit_limit(200_000),
set_compute_unit_price(micro_lamports),
*instructions,
]
msg = Message.new_with_blockhash(ixs, self.payer.pubkey(), blockhash)
tx = VersionedTransaction(msg, [self.payer])
sim = await self.client.simulate_transaction(tx)
if sim.value.err:
log.warning("sim_fail err=%s logs=%s", sim.value.err, sim.value.logs)
return None
resp = await self.client.send_transaction(
tx, opts=TxOpts(skip_preflight=True, preflight_commitment="processed")
)
await self.client.confirm_transaction(resp.value, commitment="confirmed")
return resp.value
async def main():
payer = Keypair() # load securely
async with AsyncClient(RPC) as client:
sender = SimpleSender(client, payer)
# instructions = [swap_ix_from_jupiter_api(...)]
# await sender.send(instructions, micro_lamports=10_000)
asyncio.run(main())What this demonstrates:
- Sender class isolates blockhash, budget, simulate, send, confirm.
- Priority fee via
set_compute_unit_priceis explicit per attempt. - Logging captures simulation logs for post-mortems.
- Async loop friendly structure for multiple strategies.
Deep Dive
How It Works
- Bots are read-heavy (quotes, balances) and write-burst (submit txs).
- Jupiter and other aggregators return serialized instructions or transactions to sign.
- Priority fees compete for block space; tune micro-lamports per congestion.
- Jito bundles (optional) route through block engines for MEV-aware submission.
Bot Architecture Layers
| Layer | Responsibility | Tools |
|---|---|---|
| Signal | Prices, spreads, inventory | RPC, websockets, venue APIs |
| Build | Instructions, ALTs | solders, Jupiter API |
| Risk | Size caps, kill switch | App logic |
| Submit | Fees, retries, confirm | solana-py, Jito RPC |
| Observe | PnL, failures | logging, metrics |
Python Notes
# Never block the event loop on CPU work
await asyncio.to_thread(heavy_analytics, frame)
# Jittered backoff on 429 / blockhash errors
import random
await asyncio.sleep(0.2 + random.random() * 0.3)Gotchas
- Public RPC for mainnet bots - rate limits and stale slots. Fix: dedicated provider with SLA.
- No kill switch - runaway loop drains wallet. Fix: max daily spend and inventory caps.
- Ignoring landing failures - txs expire silently. Fix: track confirmation timeouts and resubmit.
- Same blockhash on retry - guaranteed failure after expiry. Fix: rebuild each attempt.
- Storing keys in process memory - memory dumps leak secrets. Fix: minimal balance hot wallets, rotate keys.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Rust/Go bot core | Ultra-low latency | Team velocity in Python |
@solana/kit + serverless | Infrequent triggers | Tight loop market making |
| Manual CLI ops | Rare treasury moves | Sub-second reactions |
| Fully custodial CEX API | Fiat ramps | On-chain composability required |
FAQs
Is Python fast enough?
Often yes for human-scale and DeFi block times; profile hot paths and move only those to Rust if needed.
Jupiter integration?
Call Jupiter quote/swap HTTP API, deserialize returned instructions, sign locally.
How to estimate priority fees?
Use getRecentPrioritizationFees RPC or provider helpers; see priority fees section.
Devnet testing?
Validate logic on devnet but fee market behavior differs from mainnet.
Multiple wallets?
One Keypair per strategy limits blast radius.
Clock skew?
Use RPC slot/time, not laptop clock, for scheduling.
Compliance?
Log trades with signatures for audit; respect jurisdictional rules.
Websocket vs poll?
Websockets for account triggers; polling simpler for prototypes.
Surfpool for sim?
Surfpool 0.12.0 helps replay mainnet state locally before risking funds.
AnchorPy in bots?
Use when calling your own Anchor programs; aggregators return raw ix bytes.
Related
- Building Transactions in Python - tx assembly
- Swaps with Jupiter - swap integration
- Setting Priority Fees - fee tuning
- Python for Solana Best Practices - production rules
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.