@breeztech/breez-sdk-spark 0.15.1 → 0.17.0
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.
- package/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +604 -237
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +729 -434
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +63 -49
- package/bundler/storage/index.js +356 -34
- package/deno/breez_sdk_spark_wasm.d.ts +604 -237
- package/deno/breez_sdk_spark_wasm.js +729 -434
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +63 -49
- package/nodejs/breez_sdk_spark_wasm.d.ts +604 -237
- package/nodejs/breez_sdk_spark_wasm.js +741 -441
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +63 -49
- package/nodejs/index.js +10 -10
- package/nodejs/index.mjs +13 -8
- package/nodejs/mysql-session-store/errors.cjs +13 -0
- package/nodejs/{mysql-session-manager → mysql-session-store}/index.cjs +24 -21
- package/nodejs/{mysql-session-manager → mysql-session-store}/migrations.cjs +17 -11
- package/nodejs/mysql-session-store/package.json +9 -0
- package/nodejs/mysql-storage/index.cjs +358 -125
- package/nodejs/mysql-storage/migrations.cjs +67 -2
- package/nodejs/mysql-token-store/index.cjs +99 -79
- package/nodejs/mysql-token-store/migrations.cjs +59 -2
- package/nodejs/mysql-tree-store/index.cjs +15 -9
- package/nodejs/mysql-tree-store/migrations.cjs +16 -2
- package/nodejs/package.json +2 -2
- package/nodejs/postgres-session-store/errors.cjs +13 -0
- package/nodejs/{postgres-session-manager → postgres-session-store}/index.cjs +23 -23
- package/nodejs/{postgres-session-manager → postgres-session-store}/migrations.cjs +14 -14
- package/nodejs/postgres-session-store/package.json +9 -0
- package/nodejs/postgres-storage/index.cjs +296 -119
- package/nodejs/postgres-storage/migrations.cjs +51 -0
- package/nodejs/postgres-token-store/index.cjs +89 -64
- package/nodejs/postgres-token-store/migrations.cjs +44 -0
- package/nodejs/storage/index.cjs +285 -125
- package/nodejs/storage/migrations.cjs +47 -0
- package/package.json +6 -1
- package/ssr/index.js +57 -28
- package/web/breez_sdk_spark_wasm.d.ts +667 -286
- package/web/breez_sdk_spark_wasm.js +729 -434
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +63 -49
- package/web/passkey-prf-provider/index.d.ts +203 -0
- package/web/passkey-prf-provider/index.js +733 -0
- package/web/storage/index.js +356 -34
- package/nodejs/mysql-session-manager/errors.cjs +0 -13
- package/nodejs/mysql-session-manager/package.json +0 -9
- package/nodejs/postgres-session-manager/errors.cjs +0 -13
- package/nodejs/postgres-session-manager/package.json +0 -9
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* CommonJS implementation for Node.js PostgreSQL Storage
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
5
7
|
let pg;
|
|
6
8
|
try {
|
|
7
9
|
const mainModule = require.main;
|
|
@@ -38,7 +40,8 @@ const SELECT_PAYMENT_SQL = `
|
|
|
38
40
|
p.timestamp,
|
|
39
41
|
p.method,
|
|
40
42
|
p.withdraw_tx_id,
|
|
41
|
-
|
|
43
|
+
pd.tx_id AS deposit_tx_id,
|
|
44
|
+
pd.vout AS deposit_vout,
|
|
42
45
|
p.spark,
|
|
43
46
|
l.invoice AS lightning_invoice,
|
|
44
47
|
l.payment_hash AS lightning_payment_hash,
|
|
@@ -66,6 +69,7 @@ const SELECT_PAYMENT_SQL = `
|
|
|
66
69
|
LEFT JOIN brz_payment_details_lightning l ON p.id = l.payment_id AND p.user_id = l.user_id
|
|
67
70
|
LEFT JOIN brz_payment_details_token t ON p.id = t.payment_id AND p.user_id = t.user_id
|
|
68
71
|
LEFT JOIN brz_payment_details_spark s ON p.id = s.payment_id AND p.user_id = s.user_id
|
|
72
|
+
LEFT JOIN brz_payment_details_deposit pd ON p.id = pd.payment_id AND p.user_id = pd.user_id
|
|
69
73
|
LEFT JOIN brz_payment_metadata pm ON p.id = pm.payment_id AND p.user_id = pm.user_id
|
|
70
74
|
LEFT JOIN brz_lnurl_receive_metadata lrm ON l.payment_hash = lrm.payment_hash AND l.user_id = lrm.user_id`;
|
|
71
75
|
|
|
@@ -235,6 +239,15 @@ class PostgresStorage {
|
|
|
235
239
|
for (const paymentDetailsFilter of request.paymentDetailsFilter) {
|
|
236
240
|
const paymentDetailsClauses = [];
|
|
237
241
|
|
|
242
|
+
// Base type check: ensure payment type matches the filter type
|
|
243
|
+
if (paymentDetailsFilter.type === "spark") {
|
|
244
|
+
paymentDetailsClauses.push("p.spark = true");
|
|
245
|
+
} else if (paymentDetailsFilter.type === "token") {
|
|
246
|
+
paymentDetailsClauses.push("p.spark IS NULL AND t.tx_hash IS NOT NULL");
|
|
247
|
+
} else if (paymentDetailsFilter.type === "lightning") {
|
|
248
|
+
paymentDetailsClauses.push("l.htlc_status IS NOT NULL");
|
|
249
|
+
}
|
|
250
|
+
|
|
238
251
|
// Filter by HTLC status (Spark or Lightning)
|
|
239
252
|
const htlcAlias =
|
|
240
253
|
paymentDetailsFilter.type === "spark"
|
|
@@ -262,24 +275,35 @@ class PostgresStorage {
|
|
|
262
275
|
params.push(...paymentDetailsFilter.htlcStatus);
|
|
263
276
|
}
|
|
264
277
|
|
|
265
|
-
// Filter by conversion
|
|
278
|
+
// Filter by conversion type + status
|
|
266
279
|
if (
|
|
267
280
|
(paymentDetailsFilter.type === "spark" ||
|
|
268
|
-
paymentDetailsFilter.type === "token"
|
|
269
|
-
|
|
281
|
+
paymentDetailsFilter.type === "token" ||
|
|
282
|
+
paymentDetailsFilter.type === "lightning") &&
|
|
283
|
+
paymentDetailsFilter.conversionFilter !== undefined
|
|
270
284
|
) {
|
|
285
|
+
// Lightning carries the Boltz conversion; its `conversion_info.type`
|
|
286
|
+
// is the authoritative discriminator, so no payment-type column check.
|
|
271
287
|
const typeCheck =
|
|
272
288
|
paymentDetailsFilter.type === "spark"
|
|
273
289
|
? "p.spark = true"
|
|
274
|
-
:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
290
|
+
: paymentDetailsFilter.type === "token"
|
|
291
|
+
? "p.spark IS NULL"
|
|
292
|
+
: null;
|
|
293
|
+
let statusClause;
|
|
294
|
+
if (paymentDetailsFilter.conversionFilter === "ammRefundNeeded") {
|
|
295
|
+
statusClause = "pm.conversion_info::jsonb->>'type' = 'amm' AND pm.conversion_info::jsonb->>'status' = 'refundNeeded'";
|
|
296
|
+
} else if (paymentDetailsFilter.conversionFilter === "orchestraPending") {
|
|
297
|
+
statusClause = "pm.conversion_info::jsonb->>'type' = 'orchestra' AND pm.conversion_info::jsonb->>'status' NOT IN ('completed', 'failed', 'refunded')";
|
|
298
|
+
} else if (paymentDetailsFilter.conversionFilter === "boltzPending") {
|
|
299
|
+
statusClause = "pm.conversion_info::jsonb->>'type' = 'boltz' AND pm.conversion_info::jsonb->>'status' NOT IN ('completed', 'failed', 'refunded')";
|
|
300
|
+
}
|
|
301
|
+
if (statusClause) {
|
|
302
|
+
const prefix = typeCheck ? `${typeCheck} AND ` : "";
|
|
303
|
+
paymentDetailsClauses.push(
|
|
304
|
+
`${prefix}pm.conversion_info IS NOT NULL AND ${statusClause}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
283
307
|
}
|
|
284
308
|
|
|
285
309
|
// Filter by token transaction hash
|
|
@@ -353,130 +377,192 @@ class PostgresStorage {
|
|
|
353
377
|
}
|
|
354
378
|
}
|
|
355
379
|
|
|
356
|
-
async
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
await this._withTransaction(async (client) => {
|
|
363
|
-
const withdrawTxId =
|
|
364
|
-
payment.details?.type === "withdraw" ? payment.details.txId : null;
|
|
365
|
-
const depositTxId =
|
|
366
|
-
payment.details?.type === "deposit" ? payment.details.txId : null;
|
|
367
|
-
const spark = payment.details?.type === "spark" ? true : null;
|
|
380
|
+
async applyPaymentUpdate(payment) {
|
|
381
|
+
if (!payment) {
|
|
382
|
+
throw new StorageError("Payment cannot be null or undefined");
|
|
383
|
+
}
|
|
368
384
|
|
|
385
|
+
try {
|
|
386
|
+
return await this._withTransaction(async (client) => {
|
|
387
|
+
const lockKey = this._paymentUpdateLockKey(payment.id);
|
|
369
388
|
await client.query(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
ON CONFLICT(user_id, id) DO UPDATE SET
|
|
373
|
-
payment_type=EXCLUDED.payment_type,
|
|
374
|
-
status=EXCLUDED.status,
|
|
375
|
-
amount=EXCLUDED.amount,
|
|
376
|
-
fees=EXCLUDED.fees,
|
|
377
|
-
timestamp=EXCLUDED.timestamp,
|
|
378
|
-
method=EXCLUDED.method,
|
|
379
|
-
withdraw_tx_id=EXCLUDED.withdraw_tx_id,
|
|
380
|
-
deposit_tx_id=EXCLUDED.deposit_tx_id,
|
|
381
|
-
spark=EXCLUDED.spark`,
|
|
382
|
-
[
|
|
383
|
-
this.identity,
|
|
384
|
-
payment.id,
|
|
385
|
-
payment.paymentType,
|
|
386
|
-
payment.status,
|
|
387
|
-
payment.amount.toString(),
|
|
388
|
-
payment.fees.toString(),
|
|
389
|
-
payment.timestamp,
|
|
390
|
-
payment.method ? JSON.stringify(payment.method) : null,
|
|
391
|
-
withdrawTxId,
|
|
392
|
-
depositTxId,
|
|
393
|
-
spark,
|
|
394
|
-
]
|
|
389
|
+
"SELECT pg_advisory_xact_lock($1::bigint)",
|
|
390
|
+
[lockKey]
|
|
395
391
|
);
|
|
396
392
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
payment.details.invoiceDetails
|
|
412
|
-
? JSON.stringify(payment.details.invoiceDetails)
|
|
413
|
-
: null,
|
|
414
|
-
payment.details.htlcDetails
|
|
415
|
-
? JSON.stringify(payment.details.htlcDetails)
|
|
416
|
-
: null,
|
|
417
|
-
]
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (payment.details?.type === "lightning") {
|
|
422
|
-
await client.query(
|
|
423
|
-
`INSERT INTO brz_payment_details_lightning
|
|
424
|
-
(user_id, payment_id, invoice, payment_hash, destination_pubkey, description, preimage, htlc_status, htlc_expiry_time)
|
|
425
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
426
|
-
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
427
|
-
invoice=EXCLUDED.invoice,
|
|
428
|
-
payment_hash=EXCLUDED.payment_hash,
|
|
429
|
-
destination_pubkey=EXCLUDED.destination_pubkey,
|
|
430
|
-
description=EXCLUDED.description,
|
|
431
|
-
preimage=COALESCE(EXCLUDED.preimage, brz_payment_details_lightning.preimage),
|
|
432
|
-
htlc_status=COALESCE(EXCLUDED.htlc_status, brz_payment_details_lightning.htlc_status),
|
|
433
|
-
htlc_expiry_time=COALESCE(EXCLUDED.htlc_expiry_time, brz_payment_details_lightning.htlc_expiry_time)`,
|
|
434
|
-
[
|
|
435
|
-
this.identity,
|
|
436
|
-
payment.id,
|
|
437
|
-
payment.details.invoice,
|
|
438
|
-
payment.details.htlcDetails.paymentHash,
|
|
439
|
-
payment.details.destinationPubkey,
|
|
440
|
-
payment.details.description,
|
|
441
|
-
payment.details.htlcDetails?.preimage,
|
|
442
|
-
payment.details.htlcDetails?.status ?? null,
|
|
443
|
-
payment.details.htlcDetails?.expiryTime ?? 0,
|
|
444
|
-
]
|
|
393
|
+
const { rows } = await client.query(
|
|
394
|
+
"SELECT status FROM brz_payments WHERE user_id = $1 AND id = $2 FOR UPDATE",
|
|
395
|
+
[this.identity, payment.id]
|
|
396
|
+
);
|
|
397
|
+
const stored = rows.length > 0
|
|
398
|
+
? this._normalizePaymentStatus(rows[0].status)
|
|
399
|
+
: null;
|
|
400
|
+
const next = this._normalizePaymentStatus(payment.status);
|
|
401
|
+
|
|
402
|
+
if (stored != null
|
|
403
|
+
&& this._isFinalPaymentStatus(stored)
|
|
404
|
+
&& stored !== next) {
|
|
405
|
+
console.warn(
|
|
406
|
+
`Skipping payment update (would replace terminal status): id=${payment.id} stored=${stored} new=${next}`
|
|
445
407
|
);
|
|
408
|
+
return false;
|
|
446
409
|
}
|
|
447
410
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
VALUES ($1, $2, $3, $4, $5, $6)
|
|
453
|
-
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
454
|
-
metadata=EXCLUDED.metadata,
|
|
455
|
-
tx_hash=EXCLUDED.tx_hash,
|
|
456
|
-
tx_type=EXCLUDED.tx_type,
|
|
457
|
-
invoice_details=COALESCE(EXCLUDED.invoice_details, brz_payment_details_token.invoice_details)`,
|
|
458
|
-
[
|
|
459
|
-
this.identity,
|
|
460
|
-
payment.id,
|
|
461
|
-
JSON.stringify(payment.details.metadata),
|
|
462
|
-
payment.details.txHash,
|
|
463
|
-
payment.details.txType,
|
|
464
|
-
payment.details.invoiceDetails
|
|
465
|
-
? JSON.stringify(payment.details.invoiceDetails)
|
|
466
|
-
: null,
|
|
467
|
-
]
|
|
411
|
+
const sameStatus = stored === next;
|
|
412
|
+
if (sameStatus) {
|
|
413
|
+
console.debug(
|
|
414
|
+
`Skipping redundant payment event: id=${payment.id} status=${next}`
|
|
468
415
|
);
|
|
469
416
|
}
|
|
417
|
+
await this._runPaymentUpsert(client, payment);
|
|
418
|
+
return !sameStatus;
|
|
470
419
|
});
|
|
471
420
|
} catch (error) {
|
|
472
421
|
if (error instanceof StorageError) throw error;
|
|
473
422
|
throw new StorageError(
|
|
474
|
-
`Failed to
|
|
423
|
+
`Failed to apply payment update '${payment.id}': ${error.message}`,
|
|
475
424
|
error
|
|
476
425
|
);
|
|
477
426
|
}
|
|
478
427
|
}
|
|
479
428
|
|
|
429
|
+
_paymentUpdateLockKey(paymentId) {
|
|
430
|
+
return crypto
|
|
431
|
+
.createHash("sha256")
|
|
432
|
+
.update("brz_payment_update")
|
|
433
|
+
.update(this.identity)
|
|
434
|
+
.update(Buffer.from(paymentId))
|
|
435
|
+
.digest()
|
|
436
|
+
.readBigInt64BE(0)
|
|
437
|
+
.toString();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async _runPaymentUpsert(client, payment) {
|
|
441
|
+
const withdrawTxId =
|
|
442
|
+
payment.details?.type === "withdraw" ? payment.details.txId : null;
|
|
443
|
+
const spark = payment.details?.type === "spark" ? true : null;
|
|
444
|
+
|
|
445
|
+
await client.query(
|
|
446
|
+
`INSERT INTO brz_payments (user_id, id, payment_type, status, amount, fees, timestamp, method, withdraw_tx_id, spark)
|
|
447
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
448
|
+
ON CONFLICT(user_id, id) DO UPDATE SET
|
|
449
|
+
payment_type=EXCLUDED.payment_type,
|
|
450
|
+
status=EXCLUDED.status,
|
|
451
|
+
amount=EXCLUDED.amount,
|
|
452
|
+
fees=EXCLUDED.fees,
|
|
453
|
+
timestamp=EXCLUDED.timestamp,
|
|
454
|
+
method=EXCLUDED.method,
|
|
455
|
+
withdraw_tx_id=EXCLUDED.withdraw_tx_id,
|
|
456
|
+
spark=EXCLUDED.spark`,
|
|
457
|
+
[
|
|
458
|
+
this.identity,
|
|
459
|
+
payment.id,
|
|
460
|
+
payment.paymentType,
|
|
461
|
+
payment.status,
|
|
462
|
+
payment.amount.toString(),
|
|
463
|
+
payment.fees.toString(),
|
|
464
|
+
payment.timestamp,
|
|
465
|
+
payment.method ? JSON.stringify(payment.method) : null,
|
|
466
|
+
withdrawTxId,
|
|
467
|
+
spark,
|
|
468
|
+
]
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
if (payment.details?.type === "deposit") {
|
|
472
|
+
await client.query(
|
|
473
|
+
`INSERT INTO brz_payment_details_deposit (user_id, payment_id, tx_id, vout)
|
|
474
|
+
VALUES ($1, $2, $3, $4)
|
|
475
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
476
|
+
tx_id=EXCLUDED.tx_id,
|
|
477
|
+
vout=EXCLUDED.vout`,
|
|
478
|
+
[this.identity, payment.id, payment.details.txId, payment.details.vout]
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (
|
|
483
|
+
payment.details?.type === "spark" &&
|
|
484
|
+
(payment.details.invoiceDetails != null ||
|
|
485
|
+
payment.details.htlcDetails != null)
|
|
486
|
+
) {
|
|
487
|
+
await client.query(
|
|
488
|
+
`INSERT INTO brz_payment_details_spark (user_id, payment_id, invoice_details, htlc_details)
|
|
489
|
+
VALUES ($1, $2, $3, $4)
|
|
490
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
491
|
+
invoice_details=COALESCE(EXCLUDED.invoice_details, brz_payment_details_spark.invoice_details),
|
|
492
|
+
htlc_details=COALESCE(EXCLUDED.htlc_details, brz_payment_details_spark.htlc_details)`,
|
|
493
|
+
[
|
|
494
|
+
this.identity,
|
|
495
|
+
payment.id,
|
|
496
|
+
payment.details.invoiceDetails
|
|
497
|
+
? JSON.stringify(payment.details.invoiceDetails)
|
|
498
|
+
: null,
|
|
499
|
+
payment.details.htlcDetails
|
|
500
|
+
? JSON.stringify(payment.details.htlcDetails)
|
|
501
|
+
: null,
|
|
502
|
+
]
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (payment.details?.type === "lightning") {
|
|
507
|
+
await client.query(
|
|
508
|
+
`INSERT INTO brz_payment_details_lightning
|
|
509
|
+
(user_id, payment_id, invoice, payment_hash, destination_pubkey, description, preimage, htlc_status, htlc_expiry_time)
|
|
510
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
511
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
512
|
+
invoice=EXCLUDED.invoice,
|
|
513
|
+
payment_hash=EXCLUDED.payment_hash,
|
|
514
|
+
destination_pubkey=EXCLUDED.destination_pubkey,
|
|
515
|
+
description=EXCLUDED.description,
|
|
516
|
+
preimage=COALESCE(EXCLUDED.preimage, brz_payment_details_lightning.preimage),
|
|
517
|
+
htlc_status=COALESCE(EXCLUDED.htlc_status, brz_payment_details_lightning.htlc_status),
|
|
518
|
+
htlc_expiry_time=COALESCE(EXCLUDED.htlc_expiry_time, brz_payment_details_lightning.htlc_expiry_time)`,
|
|
519
|
+
[
|
|
520
|
+
this.identity,
|
|
521
|
+
payment.id,
|
|
522
|
+
payment.details.invoice,
|
|
523
|
+
payment.details.htlcDetails.paymentHash,
|
|
524
|
+
payment.details.destinationPubkey,
|
|
525
|
+
payment.details.description,
|
|
526
|
+
payment.details.htlcDetails?.preimage,
|
|
527
|
+
payment.details.htlcDetails?.status ?? null,
|
|
528
|
+
payment.details.htlcDetails?.expiryTime ?? 0,
|
|
529
|
+
]
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (payment.details?.type === "token") {
|
|
534
|
+
await client.query(
|
|
535
|
+
`INSERT INTO brz_payment_details_token
|
|
536
|
+
(user_id, payment_id, metadata, tx_hash, tx_type, invoice_details)
|
|
537
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
538
|
+
ON CONFLICT(user_id, payment_id) DO UPDATE SET
|
|
539
|
+
metadata=EXCLUDED.metadata,
|
|
540
|
+
tx_hash=EXCLUDED.tx_hash,
|
|
541
|
+
tx_type=EXCLUDED.tx_type,
|
|
542
|
+
invoice_details=COALESCE(EXCLUDED.invoice_details, brz_payment_details_token.invoice_details)`,
|
|
543
|
+
[
|
|
544
|
+
this.identity,
|
|
545
|
+
payment.id,
|
|
546
|
+
JSON.stringify(payment.details.metadata),
|
|
547
|
+
payment.details.txHash,
|
|
548
|
+
payment.details.txType,
|
|
549
|
+
payment.details.invoiceDetails
|
|
550
|
+
? JSON.stringify(payment.details.invoiceDetails)
|
|
551
|
+
: null,
|
|
552
|
+
]
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
_normalizePaymentStatus(status) {
|
|
558
|
+
return typeof status === "string" ? status.toLowerCase() : status;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
_isFinalPaymentStatus(status) {
|
|
562
|
+
const normalized = this._normalizePaymentStatus(status);
|
|
563
|
+
return normalized === "completed" || normalized === "failed";
|
|
564
|
+
}
|
|
565
|
+
|
|
480
566
|
async getPaymentById(id) {
|
|
481
567
|
try {
|
|
482
568
|
if (!id) {
|
|
@@ -769,6 +855,13 @@ class PostgresStorage {
|
|
|
769
855
|
senderComment: row.lnurl_sender_comment || null,
|
|
770
856
|
};
|
|
771
857
|
}
|
|
858
|
+
|
|
859
|
+
if (row.conversion_info) {
|
|
860
|
+
details.conversionInfo =
|
|
861
|
+
typeof row.conversion_info === "string"
|
|
862
|
+
? JSON.parse(row.conversion_info)
|
|
863
|
+
: row.conversion_info;
|
|
864
|
+
}
|
|
772
865
|
} else if (row.withdraw_tx_id) {
|
|
773
866
|
details = {
|
|
774
867
|
type: "withdraw",
|
|
@@ -778,6 +871,7 @@ class PostgresStorage {
|
|
|
778
871
|
details = {
|
|
779
872
|
type: "deposit",
|
|
780
873
|
txId: row.deposit_tx_id,
|
|
874
|
+
vout: Number(row.deposit_vout),
|
|
781
875
|
};
|
|
782
876
|
} else if (row.spark) {
|
|
783
877
|
details = {
|
|
@@ -950,6 +1044,74 @@ class PostgresStorage {
|
|
|
950
1044
|
}
|
|
951
1045
|
}
|
|
952
1046
|
|
|
1047
|
+
// ===== Cross-Chain Swap Operations =====
|
|
1048
|
+
|
|
1049
|
+
async setCrossChainSwap(swap) {
|
|
1050
|
+
try {
|
|
1051
|
+
await this.pool.query(
|
|
1052
|
+
`INSERT INTO brz_cross_chain_swaps
|
|
1053
|
+
(user_id, provider, id, is_terminal, updated_at, data, secrets)
|
|
1054
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
1055
|
+
ON CONFLICT(user_id, provider, id) DO UPDATE SET
|
|
1056
|
+
is_terminal = EXCLUDED.is_terminal,
|
|
1057
|
+
updated_at = EXCLUDED.updated_at,
|
|
1058
|
+
data = EXCLUDED.data,
|
|
1059
|
+
secrets = EXCLUDED.secrets`,
|
|
1060
|
+
[
|
|
1061
|
+
this.identity,
|
|
1062
|
+
swap.provider,
|
|
1063
|
+
swap.id,
|
|
1064
|
+
swap.isTerminal,
|
|
1065
|
+
swap.updatedAt,
|
|
1066
|
+
swap.data,
|
|
1067
|
+
swap.secrets,
|
|
1068
|
+
]
|
|
1069
|
+
);
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
throw new StorageError(
|
|
1072
|
+
`Failed to set cross-chain swap: ${error.message}`,
|
|
1073
|
+
error
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
async getCrossChainSwap(provider, id) {
|
|
1079
|
+
try {
|
|
1080
|
+
const result = await this.pool.query(
|
|
1081
|
+
`SELECT provider, id, is_terminal, updated_at, data, secrets
|
|
1082
|
+
FROM brz_cross_chain_swaps
|
|
1083
|
+
WHERE user_id = $1 AND provider = $2 AND id = $3`,
|
|
1084
|
+
[this.identity, provider, id]
|
|
1085
|
+
);
|
|
1086
|
+
if (result.rows.length === 0) {
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
return crossChainSwapFromRow(result.rows[0]);
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
throw new StorageError(
|
|
1092
|
+
`Failed to get cross-chain swap: ${error.message}`,
|
|
1093
|
+
error
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
async listActiveCrossChainSwaps(provider) {
|
|
1099
|
+
try {
|
|
1100
|
+
const result = await this.pool.query(
|
|
1101
|
+
`SELECT provider, id, is_terminal, updated_at, data, secrets
|
|
1102
|
+
FROM brz_cross_chain_swaps
|
|
1103
|
+
WHERE user_id = $1 AND provider = $2 AND is_terminal = FALSE`,
|
|
1104
|
+
[this.identity, provider]
|
|
1105
|
+
);
|
|
1106
|
+
return result.rows.map(crossChainSwapFromRow);
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
throw new StorageError(
|
|
1109
|
+
`Failed to list active cross-chain swaps: ${error.message}`,
|
|
1110
|
+
error
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
953
1115
|
// ===== Sync Operations =====
|
|
954
1116
|
|
|
955
1117
|
async syncAddOutgoingChange(record) {
|
|
@@ -1376,6 +1538,21 @@ class PostgresStorage {
|
|
|
1376
1538
|
}
|
|
1377
1539
|
}
|
|
1378
1540
|
|
|
1541
|
+
/**
|
|
1542
|
+
* Maps a brz_cross_chain_swaps row to the camelCase StoredCrossChainSwap shape
|
|
1543
|
+
* the SDK expects.
|
|
1544
|
+
*/
|
|
1545
|
+
function crossChainSwapFromRow(row) {
|
|
1546
|
+
return {
|
|
1547
|
+
provider: row.provider,
|
|
1548
|
+
id: row.id,
|
|
1549
|
+
isTerminal: row.is_terminal,
|
|
1550
|
+
updatedAt: Number(row.updated_at),
|
|
1551
|
+
data: row.data,
|
|
1552
|
+
secrets: row.secrets,
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1379
1556
|
/**
|
|
1380
1557
|
* Creates a PostgresStorageConfig with the given connection string and default pool settings.
|
|
1381
1558
|
*
|
|
@@ -467,6 +467,57 @@ class PostgresMigrationManager {
|
|
|
467
467
|
ON brz_sync_incoming(user_id, revision)`,
|
|
468
468
|
],
|
|
469
469
|
},
|
|
470
|
+
{
|
|
471
|
+
// Move deposit details into their own table so vout can be NOT NULL and
|
|
472
|
+
// the schema matches brz_payment_details_lightning / _token / _spark. We
|
|
473
|
+
// can't safely backfill the new table from the dropped deposit_tx_id
|
|
474
|
+
// column: we never stored the original SSP output_index, and vout=0 is a
|
|
475
|
+
// valid output index, so defaulting would silently mislabel. Drop the
|
|
476
|
+
// column and leave the brz_payments row in place. The read path sees an
|
|
477
|
+
// unjoined deposit row as `details: None` until the resync re-fetches the
|
|
478
|
+
// SSP user_request and the upsert inserts the new details row.
|
|
479
|
+
name: "Move deposit details into brz_payment_details_deposit table",
|
|
480
|
+
sql: [
|
|
481
|
+
`CREATE TABLE IF NOT EXISTS brz_payment_details_deposit (
|
|
482
|
+
user_id BYTEA NOT NULL,
|
|
483
|
+
payment_id TEXT NOT NULL,
|
|
484
|
+
tx_id TEXT NOT NULL,
|
|
485
|
+
vout BIGINT NOT NULL,
|
|
486
|
+
PRIMARY KEY (user_id, payment_id)
|
|
487
|
+
)`,
|
|
488
|
+
`ALTER TABLE brz_payments DROP COLUMN IF EXISTS deposit_tx_id`,
|
|
489
|
+
`UPDATE brz_settings
|
|
490
|
+
SET value = jsonb_set(value::jsonb, '{offset}', '0')::text
|
|
491
|
+
WHERE key = 'sync_offset' AND value IS NOT NULL`,
|
|
492
|
+
],
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: "Backfill conversion_info type discriminator",
|
|
496
|
+
sql: [
|
|
497
|
+
`UPDATE brz_payment_metadata SET conversion_info = conversion_info::jsonb || '{"type": "amm"}'::jsonb WHERE conversion_info IS NOT NULL AND conversion_info::jsonb->>'type' IS NULL`,
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
// Cross-chain swap rows, synced for cross-instance recovery. Shared
|
|
502
|
+
// across providers, discriminated by `provider`. Born multi-tenant
|
|
503
|
+
// (user_id in the PK). `data` is provider-opaque JSON; `secrets` is
|
|
504
|
+
// provider-opaque ciphertext (empty when the provider has none).
|
|
505
|
+
name: "Create brz_cross_chain_swaps table",
|
|
506
|
+
sql: [
|
|
507
|
+
`CREATE TABLE IF NOT EXISTS brz_cross_chain_swaps (
|
|
508
|
+
user_id BYTEA NOT NULL,
|
|
509
|
+
provider TEXT NOT NULL,
|
|
510
|
+
id TEXT NOT NULL,
|
|
511
|
+
is_terminal BOOLEAN NOT NULL,
|
|
512
|
+
updated_at BIGINT NOT NULL,
|
|
513
|
+
data TEXT NOT NULL,
|
|
514
|
+
secrets TEXT NOT NULL,
|
|
515
|
+
PRIMARY KEY (user_id, provider, id)
|
|
516
|
+
)`,
|
|
517
|
+
`CREATE INDEX brz_idx_cross_chain_swaps_user_provider_is_terminal
|
|
518
|
+
ON brz_cross_chain_swaps(user_id, provider, is_terminal)`,
|
|
519
|
+
],
|
|
520
|
+
},
|
|
470
521
|
];
|
|
471
522
|
}
|
|
472
523
|
}
|