Skip to main content
This guide walks you through migrating your existing user base to Snag’s loyalty system. Whether you’re moving from another platform or setting up Snag for the first time, this guide will help you successfully migrate your users.

Prerequisites

Before starting your migration:
Have your Snag API key ready (see Getting Started)
Export your user data from your existing system
Ensure all users have wallet addresses (or generate them first)

Migration Strategy

Follow these steps to migrate your users systematically:
1

Prepare your user data

Export your user data and ensure you have wallet addresses for each user.
Users without wallet addresses cannot be created in Snag. If your users don’t have wallet addresses, see our wallet generation guide for guidance on how to create them.

Required Data

  • walletAddress (required) - The unique blockchain wallet address

Optional Metadata

  • externalIdentifier - Your internal user ID (highly recommended)
  • displayName - User’s display name
  • emailAddress - User’s email address
  • discordUser - Discord username
  • discordUserId - Discord user ID
  • twitterUser - Twitter/X username
  • twitterUserId - Twitter/X user ID
  • telegramUsername - Telegram username
  • telegramUserId - Telegram user ID
  • logoUrl - URL to user’s avatar/profile picture
2

Set up your migration script

Create a script to process your users in batches. Here’s a complete example:
import SnagSolutions from '@snagsolutions/sdk'

const client = new SnagSolutions({
  apiKey: process.env.SNAG_API_KEY,
})

interface YourUser {
  id: string
  walletAddress: string
  name: string
  email: string
  // ... other fields
}

async function migrateUsers(users: YourUser[]) {
  const results = {
    successful: 0,
    failed: 0,
    errors: [] as Array<{ userId: string; error: string }>,
  }

  for (const user of users) {
    try {
      await client.users.createMetadata({
        walletAddress: user.walletAddress,
        externalIdentifier: user.id,
        displayName: user.name,
        emailAddress: user.email,
      })
      
      results.successful++
      console.log(`✓ Migrated user ${user.id}`)
    } catch (error) {
      results.failed++
      results.errors.push({
        userId: user.id,
        error: error.message,
      })
      console.error(`✗ Failed to migrate user ${user.id}:`, error.message)
    }
  }

  return results
}

// Usage
const users = await fetchYourUsers()
const results = await migrateUsers(users)
console.log(`Migration complete: ${results.successful} successful, ${results.failed} failed`)
3

Process in batches

For large migrations, process users in batches to avoid rate limits and make the process more manageable:
async function migrateInBatches(
  users: YourUser[],
  batchSize: number = 100,
  delayMs: number = 1000
) {
  const totalBatches = Math.ceil(users.length / batchSize)
  
  for (let i = 0; i < totalBatches; i++) {
    const start = i * batchSize
    const end = start + batchSize
    const batch = users.slice(start, end)
    
    console.log(`Processing batch ${i + 1}/${totalBatches}`)
    await migrateUsers(batch)
    
    // Add delay between batches to respect rate limits
    if (i < totalBatches - 1) {
      await new Promise(resolve => setTimeout(resolve, delayMs))
    }
  }
}
Start with a small batch (10-50 users) to verify your migration script works correctly before processing your entire user base.
4

Verify migration

After migration, verify that all users were created successfully:
async function verifyMigration(externalIds: string[]) {
  const notFound: string[] = []
  
  for (const externalId of externalIds) {
    try {
      // Query by external identifier
      const users = await client.users.list({
        externalIdentifier: externalId,
      })
      
      if (users.length === 0) {
        notFound.push(externalId)
      }
    } catch (error) {
      console.error(`Error checking user ${externalId}:`, error)
      notFound.push(externalId)
    }
  }
  
  if (notFound.length > 0) {
    console.log(`Missing users: ${notFound.length}`)
    console.log(notFound)
  } else {
    console.log('✓ All users verified successfully')
  }
  
  return notFound
}
5

Handle failed migrations

Review and retry failed migrations:
async function retryFailed(errors: Array<{ userId: string; error: string }>) {
  console.log(`Retrying ${errors.length} failed migrations...`)
  
  const usersToRetry = await fetchUsersById(errors.map(e => e.userId))
  const results = await migrateUsers(usersToRetry)
  
  return results
}
Common errors and solutions:
  • Invalid wallet address - Verify the wallet address format
  • Duplicate user - User already exists, use update instead
  • Rate limit - Increase delay between batches
  • Authentication error - Check your API key

Migration Best Practices

Before migrating your entire user base, test with a small subset (10-100 users) to identify any issues with your data format or migration script.
Always include the externalIdentifier field mapping to your internal user ID. This makes it easy to:
  • Query Snag users from your system
  • Link Snag data back to your users
  • Debug migration issues
  • Avoid duplicate migrations
Log all migration attempts with timestamps, user IDs, and results. This helps with debugging and provides an audit trail.
Have a rollback strategy in case something goes wrong. Keep track of which users were migrated so you can clean up if needed.
The metadata endpoint is idempotent - calling it multiple times with the same wallet address updates the user. This means you can safely re-run your migration script.
Watch for errors, rate limits, and performance issues during migration. Be prepared to pause and adjust your approach if needed.

Handling Users Without Wallet Addresses

If some of your users don’t have wallet addresses, you have several options:

Generate Wallets

Generate wallet addresses programmatically using viem or smart wallet providers

Prompt Users

Ask users to connect their wallets during their next login

Gradual Migration

Migrate users as they connect wallets, rather than all at once

Email-Based

Use email-to-wallet services to create wallets tied to email addresses

Complete Migration Example

Here’s a complete, production-ready migration script:
import SnagSolutions from '@snagsolutions/sdk'
import { writeFileSync } from 'fs'

const client = new SnagSolutions({
  apiKey: process.env.SNAG_API_KEY,
})

interface MigrationResult {
  timestamp: string
  totalUsers: number
  successful: number
  failed: number
  errors: Array<{ userId: string; error: string }>
}

async function runMigration(): Promise<MigrationResult> {
  console.log('Starting user migration...')
  
  // Fetch your users
  const users = await fetchYourUsers()
  console.log(`Found ${users.length} users to migrate`)
  
  const result: MigrationResult = {
    timestamp: new Date().toISOString(),
    totalUsers: users.length,
    successful: 0,
    failed: 0,
    errors: [],
  }
  
  // Process in batches
  const batchSize = 100
  const totalBatches = Math.ceil(users.length / batchSize)
  
  for (let i = 0; i < totalBatches; i++) {
    const start = i * batchSize
    const end = start + batchSize
    const batch = users.slice(start, end)
    
    console.log(`\nProcessing batch ${i + 1}/${totalBatches} (${batch.length} users)`)
    
    for (const user of batch) {
      try {
        await client.users.createMetadata({
          walletAddress: user.walletAddress,
          externalIdentifier: user.id,
          displayName: user.name,
          emailAddress: user.email,
          discordUser: user.discordUsername,
          twitterUser: user.twitterUsername,
        })
        
        result.successful++
        process.stdout.write('.')
      } catch (error) {
        result.failed++
        result.errors.push({
          userId: user.id,
          error: error.message,
        })
        process.stdout.write('✗')
      }
    }
    
    // Rate limit protection
    if (i < totalBatches - 1) {
      await new Promise(resolve => setTimeout(resolve, 1000))
    }
  }
  
  // Save results
  const filename = `migration-${Date.now()}.json`
  writeFileSync(filename, JSON.stringify(result, null, 2))
  
  console.log(`\n\nMigration complete!`)
  console.log(`✓ Successful: ${result.successful}`)
  console.log(`✗ Failed: ${result.failed}`)
  console.log(`Results saved to: ${filename}`)
  
  return result
}

// Run migration
runMigration()
  .then(() => process.exit(0))
  .catch(error => {
    console.error('Migration failed:', error)
    process.exit(1)
  })

Next Steps