gib-cli
Version:
392 lines (387 loc) ⢠15.5 kB
JavaScript
#!/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);
});