Verify Seeker device ownership by checking for the Seeker Genesis Token (SGT) with Sign-in-with-Solana and server-side token verification. Use when gating content or rewards to Seeker owners, verifying a user holds an SGT, adding anti-Sybil checks to a Solana mobile app, or implementing one-claim-per-device logic.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
seeker-genesis-token
description
Verify Seeker device ownership by checking for the Seeker Genesis Token (SGT) with Sign-in-with-Solana and server-side token verification. Use when gating content or rewards to Seeker owners, verifying a user holds an SGT, adding anti-Sybil checks to a Solana mobile app, or implementing one-claim-per-device logic.
Seeker Genesis Token verification
The Seeker Genesis Token (SGT) is a Token-2022 NFT minted once per Seeker device. Holding one
is evidence of owning a Seeker, which makes it useful for gating rewards and for
one-claim-per-device logic.
Verification has two halves, and both are required:
Prove the user controls the wallet — Sign-in-with-Solana (SIWS)
Prove that wallet holds an SGT — inspect the wallet's Token-2022 mints
Doing only the second means anyone can submit a real Seeker owner's public address and pass.
Doing only the first proves wallet control but says nothing about a device.
This must run on a server
Never decide entitlement client-side. A client can be patched, so a client-side hasSGT
boolean is worth nothing. The client's job is to collect a signature; the server verifies it
and owns the result.
Requirements:
A backend you control that can make Solana mainnet RPC calls
Storage for nonces and claim records
An RPC endpoint. A paid provider helps, since the check may enumerate many token accounts —
keep that key server-side only, never in an EXPO_PUBLIC_* variable
Prerequisites
A working wallet connection. If the app has none, use the solana-mobile-wallet skill first;
this skill assumes useMobileWallet() is available.
Testing needs a physical Seeker device — an emulator cannot hold an SGT. Plan for a code path
you can exercise without one, such as a server-side allowlist in development.
Step 1: issue a nonce from the server
The nonce must be server-generated, single-use, and short-lived. A client-generated or
reusable nonce makes the signature replayable, which defeats the exercise.
chainId is pinned to solana:mainnet deliberately rather than taken from the hook's chain:
SGTs exist only on mainnet, so a signature scoped to devnet proves nothing about a device.
This is the fully-specified payload, not the short signIn form — nonce, domain, and
version are what make the signature non-replayable and bind it to your app. The
solana-mobile-wallet skill covers both forms and when each is appropriate.
See references/sgt-verification.md for the full
implementation. It confirms three properties of a Token-2022 mint — mint authority, metadata
pointer, and token group membership — and all three must match.
Step 5: combine the checks correctly
This is where the subtle bug lives. The address whose SGT you check must be the address that
signed, read out of the verified payload — not an address the client sent alongside it:
asyncfunctionverifySeekerUser({ payload, result }) {
// 1. The nonce must be one we issued, unused, and unexpired.const record = await store.get(payload.nonce)
if (!record || record.used) thrownewError('Invalid or reused nonce.')
await store.markUsed(payload.nonce)
// 2. The signature must be valid for that payload.if (!verifySiws(payload, result)) thrownewError('Invalid signature.')
// 3. The domain must be ours, or a signature farmed by another site would pass.if (payload.domain !== 'yourdapp.com') thrownewError('Wrong domain.')
// 4. Check the SGT against the *signed* address only.const { hasSGT, mintAddress } = awaitcheckWalletForSGT(payload.address)
return { address: payload.address, hasSGT, mintAddress }
}
Taking the address from anywhere other than the verified payload lets a caller submit a real
Seeker owner's address with their own signature and be granted access.
Anti-Sybil: one claim per device
An SGT is per-device, so the mint address is the device identity. Store that, not the
wallet address — a wallet can hold a different SGT later, and a device's SGT can move between
wallets.
const { hasSGT, mintAddress } = awaitverifySeekerUser({ payload, result })
if (!hasSGT) thrownewError('No Seeker Genesis Token found.')
if (await claims.exists(mintAddress)) thrownewError('This device has already claimed.')
await claims.insert({ claimedAt: newDate(), mintAddress })
Have checkWalletForSGT return the mint address rather than a bare boolean — see the end of
the reference file.