#!/usr/bin/env node // coinos.io-Client (Konto "kielaiagent", custodial LN+Liquid). // Login-Daten aus zugaenge.txt (coinos-user/coinos-pw), Token gecacht in var/. // Nutzung: // node geld/coinos.js balance — Saldo in sats // node geld/coinos.js zahlungen — letzte Zahlungen // node geld/coinos.js pay — Lightning-Invoice zahlen // node geld/coinos.js send — an Lightning-Adresse zahlen // node geld/coinos.js liquid — neue Liquid-Einzahladresse // (Einzahlungen <300 sats verwirft coinos' Dust-Filter!) const fs = require('fs'); const path = require('path'); const BASE = 'https://coinos.io/api'; const TOKENFILE = path.join(__dirname, '../var/coinos-token.json'); const ZUGAENGE = path.join(__dirname, '../zugaenge.txt'); function creds() { const t = fs.readFileSync(ZUGAENGE, 'utf8'); const user = (t.match(/^coinos-user:\s*(\S+)/m) || [])[1]; const pw = (t.match(/^coinos-pw:\s*(\S+)/m) || [])[1]; if (!user || !pw) throw new Error('coinos-user/coinos-pw nicht in zugaenge.txt'); return { user, pw }; } async function token() { if (fs.existsSync(TOKENFILE)) { const { token: t } = JSON.parse(fs.readFileSync(TOKENFILE, 'utf8')); const r = await fetch(`${BASE}/me`, { headers: { authorization: `Bearer ${t}` } }); if (r.ok) return t; } const { user, pw } = creds(); const r = await fetch(`${BASE}/login`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ username: user, password: pw }), }); if (!r.ok) throw new Error(`login ${r.status}: ${await r.text()}`); const j = await r.json(); fs.writeFileSync(TOKENFILE, JSON.stringify({ token: j.token }), { mode: 0o600 }); return j.token; } async function api(method, p, body, t) { const r = await fetch(`${BASE}${p}`, { method, headers: { 'content-type': 'application/json', authorization: `Bearer ${t}` }, body: body ? JSON.stringify(body) : undefined, }); const text = await r.text(); if (!r.ok) throw new Error(`${method} ${p} ${r.status}: ${text.slice(0, 300)}`); try { return JSON.parse(text); } catch { return text; } } (async () => { const [cmd, a, b] = process.argv.slice(2); const t = await token(); if (cmd === 'balance') { const me = await api('GET', '/me', null, t); console.log(me.balance, 'sats'); } else if (cmd === 'zahlungen') { const ps = await api('GET', '/payments', null, t); for (const p of (Array.isArray(ps) ? ps : ps.payments || []).slice(0, 10)) console.log(new Date(Number(p.createdAt || p.created)).toISOString(), p.amount, p.type, p.confirmed ? 'ok' : 'PENDING'); } else if (cmd === 'pay') { if (!a || !a.startsWith('lnbc')) throw new Error('bolt11 fehlt'); const res = await api('POST', '/payments', { payreq: a }, t); console.log('Bezahlt:', res.amount, 'sats, fee', res.fee, 'id', res.id); } else if (cmd === 'send') { const sats = parseInt(b, 10); if (!a || !a.includes('@') || !sats) throw new Error('Nutzung: send '); const res = await api('POST', `/send/${a}/${sats}`, {}, t); console.log('Gesendet:', res.amount, 'sats, fee', res.fee, 'id', res.id); } else if (cmd === 'invoice') { const sats = parseInt(a, 10); if (!sats) throw new Error('Nutzung: invoice '); const inv = await api('POST', '/invoice', { invoice: { type: 'lightning', amount: sats } }, t); console.log(inv.text || inv.hash); } else if (cmd === 'liquid') { const inv = await api('POST', '/invoice', { invoice: { type: 'liquid', amount: 0 } }, t); console.log(inv.text); } else { console.log('Kommandos: balance | zahlungen | pay | send | liquid'); } })().catch((e) => { console.error('FEHLER:', e.message); process.exit(1); });