Gated Content Library
The Gated Content Library serves as the content management system for RitoSwap’s token gate, assembling all the exclusive content that authenticated users receive. This internal library combines HTML, CSS, JavaScript, and audio data into a cohesive package that creates an immersive experience for token holders.
Overview
When users successfully unlock the token gate with their Colored Key NFT, they gain access to exclusive content that includes the ability to submit a single message and listen to secret crypto-themed music. The Gated Content Library orchestrates this experience by packaging all necessary components into a structured format that the frontend can render seamlessly.
The Token Gate Experience
The gated content represents the culmination of the user’s journey through RitoSwap’s token system. After minting or acquiring a Colored Key NFT and proving ownership through wallet signatures, users are rewarded with access to this exclusive area. The content is designed to be ephemeral—users can submit only one message with their key before it’s marked as used, making each interaction unique and valuable.
- gatedContent.ts
Content Structure
The getGatedContent
function returns a comprehensive content package:
export async function getGatedContent() {
return {
welcomeText: string, // Welcome message for authenticated users
textSubmissionAreaHtml: string, // HTML for the message submission form
audioData: { // Audio player configuration
headline: string,
imageSrc: string,
imageAlt: string,
description: string,
title: string,
audioSrc: string, // Signed URL from R2
error?: boolean
},
styles: string, // CSS for styling the content
script: string // JavaScript for form interaction
};
}
Content Components Breakdown
The library assembles four main components that work together to create the gated experience:
1. Welcome Text
The introductory message sets expectations for users entering the exclusive area. It explains the one-time message submission rule and teases the exclusive audio content, creating anticipation and emphasizing the limited nature of the experience.
2. Text Submission Area
A carefully crafted HTML form allows users to submit their single message. The form includes a textarea for message input and a submit button that triggers the signature and submission process. The HTML is designed to be injected safely into the page with proper styling hooks.
3. Audio Data
Configuration for the AudioWrapper component, including metadata about the exclusive track and the signed URL generated by the R2 library. This creates the music player interface that showcases Rito Rhymes’ crypto-themed parody music.
4. Styling and Scripts
Custom CSS creates the cyberpunk aesthetic with glowing borders and animations, while the JavaScript handles form submission logic and connects to the parent component’s submission handler.
API Reference
Function | Signature | Returns | Notes |
---|---|---|---|
getGatedContent | (): Promise<GatedContentResponse> | Complete content package | Assembles all gated content including signed audio URL |
Response Type Structure
interface GatedContentResponse {
welcomeText: string
textSubmissionAreaHtml: string
audioData: AudioData
styles: string
script: string
}
interface AudioData {
headline: string // "Secret Crypto Music"
imageSrc: string // Album cover path
imageAlt: string // Accessibility text
description: string // Track description
title: string // "Hit Me Bitcoin One More Time"
audioSrc: string // Signed R2 URL
error?: boolean // Audio availability flag
}
Implementation Details
Audio URL Generation
The library integrates with the R2 storage system to provide secure audio access:
let audioUrl: string;
let audioError = false;
try {
audioUrl = await generateSignedAudioUrl();
} catch (error) {
console.error('Failed to generate signed audio URL:', error);
audioUrl = '';
audioError = true;
}
This error handling ensures that audio failures don’t break the entire gated experience—users can still submit their message even if the audio is temporarily unavailable.
HTML Content Structure
The text submission area HTML is carefully structured for security and functionality:
<div class="textSubmissionContainer">
<h2 class="textSubmissionTitle">Submit Your Message</h2>
<form id="gatedSubmissionForm" class="textSubmissionForm">
<textarea
id="gatedTextarea"
class="textSubmissionTextarea"
placeholder="Enter your message here..."
rows="6"
></textarea>
<button
type="submit"
id="gatedSubmitButton"
class="textSubmissionButton"
>
Sign & Submit
</button>
</form>
</div>
Each element has specific IDs and classes that connect to both the styling system and the JavaScript behavior handlers.
Styling System
The CSS creates a cohesive visual experience with the Blade Runner-inspired aesthetic:
.textSubmissionContainer {
background: rgba(0, 30, 60, 0.8);
border: var(--default-border);
box-shadow:
0 0 20px rgba(0, 123, 255, 0.3),
0 0 40px rgba(0, 123, 255, 0.2),
0 0 60px rgba(0, 123, 255, 0.1),
inset 0 0 20px rgba(0, 123, 255, 0.1);
animation: glowPulse 3s ease-in-out infinite;
}
The glowing animation and layered shadows create depth and visual interest, making the exclusive content feel special and otherworldly.
JavaScript Behavior
The embedded script handles form submission with proper validation and error handling:
form.addEventListener('submit', async (e) => {
e.preventDefault();
const text = textarea.value.trim();
if (!text) {
alert('Please enter some text');
return;
}
// Disable button and show processing state
submitButton.disabled = true;
submitButton.textContent = 'Sending...';
submitButton.classList.add('processing');
try {
window.handleGatedSubmission(text);
} catch (error) {
// Re-enable on error
submitButton.disabled = false;
submitButton.textContent = 'Sign & Submit';
submitButton.classList.remove('processing');
}
});
The script connects to the parent component through the global handleGatedSubmission
function, which is injected by the GatedContentRenderer.
Integration Flow
The gated content flows through several components in the RitoSwap system:
Step 1: User Unlocks Gate
The GateModal component verifies ownership and signatures, then calls the /api/gate-access
endpoint.
Step 2: API Fetches Content
The gate-access API calls getGatedContent()
to assemble the content package, including generating the signed audio URL.
Step 3: Content Delivery
The content is passed back through the GateModal to the GatePageWrapper via the onContentReceived
callback.
Step 4: Rendering
The GatedContentRenderer component receives the content and renders it, injecting styles, HTML, and executing the script.
Step 5: User Interaction
Users can play the exclusive audio and submit their one-time message, which triggers the signature and submission flow.
Component Integration Example
Here’s how the GatedContentRenderer uses the content:
// In GatedContentRenderer.tsx
useEffect(() => {
// Inject styles
const styleEl = document.createElement("style");
styleEl.textContent = content.styles;
document.head.appendChild(styleEl);
// Expose submission handler
(window as any).handleGatedSubmission = onSubmit;
// Inject HTML content
containerRef.current.innerHTML = `
<div class="${styles.welcomeText}">${content.welcomeText}</div>
<div class="${styles.contentArea}">${content.textSubmissionAreaHtml}</div>
`;
// Execute script
const runner = new Function(content.script);
runner();
}, [content, onSubmit]);
Security Considerations
Content Injection Safety
While the library returns HTML and JavaScript as strings, several measures ensure security:
- Trusted Source: Content comes from server-side code, not user input
- Scoped Execution: JavaScript runs in a controlled context
- No User Data: The HTML template doesn’t include any user-provided content
- Event Validation: Form submission is validated before processing
Message Submission Security
The submission flow includes multiple security layers:
// In GatePageWrapper handleGatedSubmission
const signMessage = `Token Gate Access Request:
Token ID: ${tokenId}
Address: ${address}
Timestamp: ${timestamp}
Message: ${text}`;
const signature = await signMessageAsync({ message: signMessage });
This creates a tamper-proof record of:
- Which token was used
- Who submitted the message
- When it was submitted
- The exact message content
Customization Points
Modifying the Welcome Message
The welcome text can be updated to reflect different campaigns or events:
welcomeText: process.env.GATE_WELCOME_MESSAGE ||
"Welcome inside. You get to send one message with your unused key..."
Styling Variations
The CSS can be modified for special themes:
const seasonalStyles = {
default: defaultStyles,
halloween: halloweenStyles,
holiday: holidayStyles
};
styles: seasonalStyles[currentSeason] || seasonalStyles.default
Dynamic Audio Selection
The audio data could be randomized or based on token attributes:
const tracks = [
{ title: "Hit Me Bitcoin One More Time", file: "HitMeBitcoin.mp3" },
{ title: "Crypto Killed The Radio Star", file: "CryptoKilled.mp3" },
{ title: "Sweet Chain O' Mine", file: "SweetChain.mp3" }
];
const selectedTrack = tracks[tokenId % tracks.length];
Error Handling
The library implements graceful degradation for various failure scenarios:
Audio Failure Handling
When R2 storage is unavailable:
if (content.audioData.error || audioError) {
return (
<div className={styles.audioError}>
<h3>Audio Temporarily Unavailable</h3>
<p>Please refresh the page to try again</p>
</div>
);
}
Script Execution Errors
The renderer wraps script execution in try-catch:
try {
const runner = new Function(content.script);
runner();
} catch (error) {
console.error("Error executing gated content script:", error);
}
Submission Failures
The embedded script handles submission errors:
catch (error) {
console.error('Submission error:', error);
alert('Failed to submit. Please try again.');
// Re-enable form for retry
}
Testing Considerations
When testing the gated content system:
Content Assembly Testing
describe('getGatedContent', () => {
it('returns complete content structure', async () => {
const content = await getGatedContent();
expect(content).toHaveProperty('welcomeText');
expect(content).toHaveProperty('textSubmissionAreaHtml');
expect(content).toHaveProperty('audioData');
expect(content).toHaveProperty('styles');
expect(content).toHaveProperty('script');
});
it('handles audio URL generation failure', async () => {
// Mock R2 failure
vi.mocked(generateSignedAudioUrl).mockRejectedValue(new Error('R2 error'));
const content = await getGatedContent();
expect(content.audioData.error).toBe(true);
expect(content.audioData.audioSrc).toBe('');
});
});
Integration Testing
Test the full flow from gate unlock to content rendering:
- Verify style injection doesn’t conflict with existing styles
- Ensure script execution doesn’t throw errors
- Test form submission connection to parent handler
- Validate audio player initialization
Performance Optimization
Content Caching Strategy
Since the content structure rarely changes, consider caching:
let cachedContent: GatedContentResponse | null = null;
let cacheExpiry: number = 0;
export async function getGatedContent() {
if (cachedContent && Date.now() < cacheExpiry) {
// Only regenerate audio URL
cachedContent.audioData.audioSrc = await generateSignedAudioUrl();
return cachedContent;
}
// Generate fresh content
const content = await generateFreshContent();
cachedContent = content;
cacheExpiry = Date.now() + (5 * 60 * 1000); // 5 min cache
return content;
}
Lazy Loading Assets
The audio and images can be lazy-loaded:
audioData: {
// ...other fields
lazyLoad: true,
preload: 'metadata' // Only load metadata initially
}
Summary
The Gated Content Library serves as the heart of RitoSwap’s exclusive content system, orchestrating a carefully crafted experience for token holders. By combining secure audio delivery, interactive message submission, and immersive styling, it creates a memorable moment that justifies the effort users put into obtaining and using their Colored Key NFTs. The library’s modular design, comprehensive error handling, and security considerations ensure that this exclusive content remains both special and reliable for every user who earns access to it.