Skip to content

typescript ​

Send a bundle using TypeScript with full type safety.

Types ​

typescript
export interface BundleParams {
  encoding: "base64";
  skipPreflight?: boolean;
}

export interface BundleResult {
  bundle_id: string;
  signatures: string[];
}

export interface JsonRpcResponse<T> {
  jsonrpc: "2.0";
  id: number;
  result?: T;
  error?: {
    code: number;
    message: string;
  };
}

Send Bundle Function ​

typescript
import { Transaction } from "@solana/web3.js";
import type { BundleParams, BundleResult, JsonRpcResponse } from "./types.js";

export const ENDPOINT = "https://fra.blocksprint.io/bundle/v1/YOUR_API_KEY";

export async function sendBundle(transactions: Transaction[]): Promise<BundleResult> {
  const serializedTxs = transactions.map((tx) =>
    Buffer.from(tx.serialize()).toString("base64")
  );

  const response = await fetch(ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "sendBundle",
      params: [
        serializedTxs,
        {
          encoding: "base64",
          skipPreflight: true,
        } as BundleParams,
      ],
    }),
  });

  const data: JsonRpcResponse<BundleResult> = await response.json();

  if (data.error) {
    throw new Error(`Bundle error ${data.error.code}: ${data.error.message}`);
  }

  return data.result!;
}

Full Example ​

typescript
import {
  Connection,
  Keypair,
  PublicKey,
  SystemProgram,
  Transaction,
  TransactionInstruction,
} from "@solana/web3.js";
import { sendBundle } from "./sendBundle.js";
import type { BundleResult } from "./types.js";

// Your Solana RPC (for the blockhash)
const RPC_URL = "https://api.mainnet-beta.solana.com";
// BlockSprint tip address, tip at least 0.001 SOL
const TIP_ADDRESS = new PublicKey("Sp4JHSh9cksfzXbgK7Pq2ovtn8LirLQydaJKTsiNT77");

interface BundleStep {
  instructions: TransactionInstruction[];
  signers?: Keypair[];
}

async function createBundle(
  connection: Connection,
  payer: Keypair,
  steps: BundleStep[]
): Promise<Transaction[]> {
  const { blockhash } = await connection.getLatestBlockhash();

  return steps.map((step) => {
    const tx = new Transaction();
    step.instructions.forEach((ix) => tx.add(ix));
    tx.recentBlockhash = blockhash;
    tx.feePayer = payer.publicKey;

    const signers = [payer, ...(step.signers || [])];
    tx.sign(...signers);

    return tx;
  });
}

async function executeBundle(
  wallet: Keypair,
  steps: BundleStep[]
): Promise<BundleResult> {
  const connection = new Connection(RPC_URL);
  const transactions = await createBundle(connection, wallet, steps);
  return sendBundle(transactions);
}

// Usage
async function main() {
  const wallet = Keypair.generate();

  // 0.001 SOL tip
  const tipInstruction = SystemProgram.transfer({
    fromPubkey: wallet.publicKey,
    toPubkey: TIP_ADDRESS,
    lamports: 1_000_000,
  });

  const result = await executeBundle(wallet, [
    {
      instructions: [approveInstruction],
    },
    {
      instructions: [swapInstruction, tipInstruction],
    },
  ]);

  console.log("Bundle ID:", result.bundle_id);
  result.signatures.forEach((sig, i) => {
    console.log(`Tx ${i + 1}: ${sig}`);
  });
}

Versioned Transactions ​

typescript
import { VersionedTransaction, TransactionMessage } from "@solana/web3.js";
import { ENDPOINT } from "./sendBundle.js";
import type { BundleResult, JsonRpcResponse } from "./types.js";

async function sendVersionedBundle(
  transactions: VersionedTransaction[]
): Promise<BundleResult> {
  const serializedTxs = transactions.map((tx) =>
    Buffer.from(tx.serialize()).toString("base64")
  );

  const response = await fetch(ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "sendBundle",
      params: [
        serializedTxs,
        { encoding: "base64", skipPreflight: true },
      ],
    }),
  });

  const data: JsonRpcResponse<BundleResult> = await response.json();

  if (data.error) {
    throw new Error(data.error.message);
  }

  return data.result!;
}