// In renderer process (web page). const { ipcRenderer } = require("electron"); class PaperclipBlockchain { constructor() { this.isConnected = false; this.gasPrice = 1; // 1 CLIP per gas unit this.gasLimits = { 'transfer': 21000, 'contract_call': 50000, 'contract_deploy': 100000, 'multisig_create': 75000, 'multisig': 35000 }; } async getStatus() { try { const result = await ipcRenderer.invoke("paperclip-get-status"); this.isConnected = true; return result; } catch (error) { this.isConnected = false; throw error; } } async getBlock(blockHeight, clbError, clbSuccess) { try { const block = await ipcRenderer.invoke("paperclip-get-block", blockHeight); clbSuccess(block); } catch (error) { clbError(error); } } async getBalance(address, clbError, clbSuccess) { try { const balance = await ipcRenderer.invoke("paperclip-get-balance", address); clbSuccess(balance); } catch (error) { clbError(error); } } async getNonce(address, clbError, clbSuccess) { try { const nonce = await ipcRenderer.invoke("paperclip-get-nonce", address); clbSuccess(nonce); } catch (error) { clbError(error); } } isAddress(address) { // CLIPS address validation: CLIP- followed by 64 hex characters if (!address || typeof address !== 'string') { return false; } return /^CLIP-[0-9A-Fa-f]{64}$/.test(address); } async getTransaction(txHash, clbError, clbSuccess) { try { const transaction = await ipcRenderer.invoke("paperclip-get-transaction", txHash); clbSuccess(transaction); } catch (error) { clbError(error); } } async getTransactionFee(fromAddress, toAddress, amount, txType = 'transfer', clbError, clbSuccess) { try { // Calculate gas needed based on transaction type const gasNeeded = this.gasLimits[txType] || this.gasLimits['transfer']; const fee = gasNeeded * this.gasPrice; clbSuccess({ gasNeeded: gasNeeded, gasPrice: this.gasPrice, totalFee: fee }); } catch (error) { clbError(error); } } async prepareTransaction(privateKey, fromAddress, toAddress, amount, txType = 'transfer', data = '', clbError, clbSuccess) { try { // Get current nonce const nonce = await ipcRenderer.invoke("paperclip-get-nonce", fromAddress); // Calculate gas const gasNeeded = this.gasLimits[txType] || this.gasLimits['transfer']; // Create transaction object const ClipsCrypto = require('../modules/clips-crypto.js'); const transaction = ClipsCrypto.createTransaction({ sender: fromAddress, receiver: toAddress, amount: parseInt(amount), nonce: nonce, type: txType, data: data, privateKey: privateKey }); // Add gas information transaction.gas = gasNeeded; transaction.gasPrice = this.gasPrice; transaction.fee = gasNeeded * this.gasPrice; clbSuccess(transaction); } catch (error) { clbError(error); } } async sendTransaction(transaction, clbError, clbSuccess) { try { // Convert transaction to hex for broadcasting const ClipsCrypto = require('../modules/clips-crypto.js'); const txHex = ClipsCrypto.transactionToHex(transaction); // Broadcast transaction const result = await ipcRenderer.invoke("paperclip-broadcast-tx", txHex); if (result.success) { clbSuccess({ hash: result.hash, height: result.height }); } else { clbError(result.error || "Transaction failed"); } } catch (error) { clbError(error); } } async getAccountsData(clbError, clbSuccess) { try { const rendererData = { sumBalance: 0, addressData: [] }; // Get stored wallets from database const wallets = PaperclipDatabase.getWallets(); if (!wallets || !wallets.addresses || wallets.addresses.length === 0) { clbSuccess(rendererData); return; } // Get balance for each address let processedCount = 0; const totalAddresses = wallets.addresses.length; for (let i = 0; i < wallets.addresses.length; i++) { const address = wallets.addresses[i]; const walletName = wallets.names[address] || `Account ${i + 1}`; try { const balance = await ipcRenderer.invoke("paperclip-get-balance", address); rendererData.addressData.push({ address: address, name: walletName, balance: balance, balanceFormatted: this.formatBalance(balance) }); rendererData.sumBalance += balance; } catch (error) { console.error(`Failed to get balance for ${address}:`, error); rendererData.addressData.push({ address: address, name: walletName, balance: 0, balanceFormatted: "0 CLIPS", error: true }); } processedCount++; // If all addresses processed, return data if (processedCount === totalAddresses) { rendererData.sumBalanceFormatted = this.formatBalance(rendererData.sumBalance); clbSuccess(rendererData); } } } catch (error) { clbError(error); } } formatBalance(balance) { if (balance === 0) { return "0 CLIPS"; } // Format balance with appropriate decimal places if (balance >= 1000000) { return (balance / 1000000).toFixed(2) + "M CLIPS"; } else if (balance >= 1000) { return (balance / 1000).toFixed(2) + "K CLIPS"; } else { return balance.toLocaleString() + " CLIPS"; } } async getNetworkInfo(clbError, clbSuccess) { try { const status = await this.getStatus(); const networkInfo = { nodeInfo: status.result.node_info, syncInfo: status.result.sync_info, validatorInfo: status.result.validator_info, chainId: status.result.node_info.network, latestBlockHeight: status.result.sync_info.latest_block_height, catching_up: status.result.sync_info.catching_up }; clbSuccess(networkInfo); } catch (error) { clbError(error); } } async getContractInfo(contractAddress, clbError, clbSuccess) { try { const contractInfo = await ipcRenderer.invoke("paperclip-get-contract", contractAddress); clbSuccess(contractInfo); } catch (error) { clbError(error); } } async getMultisigInfo(multisigAddress, clbError, clbSuccess) { try { const multisigInfo = await ipcRenderer.invoke("paperclip-get-multisig", multisigAddress); clbSuccess(multisigInfo); } catch (error) { clbError(error); } } async getFeePool(clbError, clbSuccess) { try { const feePool = await ipcRenderer.invoke("paperclip-get-fee-pool"); clbSuccess({ totalFees: feePool, formatted: this.formatBalance(feePool) }); } catch (error) { clbError(error); } } // Utility method to validate transaction before sending validateTransaction(transaction) { const errors = []; if (!this.isAddress(transaction.sender)) { errors.push("Invalid sender address"); } if (!this.isAddress(transaction.receiver)) { errors.push("Invalid receiver address"); } if (!transaction.amount || transaction.amount <= 0) { errors.push("Invalid amount"); } if (transaction.nonce < 0) { errors.push("Invalid nonce"); } if (!transaction.signature) { errors.push("Transaction not signed"); } return { isValid: errors.length === 0, errors: errors }; } } // Create global instance const PaperclipChain = new PaperclipBlockchain(); // Make it available globally (maintaining compatibility with existing code) window.PaperclipChain = PaperclipChain;