UNPKG

gib-cli

Version:
392 lines (387 loc) • 15.5 kB
#!/usr/bin/env node console.log(` ______ _____ ______ _ _ _ _______ _______ _______ | ____ | |_____] | | | |_____| | | |______ | |_____| __|__ |_____] |__|__| | | |_____ |_____ |______ | For all your MNEE and BSV needs. `); import dotenv from "dotenv"; dotenv.config(); import open from "open"; import { Command } from "commander"; import inquirer from "inquirer"; import keytar from "keytar"; import crypto from "crypto"; import axios from "axios"; import { PrivateKey } from "@bsv/sdk"; import { decryptPrivateKey, encryptPrivateKey } from "./utils/crytpo.js"; import { MNEEService } from "./Mnee.service.js"; import { singleLineLogger } from "./utils/helper.js"; import { PaymailClient } from "@bsv/paymail/client"; const paymailClient = new PaymailClient(); const mneeService = new MNEEService(); const program = new Command(); const SERVICE_NAME = "mnee-cli"; const PAYMAIL_SERVER = process.env.PAYMAIL_SERVER || "http://127.0.0.1:5001"; const RECEIVING_WALLET_ADDRESS = process.env.RECEIVING_WALLET_ADDRESS || "1JPnXcwKoX1YUyXcxEAXLmHdDDW4hiWnt6"; const safePrompt = async (questions) => { try { return await inquirer.prompt(questions); } catch { console.log("\nāŒ Operation cancelled by user."); process.exit(1); } }; async function saveTransaction(txid) { try { // Retrieve stored transactions from keytar const storedTxs = await keytar.getPassword(SERVICE_NAME, "transactionHistory"); let transactionHistory = storedTxs ? JSON.parse(storedTxs) : []; // Add new transaction transactionHistory.unshift(txid); // Add new TXID to the beginning // Keep only the last 10 transactions if (transactionHistory.length > 10) { transactionHistory.pop(); // Remove the oldest transaction } // Save updated history back to keytar await keytar.setPassword(SERVICE_NAME, "transactionHistory", JSON.stringify(transactionHistory)); console.log("āœ… Transaction saved locally."); } catch (error) { console.error("āŒ Error saving transaction:", error); } } async function transferMNEE(toAddress, amount, paymail) { try { console.log("\nšŸ’° Sending MNEE payment..."); const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const { password } = await safePrompt([ { type: "password", name: "password", message: "Enter your wallet password:", mask: "*", }, ]); const encryptedKey = await keytar.getPassword(SERVICE_NAME, "privateKey"); if (!encryptedKey) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const privateKeyHex = decryptPrivateKey(encryptedKey, password); if (!privateKeyHex) { console.error("āŒ Incorrect password! Decryption failed."); return; } const privateKey = PrivateKey.fromString(privateKeyHex); const request = [{ address: toAddress, amount }]; const { txid, error } = await mneeService.transfer(address, request, privateKey, singleLineLogger); if (!txid) { console.error(`āŒ Transfer failed. ${error || "Please try again."}`); return; } console.log(`āœ… MNEE transaction successful! TXID: ${txid}`); // Register the new owner of the Paymail await axios.post("http://127.0.0.1:5001/register", { paymail, amount, ownerAddress: address, }); console.log(`āœ… Successfully purchased Paymail '${paymail}'!`); } catch (error) { console.error("āŒ Error processing purchase:", error); } } program .command("create") .description("Generate a new wallet and store keys securely") .action(async () => { try { const existingAddress = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (existingAddress) { console.error("āŒ Wallet already exists. Run `mnee export-key` to retrieve keys."); return; } const entropy = crypto.randomBytes(32); const privateKey = PrivateKey.fromString(entropy.toString("hex")); const address = privateKey.toAddress(); const { password, confirmPassword } = await safePrompt([ { type: "password", name: "password", message: "Set a password for your wallet:", mask: "*", }, { type: "password", name: "confirmPassword", message: "Confirm your password:", mask: "*", }, ]); if (password !== confirmPassword) { console.error("āŒ Passwords do not match. Try again."); return; } const encryptedKey = encryptPrivateKey(privateKey.toString(), password); await keytar.setPassword(SERVICE_NAME, "privateKey", encryptedKey); await keytar.setPassword(SERVICE_NAME, "walletAddress", address); console.log("\nāœ… Wallet created successfully!"); console.log(`\n${address}\n`); } catch (error) { console.error("\nāŒ Error creating wallet:", error); } }); program .command("balance") .description("Get the balance of the wallet (MNEE & BSV)") .action(async () => { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } singleLineLogger.start("Fetching balances..."); // Fetch MNEE balance const { decimalAmount: mneeBalance } = await mneeService.getBalance(address); // Fetch BSV balance from WhatsOnChain API try { const response = await axios.get(`https://api.whatsonchain.com/v1/bsv/main/address/${address}/balance`); const bsvBalance = response.data.confirmed / 1e8; // Convert satoshis to BSV singleLineLogger.done(`\nšŸ’° Balances: \n- ${mneeBalance} MNEE\n- ${bsvBalance} BSV`); } catch (error) { singleLineLogger.done("āŒ Error fetching BSV balance."); } }); program .command("address") .description("Retrieve your wallet address") .action(async () => { try { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create` first."); return; } console.log(`\nšŸ“¬ Wallet Address:\n${address}\n`); } catch (error) { console.error("āŒ Error retrieving wallet address:", error); } }); program .command("transfer") .description("Transfer MNEE to another address or Paymail") .action(async () => { try { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const { amount, recipient } = await safePrompt([ { type: "input", name: "amount", message: "Enter the amount to transfer:" }, { type: "input", name: "recipient", message: "Enter the recipient's address or Paymail:" }, ]); const { password } = await safePrompt([ { type: "password", name: "password", message: "Enter your wallet password:", mask: "*" }, ]); const encryptedKey = await keytar.getPassword(SERVICE_NAME, "privateKey"); if (!encryptedKey) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const privateKeyHex = decryptPrivateKey(encryptedKey, password); if (!privateKeyHex) { console.error("āŒ Incorrect password! Decryption failed."); return; } const privateKey = PrivateKey.fromString(privateKeyHex); const request = [{ address: recipient, amount: parseFloat(amount) }]; singleLineLogger.start("Transferring MNEE..."); const { txid, error } = await mneeService.transfer(address, request, privateKey, singleLineLogger); if (!txid) { singleLineLogger.done(`āŒ Transfer failed. ${error || "Please try again."}`); return; } // **SAVE TX HISTORY LOCALLY** await saveTransaction(txid); singleLineLogger.done(`\nāœ… Transfer successful! TXID:\n${txid}\n`); } catch (error) { console.error("\nāŒ Operation interrupted."); process.exit(1); } }); program .command("send-bsv") .description("Send BSV to another address") .action(async () => { try { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const { amount, toAddress } = await safePrompt([ { type: "input", name: "amount", message: "Enter the amount of BSV to send:" }, { type: "input", name: "toAddress", message: "Enter the recipient's BSV address:" }, ]); const { password } = await safePrompt([ { type: "password", name: "password", message: "Enter your wallet password:", mask: "*" }, ]); const encryptedKey = await keytar.getPassword(SERVICE_NAME, "privateKey"); if (!encryptedKey) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const privateKeyHex = decryptPrivateKey(encryptedKey, password); if (!privateKeyHex) { console.error("āŒ Incorrect password! Decryption failed."); return; } const privateKey = PrivateKey.fromString(privateKeyHex); // Build BSV transaction singleLineLogger.start("Sending BSV..."); const response = await axios.post(`https://api.whatsonchain.com/v1/bsv/main/tx/send`, { inputs: [{ address, privateKey: privateKey.toWif() }], outputs: [{ address: toAddress, amount: parseFloat(amount) }], }); if (!response.data || !response.data.txid) { singleLineLogger.done("āŒ BSV Transaction Failed."); return; } const txid = response.data.txid; await saveTransaction(txid); // Save TX history locally singleLineLogger.done(`\nāœ… BSV Sent! TXID: ${txid}`); } catch (error) { console.error("\nāŒ Error sending BSV:", error); } }); program .command("tx-history") .description("Retrieve last 10 transactions (MNEE & BSV)") .action(async () => { try { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } // Retrieve stored transactions (MNEE & BSV) const storedTxs = await keytar.getPassword(SERVICE_NAME, "transactionHistory"); let transactionHistory = storedTxs ? JSON.parse(storedTxs) : []; // Fetch latest BSV transactions from WhatsOnChain API const bsvTxs = await axios.get(`https://api.whatsonchain.com/v1/bsv/main/address/${address}/history`); const bsvTxIds = bsvTxs.data.slice(0, 10).map((tx) => tx.tx_hash); console.log("\nšŸ“œ Transaction History (Last 10 TXs):\n"); [...transactionHistory, ...bsvTxIds].slice(0, 10).forEach((txid, index) => { console.log(`${index + 1}. TXID: ${txid}`); }); } catch (error) { console.error("āŒ Error retrieving transaction history:", error); } }); program .command("shop") .description("Open the Paymail Shop in your browser with your wallet") .action(async () => { try { // Retrieve wallet address from CLI storage const walletAddress = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!walletAddress) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } console.log(`šŸŒ Opening Paymail Shop with wallet: ${walletAddress}`); // Open the shop and indicate the user is from CLI await open(`http://localhost:3000?wallet=${walletAddress}&source=cli`); } catch (error) { console.error("āŒ Failed to open the Paymail Shop:", error); } }); program .command("my-paymails") .description("View your purchased Paymails") .action(async () => { try { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const response = await axios.get(`http://127.0.0.1:5001/my-paymails/${address}`); if (!response.data.paymails.length) { console.log("āŒ You do not own any Paymails."); return; } console.log("\nšŸ“œ Your Paymails:\n"); response.data.paymails.forEach((p, index) => { console.log(`${index + 1}. ${p.paymail}`); }); } catch (error) { console.error("āŒ Error retrieving your Paymails:", error); } }); program .command("sell-paymail <paymail> <price>") .description("List your Paymail for sale at a set MNEE price") .action(async (paymail, price) => { try { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } const response = await axios.post(`http://127.0.0.1:5001/sell-paymail`, { paymail, price: parseInt(price), ownerAddress: address, }); console.log(`āœ… ${response.data.message}`); } catch (error) { console.error("āŒ Error listing Paymail for sale:", error); } }); program .command("unlist-paymail <paymail>") .description("Remove your Paymail from sale") .action(async (paymail) => { try { const address = await keytar.getPassword(SERVICE_NAME, "walletAddress"); if (!address) { console.error("āŒ No wallet found. Run `mnee create-wallet` first."); return; } // Call the server to unlist the Paymail const response = await axios.post(`http://127.0.0.1:5001/unlist-paymail`, { paymail, ownerAddress: address, }); console.log(`āœ… ${response.data.message}`); } catch (error) { console.error("āŒ Error unlisting Paymail:", error); } }); program.parse(process.argv); if (!process.argv.slice(2).length) { program.help(); } process.on("SIGINT", () => { console.log("\nšŸ‘‹ Exiting program gracefully..."); process.exit(0); });