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β
Field | Type | Required | Description |
---|---|---|---|
address | string | Yes | The 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:
- Authentication Check: Verifies user session
- Input Validation: Ensures address is provided
- User Data Retrieval: Fetches current user data from Firestore
- Primary Check: Verifies if requested wallet is already primary
- Wallet Lookup: Finds the wallet in additional wallets list
- Wallet Swap: Swaps primary and selected additional wallet
- Database Update: Updates user document in Firestore
- 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')
})
})
Related Endpointsβ
GET /api/wallet/list
- List all user walletsPOST /api/wallet/create
- Create new walletGET /api/wallet/balance
- Check wallet balancePOST /api/wallet/transfer
- Transfer funds between wallets