const {app, dialog, ipcMain} = require("electron"); const path = require("path"); const fs = require("fs"); const fetch = require("node-fetch"); class PaperclipRPC { constructor() { this.isConnected = false; this.rpcUrl = "http://localhost:26657"; this.logEvents = false; // create the user data dir (needed for MacOS) if (!fs.existsSync(app.getPath("userData"))) { fs.mkdirSync(app.getPath("userData")); } if (this.logEvents) { this.logStream = fs.createWriteStream(path.join(app.getPath("userData"), "paperclip-rpc.log"), {flags: "a"}); } } _writeLog(text) { if (this.logEvents) { this.logStream.write(`${new Date().toISOString()}: ${text}\n`); } // Suppress console logging in production if (process.env.NODE_ENV === 'development') { console.log("PaperclipRPC:", text); } } async _makeRPCCall(method, params = {}) { try { const url = `${this.rpcUrl}/${method}`; const queryParams = new URLSearchParams(); Object.keys(params).forEach(key => { queryParams.append(key, params[key]); }); const fullUrl = queryParams.toString() ? `${url}?${queryParams}` : url; this._writeLog(`Making RPC call to: ${fullUrl}`); const response = await fetch(fullUrl); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { this._writeLog(`RPC call failed: ${error.message}`); throw error; } } async getStatus() { try { const result = await this._makeRPCCall("status"); this.isConnected = true; return result; } catch (error) { this.isConnected = false; throw error; } } async getBalance(address) { try { const result = await this._makeRPCCall("abci_query", { path: `"balance:${address}"` }); if (result.result && result.result.response && result.result.response.value) { // Decode base64 response const balance = Buffer.from(result.result.response.value, 'base64').toString(); return parseInt(balance) || 0; } return 0; } catch (error) { this._writeLog(`Failed to get balance for ${address}: ${error.message}`); return 0; } } async getNonce(address) { try { const result = await this._makeRPCCall("abci_query", { path: `"nonce:${address}"` }); if (result.result && result.result.response && result.result.response.value) { const nonce = Buffer.from(result.result.response.value, 'base64').toString(); return parseInt(nonce) || 0; } return 0; } catch (error) { this._writeLog(`Failed to get nonce for ${address}: ${error.message}`); return 0; } } async broadcastTransaction(txHex) { try { const result = await this._makeRPCCall("broadcast_tx_commit", { tx: txHex }); if (result.result && result.result.check_tx && result.result.check_tx.code === 0) { this._writeLog(`Transaction broadcast successful: ${result.result.hash}`); return { success: true, hash: result.result.hash, height: result.result.height }; } else { const error = result.result?.check_tx?.log || "Transaction failed"; throw new Error(error); } } catch (error) { this._writeLog(`Failed to broadcast transaction: ${error.message}`); throw error; } } async getTransaction(hash) { try { const result = await this._makeRPCCall("tx", { hash: hash, prove: "false" }); return result; } catch (error) { this._writeLog(`Failed to get transaction ${hash}: ${error.message}`); throw error; } } async getBlock(height = null) { try { const params = height ? { height: height.toString() } : {}; const result = await this._makeRPCCall("block", params); return result; } catch (error) { this._writeLog(`Failed to get block: ${error.message}`); throw error; } } async getContractInfo(contractAddress) { try { const result = await this._makeRPCCall("abci_query", { path: `"contract:${contractAddress}"` }); if (result.result && result.result.response && result.result.response.value) { const contractInfo = Buffer.from(result.result.response.value, 'base64').toString(); return JSON.parse(contractInfo); } return null; } catch (error) { this._writeLog(`Failed to get contract info for ${contractAddress}: ${error.message}`); return null; } } async getMultisigInfo(multisigAddress) { try { const result = await this._makeRPCCall("abci_query", { path: `"multisig:${multisigAddress}"` }); if (result.result && result.result.response && result.result.response.value) { const multisigInfo = Buffer.from(result.result.response.value, 'base64').toString(); return JSON.parse(multisigInfo); } return null; } catch (error) { this._writeLog(`Failed to get multisig info for ${multisigAddress}: ${error.message}`); return null; } } async getFeePool() { try { const result = await this._makeRPCCall("abci_query", { path: `"fee_pool"` }); if (result.result && result.result.response && result.result.response.value) { const feePool = Buffer.from(result.result.response.value, 'base64').toString(); return parseInt(feePool) || 0; } return 0; } catch (error) { this._writeLog(`Failed to get fee pool: ${error.message}`); return 0; } } // Staking Methods async getValidators() { try { const result = await this._makeRPCCall("abci_query", { path: `"validators"` }); if (result.result && result.result.response && result.result.response.value) { const validators = Buffer.from(result.result.response.value, 'base64').toString(); return JSON.parse(validators); } return []; } catch (error) { this._writeLog(`Failed to get validators: ${error.message}`); return []; } } async getStakingInfo(address) { try { const result = await this._makeRPCCall("abci_query", { path: `"staker:${address}"` }); if (result.result && result.result.response && result.result.response.value) { const stakingInfo = Buffer.from(result.result.response.value, 'base64').toString(); return JSON.parse(stakingInfo); } return null; } catch (error) { this._writeLog(`Failed to get staking info for ${address}: ${error.message}`); return null; } } async getValidatorInfo(validatorAddress) { try { const result = await this._makeRPCCall("abci_query", { path: `"validator:${validatorAddress}"` }); if (result.result && result.result.response && result.result.response.value) { const validatorInfo = Buffer.from(result.result.response.value, 'base64').toString(); return JSON.parse(validatorInfo); } return null; } catch (error) { this._writeLog(`Failed to get validator info for ${validatorAddress}: ${error.message}`); return null; } } async getStakingRewards(address) { try { const result = await this._makeRPCCall("abci_query", { path: `"rewards:${address}"` }); if (result.result && result.result.response && result.result.response.value) { const rewards = Buffer.from(result.result.response.value, 'base64').toString(); return parseInt(rewards) || 0; } return 0; } catch (error) { this._writeLog(`Failed to get staking rewards for ${address}: ${error.message}`); return 0; } } async getTotalStaked() { try { const result = await this._makeRPCCall("abci_query", { path: `"total_staked"` }); if (result.result && result.result.response && result.result.response.value) { const totalStaked = Buffer.from(result.result.response.value, 'base64').toString(); return parseInt(totalStaked) || 0; } return 0; } catch (error) { this._writeLog(`Failed to get total staked: ${error.message}`); return 0; } } setRPCUrl(url) { this.rpcUrl = url; this._writeLog(`RPC URL updated to: ${url}`); } startConnection() { this._writeLog("Starting PaperclipChain RPC connection"); // Test connection this.getStatus() .then(() => { this._writeLog("Connected to PaperclipChain node"); }) .catch((error) => { this._writeLog(`Connection failed: ${error.message}`); }); } stopConnection() { this._writeLog("Stopping PaperclipChain RPC connection"); this.isConnected = false; if (this.logStream) { this.logStream.end(); } } } const PaperclipNode = new PaperclipRPC(); // IPC handlers for renderer process ipcMain.handle("paperclip-get-status", async () => { return await PaperclipNode.getStatus(); }); ipcMain.handle("paperclip-get-balance", async (event, address) => { return await PaperclipNode.getBalance(address); }); ipcMain.handle("paperclip-get-nonce", async (event, address) => { return await PaperclipNode.getNonce(address); }); ipcMain.handle("paperclip-broadcast-tx", async (event, txHex) => { return await PaperclipNode.broadcastTransaction(txHex); }); ipcMain.handle("paperclip-get-transaction", async (event, hash) => { return await PaperclipNode.getTransaction(hash); }); ipcMain.handle("paperclip-get-block", async (event, height) => { return await PaperclipNode.getBlock(height); }); // Staking IPC handlers ipcMain.handle("paperclip-get-validators", async () => { return await PaperclipNode.getValidators(); }); ipcMain.handle("paperclip-get-staking-info", async (event, address) => { return await PaperclipNode.getStakingInfo(address); }); ipcMain.handle("paperclip-get-validator-info", async (event, validatorAddress) => { return await PaperclipNode.getValidatorInfo(validatorAddress); }); ipcMain.handle("paperclip-get-staking-rewards", async (event, address) => { return await PaperclipNode.getStakingRewards(address); }); ipcMain.handle("paperclip-get-total-staked", async () => { return await PaperclipNode.getTotalStaked(); }); module.exports = PaperclipNode;