Add Nostr ecash transaction history CLI
- CLI tool to export NIP-60 (Cashu Wallet) and NIP-61 (Nutzap) transaction history as CSV - Fetches and decrypts kind:17375 wallet, kind:7375 token, kind:7376 history, kind:9321 nutzap, and kind:7377 redemption events - Accepts NOSTR_SECRET_KEY env var and optional --mints flag - Outputs CSV to stdout with columns: date,type,amount,unit,mint,token_id,memo - Progress and errors go to stderr
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { initNDK } from "./ndk.js";
|
||||
import {
|
||||
fetchWalletData,
|
||||
fetchTokenEvents,
|
||||
fetchHistoryEvents,
|
||||
fetchNutzapEvents,
|
||||
fetchNutzapRedemptions,
|
||||
buildTransactionHistory,
|
||||
} from "./events.js";
|
||||
import { formatCSV } from "./csv.js";
|
||||
|
||||
function usage(): never {
|
||||
console.error(`Usage: NOSTR_SECRET_KEY=<hex|nsec> bun run src/index.ts [--mints <url1,url2,...>]
|
||||
|
||||
Options:
|
||||
--mints Comma-separated list of additional mint URLs to include
|
||||
|
||||
Environment:
|
||||
NOSTR_SECRET_KEY Required. Nostr private key in hex or nsec format.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const secretKey = process.env.NOSTR_SECRET_KEY;
|
||||
if (!secretKey) {
|
||||
console.error("Error: NOSTR_SECRET_KEY environment variable is required");
|
||||
usage();
|
||||
}
|
||||
|
||||
// Parse --mints flag
|
||||
const args = process.argv.slice(2);
|
||||
const mintsIdx = args.indexOf("--mints");
|
||||
const extraMints =
|
||||
mintsIdx !== -1 && args[mintsIdx + 1]
|
||||
? args[mintsIdx + 1].split(",").filter(Boolean)
|
||||
: [];
|
||||
|
||||
// Initialize NDK
|
||||
const { ndk, signer, user } = await initNDK(secretKey, extraMints);
|
||||
|
||||
// Fetch wallet data first (need mints for context)
|
||||
const walletData = await fetchWalletData(ndk, user, signer);
|
||||
const allMints = [...new Set([...walletData.mints, ...extraMints])];
|
||||
if (allMints.length > 0) {
|
||||
console.error(`Discovered mints: ${allMints.join(", ")}`);
|
||||
}
|
||||
|
||||
// Fetch all events in parallel
|
||||
const [tokens, history, nutzaps, redemptions] = await Promise.all([
|
||||
fetchTokenEvents(ndk, user, signer),
|
||||
fetchHistoryEvents(ndk, user, signer),
|
||||
fetchNutzapEvents(ndk, user),
|
||||
fetchNutzapRedemptions(ndk, user, signer),
|
||||
]);
|
||||
|
||||
// Build unified transaction history
|
||||
const records = buildTransactionHistory(
|
||||
walletData,
|
||||
tokens,
|
||||
history,
|
||||
nutzaps,
|
||||
redemptions
|
||||
);
|
||||
|
||||
console.error(`Total transactions: ${records.length}`);
|
||||
|
||||
// Output CSV to stdout
|
||||
const csv = formatCSV(records);
|
||||
process.stdout.write(csv + "\n");
|
||||
|
||||
// Clean shutdown
|
||||
// NDK doesn't have a disconnect method that returns a promise reliably,
|
||||
// so we just exit after output
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Handle SIGINT
|
||||
process.on("SIGINT", () => {
|
||||
console.error("\nInterrupted.");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user