Bulk pregeneration allows you to create Para wallets for multiple Twitter users ahead of time. Users can later claim these wallets by authenticating with their Twitter accounts, creating a seamless onboarding experience for airdrops, token distributions, or whitelist rewards.
How Bulk Pregeneration Works
When you bulk pregenerate wallets:
- Generate wallets for Twitter usernames using Para’s server SDK
- Store user shares securely on your backend
- Fund wallets with tokens or NFTs for airdrops (optional)
- Users claim wallets later by signing in with Twitter
- Para matches the Twitter username to the pregenerated wallet
Prerequisites
You need a Para API key and basic knowledge of Node.js/TypeScript development.
You can obtain Twitter usernames for bulk pregeneration from various sources:
- Database of pre-registered users - Users who joined your whitelist or waitlist
- Twitter API - Programmatically fetch followers, mentions, or community members
- CSV files - Export from existing user databases or CRM systems
- Contest participants - Users who engaged with your Twitter campaigns
For this tutorial, we’ll use a local CSV file as an example, but the core logic applies regardless of your data source.
Server-Side Implementation
Setup Para Server Client
First, create a Para server client to handle wallet generation:
import { Environment, Para } from "@getpara/server-sdk";
export function getParaServerClient() {
const apiKey = process.env.PARA_API_KEY;
if (!apiKey) {
throw new Error("PARA_API_KEY is required");
}
return new Para(Environment.BETA, apiKey);
}
Create Bulk Generation API
Create an API endpoint to generate wallets for Twitter usernames:
api/wallet/generate/route.ts
import { getParaServerClient } from "@/lib/para-server";
import { NextResponse } from "next/server";
interface GenerateWalletRequest {
handle: string;
type: "TWITTER";
}
export async function POST(request: Request) {
try {
const { handle, type }: GenerateWalletRequest = await request.json();
if (!handle || type !== "TWITTER") {
return NextResponse.json({ error: "Invalid handle or type" }, { status: 400 });
}
const para = getParaServerClient();
const wallet = await para.createPregenWallet({
type: "EVM",
pregenId: { xUsername: handle.trim() }
});
const userShare = await para.getUserShare();
if (!wallet || !userShare) {
throw new Error("Failed to generate wallet");
}
await storeWalletData(handle.trim(), wallet, userShare);
return NextResponse.json({
success: true,
handle: handle.trim(),
address: wallet.address
});
} catch (error) {
console.error("Wallet generation error:", error);
return NextResponse.json({ error: "Generation failed" }, { status: 500 });
}
}
async function storeWalletData(handle: string, wallet: any, userShare: any) {
// Store wallet and user share in your database
// This is critical for users to claim their wallets later
}
Batch Processing Implementation
For processing multiple handles efficiently:
hooks/use-batch-processor.ts
import { useState } from "react";
interface BatchResult {
handle: string;
success: boolean;
address?: string;
error?: string;
}
export function useBatchProcessor() {
const [processing, setProcessing] = useState(false);
const [results, setResults] = useState<BatchResult[]>([]);
const [progress, setProgress] = useState(0);
const processBatch = async (handles: string[]) => {
setProcessing(true);
setResults([]);
setProgress(0);
const batchSize = 10;
const batches = [];
for (let i = 0; i < handles.length; i += batchSize) {
batches.push(handles.slice(i, i + batchSize));
}
const allResults: BatchResult[] = [];
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
const batchResults = await Promise.all(
batch.map(async (handle) => {
try {
const response = await fetch("/api/wallet/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ handle, type: "TWITTER" })
});
const data = await response.json();
return {
handle,
success: data.success,
address: data.address,
error: data.error
};
} catch (error) {
return {
handle,
success: false,
error: "Network error"
};
}
})
);
allResults.push(...batchResults);
setResults([...allResults]);
setProgress(((i + 1) / batches.length) * 100);
// Rate limiting delay
if (i < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
setProcessing(false);
};
return {
processing,
results,
progress,
processBatch
};
}
Alternative Data Sources
From Database
You can fetch usernames directly from your database:
// Example: Fetch whitelist users from database
async function getWhitelistUsers() {
const users = await db.users.findMany({
where: { whitelisted: true },
select: { twitterUsername: true }
});
return users.map(user => user.twitterUsername).filter(Boolean);
}
Programmatically fetch Twitter usernames:
// Example: Get followers using Twitter API
async function getFollowers(userId: string) {
const response = await fetch(`https://api.twitter.com/2/users/${userId}/followers`, {
headers: {
'Authorization': `Bearer ${process.env.TWITTER_BEARER_TOKEN}`
}
});
const data = await response.json();
return data.data?.map((user: any) => user.username) || [];
}
Example: CSV Processing
For this tutorial, we’ll demonstrate with a CSV file containing Twitter handles:
handle,type
@username1,twitter
@username2,twitter
username3,twitter
The @
symbol is optional and will be automatically handled. Headers are also optional.
Frontend Considerations
While the UI implementation depends on your application’s design system, consider these patterns:
- Progress tracking - Show real-time batch processing status
- Error handling - Display failed generations with retry options
- Results export - Allow downloading generation results
- Batch size control - Let users adjust processing batch sizes
The core logic remains the same regardless of your UI framework choice.
Important Considerations
Rate Limiting
Para’s API has rate limits. Process handles in batches with delays:
// Process 10 handles at a time with 1-second delays
const batchSize = 10;
const delay = 1000; // 1 second between batches
Error Handling
Always implement retry logic for failed generations:
const retryFailed = async (failedResults: BatchResult[]) => {
const failedHandles = failedResults
.filter(result => !result.success)
.map(result => result.handle);
// Retry processing
await processBatch(failedHandles);
};
Data Storage
Store wallet data securely in your database:
- Wallet addresses for reference
- User shares for wallet claiming
- Handle mappings for Twitter username lookup
- Generation timestamps for tracking
Testing Your Implementation
- Start with small batches (5-10 handles)
- Use test Twitter handles that you control
- Verify wallet generation in Para Developer Portal
- Test claiming flow with actual Twitter authentication
Next Steps