diff --git a/README.md b/README.md index 2ef20cb..20cf771 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Traits are specific attributes of provider accounts you can verify. Examples: **Coinbase:** - `coinbase_one_active:eq:true` - Active Coinbase One subscription -- `coinbase_one_billed:eq:true` - User has been billed for Coinbase One +- `coinbase_one_billed:eq:true` - User has an active Coinbase One subscription **Instagram:** - `followers_count:gte:5000` - 5000+ followers @@ -307,12 +307,12 @@ Users can delete their own airdrop claim: For more detailed information, see the `/docs` folder: -- **[Getting Started](/docs/index.md)** - Quick overview and contact information -- **[Core Concepts](/docs/core-concepts.md)** - Understanding providers, traits, tokens, and Sybil resistance -- **[Integration Guide](/docs/integration.md)** - Complete implementation walkthrough with code examples -- **[Trait Catalog](/docs/traits.md)** - All available traits for X, Coinbase, Instagram, and TikTok -- **[API Reference](/docs/api.md)** - Complete API endpoint documentation -- **[Security & Privacy](/docs/security.md)** - Security best practices and data handling +- **[Getting Started](docs/index.md)** - Quick overview and contact information +- **[Core Concepts](docs/core-concepts.md)** - Understanding providers, traits, tokens, and Sybil resistance +- **[Integration Guide](docs/integration.md)** - Complete implementation walkthrough with code examples +- **[Trait Catalog](docs/traits.md)** - All available traits for X, Coinbase, Instagram, and TikTok +- **[API Reference](docs/api.md)** - Complete API endpoint documentation +- **[Security & Privacy](docs/security.md)** - Security best practices and data handling --- diff --git a/docs/core-concepts.md b/docs/core-concepts.md index d3072db..c4c06bf 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -63,7 +63,7 @@ The action is returned in the API response: **Use Cases:** - **Multiple campaigns**: Run separate airdrops without interference - **Feature gating**: Different tokens for different premium features -- **Time-based events**: New action per event (e.g., `raffle_jan_2025`, `raffle_feb_2025`) +- **Time-based events**: New action per event (e.g., `raffle_jan_2026`, `raffle_feb_2026`) ### Choosing Action Names diff --git a/docs/integration.md b/docs/integration.md index 1d77de1..b759f9d 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -14,7 +14,7 @@ Even if a wallet has few transactions, Base Verify reveals if the user is high-v **Example Use Cases:** - Token-gated airdrops or daily rewards -- Exclusive content access (e.g. creator coins) +- Exclusive content access (e.g., creator coins) - Identity-based rewards and loyalty programs --- @@ -256,7 +256,7 @@ export default async function handler(req, res) { }); } - // Now safe to verify signature with Base Verify API + // Now safe to check signature with Base Verify API const response = await fetch('https://verify.base.dev/v1/base_verify_token', { method: 'POST', headers: { diff --git a/docs/security.md b/docs/security.md index 7472e99..bbc797e 100644 --- a/docs/security.md +++ b/docs/security.md @@ -85,7 +85,7 @@ The user signs this message, proving they control the wallet and agree to check Users can delete their verifications at any time: - Removes all stored provider data - Invalidates future token generation -- Your app's stored tokens become meaningless (user can't re-verify with same account) +- Your app's stored tokens become completely invalid (user can't re-verify with same account) --- diff --git a/env.example b/env.example index ffef3b0..d2b8ad4 100644 --- a/env.example +++ b/env.example @@ -12,7 +12,7 @@ NEXT_PUBLIC_ONCHAINKIT_API_KEY="your_onchainkit_api_key_here" NEXT_PUBLIC_BASE_VERIFY_API_URL="https://verify.base.dev/v1" NEXT_PUBLIC_BASE_VERIFY_WEBAPP_URL="https://verify.base.dev" BASE_VERIFY_SECRET_KEY="sk_live_your_secret_key_here_64_character_hex_string" -NEXT_PUBLIC_BASE_VERIFY_PUBLISHER_KEY="pk_live_your_publisher_key_here_64_character_hex_string" +NEXT_PUBLIC_BASE_VERIFY_PUBLIC_KEY="pk_live_your_publisher_key_here_64_character_hex_string" # Your application's URL NEXT_PUBLIC_APP_URL="https://baseverifydemo.com" diff --git a/lib/config.ts b/lib/config.ts index d112421..c991fed 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -10,7 +10,7 @@ export const config = { baseVerifySecretKey: process.env.BASE_VERIFY_SECRET_KEY, // Publisher key for client-side API calls (public, requires origin validation) - baseVerifyPublisherKey: process.env.NEXT_PUBLIC_BASE_VERIFY_PUBLISHER_KEY, + baseVerifyPublicKey: process.env.NEXT_PUBLIC_BASE_VERIFY_PUBLIC_KEY, // Current app URL for redirects appUrl: process.env.NEXT_PUBLIC_APP_URL || 'https://baseverifydemo.com', diff --git a/lib/signature-generator.ts b/lib/signature-generator.ts index 59a76eb..f23dbf7 100644 --- a/lib/signature-generator.ts +++ b/lib/signature-generator.ts @@ -20,7 +20,7 @@ function buildSIWEMessage(options: SIWEOptions): { message: string; nonce: strin domain = new URL(config.appUrl).hostname, address, uri = config.appUrl, - chainId = 8453, // Base chain + chainId = config.claimChainId || 84532, action, provider, traits = {}, diff --git a/lib/trait-validator.ts b/lib/trait-validator.ts index 2f03871..ad4a603 100644 --- a/lib/trait-validator.ts +++ b/lib/trait-validator.ts @@ -36,13 +36,13 @@ function parseTraitsFromMessage(message: string): { provider: string; traits: Tr .map(line => line.replace(/^- /, '').trim()) .filter(line => line.length > 0); - // Find provider URN - const providerUrn = resources.find(r => r.match(/^urn:verify:provider:[^:]+$/)); - if (!providerUrn) { + // Find the first trait URN to extract the provider + const traitUrn = resources.find(r => r.startsWith('urn:verify:provider:')); + if (!traitUrn) { return null; } - const providerMatch = providerUrn.match(/^urn:verify:provider:([^:]+)$/); + const providerMatch = traitUrn.match(/^urn:verify:provider:([^:]+):/); if (!providerMatch) { return null; } @@ -50,6 +50,7 @@ function parseTraitsFromMessage(message: string): { provider: string; traits: Tr const provider = providerMatch[1]; const traits: TraitRequirement = {}; + // Parse trait URNs for this provider const traitPattern = new RegExp(`^urn:verify:provider:${provider}:([^:]+):([^:]+):(.+)$`); diff --git a/pages/api/verify-token.ts b/pages/api/verify-token.ts index 008b770..7dd6823 100644 --- a/pages/api/verify-token.ts +++ b/pages/api/verify-token.ts @@ -103,7 +103,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) try { // Extract user data from verification response const addressMatch = message.match(/0x[a-fA-F0-9]{40}/); - const walletAddress = addressMatch ? addressMatch[0] : ''; + const walletAddress = addressMatch ? addressMatch[0].toLowerCase() : ''; console.log('Extracted wallet address:', walletAddress); diff --git a/pages/index.tsx b/pages/index.tsx index 7e0ea92..fd1c43d 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -62,7 +62,7 @@ export default function Home({ initialUsers, error }: Props) { if (success === 'true' && address && isConnected && !isVerifying && !verificationResult) { console.log('Auto-verifying after successful redirect...'); setIsAutoVerification(true); - handleVerify(true); + handleVerify(true, router.query.code as string, router.query.state as string); // Clean up the URL to remove the success parameter const { success: _, code: __, state: ___, ...cleanQuery } = router.query; @@ -219,7 +219,7 @@ export default function Home({ initialUsers, error }: Props) { setShowVerifyModal(true); } - const handleVerify = async (isAutoVerifyFromSuccess = false) => { + const handleVerify = async (isAutoVerifyFromSuccess = false, passedCode?: string, passedState?: string) => { if (!address || !signMessage) { setVerificationError('Please connect your wallet to claim') return @@ -262,7 +262,7 @@ export default function Home({ initialUsers, error }: Props) { verifySignatureCache.set(signature); } - const { code, state } = router.query; + const code = passedCode || router.query.code; const state = passedState || router.query.state; const storedCodeVerifier = sessionStorage.getItem('pkce_code_verifier'); const storedState = sessionStorage.getItem('pkce_state'); diff --git a/pages/onchain.tsx b/pages/onchain.tsx index b2ca07c..d1bd4f6 100644 --- a/pages/onchain.tsx +++ b/pages/onchain.tsx @@ -56,7 +56,7 @@ type OnchainToken = { signature: string } -const ACTION = 'my_app_airdrop_2026' +const ACTION = 'claim_demo_x_airdrop' // Read-only client for the dedup pre-check. const publicClient = createPublicClient({ chain: baseSepolia, transport: http() }) @@ -96,8 +96,7 @@ export default function OnchainPage() { if (cachedSignature) { if (cachedSignature.address.toLowerCase() !== address.toLowerCase()) { verifySignatureCache.clear() - } else if (!cachedSignature.message.includes('urn:verify:provider:coinbase') || - !cachedSignature.message.includes(`urn:verify:action:${ACTION}`)) { + } else if (!cachedSignature.message.includes(`urn:verify:action:${ACTION}`)) { verifySignatureCache.clear() } }