- Update package.json with PaperclipWallet branding
- Replace Ethereum Web3 with Tendermint RPC client
- Implement CLIPS address format (CLIP-{64-hex})
- Add Ed25519 key management with tweetnacl
- Create PaperclipChain blockchain interface
- Support gas system and transaction types
- Add smart contract and multisig support
303 lines
8.0 KiB
JavaScript
Executable File
303 lines
8.0 KiB
JavaScript
Executable File
// 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; |