FortiBlox LogoFortiBlox Docs
AXIO Block Engine

SDK Reference

TypeScript and Rust SDK documentation for interacting with the AXIO block engine and staking protocol.

SDK Reference

AXIO provides two TypeScript SDKs: the Searcher SDK for bundle submission and MEV operations, and the Staking SDK for interacting with the rewards API and staking protocol.


Searcher SDK (@axio/searcher-sdk)

The Searcher SDK wraps the AXIO gRPC API for high-performance bundle submission, encrypted bundles, streaming, and reputation management.

Installation

npm install @axio/searcher-sdk

AxioClient

Create a client instance with your API key and engine endpoint:

import { AxioClient } from "@axio/searcher-sdk";

const client = new AxioClient({
  apiKey: process.env.AXIO_API_KEY!,
  endpoint: "grpc://engine.axio.fortiblox.com:7440",
});
ParameterTypeDescription
apiKeystringYour searcher API key
endpointstringgRPC endpoint URL

submitBundle

Submit a bundle of transactions for inclusion in the next available slot.

const result = await client.submitBundle({
  transactions: [serializedTx1, serializedTx2],
  tip: 500_000, // lamports
  mevType: "arbitrage",
  targetSlot: 149_900_001, // optional — defaults to next available
});

console.log(result.bundleId); // "d500d0bf-a73f-4a1b-a0ae-824d14e3ae45"
console.log(result.status);   // "accepted"

Parameters:

FieldTypeRequiredDescription
transactionsBuffer[]YesSerialized transactions (base64-encoded)
tipnumberYesTip amount in lamports
mevTypestringYes"sandwich" | "arbitrage" | "liquidation" | "jit" | "unknown"
targetSlotnumberNoTarget slot for inclusion

Returns: { bundleId: string; status: "accepted" | "rejected" }


submitEncryptedBundle

Submit an encrypted bundle using X25519 key exchange with ChaCha20-Poly1305 symmetric encryption. The bundle payload is encrypted client-side and only decrypted inside the engine's trusted execution context.

const result = await client.submitEncryptedBundle({
  transactions: [serializedTx1, serializedTx2],
  tip: 750_000,
  mevType: "sandwich",
  targetSlot: 149_900_050,
});

console.log(result.bundleId); // "a8f1c902-..."
console.log(result.status);   // "accepted"

The method accepts the same parameters as submitBundle. Encryption is handled transparently by the SDK using the engine's public key (fetched on first call and cached).

Returns: { bundleId: string; status: "accepted" | "rejected" }


createBundleStream

Open a bidirectional gRPC stream for high-frequency bundle submission. The stream allows you to send bundles continuously and receive confirmations as they are processed.

const stream = client.createBundleStream();

// Send bundles
stream.send({
  transactions: [serializedTx],
  tip: 600_000,
  mevType: "arbitrage",
});

// Listen for confirmations
stream.on("data", (confirmation) => {
  console.log(confirmation.bundleId);  // "d500d0bf-..."
  console.log(confirmation.status);    // "accepted" | "included" | "dropped" | "expired"
  console.log(confirmation.slot);      // 149900001
  console.log(confirmation.signatures); // ["5K8rE..."]
});

stream.on("error", (err) => {
  console.error("Stream error:", err.message);
});

stream.on("end", () => {
  console.log("Stream closed");
});

// Close when done
stream.end();

Stream events:

EventPayloadDescription
dataBundleConfirmationBundle status update
errorErrorStream-level error
endStream closed by server

getBundleStatus

Check the current status of a submitted bundle.

const status = await client.getBundleStatus("d500d0bf-a73f-4a1b-a0ae-824d14e3ae45");

console.log(status.bundleId);   // "d500d0bf-..."
console.log(status.status);     // "included"
console.log(status.slot);       // 149900001
console.log(status.signatures); // ["5K8rE...", "3mPq1..."]

Parameters:

FieldTypeDescription
bundleIdstringThe bundle ID returned from submission

Returns: { bundleId: string; status: string; slot?: number; signatures: string[] }


getTipFloor

Fetch the current dynamic tip floor. Bundles with tips below this value are rejected.

const tipFloor = await client.getTipFloor();

console.log(tipFloor); // 250000 (lamports)

Returns: number — current tip floor in lamports.


getReputation

Retrieve your searcher reputation score and metrics.

const rep = await client.getReputation();

console.log(rep.score);       // 87.4
console.log(rep.tier);        // "gold"
console.log(rep.landingRate); // 0.947
console.log(rep.simSuccess);  // 0.982

Returns:

FieldTypeDescription
scorenumberReputation score (0-100)
tierstring"bronze" | "silver" | "gold" | "platinum"
landingRatenumberFraction of bundles that landed on-chain
simSuccessnumberFraction of bundles that passed simulation

Staking SDK (@axio/staking-sdk)

The Staking SDK provides access to the AXIO rewards API, APY data, exchange rates, MEV distribution, and yield estimation.

Installation

npm install @axio/staking-sdk

AxioStakingClient

import { AxioStakingClient } from "@axio/staking-sdk";

const staking = new AxioStakingClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL || "http://localhost:7435",
  rpcUrl: process.env.NEXT_PUBLIC_RPC_URL!,
});
ParameterTypeDescription
baseUrlstringRewards API base URL
rpcUrlstringX1/Tachyon RPC endpoint

getOverview

Fetch aggregate rewards metrics for a given time period.

const overview = await staking.getOverview("24h");

console.log(overview.total_tips_earned); // 12400000000 (lamports)
console.log(overview.bundles_landed);    // 1842
console.log(overview.avg_tip);           // 6731
console.log(overview.landing_rate);      // 94.7

Parameters:

FieldTypeDescription
periodstring"1h" | "24h" | "7d" | "30d"

Returns: RewardsOverview


getAPY

Retrieve the current APY breakdown including base staking and MEV yield.

const apy = await staking.getAPY();

console.log(apy.current_apy); // 12.4
console.log(apy.base_apy);    // 7.2
console.log(apy.mev_apy);     // 5.2

Returns: APYData


getExchangeRate

Get the current aXNT-to-SOL exchange rate and recent changes.

const rate = await staking.getExchangeRate();

console.log(rate.rate);             // 1.096
console.log(rate.rate_change_24h);  // 0.12

Returns: ExchangeRateData


getReserveData

Fetch cumulative reserve injection data.

const reserve = await staking.getReserveData();

console.log(reserve.cumulative_injected_sol); // 847.3
console.log(reserve.last_injection);          // "2026-04-20T18:30:00Z"

Returns: ReserveData


getMEVDistribution

Get the MEV revenue breakdown by type for a given period.

const distribution = await staking.getMEVDistribution("24h");

for (const entry of distribution) {
  console.log(entry.mev_type); // "Sandwich", "Arbitrage", etc.
  console.log(entry.bundles);  // 312
  console.log(entry.tips);     // 5800000000
}

Parameters:

FieldTypeDescription
periodstring"1h" | "24h" | "7d" | "30d"

Returns: MEVDistEntry[]


estimateYieldWithInjection

Estimate projected yield for a given stake amount, factoring in the current APY and reserve injection schedule.

const estimate = await staking.estimateYieldWithInjection(100_000_000_000); // 100 SOL in lamports

console.log(estimate.daily);       // 0.034 SOL
console.log(estimate.monthly);     // 1.033 SOL
console.log(estimate.yearly);      // 12.4 SOL
console.log(estimate.current_apy); // 12.4

Parameters:

FieldTypeDescription
stakeLamportsnumberAmount to stake in lamports

Returns: YieldEstimate


getValidators

Retrieve the list of validators in the AXIO network with their MEV performance scores.

const validators = await staking.getValidators();

for (const v of validators) {
  console.log(v.address);    // "7Np41oe..."
  console.log(v.name);       // "FortiBlox #1"
  console.log(v.stake);      // 245000
  console.log(v.apy);        // 13.2
  console.log(v.commission);  // 5
  console.log(v.mevScore);   // 98
}

Returns: Validator[]


TypeScript Types

All types are exported from their respective SDK packages.

RewardsOverview

interface RewardsOverview {
  total_tips_earned: number;  // total tips in lamports
  bundles_landed: number;     // number of bundles included on-chain
  avg_tip: number;            // average tip per bundle in lamports
  landing_rate: number;       // percentage of bundles that landed (0-100)
}

APYData

interface APYData {
  current_apy: number;  // current blended APY (base + MEV)
  base_apy: number;     // base staking APY from inflation rewards
  mev_apy: number;      // additional APY from MEV revenue sharing
}

ExchangeRateData

interface ExchangeRateData {
  rate: number;             // current aXNT per SOL exchange rate
  rate_change_24h: number;  // percentage change over 24 hours
}

ReserveData

interface ReserveData {
  cumulative_injected_sol: number;  // total SOL injected into the reserve
  last_injection: string;           // ISO 8601 timestamp of last injection
}

MEVDistEntry

interface MEVDistEntry {
  mev_type: string;  // "Sandwich" | "Arbitrage" | "Liquidation" | "Other"
  bundles: number;   // number of bundles for this type
  tips: number;      // total tips in lamports for this type
}

YieldEstimate

interface YieldEstimate {
  daily: number;        // estimated daily yield in SOL
  monthly: number;      // estimated monthly yield in SOL
  yearly: number;       // estimated yearly yield in SOL
  current_apy: number;  // APY used for the estimate
}

Configuration

Both SDKs are configured via environment variables and constructor options.

Environment Variables

VariableDescriptionDefault
NEXT_PUBLIC_API_URLRewards API base URLhttp://localhost:7435
NEXT_PUBLIC_RPC_URLX1/Tachyon RPC endpoint
AXIO_API_KEYSearcher API key for gRPC authentication

Example .env

NEXT_PUBLIC_API_URL=https://axio.fortiblox.com/api/v1
NEXT_PUBLIC_RPC_URL=https://rpc.x1.fortiblox.com
AXIO_API_KEY=sk_live_abc123def456

Error Handling

Both SDKs throw typed errors that should be caught and handled appropriately.

Searcher SDK Errors

import { AxioClient, AxioError } from "@axio/searcher-sdk";

const client = new AxioClient({
  apiKey: process.env.AXIO_API_KEY!,
  endpoint: "grpc://engine.axio.fortiblox.com:7440",
});

try {
  const result = await client.submitBundle({
    transactions: [serializedTx],
    tip: 500_000,
    mevType: "arbitrage",
  });
  console.log("Bundle accepted:", result.bundleId);
} catch (err) {
  if (err instanceof AxioError) {
    switch (err.code) {
      case "TIP_BELOW_FLOOR":
        console.error("Tip too low. Current floor:", err.details.tipFloor);
        break;
      case "SIMULATION_FAILED":
        console.error("Simulation failed:", err.details.logs);
        break;
      case "RATE_LIMITED":
        console.error("Rate limited. Retry after:", err.details.retryAfterMs, "ms");
        break;
      case "AUTH_FAILED":
        console.error("Invalid API key");
        break;
      default:
        console.error("AXIO error:", err.code, err.message);
    }
  } else {
    console.error("Unexpected error:", err);
  }
}

Staking SDK Errors

import { AxioStakingClient } from "@axio/staking-sdk";

const staking = new AxioStakingClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL || "http://localhost:7435",
  rpcUrl: process.env.NEXT_PUBLIC_RPC_URL!,
});

try {
  const apy = await staking.getAPY();
  console.log("Current APY:", apy.current_apy);
} catch (err) {
  if (err instanceof Error) {
    if (err.message.includes("ECONNREFUSED")) {
      console.error("Rewards API is not reachable. Check NEXT_PUBLIC_API_URL.");
    } else if (err.message.includes("404")) {
      console.error("Endpoint not found. Verify API version.");
    } else {
      console.error("Staking SDK error:", err.message);
    }
  }
}

Stream Error Handling

const stream = client.createBundleStream();

stream.on("error", (err) => {
  if (err.code === 14) {
    // gRPC UNAVAILABLE — server is down or unreachable
    console.error("Connection lost. Reconnecting...");
    reconnect();
  } else if (err.code === 8) {
    // gRPC RESOURCE_EXHAUSTED — rate limited
    console.error("Rate limited on stream. Backing off...");
    backoff();
  } else {
    console.error("Stream error:", err.code, err.message);
  }
});

function reconnect() {
  setTimeout(() => {
    const newStream = client.createBundleStream();
    // Re-attach handlers...
  }, 2000);
}

function backoff() {
  setTimeout(() => {
    // Resume sending at reduced rate
  }, 5000);
}

Retry Pattern

For production systems, wrap calls in a retry helper with exponential backoff:

async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelayMs = 1000
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      if (attempt === maxRetries) throw err;
      if (err.code === "RATE_LIMITED" || err.message?.includes("429")) {
        const delay = baseDelayMs * Math.pow(2, attempt);
        await new Promise((r) => setTimeout(r, delay));
      } else {
        throw err; // Don't retry non-retryable errors
      }
    }
  }
  throw new Error("Unreachable");
}

// Usage
const result = await withRetry(() =>
  client.submitBundle({
    transactions: [serializedTx],
    tip: 500_000,
    mevType: "arbitrage",
  })
);