/** * TETSUO Wallet SDK - RPC Client */ import axios, { AxiosInstance } from 'axios'; import { Balance, Transaction, BlockchainInfo, TransactionResult, UTXO, RPCError } from './types'; export class TetsuoRPC { private client: AxiosInstance; private baseURL: string; constructor(networkUrl: string) { this.baseURL = networkUrl; this.client = axios.create({ baseURL: networkUrl, timeout: 30000, headers: { 'Content-Type': 'application/json' } }); } /** * Get blockchain info */ async getBlockchainInfo(): Promise { try { const response = await this.client.get('/api/blockchain/info'); return { blockHeight: response.data.blockHeight || 0, difficulty: response.data.difficulty || 0, networkName: response.data.networkName || 'TETSUO', isConnected: true }; } catch (error) { throw this.handleError('Failed to get blockchain info', error); } } /** * Get balance for an address */ async getBalance(address: string): Promise { try { const response = await this.client.get(`/api/wallet/balance/${address}`); return response.data.balance || 0; } catch (error) { throw this.handleError(`Failed to get balance for ${address}`, error); } } /** * Get detailed balance info */ async getDetailedBalance(address: string): Promise { try { const response = await this.client.get(`/api/wallet/balance/${address}`); return { confirmed: response.data.confirmed || 0, unconfirmed: response.data.unconfirmed || 0, total: response.data.balance || 0 }; } catch (error) { throw this.handleError(`Failed to get detailed balance`, error); } } /** * Get UTXOs for an address */ async getUTXOs(address: string): Promise { try { const response = await this.client.get(`/api/wallet/utxos/${address}`); // Map server response to UTXO type (server uses 'amount', SDK expects 'value') if (Array.isArray(response.data.utxos)) { return response.data.utxos.map((utxo: any) => ({ txid: utxo.txid, vout: utxo.vout, value: utxo.amount ? Math.floor(utxo.amount * 100_000_000) : 0, // Convert TETSUO to satoshis confirmations: utxo.confirmations || 0, scriptPubKey: utxo.scriptPubKey })); } return []; } catch (error) { throw this.handleError(`Failed to get UTXOs for ${address}`, error); } } /** * Get transaction history for an address */ async getTransactionHistory(address: string): Promise { try { const response = await this.client.get(`/api/wallet/transactions/${address}`); if (!Array.isArray(response.data.transactions)) { return []; } return response.data.transactions.map((tx: any) => ({ txid: tx.txid, amount: tx.amount || 0, isIncoming: tx.isIncoming === true, confirmations: tx.confirmations || 0, timestamp: (tx.timestamp || Math.floor(Date.now() / 1000)) * 1000, // Convert to milliseconds address: tx.address, fee: tx.fee || undefined })); } catch (error) { // Return empty array if endpoint not available return []; } } /** * Get transaction details */ async getTransaction(txid: string): Promise { try { const response = await this.client.get(`/api/transaction/${txid}`); return response.data; } catch (error) { throw this.handleError(`Failed to get transaction ${txid}`, error); } } /** * Broadcast a signed transaction */ async broadcastTransaction(transactionHex: string): Promise { try { const response = await this.client.post('/api/wallet/broadcast', { hex: transactionHex }); if (!response.data.txid) { throw new Error('No transaction ID returned'); } return response.data.txid; } catch (error) { throw this.handleError('Failed to broadcast transaction', error); } } /** * Estimate fee for transaction * Returns fee in satoshis, with safe fallback */ async estimateFee(inputCount: number, outputCount: number): Promise { const SAFE_MIN_FEE = 150000; // 0.0015 TETSUO - safe fallback try { const response = await this.client.get('/api/fee/estimate', { params: { inputCount, outputCount } }); const fee = response.data.fee || SAFE_MIN_FEE; // Always return at least the safe minimum return Math.max(fee, SAFE_MIN_FEE); } catch (error) { // Return safe fallback if endpoint fails return SAFE_MIN_FEE; } } /** * Check if address is valid */ async validateAddress(address: string): Promise { try { const response = await this.client.get(`/api/address/validate/${address}`); return response.data.valid || false; } catch (error) { return false; } } /** * Get address details */ async getAddressInfo(address: string): Promise { try { const response = await this.client.get(`/api/address/${address}`); return response.data; } catch (error) { throw this.handleError(`Failed to get address info`, error); } } /** * Health check */ async ping(): Promise { try { const response = await this.client.get('/api/ping'); return response.status === 200; } catch (error) { return false; } } /** * Private method to handle errors */ private handleError(message: string, error: any): RPCError { if (axios.isAxiosError(error)) { const status = error.response?.status || 0; const errorMsg = error.response?.data?.error || error.message; return new RPCError(`${message}: ${errorMsg}`, status); } return new RPCError(`${message}: ${(error as Error).message}`); } } /** * Create an RPC client instance */ export function createRPCClient(networkUrl: string = 'http://localhost:8080'): TetsuoRPC { return new TetsuoRPC(networkUrl); }