Skip to main content

Neon API - Wallet Authentication & Balance

Neon EVM blockchain integration for wallet-based authentication and balance checking. Supports MetaMask signature verification and real-time balance queries for native NEON tokens and USDC.

Key Features:

  • Signature-based Authentication: MetaMask wallet verification using ethers.js
  • Dual Balance Checking: Native NEON tokens + USDC token balances
  • Address Validation: Automatic checksum formatting and validation
  • Neon Devnet Integration: Direct RPC connection to Neon blockchain

Network Configuration

Neon Devnet Details

Sign In with Neon Wallet

POST/api/neon/signin

Sign In with Neon Wallet

Authenticate user using MetaMask wallet signature verification. Validates the signature against the provided message and wallet address using ethers.js verifyMessage function.

Parameters

wallet_addressstringrequired

Ethereum wallet address (will be converted to checksum format)

signaturestringrequired

MetaMask signature of the message

messagestringrequired

Original message that was signed

Request Body

{
  "wallet_address": "0x742d35Cc6645C0532F29e3BB0B4b7c0532F29e3B",
  "signature": "0x8f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
  "message": "Sign in to BRDZ with Neon wallet\nTimestamp: 2024-01-15T10:30:00Z\nNonce: abc123"
}

Response

200Signature verified successfully
{
  "success": true,
  "wallet_address": "0x742d35Cc6645C0532F29e3BB0B4b7c0532F29e3B",
  "message": "Signature verified successfully"
}
400Missing required parameters
{
  "error": "Missing wallet_address, signature, or message"
}
401Signature verification failed
{
  "error": "Signature verification failed"
}
500Internal server error
{
  "error": "Internal server error"
}
curl -X POST https://api.brdz.link/api/neon/signin \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
  "wallet_address": "0x742d35Cc6645C0532F29e3BB0B4b7c0532F29e3B",
  "signature": "0x8f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
  "message": "Sign in to BRDZ with Neon wallet\nTimestamp: 2024-01-15T10:30:00Z\nNonce: abc123"
}'

Get Neon Balance

GET/api/neon/balance/{wallet_address}

Get Neon Wallet Balance

Retrieve native NEON token balance and USDC token balance for a specified wallet address. Automatically validates and formats the address to proper checksum format.

Parameters

wallet_addressstringrequired

Ethereum wallet address (path parameter) - will be validated and converted to checksum format

Response

200Balance retrieved successfully
{
  "chain": "neon",
  "address": "0x742d35Cc6645C0532F29e3BB0B4b7c0532F29e3B",
  "wallet_address": "0x742d35Cc6645C0532F29e3BB0B4b7c0532F29e3B",
  "native_balance": "125.456789123456789",
  "usdc_balance": "1000.0"
}
400Invalid wallet address
{
  "error": "Invalid wallet address format"
}
500Balance fetch failed
{
  "error": "Failed to fetch balance",
  "details": "RPC connection timeout"
}
400_invalidAddress validation failed
{
  "error": "Invalid wallet address"
}
curl -X GET https://api.brdz.link/api/neon/balance/0x742d35Cc6645C0532F29e3BB0B4b7c0532F29e3B \
-H "x-api-key: YOUR_API_KEY"

Complete Neon Integration

MetaMask Integration Example

class NeonWalletIntegration {
constructor() {
this.isConnected = false;
this.walletAddress = null;
this.chainId = '0xE9AC0CE'; // 245022926 in hex
}

async checkMetaMaskAvailability() {
if (typeof window.ethereum === 'undefined') {
throw new Error('MetaMask is not installed. Please install MetaMask to continue.');
}
return true;
}

async addNeonNetwork() {
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: this.chainId,
chainName: 'Neon EVM DevNet',
nativeCurrency: {
name: 'NEON',
symbol: 'NEON',
decimals: 18
},
rpcUrls: ['https://245022926.rpc.thirdweb.com/'],
blockExplorerUrls: ['https://neon-devnet.blockscout.com/']
}]
});
console.log('✅ Neon network added successfully');
} catch (error) {
if (error.code === 4902) {
console.log('Neon network already added or rejected by user');
} else {
console.error('❌ Failed to add Neon network:', error);
throw error;
}
}
}

async switchToNeonNetwork() {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: this.chainId }]
});
console.log('✅ Switched to Neon network');
} catch (error) {
if (error.code === 4902) {
// Network not added yet, try to add it
await this.addNeonNetwork();
await this.switchToNeonNetwork();
} else {
console.error('❌ Failed to switch network:', error);
throw error;
}
}
}

async connectWallet() {
try {
await this.checkMetaMaskAvailability();

// Request account access
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});

if (accounts.length === 0) {
throw new Error('No accounts found. Please unlock MetaMask.');
}

this.walletAddress = accounts[0];
this.isConnected = true;

// Check and switch to Neon network
const currentChainId = await window.ethereum.request({
method: 'eth_chainId'
});

if (currentChainId !== this.chainId) {
console.log('Switching to Neon network...');
await this.switchToNeonNetwork();
}

console.log('✅ Wallet connected:', this.walletAddress);
return this.walletAddress;
} catch (error) {
console.error('❌ Wallet connection failed:', error);
throw error;
}
}

async signInWithWallet() {
if (!this.isConnected) {
await this.connectWallet();
}

try {
// Create message to sign
const timestamp = new Date().toISOString();
const nonce = Math.random().toString(36).substring(7);
const message = `Sign in to BRDZ with Neon wallet
Timestamp: ${timestamp}
Nonce: ${nonce}
Address: ${this.walletAddress}`;

console.log('Requesting signature for message:', message);

// Sign message
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, this.walletAddress]
});

console.log('✅ Message signed successfully');

// Verify with API
const response = await fetch('https://api.brdz.link/api/neon/signin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
wallet_address: this.walletAddress,
signature: signature,
message: message
})
});

const result = await response.json();

if (result.success) {
console.log('✅ Authentication successful:', result.wallet_address);

// Store auth data
localStorage.setItem('neon_wallet_address', result.wallet_address);
localStorage.setItem('neon_auth_timestamp', timestamp);

return result;
} else {
throw new Error(result.error || 'Authentication failed');
}
} catch (error) {
console.error('❌ Sign in failed:', error);
throw error;
}
}

async getWalletBalance() {
if (!this.walletAddress) {
throw new Error('Wallet not connected');
}

try {
const response = await fetch(`https://api.brdz.link/api/neon/balance/${this.walletAddress}`, {
headers: {
'x-api-key': 'YOUR_API_KEY'
}
});

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

const balance = await response.json();
console.log('💰 Wallet balance:', balance);
return balance;
} catch (error) {
console.error('❌ Balance fetch failed:', error);
throw error;
}
}

async setupEventListeners() {
if (!window.ethereum) return;

// Account changed
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
console.log('🔓 Wallet disconnected');
this.isConnected = false;
this.walletAddress = null;
localStorage.removeItem('neon_wallet_address');
} else {
console.log('🔄 Account changed to:', accounts[0]);
this.walletAddress = accounts[0];
localStorage.setItem('neon_wallet_address', accounts[0]);
}
});

// Chain changed
window.ethereum.on('chainChanged', (chainId) => {
console.log('🔄 Chain changed to:', chainId);
if (chainId !== this.chainId) {
console.log('⚠️ Please switch back to Neon network');
}
});

// Connection status
window.ethereum.on('connect', (connectInfo) => {
console.log('🔗 MetaMask connected:', connectInfo);
});

window.ethereum.on('disconnect', (error) => {
console.log('🔌 MetaMask disconnected:', error);
this.isConnected = false;
this.walletAddress = null;
});
}

async initializeApp() {
try {
await this.setupEventListeners();

// Check if previously connected
const savedAddress = localStorage.getItem('neon_wallet_address');
if (savedAddress) {
console.log('🔄 Reconnecting to saved wallet:', savedAddress);
await this.connectWallet();
}

console.log('✅ Neon wallet integration initialized');
} catch (error) {
console.error('❌ Initialization failed:', error);
}
}

disconnect() {
this.isConnected = false;
this.walletAddress = null;
localStorage.removeItem('neon_wallet_address');
localStorage.removeItem('neon_auth_timestamp');
console.log('🔓 Wallet disconnected');
}
}

// Usage
const neonWallet = new NeonWalletIntegration();

// Initialize the app
await neonWallet.initializeApp();

// Connect and sign in
try {
await neonWallet.connectWallet();
const authResult = await neonWallet.signInWithWallet();
console.log('Signed in successfully:', authResult);

// Get balance
const balance = await neonWallet.getWalletBalance();
console.log('Current balance:', balance);
} catch (error) {
console.error('Error:', error.message);
}

React Component Example

import React, { useState, useEffect } from 'react';

const NeonWalletConnect = () => {
const [isConnected, setIsConnected] = useState(false);
const [walletAddress, setWalletAddress] = useState('');
const [balance, setBalance] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');

useEffect(() => {
// Check if wallet was previously connected
const savedAddress = localStorage.getItem('neon_wallet_address');
if (savedAddress && window.ethereum) {
setWalletAddress(savedAddress);
setIsConnected(true);
fetchBalance(savedAddress);
}
}, []);

const connectWallet = async () => {
try {
setIsLoading(true);
setError('');

if (!window.ethereum) {
throw new Error('MetaMask not installed');
}

const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});

const address = accounts[0];
setWalletAddress(address);
setIsConnected(true);
localStorage.setItem('neon_wallet_address', address);

await fetchBalance(address);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};

const signIn = async () => {
try {
setIsLoading(true);
setError('');

const timestamp = new Date().toISOString();
const nonce = Math.random().toString(36).substring(7);
const message = `Sign in to BRDZ with Neon wallet\nTimestamp: ${timestamp}\nNonce: ${nonce}`;

const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, walletAddress]
});

const response = await fetch('https://api.brdz.link/api/neon/signin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
wallet_address: walletAddress,
signature: signature,
message: message
})
});

const result = await response.json();

if (result.success) {
alert('✅ Signed in successfully!');
} else {
throw new Error(result.error);
}
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};

const fetchBalance = async (address) => {
try {
const response = await fetch(`https://api.brdz.link/api/neon/balance/${address}`, {
headers: {
'x-api-key': 'YOUR_API_KEY'
}
});

if (response.ok) {
const balanceData = await response.json();
setBalance(balanceData);
} else {
console.error('Failed to fetch balance');
}
} catch (err) {
console.error('Balance fetch error:', err);
}
};

const disconnect = () => {
setIsConnected(false);
setWalletAddress('');
setBalance(null);
localStorage.removeItem('neon_wallet_address');
};

return (
<div className="neon-wallet-connect">
<h2>Neon Wallet Connection</h2>

{error && (
<div className="error">
{error}
</div>
)}

{!isConnected ? (
<button
onClick={connectWallet}
disabled={isLoading}
className="connect-button"
>
{isLoading ? 'Connecting...' : 'Connect Neon Wallet'}
</button>
) : (
<div className="wallet-info">
<p><strong>Connected:</strong> {walletAddress}</p>

{balance && (
<div className="balance-info">
<h3>Balance:</h3>
<p>NEON: {parseFloat(balance.native_balance).toFixed(6)}</p>
<p>USDC: {parseFloat(balance.usdc_balance).toFixed(2)}</p>
</div>
)}

<div className="actions">
<button
onClick={signIn}
disabled={isLoading}
className="sign-in-button"
>
{isLoading ? 'Signing...' : 'Sign In'}
</button>

<button
onClick={() => fetchBalance(walletAddress)}
disabled={isLoading}
className="refresh-button"
>
Refresh Balance
</button>

<button
onClick={disconnect}
className="disconnect-button"
>
Disconnect
</button>
</div>
</div>
)}
</div>
);
};

export default NeonWalletConnect;

Environment Configuration

Required Environment Variables

# Neon Network Configuration
NEON_RPC_URL=https://245022926.rpc.thirdweb.com/
NEON_CHAIN_ID=245022926
USDC_CONTRACT_ADDRESS_NEON=0x... # USDC contract address on Neon

# Network Details
NEON_DEVNET_EXPLORER=https://neon-devnet.blockscout.com/
NATIVE_TOKEN_NEON=NEON

Network Connection Details

// Network configuration for MetaMask
const neonNetworkConfig = {
chainId: '0xE9AC0CE', // 245022926 in hexadecimal
chainName: 'Neon EVM DevNet',
nativeCurrency: {
name: 'NEON',
symbol: 'NEON',
decimals: 18
},
rpcUrls: ['https://245022926.rpc.thirdweb.com/'],
blockExplorerUrls: ['https://neon-devnet.blockscout.com/']
};

Error Handling

Common Error Scenarios

// Comprehensive error handling for Neon integration
class NeonErrorHandler {
static handleSignInError(error) {
if (error.message.includes('User denied message signature')) {
return 'User cancelled the signature request';
} else if (error.message.includes('Signature verification failed')) {
return 'Invalid signature. Please try signing again.';
} else if (error.message.includes('Missing wallet_address')) {
return 'Wallet address is required';
} else if (error.code === 4001) {
return 'User rejected the request';
} else if (error.code === -32602) {
return 'Invalid parameters';
} else {
return `Sign in failed: ${error.message}`;
}
}

static handleBalanceError(error) {
if (error.message.includes('Invalid wallet address')) {
return 'Please provide a valid Ethereum address';
} else if (error.message.includes('Failed to fetch balance')) {
return 'Unable to connect to Neon network. Please try again.';
} else if (error.message.includes('RPC connection')) {
return 'Network connection error. Please check your internet connection.';
} else {
return `Balance fetch failed: ${error.message}`;
}
}

static handleMetaMaskError(error) {
if (error.code === 4902) {
return 'Neon network not added to MetaMask. Please add the network.';
} else if (error.code === 4001) {
return 'User rejected the network switch request';
} else if (error.message.includes('wallet_addEthereumChain')) {
return 'Failed to add Neon network. Please add manually.';
} else {
return `MetaMask error: ${error.message}`;
}
}
}

// Usage in error handling
try {
await neonWallet.signInWithWallet();
} catch (error) {
const friendlyMessage = NeonErrorHandler.handleSignInError(error);
console.error(friendlyMessage);
// Show user-friendly error message
}

Address Validation

// Validate Ethereum addresses before API calls
function validateEthereumAddress(address) {
// Basic format check
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
throw new Error('Invalid address format. Must be a 42-character hexadecimal string starting with 0x');
}

// Additional checksum validation can be added here
return address.toLowerCase();
}

// Safe balance checking with validation
async function safeGetBalance(walletAddress) {
try {
const validatedAddress = validateEthereumAddress(walletAddress);
const balance = await getNeonBalance(validatedAddress);
return balance;
} catch (error) {
const friendlyMessage = NeonErrorHandler.handleBalanceError(error);
console.error(friendlyMessage);
return null;
}
}

Security Considerations

Message Signing Best Practices

  1. Include Timestamp: Prevent replay attacks
  2. Add Nonce: Ensure message uniqueness
  3. Specify Purpose: Clear indication of what's being signed
  4. Include Domain: Prevent cross-domain attacks
// Secure message format
function createSecureMessage(walletAddress, domain = 'BRDZ') {
const timestamp = new Date().toISOString();
const nonce = crypto.randomUUID();

return `Sign in to ${domain} with Neon wallet
Address: ${walletAddress}
Timestamp: ${timestamp}
Nonce: ${nonce}
Chain: Neon EVM DevNet (245022926)`;
}

Private Key Protection

  • Never request private keys in web applications
  • Use MetaMask signing for all signature operations
  • Validate signatures server-side using ethers.js
  • Implement rate limiting for sign-in attempts

MetaMask Integration

Use MetaMask's built-in signing methods for secure wallet authentication. Never request private keys directly.

Network Configuration

Ensure users are connected to the correct Neon network (Chain ID: 245022926) before performing transactions.

Balance Precision

Native NEON balances are returned with full precision (18 decimals). Format appropriately for display purposes.