diff --git a/assets/styles/staking.css b/assets/styles/staking.css
new file mode 100644
index 0000000..d54da7c
--- /dev/null
+++ b/assets/styles/staking.css
@@ -0,0 +1,386 @@
+/* PaperclipChain Staking Interface Styles */
+
+.staking-container {
+ padding: 20px;
+ background: #f5f5f5;
+ min-height: 100vh;
+}
+
+.staking-overview {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 30px;
+ flex-wrap: wrap;
+}
+
+.staking-card {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 20px;
+ border-radius: 12px;
+ flex: 1;
+ min-width: 200px;
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
+ transition: transform 0.3s ease;
+}
+
+.staking-card:hover {
+ transform: translateY(-5px);
+}
+
+.staking-card h3 {
+ margin: 0 0 10px 0;
+ font-size: 14px;
+ opacity: 0.9;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.staking-card .amount {
+ font-size: 24px;
+ font-weight: bold;
+ margin: 0;
+}
+
+.staking-card .subtitle {
+ font-size: 12px;
+ opacity: 0.8;
+ margin-top: 5px;
+}
+
+.staking-card.success {
+ background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
+}
+
+.staking-card.warning {
+ background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
+}
+
+.staking-card.info {
+ background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
+}
+
+.quick-actions {
+ background: white;
+ padding: 20px;
+ border-radius: 12px;
+ margin-bottom: 30px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+}
+
+.quick-actions h3 {
+ margin-top: 0;
+ color: #333;
+ border-bottom: 2px solid #eee;
+ padding-bottom: 10px;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 15px;
+ flex-wrap: wrap;
+}
+
+.btn-staking {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ text-decoration: none;
+ display: inline-block;
+}
+
+.btn-staking:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+ color: white;
+}
+
+.btn-staking.warning {
+ background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
+}
+
+.btn-staking.warning:hover {
+ box-shadow: 0 5px 15px rgba(255, 152, 0, 0.4);
+}
+
+.btn-staking.success {
+ background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
+}
+
+.btn-staking.success:hover {
+ box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
+}
+
+.btn-staking.info {
+ background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
+}
+
+.btn-staking.info:hover {
+ box-shadow: 0 5px 15px rgba(33, 150, 243, 0.4);
+}
+
+.validators-section {
+ background: white;
+ padding: 20px;
+ border-radius: 12px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+}
+
+.validators-section h3 {
+ margin-top: 0;
+ color: #333;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 2px solid #eee;
+ padding-bottom: 10px;
+}
+
+.refresh-btn {
+ background: #f8f9fa;
+ border: 1px solid #dee2e6;
+ padding: 8px 12px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: background 0.3s ease;
+}
+
+.refresh-btn:hover {
+ background: #e9ecef;
+}
+
+.validators-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 20px;
+}
+
+.validators-table th,
+.validators-table td {
+ padding: 12px;
+ text-align: left;
+ border-bottom: 1px solid #eee;
+}
+
+.validators-table th {
+ background: #f8f9fa;
+ font-weight: 600;
+ color: #495057;
+ border-top: 1px solid #eee;
+}
+
+.validators-table tbody tr:hover {
+ background: #f8f9fa;
+}
+
+.validator-info {
+ display: flex;
+ flex-direction: column;
+}
+
+.validator-info strong {
+ color: #333;
+}
+
+.validator-info small {
+ color: #6c757d;
+ font-size: 11px;
+}
+
+.status-badge {
+ padding: 4px 8px;
+ border-radius: 12px;
+ font-size: 11px;
+ font-weight: 500;
+ text-transform: uppercase;
+}
+
+.status-badge.active {
+ background: #d4edda;
+ color: #155724;
+}
+
+.status-badge.inactive {
+ background: #f8d7da;
+ color: #721c24;
+}
+
+.btn-validator-action {
+ padding: 6px 12px;
+ border: none;
+ border-radius: 4px;
+ font-size: 12px;
+ cursor: pointer;
+ margin-right: 5px;
+ transition: all 0.3s ease;
+}
+
+.btn-validator-action.delegate {
+ background: #007bff;
+ color: white;
+}
+
+.btn-validator-action.delegate:hover {
+ background: #0056b3;
+}
+
+.btn-validator-action.undelegate {
+ background: #ffc107;
+ color: #212529;
+}
+
+.btn-validator-action.undelegate:hover {
+ background: #e0a800;
+}
+
+.btn-validator-action:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Modal Styles */
+.staking-modal .modal-body {
+ padding: 20px;
+}
+
+.staking-modal .form-group {
+ margin-bottom: 20px;
+}
+
+.staking-modal label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: 500;
+ color: #333;
+}
+
+.staking-modal input,
+.staking-modal select {
+ width: 100%;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ font-size: 14px;
+}
+
+.staking-modal input:focus,
+.staking-modal select:focus {
+ outline: none;
+ border-color: #667eea;
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
+}
+
+.staking-modal .help-text {
+ font-size: 12px;
+ color: #6c757d;
+ margin-top: 5px;
+}
+
+.staking-modal .alert {
+ padding: 10px 15px;
+ border-radius: 6px;
+ margin-bottom: 15px;
+}
+
+.staking-modal .alert.warning {
+ background: #fff3cd;
+ border: 1px solid #ffeaa7;
+ color: #856404;
+}
+
+.staking-modal .alert.info {
+ background: #d1ecf1;
+ border: 1px solid #bee5eb;
+ color: #0c5460;
+}
+
+.modal-buttons {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ margin-top: 20px;
+}
+
+.modal-buttons .btn {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.modal-buttons .btn-secondary {
+ background: #6c757d;
+ color: white;
+}
+
+.modal-buttons .btn-primary {
+ background: #667eea;
+ color: white;
+}
+
+.modal-buttons .btn:hover {
+ opacity: 0.9;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .staking-overview {
+ flex-direction: column;
+ }
+
+ .action-buttons {
+ flex-direction: column;
+ }
+
+ .validators-table {
+ font-size: 12px;
+ }
+
+ .validators-table th,
+ .validators-table td {
+ padding: 8px;
+ }
+}
+
+/* Loading States */
+.loading {
+ text-align: center;
+ padding: 40px;
+ color: #6c757d;
+}
+
+.loading i {
+ font-size: 24px;
+ margin-bottom: 10px;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Success/Error Messages */
+.message {
+ padding: 15px;
+ border-radius: 6px;
+ margin-bottom: 20px;
+}
+
+.message.success {
+ background: #d4edda;
+ color: #155724;
+ border: 1px solid #c3e6cb;
+}
+
+.message.error {
+ background: #f8d7da;
+ color: #721c24;
+ border: 1px solid #f5c6cb;
+}
\ No newline at end of file
diff --git a/assets/templates/staking.html b/assets/templates/staking.html
new file mode 100644
index 0000000..1539545
--- /dev/null
+++ b/assets/templates/staking.html
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total Staked
+
0 CLIPS
+
+
+
+
+
+
+
+
+
+
+
Pending Rewards
+
0 CLIPS
+
+
+
+
+
+
+
+
+
+
+ Active Validators
+ 0
+
+
+
+
+
+
+
+
+
+ Network APY
+ ~8%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Validator |
+ Status |
+ Self Stake |
+ Delegated |
+ Commission |
+ APY |
+ Your Stake |
+ Actions |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Available balance: 0 CLIPS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Your stake: 0 CLIPS
+
+
+
+
+
+
+
+ Note: Undelegated funds may have a cooldown period before they become available.
+
+
+
+
+
+
+
+
+
+
+
+ Requirements: Minimum 10,000 CLIPS stake required to create a validator.
+
+
+
+
+
+
+
+
+ Percentage of rewards you'll keep as commission
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
index dfc1343..8f5b4a2 100755
--- a/index.html
+++ b/index.html
@@ -19,6 +19,7 @@
+
diff --git a/modules/paperclip-rpc.js b/modules/paperclip-rpc.js
index e35f73e..97aa7fd 100644
--- a/modules/paperclip-rpc.js
+++ b/modules/paperclip-rpc.js
@@ -199,6 +199,93 @@ class PaperclipRPC {
}
}
+ // 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}`);
@@ -254,4 +341,25 @@ 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;
\ No newline at end of file
diff --git a/renderer/maingui.js b/renderer/maingui.js
index 4ff542b..de9f270 100755
--- a/renderer/maingui.js
+++ b/renderer/maingui.js
@@ -25,6 +25,9 @@ class MainGUI {
case "transactions":
$("#mainNavBtnTransactionsWrapper").addClass("iconSelected");
break;
+ case "staking":
+ $("#mainNavBtnStakingWrapper").addClass("iconSelected");
+ break;
case "markets":
$("#mainNavBtnMarketsWrapper").addClass("iconSelected");
break;
@@ -122,6 +125,11 @@ $("#mainNavBtnTransactions").click(function () {
EthoTransactions.renderTransactions();
});
+$("#mainNavBtnStaking").click(function () {
+ EthoMainGUI.changeAppState("staking");
+ PaperclipStaking.showStakingPage();
+});
+
$("#mainNavBtnAddressBoook").click(function () {
EthoMainGUI.changeAppState("addressBook");
EthoAddressBook.renderAddressBook();
diff --git a/renderer/staking.js b/renderer/staking.js
new file mode 100644
index 0000000..86fe1f2
--- /dev/null
+++ b/renderer/staking.js
@@ -0,0 +1,520 @@
+// PaperclipChain Staking Interface
+// Handles all staking-related functionality in the wallet
+
+const { ipcRenderer } = require('electron');
+
+class StakingManager {
+ constructor() {
+ this.validators = [];
+ this.stakingInfo = null;
+ this.totalStaked = 0;
+ this.pendingRewards = 0;
+ this.refreshInterval = null;
+ }
+
+ async init() {
+ console.log('Initializing staking manager...');
+ await this.loadStakingData();
+ this.setupEventHandlers();
+ this.startAutoRefresh();
+ }
+
+ async loadStakingData() {
+ try {
+ // Load validators
+ this.validators = await ipcRenderer.invoke('paperclip-get-validators');
+
+ // Load total staked
+ this.totalStaked = await ipcRenderer.invoke('paperclip-get-total-staked');
+
+ // Load user staking info if wallet is selected
+ if (ClipsWallet.addressList.length > 0) {
+ const currentAddress = ClipsWallet.addressList[0]; // Use first wallet as current
+ this.stakingInfo = await ipcRenderer.invoke('paperclip-get-staking-info', currentAddress);
+ this.pendingRewards = await ipcRenderer.invoke('paperclip-get-staking-rewards', currentAddress);
+ }
+
+ this.updateUI();
+ } catch (error) {
+ console.error('Failed to load staking data:', error);
+ this.showError('Failed to load staking data: ' + error.message);
+ }
+ }
+
+ updateUI() {
+ // Update overview cards
+ $('#totalStakedAmount').text(this.formatClips(this.totalStaked));
+ $('#pendingRewards').text(this.formatClips(this.pendingRewards));
+ $('#activeValidators').text(this.validators.filter(v => v.active).length);
+
+ // Update validators table
+ this.updateValidatorsTable();
+
+ // Update progress bars
+ this.updateProgressBars();
+ }
+
+ updateValidatorsTable() {
+ const tbody = $('#validatorsTableBody');
+ tbody.empty();
+
+ this.validators.forEach(validator => {
+ const apy = this.calculateValidatorAPY(validator);
+ const userStake = this.getUserStakeForValidator(validator.address);
+ const status = validator.active ?
+ 'Active' :
+ 'Inactive';
+
+ const row = `
+
+ |
+
+ ${validator.address.substring(0, 12)}...
+ ${validator.address}
+
+ |
+ ${status} |
+ ${this.formatClips(validator.stakedAmount || 0)} |
+ ${this.formatClips(validator.delegatedAmount || 0)} |
+ ${(validator.commission / 100).toFixed(2)}% |
+ ~${apy.toFixed(2)}% |
+ ${this.formatClips(userStake)} |
+
+
+
+ ${userStake > 0 ? `` : ''}
+
+ |
+
+ `;
+ tbody.append(row);
+ });
+
+ // Initialize DataTable if not already initialized
+ if (!$.fn.DataTable.isDataTable('#validatorsTable')) {
+ $('#validatorsTable').DataTable({
+ pageLength: 10,
+ responsive: true,
+ order: [[2, 'desc']] // Sort by self stake desc
+ });
+ } else {
+ $('#validatorsTable').DataTable().draw();
+ }
+ }
+
+ updateProgressBars() {
+ // Calculate total network stake percentage (mock calculation)
+ const networkSupply = 1000000000; // 1B CLIPS total supply (mock)
+ const stakedPercent = (this.totalStaked / networkSupply) * 100;
+ $('#totalStakedProgress').css('width', Math.min(stakedPercent, 100) + '%');
+
+ // Update rewards progress (mock)
+ const rewardsPercent = Math.min((this.pendingRewards / 1000) * 100, 100);
+ $('#rewardsProgress').css('width', rewardsPercent + '%');
+ }
+
+ setupEventHandlers() {
+ // Navigation
+ $('#mainNavBtnStaking').off('click').on('click', () => {
+ this.showStakingPage();
+ });
+
+ // Refresh button
+ $('#btnRefreshValidators').off('click').on('click', () => {
+ this.loadStakingData();
+ });
+
+ // Quick action buttons
+ $('#btnDelegate').off('click').on('click', () => {
+ this.showDelegateModal();
+ });
+
+ $('#btnUndelegate').off('click').on('click', () => {
+ this.showUndelegateModal();
+ });
+
+ $('#btnClaimRewards').off('click').on('click', () => {
+ this.claimRewards();
+ });
+
+ $('#btnCreateValidator').off('click').on('click', () => {
+ this.showCreateValidatorModal();
+ });
+
+ // Table action buttons (delegated event handling)
+ $(document).off('click', '.btn-delegate').on('click', '.btn-delegate', (e) => {
+ const validatorAddress = $(e.target).data('validator');
+ this.showDelegateModal(validatorAddress);
+ });
+
+ $(document).off('click', '.btn-undelegate').on('click', '.btn-undelegate', (e) => {
+ const validatorAddress = $(e.target).data('validator');
+ this.showUndelegateModal(validatorAddress);
+ });
+
+ // Modal handlers
+ $('#btnDelegateConfirm').off('click').on('click', () => {
+ this.performDelegation();
+ });
+
+ $('#btnUndelegateConfirm').off('click').on('click', () => {
+ this.performUndelegation();
+ });
+
+ $('#btnCreateValidatorConfirm').off('click').on('click', () => {
+ this.createValidator();
+ });
+
+ // Modal cancel buttons
+ $('#btnDelegateCancel, #btnUndelegateCancel, #btnCreateValidatorCancel').off('click').on('click', function() {
+ $(this).closest('.modalDialog').iziModal('close');
+ });
+ }
+
+ showStakingPage() {
+ EthoMainGUI.renderTemplate('staking.html', {});
+ this.loadStakingData();
+ this.setupEventHandlers();
+ }
+
+ showDelegateModal(validatorAddress = null) {
+ // Populate validator dropdown
+ const validatorSelect = $('#delegateValidator');
+ validatorSelect.empty().append('');
+
+ this.validators.filter(v => v.active).forEach(validator => {
+ const option = ``;
+ validatorSelect.append(option);
+ });
+
+ if (validatorAddress) {
+ validatorSelect.val(validatorAddress);
+ }
+
+ // Populate wallet dropdown
+ this.populateWalletDropdown('#delegateWallet');
+
+ // Update available balance
+ this.updateAvailableBalance('#availableBalance');
+
+ $('#dlgDelegate').iziModal('open');
+ }
+
+ showUndelegateModal(validatorAddress = null) {
+ // Populate validator dropdown with only validators user has stake with
+ const validatorSelect = $('#undelegateValidator');
+ validatorSelect.empty().append('');
+
+ this.validators.forEach(validator => {
+ const userStake = this.getUserStakeForValidator(validator.address);
+ if (userStake > 0) {
+ const option = ``;
+ validatorSelect.append(option);
+ }
+ });
+
+ if (validatorAddress) {
+ validatorSelect.val(validatorAddress);
+ this.updateYourStakeAmount(validatorAddress);
+ }
+
+ // Populate wallet dropdown
+ this.populateWalletDropdown('#undelegateWallet');
+
+ // Event handler for validator selection change
+ validatorSelect.off('change').on('change', (e) => {
+ this.updateYourStakeAmount(e.target.value);
+ });
+
+ $('#dlgUndelegate').iziModal('open');
+ }
+
+ showCreateValidatorModal() {
+ // Populate wallet dropdown
+ this.populateWalletDropdown('#validatorWallet');
+
+ $('#dlgCreateValidator').iziModal('open');
+ }
+
+ async performDelegation() {
+ try {
+ const validatorAddress = $('#delegateValidator').val();
+ const amount = parseFloat($('#delegateAmount').val());
+ const walletAddress = $('#delegateWallet').val();
+
+ if (!validatorAddress || !amount || !walletAddress || amount < 100) {
+ this.showError('Please fill all fields correctly. Minimum delegation is 100 CLIPS.');
+ return;
+ }
+
+ if (!ClipsWallet.getAddressExists(walletAddress)) {
+ this.showError('Selected wallet not found.');
+ return;
+ }
+
+ // Create delegation transaction
+ const txData = {
+ sender: walletAddress,
+ receiver: validatorAddress,
+ amount: Math.floor(amount * 1000000), // Convert to micro-CLIPS
+ txType: 'delegate',
+ gas: 30000,
+ gasPrice: 1
+ };
+
+ // Sign and broadcast transaction
+ const result = await this.sendStakingTransaction(txData, { address: walletAddress });
+
+ if (result.success) {
+ this.showSuccess('Delegation successful!');
+ $('#dlgDelegate').iziModal('close');
+ await this.loadStakingData();
+ } else {
+ this.showError('Delegation failed: ' + result.error);
+ }
+
+ } catch (error) {
+ console.error('Delegation failed:', error);
+ this.showError('Delegation failed: ' + error.message);
+ }
+ }
+
+ async performUndelegation() {
+ try {
+ const validatorAddress = $('#undelegateValidator').val();
+ const amount = parseFloat($('#undelegateAmount').val());
+ const walletAddress = $('#undelegateWallet').val();
+
+ if (!validatorAddress || !amount || !walletAddress || amount <= 0) {
+ this.showError('Please fill all fields correctly.');
+ return;
+ }
+
+ if (!ClipsWallet.getAddressExists(walletAddress)) {
+ this.showError('Selected wallet not found.');
+ return;
+ }
+
+ // Create undelegation transaction
+ const txData = {
+ sender: walletAddress,
+ receiver: validatorAddress,
+ amount: Math.floor(amount * 1000000), // Convert to micro-CLIPS
+ txType: 'undelegate',
+ gas: 30000,
+ gasPrice: 1
+ };
+
+ // Sign and broadcast transaction
+ const result = await this.sendStakingTransaction(txData, { address: walletAddress });
+
+ if (result.success) {
+ this.showSuccess('Undelegation successful!');
+ $('#dlgUndelegate').iziModal('close');
+ await this.loadStakingData();
+ } else {
+ this.showError('Undelegation failed: ' + result.error);
+ }
+
+ } catch (error) {
+ console.error('Undelegation failed:', error);
+ this.showError('Undelegation failed: ' + error.message);
+ }
+ }
+
+ async createValidator() {
+ try {
+ const stakeAmount = parseFloat($('#validatorStake').val());
+ const commission = parseFloat($('#validatorCommission').val());
+ const walletAddress = $('#validatorWallet').val();
+
+ if (!stakeAmount || !walletAddress || stakeAmount < 10000 || commission < 0 || commission > 50) {
+ this.showError('Please fill all fields correctly. Minimum stake is 10,000 CLIPS, commission must be 0-50%.');
+ return;
+ }
+
+ if (!ClipsWallet.getAddressExists(walletAddress)) {
+ this.showError('Selected wallet not found.');
+ return;
+ }
+
+ // Create validator creation transaction
+ const txData = {
+ sender: walletAddress,
+ receiver: '',
+ amount: Math.floor(stakeAmount * 1000000), // Convert to micro-CLIPS
+ txType: 'create_validator',
+ gas: 30000,
+ gasPrice: 1,
+ data: JSON.stringify({ commission: Math.floor(commission * 100) }) // Convert to basis points
+ };
+
+ // Sign and broadcast transaction
+ const result = await this.sendStakingTransaction(txData, { address: walletAddress });
+
+ if (result.success) {
+ this.showSuccess('Validator created successfully!');
+ $('#dlgCreateValidator').iziModal('close');
+ await this.loadStakingData();
+ } else {
+ this.showError('Validator creation failed: ' + result.error);
+ }
+
+ } catch (error) {
+ console.error('Validator creation failed:', error);
+ this.showError('Validator creation failed: ' + error.message);
+ }
+ }
+
+ async claimRewards() {
+ try {
+ if (ClipsWallet.addressList.length === 0) {
+ this.showError('No wallet available.');
+ return;
+ }
+
+ if (this.pendingRewards <= 0) {
+ this.showError('No rewards to claim.');
+ return;
+ }
+
+ const currentAddress = ClipsWallet.addressList[0];
+
+ // Create claim rewards transaction
+ const txData = {
+ sender: currentAddress,
+ receiver: '',
+ amount: 0,
+ txType: 'claim_rewards',
+ gas: 30000,
+ gasPrice: 1
+ };
+
+ // Sign and broadcast transaction
+ const result = await this.sendStakingTransaction(txData, { address: currentAddress });
+
+ if (result.success) {
+ this.showSuccess('Rewards claimed successfully!');
+ await this.loadStakingData();
+ } else {
+ this.showError('Claim failed: ' + result.error);
+ }
+
+ } catch (error) {
+ console.error('Claim failed:', error);
+ this.showError('Claim failed: ' + error.message);
+ }
+ }
+
+ async sendStakingTransaction(txData, wallet) {
+ try {
+ // Get current nonce
+ const nonce = await ipcRenderer.invoke('paperclip-get-nonce', wallet.address);
+ txData.nonce = nonce + 1;
+
+ // Sign transaction (this would use the actual wallet signing)
+ const signedTx = await ClipsWallet.signTransaction(txData, wallet);
+
+ // Broadcast transaction
+ const result = await ipcRenderer.invoke('paperclip-broadcast-tx', signedTx);
+
+ return result;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ // Utility functions
+ populateWalletDropdown(selector) {
+ const walletSelect = $(selector);
+ walletSelect.empty();
+
+ ClipsWallet.addressList.forEach((address, index) => {
+ const option = ``;
+ walletSelect.append(option);
+ });
+ }
+
+ async updateAvailableBalance(selector) {
+ if (ClipsWallet.addressList.length > 0) {
+ try {
+ const currentAddress = ClipsWallet.addressList[0];
+ const balance = await ipcRenderer.invoke('paperclip-get-balance', currentAddress);
+ $(selector).text(this.formatClips(balance));
+ } catch (error) {
+ $(selector).text('0 CLIPS');
+ }
+ } else {
+ $(selector).text('0 CLIPS');
+ }
+ }
+
+ updateYourStakeAmount(validatorAddress) {
+ const userStake = this.getUserStakeForValidator(validatorAddress);
+ $('#yourStakeAmount').text(this.formatClips(userStake));
+ }
+
+ getUserStakeForValidator(validatorAddress) {
+ if (!this.stakingInfo || !this.stakingInfo.delegations) {
+ return 0;
+ }
+
+ const delegation = this.stakingInfo.delegations.find(d => d.validator === validatorAddress);
+ return delegation ? delegation.amount : 0;
+ }
+
+ calculateValidatorAPY(validator) {
+ // Simplified APY calculation
+ const baseAPY = 8.0; // 8% base APY
+ const totalStake = (validator.stakedAmount || 0) + (validator.delegatedAmount || 0);
+ if (totalStake === 0) return 0;
+
+ // Adjust APY based on commission
+ const adjustedAPY = baseAPY * (1 - (validator.commission || 0) / 10000);
+ return Math.max(adjustedAPY, 0);
+ }
+
+ formatClips(microClips) {
+ return (microClips / 1000000).toFixed(6) + ' CLIPS';
+ }
+
+ startAutoRefresh() {
+ // Refresh staking data every 30 seconds
+ this.refreshInterval = setInterval(() => {
+ this.loadStakingData();
+ }, 30000);
+ }
+
+ stopAutoRefresh() {
+ if (this.refreshInterval) {
+ clearInterval(this.refreshInterval);
+ this.refreshInterval = null;
+ }
+ }
+
+ showError(message) {
+ iziToast.error({
+ title: 'Staking Error',
+ message: message,
+ position: 'topRight'
+ });
+ }
+
+ showSuccess(message) {
+ iziToast.success({
+ title: 'Staking Success',
+ message: message,
+ position: 'topRight'
+ });
+ }
+}
+
+// Initialize staking manager
+const PaperclipStaking = new StakingManager();
+
+// Export for use in other modules
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = PaperclipStaking;
+}
\ No newline at end of file