Skip to content

pactnetwork/solder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

169 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Solder

Solder architecture diagram

A modern Solana backend framework for shipping web2 backends faster

Build production-ready Solana indexers with auto-generated APIs in minutes, not days.

License: ISC Node.js

Website β€’ Documentation β€’ Examples β€’ Telegram

This project is currently in active development. APIs, features, and documentation may change without notice. Use at your own risk.


What is Solder?

Solder is a comprehensive Solana backend framework that abstracts away the complexity of building blockchain indexers and APIs. It provides:

  • πŸš€ Indexer Abstraction - Monitor Solana programs and events with minimal configuration
  • πŸ—„οΈ Built-in Database Support - Integrated Drizzle ORM with PostgreSQL
  • πŸ”Œ Auto-generated APIs - RESTful CRUD endpoints created automatically from your schema
  • πŸ” Cloud Wallet Integration - Optional GCP KMS integration for secure message signing
  • πŸ“ Type-safe - Full TypeScript support with IDL-based type inference
  • πŸ“Š Real-time Progress UI - Live terminal interface with performance metrics and health monitoring
  • ⚑ Fast Development - Go from zero to production-ready backend in minutes
  • πŸ”₯ Hot Schema Reloading - Automatic database schema synchronization during development

Quick Start

Requirements

  • Node.js >= 18
  • npm, pnpm, or yarn
  • PostgreSQL database
  • Solana RPC endpoint (optional: defaults to public mainnet RPC)
  • Realtime streaming endpoint (choose one):
    • Yellowstone gRPC endpoint (recommended): Provides low-latency block streaming with better reliability
    • WebSocket endpoint: Must support blockSubscribe RPC method (many providers don't support this)
  • Google Cloud Project with KMS (optional: only if using cloud wallets)

Solder App Setup

The fastest way to get started with Solder is using the create-solder CLI:

npx create-solder

The CLI will:

  1. Prompt you for a project name
  2. Ask where you want to create the project
  3. Ask if you want to install dependencies automatically
  4. Ask if you want to use GCP Cloud Wallets (optional)
  5. Set up a complete Solder project with example code

After creation:

cd your-project-name

# Copy and configure environment variables
cp .env.example .env
# Update .env with your RPC URL and database connection string

# Install dependencies (if you skipped during setup)
npm install

# Generate database schema
npm run generate

# Push schema to database
npm run push

# Start the indexer and API server
npm run dev

GCP Cloud Wallets Integration

Solder now supports optional GCP Cloud Wallets integration for secure message signing using Google Cloud KMS. This feature is perfect for applications that need to sign transactions or messages without exposing private keys.

What You Get

When you enable GCP Cloud Wallets during project creation, you get:

  • Cloud Wallet Dependencies - All necessary packages for GCP KMS integration
  • Message Signing API - Ready-to-use HonoJS endpoints for signing messages
  • Environment Configuration - Pre-configured environment variables for GCP setup
  • Example Implementation - Working code that demonstrates cloud wallet functionality

API Endpoints

If you enable cloud wallets, your Solder app will include these endpoints:

# Sign a message with GCP KMS
POST /api/sign-message
{
  "message": "Hello, Solana!"
}

# Check cloud wallet status
GET /api/cloud-wallet/status

Setup Requirements

To use GCP Cloud Wallets, you'll need:

  1. Google Cloud Project with KMS enabled
  2. Service Account with KMS permissions
  3. Crypto Key created in Google Cloud KMS
  4. Environment Variables configured in your .env file

Example Usage

# Test the message signing endpoint
curl -X POST http://localhost:4000/api/sign-message \
  -H "Content-Type: application/json" \
  -d '{"message": "Test message for signing"}'

# Check if cloud wallets are enabled
curl http://localhost:4000/api/cloud-wallet/status

Environment Variables

When cloud wallets are enabled, you'll need to configure these environment variables in your .env file:

# GCP Configuration
GCP_PROJECT_ID=your-project-id
GCP_LOCATION=global
GCP_KEY_RING=your-key-ring
GCP_KEY_NAME=your-key-name
GCP_KEY_VERSION=1

# Authentication (choose one method)
# Method 1: Service Account JSON file
GCP_KEY_FILENAME=./path/to/service-account-key.json

# Method 2: Service Account credentials
# GCP_CLIENT_EMAIL=your-service-account@project.iam.gserviceaccount.com
# GCP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

Hot Schema Reloading

Solder includes automatic schema synchronization during development, making it incredibly fast to iterate on your database schema.

How It Works

When you run pnpm run dev, Solder automatically:

  1. Watches your solder.schema.ts file for changes
  2. Syncs schema changes to your database instantly (using drizzle-kit push)
  3. Skips migration file generation entirely in development mode

Benefits

  • No manual pushes - Schema changes sync automatically as you save files
  • Fast iteration - See your changes reflected immediately
  • No migration files - Keep your repo clean during development
  • Production-ready - Generate migrations when you're ready to deploy

Usage

Just edit your solder.schema.ts file:

// Add a new field to your table
const trades = solderTable(
  "trades",
  {
    id: serial("id").primaryKey(),
    mint: varchar("mint", { length: 44 }).notNull(),
    // Add this new field - it syncs automatically!
    fee: integer("fee").default(0),
    // ... other fields
  },
  // ... options
);

Save the file and watch the console - your database updates automatically! πŸŽ‰

Disabling Hot Reloading

If you need to disable automatic schema syncing:

You can set enableHotReload: false in your solder.config.ts:

export const solderConfig: SolderConfig = {
  db: {
    connectionString: process.env.DATABASE_URL ?? "",
  },
  dev: {
    enableHotReload: false,
  },
};

Production Deployments

For production, generate proper migration files:

# Generate migration files
pnpm run generate

# Review and commit the migration files
git add drizzle/

# Apply migrations in production
pnpm run push

Important: Migration files in the drizzle/ folder are only needed for production deployments. In development, schema changes sync automatically without creating migration files.


solderTable Documentation

The solderTable function is the core building block for defining your database schema with Solder. It extends Drizzle ORM tables with additional metadata for automatic API generation.

Basic Usage

import { solderTable } from "@solder-build/core";
import { serial, varchar, timestamp, boolean, text } from "drizzle-orm/pg-core";

const trades = solderTable(
  "trades", // Table name
  {
    // Column definitions (standard Drizzle ORM)
    id: serial("id").primaryKey(),
    mint: varchar("mint", { length: 44 }).notNull(),
    user: varchar("user", { length: 44 }).notNull(),
    isBuy: boolean("is_buy").notNull(),
    timestamp: timestamp("timestamp", { mode: "date" }).notNull(),
    createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
  },
  {
    // Solder-specific options
    primaryKey: "id",
    api: {
      basePath: "/trades",
      enabled: true,
      operations: {
        list: true, // GET /trades
        read: true, // GET /trades/:id
        create: true, // POST /trades
        update: false, // PUT /trades/:id (disabled)
        delete: false, // DELETE /trades/:id (disabled)
      },
    },
    description: "Trade events from pump.fun",
  },
);

Options

Table Options (Third Parameter)

Option Type Required Description
primaryKey string Yes Name of the primary key column
api object No API generation configuration
description string No Table description for documentation

API Configuration

Option Type Default Description
enabled boolean true Enable/disable API generation for this table
basePath string "/{tableName}" Base path for API endpoints
operations.list boolean true Enable GET /basePath (list with query support)
operations.read boolean true Enable GET /basePath/:id (read one)
operations.create boolean true Enable POST /basePath (create)
operations.update boolean true Enable PUT /basePath/:id (update)
operations.delete boolean true Enable DELETE /basePath/:id (delete)

Fine-Grained Query API

The LIST operation supports powerful PostgREST-style query parameters for filtering, sorting, pagination, and field selection.

Important: All query parameters use database column names (snake_case like is_buy, sol_amount), not schema property names (camelCase like isBuy, solAmount).

Basic Filtering

Filter results by adding query parameters with operator syntax: field=operator.value

# Get trades where is_buy equals true
GET /trades?is_buy=eq.true

# Get trades where sol_amount is greater than 1000
GET /trades?sol_amount=gt.1000

# Multiple filters (AND condition by default)
GET /trades?is_buy=eq.true&sol_amount=gt.1000

Filter Operators

Solder supports the following comparison operators:

Operator Description Example
eq Equal ?amount=eq.100
neq Not equal ?amount=neq.0
gt Greater than ?amount=gt.1000
gte Greater than or equal ?amount=gte.1000
lt Less than ?amount=lt.500
lte Less than or equal ?amount=lte.500

Type Handling: The API automatically converts values to the appropriate type (booleans, numbers, strings, null).

Logical Operators

OR Conditions:

# Get trades where is_buy is true OR sol_amount is greater than 5000
GET /trades?or=(is_buy.eq.true,sol_amount.gt.5000)

AND Conditions (Default):

# Get trades where is_buy is true AND sol_amount is greater than 1000
GET /trades?is_buy=eq.true&sol_amount=gt.1000

Sorting

Sort results by specifying a field and direction:

# Sort by timestamp descending (newest first)
GET /trades?order=timestamp.desc

# Sort by amount ascending
GET /trades?order=sol_amount.asc

Pagination

Control the number of results and starting position:

# Get first 10 results
GET /trades?limit=10

# Skip first 20 results, then get 10
GET /trades?limit=10&offset=20

# Page 3 with 25 items per page
GET /trades?limit=25&offset=50

Field Selection

Return only specific fields instead of all columns:

# Return only mint, user, and timestamp fields
GET /trades?select=mint,user,timestamp

# Return only the amount fields
GET /trades?select=sol_amount,token_amount

Note: Field names must match the database column names (snake_case), not the schema property names.

Combining Parameters

All query parameters can be combined for powerful queries:

# Complex query: Filter, sort, paginate, and select fields
GET /trades?is_buy=eq.true&sol_amount=gt.1000&order=timestamp.desc&limit=10&select=mint,user,sol_amount,timestamp

# OR condition with sorting and pagination
GET /trades?or=(is_buy.eq.true,sol_amount.gt.5000)&order=sol_amount.desc&limit=20

# Filter by date range with field selection
GET /trades?timestamp=gte.2024-01-01&timestamp=lt.2024-02-01&select=mint,timestamp,sol_amount

Solana Use Cases

# Get recent buys over a certain amount
GET /trades?is_buy=eq.true&sol_amount=gt.10&order=timestamp.desc&limit=50

# Find all trades for a specific mint
GET /trades?mint=eq.abc123...&order=timestamp.desc

# Get large trades (buy or sell)
GET /trades?or=(sol_amount.gt.100,token_amount.gt.1000000)&order=sol_amount.desc

# Monitor recent activity
GET /trades?order=timestamp.desc&limit=100&select=mint,user,is_buy,sol_amount,timestamp

Error Handling

If a query is malformed or references invalid fields, the API will return a 400 error with details:

{
  "error": "Unknown field: invalid_field_name"
}

Backwards Compatibility

If no query parameters are provided, the API behaves as before and returns all records:

# Returns all trades (no filtering)
GET /trades

Building the Schema

After defining your tables, use solderSchema to build the final schema:

import { solderSchema } from "@solder-build/core";

const built = solderSchema(trades, users, tokens);

// Export for Drizzle Kit
export const tradesTable = trades.table;
export const usersTable = users.table;

// Export for application use
export const schema = built.schema;
export const tables = built.tables;
export type AppSchema = typeof schema;

Indexer Instance Documentation

The Indexer class is the core component for monitoring Solana blockchain events and processing them into your database.

Creating an Indexer

import { Indexer } from "@solder-build/core";

// Example with gRPC streaming (recommended)
const indexer = new Indexer({
  startBlock: 300000000, // Starting slot number
  rpcUrl: process.env.RPC_URL, // Solana RPC endpoint
  databaseUrl: process.env.DATABASE_URL, // PostgreSQL connection string
  cursorKey: "my-indexer", // Unique identifier for this indexer
  enableUIProgress: true, // Enable real-time progress UI
  grpcUrl: process.env.GRPC_URL, // Yellowstone gRPC endpoint
  grpcToken: process.env.GRPC_TOKEN, // Optional: gRPC authentication token
});

// Example with WebSocket streaming (requires blockSubscribe support)
const indexerWS = new Indexer({
  startBlock: 300000000,
  rpcUrl: process.env.RPC_URL,
  databaseUrl: process.env.DATABASE_URL,
  cursorKey: "my-indexer",
  enableUIProgress: true,
  wsUrl: process.env.WS_URL, // WebSocket endpoint (must support blockSubscribe)
});

Configuration Options

Option Type Required Description
startBlock number Yes Slot number to start indexing from
rpcUrl string Yes Solana RPC endpoint URL
databaseUrl string No PostgreSQL connection string (required for persistence)
cursorKey string No Unique key for cursor storage (defaults to "default")
enableUIProgress boolean No Enable real-time progress UI (defaults to false)
grpcUrl string Yes* Yellowstone gRPC endpoint for streaming (required if wsUrl not provided)
grpcToken string No Optional authentication token for gRPC endpoint
wsUrl string Yes* WebSocket endpoint for streaming (required if grpcUrl not provided)
logger Logger No Custom logger instance (defaults to console logger)

* Either grpcUrl or wsUrl must be provided, but not both.

⚠️ WebSocket Requirements and Limitations

Important: The wsUrl option requires a WebSocket endpoint that supports the blockSubscribe RPC method. Many RPC providers (including Helius) do not support this method, which will cause the indexer to fail with a "Method not found" error.

If WebSocket initialization fails, the indexer will stop immediately with an error message. There is no automatic fallback to HTTP polling.

Recommendation: Use grpcUrl with a Yellowstone gRPC endpoint for more reliable streaming. Yellowstone gRPC provides:

  • Lower latency than WebSocket
  • More efficient data handling
  • Better reliability and reconnection handling
  • Direct block data without additional RPC calls

Example error when WebSocket doesn't support blockSubscribe:

[ERROR] Failed to initialize websocket channel: Error: Method not found

If you encounter this error, switch to using grpcUrl instead of wsUrl.

Registering Event Handlers

Use the onEvent method to register handlers for specific program events:

import { type Idl } from "@coral-xyz/anchor";
import pumpFunIdl from "./idls/pump-fun.json";

await indexer.onEvent({
  programId: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
  idl: pumpFunIdl as Idl,
  eventName: "TradeEvent",
  handler: async (event, db) => {
    // Process the event
    await db.insert(tradesTable).values({
      mint: event.parsed.mint.toBase58(),
      user: event.parsed.user.toBase58(),
      isBuy: event.parsed.isBuy,
      timestamp: new Date(Number(event.parsed.timestamp) * 1000),
    });
  },
});

Event Handler Signature

type EventHandler<TIdl, TEventName> = (
  event: IndexerEvent<TIdl, TEventName>,
  db: NodePgDatabase,
) => Promise<void> | void;

interface IndexerEvent {
  name: string; // Event name
  contract: string; // Program ID
  type: string; // Event type
  parsed: any; // Parsed event data (typed based on IDL)
  timestamp: string; // Event timestamp
  transaction: {
    hash: string; // Transaction signature
    slot: number; // Slot number
    blockTime: number; // Block timestamp
  };
  programId: string; // Program ID
  eventName: string; // Event name
}

Starting and Stopping

// Start the indexer
await indexer.start();

// Stop the indexer
indexer.stop();

Getting Status

const status = indexer.getStatus();
console.log(status);
// {
//   isRunning: true,
//   currentSlot: 300000042,
//   registeredPrograms: 1,
//   eventHandlers: 1
// }

Progress UI

When enableUIProgress: true is set, Solder provides a real-time terminal UI that displays:

  • Chain Status: Current blockchain status and sync progress
  • Indexing Stats: Real-time event processing statistics with RPS (requests per second)
  • Event Table: Live view of processed events with counts and performance metrics
  • Progress Bar: Visual progress indicator with ETA calculations
  • Health Monitoring: Database, realtime stream (gRPC/WebSocket), and RPC connection status

The progress UI automatically updates in place, providing a clean development experience without cluttering your terminal output.

const indexer = new Indexer({
  // ... other options
  enableUIProgress: true, // Enables the real-time progress UI
});

Features:

  • Live terminal updates without scrolling
  • Performance metrics (RPS, average processing time)
  • Health status indicators
  • Progress tracking with ETA
  • Event processing statistics
  • Responsive design that adapts to terminal width

Complete Example

import { Indexer } from "@solder-build/core";
import { tradesTable } from "./schema";
import pumpFunIdl from "./idls/pump-fun.json";

export const initializeIndexer = async () => {
  const indexer = new Indexer({
    startBlock: 300000000,
    rpcUrl: process.env.RPC_URL || "https://api.mainnet-beta.solana.com",
    databaseUrl: process.env.DATABASE_URL,
    cursorKey: "pump-fun-indexer",
    enableUIProgress: true,
    // Use gRPC for reliable streaming (recommended)
    grpcUrl: process.env.GRPC_URL,
    grpcToken: process.env.GRPC_TOKEN, // Optional
    // Or use WebSocket (only if your provider supports blockSubscribe)
    // wsUrl: process.env.WS_URL,
  });

  await indexer.onEvent({
    programId: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
    idl: pumpFunIdl,
    eventName: "TradeEvent",
    handler: async (event, db) => {
      await db.insert(tradesTable).values({
        mint: event.parsed.mint.toBase58(),
        solAmount: event.parsed.solAmount.toString(),
        tokenAmount: event.parsed.tokenAmount.toString(),
        isBuy: event.parsed.isBuy,
        user: event.parsed.user.toBase58(),
        virtualSolReserves: event.parsed.virtualSolReserves.toString(),
        virtualTokenReserves: event.parsed.virtualTokenReserves.toString(),
        timestamp: new Date(Number(event.parsed.timestamp) * 1000),
      });
    },
  });

  await indexer.start();
  return indexer;
};

Architecture

Solder is built on a modern, modular architecture that combines several best-in-class technologies:

Solder Architecture

Key Components

  1. Indexer Layer - Monitors Solana blockchain for program events
  2. Schema Layer - Type-safe database schema with Drizzle ORM
  3. API Layer - Auto-generated RESTful APIs with Hono
  4. RPC Layer - Efficient Solana RPC client for block and transaction data

For a detailed explanation of Solder's architecture, features, and design philosophy, please visit our comprehensive documentation:

πŸ“š Solder Documentation


What's Inside This Monorepo?

This Turborepo includes the following packages and apps:

Apps

  • apps/docs - Documentation website (Next.js)
  • apps/web - Marketing website (Next.js)
  • apps/example-app - Example Solder application indexing pump.fun trades

Packages

  • packages/core - Core Solder framework (solder)
  • packages/cli - CLI for scaffolding projects (create-solder)
  • @repo/ui - Shared React component library
  • @repo/eslint-config - Shared ESLint configurations
  • @repo/typescript-config - Shared TypeScript configurations

Development Commands

# Build all packages
pnpm build

# Run all apps in development mode
pnpm dev

# Lint all packages
pnpm lint

# Format code
pnpm format

# Create a new Solder app
pnpm create-app

πŸš€ Ready to Build?

Start building production-ready Solana backends today

Visit solder.build β†’

Follow us on X/Twitter β€’ Check out legends.fun

gRPC Rust Indexer example (Yellowstone)

This repo includes a high-performance gRPC indexer implemented with a Rust native addon and Yellowstone Vixen streaming.

Prerequisites

  • Node.js 18+ and pnpm
  • Rust toolchain + Cargo
  • PostgreSQL (local or Docker)
  • Yellowstone Fumarole gRPC endpoint and X-Token

1) Install dependencies

pnpm install

2) Build the native addon and TypeScript

From the repo root:

pnpm --filter @solder-build/core build:native
pnpm --filter @solder-build/core build:ts
# Or one shot:
pnpm --filter @solder-build/core build

3) Start Postgres (example via Docker)

docker run --name solder-pg \
  -e POSTGRES_PASSWORD=password123 \
  -e POSTGRES_DB=app \
  -p 6500:5432 \
  -d postgres:16

Set your connection string:

export DATABASE_URL="postgresql://postgres:password123@127.0.0.1:6500/app"

4) Set Yellowstone gRPC credentials

export GRPC_ENDPOINT="https://your-fumarole-endpoint.com"
export GRPC_TOKEN="your-x-token"

Note: The example at packages/core/src/examples/grpc-indexer.ts currently hardcodes values for grpcEndpoint, xToken, and databaseUrl. For production usage, change those to read from process.env.* or supply your values before building.

5) Run the example

node packages/core/dist/examples/grpc-indexer.js

You should see:

  • Event handler registration
  • Monitored programs and handler counts
  • Streaming logs as events arrive

Stop with Ctrl+C; graceful shutdown is handled.

Troubleshooting

  • Missing index.node: rerun the native build step (build:native).
  • DB connection errors: verify DATABASE_URL matches your Postgres host/port.
  • Auth errors: confirm GRPC_TOKEN is valid and paired with the same endpoint.

About

Backend Development Framework for Solana

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors