Pre-synced whitelist
Build a Set<secp256r1PublicKey> on a schedule. At check-in, verify the tap and check membership — no RPC during the tap.
Needs a Helius RPC (searchAssets) in addition to your Solana RPC. Include every token owned by those wallets — tokens without a mint still count.
Example
import { randomUUID } from "crypto"; import { createSolanaRpc, type Address } from "@solana/kit"; import { startAuthentication, verifyResponse, fetchAllTokensFromOwner, } from "phygital-token-sdk"; const HELIUS_URL = `https://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}`; const rpc = createSolanaRpc(HELIUS_URL); const COLLECTION = "YourCollectionMint..." as Address; let whitelist = new Set<string>(); async function rebuildWhitelist() { const owners = new Set<string>(); let page = 1; // Pass 1: paginate searchAssets, collect unique owners while (true) { const { result } = await fetch(HELIUS_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: "1", method: "searchAssets", params: { grouping: ["collection", COLLECTION], tokenType: "fungible", page, limit: 1000, }, }), }).then((r) => r.json()); for (const item of result.items ?? []) { const owner = item.ownership?.owner as string | undefined; if (owner) owners.add(owner); } if (!result.items?.length || result.items.length < 1000) break; page += 1; } // Pass 2: one fetchAllTokensFromOwner per unique owner const next = new Set<string>(); for (const owner of owners) { const tokens = await fetchAllTokensFromOwner(owner as Address, rpc); for (const token of tokens) { next.add(Buffer.from(token.publicKey[0]).toString("base64url")); } } whitelist = next; } async function checkIn() { const message = randomUUID(); const response = await startAuthentication(message); const { isVerified, secp256r1PublicKey } = verifyResponse({ expectedMessage: message, response, }); if (!isVerified || !whitelist.has(secp256r1PublicKey)) { throw new Error("Access denied"); } }
Run rebuildWhitelist on a schedule (e.g. every 15 minutes). Check-in stays fast; eligibility may lag by one sync interval.