Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

⚡ GidiEngine — Smart Transaction Stack

Live Dashboard: https://gidi-engine.vercel.app
Health API: https://gidi-engine.vercel.app/api/health
Network: Solana Mainnet
Stack: Railway (Worker) + Vercel (API) + 4-Provider AI Brain


Required Questions

Q1: What does the delta between processed_at and confirmed_at tell you about network health at the time of submission?

The delta between processed_at and confirmed_at reflects how quickly the cluster reached supermajority stake agreement on the block containing your transaction.

When the network is healthy, validators vote rapidly and this delta is typically 400–800ms — roughly 2 slots at 400ms per slot. When this delta stretches to 2000ms+, it signals one or more of the following conditions:

  • Validator vote latency — a meaningful portion of stake-weighted validators are slow to propagate votes, possibly due to geographic distance or hardware load
  • Leader performance degradation — the block producer is slow to propagate shreds, delaying other validators from seeing and voting on the block
  • Network congestion — high transaction volume is causing validators to deprioritize vote transactions relative to user transactions
  • Fork competition — the cluster may be resolving a fork, requiring more rounds of voting before supermajority is achieved

In GidiEngine, we track this delta in LifecycleLogger.js as the delta field on the CONFIRMED event, calculated as CONFIRMED.ts - PROCESSED.ts. Our StrategyProfiles.js uses the rolling average of this delta across the last 10 bundles to suggest a profile: a delta consistently above 3000ms triggers aggressive mode with higher tips to ensure the transaction stays competitive during a congested window.


Q2: Why should you never use finalized commitment when fetching a blockhash for a time-sensitive transaction?

A blockhash fetched at finalized commitment is approximately 32 blocks behind the current chain tip — roughly 12–13 seconds of lag on mainnet. Solana blockhashes expire after 150 blocks (~60 seconds), but the critical issue is not expiry — it is staleness.

When you submit a transaction with a finalized-commitment blockhash, you are submitting with a blockhash that the current leader has already moved well past. The TPU (Transaction Processing Unit) at the leader will reject or deprioritize transactions whose blockhashes are far behind the current slot, because:

  1. The leader must verify the blockhash exists in the recent blockhash queue
  2. A very old blockhash may already be near expiry by the time the transaction propagates through the network
  3. In high-congestion conditions, the leader processes transactions in priority order — a stale-looking transaction signals a low-quality submission

The correct commitment for time-sensitive transactions is confirmed, which gives you a blockhash that is 2 slots behind tip — fresh enough that it will not expire before your transaction lands, yet stable enough that it has been voted on by a supermajority of stake.

GidiEngine uses confirmed commitment explicitly in freshBlockhash() inside Executor.js:

await conn.getLatestBlockhashAndContext("confirmed")

On blockhash expiry (BLOCKHASH_EXPIRED classification), Retryer.js immediately calls freshBlockhash() again at confirmed commitment before resubmitting — never reusing the expired one.


Q3: What happens to your bundle if the Jito leader skips their slot?

When a Jito-connected validator is scheduled as leader but skips their slot (due to being offline, timing issues, or a fork), your bundle is silently dropped. Unlike a regular transaction which can be forwarded to the next leader by the TPU, Jito bundles are exclusively processed by the Jito block engine running on the scheduled validator. There is no forwarding mechanism.

The practical consequences:

  1. Bundle is not executed — none of the transactions in the bundle land
  2. Tip is not charged — since the bundle never executed, the tip transfer does not happen
  3. No error is returned — the bundle submission appeared successful, but confirmation polling will time out
  4. Blockhash does not expire — the transaction is still technically valid, but it was never seen by any leader

GidiEngine handles this in two layers:

Detection: pollBundleRest() and confirmViaStream() in Executor.js run in parallel. If neither returns a confirmed or finalized status within the polling window (45 seconds), the bundle is classified as TIMEOUT with failureType: BUNDLE_FAILED.

Recovery: Retryer.js passes the BUNDLE_FAILED classification to getAgentDecision(). The AI agent reasons about the failure — recognising a leader skip pattern (especially if LeaderSchedule.js logged that the expected Jito leader was in the window) — and typically returns a conservative or aggressive profile decision to resubmit, waiting for the next confirmed Jito leader slot before retrying.

Prevention: LeaderSchedule.js caches the upcoming leader schedule and only allows Worker.js to fire within LEADER_WINDOW slots of a known Jito validator. This does not eliminate skips but significantly reduces exposure.


Architecture

See the full architecture document: GidiEngine Architecture — Notion

Solana Cluster
      │ slots (400ms)
      ▼
┌─────────────────────────┐  Railway — persistent worker
│  Observer.js            │
│  Yellowstone gRPC ──────┼── primary
│  Helius WebSocket ──────┼── auto-fallback
└──────────┬──────────────┘
           │ bus.emit("slot")
           ▼
┌─────────────────────────┐
│  Worker.js              │
│  CircuitBreaker ────────┼── trips after 3 failures
│  LeaderSchedule ────────┼── only near Jito validators
└──────────┬──────────────┘
           ▼
┌─────────────────────────┐  Vercel — serverless
│  Agent.js               │
│  AIRouter ──────────────┼── 🟣Anthropic→🟢OpenAI→🔵Gemini→🟡Groq
│  StrategyProfiles ──────┼── conservative/aggressive/sniper
└──────────┬──────────────┘
           │ {shouldExecute, profile, params}
           ▼
┌─────────────────────────┐
│  Retryer.js             │
│  Agent-driven retries   │
│  Blockhash refresh ─────┼── on BLOCKHASH_EXPIRED
│  Fault injection ───────┼── FAULT_INJECT=true
└──────────┬──────────────┘
           ▼
┌─────────────────────────┐
│  Executor.js            │
│  fetchJitoTip() ────────┼── live p50 + multiplier
│  freshBlockhash() ──────┼── confirmed commitment
│  submitBundle() ────────┼── Jito SDK
│  confirmViaStream() ────┼── slot subscription (primary)
│  pollBundleRest() ──────┼── REST fallback
└──────────┬──────────────┘
           │
    LifecycleLogger.js
    slot + tip + commitment + failure class
           │
    AlertHook.js → Slack + Discord

Failure Classification

Type Cause Recovery
BLOCKHASH_EXPIRED Blockhash too old Refresh + resubmit
FEE_TOO_LOW Priority fee underpriced Raise fee, retry
COMPUTE_EXCEEDED CU budget exceeded Agent decides skip
BUNDLE_FAILED Jito rejection/leader skip Escalate tip, retry
LEADER_SKIP Jito validator skipped slot Wait for next leader
SIMULATION_FAILED Instruction error Do not retry

Lifecycle Log Format

{
  "bundleId": "bundle_1718640000000",
  "createdAt": 1718640000000,
  "submittedSlot": 312847291,
  "tipLamports": 6250,
  "consensusMs": 4231,
  "failureType": null,
  "aiProvider": "Anthropic",
  "profile": "aggressive",
  "events": [
    { "stage": "SUBMITTED",  "commitment": "pending",   "ts": 1718640000000, "delta": 0,    "slot": 312847291 },
    { "stage": "PROCESSED",  "commitment": "processed", "ts": 1718640000800, "delta": 800,  "slot": 312847293 },
    { "stage": "CONFIRMED",  "commitment": "confirmed", "ts": 1718640003100, "delta": 2300, "slot": 312847298 },
    { "stage": "FINALIZED",  "commitment": "finalized", "ts": 1718640004231, "delta": 1131, "slot": 312847301 }
  ]
}

Deploy (Mobile — No Laptop Needed)

1. Push to GitHub

Create a repo, push these files. Edit anytime from GitHub Mobile.

2. Deploy Vercel (API layer)

  • vercel.com → New Project → import your repo
  • Add all env vars from .env.example
  • Vercel auto-deploys on every push to main

3. Deploy Railway (Worker)

  • railway.app → New Project → deploy from GitHub
  • Start command: node railway/Worker.js
  • Add all env vars, enable auto-deploy on push

4. Verify

https://gidi-engine.vercel.app/api/health

Strategy Profiles

Profile Fee Slippage Retries Tip Trigger
🐢 conservative 2,000 30bps 2 ×1.0 Stable, <3000ms consensus
🦅 aggressive 15,000 100bps 3 ×1.3 Failures, >5000ms consensus
🎯 sniper 50,000 10bps 1 ×1.75 Zero failures, <1500ms

AI Provider Chain

🟣 Anthropic (claude-sonnet-4-6)   — Primary
🟢 OpenAI    (gpt-4o-mini)         — Fallback 1
🔵 Gemini    (gemini-1.5-flash)    — Fallback 2
🟡 Groq      (llama-3.1-8b)       — Fallback 3 (free tier)

Each provider has an 8s timeout. Slack/Discord alert fires on every fallback.


Mobile Emoji Legend

Emoji Meaning
🟢 / 🟡 gRPC active / Helius fallback
🎯 Jito leader detected
🧠 Agent decision made
🔁 Retry attempt
🔑 Blockhash refreshed
🧪 Fault injection active
📤 ⚙️ ✅ 🏁 Lifecycle stages
🚨 Circuit breaker tripped
❌ ⏱️ Failure / timeout

Built with 🇳🇬 from Lagos — Gidi to the world.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages