skillZs
LIVE SKILL TAGS
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
REAL INSTALL DATA
← back to all skills
solana-mobile/solana-mobile-skills1.2k installs

seeker-genesis-token

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.

How do I install this agent skill?

npx skills add https://github.com/solana-mobile/solana-mobile-skills --skill seeker-genesis-token
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The skill provides a secure template for verifying Seeker device ownership using Solana blockchain tokens. It correctly implements industry-standard security practices, including server-side signature verification and anti-replay nonces.

  • Socketpass

    No alerts

  • Snykwarn

    Risk: MEDIUM · 2 issues

What does this agent skill do?

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:

  1. Prove the user controls the wallet — Sign-in-with-Solana (SIWS)
  2. 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.

// POST /api/siws/nonce
const nonce = crypto.randomBytes(16).toString('hex')
await store.put(nonce, { issuedAt: Date.now(), used: false }, { ttlSeconds: 300 })
return { nonce }

Step 2: sign in on the client

signIn from the wallet hook authorizes and proves ownership in a single prompt:

import { useMobileWallet } from '@wallet-ui/react-native-kit'

const { signIn } = useMobileWallet()

const output = await signIn({
  address: account.address.toString(),
  chainId: 'solana:mainnet',
  domain: 'yourdapp.com',
  issuedAt: new Date().toISOString(),
  nonce, // from step 1
  statement: 'Sign in to verify Seeker ownership',
  uri: 'https://yourdapp.com',
  version: '1',
})

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.

Post output to the server.

Step 3: verify the signature on the server

npm install @solana/wallet-standard-util
import { verifySignIn } from '@solana/wallet-standard-util'

function verifySiws(payload, result) {
  return verifySignIn(payload, {
    account: { ...result.account, publicKey: new Uint8Array(result.account.publicKey) },
    signature: new Uint8Array(result.signature),
    signedMessage: new Uint8Array(result.signedMessage),
  })
}

Step 4: check the wallet for an SGT

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:

async function verifySeekerUser({ payload, result }) {
  // 1. The nonce must be one we issued, unused, and unexpired.
  const record = await store.get(payload.nonce)
  if (!record || record.used) throw new Error('Invalid or reused nonce.')
  await store.markUsed(payload.nonce)

  // 2. The signature must be valid for that payload.
  if (!verifySiws(payload, result)) throw new Error('Invalid signature.')

  // 3. The domain must be ours, or a signature farmed by another site would pass.
  if (payload.domain !== 'yourdapp.com') throw new Error('Wrong domain.')

  // 4. Check the SGT against the *signed* address only.
  const { hasSGT, mintAddress } = await checkWalletForSGT(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 } = await verifySeekerUser({ payload, result })
if (!hasSGT) throw new Error('No Seeker Genesis Token found.')

if (await claims.exists(mintAddress)) throw new Error('This device has already claimed.')
await claims.insert({ claimedAt: new Date(), mintAddress })

Have checkWalletForSGT return the mint address rather than a bare boolean — see the end of the reference file.

Reference material

Related skills

  • solana-mobile-wallet — wallet connection and the signIn payload builder
  • seeker-domains.skr domain resolution, which Seeker users have by default

Links

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/solana-mobile/solana-mobile-skills/seeker-genesis-token">View seeker-genesis-token on skillZs</a>