Skip to main content

Set Primary Wallet API

Overview​

The Set Primary Wallet API allows authenticated users to change their primary wallet address. This endpoint swaps the current primary wallet with one of the user's additional wallets, maintaining the wallet history and encrypted private keys.

Endpoint Details​

  • URL: /api/wallet/set-primary
  • Method: POST
  • Authentication: Required (NextAuth.js session)
  • Content-Type: application/json

Authentication​

This endpoint requires user authentication via NextAuth.js session:

import { getSession } from 'next-auth/react'

const session = await getSession()
if (!session) {
// Handle unauthenticated state
}

Request Body​

FieldTypeRequiredDescription
addressstringYesThe wallet address to set as primary

Example Request​

{
"address": "0x742d35cc6cf32a532a4ac7b6c3b8b5e8f9e2cfe7"
}

Response Format​

Success Response (200)​

{
"message": "Primary wallet updated successfully"
}

Already Primary Response (200)​

{
"message": "This wallet is already the primary wallet"
}

Error Responses​

Unauthorized (401)​

{
"error": "Unauthorized"
}

Bad Request (400)​

{
"error": "Address is required"
}

Not Found (404)​

{
"error": "User not found"
}
{
"error": "Wallet not found"
}

Internal Server Error (500)​

{
"error": "Failed to set primary wallet"
}

Implementation Examples​

React Component with Form​

import { useState } from 'react'
import { useSession } from 'next-auth/react'

interface Wallet {
address: string
encryptedPrivateKey: string
createdAt: Date
}

function PrimaryWalletSelector({ additionalWallets }: { additionalWallets: Wallet[] }) {
const { data: session } = useSession()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null)

const handleSetPrimary = async (address: string) => {
if (!session) return

setLoading(true)
setError(null)
setSuccess(null)

try {
const response = await fetch('/api/wallet/set-primary', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ address }),
})

const data = await response.json()

if (!response.ok) {
throw new Error(data.error || 'Failed to set primary wallet')
}

setSuccess(data.message)
// Optionally refresh the page or update state
window.location.reload()
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error occurred')
} finally {
setLoading(false)
}
}

return (
<div className="space-y-4">
<h3 className="text-lg font-semibold">Additional Wallets</h3>

{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}

{success && (
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
{success}
</div>
)}

<div className="space-y-2">
{additionalWallets.map((wallet) => (
<div key={wallet.address} className="flex items-center justify-between p-3 border rounded">
<span className="font-mono text-sm">
{wallet.address.slice(0, 6)}...{wallet.address.slice(-4)}
</span>
<button
onClick={() => handleSetPrimary(wallet.address)}
disabled={loading}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? 'Setting...' : 'Set as Primary'}
</button>
</div>
))}
</div>
</div>
)
}

Server Action (Next.js 13+)​

'use server'

import { auth } from '@/auth'
import { redirect } from 'next/navigation'

export async function setPrimaryWalletAction(formData: FormData) {
const session = await auth()

if (!session) {
redirect('/auth/signin')
}

const address = formData.get('address') as string

if (!address) {
throw new Error('Wallet address is required')
}

const response = await fetch(`${process.env.NEXTAUTH_URL}/api/wallet/set-primary`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': request.headers.get('cookie') || '',
},
body: JSON.stringify({ address }),
})

if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Failed to set primary wallet')
}

// Redirect to wallet page with success message
redirect('/wallet?success=primary-updated')
}

cURL Example​

curl -X POST https://ring.ck.ua/api/wallet/set-primary \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=your-session-token" \
-d '{
"address": "0x742d35cc6cf32a532a4ac7b6c3b8b5e8f9e2cfe7"
}'

Business Logic​

The endpoint performs the following operations:

  1. Authentication Check: Verifies user session
  2. Input Validation: Ensures address is provided
  3. User Data Retrieval: Fetches current user data from Firestore
  4. Primary Check: Verifies if requested wallet is already primary
  5. Wallet Lookup: Finds the wallet in additional wallets list
  6. Wallet Swap: Swaps primary and selected additional wallet
  7. Database Update: Updates user document in Firestore
  8. Cache Invalidation: Clears wallet list cache for the user

Database Schema​

Before Update​

{
"userId": "user123",
"walletAddress": "0x123...abc",
"encryptedPrivateKey": "encrypted_key_1",
"additionalWallets": [
{
"address": "0x742...fe7",
"encryptedPrivateKey": "encrypted_key_2",
"createdAt": "2024-01-15T10:30:00Z"
}
]
}

After Update​

{
"userId": "user123",
"walletAddress": "0x742...fe7",
"encryptedPrivateKey": "encrypted_key_2",
"additionalWallets": [
{
"address": "0x123...abc",
"encryptedPrivateKey": "encrypted_key_1",
"createdAt": "2024-01-10T08:15:00Z"
}
]
}

Security Considerations​

  • Authentication Required: Only authenticated users can change primary wallets
  • Ownership Verification: Users can only set their own additional wallets as primary
  • Private Key Security: Encrypted private keys are safely transferred during swap
  • Cache Management: User-specific cache is cleared to prevent stale data

Performance Notes​

  • Cache Invalidation: Automatically clears wallet list cache for updated user
  • Atomic Operations: Uses Firestore transactions for data consistency
  • Minimal Data Transfer: Only necessary fields are updated in the database

Rate Limiting​

  • User-based: 10 requests per minute per authenticated user
  • Global: 1000 requests per minute across all users

Error Handling​

The API implements comprehensive error handling:

  • Input validation errors return 400 status
  • Authentication errors return 401 status
  • Missing resource errors return 404 status
  • Server errors return 500 status with generic messages
  • All errors are logged for debugging

Testing​

Unit Test Example​

import { POST } from '@/app/api/wallet/set-primary/route'
import { NextRequest } from 'next/server'

// Mock authentication
jest.mock('@/auth')

describe('/api/wallet/set-primary', () => {
it('should set primary wallet successfully', async () => {
// Mock authenticated session
(auth as jest.Mock).mockResolvedValue({
user: { id: 'user123' }
})

const request = new NextRequest('http://localhost:3000/api/wallet/set-primary', {
method: 'POST',
body: JSON.stringify({
address: '0x742d35cc6cf32a532a4ac7b6c3b8b5e8f9e2cfe7'
})
})

const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(200)
expect(data.message).toBe('Primary wallet updated successfully')
})

it('should return error for missing address', async () => {
const request = new NextRequest('http://localhost:3000/api/wallet/set-primary', {
method: 'POST',
body: JSON.stringify({})
})

const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(400)
expect(data.error).toBe('Address is required')
})
})