npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

solana-faucet-sdk

v2.0.0

Published

TypeScript SDK for interacting with the [Zebec Solana Faucet](src/artifacts/zebec_solana_faucet.json) on-chain program. The faucet lets an admin configure one or more SPL token mints, refill their balances, and lets users request tokens subject to a coold

Readme

Solana Faucet SDK

TypeScript SDK for interacting with the Zebec Solana Faucet on-chain program. The faucet lets an admin configure one or more SPL token mints, refill their balances, and lets users request tokens subject to a cooldown.

  • Program ID: 73o1ngBeTrBcC4nSiJFEnA21GtbtLEcwUcF6YWsPritQ
  • Network: devnet

Installation

npm install solana-faucet-sdk
# or
yarn add solana-faucet-sdk

Peer dependencies you will typically already have in a Solana project: @solana/web3.js, @coral-xyz/anchor, @solana/spl-token, bn.js.

Quick start

import { Keypair, Connection } from "@solana/web3.js";
import { Wallet } from "@coral-xyz/anchor";
import { createAnchorProvider, FaucetService } from "solana-faucet-sdk";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const wallet = new Wallet(Keypair.generate()); // replace with your signing wallet
const provider = createAnchorProvider(connection, wallet);

const service = FaucetService.create(provider, "devnet");

// Read on-chain faucet configs
const configs = await service.getFaucetConfigs();
console.log(configs.admin.toBase58(), configs.mintMaps);

Every state-changing method returns a TransactionPayload from @zebec-network/solana-common. Call .execute() to sign and send:

const payload = await service.requestFromFaucet({
  requester: wallet.publicKey,
  requesterTokenAccount,
  faucetTokenAccount,
  tokenMint,
});
const signature = await payload.execute({ commitment: "confirmed" });

Concepts

The on-chain program tracks two accounts:

  • Faucet — a single PDA derived from the seed "faucet". It stores the admin, the cooldown period, and a list of MintMap entries (one per supported token mint, each with its own per-request amount and current balance).
  • UserRequest — a per-user PDA derived from ["user_request", requester]. It stores a list of MintRequestTimestamp entries — the last time the user pulled from each mint.

Mints are added/removed dynamically by the admin; the SDK does not hardcode any specific token.

Providers

The SDK accepts any Anchor Provider. Two helpers are included:

import {
  createAnchorProvider,
  createReadonlyProvider,
} from "solana-faucet-sdk";

// For signing transactions:
const provider = createAnchorProvider(connection, wallet /* AnchorWallet */);

// For read-only RPC calls (no signing):
const readonly = createReadonlyProvider(connection, walletAddress?);

AnchorWallet requires publicKey, signTransaction, and signAllTransactions.

PDA helpers

import { deriveFaucetPda, deriveUserRequestPda } from "solana-faucet-sdk";

const [faucet] = deriveFaucetPda(programId);
const [userRequest] = deriveUserRequestPda(requester, programId);

FaucetService

Construct via the static factory:

const service = FaucetService.create(provider, "devnet");
service.faucetProgramId; // PublicKey of the on-chain program

Every transactional method has two forms:

  • get<Name>Instruction(...) — returns a raw TransactionInstruction you can compose into your own transaction.
  • <name>(params) — returns a TransactionPayload ready to execute.

Admin: initialize the faucet

Creates the singleton Faucet PDA. Only callable once per program; the signer becomes the initial admin.

await service.initFaucet({
  admin: wallet.publicKey,
  faucetCooldownPeriod: 86_400, // seconds between requests per mint per user
});

Admin: add or update a mint

Registers a new SPL token mint with the faucet, or updates an existing mint's per-request amount. Creates the faucet's associated token account for the mint on first insert. amountPerRequest is a human-readable amount — the SDK fetches mint decimals and scales it for you.

await service.upsertMintMap({
  admin: wallet.publicKey,
  faucetTokenAccount, // faucet's ATA for tokenMint
  tokenMint,
  amountPerRequest: 100, // 100 tokens per request (scaled internally using on-chain mint decimals)
});

Admin: remove a mint

Drains the faucet's balance for that mint back to an admin token account and removes the mint from the faucet's mint map.

await service.removeMintMap({
  admin: wallet.publicKey,
  faucetTokenAccount,
  adminTokenAccount,
  tokenMint,
});

Anyone: refill a mint balance

Anyone holding the token can top up the faucet's balance for a registered mint. The amount here is a human-readable amount — the SDK fetches mint decimals and scales it for you.

await service.refillFaucet({
  refiller: wallet.publicKey,
  refillerTokenAccount,
  faucetTokenAccount,
  tokenMint,
  amount: 1_000, // 1,000 tokens (scaled internally using on-chain mint decimals)
});

User: request tokens

Transfers amountPerRequest of the given mint from the faucet to the requester, subject to the cooldown.

await service.requestFromFaucet({
  requester: wallet.publicKey,
  requesterTokenAccount,
  faucetTokenAccount,
  tokenMint,
});

If the cooldown for this mint has not elapsed, the program returns CooldownNotElapsed.

Admin: update faucet

Rotate admin and/or change the cooldown.

await service.updateFaucet({
  admin: wallet.publicKey,
  newAdmin,
  faucetCooldownPeriod: 3_600,
});

Read: faucet configs

const configs = await service.getFaucetConfigs();
// {
//   admin: PublicKey,
//   mintMaps: [
//     {
//       mintAddress: PublicKey,
//       amountPerRequest: string, // human-readable, scaled by on-chain mint decimals
//       balance: string,          // human-readable, scaled by on-chain mint decimals
//     },
//     ...
//   ],
//   faucetCooldownPeriod: string, // seconds, stringified
// }

Read: a user's cooldown state

const info = await service.getUserCooldownPeriod(requester);
// {
//   requester: PublicKey,
//   mintRequestTimestamps: [
//     { mintAddress: PublicKey, lastRequestTimestamp: string },
//     ...
//   ],
//   faucetCooldownPeriod: string,
// }

To check whether a user can request a given mint:

const now = Math.floor(Date.now() / 1000);
const entry = info.mintRequestTimestamps.find((t) =>
  t.mintAddress.equals(tokenMint),
);
const last = entry ? Number(entry.lastRequestTimestamp) : 0;
const elapsed = now - last;
const ready = elapsed >= Number(info.faucetCooldownPeriod);

Types

type Numeric = string | number;

type MintMap = {
  mintAddress: PublicKey;
  amountPerRequest: string; // human-readable, scaled by mint decimals (e.g. "100")
  balance: string; // human-readable, scaled by mint decimals (e.g. "1000")
};

type FaucetConfigsInfo = {
  admin: PublicKey;
  mintMaps: MintMap[];
  faucetCooldownPeriod: string; // u64 (seconds), stringified
};

type MintRequestTimestamp = {
  mintAddress: PublicKey;
  lastRequestTimestamp: string; // i64, stringified
};

type UserCooldownPeriod = {
  requester: PublicKey;
  mintRequestTimestamps: MintRequestTimestamp[];
  faucetCooldownPeriod: string;
};

u64/i64 raw values (cooldown periods, timestamps) are stringified to avoid JS number-precision loss — convert with BigInt(...) or new BN(...) as needed. Token amounts returned from getters (amountPerRequest, balance) are already formatted to UI units using each mint's on-chain decimals; if you need base units, scale by 10 ** decimals or call getMintDecimals + parseToken from @zebec-network/solana-common.

How-tos

Compose multiple instructions

The low-level get<Name>Instruction(...) getters take base units (BN) directly — scale by 10 ** decimals yourself when using them.

const ix1 = await service.getUpsertMintMapInstruction(
  admin,
  faucet,
  faucetTokenAccount,
  tokenMint,
  { amountPerRequest: new BN(100_000_000) }, // base units (e.g. 100 tokens at 6 decimals)
);
const ix2 = await service.getRefillFaucetInstruction(
  admin,
  adminTokenAccount,
  faucet,
  faucetTokenAccount,
  tokenMint,
  { amount: new BN(1_000_000_000) },
);

// Build your own transaction with [ix1, ix2]...

Derive ATAs

import { getAssociatedTokenAddressSync } from "@solana/spl-token";

const [faucet] = deriveFaucetPda(service.faucetProgramId);
const faucetTokenAccount = getAssociatedTokenAddressSync(
  tokenMint,
  faucet,
  true,
);
const userTokenAccount = getAssociatedTokenAddressSync(tokenMint, requester);

Read-only usage

const provider = createReadonlyProvider(connection);
const service = FaucetService.create(provider, "devnet");

const configs = await service.getFaucetConfigs(); // works without a signer

State-changing methods require a provider that can sign (e.g., AnchorProvider).

On-chain errors

| Code | Name | Meaning | | ----- | --------------------------- | ------------------------------------------------- | | 6000 | Unauthorized | Signer is not the admin. | | 6001 | CooldownNotElapsed | User must wait before requesting this mint again. | | 6002 | InsufficientFaucetBalance | Faucet doesn't have enough of the requested mint. | | 6003+ | (see IDL) | Additional validation errors. |

The errors are exposed via the returned TransactionPayload's error map; thrown messages will include the human-readable msg.

Development

yarn install
yarn build       # compile to ./dist
yarn test        # run e2e mocha tests (requires .env with RPC + keys)
yarn format      # biome format

Tests expect these env vars (see test/shared.ts):

  • DEVNET_RPC_URL
  • DEVNET_SECRET_KEYS — JSON array of base58-encoded secret keys

License

MIT