Files
paperclip-wallet/modules/paperclip-rpc.js
matt 2c3ad62bc4 feat: Add comprehensive staking interface to PaperclipWallet
- Add staking RPC methods for validators, delegation, and rewards
- Implement complete staking UI with validator selection and delegation
- Add reward claiming functionality and validator creation interface
- Include professional staking dashboard with real-time data
- Integrate staking navigation into existing wallet interface
2025-06-17 14:38:53 -07:00

365 lines
10 KiB
JavaScript

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;