Skip to Content
Welcome to RitoSwap's documentation!
DAppAPIGate Access

Gate Access API

The Gate Access API provides authenticated access to exclusive content for Colored Key NFT holders. This endpoint implements two alternative authentication system supporting both modern SIWE (Sign-In with Ethereum) and legacy signature verification, with an optional (but active by default) middleware layer providing additional domain validation security.

Overview

While the Token Gate Verification API (covered previously) handles one-time access with email notifications, the Gate Access API focuses on real-time content delivery. Think of it as the difference between unlocking a door (token gate verification) versus actually entering and experiencing what’s inside (gate access). This API serves the actual gated content - whether that’s exclusive music, special messages, or premium features - directly to verified token holders.

The Architecture of Trust

The Gate Access system implements defense in depth through multiple layers of verification:

  1. Middleware Pre-validation (Optional) - Domain consistency checks before the request reaches the API
  2. Input Validation - Ensures request data is properly formatted and complete
  3. Rate Limiting - Prevents abuse and ensures fair access
  4. Signature Verification - Cryptographic proof of wallet ownership
  5. Blockchain Verification - Real-time confirmation of token ownership
  6. Database Checks - Ensures tokens haven’t been previously used (if applicable)
  7. Content Delivery - Secure transmission of exclusive content

This layered approach means that even if one security measure is bypassed, others remain in place to protect the gated content.

Understanding Middleware Redundancy

The middleware layer represents an interesting architectural decision: it provides security checks that the API also performs. This redundancy isn’t accidental - it’s a deliberate design choice that offers several benefits:

  • Performance - Middleware can reject invalid requests before they consume API resources
  • Flexibility - The API remains fully functional even if middleware is disabled
  • Progressive Enhancement - Systems can start with just the API and add middleware later
  • Debugging - Issues can be isolated to either the middleware or API layer

Think of it like having both a security guard at the building entrance (middleware) and keycard access at each office door (API). Either can work alone, but together they provide better security and user experience.

Nonce Generation Endpoint

The Gate Access API works in conjunction with the Nonce endpoint for SIWE authentication:

PropertyValue
URL/api/nonce
MethodGET
Success Response{ nonce: string }
Nonce TTL5 minutes (configurable)
Rate Limit10 requests per 60 seconds
Rate Limit HeadersX-RateLimit-Limit, X-RateLimit-Remaining, Retry-After

The nonce endpoint generates cryptographically secure nonces for SIWE authentication. Each nonce is unique per user identifier and expires after the configured TTL.

Nonce Endpoint Responses

Success Response (200 OK):

{ "nonce": "k8Jd93kdo0Sdk39dkD9dk3mdk93kd9Dk" }

SIWE Not Enabled (501 Not Implemented):

{ "error": "SIWE not enabled" }

Rate Limit Exceeded (429 Too Many Requests):

{ "error": "Too many requests", "limit": 10, "remaining": 0, "retryAfter": 45 }

With HTTP headers:

X-RateLimit-Limit: 10 X-RateLimit-Remaining: 0 Retry-After: 45

Internal Server Error (500):

{ "error": "Failed to generate nonce" }

This error occurs when the nonce generation process fails unexpectedly.

Endpoint Details

PropertyValue
URL/api/gate-access
MethodPOST
Content-Typeapplication/json
MiddlewareOptional domain validation layer
AuthenticationSIWE or Legacy signature verification

Network-Aware Database Operations

This API leverages RitoSwap’s sophisticated network routing infrastructure to ensure all operations target the correct blockchain network and corresponding database table. The system automatically handles network detection and routing, allowing the API to work seamlessly across Ethereum mainnet, Sepolia testnet, and local RitoNet development networks.

Automatic Network Routing

The API uses two key utilities from the prismaNetworkUtils module to handle multichain operations:

Database Operations via getTokenModel()
This function returns a unified interface that automatically routes database queries to the correct network-specific table:

import { getTokenModel } from '@/app/lib/prisma/prismaNetworkUtils' const tokenModel = getTokenModel() // Automatically queries token_ethereum, token_sepolia, or token_ritonet const token = await tokenModel.findUnique({ where: { tokenId } })

Blockchain Configuration via getChainConfig()
This function provides the correct RPC endpoints and chain metadata for the active network:

import { getChainConfig } from '@/app/lib/prisma/prismaNetworkUtils' import { createPublicClient, http } from 'viem' const chainConfig = getChainConfig() const publicClient = createPublicClient({ chain: chainConfig.chain, transport: http(chainConfig.transport) })

Network Detection Priority

The system determines the active network through environment variables in the following order of precedence:

  1. RitoNet (Local Development) - Active when NEXT_PUBLIC_RITONET=true
  2. Sepolia (Testnet) - Active when NEXT_PUBLIC_SEPOLIA=true
  3. Ethereum (Mainnet) - Default when no network flags are set

This precedence ensures development and testing environments take priority, preventing accidental mainnet operations during development.

Cross-Network Isolation

Each blockchain network maintains its own isolated database table with identical structure:

  • Ethereum Mainnettoken_ethereum table
  • Sepolia Testnettoken_sepolia table
  • RitoNet Localtoken_ritonet table

This isolation prevents cross-network data conflicts while enabling network-specific optimizations and simplified debugging. The same token ID can exist on different networks representing entirely different assets, and this architecture ensures they never interfere with each other.

The network routing happens transparently to the API logic. Developers write network-agnostic code while the infrastructure handles proper routing behind the scenes. For detailed information about the network utilities, see the Network-Aware Database Utils documentation.

Request Format

The Gate Access API provides authenticated access to exclusive content for Colored Key NFT holders. It supports two authentication modes, modern SIWE (Sign-In with Ethereum) or legacy signature verification, and includes a middleware that runs on every POST to /api/gate-access, enforcing domain validation only when SIWE is enabled and no hardcoded domain is set.

SIWE Authentication Request

interface SiweGateAccessRequest { address: string; // Ethereum address (0x...) signature: string; // SIWE message signature tokenId: number; // Token ID for access message: string; // Complete SIWE message nonce: string; // Nonce from /api/nonce endpoint }

Legacy Authentication Request

interface LegacyGateAccessRequest { address: string; // Ethereum address (0x...) signature: string; // Legacy message signature tokenId: number; // Token ID for access timestamp: number; // Unix timestamp (milliseconds) }

Request Parameters

ParameterTypeRequiredDescription
addressstringYesEthereum address claiming token ownership. Format: 0x... (42 characters)
signaturestringYesCryptographic signature proving address control. Format depends on auth mode.
tokenIdnumberYesID of the Colored Key NFT being used for access
messagestringSIWE onlyComplete EIP-4361 SIWE message including domain, nonce, and statement
noncestringSIWE onlyCryptographic nonce obtained from /api/nonce endpoint
timestampnumberLegacy onlyUnix timestamp when signature was created. Must be within 5 minutes.

Authentication Modes

The API intelligently detects which authentication mode to use based on the request parameters:

Sign-In with Ethereum (SIWE)

When SIWE is enabled and the request includes both message and nonce fields, the API uses the modern SIWE flow. This provides several security advantages:

  • Domain Binding - Messages are tied to specific domains, preventing cross-site attacks
  • Nonce Verification - Each authentication attempt requires a fresh nonce
  • Standard Format - EIP-4361 compliance ensures wallet compatibility
  • Rich Metadata - Messages can include statements, URIs, and chain information

The SIWE flow performs these verification steps:

  1. Validates the nonce hasn’t been used or expired
  2. Parses and verifies the SIWE message format
  3. Confirms the signature matches the message and address
  4. Checks domain consistency (enhanced by middleware)

Example SIWE message:

ritoswap.com wants you to sign in with your Ethereum account: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 Access gated content for token #42 URI: https://ritoswap.com Version: 1 Chain ID: 1 Nonce: k8Jd93kdo0Sdk39dkD9dk3mdk93kd9Dk Issued At: 2024-03-15T10:30:00.000Z

Response Formats

Success Response

When all verifications pass, the API returns the gated content:

{ "success": true, "access": "granted", "content": { "welcomeText": "Welcome, esteemed key holder!", "textSubmissionAreaHtml": "<div class='submission-area'>...</div>", "audioData": { "headline": "Exclusive Audio", "imageSrc": "/audio-cover.jpg", "imageAlt": "Audio cover", "description": "Exclusive content for token holders", "title": "Token Holder Music", "audioSrc": "/audio/exclusive.mp3", "error": false }, "styles": ".submission-area { ... }", "script": "// Interactive features" } }

HTTP Status Code: 200 OK

The content object structure may vary based on what exclusive content is configured for token holders.

Partial Success Response

If content generation partially fails (e.g., audio unavailable), the API still grants access but provides fallback content:

{ "success": true, "access": "granted", "content": { "welcomeText": "Welcome, esteemed key holder!", "textSubmissionAreaHtml": "<div class='submission-area'>...</div>", "audioData": { "headline": "Exclusive Audio", "imageSrc": "/audio-placeholder.jpg", "imageAlt": "Audio unavailable", "description": "Audio content temporarily unavailable", "title": "Token Holder Audio", "audioSrc": "", "error": true }, "styles": ".submission-area { padding: 20px; }", "script": "console.log('Loaded without audio');", "audioError": true, "errorMessage": "Audio temporarily unavailable" } }

Note: On audio/content generation failure, the route still returns 200 with fallback data and audioError: true.

This graceful degradation ensures users can still access core content even if some components (like audio) fail to generate. The API will always attempt to provide as much content as possible rather than failing entirely.

Error Responses

The API provides specific error messages for different failure scenarios:

Bad Request

{ "error": "Invalid JSON in request body" }

HTTP Status Code: 400 Bad Request

Common 400 errors:

  • "Invalid JSON in request body" - Malformed JSON in request
  • "Missing or invalid address field" - Address field missing or not a string
  • "Missing or invalid signature field" - Signature field missing or not a string
  • "Missing or invalid tokenId field" - TokenId field missing or not a number
  • "Missing or invalid timestamp field" - Timestamp missing in legacy flow
  • "Invalid message field" - Message not a string in SIWE flow
  • "Invalid nonce field" - Nonce not a string in SIWE flow
  • "Signature expired" - Legacy timestamp older than 5 minutes
  • "Missing host header" - Host header missing (middleware only)

Rate Limit Exceeded

{ "error": "Too many requests", "limit": 5, "remaining": 0, "retryAfter": 45 }

HTTP Status Code: 429 Too Many Requests

HTTP Headers:

X-RateLimit-Limit: 5 X-RateLimit-Remaining: 0 Retry-After: 45

The API returns both JSON body fields and HTTP headers to support different client implementations. Use whichever approach works best for your rate limiting logic.

Authentication Failures

{ "error": "Invalid signature" }

HTTP Status Code: 401 Unauthorized

Common authentication errors:

  • "Invalid or expired nonce" - SIWE nonce validation failed
  • "Invalid message format" - SIWE message parsing failed
  • "Invalid signature" - Signature doesn’t match address

Authorization Failures

{ "error": "You do not own this token" }

HTTP Status Code: 403 Forbidden

Common authorization errors:

  • "You do not own this token" - Blockchain verification failed
  • "This token has already been used" - Token previously used for access
  • "Domain mismatch" - SIWE domain doesn’t match request origin (middleware)

Not Found

{ "error": "Token not found in database" }

HTTP Status Code: 404 Not Found

This occurs when the token exists on-chain but hasn’t been synchronized to the database.

Internal Server Error

{ "error": "Internal server error" }

HTTP Status Code: 500 Internal Server Error

This indicates an unexpected exception occurred before or during request handling (e.g., internal dependency crash). Clients should implement exponential backoff and retry logic.

Content Generation Failure (after auth)

Under normal content-generation failures (e.g., audio pipeline issues), the API does not return 500. It responds with 200 OK and provides fallback content:

{ "success": true, "access": "granted", "content": { "welcomeText": "Welcome, esteemed key holder!", "textSubmissionAreaHtml": "<div class='submission-area'>...</div>", "audioData": { "headline": "Exclusive Audio", "imageSrc": "/audio-placeholder.jpg", "imageAlt": "Audio unavailable", "description": "Audio content temporarily unavailable", "title": "Token Holder Audio", "audioSrc": "", "error": true }, "audioError": true, "errorMessage": "Audio temporarily unavailable" } }

A 500 with "Failed to generate content" is returned only if both the primary content generation and the built-in fallback construction fail:

{ "error": "Failed to generate content" }

HTTP Status Code: 500 Internal Server Error

Middleware Layer

The middleware is always active for POST requests to /api/gate-access, but domain validation only runs under specific SIWE conditions.

Domain Validation Behavior

When SIWE is enabled and there is no hardcoded domain set, the middleware verifies that the domain in the signed SIWE message matches the request’s Host header.
This prevents replay attacks where a valid SIWE message for one domain is reused on another.

Host check happens first — before the request body is read, the middleware verifies that a Host header is present.
If missing, it responds with 400 and never calls request.json().

Middleware logic flow

  1. If method is not POST, skip.
  2. If SIWE is not enabled, skip.
  3. If NEXT_PUBLIC_DOMAIN is set to any value other than missing, empty string, or exactly 'false', skip domain check.
  4. Read Host header — if missing, return 400.
  5. If message or nonce is missing in the body, skip domain check.
  6. Parse JSON body:
    • If request.json() throws (malformed JSON), log and skip without rejecting.
  7. Parse SIWE message:
    • If it doesn’t match the " wants you to sign in" pattern or domain cannot be extracted, skip without rejecting.
  8. Compare extracted SIWE domain to the raw Host header (exact string match).
    • If different, return 403.
    • Note: This is a strict match — if Host includes a port (e.g., localhost:3000) but the SIWE domain does not, it will be treated as a mismatch.

When Domain Validation Runs

All of the following must be true:

  • HTTP method is POST
  • SIWE is enabled (NEXT_PUBLIC_ACTIVATE_REDIS === 'true' and both UPSTASH_REDIS_API and UPSTASH_REDIS_API_KEY are set and not 'false')
  • No hardcoded domain (NEXT_PUBLIC_DOMAIN is missing, empty, or exactly 'false')
  • Request contains both message and nonce in the body
  • A valid Host header is present

If any condition fails, the middleware still passes the request to the API, but without domain checking.

Benefits

  1. Early Rejection — Invalid SIWE requests are dropped before reading the body when Host is missing.
  2. Clear Errors — Sends specific responses for missing host or mismatched domain.
  3. Defense in Depth — Adds protection beyond API-side SIWE validation.
  4. Performance — Avoids consuming API resources for clearly invalid requests.

Error Responses

Missing Host Header
{ "error": "Missing host header" }
Status: 400 Bad Request
Happens when the request lacks a Host header.

Domain Mismatch
{ "error": "Domain mismatch" }
Status: 403 Forbidden
Happens when the SIWE message’s domain does not exactly match the Host header string.

Middleware Configuration

Example middleware file:

// app/api/gate-access/middleware.ts // ...domain validation middleware implementation... export const config = { matcher: '/api/gate-access' }

The config export ensures the middleware applies to /api/gate-access.
Activation is automatic — it runs for every POST, but domain checking only occurs under the SIWE + conditions above.

Rate Limiting

The Gate Access API implements careful rate limiting to balance security with usability:

Rate Limit Configuration

  • Limit: 5 requests per 60 seconds per identifier
  • Window: 60-second sliding window based on IP or identifier
  • Scope: Specific to the gateAccess rate limit namespace

The lower limit (compared to other endpoints) reflects that legitimate users shouldn’t need frequent access to gated content within short time periods. The 60-second window ensures fair access while preventing abuse.

Rate Limit Headers

When rate limited, the API returns both JSON response body fields and HTTP headers:

HTTP/1.1 429 Too Many Requests X-RateLimit-Limit: 5 X-RateLimit-Remaining: 0 Retry-After: 45 Content-Type: application/json { "error": "Too many requests", "limit": 5, "remaining": 0, "retryAfter": 45 }

Clients can use either the headers or the JSON body to implement rate limit handling, depending on their preference.

Rate Limit Strategy

The rate limiting serves multiple purposes:

  • Prevents content scraping attempts
  • Ensures fair access during high demand
  • Protects against denial-of-service attacks
  • Maintains quality of service for all users

Security Best Practices

When implementing gate access, follow these security guidelines:

Content Injection Safety

The API returns HTML content that gets injected into your application. Always:

  1. Sanitize HTML - Use libraries like DOMPurify before rendering
  2. Sandbox Scripts - Execute any scripts in isolated contexts
  3. Validate Styles - Ensure CSS doesn’t break your layout
  4. Content Security Policy - Implement CSP headers to limit execution

Domain Consistency

For SIWE implementations:

  • Always use the actual domain users see in their browser
  • Don’t hardcode domains unless absolutely necessary
  • Let the middleware handle domain validation when possible
  • Test thoroughly with different domain configurations

Token State Management

  • Cache content appropriately to reduce API calls
  • Clear cached content when users disconnect wallets
  • Handle token transfers gracefully (ownership can change)
  • Implement proper session management for extended access

Error Handling

  • Always validate JSON parsing to avoid syntax errors crashing your app
  • Implement proper retry logic with exponential backoff for 500 errors
  • Respect rate limits using either headers or response body
  • Provide clear user feedback for all error scenarios

Testing Strategies

Comprehensive testing ensures reliable gate access functionality:

Unit Testing

Test individual components of the gate access flow:

import { describe, it, expect, vi, beforeEach } from 'vitest' import { NextRequest } from 'next/server' import { POST } from '../route' describe('Gate Access API', () => { it('validates input and returns 400 for missing fields', async () => { const response = await POST(createTestRequest({ address: '0x123', signature: 'sig' // Missing tokenId })) expect(response.status).toBe(400) const data = await response.json() expect(data.error).toContain('Missing or invalid tokenId') }) it('successfully grants access with SIWE auth', async () => { // Mock all dependencies vi.mocked(siweServer.isSiweEnabled).mockReturnValue(true) vi.mocked(siweServer.verifyNonce).mockResolvedValue(true) vi.mocked(siweServer.verifySiweMessage).mockResolvedValue({ success: true }) vi.mocked(verifyMessage).mockResolvedValue(true) // Mock blockchain and database checks mockPublicClient.readContract.mockResolvedValue([BigInt(42), true]) mockTokenModel.findUnique.mockResolvedValue({ tokenId: 42, used: false }) const response = await POST(createTestRequest({ address: '0x123', signature: 'sig', tokenId: 42, message: 'SIWE message', nonce: 'test-nonce' })) expect(response.status).toBe(200) const data = await response.json() expect(data.success).toBe(true) expect(data.content).toBeDefined() }) it('enforces rate limiting with proper headers', async () => { vi.mocked(checkRateLimitWithNonce).mockResolvedValue({ success: false, limit: 5, remaining: 0, reset: Date.now() + 60000 }) const response = await POST(createTestRequest({ address: '0x123', signature: 'sig', tokenId: 42 })) expect(response.status).toBe(429) expect(response.headers.get('X-RateLimit-Limit')).toBe('5') expect(response.headers.get('X-RateLimit-Remaining')).toBe('0') expect(response.headers.get('Retry-After')).toBeDefined() }) it('handles partial content failure gracefully', async () => { // Mock successful auth but content generation fails setupSuccessfulAuth() vi.mocked(getGatedContent).mockRejectedValue( new Error('Audio generation failed') ) const response = await POST(createTestRequest({ address: '0x123', signature: 'sig', tokenId: 42, timestamp: Date.now() })) expect(response.status).toBe(200) const data = await response.json() expect(data.success).toBe(true) expect(data.content.audioError).toBe(true) expect(data.content.welcomeText).toBeDefined() }) })

Testing Checklist

Ensure comprehensive test coverage for:

  • ✅ Both SIWE and legacy authentication flows
  • ✅ Input validation for all required fields
  • ✅ Proper error responses for malformed JSON
  • ✅ Middleware domain validation (when enabled)
  • ✅ Middleware host header validation
  • ✅ Rate limiting enforcement with headers
  • ✅ All error scenarios and status codes (400, 401, 403, 404, 429, 500)
  • ✅ Partial content delivery failures
  • ✅ Security measures (replay protection, domain checks)
  • ✅ Database and blockchain interaction edge cases

Configuration

The Gate Access API behavior is controlled by several environment variables:

SIWE Configuration

# Enable SIWE authentication NEXT_PUBLIC_ACTIVATE_REDIS=true UPSTASH_REDIS_API=your-redis-url UPSTASH_REDIS_API_KEY=your-redis-key # Optional: Hardcode domain (disables middleware checks) NEXT_PUBLIC_DOMAIN=ritoswap.com

Content Configuration

The gated content is managed by the getGatedContent() function, which can be customized to return different content types:

// Example content structure (matches /api/gate-access response) interface GatedContent { welcomeText: string; textSubmissionAreaHtml: string; audioData?: { headline: string; imageSrc: string; imageAlt: string; description: string; title: string; audioSrc: string; error: boolean; }; styles?: string; script?: string; // Present when audio falls back audioError?: boolean; errorMessage?: string; }

Troubleshooting

Common Issues and Solutions

“Invalid JSON in request body” error

  • Ensure Content-Type header is set to application/json
  • Validate JSON syntax before sending
  • Check for trailing commas or unquoted keys

“Domain mismatch” error

  • Ensure SIWE message domain matches the request host
  • Check for proxy or CDN modifications to the Host header
  • Consider hardcoding domain if your setup requires it

“Missing host header” error

  • Check reverse proxy configuration
  • Ensure proxy forwards the Host header correctly
  • Consider using X-Forwarded-Host if needed

Content not displaying

  • Verify HTML sanitization isn’t too aggressive
  • Check browser console for CSP violations
  • Ensure styles are properly scoped to avoid conflicts
  • Check for audioError flag in partial content scenarios

Intermittent 500 errors

  • Check blockchain RPC endpoint reliability
  • Monitor database connection pool exhaustion
  • Implement proper timeout handling for content generation
  • Review server logs for specific error messages

Rate limiting too restrictive

  • Adjust limits based on actual usage patterns
  • Implement client-side caching to reduce requests
  • Consider per-token rate limiting for fairness
  • Use rate limit headers for intelligent retry logic

Summary

The Gate Access API represents a sophisticated approach to blockchain-based content gating, combining multiple authentication methods with layered security and graceful error handling. By supporting both modern SIWE and legacy signature verification, it ensures broad compatibility while maintaining high security standards.

The optional middleware layer demonstrates how defense in depth can be achieved without sacrificing flexibility - the API remains fully functional whether the middleware is active or not. This redundancy, combined with comprehensive error handling and partial content delivery capabilities, creates a robust system that gracefully handles edge cases while providing an excellent user experience.

The API’s thoughtful error responses, including proper HTTP status codes and both body and header-based rate limiting information, make it easy to integrate into any client application. The graceful degradation for partial content failures ensures users always receive the best possible experience, even when some components are temporarily unavailable.

Whether you’re building exclusive content experiences, token-gated communities, or premium features for NFT holders, this API provides the secure, flexible foundation needed to deliver content that truly rewards your token holders while protecting your valuable digital assets.