@arkade-os/sdk 0.5.0-rc.9 → 0.5.1

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 (94) hide show
  1. package/README.md +232 -51
  2. package/dist/adapters/expo.cjs +5 -5
  3. package/dist/adapters/expo.d.cts +5 -5
  4. package/dist/adapters/expo.d.ts +5 -5
  5. package/dist/adapters/expo.js +3 -3
  6. package/dist/{asyncStorageTaskQueue-DZYrAWN8.d.ts → asyncStorageTaskQueue-CdWheMqT.d.ts} +1 -1
  7. package/dist/{asyncStorageTaskQueue-ChKX-D51.d.cts → asyncStorageTaskQueue-DFt5LgW5.d.cts} +1 -1
  8. package/dist/{chunk-R4KRWX7R.js → chunk-3M22PJVB.js} +427 -1078
  9. package/dist/chunk-3M22PJVB.js.map +1 -0
  10. package/dist/{chunk-J2K7WWMJ.js → chunk-6MIGYYWB.js} +112 -243
  11. package/dist/chunk-6MIGYYWB.js.map +1 -0
  12. package/dist/{chunk-FQCMEAWN.js → chunk-FXVEOEYB.js} +32 -39
  13. package/dist/chunk-FXVEOEYB.js.map +1 -0
  14. package/dist/{chunk-7OL2P5TV.cjs → chunk-GQUFPTN7.cjs} +39 -46
  15. package/dist/chunk-GQUFPTN7.cjs.map +1 -0
  16. package/dist/{chunk-VMVHRKXM.cjs → chunk-GW42FS2X.cjs} +494 -1122
  17. package/dist/chunk-GW42FS2X.cjs.map +1 -0
  18. package/dist/{chunk-V4JZOFYT.cjs → chunk-HQNQ3CLQ.cjs} +124 -254
  19. package/dist/chunk-HQNQ3CLQ.cjs.map +1 -0
  20. package/dist/{chunk-PO2ZDZZC.cjs → chunk-JJ6QKTFZ.cjs} +12 -10
  21. package/dist/chunk-JJ6QKTFZ.cjs.map +1 -0
  22. package/dist/{chunk-3TCWQPAC.js → chunk-NKKJEA3Z.js} +5842 -4284
  23. package/dist/chunk-NKKJEA3Z.js.map +1 -0
  24. package/dist/{chunk-JFZBKPSV.js → chunk-QFB55EOZ.js} +541 -400
  25. package/dist/chunk-QFB55EOZ.js.map +1 -0
  26. package/dist/{chunk-PKD5VPYX.cjs → chunk-QKNSMVBW.cjs} +6143 -4565
  27. package/dist/chunk-QKNSMVBW.cjs.map +1 -0
  28. package/dist/{chunk-VTXMC4N7.cjs → chunk-XLHUGP3G.cjs} +548 -399
  29. package/dist/chunk-XLHUGP3G.cjs.map +1 -0
  30. package/dist/{chunk-Z3QM5XFY.js → chunk-XQRIY5K5.js} +7 -5
  31. package/dist/chunk-XQRIY5K5.js.map +1 -0
  32. package/dist/contracts/handlers/index.cjs +9 -9
  33. package/dist/contracts/handlers/index.d.cts +5 -5
  34. package/dist/contracts/handlers/index.d.ts +5 -5
  35. package/dist/contracts/handlers/index.js +2 -2
  36. package/dist/{delegate-CyBnNY9v.d.ts → delegate-BSbGQabI.d.ts} +1 -1
  37. package/dist/{delegate-Cyv2suFd.d.cts → delegate-CFCfrReG.d.cts} +1 -1
  38. package/dist/{index-BIL9boGL.d.ts → index-B3fRWO5i.d.cts} +1652 -2291
  39. package/dist/{index-BIL9boGL.d.cts → index-B3fRWO5i.d.ts} +1652 -2291
  40. package/dist/index-CzUVvIxA.d.ts +645 -0
  41. package/dist/index-Fpf4mQZr.d.cts +645 -0
  42. package/dist/index.cjs +399 -311
  43. package/dist/index.d.cts +957 -1124
  44. package/dist/index.d.ts +957 -1124
  45. package/dist/index.js +4 -4
  46. package/dist/repositories/realm/index.cjs +165 -103
  47. package/dist/repositories/realm/index.cjs.map +1 -1
  48. package/dist/repositories/realm/index.d.cts +18 -14
  49. package/dist/repositories/realm/index.d.ts +18 -14
  50. package/dist/repositories/realm/index.js +153 -91
  51. package/dist/repositories/realm/index.js.map +1 -1
  52. package/dist/repositories/sqlite/index.cjs +226 -184
  53. package/dist/repositories/sqlite/index.cjs.map +1 -1
  54. package/dist/repositories/sqlite/index.d.cts +22 -21
  55. package/dist/repositories/sqlite/index.d.ts +22 -21
  56. package/dist/repositories/sqlite/index.js +218 -176
  57. package/dist/repositories/sqlite/index.js.map +1 -1
  58. package/dist/{taskRunner-Cu4uO1to.d.ts → taskRunner-CdfoCMaw.d.ts} +1 -1
  59. package/dist/{taskRunner-DxpSuKmh.d.cts → taskRunner-Dk6oDAtd.d.cts} +1 -1
  60. package/dist/wallet/expo/background.cjs +14 -14
  61. package/dist/wallet/expo/background.cjs.map +1 -1
  62. package/dist/wallet/expo/background.d.cts +10 -16
  63. package/dist/wallet/expo/background.d.ts +10 -16
  64. package/dist/wallet/expo/background.js +6 -6
  65. package/dist/wallet/expo/background.js.map +1 -1
  66. package/dist/wallet/expo/index.cjs +19 -14
  67. package/dist/wallet/expo/index.cjs.map +1 -1
  68. package/dist/wallet/expo/index.d.cts +8 -6
  69. package/dist/wallet/expo/index.d.ts +8 -6
  70. package/dist/wallet/expo/index.js +10 -5
  71. package/dist/wallet/expo/index.js.map +1 -1
  72. package/dist/wallet-BQHNgc_v.d.ts +1874 -0
  73. package/dist/wallet-D9dvXu-K.d.cts +1874 -0
  74. package/dist/worker/expo/index.cjs +9 -9
  75. package/dist/worker/expo/index.d.cts +6 -6
  76. package/dist/worker/expo/index.d.ts +6 -6
  77. package/dist/worker/expo/index.js +5 -5
  78. package/package.json +10 -11
  79. package/dist/chunk-3TCWQPAC.js.map +0 -1
  80. package/dist/chunk-7OL2P5TV.cjs.map +0 -1
  81. package/dist/chunk-FQCMEAWN.js.map +0 -1
  82. package/dist/chunk-J2K7WWMJ.js.map +0 -1
  83. package/dist/chunk-JFZBKPSV.js.map +0 -1
  84. package/dist/chunk-PKD5VPYX.cjs.map +0 -1
  85. package/dist/chunk-PO2ZDZZC.cjs.map +0 -1
  86. package/dist/chunk-R4KRWX7R.js.map +0 -1
  87. package/dist/chunk-V4JZOFYT.cjs.map +0 -1
  88. package/dist/chunk-VMVHRKXM.cjs.map +0 -1
  89. package/dist/chunk-VTXMC4N7.cjs.map +0 -1
  90. package/dist/chunk-Z3QM5XFY.js.map +0 -1
  91. package/dist/hdWalletCapable-D1ImQqlu.d.cts +0 -2214
  92. package/dist/hdWalletCapable-DvumVrKW.d.ts +0 -2214
  93. package/dist/index-DleNQcz5.d.cts +0 -850
  94. package/dist/index-QVhQgYvU.d.ts +0 -850
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkPKD5VPYX_cjs = require('../../chunk-PKD5VPYX.cjs');
4
- require('../../chunk-V4JZOFYT.cjs');
5
- require('../../chunk-VMVHRKXM.cjs');
6
- require('../../chunk-VTXMC4N7.cjs');
3
+ var chunkQKNSMVBW_cjs = require('../../chunk-QKNSMVBW.cjs');
4
+ require('../../chunk-HQNQ3CLQ.cjs');
5
+ require('../../chunk-GW42FS2X.cjs');
6
+ var chunkXLHUGP3G_cjs = require('../../chunk-XLHUGP3G.cjs');
7
7
 
8
8
  // src/repositories/sqlite/transaction.ts
9
9
  var writeChains = /* @__PURE__ */ new WeakMap();
@@ -30,11 +30,19 @@ function runInTransaction(db, fn) {
30
30
  return run;
31
31
  }
32
32
 
33
+ // src/repositories/sqlite/prefix.ts
34
+ var SAFE_PREFIX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
35
+ function sanitizeTablePrefix(p) {
36
+ if (!SAFE_PREFIX.test(p)) throw new Error(`Invalid table prefix "${p}"`);
37
+ return p;
38
+ }
39
+
33
40
  // src/repositories/sqlite/walletRepository.ts
41
+ var MAX_BOUND_PARAMETERS = 999;
34
42
  var SQLiteWalletRepository = class {
35
43
  constructor(db, options) {
36
44
  this.db = db;
37
- this.prefix = sanitizePrefix(options?.prefix ?? "ark_");
45
+ this.prefix = sanitizeTablePrefix(options?.prefix ?? "ark_");
38
46
  this.tables = {
39
47
  vtxos: `${this.prefix}vtxos`,
40
48
  utxos: `${this.prefix}utxos`,
@@ -43,7 +51,7 @@ var SQLiteWalletRepository = class {
43
51
  };
44
52
  }
45
53
  db;
46
- version = 1;
54
+ version = 2;
47
55
  initPromise = null;
48
56
  prefix;
49
57
  tables;
@@ -99,23 +107,42 @@ var SQLiteWalletRepository = class {
99
107
  await this.db.run(
100
108
  `CREATE INDEX IF NOT EXISTS idx_${this.prefix}vtxos_script ON ${this.tables.vtxos} (script)`
101
109
  );
110
+ await this.db.run(
111
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}vtxos_script_page ON ${this.tables.vtxos} (script, address, txid, vout)`
112
+ );
113
+ await this.db.run(
114
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}vtxos_address_page ON ${this.tables.vtxos} (address, txid, vout)`
115
+ );
116
+ await this.db.run(`DROP INDEX IF EXISTS idx_${this.prefix}vtxos_live_script`);
117
+ await this.db.run(
118
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}vtxos_live_script_page ON ${this.tables.vtxos} (script, address, txid, vout)
119
+ WHERE ${UNSPENT_VTXO_PREDICATE}`
120
+ );
102
121
  await this.db.run(
103
122
  `CREATE INDEX IF NOT EXISTS idx_${this.prefix}utxos_address ON ${this.tables.utxos} (address)`
104
123
  );
124
+ await this.db.run(
125
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}utxos_address_page ON ${this.tables.utxos} (address, txid, vout)`
126
+ );
105
127
  await this.db.run(
106
128
  `CREATE INDEX IF NOT EXISTS idx_${this.prefix}transactions_address ON ${this.tables.transactions} (address)`
107
129
  );
130
+ await this.db.run(
131
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}transactions_history ON ${this.tables.transactions} (address, created_at, boarding_txid, commitment_txid, ark_txid)`
132
+ );
108
133
  }
109
134
  /**
110
135
  * Bring the `vtxos` table to the current schema (v1 = `script` NOT NULL).
111
136
  *
112
- * Three cases:
137
+ * Four cases:
113
138
  * - Fresh install: create the v1 schema directly.
114
139
  * - Legacy install without a `script` column: add it, backfill from
115
140
  * `address`, then rebuild the table with NOT NULL (SQLite cannot add
116
141
  * the NOT NULL constraint in place).
117
142
  * - Legacy install with a nullable `script` column: backfill the NULLs
118
143
  * and rebuild.
144
+ * - 0.4.x install: `script` is already NOT NULL but `virtual_status_json
145
+ * NOT NULL` is still there, and `saveVtxos` no longer writes it.
119
146
  *
120
147
  * The backfill derives `script` from the Ark address, matching what the
121
148
  * indexer would have returned — new rows from the indexer always carry a
@@ -159,10 +186,11 @@ var SQLiteWalletRepository = class {
159
186
  addedCanonicalColumns = true;
160
187
  }
161
188
  }
162
- if (addedCanonicalColumns && cols.some((c) => c.name === "virtual_status_json")) {
189
+ const hasLegacyBlob = cols.some((c) => c.name === "virtual_status_json");
190
+ if (addedCanonicalColumns && hasLegacyBlob) {
163
191
  await this.backfillCanonicalVtxoColumns();
164
192
  }
165
- if (scriptCol && scriptCol.notnull === 1) {
193
+ if (scriptCol && scriptCol.notnull === 1 && !hasLegacyBlob) {
166
194
  return;
167
195
  }
168
196
  if (!scriptCol) {
@@ -172,7 +200,7 @@ var SQLiteWalletRepository = class {
172
200
  for (const row of nullRows) {
173
201
  await this.db.run(
174
202
  `UPDATE ${this.tables.vtxos} SET script = ? WHERE txid = ? AND vout = ?`,
175
- [chunkPKD5VPYX_cjs.scriptFromArkAddress(row.address), row.txid, row.vout]
203
+ [chunkQKNSMVBW_cjs.scriptFromArkAddress(row.address), row.txid, row.vout]
176
204
  );
177
205
  }
178
206
  const tempName = `${this.tables.vtxos}__migrate_tmp`;
@@ -213,7 +241,7 @@ var SQLiteWalletRepository = class {
213
241
  WHERE virtual_status_json IS NOT NULL AND is_swept IS NULL`
214
242
  );
215
243
  for (const row of rows) {
216
- const facts = chunkPKD5VPYX_cjs.legacyVtxoFacts(row.virtual_status_json);
244
+ const facts = chunkQKNSMVBW_cjs.legacyVtxoFacts(row.virtual_status_json);
217
245
  if (!facts) continue;
218
246
  await this.db.run(
219
247
  `UPDATE ${this.tables.vtxos}
@@ -263,6 +291,23 @@ var SQLiteWalletRepository = class {
263
291
  PRIMARY KEY (txid, vout)
264
292
  )`;
265
293
  }
294
+ /**
295
+ * `INSERT OR REPLACE` rows, as many to a statement as the parameter budget allows.
296
+ * No enclosing transaction: on the shared connection it would take in other repos' writes.
297
+ */
298
+ async insertOrReplace(table, columns, rows) {
299
+ const width = columns.split(",").length;
300
+ const tuple = `(${Array(width).fill("?").join(", ")})`;
301
+ const rowsPerStatement = Math.floor(MAX_BOUND_PARAMETERS / width);
302
+ for (let i = 0; i < rows.length; i += rowsPerStatement) {
303
+ const chunk = rows.slice(i, i + rowsPerStatement);
304
+ await this.db.run(
305
+ `INSERT OR REPLACE INTO ${table} (${columns})
306
+ VALUES ${chunk.map(() => tuple).join(", ")}`,
307
+ chunk.flat()
308
+ );
309
+ }
310
+ }
266
311
  async [Symbol.asyncDispose]() {
267
312
  }
268
313
  // ── Clear ──────────────────────────────────────────────────────────
@@ -274,33 +319,22 @@ var SQLiteWalletRepository = class {
274
319
  await this.db.run(`DELETE FROM ${this.tables.walletState}`);
275
320
  }
276
321
  // ── VTXO management ────────────────────────────────────────────────
277
- async getVtxos(address) {
278
- await this.ensureInit();
279
- const rows = await this.db.all(
280
- `SELECT * FROM ${this.tables.vtxos} WHERE address = ?`,
281
- [address]
282
- );
283
- return rows.map(vtxoRowToDomain);
322
+ async getVtxosPage(address, page) {
323
+ return this.pageByAddress(this.tables.vtxos, address, page, vtxoRowToDomain);
284
324
  }
285
325
  async saveVtxos(address, vtxos) {
286
326
  await this.ensureInit();
287
- for (const vtxo of vtxos) {
288
- const s = chunkPKD5VPYX_cjs.serializeVtxo(vtxo);
289
- await this.db.run(
290
- `INSERT OR REPLACE INTO ${this.tables.vtxos}
291
- (txid, vout, value, address,
292
- tap_tree, forfeit_cb, forfeit_s, intent_cb, intent_s,
293
- status_json, created_at, is_unrolled, is_spent,
294
- is_swept, is_preconfirmed, commitment_txids_json,
295
- expires_at, expires_at_height,
296
- spent_by, settled_by, ark_tx_id, extra_witness_json, assets_json, script)
297
- VALUES (?, ?, ?, ?,
298
- ?, ?, ?, ?, ?,
299
- ?, ?, ?, ?,
300
- ?, ?, ?,
301
- ?, ?,
302
- ?, ?, ?, ?, ?, ?)`,
303
- [
327
+ await this.insertOrReplace(
328
+ this.tables.vtxos,
329
+ `txid, vout, value, address,
330
+ tap_tree, forfeit_cb, forfeit_s, intent_cb, intent_s,
331
+ status_json, created_at, is_unrolled, is_spent,
332
+ is_swept, is_preconfirmed, commitment_txids_json,
333
+ expires_at, expires_at_height,
334
+ spent_by, settled_by, ark_tx_id, extra_witness_json, assets_json, script`,
335
+ vtxos.map((vtxo) => {
336
+ const s = chunkQKNSMVBW_cjs.serializeVtxo(vtxo);
337
+ return [
304
338
  s.txid,
305
339
  s.vout,
306
340
  s.value,
@@ -311,13 +345,13 @@ var SQLiteWalletRepository = class {
311
345
  s.intentTapLeafScript.cb,
312
346
  s.intentTapLeafScript.s,
313
347
  JSON.stringify(s.status),
314
- typeof s.createdAt === "string" ? s.createdAt : s.createdAt instanceof Date ? s.createdAt.toISOString() : new Date(s.createdAt).toISOString(),
348
+ chunkQKNSMVBW_cjs.createdAtToIso(s.createdAt),
315
349
  s.isUnrolled ? 1 : 0,
316
350
  s.isSpent === void 0 ? null : s.isSpent ? 1 : 0,
317
351
  s.isSwept === void 0 ? null : s.isSwept ? 1 : 0,
318
352
  s.isPreconfirmed === void 0 ? null : s.isPreconfirmed ? 1 : 0,
319
353
  s.commitmentTxIds ? JSON.stringify(s.commitmentTxIds) : null,
320
- s.expiresAt === void 0 ? null : s.expiresAt instanceof Date ? s.expiresAt.toISOString() : new Date(s.expiresAt).toISOString(),
354
+ s.expiresAt === void 0 ? null : new Date(s.expiresAt).toISOString(),
321
355
  s.expiresAtHeight ?? null,
322
356
  s.spentBy ?? null,
323
357
  s.settledBy ?? null,
@@ -325,61 +359,68 @@ var SQLiteWalletRepository = class {
325
359
  s.extraWitness ? JSON.stringify(s.extraWitness) : null,
326
360
  s.assets ? JSON.stringify(s.assets) : null,
327
361
  s.script ?? null
328
- ]
329
- );
330
- }
362
+ ];
363
+ })
364
+ );
331
365
  }
332
366
  async deleteVtxos(address) {
333
367
  await this.ensureInit();
334
368
  await this.db.run(`DELETE FROM ${this.tables.vtxos} WHERE address = ?`, [address]);
335
369
  }
336
- async getVtxosForScript(script) {
370
+ async getVtxosForScriptPage(script, page, options) {
371
+ chunkQKNSMVBW_cjs.assertPageRequest(page);
337
372
  await this.ensureInit();
373
+ const after = page.after;
338
374
  const rows = await this.db.all(
339
- `SELECT * FROM ${this.tables.vtxos} WHERE script = ?`,
340
- [script]
375
+ `SELECT * FROM ${this.tables.vtxos} WHERE script = ?
376
+ ${options?.unspentOnly ? `AND ${UNSPENT_VTXO_PREDICATE}` : ""}
377
+ ${after ? "AND (address > ? OR (address = ? AND (txid > ? OR (txid = ? AND vout > ?))))" : ""}
378
+ ORDER BY address, txid, vout LIMIT ?`,
379
+ after ? [
380
+ script,
381
+ after.address,
382
+ after.address,
383
+ after.txid,
384
+ after.txid,
385
+ after.vout,
386
+ page.limit + 1
387
+ ] : [script, page.limit + 1]
388
+ );
389
+ const result = chunkQKNSMVBW_cjs.pageResult(
390
+ rows.map((row) => ({ address: row.address, vtxo: vtxoRowToDomain(row) })),
391
+ page.limit,
392
+ (row) => ({ address: row.address, txid: row.vtxo.txid, vout: row.vtxo.vout })
341
393
  );
342
- return rows.map(vtxoRowToDomain);
394
+ return options?.unspentOnly ? { ...result, items: result.items.filter((row) => !chunkXLHUGP3G_cjs.isVtxoSpent(row.vtxo)) } : result;
343
395
  }
344
396
  async saveVtxosForScript(key, vtxos) {
345
- if (!key.address) {
346
- throw new Error("SQLiteWalletRepository requires an address");
347
- }
348
- for (const vtxo of vtxos) {
349
- if (!chunkPKD5VPYX_cjs.isVtxoForScript(vtxo, key.script)) {
350
- throw new Error(
351
- `VTXO ${vtxo.txid}:${vtxo.vout} script mismatch: expected ${key.script}, got ${vtxo.script}`
352
- );
353
- }
354
- }
355
- return this.saveVtxos(key.address, vtxos);
397
+ return this.saveVtxos(chunkQKNSMVBW_cjs.checkSaveVtxosForScript("SQLiteWalletRepository", key, vtxos), vtxos);
356
398
  }
357
399
  async deleteVtxosForScript(script) {
358
400
  await this.ensureInit();
359
401
  await this.db.run(`DELETE FROM ${this.tables.vtxos} WHERE script = ?`, [script]);
360
402
  }
361
403
  // ── UTXO management ────────────────────────────────────────────────
362
- async getUtxos(address) {
404
+ async getUtxosPage(address, page) {
405
+ return this.pageByAddress(this.tables.utxos, address, page, utxoRowToDomain);
406
+ }
407
+ async pageByAddress(table, address, page, deserialize) {
408
+ chunkQKNSMVBW_cjs.assertPageRequest(page);
363
409
  await this.ensureInit();
364
410
  const rows = await this.db.all(
365
- `SELECT * FROM ${this.tables.utxos} WHERE address = ?`,
366
- [address]
411
+ `SELECT * FROM ${table} WHERE address = ? AND (txid, vout) > (?, ?)
412
+ ORDER BY txid, vout LIMIT ?`,
413
+ [address, page.after?.txid ?? "", page.after?.vout ?? -1, page.limit + 1]
367
414
  );
368
- return rows.map(utxoRowToDomain);
415
+ const result = chunkQKNSMVBW_cjs.pageResult(rows, page.limit, ({ txid, vout }) => ({ txid, vout }));
416
+ return { items: result.items.map(deserialize), nextCursor: result.nextCursor };
369
417
  }
370
- async saveUtxos(address, utxos) {
418
+ async saveUtxos(addressOrBatch, utxos) {
371
419
  await this.ensureInit();
372
- for (const utxo of utxos) {
373
- const s = chunkPKD5VPYX_cjs.serializeUtxo(utxo);
374
- await this.db.run(
375
- `INSERT OR REPLACE INTO ${this.tables.utxos}
376
- (txid, vout, value, address,
377
- tap_tree, forfeit_cb, forfeit_s, intent_cb, intent_s,
378
- status_json, extra_witness_json)
379
- VALUES (?, ?, ?, ?,
380
- ?, ?, ?, ?, ?,
381
- ?, ?)`,
382
- [
420
+ const rows = chunkQKNSMVBW_cjs.utxoEntries(addressOrBatch, utxos).flatMap(
421
+ ([address, list]) => list.map((utxo) => {
422
+ const s = chunkQKNSMVBW_cjs.serializeUtxo(utxo);
423
+ return [
383
424
  s.txid,
384
425
  s.vout,
385
426
  s.value,
@@ -391,45 +432,71 @@ var SQLiteWalletRepository = class {
391
432
  s.intentTapLeafScript.s,
392
433
  JSON.stringify(s.status),
393
434
  s.extraWitness ? JSON.stringify(s.extraWitness) : null
394
- ]
395
- );
396
- }
435
+ ];
436
+ })
437
+ );
438
+ await this.insertOrReplace(
439
+ this.tables.utxos,
440
+ `txid, vout, value, address,
441
+ tap_tree, forfeit_cb, forfeit_s, intent_cb, intent_s,
442
+ status_json, extra_witness_json`,
443
+ rows
444
+ );
397
445
  }
398
446
  async deleteUtxos(address) {
399
447
  await this.ensureInit();
400
448
  await this.db.run(`DELETE FROM ${this.tables.utxos} WHERE address = ?`, [address]);
401
449
  }
402
450
  // ── Transaction history ────────────────────────────────────────────
403
- async getTransactionHistory(address) {
451
+ async getTransactionHistoryPage(filter, page) {
452
+ chunkQKNSMVBW_cjs.assertPageRequest(page);
453
+ chunkQKNSMVBW_cjs.assertHistoryPageFilter(filter);
404
454
  await this.ensureInit();
455
+ const conditions = ["address = ?"];
456
+ const params = [filter.address];
457
+ if (filter.since !== void 0) {
458
+ conditions.push("created_at >= ?");
459
+ params.push(filter.since);
460
+ }
461
+ if (page.after !== void 0) {
462
+ conditions.push(
463
+ "(created_at, boarding_txid, commitment_txid, ark_txid) > (?, ?, ?, ?)"
464
+ );
465
+ params.push(
466
+ page.after.createdAt,
467
+ page.after.key.boardingTxid,
468
+ page.after.key.commitmentTxid,
469
+ page.after.key.arkTxid
470
+ );
471
+ }
405
472
  const rows = await this.db.all(
406
- `SELECT * FROM ${this.tables.transactions} WHERE address = ? ORDER BY created_at ASC`,
407
- [address]
473
+ `SELECT * FROM ${this.tables.transactions} WHERE ${conditions.join(" AND ")}
474
+ ORDER BY created_at, boarding_txid, commitment_txid, ark_txid LIMIT ?`,
475
+ [...params, page.limit + 1]
408
476
  );
409
- return rows.map(txRowToDomain);
477
+ return chunkQKNSMVBW_cjs.pageResult(rows.map(txRowToDomain), page.limit, (tx) => ({
478
+ createdAt: tx.createdAt,
479
+ key: tx.key
480
+ }));
410
481
  }
411
482
  async saveTransactions(address, txs) {
412
483
  await this.ensureInit();
413
- for (const tx of txs) {
414
- await this.db.run(
415
- `INSERT OR REPLACE INTO ${this.tables.transactions}
416
- (address, boarding_txid, commitment_txid, ark_txid,
417
- type, amount, settled, created_at, assets_json)
418
- VALUES (?, ?, ?, ?,
419
- ?, ?, ?, ?, ?)`,
420
- [
421
- address,
422
- tx.key.boardingTxid,
423
- tx.key.commitmentTxid,
424
- tx.key.arkTxid,
425
- tx.type,
426
- tx.amount,
427
- tx.settled ? 1 : 0,
428
- tx.createdAt,
429
- tx.assets ? JSON.stringify(chunkPKD5VPYX_cjs.serializeAssets(tx.assets)) : null
430
- ]
431
- );
432
- }
484
+ await this.insertOrReplace(
485
+ this.tables.transactions,
486
+ `address, boarding_txid, commitment_txid, ark_txid,
487
+ type, amount, settled, created_at, assets_json`,
488
+ txs.map((tx) => [
489
+ address,
490
+ tx.key.boardingTxid,
491
+ tx.key.commitmentTxid,
492
+ tx.key.arkTxid,
493
+ tx.type,
494
+ tx.amount,
495
+ tx.settled ? 1 : 0,
496
+ tx.createdAt,
497
+ tx.assets ? JSON.stringify(chunkQKNSMVBW_cjs.serializeAssets(tx.assets)) : null
498
+ ])
499
+ );
433
500
  }
434
501
  async deleteTransactions(address) {
435
502
  await this.ensureInit();
@@ -464,15 +531,7 @@ var SQLiteWalletRepository = class {
464
531
  );
465
532
  }
466
533
  };
467
- var SAFE_PREFIX = /^[a-zA-Z0-9_]+$/;
468
- function sanitizePrefix(prefix) {
469
- if (!SAFE_PREFIX.test(prefix)) {
470
- throw new Error(
471
- `Invalid table prefix "${prefix}": only letters, digits, and underscores are allowed`
472
- );
473
- }
474
- return prefix;
475
- }
534
+ var UNSPENT_VTXO_PREDICATE = "(is_spent IS NULL OR is_spent = 0) AND (spent_by IS NULL OR spent_by = '') AND (settled_by IS NULL OR settled_by = '')";
476
535
  function vtxoRowToDomain(row) {
477
536
  const serialized = {
478
537
  txid: row.txid,
@@ -504,9 +563,9 @@ function vtxoRowToDomain(row) {
504
563
  // Post-migration every row has `script`, but the backfill is
505
564
  // idempotent: derive from `address` if the legacy column is still
506
565
  // null (e.g. the migration hasn't run yet on this handle).
507
- script: row.script ?? chunkPKD5VPYX_cjs.scriptFromArkAddress(row.address)
566
+ script: row.script ?? chunkQKNSMVBW_cjs.scriptFromArkAddress(row.address)
508
567
  };
509
- return chunkPKD5VPYX_cjs.deserializeVtxo(serialized);
568
+ return chunkQKNSMVBW_cjs.deserializeVtxo(serialized);
510
569
  }
511
570
  function utxoRowToDomain(row) {
512
571
  const serialized = {
@@ -525,7 +584,7 @@ function utxoRowToDomain(row) {
525
584
  status: JSON.parse(row.status_json),
526
585
  extraWitness: row.extra_witness_json ? JSON.parse(row.extra_witness_json) : void 0
527
586
  };
528
- return chunkPKD5VPYX_cjs.deserializeUtxo(serialized);
587
+ return chunkQKNSMVBW_cjs.deserializeUtxo(serialized);
529
588
  }
530
589
  function txRowToDomain(row) {
531
590
  const tx = {
@@ -540,20 +599,21 @@ function txRowToDomain(row) {
540
599
  createdAt: row.created_at
541
600
  };
542
601
  if (row.assets_json) {
543
- tx.assets = chunkPKD5VPYX_cjs.deserializeAssets(JSON.parse(row.assets_json));
602
+ tx.assets = chunkQKNSMVBW_cjs.deserializeAssets(JSON.parse(row.assets_json));
544
603
  }
545
604
  return tx;
546
605
  }
547
606
 
548
607
  // src/repositories/sqlite/contractRepository.ts
608
+ var WATCH_STATE = "IFNULL(watch, 'watched')";
549
609
  var SQLiteContractRepository = class {
550
610
  constructor(db, options) {
551
611
  this.db = db;
552
- this.prefix = sanitizePrefix2(options?.prefix ?? "ark_");
612
+ this.prefix = sanitizeTablePrefix(options?.prefix ?? "ark_");
553
613
  this.table = `${this.prefix}contracts`;
554
614
  }
555
615
  db;
556
- version = 2;
616
+ version = 3;
557
617
  initPromise = null;
558
618
  prefix;
559
619
  table;
@@ -586,6 +646,12 @@ var SQLiteContractRepository = class {
586
646
  await this.db.run(
587
647
  `CREATE INDEX IF NOT EXISTS idx_${this.prefix}contracts_state ON ${this.table} (state)`
588
648
  );
649
+ await this.db.run(
650
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}contracts_watch_state ON ${this.table} (${WATCH_STATE}, script)`
651
+ );
652
+ await this.db.run(
653
+ `CREATE INDEX IF NOT EXISTS idx_${this.prefix}contracts_type_watch_state ON ${this.table} (type, ${WATCH_STATE}, script)`
654
+ );
589
655
  }
590
656
  async addColumnIfMissing(column, type) {
591
657
  const columns = await this.db.all(`PRAGMA table_info(${this.table})`);
@@ -600,22 +666,28 @@ var SQLiteContractRepository = class {
600
666
  await this.db.run(`DELETE FROM ${this.table}`);
601
667
  }
602
668
  // ── Contract management ────────────────────────────────────────────
603
- async getContracts(filter) {
669
+ async getContractsPage(filter, page) {
670
+ chunkQKNSMVBW_cjs.assertPageRequest(page);
604
671
  await this.ensureInit();
672
+ if (chunkQKNSMVBW_cjs.contractFilterMatchesNothing(filter)) return { items: [] };
605
673
  const conditions = [];
606
674
  const params = [];
607
675
  if (filter) {
608
676
  this.addFilterCondition(conditions, params, "script", filter.script);
609
677
  this.addFilterCondition(conditions, params, "state", filter.state);
610
678
  this.addFilterCondition(conditions, params, "type", filter.type);
611
- this.addWatchCondition(conditions, params, filter.watch);
679
+ this.addFilterCondition(conditions, params, WATCH_STATE, filter.watch);
612
680
  }
613
- let sql = `SELECT * FROM ${this.table}`;
614
- if (conditions.length > 0) {
615
- sql += ` WHERE ${conditions.join(" AND ")}`;
681
+ if (page.after !== void 0) {
682
+ conditions.push("script > ?");
683
+ params.push(page.after);
616
684
  }
617
- const rows = await this.db.all(sql, params);
618
- return rows.map(contractRowToDomain);
685
+ const where = conditions.length ? ` WHERE ${conditions.join(" AND ")}` : "";
686
+ const rows = await this.db.all(
687
+ `SELECT * FROM ${this.table}${where} ORDER BY script LIMIT ?`,
688
+ [...params, page.limit + 1]
689
+ );
690
+ return chunkQKNSMVBW_cjs.pageResult(rows.map(contractRowToDomain), page.limit, (contract) => contract.script);
619
691
  }
620
692
  async saveContract(contract) {
621
693
  await this.ensureInit();
@@ -655,29 +727,7 @@ var SQLiteContractRepository = class {
655
727
  params.push(value);
656
728
  }
657
729
  }
658
- /**
659
- * Same as {@link addFilterCondition}, except a row predating the
660
- * column stores NULL and must match `"watched"`.
661
- */
662
- addWatchCondition(conditions, params, value) {
663
- if (value === void 0) return;
664
- const wanted = Array.isArray(value) ? value : [value];
665
- if (wanted.length === 0) return;
666
- const placeholders = wanted.map(() => "?").join(", ");
667
- const clause = `watch IN (${placeholders})`;
668
- conditions.push(wanted.includes("watched") ? `(${clause} OR watch IS NULL)` : clause);
669
- params.push(...wanted);
670
- }
671
730
  };
672
- var SAFE_PREFIX2 = /^[a-zA-Z0-9_]+$/;
673
- function sanitizePrefix2(prefix) {
674
- if (!SAFE_PREFIX2.test(prefix)) {
675
- throw new Error(
676
- `Invalid table prefix "${prefix}": only letters, digits, and underscores are allowed`
677
- );
678
- }
679
- return prefix;
680
- }
681
731
  function contractRowToDomain(row) {
682
732
  const contract = {
683
733
  script: row.script,
@@ -700,15 +750,10 @@ function contractRowToDomain(row) {
700
750
  }
701
751
 
702
752
  // src/repositories/sqlite/intentRepository.ts
703
- var SAFE_PREFIX3 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
704
- function sanitizePrefix3(p) {
705
- if (!SAFE_PREFIX3.test(p)) throw new Error(`Invalid table prefix "${p}"`);
706
- return p;
707
- }
708
753
  var SQLiteIntentRepository = class {
709
754
  constructor(db, options) {
710
755
  this.db = db;
711
- this.prefix = sanitizePrefix3(options?.prefix ?? "ark_");
756
+ this.prefix = sanitizeTablePrefix(options?.prefix ?? "ark_");
712
757
  this.t = `${this.prefix}intents`;
713
758
  }
714
759
  db;
@@ -747,19 +792,16 @@ var SQLiteIntentRepository = class {
747
792
  `CREATE INDEX IF NOT EXISTS idx_${this.prefix}intent_state ON ${this.t} (state)`
748
793
  );
749
794
  }
750
- withTx(fn) {
751
- return runInTransaction(this.db, fn);
752
- }
753
795
  async clear() {
754
796
  await this.ensureInit();
755
- await this.withTx(async () => {
797
+ await runInTransaction(this.db, async () => {
756
798
  await this.db.run(`DELETE FROM ${this.t}`);
757
799
  });
758
800
  }
759
801
  async saveIntent(intent) {
760
802
  await this.ensureInit();
761
803
  const now = Date.now();
762
- await this.withTx(async () => {
804
+ await runInTransaction(this.db, async () => {
763
805
  await this.db.run(
764
806
  `INSERT INTO ${this.t}
765
807
  (intent_tx_id, intent_id, state, valid_from, valid_until,
@@ -807,22 +849,37 @@ var SQLiteIntentRepository = class {
807
849
  );
808
850
  });
809
851
  }
810
- async getIntents(filter) {
852
+ async getIntentsPage(filter, page) {
853
+ chunkQKNSMVBW_cjs.assertPageRequest(page);
811
854
  await this.ensureInit();
812
- const rows = await this.db.all(
813
- `SELECT * FROM ${this.t} ORDER BY created_at ASC, intent_tx_id ASC`
814
- );
815
- let out = rows.map(rowToIntent);
816
- if (filter) out = out.filter((i) => chunkPKD5VPYX_cjs.intentMatchesFilter(i, filter));
817
- const { skip, end } = chunkPKD5VPYX_cjs.intentPageBounds(filter, out.length);
818
- return out.slice(skip, end);
855
+ if (filter?.states?.length === 0) return { items: [] };
856
+ const stateSql = filter?.states ? ` AND state IN (${filter.states.map(() => "?").join(", ")})` : "";
857
+ const stateParams = filter?.states ?? [];
858
+ const batchSize = Math.max(64, page.limit + 1);
859
+ const rows = [];
860
+ let after = page.after;
861
+ while (rows.length <= page.limit) {
862
+ const batch = await this.db.all(
863
+ `SELECT * FROM ${this.t} WHERE intent_tx_id > ?${stateSql} ORDER BY intent_tx_id LIMIT ?`,
864
+ [after ?? "", ...stateParams, batchSize]
865
+ );
866
+ if (batch.length === 0) break;
867
+ for (const row of batch) {
868
+ after = row.intent_tx_id;
869
+ const intent = rowToIntent(row);
870
+ if (!filter || chunkQKNSMVBW_cjs.intentMatchesFilter(intent, filter)) rows.push(intent);
871
+ if (rows.length > page.limit) break;
872
+ }
873
+ if (batch.length < batchSize) break;
874
+ }
875
+ return chunkQKNSMVBW_cjs.pageResult(rows, page.limit, (intent) => intent.intentTxId);
819
876
  }
820
877
  async getLockedVtxoOutpoints() {
821
878
  await this.ensureInit();
822
879
  const rows = await this.db.all(`SELECT * FROM ${this.t}`);
823
880
  const out = [];
824
881
  for (const r of rows) {
825
- if (chunkPKD5VPYX_cjs.isTerminalIntentState(r.state)) continue;
882
+ if (chunkQKNSMVBW_cjs.isTerminalIntentState(r.state)) continue;
826
883
  for (const o of JSON.parse(r.intent_vtxos_json)) out.push(o);
827
884
  }
828
885
  return out;
@@ -853,15 +910,10 @@ function rowToIntent(r) {
853
910
  }
854
911
 
855
912
  // src/repositories/sqlite/virtualTxRepository.ts
856
- var SAFE_PREFIX4 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
857
- function sanitizePrefix4(p) {
858
- if (!SAFE_PREFIX4.test(p)) throw new Error(`Invalid table prefix "${p}"`);
859
- return p;
860
- }
861
913
  var SQLiteVirtualTxRepository = class {
862
914
  constructor(db, options) {
863
915
  this.db = db;
864
- this.prefix = sanitizePrefix4(options?.prefix ?? "ark_");
916
+ this.prefix = sanitizeTablePrefix(options?.prefix ?? "ark_");
865
917
  this.tTx = `${this.prefix}virtual_txs`;
866
918
  this.tBranch = `${this.prefix}vtxo_branches`;
867
919
  }
@@ -893,12 +945,9 @@ var SQLiteVirtualTxRepository = class {
893
945
  `CREATE INDEX IF NOT EXISTS idx_${this.prefix}branch_vtxid ON ${this.tBranch} (virtual_txid)`
894
946
  );
895
947
  }
896
- tx(fn) {
897
- return runInTransaction(this.db, fn);
898
- }
899
948
  async clear() {
900
949
  await this.ensureInit();
901
- await this.tx(async () => {
950
+ await runInTransaction(this.db, async () => {
902
951
  await this.db.run(`DELETE FROM ${this.tTx}`);
903
952
  await this.db.run(`DELETE FROM ${this.tBranch}`);
904
953
  });
@@ -906,7 +955,7 @@ var SQLiteVirtualTxRepository = class {
906
955
  async upsertVirtualTxs(txs) {
907
956
  if (txs.length === 0) return;
908
957
  await this.ensureInit();
909
- await this.tx(async () => {
958
+ await runInTransaction(this.db, async () => {
910
959
  const placeholders = txs.map(() => "?").join(", ");
911
960
  const existing = await this.db.all(
912
961
  `SELECT * FROM ${this.tTx} WHERE txid IN (${placeholders})`,
@@ -917,7 +966,7 @@ var SQLiteVirtualTxRepository = class {
917
966
  const prev = prevByTxid.get(t.txid);
918
967
  const psbt = t.psbt ?? prev?.psbt ?? null;
919
968
  const expires = t.expiresAt ?? prev?.expires_at ?? null;
920
- const type = chunkPKD5VPYX_cjs.mergeChainedTxType(t.type, prev?.type);
969
+ const type = chunkQKNSMVBW_cjs.mergeChainedTxType(t.type, prev?.type);
921
970
  await this.db.run(
922
971
  `INSERT OR REPLACE INTO ${this.tTx} (txid, psbt, expires_at, type) VALUES (?, ?, ?, ?)`,
923
972
  [t.txid, psbt, expires, type]
@@ -933,7 +982,7 @@ var SQLiteVirtualTxRepository = class {
933
982
  }
934
983
  async setBranch(vtxo, branch) {
935
984
  await this.ensureInit();
936
- await this.tx(async () => {
985
+ await runInTransaction(this.db, async () => {
937
986
  await this.db.run(`DELETE FROM ${this.tBranch} WHERE vtxo_txid = ? AND vtxo_vout = ?`, [
938
987
  vtxo.txid,
939
988
  vtxo.vout
@@ -968,7 +1017,7 @@ var SQLiteVirtualTxRepository = class {
968
1017
  }
969
1018
  async pruneForSpentVtxo(vtxo) {
970
1019
  await this.ensureInit();
971
- await this.tx(async () => {
1020
+ await runInTransaction(this.db, async () => {
972
1021
  const removed = await this.db.all(
973
1022
  `SELECT DISTINCT virtual_txid FROM ${this.tBranch} WHERE vtxo_txid = ? AND vtxo_vout = ?`,
974
1023
  [vtxo.txid, vtxo.vout]
@@ -1001,16 +1050,9 @@ function rowToTx(r) {
1001
1050
  };
1002
1051
  }
1003
1052
 
1004
- // src/repositories/sqlite/prefix.ts
1005
- var SAFE_PREFIX5 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
1006
- function sanitizeTablePrefix(p) {
1007
- if (!SAFE_PREFIX5.test(p)) throw new Error(`Invalid table prefix "${p}"`);
1008
- return p;
1009
- }
1010
-
1011
1053
  Object.defineProperty(exports, "ChainedTxType", {
1012
1054
  enumerable: true,
1013
- get: function () { return chunkPKD5VPYX_cjs.ChainedTxType; }
1055
+ get: function () { return chunkQKNSMVBW_cjs.ChainedTxType; }
1014
1056
  });
1015
1057
  exports.SQLiteContractRepository = SQLiteContractRepository;
1016
1058
  exports.SQLiteIntentRepository = SQLiteIntentRepository;