Transaction Lifecycle Diagram
The following diagram shows the complete lifecycle of a transaction from creation to confirmation, including error handling and competition scenarios:Transaction Creation
Cross Detection
Transactions begin when the bot detects a profitable opportunity: Filler Bot:- Auction orders crossing resting liquidity
- Swift orders matching resting orders
- Resting limit orders that can be crossed against each other
- Users with
total_collateral < margin_requirement - Perp positions that can be filled against DLOB makers
- Spot borrows that can be repaid via Jupiter swaps
Build Transaction
Once a cross is detected, the bot builds a transaction: Filler - Auction Fill:Calculate Priority Fee
The bot uses dynamic priority fees based on recent network activity:- Monitors recent transactions for relevant program accounts
- Tracks priority fees paid by successful transactions
- Calculates percentile-based fee recommendations:
- 50th percentile: Standard fills
- 60th percentile: Swift fills (more time-sensitive)
- 60th percentile: Liquidations
Filler adds
slot % 2 entropy to priority fee to ensure consecutive resubmissions have unique transaction hashes.- Network congestion varies significantly
- Fixed fees either waste money or miss opportunities
- Percentile-based approach balances cost and inclusion rate
Set CU Limit
Compute Unit (CU) limits prevent excessive fees:- Auction fills: 200k-300k CU (configured via
--fill-cu-limit) - Swift fills: 400k CU (configured via
--swift-cu-limit) - Liquidations: 500k-600k CU
CU limits are set conservatively to handle worst-case scenarios (many account lookups, complex calculations).
Transaction Sending
TxWorker Architecture
TheTxWorker runs in a dedicated thread to avoid blocking the main event loop:
- Transaction signing and RPC calls can block
- Keeps main loop responsive to new events
- Allows parallel transaction submission
Sign & Send to RPC
The worker signs and sends the transaction:skip_preflight: true- Skip simulation to reduce latencymax_retries: Some(0)- No automatic retriescommitment: Confirmed- Wait for ~13 block confirmations
Skipping preflight simulation reduces latency by ~100-200ms but means errors are only caught on-chain.
Add to PendingTxs
The transaction is added to a circular buffer for tracking:signature: Transaction signatureintent: What the transaction attempted (fill, liquidation, etc.)cu_limit: Compute unit limit usedts: Timestamp for latency calculation
- Fixed size (typically 1024 entries)
- Overwrites oldest entries when full
- Allows fast signature lookup for confirmations
Increment Metrics
Send metrics are immediately recorded:Transaction Confirmation
gRPC Transaction Update
Drift’s gRPC service streams transaction updates in real-time:signature: Transaction signatureslot: Slot where transaction landederror: Optional error if transaction failed
gRPC transaction updates provide faster confirmation than polling RPC, typically arriving within 1-2 slots.
TxWorker.confirm_tx
The confirm handler processes the update:- Lookup in PendingTxs: Find metadata by signature
- Check if known: If not found, ignore (may have been overwritten)
- Fetch full transaction: Get complete details from RPC
- Parse logs: Extract events from transaction logs
- Compare to intent: Verify expected fills occurred
- Update metrics: Record success, fill rate, latency
- Remove from pending: Clear from buffer
Get Transaction Details
The full transaction is fetched from RPC:- gRPC update doesn’t include logs
- Need logs to parse fill events
- Need to verify transaction actually did what we expected
Parse Transaction Logs
Transaction logs contain events emitted by the Drift program: Fill Events:OrderActionRecord: Order filled, triggered, or expiredFillRecord: Details of a specific fill (price, amount, fees)
LiquidationRecord: Liquidation executed
- Iterate through transaction logs
- Look for Drift event discriminators
- Deserialize event data using Anchor schemas
- Extract relevant fields (fill amounts, fees, etc.)
Update Metrics
Confirmation metrics capture the complete transaction outcome: Success Metrics:Success Path
When a transaction succeeds, the bot performs detailed analysis:Parse Fill Events
Extract allFillRecord events from logs:
Compare Expected vs Actual Fills
The bot compares actual fills to the original intent:- Detect competition (other bots filled orders first)
- Identify inefficiencies (wrong maker selection)
- Track fill rate for strategy optimization
Record Performance Metrics
Detailed metrics enable strategy refinement: Latency Metrics:- Time from cross detection to transaction sent
- Time from sent to confirmed
- Total end-to-end latency
- Percentage of expected fills achieved
- Partial fill frequency
- Competition rate (orders filled by others)
- Fees paid vs. fees earned
- Expected profit vs. actual profit
- Priority fee efficiency (inclusion rate)
Error Paths
Send Error
Errors during transaction submission: Common causes:- Blockhash expired (transaction took too long to build)
- Insufficient SOL for transaction fees
- RPC connection failure
- Invalid transaction (malformed instruction)
- Log error with full context
- Increment error metrics
- Do NOT retry automatically (may double-fill)
- Alert if error rate exceeds threshold
Confirmation Error
Errors when fetching transaction details: Common causes:- Transaction not found (RPC node doesn’t have it)
- RPC timeout
- Transaction dropped from ledger (too old)
- Retry fetch with exponential backoff
- Check multiple RPC nodes if available
- After max retries, assume transaction failed
Transaction Failed
Transaction confirmed but execution failed: Common error types: Insufficient Funds:- Bot lacks SOL for transaction fees
- Bot lacks USDC for margin requirements
- Solution: Top up bot wallet
- Transaction used more CU than limit
- Complex transaction with many accounts
- Solution: Increase CU limit for this intent type
- Another bot filled the order first
- Normal competition scenario
- Solution: Optimize latency or priority fee
- Oracle price too stale
- Oracle account not updated
- Solution: Check oracle subscription
- User can’t fulfill the order (liquidator)
- User’s collateral changed
- Solution: Recheck user state before liquidation
Competition Scenarios
Keepered markets are highly competitive. Common scenarios:AMM Fill Beats Us
The protocol’s vAMM filled the order before our transaction landed: Why it happens:- vAMM is always available as fallback liquidity
- vAMM fills happen atomically with other instructions
- Other bots may trigger vAMM fills
- Parse logs for vAMM fill events
- Compare maker addresses (vAMM has special address)
- This is expected and acceptable
- Focus on orders where we have better pricing than vAMM
Higher Priority Fee
Another bot paid a higher priority fee and got included first: Why it happens:- Validators prioritize by fee per CU
- Multiple bots compete for same fill
- Network congestion increases competition
- Check slot of our transaction vs. when order was filled
- Compare our priority fee to successful competitors
- Increase percentile used (60th → 75th)
- Add competitive bonus for high-value fills
- Balance cost vs. win rate
Order Already Filled
The order was completely filled before our transaction: Why it happens:- Extremely competitive order (good price)
- Our cross detection latency too high
- Another bot has lower latency
- Transaction fails with “OrderDoesNotExist” error
- Or succeeds with 0 fills
- Optimize event processing latency
- Consider if order is worth competing for
- May need better infrastructure (closer RPC nodes)
Partial Fill
We expected multiple fills but only got some: Why it happens:- Mixed scenario: we filled some makers, others were filled by competitors
- Our transaction included multiple maker matches
- Makers at worse prices were already filled
- Prioritize best makers first in transaction
- Consider whether multiple makers worth the gas
- Track which maker positions are most competitive
Competition is healthy and expected in keeper markets. The goal is not to win every fill, but to maintain profitability while providing valuable liquidity matching services.
Best Practices
- Monitor metrics continuously: Set up alerts for high error rates
- Log full context on errors: Include intent, signature, slot for debugging
- Don’t retry blindly: Retrying fills can lead to double-fills
- Balance priority fees: Too low = miss opportunities, too high = unprofitable
- Track competition: Learn from losses to optimize strategy
- Test in dry mode first: Validate logic before risking capital