Solana Web3.js
Nexgent uses @solana/web3.js for all Solana blockchain interactions including wallet management, balance queries, and transaction signing.
Overview
| Feature | Usage |
|---|---|
| Wallet Keypairs | Load and manage wallet keys |
| Balance Queries | Check on-chain SOL balances |
| Transaction Signing | Sign swap transactions |
| RPC Connection | Communicate with Solana network |
Configuration
# Solana RPC URL (mainnet)
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
# Wallet private keys (base58 encoded)
WALLET_1=your-base58-private-key
WALLET_2=another-private-key⚠️
Never commit private keys to version control. Use environment variables or a secrets manager.
Wallet Management
Wallet Loader
Loads wallets from environment variables at startup:
// infrastructure/wallets/wallet-loader.ts
import { Keypair } from '@solana/web3.js';
import bs58 from 'bs58';
export class WalletLoader {
loadWallets(): { wallets: Map<string, Keypair>; errors: WalletError[] } {
const wallets = new Map<string, Keypair>();
const errors: WalletError[] = [];
// Load WALLET_1, WALLET_2, etc.
for (let i = 1; i <= 10; i++) {
const envKey = `WALLET_${i}`;
const privateKey = process.env[envKey];
if (privateKey) {
try {
// Decode base58 private key
const secretKey = bs58.decode(privateKey);
const keypair = Keypair.fromSecretKey(secretKey);
const publicKey = keypair.publicKey.toBase58();
wallets.set(publicKey, keypair);
console.log(`✅ Loaded wallet ${i}: ${publicKey.slice(0, 8)}...`);
} catch (error) {
errors.push({
envKey,
error: 'Invalid private key format',
});
}
}
}
return { wallets, errors };
}
}Wallet Store
Stores keypairs in memory for transaction signing:
// infrastructure/wallets/wallet-store.ts
class WalletStore {
private wallets: Map<string, Keypair> = new Map();
initialize(wallets: Map<string, Keypair>): void {
this.wallets = wallets;
console.log(`💼 Wallet store initialized with ${wallets.size} wallets`);
}
getKeypair(walletAddress: string): Keypair | undefined {
return this.wallets.get(walletAddress);
}
hasWallet(walletAddress: string): boolean {
return this.wallets.has(walletAddress);
}
getAddresses(): string[] {
return Array.from(this.wallets.keys());
}
}
export const walletStore = new WalletStore();Balance Service
Queries on-chain SOL balances via RPC:
// infrastructure/external/solana/solana-balance-service.ts
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
class SolanaBalanceService {
private connection: Connection | null = null;
initialize(rpcUrl?: string): void {
const url = rpcUrl || process.env.SOLANA_RPC_URL
|| 'https://api.mainnet-beta.solana.com';
this.connection = new Connection(url, 'confirmed');
console.log(`✅ Solana RPC initialized: ${url}`);
}
async getOnChainBalance(walletAddress: string): Promise<OnChainBalanceResult> {
// Validate address
if (!this.isValidAddress(walletAddress)) {
throw new SolanaBalanceServiceError(
`Invalid address: ${walletAddress}`,
'INVALID_ADDRESS'
);
}
// Reject simulation wallets
if (walletAddress.startsWith('sim_')) {
throw new SolanaBalanceServiceError(
'Cannot check balance for simulation wallets',
'SIMULATION_WALLET'
);
}
const publicKey = new PublicKey(walletAddress);
const balanceLamports = await this.connection!.getBalance(publicKey);
const balanceSol = balanceLamports / LAMPORTS_PER_SOL;
return {
walletAddress,
balanceLamports,
balanceSol,
};
}
isValidAddress(address: string): boolean {
try {
new PublicKey(address);
return true;
} catch {
return false;
}
}
}Transaction Signing
Jupiter returns base64-encoded transactions that need to be signed:
// In JupiterSwapProvider
private signTransaction(base64Transaction: string, keypair: Keypair): string {
// Decode base64 to buffer
const transactionBuffer = Buffer.from(base64Transaction, 'base64');
// Deserialize (Jupiter uses VersionedTransaction v0)
const transaction = VersionedTransaction.deserialize(transactionBuffer);
// Sign with wallet keypair
transaction.sign([keypair]);
// Serialize and encode back to base64
const signedBuffer = transaction.serialize();
return Buffer.from(signedBuffer).toString('base64');
}Token Metadata Service
Fetches SPL token metadata from on-chain:
// infrastructure/external/solana/token-metadata-service.ts
import { Connection, PublicKey } from '@solana/web3.js';
class TokenMetadataService {
private connection: Connection;
async getTokenSymbol(tokenAddress: string): Promise<string | null> {
try {
const mintAddress = new PublicKey(tokenAddress);
// Get token account info
const accountInfo = await this.connection.getAccountInfo(mintAddress);
if (!accountInfo) {
return null;
}
// Parse metadata from account data
// (Implementation depends on token metadata program)
return symbol;
} catch (error) {
console.error(`Failed to fetch metadata for ${tokenAddress}`);
return null;
}
}
}RPC Connection
Connection Configuration
import { Connection, Commitment } from '@solana/web3.js';
// Create connection with commitment level
const connection = new Connection(
process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
'confirmed' // Commitment level
);Commitment Levels
| Level | Description | Use Case |
|---|---|---|
processed | Fastest, may be rolled back | Not recommended |
confirmed | Supermajority confirmation | Balance queries |
finalized | Fully finalized | Critical operations |
Nexgent uses confirmed commitment for balance queries. Transaction confirmations from Jupiter use their own confirmation mechanism.
Key Types
Keypair
import { Keypair } from '@solana/web3.js';
// From secret key (byte array)
const keypair = Keypair.fromSecretKey(secretKey);
// Get public key (wallet address)
const address = keypair.publicKey.toBase58();
// Sign data
transaction.sign([keypair]);PublicKey
import { PublicKey } from '@solana/web3.js';
// From string (base58)
const publicKey = new PublicKey('So11111111111111111111111111111111111111112');
// SOL mint address constant
const SOL_MINT = new PublicKey('So11111111111111111111111111111111111111112');VersionedTransaction
import { VersionedTransaction } from '@solana/web3.js';
// Deserialize from bytes
const transaction = VersionedTransaction.deserialize(buffer);
// Sign
transaction.sign([keypair]);
// Serialize
const bytes = transaction.serialize();Constants
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
// Convert lamports to SOL
const sol = lamports / LAMPORTS_PER_SOL; // 1 SOL = 1,000,000,000 lamports
// Convert SOL to lamports
const lamports = sol * LAMPORTS_PER_SOL;
// SOL mint address (Wrapped SOL)
const SOL_MINT_ADDRESS = 'So11111111111111111111111111111111111111112';Error Handling
| Error | Cause | Resolution |
|---|---|---|
INVALID_ADDRESS | Malformed wallet address | Check address format |
SIMULATION_WALLET | Tried to query sim wallet | Use real addresses for live mode |
RPC_ERROR | RPC connection failed | Check RPC URL, try again |
WALLET_NOT_FOUND | Keypair not in store | Check env vars, restart |
Security
Private Key Handling
- Never store in database - Keys are in env vars only
- Memory only - Loaded at startup, stored in Map
- No logging - Never log private keys
- Limited access - Only
walletStore.getKeypair()returns keypairs
// Safe: Return keypair only for signing
const keypair = walletStore.getKeypair(walletAddress);
// Never: Don't expose or log
console.log(keypair.secretKey); // ❌ NEVER