Skip to main content

Pregeneration API

Create user wallets linked to OAuth accounts before users log in.

🎉 Production Ready: This API is fully operational and battle-tested. It's idempotent, handles both new and existing users gracefully, and requires no Privy credentials from you.

Overview

The Pregeneration API allows you to create wallets for users based on their social media identities (Discord, Twitter, GitHub, etc.) before they log into your app. This enables instant rewards and viral growth mechanics.

No Setup Required: Just call our API - we handle all Privy integration server-side for you!

Production Base URL:

https://register.loyalteez.app

Full Endpoint:

https://register.loyalteez.app/loyalteez-api/pregenerate-user

Alternative (Workers.dev):

https://loyalteez-pregeneration.taylor-cox1199.workers.dev/loyalteez-api/pregenerate-user

POST /loyalteez-api/pregenerate-user

Create a wallet linked to a user's OAuth account.

Request

Headers:

Content-Type: application/json

Body:

{
"brand_id": "your-brand-id",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"oauth_username": "gamer123",
"metadata": {
"source": "game_bot",
"campaign": "launch_week"
}
}

Parameters

FieldTypeRequiredDescription
brand_idstringYesYour Loyalteez Brand ID
oauth_providerstringYesOAuth provider: discord, twitter, github, google, telegram, spotify, instagram, or tiktok
oauth_user_idstringYesUser's unique ID on the OAuth platform (must be real OAuth ID from actual account)
oauth_usernamestringRecommendedUser's username on that platform (validated against real provider formats)
metadataobjectNoAdditional data to store with the pregeneration event

OAuth User ID Formats

ProviderID FormatExampleWhere to Find
Discord17-20 digit snowflake ID"123456789012345678"user.id from Discord API
Twitter/XNumeric string"987654321"user.id_str from Twitter API
GitHubNumeric string"45678901"user.id from GitHub API
GoogleLong numeric string"108012345678901234567"sub claim from OAuth token
TelegramNumeric"123456789"user.id from Telegram Bot API
SpotifyAlphanumeric string"abc123xyz"user.id from Spotify API
InstagramNumeric string"123456789"user.id from Instagram Graph API
TikTokAlphanumeric string"abc123"open_id from TikTok API

Important: All IDs must be from real OAuth accounts. The system validates credentials with the actual OAuth providers and rejects test/fake accounts for security.

Response

Success (200 - New User):

{
"success": true,
"wallet_address": "0x1234567890123456789012345678901234567890",
"privy_user_id": "did:privy:clpr_abc123...",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"created_new": true,
"message": "Wallet created for gamer123",
"next_steps": [
"Use wallet_address to reward user with LTZ",
"User will see rewards when they log in with discord"
]
}

Success (200 - Existing User):

{
"success": true,
"wallet_address": "0x1234567890123456789012345678901234567890",
"privy_user_id": "did:privy:clpr_abc123...",
"oauth_provider": "discord",
"oauth_user_id": "123456789012345678",
"created_new": false,
"message": "Wallet retrieved for existing user gamer123",
"next_steps": [
"Use wallet_address to reward user with LTZ",
"User will see rewards when they log in with discord"
]
}

Note: The created_new field indicates whether a new wallet was created (true) or an existing wallet was retrieved (false). The endpoint is idempotent - calling it multiple times with the same OAuth credentials returns the same wallet address.

Error (400 - Missing Fields):

{
"error": "Missing required fields",
"required": [
"brand_id",
"oauth_provider",
"oauth_user_id"
]
}

Error (400 - Invalid Provider):

{
"error": "Invalid OAuth provider",
"valid_providers": [
"discord",
"twitter",
"github",
"google",
"telegram"
]
}

Error (400 - Invalid Discord ID):

{
"error": "Invalid Discord user ID",
"details": "Discord user IDs must be 17-20 digit numeric strings (snowflake IDs)",
"example": "123456789012345678"
}

Error (400 - Invalid OAuth Credentials):

{
"error": "Invalid OAuth credentials",
"hint": "This endpoint requires REAL OAuth user IDs and usernames from actual discord accounts. Test credentials are rejected by Privy for security.",
"provider": "discord"
}

Error (429 - Rate Limited):

{
"error": "Rate limit exceeded, please try again later"
}

Error (500 - Server Error):

{
"error": "Internal server error",
"details": "Failed to create wallet"
}

Security Note: The pregeneration endpoint validates all OAuth credentials with the actual OAuth providers (Discord, Twitter, GitHub, etc.) to ensure accounts are real. Test/fake credentials will be rejected with helpful error messages.


How It Works

1. Idempotent Wallet Creation

Same OAuth ID = Same Wallet (Always)

The endpoint is idempotent - calling it multiple times with the same OAuth credentials always returns the same wallet address, whether it's creating a new wallet or retrieving an existing one.

// First call - Creates new wallet
POST /pregenerate-user { oauth_provider: "discord", oauth_user_id: "123" }
// Response: { wallet_address: "0xABC...", created_new: true }

// Second call - Returns existing wallet
POST /pregenerate-user { oauth_provider: "discord", oauth_user_id: "123" }
// Response: { wallet_address: "0xABC...", created_new: false }

// User logs in with Discord
// Gets same wallet: 0xABC...

This means:

  • ✅ Safe to call multiple times (no duplicate wallets)
  • ✅ No need to check if wallet exists first
  • ✅ Perfect for reward failsafe systems
  • created_new flag tells you what happened

This is by design - wallets are permanently linked to OAuth accounts for security and consistency.

2. Cross-Platform Considerations

Different OAuth providers = Different wallets:

// Pregenerate with Discord
POST { oauth_provider: "discord", oauth_user_id: "123" }
// Creates wallet A

// Pregenerate with Twitter (same person!)
POST { oauth_provider: "twitter", oauth_user_id: "456" }
// Creates wallet B (different!)

Best practice: Choose one primary OAuth provider per user and always use that.

3. Login Flow

1. You pregenerate wallet for discord:123
2. You award 100 LTZ to that wallet
3. User logs into Loyalteez with Discord
4. Privy recognizes discord:123
5. User sees their pregenerated wallet + 100 LTZ! 🎉

Complete Example Flow

Step 1: Pregenerate Wallet

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"
}'

Response:

{
"success": true,
"wallet_address": "0xABC123...",
"privy_user_id": "did:privy:...",
"message": "Wallet pregenerated for gamer123"
}

Step 2: Award LTZ

curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-d '{
"event": "pregeneration_reward",
"email": "[email protected]",
"metadata": {
"brand_id": "your-brand-id",
"wallet_address": "0xABC123...",
"reward_amount": 100
}
}'

Step 3: Notify User

// In your Discord bot
message.reply(
`🎉 You earned 100 LTZ! ` +
`Log in at https://loyalteez.app with Discord to see your rewards!\n` +
`Your wallet: ${wallet_address}`
);

Rate Limits

LimitValueScope
Pregeneration requests100 per minutePer brand
Daily pregenerations10,000Per brand

If you need higher limits, contact [email protected]


Idempotency & Failsafe Behavior

Calling the pregeneration endpoint multiple times with the same oauth_provider + oauth_user_id:

  • ✅ Returns the same wallet address every time
  • ✅ Does NOT create duplicate wallets
  • ✅ Safe to call multiple times (no side effects)
  • created_new field indicates new vs. existing wallet

Perfect for reward failsafe systems:

// Developer pattern: Always call pregeneration, don't care if new or existing
async function rewardUser(provider, userId, amount) {
// Step 1: Get wallet (creates if new, retrieves if existing)
const { wallet_address, created_new } = await pregenerateWallet(provider, userId);

console.log(created_new ? 'New user!' : 'Returning user!');

// Step 2: Send rewards to wallet
await sendLTZ(wallet_address, amount);
}

This means:

  • You can retry failed requests safely
  • No need to check if wallet exists first
  • Simplifies error handling
  • Works as a "get or create" pattern

Best Practices

✅ DO

  1. Call from your backend/server

    // ✅ Server-side (secure)
    await fetch('https://register.loyalteez.app/loyalteez-api/pregenerate-user', {
    method: 'POST',
    body: JSON.stringify({ brand_id, oauth_provider, oauth_user_id })
    });
  2. Validate OAuth IDs

    // ✅ Validate format
    function isValidDiscordId(id) {
    return /^\d{17,19}$/.test(id);
    }
  3. Store the provider you used

    // ✅ Remember which OAuth provider
    database.saveUser({
    userId: '123',
    primaryOAuthProvider: 'discord', // Store this!
    oauthUserId: '456'
    });
  4. Handle errors gracefully

    // ✅ Don't break if pregeneration fails
    try {
    await pregenerateWallet(...);
    } catch (error) {
    console.error('Pregeneration failed:', error);
    // Queue for retry or log for manual review
    }

❌ DON'T

  1. Don't call from frontend/browser

    // ❌ NEVER - users could pregenerate for anyone!
    // This should only happen server-side
  2. Don't use email/username as ID

    // ❌ WRONG - These can change
    oauth_user_id: user.email
    oauth_user_id: user.username

    // ✅ CORRECT - Platform IDs never change
    oauth_user_id: user.id
  3. Don't mix OAuth providers

    // ❌ Inconsistent - creates different wallets
    pregenerate('discord', '123'); // Wallet A
    pregenerate('twitter', '456'); // Wallet B (different person!)

    // ✅ Consistent - same provider
    const provider = getUserPrimaryProvider(userId);
    pregenerate(provider, oauthId);

Testing

Test Endpoint

curl -X POST https://register.loyalteez.app/loyalteez-api/pregenerate-user \
-H "Content-Type: application/json" \
-d '{
"brand_id": "test-brand",
"oauth_provider": "discord",
"oauth_user_id": "test_'$(date +%s)'",
"oauth_username": "test_user"
}'

Verify Wallet Creation

  1. Note the wallet_address from response
  2. Check on block explorer: https://soneium.blockscout.com/address/{wallet_address}
  3. Log into Loyalteez with the OAuth account
  4. Verify you see the pregenerated wallet

Security

Server-Side Only

⚠️ CRITICAL: Only call this endpoint from your backend server, never from browser/frontend code.

Why?

  • If exposed to frontend, users could pregenerate wallets for anyone
  • Could be abused to create spam accounts
  • No authentication on this endpoint (by design for simplicity)

Solution:

  • Keep pregeneration logic on your server
  • Validate user identity before calling
  • Rate limit your own API

Audit Logging

All pregeneration events are logged with:

  • Brand ID
  • OAuth provider & user ID
  • Timestamp
  • Wallet address created
  • Metadata provided

This helps with:

  • Debugging issues
  • Tracking abuse
  • Compliance
  • Analytics

Troubleshooting

"Invalid OAuth user ID format"

Cause: Wrong ID type or format

Fix:

// Discord: Must be string of 17-19 digits
oauth_user_id: String(discordUser.id) // ✅

// Twitter: Must be string
oauth_user_id: twitterUser.id_str // ✅

// GitHub: Convert number to string
oauth_user_id: String(githubUser.id) // ✅

"Rate limit exceeded"

Cause: Too many requests in short time

Fix:

  • Implement caching (cache wallet addresses for 1 hour)
  • Add request debouncing
  • Contact us for higher limits if needed

"Wallet not showing for user"

Cause: User logged in with different OAuth provider

Fix:

// Make sure you use same provider
const storedProvider = database.getUserProvider(userId);
await pregenerate(storedProvider, oauthId); // ✅ Consistent


Support

Questions? Email [email protected]

Need higher rate limits? Contact us at [email protected]

Found a bug? Report at [email protected]