S

seeker-genesis-token

by @solana-mobilev
4.5(120)

このスキルは、Seeker Genesis Token (SGT) の確認と Sign-in-with-Solana およびサーバー側検証を組み合わせて、Solana Seeker デバイスの所有権を検証します。コンテンツのゲーティング、報酬の付与、Sybil 攻撃対策、デバイスごとの1回限りのクレームロジックに使用でき、クライアント側の偽装やリプレイ攻撃を防ぎます。サーバー上で署名とトークン保持の両方を確認するため、安全で信頼性の高いデバイス連携アクセス制御を実現します。

solananft-verificationtoken-gatingdevice-ownershipGitHub
インストール方法
npx skills add https://github.com/solana-mobile/solana-mobile-skills --skill seeker-genesis-token
compare_arrows

Before / After 効果比較

1 组
使用前

開発者はSIWS署名検証、Token-2022アカウント列挙、リプレイ防止ロジックをゼロから実装する必要があり、アドレス結合やnonce管理のバグが発生しやすく、デバッグに多くの時間を費やし、セキュリティリスクも高くなっていました。

使用後

このスキルは署名とSGTチェックを組み合わせた完全なサーバー側検証フローを提供し、セキュリティホールと開発時間を大幅に削減します。数行のコードで安全に統合できます。

SKILL.md

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

ユーザーレビュー (0)

レビューを書く

効果
使いやすさ
ドキュメント
互換性

レビューなし

統計データ

インストール数10.3K
評価4.5 / 5.0
バージョン
更新日2026年10月23日
比較事例1 件

ユーザー評価

4.5(120)
5
37%
4
43%
3
13%
2
5%
1
2%

この Skill を評価

0.0

対応プラットフォーム

🤖claude-code

タイムライン

作成2026年8月30日
最終更新2026年10月23日
🎁 Agent 知識カード
アンケート