"Cross-Chain Arbitrage Architecture: Steem → SBD → Upbit Pipeline"
The Problem
SBD (Steem Blockchain Dollar) is a stablecoin on the Steem blockchain. To increase market supply and maintain price stability, we designed a systematic arbitrage process:
Steem Blockchain → Withdraw SBD → Upbit Exchange → Sell SBD → Buy STEEM → Send Back to SteemThis simple loop requires serious distributed systems infrastructure.
Architecture
Event-Driven Pipeline
The entire system is built on an event-driven architecture:
type PipelineEvent struct {
Type string `json:"type"`
Payload interface{} `json:"payload"`
TraceID string `json:"trace_id"`
Timestamp time.Time `json:"timestamp"`
}Each stage produces an event consumed by the next:
withdrawal:requested— SBD withdrawal request from Steem blockchainwithdrawal:confirmed— Withdrawal confirmed on-chainexchange:order_placed— Sell order placed on Upbitexchange:order_filled— Order filled, STEEM purchasedtransfer:initiated— STEEM sent back to Steem blockchain
State Machine Design
Each arbitrage cycle is managed by a state machine:
type CycleState int
const (
StateIdle CycleState = iota
StateWithdrawing
StateWaitingConfirmation
StateSelling
StateBuying
StateTransferring
StateCompleted
StateFailed
)Benefits:
- Automatic rollback or retry on any stage failure
- Hundreds of cycles run independently in parallel
- System resumes from saved state after crash
Concurrency Model
Node.js event loop manages hundreds of concurrent arbitrage cycles:
class ArbitrageEngine {
private cycles: Map<string, CycleStateMachine> = new Map();
private queue: Queue<ArbitrageTask>;
async processCycle(task: ArbitrageTask) {
const machine = new CycleStateMachine(task);
this.cycles.set(task.id, machine);
while (!machine.isTerminal()) {
const action = machine.currentAction();
try {
const result = await this.executeAction(action);
machine.transition(result);
} catch (err) {
machine.handleError(err);
await this.backoff(machine.retryCount);
}
}
}
}Resilience Patterns
Circuit Breaker: Opens when Upbit API hits rate limits or downtime:
const upbitBreaker = new CircuitBreaker({
threshold: 5,
resetTimeout: 30000,
onOpen: () => switchToFallbackExchange(),
});Retry with Exponential Backoff: Blockchain transactions can be delayed:
async function waitForConfirmation(txId: string, maxRetries = 10) {
for (let i = 0; i < maxRetries; i++) {
const receipt = await steemNode.getTransaction(txId);
if (receipt.confirmed) return receipt;
await sleep(Math.min(1000 * Math.pow(2, i), 30000));
}
throw new Error("Transaction confirmation timeout");
}Monitoring & Observability
Each pipeline step collects:
- Metrics: Prometheus — latency, success/failure rates
- Tracing: OpenTelemetry — full trace per cycle
- Logging: Structured JSON, each event tagged with trace_id
arbitrage_cycles_total{status="completed"} 12847
arbitrage_cycle_duration_seconds{quantile="0.99"} 45.2
arbitrage_profit_total_usd 32450Key Takeaways
- Blockchain ops are async and unreliable — Always verify, always timeout
- Exchange APIs are rate-limited — Circuit breakers and queuing are mandatory
- State machines are non-negotiable — Distributed systems require explicit state management
- Monitoring is everything — Without observability, debugging is impossible