@breeztech/breez-sdk-spark 0.15.0 → 0.16.1-dev1

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 (51) hide show
  1. package/breez-sdk-spark.tgz +0 -0
  2. package/bundler/breez_sdk_spark_wasm.d.ts +511 -215
  3. package/bundler/breez_sdk_spark_wasm.js +1 -1
  4. package/bundler/breez_sdk_spark_wasm_bg.js +567 -414
  5. package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
  6. package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  7. package/bundler/storage/index.js +205 -15
  8. package/deno/breez_sdk_spark_wasm.d.ts +511 -215
  9. package/deno/breez_sdk_spark_wasm.js +567 -414
  10. package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
  11. package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  12. package/nodejs/breez_sdk_spark_wasm.d.ts +511 -215
  13. package/nodejs/breez_sdk_spark_wasm.js +578 -421
  14. package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
  15. package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  16. package/nodejs/index.js +10 -10
  17. package/nodejs/index.mjs +12 -8
  18. package/nodejs/mysql-session-store/errors.cjs +13 -0
  19. package/nodejs/{mysql-session-manager → mysql-session-store}/index.cjs +24 -21
  20. package/nodejs/{mysql-session-manager → mysql-session-store}/migrations.cjs +17 -11
  21. package/nodejs/mysql-session-store/package.json +9 -0
  22. package/nodejs/mysql-storage/index.cjs +229 -111
  23. package/nodejs/mysql-storage/migrations.cjs +37 -2
  24. package/nodejs/mysql-token-store/index.cjs +99 -79
  25. package/nodejs/mysql-token-store/migrations.cjs +59 -2
  26. package/nodejs/mysql-tree-store/index.cjs +15 -9
  27. package/nodejs/mysql-tree-store/migrations.cjs +16 -2
  28. package/nodejs/package.json +2 -2
  29. package/nodejs/postgres-session-store/errors.cjs +13 -0
  30. package/nodejs/{postgres-session-manager → postgres-session-store}/index.cjs +23 -23
  31. package/nodejs/{postgres-session-manager → postgres-session-store}/migrations.cjs +14 -14
  32. package/nodejs/postgres-session-store/package.json +9 -0
  33. package/nodejs/postgres-storage/index.cjs +174 -107
  34. package/nodejs/postgres-storage/migrations.cjs +24 -0
  35. package/nodejs/postgres-token-store/index.cjs +89 -64
  36. package/nodejs/postgres-token-store/migrations.cjs +44 -0
  37. package/nodejs/storage/index.cjs +167 -113
  38. package/nodejs/storage/migrations.cjs +23 -0
  39. package/package.json +6 -1
  40. package/ssr/index.js +52 -28
  41. package/web/breez_sdk_spark_wasm.d.ts +566 -261
  42. package/web/breez_sdk_spark_wasm.js +567 -414
  43. package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
  44. package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  45. package/web/passkey-prf-provider/index.d.ts +203 -0
  46. package/web/passkey-prf-provider/index.js +733 -0
  47. package/web/storage/index.js +205 -15
  48. package/nodejs/mysql-session-manager/errors.cjs +0 -13
  49. package/nodejs/mysql-session-manager/package.json +0 -9
  50. package/nodejs/postgres-session-manager/errors.cjs +0 -13
  51. package/nodejs/postgres-session-manager/package.json +0 -9
@@ -201,12 +201,14 @@ class PostgresTokenStore {
201
201
  [this.identity, cleanupCutoff]
202
202
  );
203
203
 
204
- // Get recent spent output IDs (spent_at >= refresh_timestamp)
204
+ // Get recent spent outpoints (spent_at >= refresh_timestamp)
205
205
  const spentResult = await client.query(
206
- "SELECT output_id FROM brz_token_spent_outputs WHERE user_id = $1 AND spent_at >= $2",
206
+ "SELECT prev_tx_hash, prev_tx_vout FROM brz_token_spent_outputs WHERE user_id = $1 AND spent_at >= $2",
207
207
  [this.identity, refreshTimestamp]
208
208
  );
209
- const spentIds = new Set(spentResult.rows.map((r) => r.output_id));
209
+ const spentOutpoints = new Set(
210
+ spentResult.rows.map((r) => `${r.prev_tx_hash}:${r.prev_tx_vout}`)
211
+ );
210
212
 
211
213
  // Delete non-reserved outputs added BEFORE the refresh started
212
214
  await client.query(
@@ -214,17 +216,17 @@ class PostgresTokenStore {
214
216
  [this.identity, refreshTimestamp]
215
217
  );
216
218
 
217
- // Build a set of all incoming output IDs for reconciliation
218
- const incomingOutputIds = new Set();
219
+ // Build a set of all incoming outpoints for reconciliation
220
+ const incomingOutpoints = new Set();
219
221
  for (const to of tokenOutputs) {
220
222
  for (const o of to.outputs) {
221
- incomingOutputIds.add(o.output.id);
223
+ incomingOutpoints.add(`${o.prevTxHash}:${o.prevTxVout}`);
222
224
  }
223
225
  }
224
226
 
225
227
  // Reconcile reservations: find reserved outputs that no longer exist
226
228
  const reservedRows = await client.query(
227
- `SELECT r.id, o.id AS output_id
229
+ `SELECT r.id, o.prev_tx_hash, o.prev_tx_vout
228
230
  FROM brz_token_reservations r
229
231
  JOIN brz_token_outputs o
230
232
  ON o.reservation_id = r.id AND o.user_id = r.user_id
@@ -232,28 +234,30 @@ class PostgresTokenStore {
232
234
  [this.identity]
233
235
  );
234
236
 
235
- // Group reserved outputs by reservation ID
237
+ // Group reserved outpoints by reservation ID
236
238
  const reservationOutputs = new Map();
237
239
  for (const row of reservedRows.rows) {
238
240
  if (!reservationOutputs.has(row.id)) {
239
241
  reservationOutputs.set(row.id, []);
240
242
  }
241
- reservationOutputs.get(row.id).push(row.output_id);
243
+ reservationOutputs.get(row.id).push([row.prev_tx_hash, row.prev_tx_vout]);
242
244
  }
243
245
 
244
246
  // Find reservations that have no valid outputs after reconciliation
245
247
  const reservationsToDelete = [];
246
- const outputsToRemoveFromReservation = [];
247
- for (const [reservationId, outputIds] of reservationOutputs) {
248
- const validIds = outputIds.filter((id) => incomingOutputIds.has(id));
249
- if (validIds.length === 0) {
250
- reservationsToDelete.push(reservationId);
251
- } else {
252
- for (const id of outputIds) {
253
- if (!incomingOutputIds.has(id)) {
254
- outputsToRemoveFromReservation.push(id);
248
+ const outpointsToRemoveFromReservation = [];
249
+ for (const [reservationId, outpoints] of reservationOutputs) {
250
+ const hasValid = outpoints.some(([h, v]) =>
251
+ incomingOutpoints.has(`${h}:${v}`)
252
+ );
253
+ if (hasValid) {
254
+ for (const [h, v] of outpoints) {
255
+ if (!incomingOutpoints.has(`${h}:${v}`)) {
256
+ outpointsToRemoveFromReservation.push([h, v]);
255
257
  }
256
258
  }
259
+ } else {
260
+ reservationsToDelete.push(reservationId);
257
261
  }
258
262
  }
259
263
 
@@ -270,10 +274,16 @@ class PostgresTokenStore {
270
274
  }
271
275
 
272
276
  // Delete individual reserved outputs that no longer exist
273
- if (outputsToRemoveFromReservation.length > 0) {
277
+ if (outpointsToRemoveFromReservation.length > 0) {
278
+ const txHashes = outpointsToRemoveFromReservation.map(([h]) => h);
279
+ const vouts = outpointsToRemoveFromReservation.map(([, v]) => v);
274
280
  await client.query(
275
- "DELETE FROM brz_token_outputs WHERE user_id = $1 AND id = ANY($2)",
276
- [this.identity, outputsToRemoveFromReservation]
281
+ `DELETE FROM brz_token_outputs
282
+ WHERE user_id = $1
283
+ AND (prev_tx_hash, prev_tx_vout) IN (
284
+ SELECT * FROM UNNEST($2::text[], $3::int[])
285
+ )`,
286
+ [this.identity, txHashes, vouts]
277
287
  );
278
288
 
279
289
  // Check if any reservations are now empty
@@ -281,7 +291,7 @@ class PostgresTokenStore {
281
291
  `SELECT r.id FROM brz_token_reservations r
282
292
  LEFT JOIN brz_token_outputs o
283
293
  ON o.reservation_id = r.id AND o.user_id = r.user_id
284
- WHERE r.user_id = $1 AND o.id IS NULL`,
294
+ WHERE r.user_id = $1 AND o.prev_tx_hash IS NULL`,
285
295
  [this.identity]
286
296
  );
287
297
  const emptyIds = emptyReservations.rows.map((r) => r.id);
@@ -293,13 +303,15 @@ class PostgresTokenStore {
293
303
  }
294
304
  }
295
305
 
296
- // Collect IDs of currently reserved outputs (that survived reconciliation)
297
- const reservedOutputIdsResult = await client.query(
298
- "SELECT id FROM brz_token_outputs WHERE user_id = $1 AND reservation_id IS NOT NULL",
306
+ // Collect outpoints of currently reserved outputs (that survived reconciliation)
307
+ const reservedOutpointsResult = await client.query(
308
+ "SELECT prev_tx_hash, prev_tx_vout FROM brz_token_outputs WHERE user_id = $1 AND reservation_id IS NOT NULL",
299
309
  [this.identity]
300
310
  );
301
- const reservedOutputIds = new Set(
302
- reservedOutputIdsResult.rows.map((r) => r.id)
311
+ const reservedOutpoints = new Set(
312
+ reservedOutpointsResult.rows.map(
313
+ (r) => `${r.prev_tx_hash}:${r.prev_tx_vout}`
314
+ )
303
315
  );
304
316
 
305
317
  // Delete orphan metadata (per-tenant)
@@ -318,7 +330,8 @@ class PostgresTokenStore {
318
330
  await this._upsertMetadata(client, to.metadata);
319
331
 
320
332
  for (const output of to.outputs) {
321
- if (reservedOutputIds.has(output.output.id) || spentIds.has(output.output.id)) {
333
+ const outpoint = `${output.prevTxHash}:${output.prevTxVout}`;
334
+ if (reservedOutpoints.has(outpoint) || spentOutpoints.has(outpoint)) {
322
335
  continue;
323
336
  }
324
337
  await this._insertSingleOutput(
@@ -399,7 +412,7 @@ class PostgresTokenStore {
399
412
  const result = await this.pool.query(
400
413
  `SELECT m.identifier, m.issuer_public_key, m.name, m.ticker, m.decimals,
401
414
  m.max_supply, m.is_freezable, m.creation_entity_public_key,
402
- o.id AS output_id, o.owner_public_key, o.revocation_commitment,
415
+ o.owner_public_key, o.revocation_commitment,
403
416
  o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
404
417
  o.token_public_key, o.token_amount, o.token_identifier,
405
418
  o.prev_tx_hash, o.prev_tx_vout, o.reservation_id,
@@ -428,7 +441,7 @@ class PostgresTokenStore {
428
441
 
429
442
  const entry = map.get(row.identifier);
430
443
 
431
- if (!row.output_id) {
444
+ if (!row.prev_tx_hash) {
432
445
  continue;
433
446
  }
434
447
 
@@ -476,7 +489,7 @@ class PostgresTokenStore {
476
489
  const result = await this.pool.query(
477
490
  `SELECT m.identifier, m.issuer_public_key, m.name, m.ticker, m.decimals,
478
491
  m.max_supply, m.is_freezable, m.creation_entity_public_key,
479
- o.id AS output_id, o.owner_public_key, o.revocation_commitment,
492
+ o.owner_public_key, o.revocation_commitment,
480
493
  o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
481
494
  o.token_public_key, o.token_amount, o.token_identifier,
482
495
  o.prev_tx_hash, o.prev_tx_vout, o.reservation_id,
@@ -504,7 +517,7 @@ class PostgresTokenStore {
504
517
  };
505
518
 
506
519
  for (const row of result.rows) {
507
- if (!row.output_id) {
520
+ if (!row.prev_tx_hash) {
508
521
  continue;
509
522
  }
510
523
 
@@ -541,19 +554,20 @@ class PostgresTokenStore {
541
554
  */
542
555
  async updateTokenOutputs(outputsToRemove, outputsToAdd) {
543
556
  try {
544
- await this._withTransaction(async (client) => {
557
+ // Serialize against the other token-store mutators (refresh, reservation,
558
+ // finalization), which take the same per-user advisory lock.
559
+ await this._withWriteTransaction(async (client) => {
545
560
  // 1. Remove spent outputs and mark as spent.
546
561
  if (outputsToRemove && outputsToRemove.length > 0) {
547
562
  for (const [txHash, vout] of outputsToRemove) {
548
563
  const result = await client.query(
549
- "DELETE FROM brz_token_outputs WHERE user_id = $1 AND prev_tx_hash = $2 AND prev_tx_vout = $3 RETURNING id",
564
+ "DELETE FROM brz_token_outputs WHERE user_id = $1 AND prev_tx_hash = $2 AND prev_tx_vout = $3",
550
565
  [this.identity, txHash, vout]
551
566
  );
552
- if (result.rows.length > 0) {
553
- const outputId = result.rows[0].id;
567
+ if (result.rowCount > 0) {
554
568
  await client.query(
555
- "INSERT INTO brz_token_spent_outputs (user_id, output_id, spent_at) VALUES ($1, $2, NOW()) ON CONFLICT DO NOTHING",
556
- [this.identity, outputId]
569
+ "INSERT INTO brz_token_spent_outputs (user_id, prev_tx_hash, prev_tx_vout, spent_at) VALUES ($1, $2, $3, NOW()) ON CONFLICT DO NOTHING",
570
+ [this.identity, txHash, vout]
557
571
  );
558
572
  }
559
573
  }
@@ -563,11 +577,16 @@ class PostgresTokenStore {
563
577
  if (outputsToAdd) {
564
578
  await this._upsertMetadata(client, outputsToAdd.metadata);
565
579
 
566
- const outputIds = outputsToAdd.outputs.map((o) => o.output.id);
567
- if (outputIds.length > 0) {
580
+ if (outputsToAdd.outputs.length > 0) {
581
+ const txHashes = outputsToAdd.outputs.map((o) => o.prevTxHash);
582
+ const vouts = outputsToAdd.outputs.map((o) => o.prevTxVout);
568
583
  await client.query(
569
- "DELETE FROM brz_token_spent_outputs WHERE user_id = $1 AND output_id = ANY($2)",
570
- [this.identity, outputIds]
584
+ `DELETE FROM brz_token_spent_outputs
585
+ WHERE user_id = $1
586
+ AND (prev_tx_hash, prev_tx_vout) IN (
587
+ SELECT * FROM UNNEST($2::text[], $3::int[])
588
+ )`,
589
+ [this.identity, txHashes, vouts]
571
590
  );
572
591
  }
573
592
 
@@ -635,7 +654,7 @@ class PostgresTokenStore {
635
654
 
636
655
  // Get available (non-reserved) outputs
637
656
  const outputRows = await client.query(
638
- `SELECT o.id AS output_id, o.owner_public_key, o.revocation_commitment,
657
+ `SELECT o.owner_public_key, o.revocation_commitment,
639
658
  o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
640
659
  o.token_public_key, o.token_amount, o.token_identifier,
641
660
  o.prev_tx_hash, o.prev_tx_vout
@@ -650,10 +669,12 @@ class PostgresTokenStore {
650
669
 
651
670
  // Filter by preferred if provided
652
671
  if (preferredOutputs && preferredOutputs.length > 0) {
653
- const preferredIds = new Set(
654
- preferredOutputs.map((p) => p.output.id)
672
+ const preferredOutpoints = new Set(
673
+ preferredOutputs.map((p) => `${p.prevTxHash}:${p.prevTxVout}`)
674
+ );
675
+ outputs = outputs.filter((o) =>
676
+ preferredOutpoints.has(`${o.prevTxHash}:${o.prevTxVout}`)
655
677
  );
656
- outputs = outputs.filter((o) => preferredIds.has(o.output.id));
657
678
  }
658
679
 
659
680
  // Select outputs based on target
@@ -733,12 +754,17 @@ class PostgresTokenStore {
733
754
  [this.identity, reservationId, purpose]
734
755
  );
735
756
 
736
- // Set reservation_id on selected outputs
737
- const selectedIds = selectedOutputs.map((o) => o.output.id);
738
- if (selectedIds.length > 0) {
757
+ // Set reservation_id on selected outputs (by outpoint)
758
+ if (selectedOutputs.length > 0) {
759
+ const selectedTxHashes = selectedOutputs.map((o) => o.prevTxHash);
760
+ const selectedVouts = selectedOutputs.map((o) => o.prevTxVout);
739
761
  await client.query(
740
- "UPDATE brz_token_outputs SET reservation_id = $1 WHERE user_id = $3 AND id = ANY($2)",
741
- [reservationId, selectedIds, this.identity]
762
+ `UPDATE brz_token_outputs SET reservation_id = $1
763
+ WHERE user_id = $4
764
+ AND (prev_tx_hash, prev_tx_vout) IN (
765
+ SELECT * FROM UNNEST($2::text[], $3::int[])
766
+ )`,
767
+ [reservationId, selectedTxHashes, selectedVouts, this.identity]
742
768
  );
743
769
  }
744
770
 
@@ -810,19 +836,20 @@ class PostgresTokenStore {
810
836
  }
811
837
  const isSwap = reservationResult.rows[0].purpose === "Swap";
812
838
 
813
- // Get reserved output IDs and mark them as spent
839
+ // Get reserved outpoints and mark them as spent
814
840
  const reservedOutputsResult = await client.query(
815
- "SELECT id FROM brz_token_outputs WHERE user_id = $1 AND reservation_id = $2",
841
+ "SELECT prev_tx_hash, prev_tx_vout FROM brz_token_outputs WHERE user_id = $1 AND reservation_id = $2",
816
842
  [this.identity, id]
817
843
  );
818
- const reservedOutputIds = reservedOutputsResult.rows.map((r) => r.id);
819
844
 
820
- if (reservedOutputIds.length > 0) {
845
+ if (reservedOutputsResult.rows.length > 0) {
846
+ const txHashes = reservedOutputsResult.rows.map((r) => r.prev_tx_hash);
847
+ const vouts = reservedOutputsResult.rows.map((r) => r.prev_tx_vout);
821
848
  await client.query(
822
- `INSERT INTO brz_token_spent_outputs (user_id, output_id)
823
- SELECT $2, output_id FROM UNNEST($1::text[]) AS t(output_id)
849
+ `INSERT INTO brz_token_spent_outputs (user_id, prev_tx_hash, prev_tx_vout)
850
+ SELECT $3, h, v FROM UNNEST($1::text[], $2::int[]) AS t(h, v)
824
851
  ON CONFLICT DO NOTHING`,
825
- [reservedOutputIds, this.identity]
852
+ [txHashes, vouts, this.identity]
826
853
  );
827
854
  }
828
855
 
@@ -965,13 +992,13 @@ class PostgresTokenStore {
965
992
  async _insertSingleOutput(client, tokenIdentifier, output) {
966
993
  await client.query(
967
994
  `INSERT INTO brz_token_outputs
968
- (user_id, id, token_identifier, owner_public_key, revocation_commitment,
995
+ (user_id, token_identifier, owner_public_key, revocation_commitment,
969
996
  withdraw_bond_sats, withdraw_relative_block_locktime,
970
997
  token_public_key, token_amount, prev_tx_hash, prev_tx_vout, added_at)
971
- VALUES ($11, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())
972
- ON CONFLICT (user_id, id) DO NOTHING`,
998
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())
999
+ ON CONFLICT (user_id, prev_tx_hash, prev_tx_vout) DO NOTHING`,
973
1000
  [
974
- output.output.id,
1001
+ this.identity,
975
1002
  tokenIdentifier,
976
1003
  output.output.ownerPublicKey,
977
1004
  output.output.revocationCommitment,
@@ -981,7 +1008,6 @@ class PostgresTokenStore {
981
1008
  output.output.tokenAmount,
982
1009
  output.prevTxHash,
983
1010
  output.prevTxVout,
984
- this.identity,
985
1011
  ]
986
1012
  );
987
1013
  }
@@ -1008,7 +1034,6 @@ class PostgresTokenStore {
1008
1034
  _outputFromRow(row) {
1009
1035
  return {
1010
1036
  output: {
1011
- id: row.output_id,
1012
1037
  ownerPublicKey: row.owner_public_key,
1013
1038
  revocationCommitment: row.revocation_commitment,
1014
1039
  withdrawBondSats: Number(row.withdraw_bond_sats),
@@ -337,6 +337,50 @@ class TokenStoreMigrationManager {
337
337
  ADD PRIMARY KEY (user_id)`,
338
338
  ],
339
339
  },
340
+ {
341
+ // Mirrors Rust migration 3 in spark-postgres/src/token_store.rs.
342
+ // Re-keys brz_token_spent_outputs by (prev_tx_hash, prev_tx_vout) instead
343
+ // of the operator-issued output id. v3 FinalTokenOutput carries no id
344
+ // field, so post-broadcast spent markers only have an outpoint to work
345
+ // with. Existing output_id-keyed rows can't be backfilled (no outpoint
346
+ // stored alongside them), so the table is wiped on upgrade — spent
347
+ // markers are short-lived (5 minute cleanup window) so wiping is
348
+ // equivalent to letting them age out.
349
+ name: "Re-key spent outputs by (prev_tx_hash, prev_tx_vout)",
350
+ sql: [
351
+ `DROP TABLE IF EXISTS brz_token_spent_outputs`,
352
+ `CREATE TABLE brz_token_spent_outputs (
353
+ user_id BYTEA NOT NULL,
354
+ prev_tx_hash TEXT NOT NULL,
355
+ prev_tx_vout INTEGER NOT NULL,
356
+ spent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
357
+ PRIMARY KEY (user_id, prev_tx_hash, prev_tx_vout)
358
+ )`,
359
+ ],
360
+ },
361
+ {
362
+ // Re-key brz_token_outputs by (prev_tx_hash, prev_tx_vout) and drop the
363
+ // legacy id column. id already held "{prev_tx_hash}:{vout}", so the
364
+ // outpoint is the natural key. Dedup any duplicate-outpoint rows
365
+ // (possible from pre-outpoint code) before adding the composite PK,
366
+ // preferring rows that hold a reservation.
367
+ name: "Re-key token outputs by (prev_tx_hash, prev_tx_vout), drop legacy id",
368
+ sql: [
369
+ `DELETE FROM brz_token_outputs WHERE ctid IN (
370
+ SELECT ctid FROM (
371
+ SELECT ctid, ROW_NUMBER() OVER (
372
+ PARTITION BY user_id, prev_tx_hash, prev_tx_vout
373
+ ORDER BY (reservation_id IS NULL) ASC, id ASC
374
+ ) AS rn
375
+ FROM brz_token_outputs
376
+ ) t WHERE t.rn > 1
377
+ )`,
378
+ `ALTER TABLE brz_token_outputs
379
+ DROP CONSTRAINT IF EXISTS brz_token_outputs_pkey,
380
+ ADD PRIMARY KEY (user_id, prev_tx_hash, prev_tx_vout)`,
381
+ `ALTER TABLE brz_token_outputs DROP COLUMN id`,
382
+ ],
383
+ },
340
384
  ];
341
385
  }
342
386
  }