On-chain ownership lookup
Verify the tap, fetch the on-chain token, and gate on owner or any other field.
On-chain Token
fetchPhygitalToken returns an account whose data field matches this layout:
type PhygitalToken = { tokenType: PhygitalTokenType; // Controlled | Bearer owner: Address; lastSignCount: number; isLocked: boolean; publicKey: Secp256r1Pubkey; identifier: Secp256r1Pubkey; mint: Address; };
owner is the wallet that claimed the accessory. It starts as the default (zero) pubkey until the first transfer — gate on this even when the token has no mint. identifier is the chip binding field — distinct from the passkey that seeds the PDA.
mint is optional. After set_mint it holds an SPL mint pubkey; otherwise it stays the default (zero) pubkey. set_mint does not mint or transfer tokens — it only stores the mint address when you want a collection binding.
Example
import { randomUUID } from "crypto"; import { createSolanaRpc, type Address } from "@solana/kit"; import { startAuthentication, verifyResponse, findTokenPda, fetchPhygitalToken, parseSecp256r1Pubkey, } from "phygital-token-sdk"; const rpc = createSolanaRpc(process.env.SOLANA_RPC_URL!); const DEFAULT = "11111111111111111111111111111111" as Address; async function checkIn() { const message = randomUUID(); const response = await startAuthentication(message); const { isVerified, secp256r1PublicKey } = verifyResponse({ expectedMessage: message, response, }); if (!isVerified) throw new Error("Access denied"); const tokenPda = await findTokenPda(parseSecp256r1Pubkey(secp256r1PublicKey)); const token = await fetchPhygitalToken(rpc, tokenPda); if (token.data.owner === DEFAULT) { throw new Error("Access denied — not claimed"); } // Optional: if this token has a mint, also gate on mint // const MINT = "YourMint..." as Address; // if (token.data.mint !== DEFAULT && token.data.mint !== MINT) { // throw new Error("Access denied — wrong mint"); // } }