Skip to content

TypeScript SDK

The TypeScript SDK is @alea-drand/sdk on npm. It builds, signs, and sends the verify transaction for you, polls for confirmation, and extracts the 32-byte randomness from transaction return data.

Terminal window
npm install @alea-drand/sdk

Current version: 0.2.0. Apache 2.0. ESM-only. Peer dependencies:

Terminal window
npm install @solana/web3.js@^1.95 @coral-xyz/anchor@^0.30.1

This page is guide first, full reference second. The reference at the bottom covers every export from src/index.ts.

getVerifiedRandomness — fetch + verify in one call

Section titled “getVerifiedRandomness — fetch + verify in one call”
import { getVerifiedRandomness } from "@alea-drand/sdk";
import { Connection, Keypair } from "@solana/web3.js";
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const signer = Keypair.fromSecretKey(/* ... */);
const randomness: Uint8Array = await getVerifiedRandomness({
connection,
signer,
});
// randomness.length === 32, freshly verified on-chain

Fetches the latest drand round from the evmnet chain, builds an Anchor verify instruction, signs with signer, broadcasts via sendRawTransaction, polls for confirmation, and returns the 32-byte randomness. Throws AleaError on any failure.

verifyDrandBeacon — verify a specific round

Section titled “verifyDrandBeacon — verify a specific round”
import { verifyDrandBeacon, fetchBeacon } from "@alea-drand/sdk";
const beacon = await fetchBeacon(9337227n);
// beacon.round === 9337227n
// beacon.signature is a 64-byte Uint8Array
const randomness = await verifyDrandBeacon({
connection,
signer,
round: beacon.round,
signature: beacon.signature,
});

Use this when you need to verify a specific round (commit-reveal patterns, replaying a round for inspection, reproducing a historical draw).

Both functions accept a Keypair or a wallet-adapter Wallet. If you pass a wallet-adapter wallet, its signTransaction() method is used — the user’s wallet signs in the browser, no private keys touch your code:

import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { getVerifiedRandomness } from "@alea-drand/sdk";
function DrawButton() {
const { connection } = useConnection();
const wallet = useWallet();
async function handleDraw() {
if (!wallet.connected) return;
const randomness = await getVerifiedRandomness({
connection,
signer: wallet, // wallet-adapter Wallet shape; structurally compatible
});
console.log(Buffer.from(randomness).toString("hex"));
}
}

The SDK detects browser wallets structurally (looks for sendTransaction), so it works with any wallet-adapter implementation without importing wallet-adapter types at runtime. @solana/wallet-adapter-base is an optional peer dependency.

import { AleaError, ERRORS } from "@alea-drand/sdk";
try {
await getVerifiedRandomness({ connection, signer });
} catch (err) {
if (err instanceof AleaError) {
console.error(`Alea ${err.code}: ${err.message}`);
const humanName = ERRORS[err.code];
// humanName = "InvalidSignature: BLS signature verification failed", etc.
} else {
// Network error, RPC error, etc. — not an Alea error.
throw err;
}
}

err.code is either an Alea custom program error code (the 6xxx range, defined by Alea’s AleaError enum) or an Anchor framework code (2xxx/3xxx, e.g. 2001 ConstraintHasOne). The SDK’s ERRORS map includes both. Full table in Errors reference.

Connection URL determines the cluster, not the SDK. The program ID is cluster-agnostic — same vanity address on devnet and mainnet by design.

import { Connection } from "@solana/web3.js";
import { getVerifiedRandomness, DEVNET_PROGRAM_ID } from "@alea-drand/sdk";
// Devnet (today)
const devnet = new Connection("https://api.devnet.solana.com", "confirmed");
const r1 = await getVerifiedRandomness({ connection: devnet, signer });
// Mainnet (when live). Explicitly pass programId once MAINNET_PROGRAM_ID is set.
const mainnet = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
const r2 = await getVerifiedRandomness({
connection: mainnet,
signer,
// programId: MAINNET_PROGRAM_ID, // currently throws on access — set post-deploy
});

MAINNET_PROGRAM_ID is exported as a Proxy that throws on any property access until the mainnet program deploys. Importing it is safe; using it before the mainnet release version of the SDK throws Error: MAINNET_PROGRAM_ID not set.

The SDK is ESM-only and loads its Anchor IDL via readFileSync from a bundled JSON. That means it runs cleanly in Node-compatible environments — Vite, Next.js (Node runtime), esbuild, plain Node servers. It does NOT run on edge runtimes that lack fs — Cloudflare Workers, Vercel Edge, Deno Deploy. On those, either run the verify server-side and return the randomness to the edge, or fork the SDK and inline the IDL at build time via a bundler transform.

function getVerifiedRandomness(options: {
connection: Connection;
signer: Keypair | Wallet;
programId?: PublicKey;
commitment?: Commitment;
round?: bigint;
computeUnits?: number;
}): Promise<Uint8Array>

Fetches the latest drand round (or the specified round), builds, signs, sends, and confirms the verify transaction. Returns 32 bytes of verified randomness.

  • connection — a web3.js Connection to the target cluster
  • signer — a Keypair or wallet-adapter Wallet that pays the tx fee
  • programId — override the Alea program ID. Defaults to DEVNET_PROGRAM_ID.
  • commitment — Solana commitment level. Defaults to "confirmed".
  • round — specific drand round to verify. Defaults to current round.
  • computeUnits — override the CU budget. Defaults to 900_000.
function verifyDrandBeacon(args: {
connection: Connection;
signer: Keypair | Wallet;
round: bigint;
signature: Uint8Array;
programId?: PublicKey;
computeUnits?: number;
}): Promise<Uint8Array>

Verifies a (round, signature) pair you already have. Same return / throwing behavior as getVerifiedRandomness; use this one when the beacon is already fetched.

  • round — drand round number as bigint
  • signature — 64-byte G1 point, uncompressed (x || y) big-endian
function fetchBeacon(round?: bigint): Promise<DrandBeacon>

Fetches a drand evmnet beacon. Cycles through 5 endpoints (api.drand.sh + two mirrors + Cloudflare + a secureweb3 backup) with a 5-second per-request timeout. Up to 3 retry passes, so worst-case 15 individual HTTP attempts before giving up.

If round is omitted, fetches the latest round. If a 404 comes back on “latest” (round not yet produced), backs off by one round and retries.

Returns { round: bigint, signature: Uint8Array, unverifiedRandomness: string }. The unverifiedRandomness field is drand’s own hex string — not on-chain verified. Use getVerifiedRandomness or verifyDrandBeacon for trust.

function getCurrentRound(): bigint

Computes the current drand round number from Date.now(), genesis time, and period. Pure function, no I/O. Year-2086 safe (returns bigint).

function getRoundAt(timestamp: bigint): bigint

Returns the drand round number active at a given Unix-seconds timestamp. Useful for anchoring a commit to a future round.

function isRoundRecent(
round: bigint,
config: { genesisTime: bigint; period: bigint },
clock: { unixTimestamp: bigint },
maxAgeSeconds: bigint,
): boolean

Pure, no I/O. Returns true if the round’s drand-derived timestamp is within maxAgeSeconds of the given clock. Returns false for round 0, and for future rounds whose expected timestamp is past clock.unixTimestamp.

Useful for pre-flight freshness checks in off-chain code. Behavior differs slightly from the Rust SDK’s is_round_recent on future-dated rounds: the Rust version uses saturating arithmetic on u64 and returns true when the round’s timestamp is in the future (saturates to 0 age), while this TS version rejects future rounds explicitly. Either side catches stale beacons; future-round handling differs because the Rust version runs on-chain against Solana’s own clock (which shouldn’t see future rounds in practice) and the TS version is a pre-flight guard where future rounds indicate caller confusion. The on-chain authoritative check runs via require!(alea_sdk::is_round_recent(...)) in your Rust program regardless — don’t skip that step.

function createVerifyInstruction(options: {
round: bigint;
signature: Uint8Array;
programId?: PublicKey;
}): TransactionInstruction

Low-level instruction builder. Returns a TransactionInstruction with the verify discriminator and data correctly encoded, and the Config PDA set as the first account key. It does not include the payer signer key. The on-chain Verify accounts struct expects [config, payer] in that order, so you must append the payer key yourself before sending:

import { createVerifyInstruction } from "@alea-drand/sdk";
const ix = createVerifyInstruction({ round, signature });
ix.keys.push({ pubkey: payer.publicKey, isSigner: true, isWritable: false });
// now ix.keys is [config, payer] — the order Anchor expects

Use this when you’re composing the verify into a multi-instruction transaction and need to control tx layout precisely. For most applications, getVerifiedRandomness or verifyDrandBeacon are simpler — they wire up keys, signer, compute budget, and retry logic end-to-end. A future SDK release will likely change this helper to accept a payer: PublicKey argument and append the key for you.

function getConfigAddress(programId?: PublicKey): PublicKey

Derives the Alea Config PDA from seeds [Buffer.from("config")] for a given program ID. Defaults to DEVNET_PROGRAM_ID.

NameValuePurpose
DRAND_CHAIN_HASH"04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3"evmnet chain hash — explicit chain pin for drand requests
DRAND_GENESIS_TIME1727521075Unix seconds for evmnet genesis (2024-09-28T13:37:55Z)
DRAND_PERIOD3Beacon cadence in seconds
DRAND_ENDPOINTSreadonly string[] (5 URLs)Fallback endpoint list used by fetchBeacon
DEVNET_PROGRAM_IDPublicKey("ALEAydzHd4cN2EWcdHKp4hehAE4B88b16gqVtVqsck2U")Devnet program ID (same vanity used for mainnet)
MAINNET_PROGRAM_IDPublicKey (throwing Proxy)Placeholder until mainnet release. Accessing any property throws until the SDK ships with the live mainnet ID set.
interface DrandBeacon {
round: bigint;
signature: Uint8Array; // 64 bytes, uncompressed G1
unverifiedRandomness: string; // hex — NOT on-chain verified
}
interface DrandConfig {
genesisTime: bigint;
period: bigint;
}

Shape-compatible with the subset of on-chain Config fields used by isRoundRecent.

interface SolanaClock {
unixTimestamp: bigint;
}

Shape-compatible with solana-web3.js’s Clock sysvar.

interface BeaconResult {
round: bigint;
signature: Uint8Array;
unverifiedRandomness: string;
}

Alias for DrandBeacon, kept for future public-API extension.

interface VerifyOptions {
programId?: PublicKey;
computeUnits?: number;
}
class AleaError extends Error {
code: number;
constructor(code: number, message: string);
}

Thrown by both core functions when the on-chain program reverts or when the SDK can’t extract return data.

const ERRORS: Record<number, string>

Maps error codes the SDK knows about to human-readable descriptions. Used internally to produce AleaError.message:

CodeDescription
2001ConstraintHasOne: Signer is not the config authority (Anchor framework)
6000InvalidSignature: BLS signature verification failed
6001InvalidG1Point: Signature bytes are not a valid G1 point
6002RoundZero: Round number must be greater than 0
6003InvalidFieldElement: Field element out of valid range (reserved, unreachable)
6004NoSquareRoot: Square root does not exist (infrastructure)
6005InvalidG2Point: Public key bytes are not a valid G2 point (reserved, unreachable)
6006PairingError: alt_bn128_pairing syscall failed
6007WrongChainHash: chain_hash does not match evmnet (init-time only)
6008WrongPubkey: pubkey_g2 does not match evmnet (init-time only)
6009ReturnDataMissing: TS-SDK-only. Thrown when getTransaction().meta.returnData is absent after a successful send (indexer lag, stripped RPC response). The on-chain program doesn’t emit this code directly.

The v0.2.0 TS SDK’s ERRORS map ends at 6009. The on-chain program added 6010 InvalidGenesisTime, 6011 InvalidPeriod, and 6012 UnauthorizedInit in later program releases — all init-time guards that don’t fire during verify. A future SDK release will extend the map. See Errors reference for the canonical table.

A reverting verify from your consumer code most commonly throws 6000 (bad signature) or 6002 (zero round). 6004/6006 are infrastructure-level and shouldn’t appear for drand-issued beacons.

The SDK loads its Anchor IDL via readFileSync rather than an import ... with { type: "json" } assertion. This sidesteps the Node 18/22 syntax split entirely — no import-assertion version headaches across runtime targets.

Anchor 0.30.1 is incompatible with web3.js 1.98+ on the error-handling path: Anchor’s .rpc() loses custom error codes against modern web3.js. The SDK builds transactions via Anchor but sends them with raw connection.sendRawTransaction, then reads errors from getTransaction().meta.err. See Common Pitfalls §1.

skipPreflight: true is required for Alea verifies because the pairing syscall’s CU usage can outpace preflight simulation’s blockhash window under high-CU load. The SDK sets it by default.

Helius indexer lag is real. After sendRawTransaction returns, getTransaction() can return null for 2–5 seconds while the RPC indexer catches up. The SDK retries up to 15 times with 1-second backoff before giving up. See Common Pitfalls §4.

The SDK also prepends ComputeBudgetProgram.setComputeUnitLimit({ units: 900_000 }) to every verify transaction. Override the limit via the computeUnits option if you need headroom for additional on-chain work in the same tx.