@breeztech/breez-sdk-spark 0.27.1 → 0.27.3-dev
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/README.md +9 -8
- package/bundler/breez_sdk_spark_wasm.d.ts +141 -16
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +190 -40
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
- package/bundler/index.js +6 -13
- package/bundler/storage/index.js +89 -17
- package/deno/breez_sdk_spark_wasm.d.ts +141 -16
- package/deno/breez_sdk_spark_wasm.js +190 -40
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
- package/nodejs/breez_sdk_spark_wasm.d.ts +141 -16
- package/nodejs/breez_sdk_spark_wasm.js +194 -40
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
- package/nodejs/index.d.mts +3 -0
- package/nodejs/index.js +30 -120
- package/nodejs/index.mjs +4 -0
- package/nodejs/mysql-session-store/index.cjs +24 -7
- package/nodejs/mysql-session-store/migrations.cjs +6 -0
- package/nodejs/mysql-storage/index.cjs +36 -19
- package/nodejs/mysql-storage/migrations.cjs +24 -9
- package/nodejs/mysql-token-store/index.cjs +28 -18
- package/nodejs/postgres-session-store/index.cjs +25 -7
- package/nodejs/postgres-session-store/migrations.cjs +12 -9
- package/nodejs/postgres-storage/index.cjs +36 -19
- package/nodejs/postgres-storage/migrations.cjs +24 -9
- package/nodejs/postgres-token-store/index.cjs +32 -18
- package/nodejs/postgres-token-store/migrations.cjs +5 -9
- package/nodejs/postgres-tree-store/migrations.cjs +5 -9
- package/nodejs/storage/index.cjs +35 -14
- package/nodejs/storage/migrations.cjs +25 -15
- package/nodejs/tree-store/index.cjs +1 -1
- package/nodejs/tree-store/migrations.cjs +6 -13
- package/package.json +27 -6
- package/ssr/index.js +24 -0
- package/web/breez_sdk_spark_wasm.d.ts +164 -27
- package/web/breez_sdk_spark_wasm.js +190 -40
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +23 -11
- package/web/index.js +6 -13
- package/web/passkey-prf-provider/index.js +1 -0
- package/web/storage/index.js +89 -17
- package/breez-sdk-spark.tgz +0 -0
- package/itest/.gitignore +0 -2
- package/itest/database-drivers.test.js +0 -45
- package/itest/helpers/assert.test.js +0 -70
- package/itest/helpers/faucet.js +0 -68
- package/itest/helpers/lnurl-fixture.js +0 -229
- package/itest/helpers/scenario.js +0 -444
- package/itest/package-lock.json +0 -519
- package/itest/package.json +0 -18
- package/itest/scenarios.test.js +0 -47
- 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
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
"
|
|
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("
|
|
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("
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
|
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
|
|
119
|
-
|
|
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,
|
|
122
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
-
|
|
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(
|