@breeztech/breez-sdk-spark 0.27.2 → 0.28.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.
Files changed (55) hide show
  1. package/README.md +9 -8
  2. package/bundler/breez_sdk_spark_wasm.d.ts +141 -16
  3. package/bundler/breez_sdk_spark_wasm.js +1 -1
  4. package/bundler/breez_sdk_spark_wasm_bg.js +190 -40
  5. package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
  6. package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
  7. package/bundler/index.js +6 -13
  8. package/bundler/storage/index.js +89 -17
  9. package/deno/breez_sdk_spark_wasm.d.ts +141 -16
  10. package/deno/breez_sdk_spark_wasm.js +190 -40
  11. package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
  12. package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
  13. package/nodejs/breez_sdk_spark_wasm.d.ts +141 -16
  14. package/nodejs/breez_sdk_spark_wasm.js +194 -40
  15. package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
  16. package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
  17. package/nodejs/index.d.mts +3 -0
  18. package/nodejs/index.js +30 -120
  19. package/nodejs/index.mjs +4 -0
  20. package/nodejs/mysql-session-store/index.cjs +24 -7
  21. package/nodejs/mysql-session-store/migrations.cjs +6 -0
  22. package/nodejs/mysql-storage/index.cjs +36 -19
  23. package/nodejs/mysql-storage/migrations.cjs +24 -9
  24. package/nodejs/mysql-token-store/index.cjs +28 -18
  25. package/nodejs/postgres-session-store/index.cjs +25 -7
  26. package/nodejs/postgres-session-store/migrations.cjs +12 -9
  27. package/nodejs/postgres-storage/index.cjs +36 -19
  28. package/nodejs/postgres-storage/migrations.cjs +24 -9
  29. package/nodejs/postgres-token-store/index.cjs +32 -18
  30. package/nodejs/postgres-token-store/migrations.cjs +5 -9
  31. package/nodejs/postgres-tree-store/migrations.cjs +5 -9
  32. package/nodejs/storage/index.cjs +35 -14
  33. package/nodejs/storage/migrations.cjs +25 -15
  34. package/nodejs/tree-store/index.cjs +1 -1
  35. package/nodejs/tree-store/migrations.cjs +6 -13
  36. package/package.json +27 -6
  37. package/ssr/index.js +24 -0
  38. package/web/breez_sdk_spark_wasm.d.ts +164 -27
  39. package/web/breez_sdk_spark_wasm.js +190 -40
  40. package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
  41. package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
  42. package/web/index.js +6 -13
  43. package/web/passkey-prf-provider/index.js +1 -0
  44. package/web/storage/index.js +89 -17
  45. package/breez-sdk-spark.tgz +0 -0
  46. package/itest/.gitignore +0 -2
  47. package/itest/database-drivers.test.js +0 -45
  48. package/itest/helpers/assert.test.js +0 -70
  49. package/itest/helpers/faucet.js +0 -68
  50. package/itest/helpers/lnurl-fixture.js +0 -229
  51. package/itest/helpers/scenario.js +0 -444
  52. package/itest/package-lock.json +0 -519
  53. package/itest/package.json +0 -18
  54. package/itest/scenarios.test.js +0 -47
  55. package/itest/smoke.test.js +0 -184
package/nodejs/index.mjs CHANGED
@@ -9,6 +9,7 @@ export const {
9
9
  connectWithSigningOnlySigner,
10
10
  createTurnkeySigner,
11
11
  createTurnkeySigningOnlySigner,
12
+ defaultBridgingConfig,
12
13
  defaultConfig,
13
14
  defaultExternalSigners,
14
15
  defaultMysqlStorageConfig,
@@ -19,6 +20,7 @@ export const {
19
20
  getSparkStatus,
20
21
  initLogging,
21
22
  mysqlStorage,
23
+ newBridgingClient,
22
24
  newRestChainService,
23
25
  newSharedSdkContext,
24
26
  parseSparkConfig,
@@ -28,6 +30,8 @@ export const {
28
30
  task_worker_entry_point,
29
31
  BitcoinChainServiceHandle,
30
32
  BreezSdk,
33
+ BridgingClient,
34
+ ConsoleLogger,
31
35
  DefaultCpfpSigner,
32
36
  DefaultSessionStore,
33
37
  ExternalBreezSignerHandle,
@@ -71,13 +71,14 @@ class MysqlSessionStore {
71
71
 
72
72
  /**
73
73
  * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
74
- * @returns {Promise<{token: string, expiration: number} | null>}
74
+ * @returns {Promise<{token: string, expiration: number, refreshToken?: string, refreshTokenExpiration?: number} | null>}
75
75
  */
76
76
  async getSession(serviceIdentityKey) {
77
77
  const serviceKey = _decodePubkey(serviceIdentityKey);
78
78
  try {
79
79
  const [rows] = await this.pool.execute(
80
- `SELECT token, expiration FROM brz_sessions
80
+ `SELECT token, expiration, refresh_token, refresh_token_expiration
81
+ FROM brz_sessions
81
82
  WHERE user_id = ? AND service_identity_key = ?`,
82
83
  [this.identity, serviceKey]
83
84
  );
@@ -88,6 +89,11 @@ class MysqlSessionStore {
88
89
  return {
89
90
  token: row.token,
90
91
  expiration: Number(row.expiration),
92
+ refreshToken: row.refresh_token ?? undefined,
93
+ refreshTokenExpiration:
94
+ row.refresh_token_expiration == null
95
+ ? undefined
96
+ : Number(row.refresh_token_expiration),
91
97
  };
92
98
  } catch (error) {
93
99
  throw new SessionStoreError(
@@ -99,16 +105,27 @@ class MysqlSessionStore {
99
105
 
100
106
  /**
101
107
  * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
102
- * @param {{token: string, expiration: number}} session
108
+ * @param {{token: string, expiration: number, refreshToken?: string, refreshTokenExpiration?: number}} session
103
109
  */
104
110
  async setSession(serviceIdentityKey, session) {
105
111
  const serviceKey = _decodePubkey(serviceIdentityKey);
106
112
  try {
107
113
  await this.pool.execute(
108
- `INSERT INTO brz_sessions (user_id, service_identity_key, token, expiration)
109
- VALUES (?, ?, ?, ?)
110
- ON DUPLICATE KEY UPDATE token = VALUES(token), expiration = VALUES(expiration)`,
111
- [this.identity, serviceKey, session.token, session.expiration]
114
+ `INSERT INTO brz_sessions
115
+ (user_id, service_identity_key, token, expiration,
116
+ refresh_token, refresh_token_expiration)
117
+ VALUES (?, ?, ?, ?, ?, ?)
118
+ ON DUPLICATE KEY UPDATE token = VALUES(token), expiration = VALUES(expiration),
119
+ refresh_token = VALUES(refresh_token),
120
+ refresh_token_expiration = VALUES(refresh_token_expiration)`,
121
+ [
122
+ this.identity,
123
+ serviceKey,
124
+ session.token,
125
+ session.expiration,
126
+ session.refreshToken ?? null,
127
+ session.refreshTokenExpiration ?? null,
128
+ ]
112
129
  );
113
130
  } catch (error) {
114
131
  throw new SessionStoreError(
@@ -129,6 +129,12 @@ class MysqlSessionStoreMigrationManager {
129
129
  `ALTER TABLE \`${SESSION_MIGRATIONS_TABLE}\` MODIFY COLUMN applied_at DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP(6))`,
130
130
  ],
131
131
  },
132
+ {
133
+ name: "Add refresh token columns to brz_sessions",
134
+ sql: [
135
+ `ALTER TABLE brz_sessions ADD COLUMN refresh_token TEXT NULL, ADD COLUMN refresh_token_expiration BIGINT NULL`,
136
+ ],
137
+ },
132
138
  ];
133
139
  }
134
140
  }
@@ -65,6 +65,8 @@ const SELECT_PAYMENT_SQL = `
65
65
  p.timestamp,
66
66
  p.method,
67
67
  p.withdraw_tx_id,
68
+ p.withdraw_vout,
69
+ p.withdraw_destination_address,
68
70
  pd.tx_id AS deposit_tx_id,
69
71
  pd.vout AS deposit_vout,
70
72
  p.spark,
@@ -88,6 +90,7 @@ const SELECT_PAYMENT_SQL = `
88
90
  lrm.nostr_zap_request AS lnurl_nostr_zap_request,
89
91
  lrm.nostr_zap_receipt AS lnurl_nostr_zap_receipt,
90
92
  lrm.sender_comment AS lnurl_sender_comment,
93
+ lrm.lightning_address AS lnurl_lightning_address,
91
94
  lrm.payment_hash AS lnurl_payment_hash,
92
95
  pm.parent_payment_id
93
96
  FROM brz_payments p
@@ -510,13 +513,15 @@ class MysqlStorage {
510
513
  await this._runPaymentUpsert(conn, payment);
511
514
  shouldEmit = true;
512
515
  } else if (stored === next) {
513
- console.debug(
516
+ this._log(
517
+ "DEBUG",
514
518
  `Skipping redundant payment event: id=${payment.id} status=${next}`
515
519
  );
516
520
  await this._runPaymentUpsert(conn, payment);
517
521
  shouldEmit = false;
518
522
  } else if (this._isFinalPaymentStatus(stored)) {
519
- console.warn(
523
+ this._log(
524
+ "WARN",
520
525
  `Skipping payment update (would replace terminal status): id=${payment.id} stored=${stored} new=${next}`
521
526
  );
522
527
  shouldEmit = false;
@@ -566,13 +571,12 @@ class MysqlStorage {
566
571
  }
567
572
 
568
573
  async _runPaymentUpsert(conn, payment) {
569
- const withdrawTxId =
570
- payment.details?.type === "withdraw" ? payment.details.txId : null;
574
+ const withdraw = payment.details?.type === "withdraw" ? payment.details : null;
571
575
  const spark = payment.details?.type === "spark" ? 1 : null;
572
576
 
573
577
  await conn.query(
574
- `INSERT INTO brz_payments (user_id, id, payment_type, status, amount, fees, timestamp, method, withdraw_tx_id, spark)
575
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
578
+ `INSERT INTO brz_payments (user_id, id, payment_type, status, amount, fees, timestamp, method, withdraw_tx_id, withdraw_vout, withdraw_destination_address, spark)
579
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
576
580
  ON DUPLICATE KEY UPDATE
577
581
  payment_type=VALUES(payment_type),
578
582
  status=VALUES(status),
@@ -581,6 +585,8 @@ class MysqlStorage {
581
585
  timestamp=VALUES(timestamp),
582
586
  method=VALUES(method),
583
587
  withdraw_tx_id=VALUES(withdraw_tx_id),
588
+ withdraw_vout=VALUES(withdraw_vout),
589
+ withdraw_destination_address=VALUES(withdraw_destination_address),
584
590
  spark=VALUES(spark)`,
585
591
  [
586
592
  this.identity,
@@ -591,7 +597,9 @@ class MysqlStorage {
591
597
  payment.fees.toString(),
592
598
  payment.timestamp,
593
599
  payment.method ? toJson(payment.method) : null,
594
- withdrawTxId,
600
+ withdraw?.txId ?? null,
601
+ withdraw?.vout ?? null,
602
+ withdraw?.destinationAddress ?? null,
595
603
  spark,
596
604
  ]
597
605
  );
@@ -692,6 +700,10 @@ class MysqlStorage {
692
700
  .slice(0, 32);
693
701
  }
694
702
 
703
+ _log(level, line) {
704
+ this.logger?.log({ line, level });
705
+ }
706
+
695
707
  _normalizePaymentStatus(status) {
696
708
  return typeof status === "string" ? status.toLowerCase() : status;
697
709
  }
@@ -1170,18 +1182,20 @@ class MysqlStorage {
1170
1182
  await this._withTransaction(async (conn) => {
1171
1183
  for (const item of metadata) {
1172
1184
  await conn.query(
1173
- `INSERT INTO brz_lnurl_receive_metadata (user_id, payment_hash, nostr_zap_request, nostr_zap_receipt, sender_comment)
1174
- VALUES (?, ?, ?, ?, ?)
1185
+ `INSERT INTO brz_lnurl_receive_metadata (user_id, payment_hash, nostr_zap_request, nostr_zap_receipt, sender_comment, lightning_address)
1186
+ VALUES (?, ?, ?, ?, ?, ?)
1175
1187
  ON DUPLICATE KEY UPDATE
1176
1188
  nostr_zap_request = VALUES(nostr_zap_request),
1177
1189
  nostr_zap_receipt = VALUES(nostr_zap_receipt),
1178
- sender_comment = VALUES(sender_comment)`,
1190
+ sender_comment = VALUES(sender_comment),
1191
+ lightning_address = VALUES(lightning_address)`,
1179
1192
  [
1180
1193
  this.identity,
1181
1194
  item.paymentHash,
1182
1195
  item.nostrZapRequest || null,
1183
1196
  item.nostrZapReceipt || null,
1184
1197
  item.senderComment || null,
1198
+ item.lightningAddress || null,
1185
1199
  ]
1186
1200
  );
1187
1201
  }
@@ -1232,6 +1246,7 @@ class MysqlStorage {
1232
1246
  nostrZapRequest: row.lnurl_nostr_zap_request || null,
1233
1247
  nostrZapReceipt: row.lnurl_nostr_zap_receipt || null,
1234
1248
  senderComment: row.lnurl_sender_comment || null,
1249
+ lightningAddress: row.lnurl_lightning_address || null,
1235
1250
  };
1236
1251
  }
1237
1252
 
@@ -1239,10 +1254,16 @@ class MysqlStorage {
1239
1254
  details.conversionInfo = parseJson(row.conversion_info);
1240
1255
  }
1241
1256
  } else if (row.withdraw_tx_id) {
1242
- details = {
1243
- type: "withdraw",
1244
- txId: row.withdraw_tx_id,
1245
- };
1257
+ // A withdrawal stored before the SDK kept these values has no details
1258
+ // until the next sync rewrites it.
1259
+ if (row.withdraw_vout != null && row.withdraw_destination_address != null) {
1260
+ details = {
1261
+ type: "withdraw",
1262
+ txId: row.withdraw_tx_id,
1263
+ vout: Number(row.withdraw_vout),
1264
+ destinationAddress: row.withdraw_destination_address,
1265
+ };
1266
+ }
1246
1267
  } else if (row.deposit_tx_id) {
1247
1268
  details = {
1248
1269
  type: "deposit",
@@ -1513,11 +1534,7 @@ class MysqlStorage {
1513
1534
 
1514
1535
  if (deleteResult.affectedRows === 0) {
1515
1536
  const msg = `complete_outgoing_sync: DELETE from brz_sync_outgoing matched 0 rows (type=${record.id.type}, data_id=${record.id.dataId}, revision=${localRevision})`;
1516
- if (this.logger && typeof this.logger.log === "function") {
1517
- this.logger.log({ line: msg, level: "warn" });
1518
- } else {
1519
- console.warn(`[MysqlStorage] ${msg}`);
1520
- }
1537
+ this._log("WARN", msg);
1521
1538
  }
1522
1539
 
1523
1540
  await conn.query(
@@ -101,20 +101,20 @@ class MysqlMigrationManager {
101
101
  const migrations = this._getMigrations(identity);
102
102
 
103
103
  if (currentVersion >= migrations.length) {
104
- this._log("info", `Database is up to date (version ${currentVersion})`);
104
+ this._log("INFO", `Database is up to date (version ${currentVersion})`);
105
105
  await conn.query("COMMIT");
106
106
  return;
107
107
  }
108
108
 
109
109
  this._log(
110
- "info",
110
+ "INFO",
111
111
  `Migrating database from version ${currentVersion} to ${migrations.length}`
112
112
  );
113
113
 
114
114
  for (let i = currentVersion; i < migrations.length; i++) {
115
115
  const migration = migrations[i];
116
116
  const version = i + 1;
117
- this._log("debug", `Running migration ${version}: ${migration.name}`);
117
+ this._log("DEBUG", `Running migration ${version}: ${migration.name}`);
118
118
 
119
119
  for (const step of migration.sql) {
120
120
  await runMigrationStep(conn, step);
@@ -127,7 +127,7 @@ class MysqlMigrationManager {
127
127
  }
128
128
 
129
129
  await conn.query("COMMIT");
130
- this._log("info", "Database migration completed successfully");
130
+ this._log("INFO", "Database migration completed successfully");
131
131
  } catch (error) {
132
132
  await conn.query("ROLLBACK").catch(() => {});
133
133
  throw new StorageError(
@@ -146,11 +146,7 @@ class MysqlMigrationManager {
146
146
  }
147
147
 
148
148
  _log(level, message) {
149
- if (this.logger && typeof this.logger.log === "function") {
150
- this.logger.log({ line: message, level });
151
- } else if (level === "error") {
152
- console.error(`[MysqlMigrationManager] ${message}`);
153
- }
149
+ this.logger?.log({ line: message, level });
154
150
  }
155
151
 
156
152
  /**
@@ -655,6 +651,25 @@ class MysqlMigrationManager {
655
651
  )`,
656
652
  ],
657
653
  },
654
+ {
655
+ // With the sync offset at 0, the SDK re-fetches every transfer on its
656
+ // next sync and fills in both columns for past withdrawals.
657
+ name: "Add withdrawal destination address and output index",
658
+ sql: [
659
+ `ALTER TABLE brz_payments ADD COLUMN withdraw_vout INT UNSIGNED NULL`,
660
+ `ALTER TABLE brz_payments ADD COLUMN withdraw_destination_address TEXT NULL`,
661
+ `UPDATE brz_settings
662
+ SET value = JSON_SET(value, '$.offset', 0)
663
+ WHERE \`key\` = 'sync_offset' AND value IS NOT NULL`,
664
+ ],
665
+ },
666
+ {
667
+ name: "Add lightning address to brz_lnurl_receive_metadata and force its re-sync",
668
+ sql: [
669
+ `ALTER TABLE brz_lnurl_receive_metadata ADD COLUMN lightning_address LONGTEXT NULL`,
670
+ `DELETE FROM brz_settings WHERE \`key\` = 'lnurl_metadata_updated_after'`,
671
+ ],
672
+ },
658
673
  ];
659
674
  }
660
675
  }
@@ -399,7 +399,7 @@ class MysqlTokenStore {
399
399
  const metadata = _metadataFor(tokenOutputs.metadata, tokenIdentifier);
400
400
  if (!metadata) {
401
401
  this._log(
402
- "warn",
402
+ "WARN",
403
403
  `Skipping outputs of token ${tokenIdentifier}: no metadata provided`
404
404
  );
405
405
  continue;
@@ -634,31 +634,26 @@ class MysqlTokenStore {
634
634
  }
635
635
 
636
636
  // 2. Insert new outputs, grouped by the token each one names.
637
- for (const [tokenIdentifier, outputs] of _groupOutputsByToken(
637
+ const spentOutpoints = await this._spentOutpointsAmong(
638
+ conn,
638
639
  outputsToAdd?.outputs
640
+ );
641
+ const unspentOutputs = (outputsToAdd?.outputs ?? []).filter(
642
+ (o) => !spentOutpoints.has(`${o.prevTxHash}:${o.prevTxVout}`)
643
+ );
644
+ for (const [tokenIdentifier, outputs] of _groupOutputsByToken(
645
+ unspentOutputs
639
646
  )) {
640
647
  const metadata = _metadataFor(outputsToAdd.metadata, tokenIdentifier);
641
648
  if (!metadata) {
642
649
  this._log(
643
- "warn",
650
+ "WARN",
644
651
  `Skipping outputs of token ${tokenIdentifier}: no metadata provided`
645
652
  );
646
653
  continue;
647
654
  }
648
655
  await this._upsertMetadata(conn, metadata);
649
656
 
650
- // Clear spent status for outputs being (re-)added.
651
- const params = [this.identity];
652
- for (const o of outputs) {
653
- params.push(o.prevTxHash, o.prevTxVout);
654
- }
655
- await conn.query(
656
- `DELETE FROM brz_token_spent_outputs WHERE user_id = ? AND (prev_tx_hash, prev_tx_vout) IN (${buildPairPlaceholders(
657
- outputs.length
658
- )})`,
659
- params
660
- );
661
-
662
657
  for (const output of outputs) {
663
658
  await this._insertSingleOutput(conn, tokenIdentifier, output);
664
659
  }
@@ -991,9 +986,7 @@ class MysqlTokenStore {
991
986
  // ===== Private Helpers =====
992
987
 
993
988
  _log(level, message) {
994
- if (this.logger && typeof this.logger.log === "function") {
995
- this.logger.log({ line: message, level });
996
- }
989
+ this.logger?.log({ line: message, level });
997
990
  }
998
991
 
999
992
  /**
@@ -1185,6 +1178,23 @@ class MysqlTokenStore {
1185
1178
  );
1186
1179
  }
1187
1180
 
1181
+ async _spentOutpointsAmong(conn, outputs) {
1182
+ if (!outputs || outputs.length === 0) {
1183
+ return new Set();
1184
+ }
1185
+ const params = [this.identity];
1186
+ for (const o of outputs) {
1187
+ params.push(o.prevTxHash, o.prevTxVout);
1188
+ }
1189
+ const [rows] = await conn.query(
1190
+ `SELECT prev_tx_hash, prev_tx_vout FROM brz_token_spent_outputs WHERE user_id = ? AND (prev_tx_hash, prev_tx_vout) IN (${buildPairPlaceholders(
1191
+ outputs.length
1192
+ )})`,
1193
+ params
1194
+ );
1195
+ return new Set(rows.map((r) => `${r.prev_tx_hash}:${r.prev_tx_vout}`));
1196
+ }
1197
+
1188
1198
  async _insertSingleOutput(conn, tokenIdentifier, output) {
1189
1199
  // ON DUPLICATE KEY UPDATE prev_tx_hash = prev_tx_hash no-ops on the
1190
1200
  // (user_id, prev_tx_hash, prev_tx_vout) primary key conflict only — unlike
@@ -80,13 +80,14 @@ class PostgresSessionStore {
80
80
  * if no session is cached. The Rust adapter maps `null` to
81
81
  * `SessionStoreError::NotFound`.
82
82
  * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
83
- * @returns {Promise<{token: string, expiration: number} | null>}
83
+ * @returns {Promise<{token: string, expiration: number, refreshToken?: string, refreshTokenExpiration?: number} | null>}
84
84
  */
85
85
  async getSession(serviceIdentityKey) {
86
86
  const serviceKey = _decodePubkey(serviceIdentityKey);
87
87
  try {
88
88
  const { rows } = await this.pool.query(
89
- `SELECT token, expiration FROM brz_sessions
89
+ `SELECT token, expiration, refresh_token, refresh_token_expiration
90
+ FROM brz_sessions
90
91
  WHERE user_id = $1 AND service_identity_key = $2`,
91
92
  [this.identity, serviceKey]
92
93
  );
@@ -97,6 +98,11 @@ class PostgresSessionStore {
97
98
  return {
98
99
  token: row.token,
99
100
  expiration: Number(row.expiration),
101
+ refreshToken: row.refresh_token ?? undefined,
102
+ refreshTokenExpiration:
103
+ row.refresh_token_expiration == null
104
+ ? undefined
105
+ : Number(row.refresh_token_expiration),
100
106
  };
101
107
  } catch (error) {
102
108
  throw new SessionStoreError(
@@ -109,17 +115,29 @@ class PostgresSessionStore {
109
115
  /**
110
116
  * Upserts a session for the given service identity key.
111
117
  * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
112
- * @param {{token: string, expiration: number}} session
118
+ * @param {{token: string, expiration: number, refreshToken?: string, refreshTokenExpiration?: number}} session
113
119
  */
114
120
  async setSession(serviceIdentityKey, session) {
115
121
  const serviceKey = _decodePubkey(serviceIdentityKey);
116
122
  try {
117
123
  await this.pool.query(
118
- `INSERT INTO brz_sessions (user_id, service_identity_key, token, expiration)
119
- VALUES ($1, $2, $3, $4)
124
+ `INSERT INTO brz_sessions
125
+ (user_id, service_identity_key, token, expiration,
126
+ refresh_token, refresh_token_expiration)
127
+ VALUES ($1, $2, $3, $4, $5, $6)
120
128
  ON CONFLICT (user_id, service_identity_key)
121
- DO UPDATE SET token = EXCLUDED.token, expiration = EXCLUDED.expiration`,
122
- [this.identity, serviceKey, session.token, session.expiration]
129
+ DO UPDATE SET token = EXCLUDED.token,
130
+ expiration = EXCLUDED.expiration,
131
+ refresh_token = EXCLUDED.refresh_token,
132
+ refresh_token_expiration = EXCLUDED.refresh_token_expiration`,
133
+ [
134
+ this.identity,
135
+ serviceKey,
136
+ session.token,
137
+ session.expiration,
138
+ session.refreshToken ?? null,
139
+ session.refreshTokenExpiration ?? null,
140
+ ]
123
141
  );
124
142
  } catch (error) {
125
143
  throw new SessionStoreError(
@@ -50,7 +50,7 @@ class SessionStoreMigrationManager {
50
50
 
51
51
  if (currentVersion >= migrations.length) {
52
52
  this._log(
53
- "info",
53
+ "INFO",
54
54
  `Session store database is up to date (version ${currentVersion})`
55
55
  );
56
56
  await client.query("COMMIT");
@@ -58,7 +58,7 @@ class SessionStoreMigrationManager {
58
58
  }
59
59
 
60
60
  this._log(
61
- "info",
61
+ "INFO",
62
62
  `Migrating session store database from version ${currentVersion} to ${migrations.length}`
63
63
  );
64
64
 
@@ -66,7 +66,7 @@ class SessionStoreMigrationManager {
66
66
  const migration = migrations[i];
67
67
  const version = i + 1;
68
68
  this._log(
69
- "debug",
69
+ "DEBUG",
70
70
  `Running session store migration ${version}: ${migration.name}`
71
71
  );
72
72
 
@@ -82,7 +82,7 @@ class SessionStoreMigrationManager {
82
82
 
83
83
  await client.query("COMMIT");
84
84
  this._log(
85
- "info",
85
+ "INFO",
86
86
  "Session store database migration completed successfully"
87
87
  );
88
88
  } catch (error) {
@@ -134,11 +134,7 @@ class SessionStoreMigrationManager {
134
134
  }
135
135
 
136
136
  _log(level, message) {
137
- if (this.logger && typeof this.logger.log === "function") {
138
- this.logger.log({ line: message, level });
139
- } else if (level === "error") {
140
- console.error(`[SessionStoreMigrationManager] ${message}`);
141
- }
137
+ this.logger?.log({ line: message, level });
142
138
  }
143
139
 
144
140
  /**
@@ -158,6 +154,13 @@ class SessionStoreMigrationManager {
158
154
  )`,
159
155
  ],
160
156
  },
157
+ {
158
+ name: "Add refresh token columns to brz_sessions",
159
+ sql: [
160
+ `ALTER TABLE brz_sessions ADD COLUMN IF NOT EXISTS refresh_token TEXT`,
161
+ `ALTER TABLE brz_sessions ADD COLUMN IF NOT EXISTS refresh_token_expiration BIGINT`,
162
+ ],
163
+ },
161
164
  ];
162
165
  }
163
166
  }
@@ -50,6 +50,8 @@ const SELECT_PAYMENT_SQL = `
50
50
  p.timestamp,
51
51
  p.method,
52
52
  p.withdraw_tx_id,
53
+ p.withdraw_vout,
54
+ p.withdraw_destination_address,
53
55
  pd.tx_id AS deposit_tx_id,
54
56
  pd.vout AS deposit_vout,
55
57
  p.spark,
@@ -73,6 +75,7 @@ const SELECT_PAYMENT_SQL = `
73
75
  lrm.nostr_zap_request AS lnurl_nostr_zap_request,
74
76
  lrm.nostr_zap_receipt AS lnurl_nostr_zap_receipt,
75
77
  lrm.sender_comment AS lnurl_sender_comment,
78
+ lrm.lightning_address AS lnurl_lightning_address,
76
79
  lrm.payment_hash AS lnurl_payment_hash,
77
80
  pm.parent_payment_id
78
81
  FROM brz_payments p
@@ -412,7 +415,8 @@ class PostgresStorage {
412
415
  if (stored != null
413
416
  && this._isFinalPaymentStatus(stored)
414
417
  && stored !== next) {
415
- console.warn(
418
+ this._log(
419
+ "WARN",
416
420
  `Skipping payment update (would replace terminal status): id=${payment.id} stored=${stored} new=${next}`
417
421
  );
418
422
  return false;
@@ -420,7 +424,8 @@ class PostgresStorage {
420
424
 
421
425
  const sameStatus = stored === next;
422
426
  if (sameStatus) {
423
- console.debug(
427
+ this._log(
428
+ "DEBUG",
424
429
  `Skipping redundant payment event: id=${payment.id} status=${next}`
425
430
  );
426
431
  }
@@ -448,13 +453,12 @@ class PostgresStorage {
448
453
  }
449
454
 
450
455
  async _runPaymentUpsert(client, payment) {
451
- const withdrawTxId =
452
- payment.details?.type === "withdraw" ? payment.details.txId : null;
456
+ const withdraw = payment.details?.type === "withdraw" ? payment.details : null;
453
457
  const spark = payment.details?.type === "spark" ? true : null;
454
458
 
455
459
  await client.query(
456
- `INSERT INTO brz_payments (user_id, id, payment_type, status, amount, fees, timestamp, method, withdraw_tx_id, spark)
457
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
460
+ `INSERT INTO brz_payments (user_id, id, payment_type, status, amount, fees, timestamp, method, withdraw_tx_id, withdraw_vout, withdraw_destination_address, spark)
461
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
458
462
  ON CONFLICT(user_id, id) DO UPDATE SET
459
463
  payment_type=EXCLUDED.payment_type,
460
464
  status=EXCLUDED.status,
@@ -463,6 +467,8 @@ class PostgresStorage {
463
467
  timestamp=EXCLUDED.timestamp,
464
468
  method=EXCLUDED.method,
465
469
  withdraw_tx_id=EXCLUDED.withdraw_tx_id,
470
+ withdraw_vout=EXCLUDED.withdraw_vout,
471
+ withdraw_destination_address=EXCLUDED.withdraw_destination_address,
466
472
  spark=EXCLUDED.spark`,
467
473
  [
468
474
  this.identity,
@@ -473,7 +479,9 @@ class PostgresStorage {
473
479
  payment.fees.toString(),
474
480
  payment.timestamp,
475
481
  payment.method ? toJson(payment.method) : null,
476
- withdrawTxId,
482
+ withdraw?.txId ?? null,
483
+ withdraw?.vout ?? null,
484
+ withdraw?.destinationAddress ?? null,
477
485
  spark,
478
486
  ]
479
487
  );
@@ -564,6 +572,10 @@ class PostgresStorage {
564
572
  }
565
573
  }
566
574
 
575
+ _log(level, line) {
576
+ this.logger?.log({ line, level });
577
+ }
578
+
567
579
  _normalizePaymentStatus(status) {
568
580
  return typeof status === "string" ? status.toLowerCase() : status;
569
581
  }
@@ -1036,18 +1048,20 @@ class PostgresStorage {
1036
1048
  await this._withTransaction(async (client) => {
1037
1049
  for (const item of metadata) {
1038
1050
  await client.query(
1039
- `INSERT INTO brz_lnurl_receive_metadata (user_id, payment_hash, nostr_zap_request, nostr_zap_receipt, sender_comment)
1040
- VALUES ($1, $2, $3, $4, $5)
1051
+ `INSERT INTO brz_lnurl_receive_metadata (user_id, payment_hash, nostr_zap_request, nostr_zap_receipt, sender_comment, lightning_address)
1052
+ VALUES ($1, $2, $3, $4, $5, $6)
1041
1053
  ON CONFLICT(user_id, payment_hash) DO UPDATE SET
1042
1054
  nostr_zap_request = EXCLUDED.nostr_zap_request,
1043
1055
  nostr_zap_receipt = EXCLUDED.nostr_zap_receipt,
1044
- sender_comment = EXCLUDED.sender_comment`,
1056
+ sender_comment = EXCLUDED.sender_comment,
1057
+ lightning_address = EXCLUDED.lightning_address`,
1045
1058
  [
1046
1059
  this.identity,
1047
1060
  item.paymentHash,
1048
1061
  item.nostrZapRequest || null,
1049
1062
  item.nostrZapReceipt || null,
1050
1063
  item.senderComment || null,
1064
+ item.lightningAddress || null,
1051
1065
  ]
1052
1066
  );
1053
1067
  }
@@ -1105,6 +1119,7 @@ class PostgresStorage {
1105
1119
  nostrZapRequest: row.lnurl_nostr_zap_request || null,
1106
1120
  nostrZapReceipt: row.lnurl_nostr_zap_receipt || null,
1107
1121
  senderComment: row.lnurl_sender_comment || null,
1122
+ lightningAddress: row.lnurl_lightning_address || null,
1108
1123
  };
1109
1124
  }
1110
1125
 
@@ -1115,10 +1130,16 @@ class PostgresStorage {
1115
1130
  : row.conversion_info;
1116
1131
  }
1117
1132
  } else if (row.withdraw_tx_id) {
1118
- details = {
1119
- type: "withdraw",
1120
- txId: row.withdraw_tx_id,
1121
- };
1133
+ // A withdrawal stored before the SDK kept these values has no details
1134
+ // until the next sync rewrites it.
1135
+ if (row.withdraw_vout != null && row.withdraw_destination_address != null) {
1136
+ details = {
1137
+ type: "withdraw",
1138
+ txId: row.withdraw_tx_id,
1139
+ vout: Number(row.withdraw_vout),
1140
+ destinationAddress: row.withdraw_destination_address,
1141
+ };
1142
+ }
1122
1143
  } else if (row.deposit_tx_id) {
1123
1144
  details = {
1124
1145
  type: "deposit",
@@ -1423,11 +1444,7 @@ class PostgresStorage {
1423
1444
 
1424
1445
  if (deleteResult.rowCount === 0) {
1425
1446
  const msg = `complete_outgoing_sync: DELETE from brz_sync_outgoing matched 0 rows (type=${record.id.type}, data_id=${record.id.dataId}, revision=${localRevision})`;
1426
- if (this.logger && typeof this.logger.log === "function") {
1427
- this.logger.log({ line: msg, level: "warn" });
1428
- } else {
1429
- console.warn(`[PostgresStorage] ${msg}`);
1430
- }
1447
+ this._log("WARN", msg);
1431
1448
  }
1432
1449
 
1433
1450
  await client.query(