@hyperbridge/simplex 0.18.1 → 0.18.2

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/dist/sqlite.cjs CHANGED
@@ -479,18 +479,31 @@ var BID_COLUMNS = `
479
479
  retracted_at as retractedAt,
480
480
  retract_extrinsic_hash as retractExtrinsicHash,
481
481
  dead,
482
- bid,
482
+ on_orderbook as onOrderbook,
483
+ priced_by as pricedBy,
483
484
  reservations
484
485
  `;
485
- function parseHolds(raw) {
486
- if (!raw) return [];
486
+ function parseJson(raw) {
487
+ if (!raw) return null;
487
488
  try {
488
- const parsed = JSON.parse(raw);
489
- return Array.isArray(parsed) ? parsed : [];
489
+ return JSON.parse(raw);
490
490
  } catch {
491
- return [];
491
+ return null;
492
492
  }
493
493
  }
494
+ function pricedBy(row) {
495
+ const current = parseJson(row.pricedBy);
496
+ if (current && typeof current === "object") return current;
497
+ const legacy = parseJson(row.reservations);
498
+ const first = Array.isArray(legacy) ? legacy[0] : null;
499
+ if (!first || typeof first !== "object" || typeof first.limitOrderId !== "string") return null;
500
+ return {
501
+ limitOrderId: first.limitOrderId,
502
+ payout: String(first.payout ?? first.amount),
503
+ ...first.take !== void 0 ? { take: String(first.take) } : {},
504
+ ...first.takeDecimals !== void 0 ? { takeDecimals: Number(first.takeDecimals) } : {}
505
+ };
506
+ }
494
507
  var SqliteBidStore = class {
495
508
  constructor(db, loggers = defaultLoggerContext()) {
496
509
  this.db = db;
@@ -513,7 +526,11 @@ var SqliteBidStore = class {
513
526
  retracted_at TEXT,
514
527
  retract_extrinsic_hash TEXT,
515
528
  dead INTEGER NOT NULL DEFAULT 0,
516
- reservations TEXT
529
+ on_orderbook INTEGER NOT NULL DEFAULT 0,
530
+ -- Only read, as the provenance of a bid written before priced_by. Kept, and
531
+ -- never written, so a rollback to a build that still reads it finds the column.
532
+ reservations TEXT,
533
+ priced_by TEXT
517
534
  );
518
535
 
519
536
  CREATE INDEX IF NOT EXISTS idx_bids_commitment ON bids(commitment);
@@ -522,12 +539,12 @@ var SqliteBidStore = class {
522
539
  CREATE INDEX IF NOT EXISTS idx_bids_created_at ON bids(created_at);
523
540
  `);
524
541
  const columns = columnNames(this.db, "bids");
525
- for (const column of ["dead", "pending"]) {
542
+ for (const column of ["dead", "pending", "on_orderbook"]) {
526
543
  if (columns.has(column)) continue;
527
544
  this.db.exec(`ALTER TABLE bids ADD COLUMN ${column} INTEGER NOT NULL DEFAULT 0`);
528
545
  this.logger.info({ column }, "Migrated bid storage schema");
529
546
  }
530
- for (const column of ["bid", "reservations"]) {
547
+ for (const column of ["bid", "reservations", "priced_by"]) {
531
548
  if (columns.has(column)) continue;
532
549
  this.db.exec(`ALTER TABLE bids ADD COLUMN ${column} TEXT`);
533
550
  this.logger.info({ column }, "Migrated bid storage schema");
@@ -536,19 +553,21 @@ var SqliteBidStore = class {
536
553
  }
537
554
  // biome-ignore lint/suspicious/noExplicitAny: raw sqlite row
538
555
  toStoredBid(row) {
556
+ const { reservations: _legacy, ...rest } = row;
539
557
  return {
540
- ...row,
558
+ ...rest,
541
559
  success: Boolean(row.success),
542
560
  pending: Boolean(row.pending),
543
561
  retracted: Boolean(row.retracted),
544
562
  dead: Boolean(row.dead),
545
- reservations: parseHolds(row.reservations)
563
+ onOrderbook: Boolean(row.onOrderbook),
564
+ pricedBy: pricedBy(row)
546
565
  };
547
566
  }
548
567
  async store(bid) {
549
568
  const result = this.db.prepare(`
550
- INSERT INTO bids (commitment, bid, extrinsic_hash, block_hash, success, pending, error, reservations)
551
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
569
+ INSERT INTO bids (commitment, bid, extrinsic_hash, block_hash, success, pending, error, on_orderbook, priced_by)
570
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
552
571
  `).run(
553
572
  bid.commitment,
554
573
  bid.bid ?? null,
@@ -557,7 +576,8 @@ var SqliteBidStore = class {
557
576
  bid.success ? 1 : 0,
558
577
  bid.pending ? 1 : 0,
559
578
  bid.error || null,
560
- bid.reservations?.length ? JSON.stringify(bid.reservations) : null
579
+ bid.onOrderbook ? 1 : 0,
580
+ bid.pricedBy ? JSON.stringify(bid.pricedBy) : null
561
581
  );
562
582
  this.logger.debug({ id: result.lastInsertRowid, commitment: bid.commitment, success: bid.success }, "Bid stored");
563
583
  }
@@ -583,37 +603,33 @@ var SqliteBidStore = class {
583
603
  const rows = this.db.prepare(`SELECT ${BID_COLUMNS} FROM bids WHERE created_at BETWEEN ? AND ? ORDER BY created_at DESC`).all(sqliteDatetime(from), sqliteDatetime(to));
584
604
  return rows.map((row) => this.toStoredBid(row));
585
605
  }
586
- async markRetracted(commitment, retractExtrinsicHash) {
606
+ async markRetracted(commitment, retractExtrinsicHash, rows) {
607
+ if (rows?.length === 0) return false;
587
608
  const result = this.db.prepare(`
588
609
  UPDATE bids
589
610
  SET retracted = 1, retracted_at = datetime('now'), retract_extrinsic_hash = ?
590
- WHERE commitment = ? AND retracted = 0
591
- `).run(retractExtrinsicHash, commitment);
611
+ WHERE commitment = ? AND retracted = 0${rows ? ` AND id IN (${rows.map(() => "?").join(",")})` : ""}
612
+ `).run(retractExtrinsicHash, commitment, ...rows ?? []);
592
613
  if (result.changes > 0) {
593
614
  this.logger.info({ commitment, retractExtrinsicHash }, "Bid marked as retracted");
594
615
  return true;
595
616
  }
596
617
  return false;
597
618
  }
598
- async claimReservation(commitment, bid) {
599
- const rows = bid === void 0 ? this.db.prepare("SELECT id, reservations FROM bids WHERE commitment = ? AND reservations IS NOT NULL ORDER BY id").all(commitment) : this.db.prepare(
600
- "SELECT id, reservations FROM bids WHERE commitment = ? AND bid = ? AND reservations IS NOT NULL ORDER BY id"
601
- ).all(commitment, bid);
602
- const claimed = [];
603
- for (const row of rows) {
604
- const result = this.db.prepare("UPDATE bids SET reservations = NULL WHERE id = ? AND reservations = ?").run(row.id, row.reservations);
605
- if (result.changes === 1) claimed.push(...parseHolds(row.reservations));
606
- }
607
- return claimed;
608
- }
609
619
  async byLimitOrder(limitOrderId, limit = 100) {
610
- const rows = this.db.prepare(`SELECT ${BID_COLUMNS} FROM bids WHERE reservations LIKE ? ORDER BY id DESC LIMIT ?`).all(`%${limitOrderId}%`, Math.min(Math.max(limit, 1), 500));
611
- return rows.map((row) => this.toStoredBid(row)).filter((bid) => bid.reservations.some((hold) => hold.limitOrderId === limitOrderId));
620
+ const pattern = `%${limitOrderId}%`;
621
+ const rows = this.db.prepare(
622
+ `SELECT ${BID_COLUMNS} FROM bids WHERE priced_by LIKE ? OR (priced_by IS NULL AND reservations LIKE ?) ORDER BY id DESC LIMIT ?`
623
+ ).all(pattern, pattern, Math.min(Math.max(limit, 1), 500));
624
+ return rows.map((row) => this.toStoredBid(row)).filter((bid) => bid.pricedBy?.limitOrderId === limitOrderId);
612
625
  }
613
- async markDead(commitment) {
614
- const result = this.db.prepare("UPDATE bids SET dead = 1 WHERE commitment = ? AND retracted = 0 AND dead = 0").run(commitment);
626
+ async markDead(commitment, rows) {
627
+ if (rows?.length === 0) return false;
628
+ const result = this.db.prepare(
629
+ `UPDATE bids SET dead = 1 WHERE commitment = ? AND retracted = 0 AND dead = 0${rows ? ` AND id IN (${rows.map(() => "?").join(",")})` : ""}`
630
+ ).run(commitment, ...rows ?? []);
615
631
  if (result.changes > 0) {
616
- this.logger.debug({ commitment }, "Bid marked as dead (order filled on-chain)");
632
+ this.logger.debug({ commitment }, "Bid marked dead, due for retraction on the next sweep");
617
633
  return true;
618
634
  }
619
635
  return false;
@@ -622,8 +638,8 @@ var SqliteBidStore = class {
622
638
  const stats = this.db.prepare(`
623
639
  SELECT
624
640
  COUNT(*) as total,
625
- SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful,
626
- SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed,
641
+ SUM(CASE WHEN success = 1 OR on_orderbook = 1 THEN 1 ELSE 0 END) as successful,
642
+ SUM(CASE WHEN success = 0 AND on_orderbook = 0 THEN 1 ELSE 0 END) as failed,
627
643
  SUM(CASE WHEN retracted = 1 THEN 1 ELSE 0 END) as retracted,
628
644
  SUM(CASE WHEN (success = 1 OR pending = 1) AND retracted = 0 THEN 1 ELSE 0 END) as pendingRetraction
629
645
  FROM bids
@@ -637,7 +653,7 @@ var SqliteBidStore = class {
637
653
  };
638
654
  }
639
655
  async failed(limit = 100) {
640
- const rows = this.db.prepare(`SELECT ${BID_COLUMNS} FROM bids WHERE success = 0 ORDER BY created_at DESC LIMIT ?`).all(limit);
656
+ const rows = this.db.prepare(`SELECT ${BID_COLUMNS} FROM bids WHERE success = 0 AND on_orderbook = 0 ORDER BY created_at DESC LIMIT ?`).all(limit);
641
657
  return rows.map((row) => this.toStoredBid(row));
642
658
  }
643
659
  async byCommitments(commitments) {
@@ -697,7 +713,6 @@ var LIMIT_ORDER_COLUMNS = `
697
713
  price,
698
714
  size,
699
715
  remaining,
700
- reserved,
701
716
  accepted_sources as acceptedSources,
702
717
  ttl_secs as ttlSecs,
703
718
  expires_at as expiresAt,
@@ -711,11 +726,13 @@ var LIMIT_ORDER_COLUMNS = `
711
726
  created_at as createdAt,
712
727
  updated_at as updatedAt
713
728
  `;
714
- var LimitOrderWriteError = class extends Error {
715
- };
716
729
  function statusGuard(only) {
717
730
  return only && only.length > 0 ? ` AND status IN (${only.map(() => "?").join(", ")})` : "";
718
731
  }
732
+ function clampable(only) {
733
+ const live = ["open", "resizing"];
734
+ return only && only.length > 0 ? live.filter((status) => only.includes(status)) : live;
735
+ }
719
736
  var SqliteLimitOrderStore = class {
720
737
  constructor(db, loggers = defaultLoggerContext()) {
721
738
  this.db = db;
@@ -734,6 +751,9 @@ var SqliteLimitOrderStore = class {
734
751
  price TEXT NOT NULL,
735
752
  size TEXT NOT NULL,
736
753
  remaining TEXT NOT NULL,
754
+ -- Never read or written: limit orders keep no local holds. Kept, and reset to
755
+ -- '0' on every start, so a rollback to a build that still reads it finds the
756
+ -- column and no stale hold.
737
757
  reserved TEXT NOT NULL DEFAULT '0',
738
758
  accepted_sources TEXT NOT NULL,
739
759
  ttl_secs INTEGER NOT NULL,
@@ -761,11 +781,15 @@ var SqliteLimitOrderStore = class {
761
781
  amount TEXT NOT NULL,
762
782
  amount_in TEXT,
763
783
  transaction_hash TEXT,
784
+ log_index INTEGER,
764
785
  filled_at TEXT NOT NULL DEFAULT (datetime('now'))
765
786
  );
766
787
 
767
788
  CREATE INDEX IF NOT EXISTS idx_limit_order_fills_order ON limit_order_fills(limit_order_id);
768
789
  `);
790
+ if (columnNames(this.db, "limit_orders").has("reserved") && this.db.prepare("SELECT 1 FROM limit_orders WHERE reserved <> '0' LIMIT 1").get()) {
791
+ this.db.exec("UPDATE limit_orders SET reserved = '0' WHERE reserved <> '0'");
792
+ }
769
793
  if (!columnNames(this.db, "limit_orders").has("entry_point")) {
770
794
  this.db.exec("ALTER TABLE limit_orders ADD COLUMN entry_point TEXT");
771
795
  this.logger.info({ column: "entry_point" }, "Migrated limit order storage schema");
@@ -774,6 +798,10 @@ var SqliteLimitOrderStore = class {
774
798
  this.db.exec("ALTER TABLE limit_order_fills ADD COLUMN amount_in TEXT");
775
799
  this.logger.info({ column: "amount_in" }, "Migrated limit order fill schema");
776
800
  }
801
+ if (!columnNames(this.db, "limit_order_fills").has("log_index")) {
802
+ this.db.exec("ALTER TABLE limit_order_fills ADD COLUMN log_index INTEGER");
803
+ this.logger.info({ column: "log_index" }, "Migrated limit order fill schema");
804
+ }
777
805
  }
778
806
  // biome-ignore lint/suspicious/noExplicitAny: raw sqlite row
779
807
  toLimitOrder(row) {
@@ -855,43 +883,17 @@ var SqliteLimitOrderStore = class {
855
883
  ).run(status, lastError, id, ...only ?? []);
856
884
  return result.changes === 1 ? this.read(id) : null;
857
885
  }
858
- async reserve(id, amount) {
859
- const order = this.read(id);
860
- if (!order || order.status !== "open") return false;
861
- if (BigInt(amount) > BigInt(order.remaining)) return false;
862
- const reserved = BigInt(order.reserved) + BigInt(amount);
863
- const result = this.db.prepare(`
864
- UPDATE limit_orders SET reserved = ?, updated_at = datetime('now')
865
- WHERE id = ? AND reserved = ? AND status = 'open'
866
- `).run(reserved.toString(), id, order.reserved);
867
- return result.changes === 1;
868
- }
869
- async transaction(settle) {
870
- this.db.exec("BEGIN");
871
- try {
872
- const result = await settle();
873
- this.db.exec("COMMIT");
874
- return result;
875
- } catch (err) {
876
- if (this.db.isTransaction !== false) {
877
- try {
878
- this.db.exec("ROLLBACK");
879
- } catch {
880
- }
881
- }
882
- throw err;
883
- }
884
- }
885
886
  async recordFill(fill) {
886
887
  this.db.prepare(
887
- "INSERT INTO limit_order_fills (limit_order_id, commitment, bid, amount, amount_in, transaction_hash) VALUES (?, ?, ?, ?, ?, ?)"
888
+ "INSERT INTO limit_order_fills (limit_order_id, commitment, bid, amount, amount_in, transaction_hash, log_index) VALUES (?, ?, ?, ?, ?, ?, ?)"
888
889
  ).run(
889
890
  fill.limitOrderId,
890
891
  fill.commitment,
891
892
  fill.bid ?? null,
892
893
  fill.amount,
893
894
  fill.amountIn ?? null,
894
- fill.transactionHash ?? null
895
+ fill.transactionHash ?? null,
896
+ fill.logIndex ?? null
895
897
  );
896
898
  }
897
899
  async fillHistory() {
@@ -904,38 +906,23 @@ var SqliteLimitOrderStore = class {
904
906
  }
905
907
  async fills(limitOrderId, limit = 100) {
906
908
  return this.db.prepare(
907
- `SELECT id, limit_order_id as limitOrderId, commitment, bid, amount, amount_in as amountIn, transaction_hash as transactionHash,
909
+ `SELECT id, limit_order_id as limitOrderId, commitment, bid, amount, amount_in as amountIn, transaction_hash as transactionHash, log_index as logIndex,
908
910
  filled_at as filledAt
909
911
  FROM limit_order_fills WHERE limit_order_id = ? ORDER BY id DESC LIMIT ?`
910
912
  ).all(limitOrderId, Math.min(Math.max(limit, 1), 500));
911
913
  }
912
- async drawDown(id, amount) {
913
- const order = this.read(id);
914
- if (!order) return null;
915
- const remaining = BigInt(order.remaining) - BigInt(amount);
916
- const result = this.db.prepare("UPDATE limit_orders SET remaining = ?, updated_at = datetime('now') WHERE id = ? AND remaining = ?").run((remaining > 0n ? remaining : 0n).toString(), id, order.remaining);
917
- if (result.changes !== 1) throw new LimitOrderWriteError(`Another writer moved limit order '${id}' mid draw-down`);
918
- return this.read(id);
919
- }
920
- async clampRemaining(id, room) {
914
+ async clampRemaining(id, room, only) {
915
+ const statuses = clampable(only);
921
916
  const order = this.read(id);
922
- if (!order || order.status !== "open") return null;
923
- const sum = BigInt(room) + BigInt(order.reserved);
924
- const target = sum > 0n ? sum : 0n;
917
+ if (!order || !statuses.includes(order.status)) return null;
918
+ const target = BigInt(room) > 0n ? BigInt(room) : 0n;
925
919
  if (target >= BigInt(order.remaining)) return null;
926
920
  const result = this.db.prepare(`
927
921
  UPDATE limit_orders SET remaining = ?, updated_at = datetime('now')
928
- WHERE id = ? AND remaining = ? AND reserved = ? AND status = 'open'
929
- `).run(target.toString(), id, order.remaining, order.reserved);
922
+ WHERE id = ? AND remaining = ? AND status IN (${statuses.map(() => "?").join(", ")})
923
+ `).run(target.toString(), id, order.remaining, ...statuses);
930
924
  return result.changes === 1 ? this.read(id) : null;
931
925
  }
932
- async release(id, amount) {
933
- const order = this.read(id);
934
- if (!order) return;
935
- const reserved = BigInt(order.reserved) - BigInt(amount);
936
- const result = this.db.prepare("UPDATE limit_orders SET reserved = ?, updated_at = datetime('now') WHERE id = ? AND reserved = ?").run((reserved > 0n ? reserved : 0n).toString(), id, order.reserved);
937
- if (result.changes !== 1) throw new LimitOrderWriteError(`Another writer moved limit order '${id}' mid release`);
938
- }
939
926
  };
940
927
 
941
928
  // src/data/sqlite/state.ts