@getalby/pkgzap-cli
Version:
Boost your project dependencies via lightning ⚡️
151 lines (128 loc) • 5.22 kB
JavaScript
import {webln} from 'alby-js-sdk'
import { LightningAddress } from 'alby-tools'
import chalk from 'chalk'
import fs from 'fs'
import readline from 'readline'
import 'websocket-polyfill';
import * as crypto from 'node:crypto';
import { fetchFundingInfo } from '@getalby/pkgzap';
import os from 'os';
global.crypto = crypto;
function encryptData(data, password) {
const salt = crypto.randomBytes(16);
const key = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256');
const iv = crypto.randomBytes(16);
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(data);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return salt.toString('hex') + iv.toString('hex') + encrypted.toString('hex')
}
function decryptData(cipher, password) {
const salt = Buffer.from(cipher.slice(0, 32), 'hex');
const iv = Buffer.from(cipher.slice(32, 64), 'hex');
const key = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256');
let encryptedText = Buffer.from(cipher.slice(64), 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const homedir = os.homedir();
const nwcPath = `${homedir}/.pkgzap`;
function waitForInput(question) {
return new Promise(resolve => {
rl.question(question, (answer) => {
resolve(answer);
});
});
}
async function authenticate() {
const nwc = webln.NostrWebLNProvider.withNewSecret();
const url = await nwc.getAuthorizationUrl({ name: 'pkgzap' });
console.log(chalk.green(`Please approve the NWC connection: ${chalk.blue.underline(url)}`))
await waitForInput(`And press enter/return to continue...`);
const nwcData = nwc.getNostrWalletConnectUrl();
console.log("Authentication Successful. Saving the NostrWalletConnect URL...");
const password = await waitForInput(chalk.magenta(`Enter a password to encrypt the NWC URL: `));
fs.writeFile(nwcPath, encryptData(nwcData, password), (err) => {
if (err) {
console.error(chalk.red("Error saving NostrWalletConnect URL"));
console.error(chalk.red(err.error || err));
return;
}
console.log(chalk.green(`Saved in ${nwcPath}`));
});
return nwc;
}
async function payLNDependencyOwner(nwc, packageName, lnAddress, amount) {
try {
const ln = new LightningAddress(lnAddress);
await ln.fetch();
const invoice = await ln.requestInvoice({satoshi: amount});
await nwc.sendPayment(invoice.paymentRequest);
console.info(chalk.yellow(packageName + ": ") + chalk.green(`Payment Successful!`));
}
catch (e) {
console.error(chalk.red(e.error || e));
if (e.error === 'The public key does not have a wallet connected.') {
console.error(chalk.yellow("Try again to authenticate."));
fs.writeFile(nwcPath, "", (err) => {
if (err) {
console.error(chalk.red("Error removing NostrWalletConnect URL"));
console.error(chalk.red(err.error || err));
return;
}
});
}
console.error(chalk.red("Aborting..."));
process.exit(1);
}
return;
}
export async function cli() {
console.log(chalk.yellow(`Send sats to your project's dependencies!`))
let packageJsonData
try {
packageJsonData = await fs.promises.readFile('package.json', 'utf8');
} catch (e) {
console.error(chalk.red("Error reading package.json, does it exist?"));
console.error(chalk.red("Aborting..."));
process.exit(1);
}
const fundingInfo = await fetchFundingInfo(JSON.parse(packageJsonData), 1, process);
const deps = Object.keys(fundingInfo).length;
process.stdout.clearLine(0);
if (!deps) {
console.log(chalk.yellow(`\rNo dependencies with lightning details found :(`))
console.log(chalk.cyan(`Let developers know how they can receive sats: https://getalby.github.io/pkgzap/#developer`))
process.exit();
}
console.log(chalk.cyan(`\rFound ${deps} dependencies with lightning details.`))
const amount = await waitForInput(chalk.magenta(`How much do you want to send in total? Amount (in sats): `));
let nwc;
try {
const nwcData = await fs.promises.readFile(nwcPath, 'utf8');
if (!nwcData) throw new Error("no nwc data");
console.log(chalk.cyan('Trying to fetch NWC with the provided URL...'));
const password = await waitForInput(chalk.magenta(`Enter your password to decrypt the NWC URL: `));
nwc = new webln.NostrWebLNProvider({ nostrWalletConnectUrl: decryptData(nwcData, password) });
} catch (e) {
if (e.message.indexOf("bad decrypt") !== -1) {
console.log(chalk.red('Invalid password, authenticate again.'));
}
nwc = await authenticate();
}
await nwc.enable();
const satsPerDep = Math.floor(amount/deps);
console.log(`Supporting ${deps} packages with ${satsPerDep} sats each...`);
for (const [pkgName, lnAddress] of Object.entries(fundingInfo)) {
await payLNDependencyOwner(nwc, pkgName, lnAddress, Math.floor(amount/deps));
}
process.exit();
}
cli()