DOCUMENTATION

BlindRail SDK reference

program HzGxztZEtQkkNyXDKejtUu4M9RtXa1TPoiGhiLnP9zBK · same on mainnet & devnet

Getting started

Introduction

blindrail-sdk is a TypeScript client for the BlindRail treasury program on Solana. Open on-chain SOL treasuries, accept payments, pay out to any address, add SPL / Token-2022 token vaults, airdrop SOL or tokens, and stake the protocol token for fee discounts — from a backend or the browser. Every instruction is input-validated, the IDL is bundled, and transactions confirm over HTTP (so a flaky RPC websocket can't leave a transaction hanging). SOL amounts are in lamports (1 SOL = 1,000,000,000); token amounts are in the mint's base units.

Installation

Install from npm. Peer deps @solana/web3.js and @coral-xyz/anchor are pulled in automatically; the SDK re-exports the handles you need.
npm install blindrail-sdk

import { TreasuryClient, Connection, Keypair, PublicKey, BN } from "blindrail-sdk";

Initialize the client

From a keypair (backend/scripts) or an Anchor provider (browser wallet). The signing wallet is the treasury admin for management calls, or a delegated operator for payouts.
// Backend
const connection = new Connection("https://your-rpc-endpoint", "confirmed");
const client = TreasuryClient.fromKeypair(connection, keypair);

// Browser (wallet adapter)
import { AnchorProvider } from "@coral-xyz/anchor";
const provider = new AnchorProvider(connection, wallet, { commitment: "confirmed" });
const client = new TreasuryClient(provider);

The treasury address

A treasury is an account at a Program Derived Address (PDA). That address — one base58 string — is the only identifier you need; pass it to every call. createTreasury returns it; store it and reuse it. You never handle seeds, the numeric id, or the vault — the SDK derives those.

Fees & the rent reserve

  • SOL payout fee — basis points taken from each SOL payout; deducted from the amount (recipient gets amount − fee). Read via fetchProtocol().feeBps.
  • Creation fee — flat SOL charged when opening a treasury (read via fetchCreationFee()).
  • Token-vault fees — adding a mint to a treasury costs a flat SOL fee, and each token payout costs a flat SOL fee; the recipient always receives the full token amount. Read both via fetchTokenFees().
  • Airdrop fee — a flat 0.001 SOL per recipient, paid straight from the sender (no treasury needed).
  • Staking discount — staking the protocol token (DRAIL) reduces the SOL payout & token-payout fees by 25–100%. The SDK applies it automatically when you have an active stake; see stake.
  • Rent reserve — the vault must stay rent-exempt (~0.00089 SOL), so the max single payout is vaultBalance − 0.00089 SOL. Use closeTreasury to recover 100%.

Use with AI agents

Agent skill

BlindRail ships an official agent skill — a compact spec that teaches AI coding tools how to use blindrail-sdk correctly (lamports, the treasury-address-is-the-PDA rule, the { signature, treasury, id } return shape, HTTP-polled confirmation, the operator security model). One command drops it into your project. Works natively as a skill in Claude, and as a rules/context file for every other major assistant.
# Claude Code / Claude Agent SDK (native skill → .claude/skills/blindrail/)
npx blindrail-sdk add-skill

# pick a tool, or install for all of them at once
npx blindrail-sdk add-skill cursor     # → .cursor/rules/blindrail.md
npx blindrail-sdk add-skill windsurf   # → .windsurf/rules/blindrail.md
npx blindrail-sdk add-skill copilot    # → .github/copilot-instructions.md
npx blindrail-sdk add-skill codex      # → AGENTS.md (Codex & AGENTS.md-aware agents)
npx blindrail-sdk add-skill all

Supported tools

  • Claude Code / Claude Agent SDK — installed as a native skill at .claude/skills/blindrail/.
  • Cursor — project rule at .cursor/rules/blindrail.md.
  • Windsurf — rule at .windsurf/rules/blindrail.md.
  • GitHub Copilot — repo instructions at .github/copilot-instructions.md.
  • OpenAI Codex & other AGENTS.md-aware agentsAGENTS.md at the repo root.
After installing, restart the tool (or session) so it picks up the file. Then ask it to integrate BlindRail and it will use the SDK correctly out of the box.

Treasury

createTreasury

createTreasury(name?, opts?): Promise<{ signature, treasury, id }>
Open a new treasury (charges the creation fee). Returns the treasury address.
PARAMETERS
name
string
Display name, ≤ 32 bytes. Defaults to empty.
opts.id
number | BN
Id used to derive the address; auto-generated if omitted.
RETURNS
{ signature, treasury: PublicKey, id: BN } — store treasury.
const { signature, treasury, id } = await client.createTreasury("Season Rewards");

deposit

deposit(treasury, amount): Promise<string>
Fund the treasury vault with SOL. Anyone can deposit.
PARAMETERS
treasury*
PublicKey
The treasury address.
amount*
number | BN
Lamports to deposit.
RETURNS
Transaction signature.
await client.deposit(treasury, 1_000_000_000); // 1 SOL

closeTreasury

closeTreasury(treasury): Promise<string>
Drain the entire vault back to the admin and close the treasury. Admin only.
PARAMETERS
treasury*
PublicKey
The treasury address.
RETURNS
Transaction signature.
await client.closeTreasury(treasury);

fetchTreasury

fetchTreasury(treasury): Promise<TreasuryAccount>
Read a treasury's on-chain state.
PARAMETERS
treasury*
PublicKey
The treasury address.
RETURNS
{ admin, creator, name, operators, operatorCount, totalDeposited, totalPaidOut, paused, pendingAdmin }
const t = await client.fetchTreasury(treasury);
console.log(t.name, t.totalDeposited.toString());

Payments

pay

pay(treasury, amount, reference?): Promise<{ signature, receipt }>
Customer payment into a treasury with an order/invoice reference. The full amount lands in the treasury (no inbound fee); returns a receipt and emits a PaymentReceived event for reconciliation.
PARAMETERS
treasury*
PublicKey
The treasury address.
amount*
number | BN
Lamports to pay.
reference
number | BN
Order/invoice id (default 0).
RETURNS
{ signature, receipt: { treasury, payer, amount, reference, newTotalDeposited, timestamp } }
const { receipt } = await client.pay(treasury, 50_000_000, 100423);

Payouts

payout

payout(treasury, recipient, amount): Promise<string>
Pay SOL from the vault to any address. The protocol fee is deducted from amount (recipient receives amount − fee). Callable by the admin or a delegated operator.
PARAMETERS
treasury*
PublicKey
The treasury address.
recipient*
PublicKey
Destination address.
amount*
number | BN
Gross lamports debited from the vault.
RETURNS
Transaction signature.
await client.payout(treasury, recipient, 50_000_000); // 0.05 SOL gross

batchPayout

batchPayout(treasury, items): Promise<string>
Up to 10 payouts in a single transaction (MAX_BATCH_PAYOUTS = 10) — the Solana transaction-size limit. Admin or operator. For larger lists (e.g. a CSV of hundreds of recipients), split them into chunks of 10 and send one transaction per chunk, as shown below. The dashboard's CSV / Excel upload does exactly this, with a confirmation step and progress.
PARAMETERS
treasury*
PublicKey
The treasury address.
items*
{ recipient: PublicKey; amount: number | BN }[]
Up to 10 recipients per call.
RETURNS
Transaction signature.
// single batch (≤ 10)
await client.batchPayout(treasury, [
  { recipient: addrA, amount: 50_000_000 },
  { recipient: addrB, amount: 30_000_000 },
]);

// larger list — chunk into groups of 10
const all = [/* { recipient, amount } … hundreds */];
for (let i = 0; i < all.length; i += 10) {
  await client.batchPayout(treasury, all.slice(i, i + 10));
}

Token vaults

createTokenVault

createTokenVault(treasury, mint, tokenProgram?): Promise<{ signature, vault }>
Add an SPL / Token-2022 mint to a treasury — opens a vault (an ATA owned by the treasury PDA) for that mint. Charges the flat add-mint fee in SOL. A treasury can hold many mints; deposits afterwards are plain token transfers into the returned vault. Admin only.
PARAMETERS
treasury*
PublicKey
The treasury address.
mint*
PublicKey
The token mint to add.
tokenProgram
PublicKey
SPL Token (default) or Token-2022. Use fetchMintInfo to detect.
RETURNS
{ signature, vault: PublicKey } — the treasury's token vault address.
const info = await client.fetchMintInfo(mint);
const { vault } = await client.createTokenVault(treasury, mint, info.tokenProgram);

payoutToken

payoutToken(treasury, mint, recipient, amount, tokenProgram?): Promise<string>
Pay tokens from a treasury's vault. The recipient receives the full token amount; the fee is charged separately in SOL (the configurable token-payout fee, reduced by any staking discount).amount is in base units. Admin or operator.
PARAMETERS
treasury*
PublicKey
The treasury address.
mint*
PublicKey
The mint being paid out.
recipient*
PublicKey
Recipient wallet (its ATA is derived and created if needed).
amount*
number | BN | bigint
Base-unit token amount.
tokenProgram
PublicKey
SPL Token (default) or Token-2022.
RETURNS
Transaction signature.
await client.payoutToken(treasury, mint, recipient, 250_000_000n); // 250 tokens (9 dp)

closeTokenVault

closeTokenVault(treasury, mint, adminTokenAccount, tokenProgram?): Promise<string>
Sweep a treasury's token vault to the admin's token account and close it, reclaiming the rent. Admin only.
PARAMETERS
treasury*
PublicKey
The treasury address.
mint*
PublicKey
The mint whose vault to close.
adminTokenAccount*
PublicKey
Admin's ATA for the mint (must exist).
tokenProgram
PublicKey
SPL Token (default) or Token-2022.
RETURNS
Transaction signature.
await client.closeTokenVault(treasury, mint, adminAta);

fetchTokenFees

fetchTokenFees(): Promise<{ addMintFee, payoutFee } | null>
The current token-vault fees in lamports — the SOL charged to add a mint and per token payout.
RETURNS
{ addMintFee: BN, payoutFee: BN } | null
const f = await client.fetchTokenFees();
console.log(f?.addMintFee.toString(), f?.payoutFee.toString());

fetchMintInfo

fetchMintInfo(mint): Promise<MintInfo | null>
Read a mint's decimals and owning token program (SPL Token vs Token-2022) — pass the program through to the token calls.
PARAMETERS
mint*
PublicKey
The token mint.
RETURNS
{ mint, decimals, tokenProgram } | null
const info = await client.fetchMintInfo(mint);

Airdrops

airdropSol

airdropSol(items): Promise<string>
Send SOL to many wallets in one transaction — no treasury needed, straight from the signer. Flat 0.001 SOL fee per recipient. Up to 10 per transaction (MAX_AIRDROP_RECIPIENTS = 10); chunk larger lists like batchPayout.
PARAMETERS
items*
{ recipient: PublicKey; amount: number | BN }[]
Recipient wallets + lamport amounts (≤ 10).
RETURNS
Transaction signature.
await client.airdropSol([
  { recipient: addrA, amount: 10_000_000 },
  { recipient: addrB, amount: 10_000_000 },
]);

airdropToken

airdropToken(mint, items, tokenProgram?): Promise<string>
Send tokens to many wallets in one transaction, from the signer's token account. Each recipient must already hold a token account for the mint. Flat 0.001 SOL fee per recipient; up to 10 per transaction.
PARAMETERS
mint*
PublicKey
The token mint to send.
items*
{ recipient: PublicKey; amount: number | BN }[]
Recipient wallets + base-unit amounts (≤ 10).
tokenProgram
PublicKey
SPL Token (default) or Token-2022.
RETURNS
Transaction signature.
await client.airdropToken(mint, [
  { recipient: addrA, amount: 100_000_000n },
  { recipient: addrB, amount: 100_000_000n },
]);

Staking

stake

stake(mint, amount, tokenProgram?): Promise<string>
Stake (or top up) the protocol token (DRAIL) to earn a fee discount: 500k → 25%, 1M → 50%, 2.5M → 75%, 5M → 100% off the SOL payout & token-payout fees. Staking (or any top-up) resets a 30-day lock. The discount applies automatically to your payouts while staked. amount is in base units.
PARAMETERS
mint*
PublicKey
The protocol-token (DRAIL) mint.
amount*
number | BN | bigint
Base-unit amount to stake.
tokenProgram
PublicKey
SPL Token (default) or Token-2022.
RETURNS
Transaction signature.
await client.stake(drailMint, 500_000_000_000n); // 500k DRAIL (9 dp)

unstake

unstake(mint, amount, tokenProgram?): Promise<string>
Withdraw staked tokens after the 30-day lock has elapsed. Partial withdrawals are allowed; the discount continues to apply to whatever remains staked.
PARAMETERS
mint*
PublicKey
The protocol-token (DRAIL) mint.
amount*
number | BN | bigint
Base-unit amount to withdraw.
tokenProgram
PublicKey
SPL Token (default) or Token-2022.
RETURNS
Transaction signature.
await client.unstake(drailMint, 500_000_000_000n);

fetchStake

fetchStake(owner?): Promise<{ amount, unlockAt } | null>
The staked amount and unlock timestamp for a wallet (defaults to the signer), or null if it hasn't staked.
PARAMETERS
owner
PublicKey
Wallet to read (default: the signer).
RETURNS
{ amount: BN, unlockAt: BN } | null
const s = await client.fetchStake();
if (s) console.log(s.amount.toString(), new Date(s.unlockAt.toNumber() * 1000));

fetchStakingConfig

fetchStakingConfig(): Promise<StakingConfig | null>
The staking config — the protocol-token mint and the four tier thresholds — or null if staking isn't live yet.
RETURNS
{ mint, tier1, tier2, tier3, tier4, totalStaked } | null
const cfg = await client.fetchStakingConfig();

Operators

addOperator

addOperator(treasury, operator): Promise<string>
Authorize a delegated operator key that can sign payouts (max 5). Admin only — keep the admin key offline; let your server hold the operator key.
PARAMETERS
treasury*
PublicKey
The treasury address.
operator*
PublicKey
Operator key to authorize.
RETURNS
Transaction signature.
await client.addOperator(treasury, operatorPublicKey);

removeOperator

removeOperator(treasury, operator): Promise<string>
Revoke an operator. Admin only.
PARAMETERS
treasury*
PublicKey
The treasury address.
operator*
PublicKey
Operator key to revoke.
RETURNS
Transaction signature.
await client.removeOperator(treasury, operatorPublicKey);

Management

setPaused

setPaused(treasury, paused): Promise<string>
Pause or resume payouts. Deposits and payments still work while paused. Admin only.
PARAMETERS
treasury*
PublicKey
The treasury address.
paused*
boolean
true to pause, false to resume.
RETURNS
Transaction signature.
await client.setPaused(treasury, true);

nominateAdmin

nominateAdmin(treasury, newAdmin): Promise<string>
Step 1 of a two-step admin transfer. The admin does not change until the nominee calls acceptAdmin, so a wrong address can't lock you out. Admin only.
PARAMETERS
treasury*
PublicKey
The treasury address.
newAdmin*
PublicKey
Wallet to nominate.
RETURNS
Transaction signature.
await client.nominateAdmin(treasury, newAdminPubkey);

acceptAdmin

acceptAdmin(treasury): Promise<string>
Step 2 — the nominated wallet (the current signer) accepts and becomes the admin.
PARAMETERS
treasury*
PublicKey
The treasury address.
RETURNS
Transaction signature.
// signed by the nominated wallet's client
await newAdminClient.acceptAdmin(treasury);

Reads

fetchProtocol

fetchProtocol(): Promise<ProtocolConfig | null>
Read the protocol config — useful for the current payout fee.
RETURNS
{ feeBps, totalTreasuries, totalFeesCollected }
const p = await client.fetchProtocol();
console.log(p.feeBps); // e.g. 100 = 1%

fetchCreationFee

fetchCreationFee(): Promise<BN | null>
Current treasury-creation fee in lamports (null if not configured).
RETURNS
BN | null
const fee = await client.fetchCreationFee();
Open the dashboard →Back home