Update wallet UI to use PaperclipChain
- Remove Ethereum/Web3 references from HTML - Create PaperclipWallets class with Ed25519 key support - Add PaperclipDatabase for wallet storage - Update module loading order in index.html - Convert button classes from btn-etho to btn-clips
This commit is contained in:
49
index.html
49
index.html
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>$ETHO Desktop Wallet</title>
|
<title>PaperclipWallet</title>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="./assets/styles/materialize.min.css">
|
<link rel="stylesheet" href="./assets/styles/materialize.min.css">
|
||||||
<link rel="stylesheet" href="./assets/styles/datatables.min.css">
|
<link rel="stylesheet" href="./assets/styles/datatables.min.css">
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
<span id="nodestorage" style="display:none;">Loading</span>
|
<span id="nodestorage" style="display:none;">Loading</span>
|
||||||
|
|
||||||
<!-- normal script imports etc -->
|
<!-- normal script imports etc -->
|
||||||
<script src="https://cdn.jsdelivr.net/gh/ethereum/web3.js@1.0.0-beta.36/dist/web3.min.js" integrity="sha256-nWBTbvxhJgjslRyuAKJHK+XcZPlCnmIAAMixz6EefVk=" crossorigin="anonymous"></script>
|
|
||||||
<script src="./assets/scripts/jquery.min.js"></script>
|
<script src="./assets/scripts/jquery.min.js"></script>
|
||||||
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
|
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
|
||||||
<script src="./assets/scripts/handlebars.js"></script>
|
<script src="./assets/scripts/handlebars.js"></script>
|
||||||
@@ -74,10 +73,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
var timeouts = [
|
var timeouts = [
|
||||||
{ text: "Starting Node...", delay: 1250 },
|
{ text: "Starting PaperclipChain node...", delay: 1250 },
|
||||||
{ text: "Connecting to Network...", delay: 6400 },
|
{ text: "Connecting to network...", delay: 6400 },
|
||||||
{ text: "Updating...", delay: 7320 },
|
{ text: "Loading wallet...", delay: 7320 },
|
||||||
{ text: "Launching...", delay: 7400 }
|
{ text: "Ready", delay: 7400 }
|
||||||
];
|
];
|
||||||
|
|
||||||
timeouts.forEach((item, index) => {
|
timeouts.forEach((item, index) => {
|
||||||
@@ -86,14 +85,16 @@
|
|||||||
}, item.delay + Math.random() * 50 * index);
|
}, item.delay + Math.random() * 50 * index);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("onGethReady", function () {
|
$(document).on("onNodeReady", function () {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loading_screen.finish();
|
loading_screen.finish();
|
||||||
}, 5500);
|
}, 5500);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(window).on("beforeunload", function () {
|
$(window).on("beforeunload", function () {
|
||||||
EthoBlockchain.closeConnection();
|
if (window.PaperclipChain) {
|
||||||
|
PaperclipChain.stopConnection();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a class="item" style="padding-top: 30px; padding-left: 13px" id="mainNavBtnSend" href="#" data-tippy="Send Funds" data-tippy-delay="100">
|
<a class="item" style="padding-top: 30px; padding-left: 13px" id="mainNavBtnSend" href="#" data-tippy="Send Funds" data-tippy-delay="100">
|
||||||
<i class="fas fa-comment-dollar fa-1x"></i>
|
<i class="fas fa-comment-dollar fa-1x"></i>
|
||||||
<span class="sendEtho"> Send </span>
|
<span class="sendClips"> Send </span>
|
||||||
</a>
|
</a>
|
||||||
<a class="item" style="padding-top: 30px; padding-left: 13px" id="mainNavBtnTransactions" href="#" data-tippy="Transactions" data-tippy-delay="100">
|
<a class="item" style="padding-top: 30px; padding-left: 13px" id="mainNavBtnTransactions" href="#" data-tippy="Transactions" data-tippy-delay="100">
|
||||||
<i class="fas fa-exchange-alt fa-1x"></i>
|
<i class="fas fa-exchange-alt fa-1x"></i>
|
||||||
@@ -142,17 +143,17 @@
|
|||||||
<div id="mainContent"></div>
|
<div id="mainContent"></div>
|
||||||
<div id="syncProgress"></div>
|
<div id="syncProgress"></div>
|
||||||
<script>
|
<script>
|
||||||
// You can also require other files to run in this process
|
// PaperclipChain renderer modules
|
||||||
require('./renderer/about.js');
|
require('./renderer/paperclip-database.js');
|
||||||
require('./renderer/send.js');
|
require('./renderer/blockchain.js');
|
||||||
|
require('./renderer/paperclip-wallets.js');
|
||||||
require('./renderer/utils.js');
|
require('./renderer/utils.js');
|
||||||
require('./renderer/maingui.js');
|
require('./renderer/maingui.js');
|
||||||
|
require('./renderer/about.js');
|
||||||
|
require('./renderer/send.js');
|
||||||
require('./renderer/syncing.js');
|
require('./renderer/syncing.js');
|
||||||
require('./renderer/markets.js');
|
require('./renderer/markets.js');
|
||||||
require('./renderer/settings.js');
|
require('./renderer/settings.js');
|
||||||
require('./renderer/wallets.js');
|
|
||||||
require('./renderer/database.js');
|
|
||||||
require('./renderer/blockchain.js');
|
|
||||||
require('./renderer/addressBook.js');
|
require('./renderer/addressBook.js');
|
||||||
require('./renderer/transactions.js');
|
require('./renderer/transactions.js');
|
||||||
require('./renderer/tableTransactions.js');
|
require('./renderer/tableTransactions.js');
|
||||||
@@ -165,7 +166,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span id="txtGeneralError"></span>
|
<span id="txtGeneralError"></span>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-etho btn-dialog-confirm" id="btnGeneralErrorOK">OK</button>
|
<button type="button" class="btn btn-clips btn-dialog-confirm" id="btnGeneralErrorOK">OK</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -175,27 +176,27 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span id="txtGeneralConfirm"></span>
|
<span id="txtGeneralConfirm"></span>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-etho btn-dialog-cancel" id="btnGeneralConfirmNo">No</button>
|
<button type="button" class="btn btn-clips btn-dialog-cancel" id="btnGeneralConfirmNo">No</button>
|
||||||
<button type="button" class="btn btn-etho btn-dialog-confirm" id="btnGeneralConfirmYes">Yes</button>
|
<button type="button" class="btn btn-clips btn-dialog-confirm" id="btnGeneralConfirmYes">Yes</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- The modal for about info -->
|
<!-- The modal for about info -->
|
||||||
<div id="dlgAboutInfo" class="modalDialog" data-izimodal-title="About Etho Protocol Wallet" data-izimodal-icon="icon-home">
|
<div id="dlgAboutInfo" class="modalDialog" data-izimodal-title="About PaperclipWallet" data-izimodal-icon="icon-home">
|
||||||
<div class="modalBody">
|
<div class="modalBody">
|
||||||
<div class="aboutInfo">
|
<div class="aboutInfo">
|
||||||
<div class="infoText" id="aboutInfoWallet">Etho Protocol Wallet</div>
|
<div class="infoText" id="aboutInfoWallet">PaperclipWallet</div>
|
||||||
<div class="infoText" id="aboutInfoGitHub">GitHub:
|
<div class="infoText" id="aboutInfoGitHub">GitHub:
|
||||||
<a id="urlOpenGitHub" href="https://github.com/Ether1Project/Ether1DesktopWallet">https://github.com/Ether1Project/Ether1DesktopWallet</a>
|
<a id="urlOpenGitHub" href="https://git.takoyaki.cool/matt/paperclip-wallet">https://git.takoyaki.cool/matt/paperclip-wallet</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="infoText" id="aboutInfoLicence">Made under
|
<div class="infoText" id="aboutInfoLicence">Made under
|
||||||
<a id="urlOpenLicence" href="https://choosealicense.com/licenses/gpl-3.0">GPL v3.0</a>
|
<a id="urlOpenLicence" href="https://choosealicense.com/licenses/cc0-1.0">CC0 1.0</a>
|
||||||
licence
|
licence
|
||||||
</div>
|
</div>
|
||||||
<div class="infoText" id="aboutInfoVersion">Version:
|
<div class="infoText" id="aboutInfoVersion">Version:
|
||||||
<span id="versionNumber"></span></div>
|
<span id="versionNumber"></span></div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-etho btn-dialog-confirm" id="btnAboutInfoClose">Close</button>
|
<button type="button" class="btn btn-clips btn-dialog-confirm" id="btnAboutInfoClose">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
@@ -204,7 +205,7 @@
|
|||||||
<div id="dlgShowAddressQRCode" class="modalDialog" data-izimodal-title="Address QR-Code" data-izimodal-subtitle="Scan the QR-Code to get the address..." data-izimodal-icon="icon-home">
|
<div id="dlgShowAddressQRCode" class="modalDialog" data-izimodal-title="Address QR-Code" data-izimodal-subtitle="Scan the QR-Code to get the address..." data-izimodal-icon="icon-home">
|
||||||
<div class="modalBody">
|
<div class="modalBody">
|
||||||
<div id="addrQRCode"></div>
|
<div id="addrQRCode"></div>
|
||||||
<button type="button" class="btn btn-etho btn-dialog-confirm" id="btnScanQRCodeClose">Close</button>
|
<button type="button" class="btn btn-clips btn-dialog-confirm" id="btnScanQRCodeClose">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
296
renderer/paperclip-database.js
Normal file
296
renderer/paperclip-database.js
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
const storage = require('electron-storage');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
class PaperclipDatabase {
|
||||||
|
constructor() {
|
||||||
|
this.walletFile = 'paperclip-wallets.json';
|
||||||
|
this.settingsFile = 'paperclip-settings.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallet operations
|
||||||
|
getWallets() {
|
||||||
|
try {
|
||||||
|
const data = storage.getSync(this.walletFile);
|
||||||
|
return data || { addresses: [], names: {}, keys: {} };
|
||||||
|
} catch (error) {
|
||||||
|
return { addresses: [], names: {}, keys: {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveWallet(wallet) {
|
||||||
|
try {
|
||||||
|
const wallets = this.getWallets();
|
||||||
|
|
||||||
|
// Add address if not exists
|
||||||
|
if (!wallets.addresses.includes(wallet.address)) {
|
||||||
|
wallets.addresses.push(wallet.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save wallet data
|
||||||
|
wallets.names[wallet.address] = wallet.name;
|
||||||
|
wallets.keys[wallet.address] = {
|
||||||
|
publicKey: wallet.publicKey,
|
||||||
|
privateKey: wallet.privateKey, // TODO: Encrypt with password
|
||||||
|
created: wallet.created
|
||||||
|
};
|
||||||
|
|
||||||
|
storage.setSync(this.walletFile, wallets);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save wallet:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getWallet(address) {
|
||||||
|
try {
|
||||||
|
const wallets = this.getWallets();
|
||||||
|
|
||||||
|
if (!wallets.addresses.includes(address)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: address,
|
||||||
|
name: wallets.names[address] || 'Account',
|
||||||
|
publicKey: wallets.keys[address]?.publicKey,
|
||||||
|
privateKey: wallets.keys[address]?.privateKey,
|
||||||
|
created: wallets.keys[address]?.created
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get wallet:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeWallet(address) {
|
||||||
|
try {
|
||||||
|
const wallets = this.getWallets();
|
||||||
|
|
||||||
|
// Remove from addresses array
|
||||||
|
const index = wallets.addresses.indexOf(address);
|
||||||
|
if (index > -1) {
|
||||||
|
wallets.addresses.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove wallet data
|
||||||
|
delete wallets.names[address];
|
||||||
|
delete wallets.keys[address];
|
||||||
|
|
||||||
|
storage.setSync(this.walletFile, wallets);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to remove wallet:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWalletName(address, newName) {
|
||||||
|
try {
|
||||||
|
const wallets = this.getWallets();
|
||||||
|
|
||||||
|
if (wallets.addresses.includes(address)) {
|
||||||
|
wallets.names[address] = newName;
|
||||||
|
storage.setSync(this.walletFile, wallets);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update wallet name:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address book operations
|
||||||
|
getAddressBook() {
|
||||||
|
try {
|
||||||
|
const data = storage.getSync('address-book.json');
|
||||||
|
return data || [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAddressBookEntry(entry) {
|
||||||
|
try {
|
||||||
|
const addressBook = this.getAddressBook();
|
||||||
|
|
||||||
|
// Check if address already exists
|
||||||
|
const existingIndex = addressBook.findIndex(item => item.address === entry.address);
|
||||||
|
|
||||||
|
if (existingIndex > -1) {
|
||||||
|
// Update existing entry
|
||||||
|
addressBook[existingIndex] = entry;
|
||||||
|
} else {
|
||||||
|
// Add new entry
|
||||||
|
addressBook.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.setSync('address-book.json', addressBook);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save address book entry:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAddressBookEntry(address) {
|
||||||
|
try {
|
||||||
|
const addressBook = this.getAddressBook();
|
||||||
|
const filtered = addressBook.filter(item => item.address !== address);
|
||||||
|
|
||||||
|
storage.setSync('address-book.json', filtered);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to remove address book entry:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings operations
|
||||||
|
getSettings() {
|
||||||
|
try {
|
||||||
|
const data = storage.getSync(this.settingsFile);
|
||||||
|
return data || {
|
||||||
|
rpcUrl: 'http://localhost:26657',
|
||||||
|
autoConnect: true,
|
||||||
|
notifications: true
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
rpcUrl: 'http://localhost:26657',
|
||||||
|
autoConnect: true,
|
||||||
|
notifications: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSetting(key, value) {
|
||||||
|
try {
|
||||||
|
const settings = this.getSettings();
|
||||||
|
settings[key] = value;
|
||||||
|
storage.setSync(this.settingsFile, settings);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save setting:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings(settingsObj) {
|
||||||
|
try {
|
||||||
|
storage.setSync(this.settingsFile, settingsObj);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save settings:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction history (local cache)
|
||||||
|
getTransactionHistory(address) {
|
||||||
|
try {
|
||||||
|
const data = storage.getSync(`tx-history-${address}.json`);
|
||||||
|
return data || [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveTransaction(address, transaction) {
|
||||||
|
try {
|
||||||
|
const history = this.getTransactionHistory(address);
|
||||||
|
|
||||||
|
// Check if transaction already exists
|
||||||
|
const exists = history.some(tx => tx.hash === transaction.hash);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
history.unshift(transaction); // Add to beginning
|
||||||
|
|
||||||
|
// Keep only last 100 transactions
|
||||||
|
if (history.length > 100) {
|
||||||
|
history.splice(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.setSync(`tx-history-${address}.json`, history);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save transaction:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility methods
|
||||||
|
clearAllData() {
|
||||||
|
try {
|
||||||
|
storage.removeSync(this.walletFile);
|
||||||
|
storage.removeSync(this.settingsFile);
|
||||||
|
storage.removeSync('address-book.json');
|
||||||
|
|
||||||
|
// Clear transaction histories
|
||||||
|
const wallets = this.getWallets();
|
||||||
|
wallets.addresses.forEach(address => {
|
||||||
|
try {
|
||||||
|
storage.removeSync(`tx-history-${address}.json`);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore errors for non-existent files
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to clear data:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exportWallets() {
|
||||||
|
try {
|
||||||
|
const wallets = this.getWallets();
|
||||||
|
const settings = this.getSettings();
|
||||||
|
const addressBook = this.getAddressBook();
|
||||||
|
|
||||||
|
return {
|
||||||
|
wallets: wallets,
|
||||||
|
settings: settings,
|
||||||
|
addressBook: addressBook,
|
||||||
|
exportDate: new Date().toISOString()
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to export data:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
importWallets(data) {
|
||||||
|
try {
|
||||||
|
if (data.wallets) {
|
||||||
|
storage.setSync(this.walletFile, data.wallets);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.settings) {
|
||||||
|
storage.setSync(this.settingsFile, data.settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.addressBook) {
|
||||||
|
storage.setSync('address-book.json', data.addressBook);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to import data:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create global instance
|
||||||
|
const PaperclipDatabase = new PaperclipDatabase();
|
||||||
|
|
||||||
|
// Make it available globally
|
||||||
|
window.PaperclipDatabase = PaperclipDatabase;
|
||||||
|
|
||||||
|
module.exports = PaperclipDatabase;
|
||||||
271
renderer/paperclip-wallets.js
Normal file
271
renderer/paperclip-wallets.js
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
const {ipcRenderer} = require("electron");
|
||||||
|
|
||||||
|
class PaperclipWallets {
|
||||||
|
constructor() {
|
||||||
|
this.addressList = [];
|
||||||
|
this.price = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPrice() {
|
||||||
|
return this.price;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setPrice(price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAddressList() {
|
||||||
|
return this.addressList;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAddressList() {
|
||||||
|
this.addressList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAddressExists(address) {
|
||||||
|
if (address) {
|
||||||
|
return this.addressList.indexOf(address) > -1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAddressToList(address) {
|
||||||
|
if (address) {
|
||||||
|
this.addressList.push(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNewWallet(password) {
|
||||||
|
try {
|
||||||
|
const ClipsCrypto = require('../modules/clips-crypto.js');
|
||||||
|
const keyPair = ClipsCrypto.generateKeyPair();
|
||||||
|
|
||||||
|
// Store wallet in database
|
||||||
|
const wallet = {
|
||||||
|
address: keyPair.address,
|
||||||
|
publicKey: keyPair.publicKey,
|
||||||
|
privateKey: keyPair.privateKey, // In production, encrypt this with password
|
||||||
|
name: `Account ${this.addressList.length + 1}`,
|
||||||
|
created: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
PaperclipDatabase.saveWallet(wallet);
|
||||||
|
this.addAddressToList(keyPair.address);
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to create wallet: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async importFromPrivateKey(privateKeyHex, password) {
|
||||||
|
try {
|
||||||
|
const ClipsCrypto = require('../modules/clips-crypto.js');
|
||||||
|
const keyPair = ClipsCrypto.importPrivateKey(privateKeyHex);
|
||||||
|
|
||||||
|
const wallet = {
|
||||||
|
address: keyPair.address,
|
||||||
|
publicKey: keyPair.publicKey,
|
||||||
|
privateKey: keyPair.privateKey,
|
||||||
|
name: `Imported Account`,
|
||||||
|
created: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
PaperclipDatabase.saveWallet(wallet);
|
||||||
|
this.addAddressToList(keyPair.address);
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to import wallet: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendTransaction(fromAddress, toAddress, amount, password) {
|
||||||
|
try {
|
||||||
|
// Get wallet data
|
||||||
|
const wallet = PaperclipDatabase.getWallet(fromAddress);
|
||||||
|
if (!wallet) {
|
||||||
|
throw new Error("Wallet not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate address format
|
||||||
|
const ClipsCrypto = require('../modules/clips-crypto.js');
|
||||||
|
if (!ClipsCrypto.isValidAddress(toAddress)) {
|
||||||
|
throw new Error("Invalid recipient address");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current nonce
|
||||||
|
const nonce = await ipcRenderer.invoke("paperclip-get-nonce", fromAddress);
|
||||||
|
|
||||||
|
// Create and sign transaction
|
||||||
|
const transaction = ClipsCrypto.createTransaction({
|
||||||
|
sender: fromAddress,
|
||||||
|
receiver: toAddress,
|
||||||
|
amount: parseInt(amount),
|
||||||
|
nonce: nonce,
|
||||||
|
type: 'transfer',
|
||||||
|
data: '',
|
||||||
|
privateKey: wallet.privateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
// Broadcast transaction
|
||||||
|
const result = await ipcRenderer.invoke("paperclip-broadcast-tx",
|
||||||
|
ClipsCrypto.transactionToHex(transaction));
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return {
|
||||||
|
hash: result.hash,
|
||||||
|
height: result.height
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(result.error || "Transaction failed");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Transaction failed: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWalletBalance(address) {
|
||||||
|
try {
|
||||||
|
const balance = await ipcRenderer.invoke("paperclip-get-balance", address);
|
||||||
|
return balance;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to get balance for ${address}:`, error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshWalletData() {
|
||||||
|
try {
|
||||||
|
this.clearAddressList();
|
||||||
|
|
||||||
|
const wallets = PaperclipDatabase.getWallets();
|
||||||
|
if (!wallets || !wallets.addresses) {
|
||||||
|
return {
|
||||||
|
sumBalance: 0,
|
||||||
|
addressData: [],
|
||||||
|
sumBalanceFormatted: "0 CLIPS"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
sumBalance: 0,
|
||||||
|
addressData: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get balance for each wallet
|
||||||
|
for (const address of wallets.addresses) {
|
||||||
|
const wallet = PaperclipDatabase.getWallet(address);
|
||||||
|
const balance = await this.getWalletBalance(address);
|
||||||
|
|
||||||
|
data.addressData.push({
|
||||||
|
address: address,
|
||||||
|
name: wallet ? wallet.name : `Account`,
|
||||||
|
balance: balance,
|
||||||
|
balanceFormatted: this.formatBalance(balance)
|
||||||
|
});
|
||||||
|
|
||||||
|
data.sumBalance += balance;
|
||||||
|
this.addAddressToList(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.sumBalanceFormatted = this.formatBalance(data.sumBalance);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to refresh wallet data: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBalance(balance) {
|
||||||
|
if (balance === 0) {
|
||||||
|
return "0 CLIPS";
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateAddress(address) {
|
||||||
|
const ClipsCrypto = require('../modules/clips-crypto.js');
|
||||||
|
return ClipsCrypto.isValidAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateAmount(amount) {
|
||||||
|
const num = parseFloat(amount);
|
||||||
|
return !isNaN(num) && num > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Event Handlers
|
||||||
|
async handleCreateNewWallet() {
|
||||||
|
const password = $("#walletPasswordFirst").val();
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
throw new Error("Password cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== $("#walletPasswordSecond").val()) {
|
||||||
|
throw new Error("Passwords do not match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.createNewWallet(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleImportPrivateKey() {
|
||||||
|
const privateKey = $("#inputPrivateKey").val();
|
||||||
|
const password = $("#keyPasswordFirst").val();
|
||||||
|
|
||||||
|
if (!privateKey) {
|
||||||
|
throw new Error("Private key cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
throw new Error("Password cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== $("#keyPasswordSecond").val()) {
|
||||||
|
throw new Error("Passwords do not match");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.importFromPrivateKey(privateKey, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleSendTransaction() {
|
||||||
|
const fromAddress = $("#sendFromAddress").val();
|
||||||
|
const toAddress = $("#sendToAddress").val();
|
||||||
|
const amount = $("#sendAmount").val();
|
||||||
|
const password = $("#sendPassword").val();
|
||||||
|
|
||||||
|
if (!fromAddress) {
|
||||||
|
throw new Error("From address is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toAddress) {
|
||||||
|
throw new Error("To address is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.validateAddress(toAddress)) {
|
||||||
|
throw new Error("Invalid recipient address format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.validateAmount(amount)) {
|
||||||
|
throw new Error("Invalid amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
throw new Error("Password is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.sendTransaction(fromAddress, toAddress, amount, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create global instance
|
||||||
|
const ClipsWallet = new PaperclipWallets();
|
||||||
|
|
||||||
|
// Make it available globally
|
||||||
|
window.ClipsWallet = ClipsWallet;
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
const {ipcRenderer} = require("electron");
|
const {ipcRenderer} = require("electron");
|
||||||
|
|
||||||
class Wallets {
|
class PaperclipWallets {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.addressList = [];
|
this.addressList = [];
|
||||||
|
this.price = 0; // CLIPS price placeholder
|
||||||
$.getJSON("https://min-api.cryptocompare.com/data/price?fsym=ETHO&tsyms=USD", function (price) {
|
|
||||||
EthoWallets._setPrice(price.USD);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPrice() {
|
_getPrice() {
|
||||||
@@ -35,32 +32,32 @@ class Wallets {
|
|||||||
|
|
||||||
addAddressToList(address) {
|
addAddressToList(address) {
|
||||||
if (address) {
|
if (address) {
|
||||||
this.addressList.push(address.toLowerCase());
|
this.addressList.push(address); // CLIPS addresses are case-sensitive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enableButtonTooltips() {
|
enableButtonTooltips() {
|
||||||
EthoUtils.createToolTip("#btnNewAddress", "Create New Address");
|
PaperclipUtils.createToolTip("#btnNewAddress", "Create New Address");
|
||||||
EthoUtils.createToolTip("#btnRefreshAddress", "Refresh Address List");
|
PaperclipUtils.createToolTip("#btnRefreshAddress", "Refresh Address List");
|
||||||
EthoUtils.createToolTip("#btnExportAccounts", "Export Accounts");
|
PaperclipUtils.createToolTip("#btnExportAccounts", "Export Accounts");
|
||||||
EthoUtils.createToolTip("#btnImportAccounts", "Import Accounts");
|
PaperclipUtils.createToolTip("#btnImportAccounts", "Import Accounts");
|
||||||
EthoUtils.createToolTip("#btnImportFromPrivateKey", "Import From Private Key");
|
PaperclipUtils.createToolTip("#btnImportFromPrivateKey", "Import From Private Key");
|
||||||
}
|
}
|
||||||
|
|
||||||
validateNewAccountForm() {
|
validateNewAccountForm() {
|
||||||
if (EthoMainGUI.getAppState() == "account") {
|
if (PaperclipMainGUI.getAppState() == "account") {
|
||||||
if (!$("#walletPasswordFirst").val()) {
|
if (!$("#walletPasswordFirst").val()) {
|
||||||
EthoMainGUI.showGeneralError("Password cannot be empty!");
|
PaperclipMainGUI.showGeneralError("Password cannot be empty!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$("#walletPasswordSecond").val()) {
|
if (!$("#walletPasswordSecond").val()) {
|
||||||
EthoMainGUI.showGeneralError("Password cannot be empty!");
|
PaperclipMainGUI.showGeneralError("Password cannot be empty!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($("#walletPasswordFirst").val() !== $("#walletPasswordSecond").val()) {
|
if ($("#walletPasswordFirst").val() !== $("#walletPasswordSecond").val()) {
|
||||||
EthoMainGUI.showGeneralError("Passwords do not match!");
|
PaperclipMainGUI.showGeneralError("Passwords do not match!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user