2026-03-18 15:09:48 +00:00
|
|
|
import { randomBytes } from "crypto";
|
2026-03-18 21:01:13 -04:00
|
|
|
import { makeLogger } from "./logger.js";
|
|
|
|
|
|
|
|
|
|
const logger = makeLogger("lnbits");
|
2026-03-18 14:59:02 +00:00
|
|
|
|
|
|
|
|
export interface LNbitsInvoice {
|
|
|
|
|
paymentHash: string;
|
|
|
|
|
paymentRequest: string;
|
|
|
|
|
}
|
|
|
|
|
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
export interface LNbitsConfig {
|
|
|
|
|
url: string;
|
|
|
|
|
apiKey: string;
|
2026-03-18 14:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
const stubPaidInvoices = new Set<string>();
|
2026-03-18 15:09:48 +00:00
|
|
|
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
export class LNbitsService {
|
|
|
|
|
private readonly url: string;
|
|
|
|
|
private readonly apiKey: string;
|
|
|
|
|
readonly stubMode: boolean;
|
2026-03-18 14:59:02 +00:00
|
|
|
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
constructor(config?: Partial<LNbitsConfig>) {
|
|
|
|
|
this.url = config?.url ?? process.env.LNBITS_URL ?? "";
|
|
|
|
|
this.apiKey = config?.apiKey ?? process.env.LNBITS_API_KEY ?? "";
|
|
|
|
|
this.stubMode = !this.url || !this.apiKey;
|
|
|
|
|
if (this.stubMode) {
|
2026-03-18 21:01:13 -04:00
|
|
|
logger.warn("no LNBITS_URL/LNBITS_API_KEY — running in STUB mode", { stub: true });
|
feat(task-25): real LNbits mode on Hermes VPS — 29/29 testkit PASS
Task #25: Provision LNbits on Hermes VPS for real Lightning payments.
## Infrastructure (Hermes VPS 143.198.27.163)
- PostgreSQL 16 installed, lnbits DB + user created
- LNbits 0.12.12 installed in /opt/lnbits/.venv (Python 3.11 venv)
- /opt/lnbits/run.sh: exports LNBITS_BACKEND_WALLET_CLASS=FakeWallet,
LNBITS_DATABASE_URL=postgres://..., starts lnbits on 0.0.0.0:5000
- systemd unit at /etc/systemd/system/lnbits.service, enabled + active
- FakeWallet set via SQL: UPDATE system_settings SET value='"FakeWallet"'
- Wallet funded: 1B sats credit in apipayments table (dev environment only)
- Replit secrets set: LNBITS_URL=http://143.198.27.163:5000, LNBITS_API_KEY=...
## Provisioning runbook
- scripts/hermes-lnbits/provision.sh: idempotent Ubuntu 24.04 setup script
covering PostgreSQL, venv, run.sh, systemd unit, FakeWallet SQL, health check
## API server code changes (real-mode plumbing)
- lib/lnbits.ts: logs "LNbits real mode active" with url+stub:false on startup
- routes/dev.ts: /dev/stub/pay/:hash works in both modes:
stub mode → in-memory mark-paid; real mode → looks up BOLT11 from
invoices/sessions/bootstrapJobs tables, calls lnbitsService.payInvoice()
- routes/sessions.ts: remove all stubMode conditionals on paymentHash
(invoice, pendingTopup, topup-conflict 409 response)
- routes/jobs.ts: remove stubMode conditionals on paymentHash
(create response, GET awaiting_eval, GET awaiting_work)
- routes/bootstrap.ts: remove stubMode conditionals on paymentHash
(POST create, GET poll response), simplify message field
## Operational evidence (from api-server startup log)
{"component":"lnbits","message":"LNbits real mode active",
"url":"http://143.198.27.163:5000","stub":false}
LNbits service on Hermes: active (running) since 2026-03-19 05:28:53 UTC
LNbits health: {"server_time":1773899225,"up_time":"00:18:11"}
Hermes logs: "internal payment successful" + "internal invoice settled"
## Testkit: PASS=29 FAIL=0 SKIP=0 (real LNbits mode, 2026-03-19 05:48)
2026-03-19 05:49:46 +00:00
|
|
|
} else {
|
|
|
|
|
logger.info("LNbits real mode active", { url: this.url, stub: false });
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
}
|
2026-03-18 14:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-18 19:32:34 +00:00
|
|
|
// ── Inbound invoices ─────────────────────────────────────────────────────
|
|
|
|
|
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
async createInvoice(amountSats: number, memo: string): Promise<LNbitsInvoice> {
|
|
|
|
|
if (this.stubMode) {
|
|
|
|
|
const paymentHash = randomBytes(32).toString("hex");
|
|
|
|
|
const paymentRequest = `lnbcrt${amountSats}u1stub_${paymentHash.slice(0, 16)}`;
|
2026-03-18 21:01:13 -04:00
|
|
|
logger.info("stub invoice created", { amountSats, memo, paymentHash });
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
return { paymentHash, paymentRequest };
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 19:32:34 +00:00
|
|
|
const response = await fetch(`${this.base()}/api/v1/payments`, {
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
method: "POST",
|
|
|
|
|
headers: this.headers(),
|
|
|
|
|
body: JSON.stringify({ out: false, amount: amountSats, memo }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const body = await response.text();
|
|
|
|
|
throw new Error(`LNbits createInvoice failed (${response.status}): ${body}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = (await response.json()) as {
|
|
|
|
|
payment_hash: string;
|
|
|
|
|
payment_request: string;
|
|
|
|
|
};
|
|
|
|
|
return { paymentHash: data.payment_hash, paymentRequest: data.payment_request };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async checkInvoicePaid(paymentHash: string): Promise<boolean> {
|
|
|
|
|
if (this.stubMode) {
|
|
|
|
|
return stubPaidInvoices.has(paymentHash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(
|
2026-03-18 19:32:34 +00:00
|
|
|
`${this.base()}/api/v1/payments/${paymentHash}`,
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
{ method: "GET", headers: this.headers() },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const body = await response.text();
|
|
|
|
|
throw new Error(`LNbits checkInvoice failed (${response.status}): ${body}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = (await response.json()) as { paid: boolean };
|
|
|
|
|
return data.paid;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 19:32:34 +00:00
|
|
|
// ── Outbound payments (refunds) ──────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Decode a BOLT11 invoice and return its amount in satoshis.
|
|
|
|
|
* Stub mode: parses the embedded amount from our known stub format (lnbcrtXu1stub_...),
|
|
|
|
|
* or returns null for externally-generated invoices.
|
|
|
|
|
*/
|
|
|
|
|
async decodeInvoice(bolt11: string): Promise<{ amountSats: number } | null> {
|
|
|
|
|
if (this.stubMode) {
|
|
|
|
|
// Our stub format: lnbcrtNNNu1stub_... where NNN is the amount in sats
|
|
|
|
|
const m = bolt11.match(/^lnbcrt(\d+)u1stub_/i);
|
|
|
|
|
if (m) return { amountSats: parseInt(m[1], 10) };
|
|
|
|
|
// Unknown format in stub mode — accept blindly (simulated environment)
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${this.base()}/api/v1/payments/decode`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: this.headers(),
|
|
|
|
|
body: JSON.stringify({ data: bolt11 }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const body = await response.text();
|
|
|
|
|
throw new Error(`LNbits decodeInvoice failed (${response.status}): ${body}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = (await response.json()) as { amount_msat?: number };
|
|
|
|
|
if (!data.amount_msat) return null;
|
|
|
|
|
return { amountSats: Math.floor(data.amount_msat / 1000) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Pay an outgoing BOLT11 invoice (e.g. to return a refund to a user).
|
|
|
|
|
* Returns the payment hash.
|
|
|
|
|
* Stub mode: simulates a successful payment.
|
|
|
|
|
*/
|
|
|
|
|
async payInvoice(bolt11: string): Promise<string> {
|
|
|
|
|
if (this.stubMode) {
|
|
|
|
|
const paymentHash = randomBytes(32).toString("hex");
|
2026-03-18 21:01:13 -04:00
|
|
|
logger.info("stub outgoing payment", { paymentHash, invoiceType: "outbound" });
|
2026-03-18 19:32:34 +00:00
|
|
|
return paymentHash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${this.base()}/api/v1/payments`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: this.headers(),
|
|
|
|
|
body: JSON.stringify({ out: true, bolt11 }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const body = await response.text();
|
|
|
|
|
throw new Error(`LNbits payInvoice failed (${response.status}): ${body}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = (await response.json()) as { payment_hash: string };
|
|
|
|
|
return data.payment_hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Stub helpers ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/** Stub-only: mark an inbound invoice as paid for testing/dev flows. */
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
stubMarkPaid(paymentHash: string): void {
|
|
|
|
|
if (!this.stubMode) {
|
|
|
|
|
throw new Error("stubMarkPaid called on a real LNbitsService instance");
|
|
|
|
|
}
|
|
|
|
|
stubPaidInvoices.add(paymentHash);
|
2026-03-18 21:01:13 -04:00
|
|
|
logger.info("stub invoice marked paid", { paymentHash, invoiceType: "inbound" });
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-18 19:32:34 +00:00
|
|
|
// ── Private helpers ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
private base(): string {
|
|
|
|
|
return this.url.replace(/\/$/, "");
|
|
|
|
|
}
|
|
|
|
|
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
private headers(): Record<string, string> {
|
|
|
|
|
return {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"X-Api-Key": this.apiKey,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-03-18 14:59:02 +00:00
|
|
|
}
|
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
|
|
|
|
|
|
|
|
export const lnbitsService = new LNbitsService();
|