OAuth Pregeneration - Reward Users Before They Log In
๐ Status: Production Ready | โฑ๏ธ Setup Time: 20 minutes | ๐ง Skill Level: โญโญ Intermediate
What is this? A fully operational API that lets you reward users based on their social media identities (Discord, Twitter, GitHub, etc.) before they ever log into your app.
Use this if you're: Building games, bots, or apps where you know your users' social IDs but they haven't logged into Loyalteez yet.
What you need: Just your Loyalteez Brand ID - no Privy credentials required!
The Problem This Solvesโ
Traditional Flow (Limitation)โ
1. User logs into your app with Discord
2. Privy creates wallet for them
3. THEN you can reward them with LTZ
Issue: You can't reward users who haven't logged in yet.
Pregeneration Flow (Solution)โ
1. User earns reward in your Discord server (never logged in)
2. You pregenerate a Privy account linked to their Discord
3. LTZ is deposited to their pregenerated wallet
4. When they eventually log in with Discord โ they see their rewards! ๐
Benefit: Reward users immediately, they discover their rewards when they log in.
How It Worksโ
Plain English Explanationโ
Think of it like:
- You're sending a gift card to someone's email
- Even if they don't have an account yet
- When they create an account with that email โ the gift is waiting!
Technical explanation:
- Privy's API lets you create user accounts linked to OAuth identities
- Each OAuth provider (Discord, Twitter, etc.) has unique user IDs
- You can create a wallet for a user using their OAuth ID
- When they log in with that OAuth provider โ they get the same wallet
- It's deterministic: Same OAuth ID = Same wallet (always)
Use Casesโ
1. Discord Game Botโ
Scenario: You have a Discord bot game where users earn points.
User plays game in Discord โ Earns 100 LTZ
User has never used Loyalteez before
Bot pregenerates wallet linked to Discord ID
User sees "You earned 100 LTZ! Log in at loyalteez.app to claim"
User logs in with Discord โ 100 LTZ is already there!
2. Twitter/X Engagement Campaignโ
Scenario: Reward people who tweet about your product.
User tweets with #YourHashtag
Your bot detects the tweet
Pregenerate wallet linked to Twitter ID
Award 50 LTZ to their wallet
DM them: "You earned 50 LTZ! Log in to see your rewards"
3. GitHub Contributionsโ
Scenario: Reward open source contributors.
User submits a PR to your repo
GitHub webhook triggers your server
Pregenerate wallet linked to GitHub ID
Award 200 LTZ to their wallet
Comment on PR: "Thanks! 200 LTZ credited to your account"
4. Multi-Platform Onboardingโ
Scenario: User interacts on multiple platforms before signing up.
Day 1: User joins Discord server โ Pregenerate + award 10 LTZ
Day 2: User retweets your post โ Award 5 LTZ to same user
Day 3: User finally logs in โ Sees 15 LTZ total waiting!
Implementation Guideโ
Prerequisitesโ
You'll need:
- Loyalteez Brand ID (get this from your dashboard)
- Backend server (Node.js, Python, or any language with HTTP support)
- User's OAuth provider and their ID (from their real OAuth account)
Important: โ ๏ธ NO Privy credentials needed! You call Loyalteez's API and we handle all Privy integration server-side for security!
Step 1: Understanding the Architectureโ
Why this architecture?
โ Wrong approach: Use your own Privy account
- Wallets created in YOUR Privy instance
- Not connected to Loyalteez system
- Can't receive LTZ rewards
โ Correct approach: Call Loyalteez API
- We use OUR Privy credentials server-side
- Wallets created in Loyalteez's Privy instance
- Fully integrated with LTZ rewards
- User sees everything when they log in
No SDK installation needed - Just make HTTP requests to our API!
Step 2: Server-Side Pregeneration Codeโ
Node.js / TypeScriptโ
import axios from 'axios';
// =========================================
// FUNCTION: Pregenerate User + Reward LTZ
// =========================================
async function rewardUserByOAuth(
provider: 'discord' | 'twitter' | 'github' | 'google' | 'telegram',
userId: string, // Their ID on that platform
username: string, // Their username (for display)
ltzAmount: number // How much LTZ to award
) {
try {
// ==========================================
// STEP 1: Pregenerate Wallet via Loyalteez API
// ==========================================
// Calls Loyalteez's pregeneration endpoint
// We handle Privy integration server-side
const pregenerationResponse = await axios.post(
'https://register.loyalteez.app/loyalteez-api/pregenerate-user',
{
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: provider,
oauth_user_id: userId,
oauth_username: username,
metadata: {
source: 'automatic_reward',
timestamp: Date.now()
}
}
);
const { wallet_address } = pregenerationResponse.data;
console.log(`โ
Pregenerated wallet for ${username}`);
console.log(`Wallet address: ${wallet_address}`);
// ==========================================
// STEP 2: Award LTZ to Their Wallet
// ==========================================
// Use Loyalteez event API to credit LTZ
await axios.post('https://api.loyalteez.app/loyalteez-api/manual-event', {
event: 'oauth_pregeneration_reward',
email: `${provider}_${userId}@pregenerated.loyalteez.app`, // Synthetic email
metadata: {
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: provider,
oauth_user_id: userId,
oauth_username: username,
wallet_address: wallet_address,
reward_amount: ltzAmount
}
});
console.log(`โ
Awarded ${ltzAmount} LTZ to ${username}`);
return {
success: true,
wallet: wallet_address,
message: `${username} earned ${ltzAmount} LTZ! Log in with ${provider} to claim.`
};
} catch (error) {
console.error('โ Pregeneration failed:', error);
throw error;
}
}
// ==========================================
// EXAMPLE USAGE: Discord Bot
// ==========================================
async function handleDiscordGameWin(discordUserId: string, discordUsername: string) {
const result = await rewardUserByOAuth(
'discord',
discordUserId, // e.g., "123456789012345678"
discordUsername, // e.g., "gamer123"
100 // Award 100 LTZ
);
// Send them a DM or message
return `๐ Congrats ${discordUsername}! ${result.message}`;
}
// ==========================================
// EXAMPLE USAGE: Twitter Engagement
// ==========================================
async function handleTwitterMention(twitterUserId: string, twitterHandle: string) {
const result = await rewardUserByOAuth(
'twitter',
twitterUserId, // e.g., "987654321"
twitterHandle, // e.g., "@cryptofan"
50 // Award 50 LTZ
);
// Reply to their tweet or DM them
return `Thanks ${twitterHandle}! ${result.message}`;
}
// ==========================================
// EXAMPLE USAGE: GitHub Contribution
// ==========================================
async function handleGitHubPR(githubUserId: string, githubUsername: string) {
const result = await rewardUserByOAuth(
'github',
githubUserId, // e.g., "45678901"
githubUsername, // e.g., "dev_contributor"
200 // Award 200 LTZ for PR
);
// Comment on PR
return `๐ ${result.message}`;
}
Pythonโ
import requests
import os
def reward_user_by_oauth(provider, user_id, username, ltz_amount):
"""
Pregenerate user wallet and reward them with LTZ
Args:
provider: 'discord', 'twitter', 'github', 'google', or 'telegram'
user_id: User's unique ID on that platform
username: User's display name
ltz_amount: Amount of LTZ to award
"""
try:
# STEP 1: Pregenerate wallet via Loyalteez API
pregeneration_response = requests.post(
'https://register.loyalteez.app/loyalteez-api/pregenerate-user',
json={
'brand_id': os.getenv('VITE_LOYALTEEZ_BRAND_ID'),
'oauth_provider': provider,
'oauth_user_id': user_id,
'oauth_username': username,
'metadata': {
'source': 'automatic_reward',
'timestamp': int(time.time() * 1000)
}
}
)
pregeneration_response.raise_for_status()
wallet_data = pregeneration_response.json()
wallet_address = wallet_data['wallet_address']
print(f"โ
Pregenerated wallet for {username}")
print(f"Wallet address: {wallet_address}")
# STEP 2: Award LTZ via event API
reward_response = requests.post(
'https://api.loyalteez.app/loyalteez-api/manual-event',
json={
'event': 'oauth_pregeneration_reward',
'email': f"{provider}_{user_id}@pregenerated.loyalteez.app",
'metadata': {
'brand_id': os.getenv('VITE_LOYALTEEZ_BRAND_ID'),
'oauth_provider': provider,
'oauth_user_id': user_id,
'oauth_username': username,
'wallet_address': wallet_address,
'reward_amount': ltz_amount
}
}
)
reward_response.raise_for_status()
print(f"โ
Awarded {ltz_amount} LTZ to {username}")
return {
'success': True,
'wallet': wallet_address,
'message': f"{username} earned {ltz_amount} LTZ! Log in with {provider} to claim."
}
except Exception as e:
print(f"โ Pregeneration failed: {e}")
raise
# Example: Discord bot reward
def handle_discord_game_win(discord_user_id, discord_username):
return reward_user_by_oauth('discord', discord_user_id, discord_username, 100)
REST API (Any Language)โ
Simple HTTP requests - works with any programming language:
# Step 1: Pregenerate wallet via Loyalteez API
curl -X POST https://register.loyalteez.app/loyalteez-api/pregenerate-user \
-H "Content-Type: application/json" \
-d '{
"brand_id": "your-brand-id",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"oauth_username": "gamer123",
"metadata": {
"source": "game_bot",
"game": "space_adventure"
}
}'
# Response:
# {
# "success": true,
# "wallet_address": "0xABC123...",
# "privy_user_id": "did:privy:...",
# "oauth_provider": "discord",
# "oauth_user_id": "123456789012345678",
# "message": "Wallet pregenerated for gamer123"
# }
# Step 2: Award LTZ to the wallet
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"event": "oauth_pregeneration_reward",
"email": "[email protected]",
"metadata": {
"brand_id": "your-brand-id",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"oauth_username": "gamer123",
"wallet_address": "0xABC123...",
"reward_amount": 100
}
}'
That's it! Two API calls and you're done. No SDKs, no Privy credentials needed.
Step 3: Environment Variablesโ
Add this to your .env:
# Loyalteez Configuration
VITE_LOYALTEEZ_BRAND_ID=your_brand_id_here
That's it! Just your Brand ID. No Privy credentials, no API keys - we handle all authentication server-side for security.
Security: โ ๏ธ Never commit .env to Git! Add to .gitignore
Step 4: Understanding the Responseโ
The pregeneration endpoint returns:
{
success: true,
wallet_address: string, // Ethereum wallet address
privy_user_id: string, // Privy DID (did:privy:...)
oauth_provider: string, // Provider you used
oauth_user_id: string, // OAuth ID you provided
created_new: boolean, // true = new wallet, false = existing
message: string, // Human-readable status
next_steps: string[] // What to do next
}
The created_new field is key:
true= First time pregeneration, new wallet createdfalse= Wallet already existed, returned existing one
This lets you track new users vs. returning users!
Important Detailsโ
1. OAuth Provider Subject IDsโ
Each OAuth provider has unique user IDs:
| Provider | Where to Get User ID | Example Format |
|---|---|---|
| Discord | user.id from Discord API | "123456789012345678" (string) |
| Twitter/X | user.id from Twitter API | "987654321" (string) |
| GitHub | user.id from GitHub API | "45678901" (number as string) |
sub claim from OAuth token | "108012345678901234567" |
Critical: Always use the platform's user ID, not email or username!
2. Deterministic = Same User, Same Walletโ
Key concept:
// First time: Creates wallet
rewardUserByOAuth('discord', '123456', 'user1', 50);
// Wallet created: 0xABC...
// Later: Same Discord ID = Same wallet
rewardUserByOAuth('discord', '123456', 'user1', 25);
// Same wallet: 0xABC... (gets +25 LTZ)
// User logs in with Discord
// Privy gives them: 0xABC... (has 75 LTZ total!)
This means:
- โ You can reward users multiple times before they log in
- โ All rewards accumulate in the same wallet
- โ When they log in, everything is there!
3. Cross-Platform Considerationsโ
Q: What if a user has both Discord and Twitter but you reward both?
A: They'll have separate wallets for each OAuth provider (unless Privy links them).
// Reward Discord ID
rewardUserByOAuth('discord', '123', 'user', 50);
// Creates wallet A
// Reward Twitter ID (same person!)
rewardUserByOAuth('twitter', '456', 'user', 50);
// Creates wallet B (different!)
// Solution: Pick ONE primary OAuth provider per user
Best practice:
- Choose one primary OAuth provider (e.g., Discord)
- Store user's primary provider in your database
- Always use that provider for rewards
4. When Wallet Is Already Createdโ
Q: What if you try to pregenerate a user who already logged in?
A: Privy returns the existing wallet, no problem!
// User already logged in โ wallet exists
const result = await privy.importUser({
linkedAccounts: [{ type: 'discord', subject: '123' }],
createEmbeddedWallet: true
});
// Result: Returns existing wallet (doesn't create new one)
// You can safely call this multiple times
Complete Example: Discord Game Botโ
Full Working Bot with Pregenerationโ
const { Client, GatewayIntentBits } = require('discord.js');
const axios = require('axios');
require('dotenv').config();
// ==========================================
// INITIALIZE DISCORD CLIENT
// ==========================================
const discord = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
// ==========================================
// GAME STATE (in production, use database)
// ==========================================
const userScores = new Map();
// ==========================================
// HELPER: Pregenerate + Reward
// ==========================================
async function rewardDiscordUser(userId, username, amount) {
try {
// Step 1: Pregenerate wallet via Loyalteez API
const pregenerationResponse = await axios.post(
'https://register.loyalteez.app/loyalteez-api/pregenerate-user',
{
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: 'discord',
oauth_user_id: userId,
oauth_username: username,
metadata: {
source: 'discord_game_bot',
game: 'score_game'
}
}
);
const { wallet_address } = pregenerationResponse.data;
// Step 2: Award LTZ
await axios.post(
'https://api.loyalteez.app/loyalteez-api/manual-event',
{
event: 'discord_game_win',
email: `discord_${userId}@pregenerated.loyalteez.app`,
metadata: {
brand_id: process.env.VITE_LOYALTEEZ_BRAND_ID,
oauth_provider: 'discord',
oauth_user_id: userId,
oauth_username: username,
wallet_address: wallet_address,
reward_amount: amount
}
}
);
return {
success: true,
wallet: wallet_address,
amount
};
} catch (error) {
console.error('Reward failed:', error);
return { success: false, error: error.message };
}
}
// ==========================================
// GAME COMMANDS
// ==========================================
discord.on('messageCreate', async (message) => {
if (message.author.bot) return;
const userId = message.author.id;
const username = message.author.username;
// Command: !play
if (message.content === '!play') {
const score = Math.floor(Math.random() * 100);
userScores.set(userId, (userScores.get(userId) || 0) + score);
message.reply(
`๐ฎ You scored ${score} points! Total: ${userScores.get(userId)}`
);
// If score > 50, reward with LTZ
if (score > 50) {
const ltzReward = Math.floor(score / 10);
const result = await rewardDiscordUser(userId, username, ltzReward);
if (result.success) {
message.reply(
`๐ **Bonus!** You earned ${ltzReward} LTZ!\n` +
`Log in at https://loyalteez.app with Discord to see your rewards!\n` +
`Your wallet: \`${result.wallet}\``
);
}
}
}
// Command: !balance
if (message.content === '!balance') {
const points = userScores.get(userId) || 0;
message.reply(
`๐ **Your Stats:**\n` +
`Game Points: ${points}\n` +
`Log in at https://loyalteez.app to see your LTZ balance!`
);
}
});
// ==========================================
// START BOT
// ==========================================
discord.login(process.env.DISCORD_BOT_TOKEN);
console.log('๐ค Discord game bot running with OAuth pregeneration!');
What This Bot Doesโ
- User plays game:
!playโ random score - High score (>50): Automatically pregenerates wallet and awards LTZ
- User checks balance:
!balanceโ Shows stats - User logs in later: Discovers all their LTZ waiting!
Best Practicesโ
โ Do Thisโ
-
Use server-side only
- Never expose
PRIVY_APP_SECRETto browsers - Run pregeneration from your backend
- Never expose
-
Store provider in database
// Track which OAuth provider you used
database.saveUser({
internalId: 'user_123',
oauthProvider: 'discord', // โ Store this!
oauthUserId: '987654321',
firstRewardedAt: new Date()
}); -
Handle errors gracefully
try {
await rewardUserByOAuth(...);
} catch (error) {
// Don't break your app if Privy is down
console.error('Reward failed:', error);
// Queue for retry later
} -
Notify users
// Tell them about their rewards!
sendDM(userId,
`๐ You earned ${amount} LTZ! ` +
`Log in at loyalteez.app with ${provider} to claim.`
); -
Cache Privy responses
// Don't create the same user multiple times
const cached = cache.get(`privy_${userId}`);
if (cached) return cached.wallet;
const user = await privy.importUser(...);
cache.set(`privy_${userId}`, user, 3600); // 1 hour
โ Don't Do Thisโ
-
Don't use email/username as subject
// โ WRONG - Not deterministic
subject: user.email // Emails can change!
subject: user.username // Usernames can change!
// โ CORRECT - Use platform's unique ID
subject: user.id // Platform ID never changes -
Don't expose App Secret
// โ WRONG - Never in frontend!
const privy = new PrivyClient(appId, appSecret);
// โ CORRECT - Backend only
// Run pregeneration on your server -
Don't skip error handling
// โ WRONG - Unhandled errors
await privy.importUser(...);
// โ CORRECT - Handle failures
try {
await privy.importUser(...);
} catch (error) {
logError(error);
notifyAdmin(error);
} -
Don't forget rate limits
// โ Add rate limiting
const limited = await rateLimiter.check(userId);
if (limited) {
return { error: 'Too many requests' };
}
Troubleshootingโ
Issue: "Invalid subject ID"โ
Cause: Using wrong ID format
Fix: Check the provider's documentation for correct ID format:
// Discord: user.id (string of numbers)
subject: message.author.id // "123456789012345678"
// Twitter: user.id (string)
subject: tweet.user.id_str // "987654321"
// GitHub: user.id (number as string)
subject: String(ghUser.id) // "45678901"
Issue: "User already exists with different linked account"โ
Cause: Trying to link a second OAuth provider to existing user
Fix: Each OAuth provider creates a separate user (by design):
// Pick ONE primary provider per user
// Store in your database which provider you used
const primaryProvider = await db.getUserProvider(internalUserId);
await rewardUserByOAuth(primaryProvider, ...);
Issue: "Wallet not showing rewards"โ
Cause: Different OAuth provider used for pregeneration vs login
Fix: Ensure consistency:
// Database: User's primary provider
{ userId: '123', provider: 'discord', oauthId: '456' }
// Always use same provider
await rewardUserByOAuth('discord', '456', ...); // โ
await rewardUserByOAuth('twitter', '789', ...); // โ Different provider!
Issue: "Rate limited by Privy"โ
Cause: Too many API calls
Fix: Implement caching and rate limiting:
// Cache user wallets
const cache = new Map();
async function getCachedWallet(provider, userId) {
const key = `${provider}_${userId}`;
if (cache.has(key)) return cache.get(key);
const user = await privy.importUser(...);
cache.set(key, user.wallet.address, 3600000); // 1 hour
return user.wallet.address;
}
Testing Guideโ
Step 1: Test Pregenerationโ
// Test script: test-pregeneration.js
async function test() {
console.log('๐งช Testing OAuth pregeneration...\n');
// Test 1: Create new user
console.log('Test 1: First-time pregeneration');
const result1 = await rewardUserByOAuth(
'discord',
'test_' + Date.now(), // Unique test ID
'TestUser',
10
);
console.log('โ
Wallet created:', result1.wallet);
// Test 2: Same user again (should return same wallet)
console.log('\nTest 2: Repeat pregeneration');
const result2 = await rewardUserByOAuth(
'discord',
'test_' + Date.now(),
'TestUser',
10
);
console.log('โ
Same wallet:', result2.wallet === result1.wallet);
console.log('\n๐ Tests passed!');
}
test().catch(console.error);
Step 2: Test Login Flowโ
- Pregenerate a wallet for a test Discord account
- Log into your Loyalteez frontend with that Discord account
- Verify the pregenerated wallet appears
- Check LTZ balance shows rewards
Security Considerationsโ
1. Server-Side Onlyโ
// โ
Call from your backend
fetch('https://register.loyalteez.app/loyalteez-api/pregenerate-user', {
method: 'POST',
body: JSON.stringify({ brand_id, oauth_provider, oauth_user_id })
});
// โ NEVER call from browser/frontend
// Users could pregenerate wallets for anyone!
2. Validate User IDsโ
// โ
Validate before using
function isValidDiscordId(id) {
return /^\d{17,19}$/.test(id); // Discord IDs are 17-19 digits
}
if (!isValidDiscordId(userId)) {
throw new Error('Invalid Discord ID');
}
3. Rate Limitingโ
// โ
Prevent abuse
const attempts = rewardAttempts.get(userId) || 0;
if (attempts > 10) {
throw new Error('Rate limit exceeded');
}
rewardAttempts.set(userId, attempts + 1);
4. Audit Loggingโ
// โ
Log all pregeneration attempts
await auditLog.create({
action: 'oauth_pregeneration',
provider,
userId,
amount,
timestamp: new Date(),
success: true
});
Summary for Non-Technical Peopleโ
What this feature does:
- Lets you reward users based on their social media accounts
- Even if they've never logged into your app before
- When they eventually log in, all their rewards are waiting
Why it's powerful:
- Instant rewards (no login required first)
- Viral growth (users discover rewards when they log in)
- Frictionless onboarding (rewards waiting = incentive to join)
Setup time: 20 minutes (one-time) Maintenance: None (automatic)
Next Stepsโ
-
Get your Brand ID
- Log into your Loyalteez dashboard
- Copy your Brand ID
- Add it to your
.envfile asVITE_LOYALTEEZ_BRAND_ID
-
Choose your integration
- Discord Bot - Community rewards
- Twitter Bot - Social engagement
- GitHub App - Contributor rewards
-
Implement pregeneration
- Copy the code examples from this guide
- Test with a real OAuth account (test credentials won't work)
- Deploy to production
-
Notify users
- Tell them about their rewards
- Include login link:
https://loyalteez.app - Explain they need to log in with the same OAuth provider (Discord, Twitter, etc.)
Related Documentationโ
- Understanding Concepts - Core concepts
- Discord Integration - Discord bot setup
- Custom Integration - Other platforms
- Privy Documentation - Official Privy docs
Questions? Join our Discord: discord.gg/loyalteez
Need help? Email: [email protected]