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.
npm install @alea-drand/sdkCurrent version: 0.2.0. Apache 2.0. ESM-only. Peer dependencies:
npm install @solana/web3.js@^1.95 @coral-xyz/anchor@^0.30.1This page is guide first, full reference second. The reference at the bottom covers every export from src/index.ts.
Integration guide
Section titled “Integration guide”The two main entry points
Section titled “The two main entry points”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-chainFetches 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).
Browser wallets
Section titled “Browser wallets”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.
Error handling
Section titled “Error handling”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.
Cluster selection
Section titled “Cluster selection”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.
SSR and bundlers
Section titled “SSR and bundlers”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.
Full API reference
Section titled “Full API reference”Core functions
Section titled “Core functions”getVerifiedRandomness
Section titled “getVerifiedRandomness”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— aweb3.jsConnection to the target clustersigner— aKeypairor wallet-adapterWalletthat pays the tx feeprogramId— override the Alea program ID. Defaults toDEVNET_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 to900_000.
verifyDrandBeacon
Section titled “verifyDrandBeacon”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 asbigintsignature— 64-byte G1 point, uncompressed(x || y)big-endian
Drand helpers
Section titled “Drand helpers”fetchBeacon
Section titled “fetchBeacon”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.
getCurrentRound
Section titled “getCurrentRound”function getCurrentRound(): bigintComputes the current drand round number from Date.now(), genesis time, and period. Pure function, no I/O. Year-2086 safe (returns bigint).
getRoundAt
Section titled “getRoundAt”function getRoundAt(timestamp: bigint): bigintReturns the drand round number active at a given Unix-seconds timestamp. Useful for anchoring a commit to a future round.
isRoundRecent
Section titled “isRoundRecent”function isRoundRecent( round: bigint, config: { genesisTime: bigint; period: bigint }, clock: { unixTimestamp: bigint }, maxAgeSeconds: bigint,): booleanPure, 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.
Instruction helpers
Section titled “Instruction helpers”createVerifyInstruction
Section titled “createVerifyInstruction”function createVerifyInstruction(options: { round: bigint; signature: Uint8Array; programId?: PublicKey;}): TransactionInstructionLow-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 expectsUse 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.
getConfigAddress
Section titled “getConfigAddress”function getConfigAddress(programId?: PublicKey): PublicKeyDerives the Alea Config PDA from seeds [Buffer.from("config")] for a given program ID. Defaults to DEVNET_PROGRAM_ID.
Constants
Section titled “Constants”| Name | Value | Purpose |
|---|---|---|
DRAND_CHAIN_HASH | "04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3" | evmnet chain hash — explicit chain pin for drand requests |
DRAND_GENESIS_TIME | 1727521075 | Unix seconds for evmnet genesis (2024-09-28T13:37:55Z) |
DRAND_PERIOD | 3 | Beacon cadence in seconds |
DRAND_ENDPOINTS | readonly string[] (5 URLs) | Fallback endpoint list used by fetchBeacon |
DEVNET_PROGRAM_ID | PublicKey("ALEAydzHd4cN2EWcdHKp4hehAE4B88b16gqVtVqsck2U") | Devnet program ID (same vanity used for mainnet) |
MAINNET_PROGRAM_ID | PublicKey (throwing Proxy) | Placeholder until mainnet release. Accessing any property throws until the SDK ships with the live mainnet ID set. |
DrandBeacon
Section titled “DrandBeacon”interface DrandBeacon { round: bigint; signature: Uint8Array; // 64 bytes, uncompressed G1 unverifiedRandomness: string; // hex — NOT on-chain verified}DrandConfig
Section titled “DrandConfig”interface DrandConfig { genesisTime: bigint; period: bigint;}Shape-compatible with the subset of on-chain Config fields used by isRoundRecent.
SolanaClock
Section titled “SolanaClock”interface SolanaClock { unixTimestamp: bigint;}Shape-compatible with solana-web3.js’s Clock sysvar.
BeaconResult
Section titled “BeaconResult”interface BeaconResult { round: bigint; signature: Uint8Array; unverifiedRandomness: string;}Alias for DrandBeacon, kept for future public-API extension.
VerifyOptions
Section titled “VerifyOptions”interface VerifyOptions { programId?: PublicKey; computeUnits?: number;}Errors
Section titled “Errors”AleaError
Section titled “AleaError”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.
ERRORS
Section titled “ERRORS”const ERRORS: Record<number, string>Maps error codes the SDK knows about to human-readable descriptions. Used internally to produce AleaError.message:
| Code | Description |
|---|---|
| 2001 | ConstraintHasOne: Signer is not the config authority (Anchor framework) |
| 6000 | InvalidSignature: BLS signature verification failed |
| 6001 | InvalidG1Point: Signature bytes are not a valid G1 point |
| 6002 | RoundZero: Round number must be greater than 0 |
| 6003 | InvalidFieldElement: Field element out of valid range (reserved, unreachable) |
| 6004 | NoSquareRoot: Square root does not exist (infrastructure) |
| 6005 | InvalidG2Point: Public key bytes are not a valid G2 point (reserved, unreachable) |
| 6006 | PairingError: alt_bn128_pairing syscall failed |
| 6007 | WrongChainHash: chain_hash does not match evmnet (init-time only) |
| 6008 | WrongPubkey: pubkey_g2 does not match evmnet (init-time only) |
| 6009 | ReturnDataMissing: 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.
What the SDK handles internally
Section titled “What the SDK handles internally”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.
Related
Section titled “Related”- Rust SDK — on-chain CPI side
- CPI Integration Guide — full lottery walkthrough
- Common Pitfalls — integration footguns
- Errors reference — Cmd+F lookup