MVP for a Base-chain NFT social identity loop:
- X OAuth login
- Base wallet signature verification
- Backend-only NFT score calculation
- Dashboard and public leaderboard
- Chrome Manifest V3 extension that displays score on
x.comprofiles - Optional agent wallet slot verified through the agent/ACP flow
npm install
cp .env.example .env.local
npm run devFill .env.local with X OAuth, Supabase, NFT provider, Base RPC, and collection settings. Do not use XAMPP.
Required Supabase setup:
- Create a Supabase project.
- Run supabase/schema.sql in the SQL editor, or apply the migrations in supabase/migrations.
- Add
NEXT_PUBLIC_SUPABASE_URLandSUPABASE_SERVICE_ROLE_KEYto.env.local.
Every X account maps to one profile with two optional wallet slots:
human: the main holder wallet. Users connect, sign, and disconnect it directly from the dashboard.agent: an optional plus slot for AI agents. It is verified through the agent/ACP flow, not the browser wallet connect endpoint. The dashboard can disconnect it, but cannot connect/verify it from the web.
NFTs from both slots are merged into one combined OG score.
Agents verify the agent wallet slot through the ACP CLI or console. scripts/smoke-acp-agent-wallet.ts is a smoke test that:
- resolves the agent wallet from the ACP CLI,
- signs the same OG-Block verification message,
- upserts the
agentwallet row, - recalculates the combined score.
The agent page at /agent-guide is a public, crawlable instruction block for any agent. Virtual IO branding only appears in the X extension when the agent wallet holds the AgentIdentity NFT.
Edit lib/config/score-rules.ts to change:
- Target NFT contract
- Early token threshold
- Points for project NFT, additional NFTs, rare traits, and early token IDs
- Rare trait definitions
NFT fetching is behind lib/nft/providers.ts. NFT_PROVIDER=mock is useful for local UI testing. alchemy fetches real Base NFT holdings for a wallet. Set TARGET_NFT_CONTRACT_ADDRESS=all to score every NFT in the wallet, or set it to one contract address to score only that collection. rpc is implemented for enumerable contracts only; simplehash and reservoir can be added behind the same interface.
Scam/spam filtering:
NFT_EXCLUDE_SPAM=trueasks Alchemy to remove NFTs it classifies as spam.NFT_REQUIRE_VERIFIED_CONTRACT=trueonly scores NFT contracts with verified source code on Base through Etherscan API v2 (chainid=8453). This is enabled by default because wallet-wide scoring can otherwise include scam airdrops.BASESCAN_API_KEYis required whenNFT_REQUIRE_VERIFIED_CONTRACT=true; use a BaseScan/Etherscan API v2 key. Without it, contracts are treated as unverified.- When verified-contract filtering is enabled, verified source code takes priority over Alchemy spam flags to avoid false positives on legitimate contracts.
NFT_MIN_FLOOR_PRICE_ETH=0.001only scores collections with an available floor at or above that ETH value.
Blocklist (stored in Supabase):
- Phishing and scam sources are blocked through the
nft_blocklisttable (kind=contractorcreator,value= lowercase address). - supabase/schema.sql and the migration seed the known
Fake_Phishing3515710contract/creator. - The provider reads
nft_blockliston each refresh and also blocks any creator name containingphishing. - Env overrides are still supported for local testing:
NFT_BLOCKLIST_CONTRACTS: comma-separated contract addressesNFT_BLOCKLIST_CREATORS: comma-separated creator addresses
GET /api/mePOST /api/wallet/connect(human slot only from the dashboard)POST /api/wallet/disconnect(human or agent slot)POST /api/score/refreshGET /api/profile/:handleGET /api/leaderboard
Load the extension directory in Chrome developer mode.
- Open
chrome://extensions. - Enable Developer mode.
- Load unpacked extension from
extension. - Open extension options and set the backend URL if it is not
http://localhost:3000.
The extension only calls GET /api/profile/:handle and injects public score data. It does not contain secrets or calculate scores. The badge shows the OG score, rank, and a Virtual IO pill when the agent wallet holds the AgentIdentity NFT.
- Push the repo to GitHub.
- Import into Vercel.
- Set the same env vars in Vercel.
- Set
NEXTAUTH_URLandPUBLIC_APP_URLto the deployed URL. - Configure X OAuth callback for
/api/auth/callback/twitter.
Vercel Cron calls GET /api/cron/refresh-scores once per day using vercel.json. The schedule is 0 18 * * *, which runs at 01:00 WIB because Vercel cron schedules use UTC.
Set these env vars in Vercel:
CRON_SECRET: long random secret used to authorize the cron endpoint.CRON_REFRESH_LIMIT=50: maximum latest verified wallets refreshed per cron run.