@garydevenay/emporion 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +213 -5
  2. package/dist/src/cli.js +1195 -182
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/context-store.d.ts +22 -0
  5. package/dist/src/context-store.js +133 -0
  6. package/dist/src/context-store.js.map +1 -0
  7. package/dist/src/daemon.d.ts +2 -0
  8. package/dist/src/daemon.js.map +1 -1
  9. package/dist/src/errors.d.ts +8 -0
  10. package/dist/src/errors.js +8 -0
  11. package/dist/src/errors.js.map +1 -1
  12. package/dist/src/experience/deals-store.d.ts +37 -0
  13. package/dist/src/experience/deals-store.js +96 -0
  14. package/dist/src/experience/deals-store.js.map +1 -0
  15. package/dist/src/index.d.ts +5 -0
  16. package/dist/src/index.js +4 -0
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/wallet/config-store.d.ts +16 -0
  19. package/dist/src/wallet/config-store.js +180 -0
  20. package/dist/src/wallet/config-store.js.map +1 -0
  21. package/dist/src/wallet/index.d.ts +3 -0
  22. package/dist/src/wallet/index.js +4 -0
  23. package/dist/src/wallet/index.js.map +1 -0
  24. package/dist/src/wallet/ledger.d.ts +50 -0
  25. package/dist/src/wallet/ledger.js +340 -0
  26. package/dist/src/wallet/ledger.js.map +1 -0
  27. package/dist/src/wallet/nostr-nwc-adapter.d.ts +40 -0
  28. package/dist/src/wallet/nostr-nwc-adapter.js +506 -0
  29. package/dist/src/wallet/nostr-nwc-adapter.js.map +1 -0
  30. package/dist/src/wallet/nwc-adapter.d.ts +20 -0
  31. package/dist/src/wallet/nwc-adapter.js +233 -0
  32. package/dist/src/wallet/nwc-adapter.js.map +1 -0
  33. package/dist/src/wallet/service.d.ts +42 -0
  34. package/dist/src/wallet/service.js +390 -0
  35. package/dist/src/wallet/service.js.map +1 -0
  36. package/dist/src/wallet/types.d.ts +140 -0
  37. package/dist/src/wallet/types.js +2 -0
  38. package/dist/src/wallet/types.js.map +1 -0
  39. package/dist/test/experience.test.d.ts +1 -0
  40. package/dist/test/experience.test.js +232 -0
  41. package/dist/test/experience.test.js.map +1 -0
  42. package/dist/test/wallet.test.d.ts +1 -0
  43. package/dist/test/wallet.test.js +853 -0
  44. package/dist/test/wallet.test.js.map +1 -0
  45. package/package.json +2 -1
@@ -0,0 +1,233 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { InvoiceCreationError, PaymentFailedError, WalletAuthError, WalletUnavailableError } from "../errors.js";
3
+ const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
4
+ function parseInteger(value) {
5
+ if (typeof value === "number" && Number.isFinite(value)) {
6
+ return Math.trunc(value);
7
+ }
8
+ if (typeof value === "string" && /^\d+$/.test(value)) {
9
+ return Number.parseInt(value, 10);
10
+ }
11
+ return undefined;
12
+ }
13
+ function pickString(record, fields) {
14
+ for (const field of fields) {
15
+ const value = record[field];
16
+ if (typeof value === "string" && value.trim().length > 0) {
17
+ return value;
18
+ }
19
+ }
20
+ return undefined;
21
+ }
22
+ function toRecord(value) {
23
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
24
+ return {};
25
+ }
26
+ return value;
27
+ }
28
+ function normalizeInvoiceStatus(value) {
29
+ if (value === "paid" || value === "settled" || value === "complete") {
30
+ return "paid";
31
+ }
32
+ if (value === "expired") {
33
+ return "expired";
34
+ }
35
+ if (value === "canceled" || value === "cancelled") {
36
+ return "canceled";
37
+ }
38
+ return "created";
39
+ }
40
+ function normalizePaymentStatus(value) {
41
+ if (value === "succeeded" || value === "paid" || value === "settled" || value === "complete") {
42
+ return "succeeded";
43
+ }
44
+ if (value === "failed" || value === "error") {
45
+ return "failed";
46
+ }
47
+ return "pending";
48
+ }
49
+ function normalizeExpiresAt(value) {
50
+ if (typeof value === "string" && value.trim().length > 0) {
51
+ return value;
52
+ }
53
+ const seconds = parseInteger(value);
54
+ if (seconds !== undefined && seconds > 0) {
55
+ return new Date(seconds * 1_000).toISOString();
56
+ }
57
+ return undefined;
58
+ }
59
+ function toErrorMessage(error) {
60
+ return error instanceof Error ? error.message : String(error);
61
+ }
62
+ function parseNwcConnectionUri(connectionUri) {
63
+ if (!connectionUri.startsWith("nwc+http://") && !connectionUri.startsWith("nwc+https://")) {
64
+ throw new WalletUnavailableError("NWC connection URI must start with nwc+http:// or nwc+https://");
65
+ }
66
+ const normalized = connectionUri.replace(/^nwc\+/, "");
67
+ let endpoint;
68
+ try {
69
+ endpoint = new URL(normalized);
70
+ }
71
+ catch (error) {
72
+ throw new WalletUnavailableError(`Invalid NWC connection URI: ${toErrorMessage(error)}`);
73
+ }
74
+ if (endpoint.protocol !== "http:" && endpoint.protocol !== "https:") {
75
+ throw new WalletUnavailableError("NWC endpoint protocol must be http or https");
76
+ }
77
+ const token = endpoint.searchParams.get("token") ?? undefined;
78
+ const endpointDisplay = `${endpoint.origin}${endpoint.pathname}`;
79
+ return {
80
+ endpoint,
81
+ endpointDisplay,
82
+ ...(token ? { token } : {})
83
+ };
84
+ }
85
+ export function parseNwcConnectionMetadata(connectionUri) {
86
+ const parsed = parseNwcConnectionUri(connectionUri);
87
+ return {
88
+ endpoint: parsed.endpointDisplay
89
+ };
90
+ }
91
+ export class NwcWalletAdapter {
92
+ timeoutMs;
93
+ endpoint;
94
+ token;
95
+ constructor(connectionUri, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
96
+ this.timeoutMs = timeoutMs;
97
+ const parsed = parseNwcConnectionUri(connectionUri);
98
+ this.endpoint = parsed.endpoint;
99
+ if (parsed.token) {
100
+ this.token = parsed.token;
101
+ }
102
+ }
103
+ async createInvoice(input) {
104
+ const response = await this.callRpc("create_invoice", {
105
+ amount: input.amountSats,
106
+ memo: input.memo,
107
+ expires_at: input.expiresAt
108
+ }, "createInvoice");
109
+ const record = toRecord(response);
110
+ const bolt11 = pickString(record, ["bolt11", "invoice", "payment_request"]);
111
+ const externalRef = pickString(record, ["external_ref", "payment_hash", "id"]);
112
+ if (!bolt11 || !externalRef) {
113
+ throw new InvoiceCreationError("NWC response did not include invoice and external reference");
114
+ }
115
+ const expiresAt = normalizeExpiresAt(record.expires_at ?? record.expiresAt);
116
+ return {
117
+ bolt11,
118
+ externalRef,
119
+ status: normalizeInvoiceStatus(record.status),
120
+ ...(expiresAt ? { expiresAt } : {})
121
+ };
122
+ }
123
+ async payInvoice(input) {
124
+ const response = await this.callRpc("pay_invoice", {
125
+ invoice: input.invoice
126
+ }, "payInvoice");
127
+ const record = toRecord(response);
128
+ const externalRef = pickString(record, ["external_ref", "payment_hash", "id"]);
129
+ if (!externalRef) {
130
+ throw new PaymentFailedError("NWC response did not include payment external reference");
131
+ }
132
+ const status = normalizePaymentStatus(record.status ?? (record.preimage ? "succeeded" : "pending"));
133
+ const amountSats = parseInteger(record.amount_sats ?? record.amount) ?? 0;
134
+ const feeSats = parseInteger(record.fee_sats ?? record.fee_paid ?? record.fee) ?? 0;
135
+ const failureReason = pickString(record, ["failure_reason", "error", "message"]);
136
+ if (status === "failed") {
137
+ throw new PaymentFailedError(failureReason ?? "Payment failed via NWC");
138
+ }
139
+ return {
140
+ externalRef,
141
+ amountSats,
142
+ feeSats,
143
+ status,
144
+ ...(failureReason ? { failureReason } : {})
145
+ };
146
+ }
147
+ async getInvoiceStatus(externalRef) {
148
+ const response = await this.callRpc("get_invoice", {
149
+ external_ref: externalRef
150
+ }, "getInvoiceStatus");
151
+ return normalizeInvoiceStatus(toRecord(response).status);
152
+ }
153
+ async getPaymentStatus(externalRef) {
154
+ const response = await this.callRpc("get_payment", {
155
+ external_ref: externalRef
156
+ }, "getPaymentStatus");
157
+ const record = toRecord(response);
158
+ const status = normalizePaymentStatus(record.status);
159
+ const feeSats = parseInteger(record.fee_sats ?? record.fee_paid ?? record.fee);
160
+ const failureReason = pickString(record, ["failure_reason", "error", "message"]);
161
+ return {
162
+ status,
163
+ ...(feeSats !== undefined ? { feeSats } : {}),
164
+ ...(failureReason ? { failureReason } : {})
165
+ };
166
+ }
167
+ async disconnect() {
168
+ // HTTP-based adapter has no persistent session to close.
169
+ }
170
+ async callRpc(method, params, operation) {
171
+ const controller = new AbortController();
172
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
173
+ try {
174
+ const response = await fetch(this.endpoint, {
175
+ method: "POST",
176
+ headers: {
177
+ "content-type": "application/json",
178
+ ...(this.token ? { authorization: `Bearer ${this.token}` } : {})
179
+ },
180
+ body: JSON.stringify({
181
+ jsonrpc: "2.0",
182
+ id: randomUUID(),
183
+ method,
184
+ params
185
+ }),
186
+ signal: controller.signal
187
+ });
188
+ if (response.status === 401 || response.status === 403) {
189
+ throw new WalletAuthError(`Wallet authentication failed during ${operation}`);
190
+ }
191
+ if (!response.ok) {
192
+ throw new WalletUnavailableError(`Wallet provider returned HTTP ${response.status} during ${operation}`);
193
+ }
194
+ let payload;
195
+ try {
196
+ payload = (await response.json());
197
+ }
198
+ catch (error) {
199
+ throw new WalletUnavailableError(`Wallet provider returned invalid JSON during ${operation}`, {
200
+ cause: error
201
+ });
202
+ }
203
+ if (payload && typeof payload === "object" && "error" in payload && payload.error) {
204
+ const code = payload.error.code;
205
+ const message = payload.error.message ?? `Wallet provider error during ${operation}`;
206
+ if (code === 401 || code === 403) {
207
+ throw new WalletAuthError(message);
208
+ }
209
+ if (operation === "createInvoice") {
210
+ throw new InvoiceCreationError(message);
211
+ }
212
+ if (operation === "payInvoice") {
213
+ throw new PaymentFailedError(message);
214
+ }
215
+ throw new WalletUnavailableError(message);
216
+ }
217
+ return payload.result;
218
+ }
219
+ catch (error) {
220
+ if (error instanceof WalletAuthError || error instanceof WalletUnavailableError || error instanceof InvoiceCreationError || error instanceof PaymentFailedError) {
221
+ throw error;
222
+ }
223
+ if (error instanceof Error && error.name === "AbortError") {
224
+ throw new WalletUnavailableError(`Wallet provider timed out during ${operation}`);
225
+ }
226
+ throw new WalletUnavailableError(`Wallet provider request failed during ${operation}: ${toErrorMessage(error)}`);
227
+ }
228
+ finally {
229
+ clearTimeout(timeout);
230
+ }
231
+ }
232
+ }
233
+ //# sourceMappingURL=nwc-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nwc-adapter.js","sourceRoot":"","sources":["../../../src/wallet/nwc-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { type Logger } from "../logger.js";
2
+ import type { AutoSettleCandidate, AutoSettleResult, CreateInvoiceInput, CreateInvoiceResult, DaemonWalletStatus, PayInvoiceInput, PayInvoiceResult, PollUpdatesResult, WalletAdapter, WalletLedgerListFilters, WalletStatus } from "./types.js";
3
+ interface WalletServiceOptions {
4
+ dataDir: string;
5
+ env?: NodeJS.ProcessEnv;
6
+ logger?: Logger;
7
+ requireUnlockOnStart?: boolean;
8
+ now?: () => string;
9
+ adapterFactory?: (connectionUri: string) => WalletAdapter;
10
+ }
11
+ export declare class WalletService {
12
+ private readonly configStore;
13
+ private readonly env;
14
+ private readonly logger;
15
+ private readonly adapterFactory;
16
+ private readonly now;
17
+ private runtimeWalletKey;
18
+ private readonly ledger;
19
+ private adapterCache;
20
+ private constructor();
21
+ static create(options: WalletServiceOptions): Promise<WalletService>;
22
+ close(): Promise<void>;
23
+ connect(connectionUri: string): Promise<{
24
+ status: WalletStatus;
25
+ endpoint: string;
26
+ }>;
27
+ disconnect(): Promise<WalletStatus>;
28
+ rotateKey(newKeyMaterial: string): Promise<void>;
29
+ status(): Promise<WalletStatus>;
30
+ daemonStatus(): Promise<DaemonWalletStatus>;
31
+ createInvoice(input: CreateInvoiceInput): Promise<CreateInvoiceResult>;
32
+ payInvoice(input: PayInvoiceInput): Promise<PayInvoiceResult>;
33
+ listLedger(filters: WalletLedgerListFilters): Promise<unknown[]>;
34
+ pollUpdates(): Promise<PollUpdatesResult>;
35
+ attemptAutoSettle(candidate: AutoSettleCandidate): Promise<AutoSettleResult>;
36
+ setRuntimeKey(keyMaterial: string | null): void;
37
+ private assertUnlockIfConfigured;
38
+ private canUnlockConfig;
39
+ private getUnlockedAdapter;
40
+ private getWalletKey;
41
+ }
42
+ export {};
@@ -0,0 +1,390 @@
1
+ import { createLogger } from "../logger.js";
2
+ import { InvoiceCreationError, PaymentFailedError, WalletAuthError, WalletUnavailableError } from "../errors.js";
3
+ import { WalletConfigStore, getWalletKeyFromEnv } from "./config-store.js";
4
+ import { WalletLedger } from "./ledger.js";
5
+ import { NwcWalletAdapter, parseNwcConnectionMetadata } from "./nwc-adapter.js";
6
+ import { NostrWalletConnectAdapter, isNostrWalletConnectUri, parseNostrWalletConnectMetadata } from "./nostr-nwc-adapter.js";
7
+ function normalizeUnknownError(error) {
8
+ return error instanceof Error ? error : new Error(String(error));
9
+ }
10
+ function assertPositiveInteger(value, fieldName) {
11
+ if (!Number.isInteger(value) || value <= 0) {
12
+ throw new Error(`${fieldName} must be a positive integer`);
13
+ }
14
+ }
15
+ function lightningReferenceDedupeKey(ref) {
16
+ return `${ref.type}:${ref.network}:${ref.reference}`;
17
+ }
18
+ function parseWalletConnectionMetadata(connectionUri) {
19
+ if (isNostrWalletConnectUri(connectionUri)) {
20
+ return parseNostrWalletConnectMetadata(connectionUri);
21
+ }
22
+ return parseNwcConnectionMetadata(connectionUri);
23
+ }
24
+ function createWalletAdapterForConnection(connectionUri) {
25
+ if (isNostrWalletConnectUri(connectionUri)) {
26
+ return new NostrWalletConnectAdapter(connectionUri);
27
+ }
28
+ return new NwcWalletAdapter(connectionUri);
29
+ }
30
+ export class WalletService {
31
+ configStore;
32
+ env;
33
+ logger;
34
+ adapterFactory;
35
+ now;
36
+ runtimeWalletKey = null;
37
+ ledger;
38
+ adapterCache = null;
39
+ constructor(options, ledger) {
40
+ this.configStore = new WalletConfigStore(options.dataDir);
41
+ this.ledger = ledger;
42
+ this.env = options.env ?? process.env;
43
+ this.logger = options.logger ?? createLogger("error");
44
+ this.adapterFactory = options.adapterFactory ?? createWalletAdapterForConnection;
45
+ this.now = options.now ?? (() => new Date().toISOString());
46
+ }
47
+ static async create(options) {
48
+ const ledger = await WalletLedger.create(options.dataDir);
49
+ const service = new WalletService(options, ledger);
50
+ if (options.requireUnlockOnStart) {
51
+ await service.assertUnlockIfConfigured();
52
+ }
53
+ return service;
54
+ }
55
+ async close() {
56
+ if (this.adapterCache) {
57
+ await this.adapterCache.adapter.disconnect();
58
+ this.adapterCache = null;
59
+ }
60
+ }
61
+ async connect(connectionUri) {
62
+ const keyMaterial = this.getWalletKey();
63
+ const metadata = parseWalletConnectionMetadata(connectionUri);
64
+ await this.configStore.writeConnection({
65
+ backend: "nwc",
66
+ network: "bitcoin",
67
+ connectionUri,
68
+ endpoint: metadata.endpoint,
69
+ connectedAt: this.now()
70
+ }, keyMaterial);
71
+ if (this.adapterCache) {
72
+ await this.adapterCache.adapter.disconnect();
73
+ this.adapterCache = null;
74
+ }
75
+ return {
76
+ status: await this.status(),
77
+ endpoint: metadata.endpoint
78
+ };
79
+ }
80
+ async disconnect() {
81
+ if (this.adapterCache) {
82
+ await this.adapterCache.adapter.disconnect();
83
+ this.adapterCache = null;
84
+ }
85
+ await this.configStore.clearConnection();
86
+ return this.status();
87
+ }
88
+ async rotateKey(newKeyMaterial) {
89
+ const currentKey = this.getWalletKey();
90
+ if (newKeyMaterial.trim().length === 0) {
91
+ throw new WalletAuthError("New wallet key must not be blank");
92
+ }
93
+ await this.configStore.rotateKey(currentKey, newKeyMaterial);
94
+ }
95
+ async status() {
96
+ const metadata = await this.configStore.readMetadata();
97
+ const { pendingInvoices, pendingPayments } = this.ledger.getPendingCounts();
98
+ if (!metadata) {
99
+ return {
100
+ connected: false,
101
+ backend: "nwc",
102
+ network: "bitcoin",
103
+ autoSettleEnabled: false,
104
+ pendingInvoices,
105
+ pendingPayments,
106
+ locked: false
107
+ };
108
+ }
109
+ const locked = !(await this.canUnlockConfig());
110
+ return {
111
+ connected: true,
112
+ backend: "nwc",
113
+ network: "bitcoin",
114
+ autoSettleEnabled: !locked,
115
+ pendingInvoices,
116
+ pendingPayments,
117
+ locked
118
+ };
119
+ }
120
+ async daemonStatus() {
121
+ const status = await this.status();
122
+ return {
123
+ connected: status.connected,
124
+ backend: status.backend,
125
+ network: status.network,
126
+ autoSettleEnabled: status.autoSettleEnabled,
127
+ pendingInvoices: status.pendingInvoices,
128
+ pendingPayments: status.pendingPayments
129
+ };
130
+ }
131
+ async createInvoice(input) {
132
+ assertPositiveInteger(input.amountSats, "--amount-sats");
133
+ const adapter = await this.getUnlockedAdapter();
134
+ let adapterResult;
135
+ try {
136
+ adapterResult = await adapter.createInvoice(input);
137
+ }
138
+ catch (error) {
139
+ if (error instanceof InvoiceCreationError || error instanceof WalletAuthError || error instanceof WalletUnavailableError) {
140
+ throw error;
141
+ }
142
+ throw new InvoiceCreationError(normalizeUnknownError(error).message, { cause: error });
143
+ }
144
+ const invoice = await this.ledger.addInvoice({
145
+ amount: input.amountSats,
146
+ network: "bitcoin",
147
+ externalRef: adapterResult.externalRef,
148
+ bolt11: adapterResult.bolt11,
149
+ status: adapterResult.status,
150
+ now: this.now(),
151
+ ...(input.memo ? { memo: input.memo } : {}),
152
+ ...(adapterResult.expiresAt ? { expiresAt: adapterResult.expiresAt } : {})
153
+ });
154
+ return {
155
+ invoice,
156
+ bolt11: adapterResult.bolt11
157
+ };
158
+ }
159
+ async payInvoice(input) {
160
+ const adapter = await this.getUnlockedAdapter();
161
+ let adapterResult;
162
+ try {
163
+ adapterResult = await adapter.payInvoice(input);
164
+ }
165
+ catch (error) {
166
+ if (error instanceof PaymentFailedError || error instanceof WalletAuthError || error instanceof WalletUnavailableError) {
167
+ throw error;
168
+ }
169
+ throw new PaymentFailedError(normalizeUnknownError(error).message, { cause: error });
170
+ }
171
+ const now = this.now();
172
+ const payment = await this.ledger.addPayment({
173
+ sourceRef: input.sourceRef ?? input.invoice,
174
+ amount: adapterResult.amountSats,
175
+ fee: adapterResult.feeSats,
176
+ externalRef: adapterResult.externalRef,
177
+ status: adapterResult.status,
178
+ now,
179
+ ...(adapterResult.failureReason ? { failureReason: adapterResult.failureReason } : {})
180
+ });
181
+ return {
182
+ payment
183
+ };
184
+ }
185
+ async listLedger(filters) {
186
+ return this.ledger.list(filters);
187
+ }
188
+ async pollUpdates() {
189
+ const currentStatus = await this.status();
190
+ if (!currentStatus.connected || currentStatus.locked) {
191
+ return { updatedInvoices: 0, updatedPayments: 0 };
192
+ }
193
+ const adapter = await this.getUnlockedAdapter();
194
+ let updatedInvoices = 0;
195
+ let updatedPayments = 0;
196
+ for (const invoice of this.ledger.listPendingInvoices()) {
197
+ try {
198
+ const nextStatus = await adapter.getInvoiceStatus(invoice.externalRef);
199
+ if (nextStatus !== invoice.status) {
200
+ await this.ledger.transitionInvoice(invoice.id, nextStatus, this.now());
201
+ updatedInvoices += 1;
202
+ }
203
+ }
204
+ catch (error) {
205
+ this.logger.warn("Wallet poll invoice update failed", {
206
+ invoiceId: invoice.id,
207
+ error: normalizeUnknownError(error).message
208
+ });
209
+ }
210
+ }
211
+ for (const payment of this.ledger.listPendingPayments()) {
212
+ try {
213
+ const next = await adapter.getPaymentStatus(payment.externalRef);
214
+ if (next.status !== payment.status || next.feeSats !== undefined || next.failureReason !== undefined) {
215
+ const transitionOptions = {
216
+ ...(next.feeSats !== undefined ? { fee: next.feeSats } : {}),
217
+ ...(next.failureReason ? { failureReason: next.failureReason } : {})
218
+ };
219
+ await this.ledger.transitionPayment(payment.id, next.status, this.now(), {
220
+ ...transitionOptions
221
+ });
222
+ updatedPayments += 1;
223
+ }
224
+ }
225
+ catch (error) {
226
+ this.logger.warn("Wallet poll payment update failed", {
227
+ paymentId: payment.id,
228
+ error: normalizeUnknownError(error).message
229
+ });
230
+ }
231
+ }
232
+ return {
233
+ updatedInvoices,
234
+ updatedPayments
235
+ };
236
+ }
237
+ async attemptAutoSettle(candidate) {
238
+ const status = await this.status();
239
+ if (!status.connected || status.locked) {
240
+ return {
241
+ executed: false,
242
+ deduped: false,
243
+ reason: "wallet-unavailable"
244
+ };
245
+ }
246
+ const lightningReference = lightningReferenceDedupeKey(candidate.lightningRef);
247
+ if (this.ledger.hasAutoSettle(candidate.eventId, lightningReference)) {
248
+ return {
249
+ executed: false,
250
+ deduped: true,
251
+ reason: "already-settled"
252
+ };
253
+ }
254
+ if (candidate.lightningRef.network !== "bitcoin") {
255
+ await this.ledger.addAutoSettle({
256
+ triggerObjectKind: candidate.triggerObjectKind,
257
+ triggerObjectId: candidate.triggerObjectId,
258
+ eventId: candidate.eventId,
259
+ lightningReference,
260
+ action: "pay-bolt11",
261
+ result: "skipped",
262
+ detail: "unsupported-network",
263
+ createdAt: this.now()
264
+ });
265
+ return {
266
+ executed: true,
267
+ deduped: false,
268
+ state: "skipped",
269
+ reason: "unsupported-network"
270
+ };
271
+ }
272
+ if (candidate.lightningRef.type !== "bolt11") {
273
+ await this.ledger.addAutoSettle({
274
+ triggerObjectKind: candidate.triggerObjectKind,
275
+ triggerObjectId: candidate.triggerObjectId,
276
+ eventId: candidate.eventId,
277
+ lightningReference,
278
+ action: "pay-bolt11",
279
+ result: "skipped",
280
+ detail: "unsupported-reference-type",
281
+ createdAt: this.now()
282
+ });
283
+ return {
284
+ executed: true,
285
+ deduped: false,
286
+ state: "skipped",
287
+ reason: "unsupported-reference-type"
288
+ };
289
+ }
290
+ try {
291
+ const result = await this.payInvoice({
292
+ invoice: candidate.lightningRef.reference,
293
+ sourceRef: `${candidate.triggerObjectKind}:${candidate.triggerObjectId}:${candidate.eventId}`
294
+ });
295
+ await this.ledger.addAutoSettle({
296
+ triggerObjectKind: candidate.triggerObjectKind,
297
+ triggerObjectId: candidate.triggerObjectId,
298
+ eventId: candidate.eventId,
299
+ lightningReference,
300
+ action: "pay-bolt11",
301
+ result: "succeeded",
302
+ paymentId: result.payment.id,
303
+ createdAt: this.now()
304
+ });
305
+ return {
306
+ executed: true,
307
+ deduped: false,
308
+ state: "succeeded",
309
+ paymentId: result.payment.id
310
+ };
311
+ }
312
+ catch (error) {
313
+ const message = normalizeUnknownError(error).message;
314
+ await this.ledger.addAutoSettle({
315
+ triggerObjectKind: candidate.triggerObjectKind,
316
+ triggerObjectId: candidate.triggerObjectId,
317
+ eventId: candidate.eventId,
318
+ lightningReference,
319
+ action: "pay-bolt11",
320
+ result: "failed",
321
+ detail: message,
322
+ createdAt: this.now()
323
+ });
324
+ return {
325
+ executed: true,
326
+ deduped: false,
327
+ state: "failed",
328
+ reason: message
329
+ };
330
+ }
331
+ }
332
+ setRuntimeKey(keyMaterial) {
333
+ if (keyMaterial === null) {
334
+ this.runtimeWalletKey = null;
335
+ return;
336
+ }
337
+ const trimmed = keyMaterial.trim();
338
+ this.runtimeWalletKey = trimmed.length > 0 ? trimmed : null;
339
+ }
340
+ async assertUnlockIfConfigured() {
341
+ const configured = await this.configStore.hasEncryptedConfig();
342
+ if (!configured) {
343
+ return;
344
+ }
345
+ const key = this.getWalletKey();
346
+ const config = await this.configStore.readConnection(key);
347
+ if (!config) {
348
+ throw new WalletUnavailableError("Wallet config is incomplete");
349
+ }
350
+ }
351
+ async canUnlockConfig() {
352
+ try {
353
+ const key = this.getWalletKey();
354
+ const config = await this.configStore.readConnection(key);
355
+ return !!config;
356
+ }
357
+ catch {
358
+ return false;
359
+ }
360
+ }
361
+ async getUnlockedAdapter() {
362
+ const key = this.getWalletKey();
363
+ const config = await this.configStore.readConnection(key);
364
+ if (!config) {
365
+ throw new WalletUnavailableError("No wallet connection is configured");
366
+ }
367
+ if (config.backend !== "nwc") {
368
+ throw new WalletUnavailableError(`Unsupported wallet backend: ${config.backend}`);
369
+ }
370
+ if (this.adapterCache && this.adapterCache.connectionUri === config.connectionUri) {
371
+ return this.adapterCache.adapter;
372
+ }
373
+ if (this.adapterCache) {
374
+ await this.adapterCache.adapter.disconnect();
375
+ }
376
+ const adapter = this.adapterFactory(config.connectionUri);
377
+ this.adapterCache = {
378
+ connectionUri: config.connectionUri,
379
+ adapter
380
+ };
381
+ return adapter;
382
+ }
383
+ getWalletKey() {
384
+ if (this.runtimeWalletKey) {
385
+ return this.runtimeWalletKey;
386
+ }
387
+ return getWalletKeyFromEnv(this.env);
388
+ }
389
+ }
390
+ //# sourceMappingURL=service.js.map