@pafi-dev/issuer-postgres 0.1.1 → 0.2.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.
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { DataSource } from 'typeorm';
2
2
  import { Address, Hex } from 'viem';
3
- import { IPointLedger, LockedMintRequest, PendingCredit, MintingStatus, IIndexerCursorStore } from '@pafi-dev/issuer';
3
+ import { IPointLedger, LockedMintRequest, PendingCredit, MintingStatus, IIndexerCursorStore, IRedemptionHistoryStore } from '@pafi-dev/issuer';
4
4
  import { LockedMintEntity, PendingCreditEntity } from './entities/index.cjs';
5
- export { IndexerCursorEntity, LedgerJournalEntity, PAFI_ENTITIES, PendingCreditStatus, UserBalanceEntity } from './entities/index.cjs';
6
- export { InitialSchema1700000000000, PAFI_MIGRATIONS } from './migrations/index.cjs';
5
+ export { IndexerCursorEntity, LedgerJournalEntity, PAFI_ENTITIES, PendingCreditStatus, RedemptionHistoryEntity, UserBalanceEntity } from './entities/index.cjs';
6
+ export { CreateRedemptionHistory1746230400001, InitialSchema1700000000000, PAFI_MIGRATIONS } from './migrations/index.cjs';
7
7
 
8
8
  interface PostgresPointLedgerOptions {
9
9
  /**
@@ -38,6 +38,32 @@ declare class PostgresPointLedger implements IPointLedger {
38
38
  private readonly logger;
39
39
  constructor(dataSource: DataSource, options?: PostgresPointLedgerOptions);
40
40
  getBalance(userAddress: Address, tokenAddress?: Address): Promise<bigint>;
41
+ /**
42
+ * Background sweep — marks all expired PENDING locks as EXPIRED in
43
+ * a single UPDATE. Issuers SHOULD call this periodically (e.g.
44
+ * every 1-5 minutes via a cron / NestJS `@Interval`) to keep the
45
+ * lock table from growing unbounded.
46
+ *
47
+ * A single sweep amortizes the write cost vs `getBalance` doing a
48
+ * wide UPDATE on every read. Returns the number of rows transitioned.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { Interval } from "@nestjs/schedule";
53
+ *
54
+ * @Injectable()
55
+ * export class LockSweepService {
56
+ * constructor(private readonly ledger: PostgresPointLedger) {}
57
+ *
58
+ * @Interval(5 * 60 * 1000) // 5 minutes
59
+ * async sweep() {
60
+ * const swept = await this.ledger.markExpiredLocks();
61
+ * this.logger.debug(`expired ${swept} mint locks`);
62
+ * }
63
+ * }
64
+ * ```
65
+ */
66
+ markExpiredLocks(): Promise<number>;
41
67
  getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
42
68
  getMintLock(lockId: string, userAddress?: Address): Promise<LockedMintRequest | null>;
43
69
  /** Raw TypeORM row — escape hatch for callers that need entity fields. */
@@ -94,4 +120,27 @@ declare class PostgresCursorStore implements IIndexerCursorStore {
94
120
  forKey(cursorId: string): IIndexerCursorStore;
95
121
  }
96
122
 
97
- export { LockedMintEntity, PendingCreditEntity, PostgresCursorStore, PostgresPointLedger, type PostgresPointLedgerOptions };
123
+ /**
124
+ * Postgres-backed IRedemptionHistoryStore. Append-only — every
125
+ * `recordRedemption` writes a new row. Reads (`sumRedeemedSince` /
126
+ * `getLastRedeemedAtUnixSec`) hit the (user, time) composite index.
127
+ *
128
+ * Addresses are normalized to checksum form on write and lower-cased
129
+ * on read so the index works regardless of casing inconsistency. The
130
+ * entity stores the canonical (checksummed) form.
131
+ */
132
+ declare class PostgresRedemptionHistoryStore implements IRedemptionHistoryStore {
133
+ private readonly dataSource;
134
+ constructor(dataSource: DataSource);
135
+ sumRedeemedSince(user: Address, sinceUnixSec: number, pointTokenAddress?: Address): Promise<bigint>;
136
+ getLastRedeemedAtUnixSec(user: Address, pointTokenAddress?: Address): Promise<number | null>;
137
+ recordRedemption(entry: {
138
+ user: Address;
139
+ amountPt: bigint;
140
+ pointTokenAddress?: Address;
141
+ unixSec: number;
142
+ reservationId?: string;
143
+ }): Promise<void>;
144
+ }
145
+
146
+ export { LockedMintEntity, PendingCreditEntity, PostgresCursorStore, PostgresPointLedger, type PostgresPointLedgerOptions, PostgresRedemptionHistoryStore };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { DataSource } from 'typeorm';
2
2
  import { Address, Hex } from 'viem';
3
- import { IPointLedger, LockedMintRequest, PendingCredit, MintingStatus, IIndexerCursorStore } from '@pafi-dev/issuer';
3
+ import { IPointLedger, LockedMintRequest, PendingCredit, MintingStatus, IIndexerCursorStore, IRedemptionHistoryStore } from '@pafi-dev/issuer';
4
4
  import { LockedMintEntity, PendingCreditEntity } from './entities/index.js';
5
- export { IndexerCursorEntity, LedgerJournalEntity, PAFI_ENTITIES, PendingCreditStatus, UserBalanceEntity } from './entities/index.js';
6
- export { InitialSchema1700000000000, PAFI_MIGRATIONS } from './migrations/index.js';
5
+ export { IndexerCursorEntity, LedgerJournalEntity, PAFI_ENTITIES, PendingCreditStatus, RedemptionHistoryEntity, UserBalanceEntity } from './entities/index.js';
6
+ export { CreateRedemptionHistory1746230400001, InitialSchema1700000000000, PAFI_MIGRATIONS } from './migrations/index.js';
7
7
 
8
8
  interface PostgresPointLedgerOptions {
9
9
  /**
@@ -38,6 +38,32 @@ declare class PostgresPointLedger implements IPointLedger {
38
38
  private readonly logger;
39
39
  constructor(dataSource: DataSource, options?: PostgresPointLedgerOptions);
40
40
  getBalance(userAddress: Address, tokenAddress?: Address): Promise<bigint>;
41
+ /**
42
+ * Background sweep — marks all expired PENDING locks as EXPIRED in
43
+ * a single UPDATE. Issuers SHOULD call this periodically (e.g.
44
+ * every 1-5 minutes via a cron / NestJS `@Interval`) to keep the
45
+ * lock table from growing unbounded.
46
+ *
47
+ * A single sweep amortizes the write cost vs `getBalance` doing a
48
+ * wide UPDATE on every read. Returns the number of rows transitioned.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { Interval } from "@nestjs/schedule";
53
+ *
54
+ * @Injectable()
55
+ * export class LockSweepService {
56
+ * constructor(private readonly ledger: PostgresPointLedger) {}
57
+ *
58
+ * @Interval(5 * 60 * 1000) // 5 minutes
59
+ * async sweep() {
60
+ * const swept = await this.ledger.markExpiredLocks();
61
+ * this.logger.debug(`expired ${swept} mint locks`);
62
+ * }
63
+ * }
64
+ * ```
65
+ */
66
+ markExpiredLocks(): Promise<number>;
41
67
  getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
42
68
  getMintLock(lockId: string, userAddress?: Address): Promise<LockedMintRequest | null>;
43
69
  /** Raw TypeORM row — escape hatch for callers that need entity fields. */
@@ -94,4 +120,27 @@ declare class PostgresCursorStore implements IIndexerCursorStore {
94
120
  forKey(cursorId: string): IIndexerCursorStore;
95
121
  }
96
122
 
97
- export { LockedMintEntity, PendingCreditEntity, PostgresCursorStore, PostgresPointLedger, type PostgresPointLedgerOptions };
123
+ /**
124
+ * Postgres-backed IRedemptionHistoryStore. Append-only — every
125
+ * `recordRedemption` writes a new row. Reads (`sumRedeemedSince` /
126
+ * `getLastRedeemedAtUnixSec`) hit the (user, time) composite index.
127
+ *
128
+ * Addresses are normalized to checksum form on write and lower-cased
129
+ * on read so the index works regardless of casing inconsistency. The
130
+ * entity stores the canonical (checksummed) form.
131
+ */
132
+ declare class PostgresRedemptionHistoryStore implements IRedemptionHistoryStore {
133
+ private readonly dataSource;
134
+ constructor(dataSource: DataSource);
135
+ sumRedeemedSince(user: Address, sinceUnixSec: number, pointTokenAddress?: Address): Promise<bigint>;
136
+ getLastRedeemedAtUnixSec(user: Address, pointTokenAddress?: Address): Promise<number | null>;
137
+ recordRedemption(entry: {
138
+ user: Address;
139
+ amountPt: bigint;
140
+ pointTokenAddress?: Address;
141
+ unixSec: number;
142
+ reservationId?: string;
143
+ }): Promise<void>;
144
+ }
145
+
146
+ export { LockedMintEntity, PendingCreditEntity, PostgresCursorStore, PostgresPointLedger, type PostgresPointLedgerOptions, PostgresRedemptionHistoryStore };
package/dist/index.js CHANGED
@@ -248,6 +248,34 @@ LedgerJournalEntity = __decorateClass([
248
248
  ], LedgerJournalEntity);
249
249
 
250
250
  // src/postgresPointLedger.ts
251
+ var RETRIABLE_PG_CODES = /* @__PURE__ */ new Set(["40P01", "40001"]);
252
+ function isRetriablePgError(err) {
253
+ const e = err;
254
+ if (!e) return false;
255
+ if (e.code && RETRIABLE_PG_CODES.has(e.code)) return true;
256
+ if (e.message && /deadlock detected|could not serialize/i.test(e.message)) {
257
+ return true;
258
+ }
259
+ return false;
260
+ }
261
+ async function withDeadlockRetry(dataSource, fn, maxAttempts = 3) {
262
+ let attempt = 0;
263
+ let delayMs = 25;
264
+ for (; ; ) {
265
+ attempt++;
266
+ try {
267
+ return await dataSource.transaction(fn);
268
+ } catch (err) {
269
+ if (attempt >= maxAttempts || !isRetriablePgError(err)) {
270
+ throw err;
271
+ }
272
+ await new Promise(
273
+ (r) => setTimeout(r, delayMs + Math.floor(Math.random() * delayMs))
274
+ );
275
+ delayMs *= 2;
276
+ }
277
+ }
278
+ }
251
279
  var PostgresPointLedger = class {
252
280
  constructor(dataSource, options = {}) {
253
281
  this.dataSource = dataSource;
@@ -260,13 +288,47 @@ var PostgresPointLedger = class {
260
288
  // ---------------------------------------------------------------------
261
289
  async getBalance(userAddress, tokenAddress) {
262
290
  const { user, token } = normalize(userAddress, tokenAddress);
263
- await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
264
- const balanceRow = await this.dataSource.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
291
+ const [balanceRow, locked] = await Promise.all([
292
+ this.dataSource.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } }),
293
+ this.sumPendingLocks(user, token)
294
+ ]);
265
295
  const total = balanceRow?.balance ?? 0n;
266
- const locked = await this.sumPendingLocks(user, token);
267
296
  const available = total - locked;
268
297
  return available < 0n ? 0n : available;
269
298
  }
299
+ /**
300
+ * Background sweep — marks all expired PENDING locks as EXPIRED in
301
+ * a single UPDATE. Issuers SHOULD call this periodically (e.g.
302
+ * every 1-5 minutes via a cron / NestJS `@Interval`) to keep the
303
+ * lock table from growing unbounded.
304
+ *
305
+ * A single sweep amortizes the write cost vs `getBalance` doing a
306
+ * wide UPDATE on every read. Returns the number of rows transitioned.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * import { Interval } from "@nestjs/schedule";
311
+ *
312
+ * @Injectable()
313
+ * export class LockSweepService {
314
+ * constructor(private readonly ledger: PostgresPointLedger) {}
315
+ *
316
+ * @Interval(5 * 60 * 1000) // 5 minutes
317
+ * async sweep() {
318
+ * const swept = await this.ledger.markExpiredLocks();
319
+ * this.logger.debug(`expired ${swept} mint locks`);
320
+ * }
321
+ * }
322
+ * ```
323
+ */
324
+ async markExpiredLocks() {
325
+ const result = await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
326
+ const swept = result.affected ?? 0;
327
+ if (swept > 0) {
328
+ this.logger?.debug?.(`markExpiredLocks: swept ${swept} mint locks`);
329
+ }
330
+ return swept;
331
+ }
270
332
  async getLockedRequests(userAddress, tokenAddress) {
271
333
  const { user, token } = normalize(userAddress, tokenAddress);
272
334
  const rows = await this.dataSource.getRepository(LockedMintEntity).find({
@@ -332,8 +394,8 @@ var PostgresPointLedger = class {
332
394
  throw new Error("creditBalance: amount must be positive");
333
395
  }
334
396
  const { user, token } = normalize(userAddress, tokenAddress);
335
- await this.dataSource.transaction(async (tx) => {
336
- const existing = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
397
+ await withDeadlockRetry(this.dataSource, async (tx) => {
398
+ const existing = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
337
399
  const next = (existing?.balance ?? 0n) + amount;
338
400
  await tx.getRepository(UserBalanceEntity).upsert(
339
401
  { userAddress: user, tokenAddress: token, balance: next },
@@ -356,8 +418,8 @@ var PostgresPointLedger = class {
356
418
  throw new Error("lockForMinting: lockDurationMs must be positive");
357
419
  }
358
420
  const { user, token } = normalize(userAddress, tokenAddress);
359
- return this.dataSource.transaction(async (tx) => {
360
- const balanceRow = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
421
+ return withDeadlockRetry(this.dataSource, async (tx) => {
422
+ const balanceRow = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
361
423
  const total = balanceRow?.balance ?? 0n;
362
424
  const pendingTotal = await tx.getRepository(LockedMintEntity).createQueryBuilder("lock").select("COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)", "sum").where("lock.user_address = :user", { user }).andWhere("lock.token_address = :token", { token }).andWhere("lock.status = :pending", { pending: "PENDING" }).andWhere("lock.expires_at > :now", { now: /* @__PURE__ */ new Date() }).getRawOne();
363
425
  const locked = pendingTotal ? BigInt(pendingTotal.sum) : 0n;
@@ -391,8 +453,8 @@ var PostgresPointLedger = class {
391
453
  throw new Error("deductBalance: amount must be positive");
392
454
  }
393
455
  const { user, token } = normalize(userAddress, tokenAddress);
394
- await this.dataSource.transaction(async (tx) => {
395
- const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
456
+ await withDeadlockRetry(this.dataSource, async (tx) => {
457
+ const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
396
458
  if (!balance || balance.balance < amount) {
397
459
  throw new Error(
398
460
  `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
@@ -459,8 +521,8 @@ var PostgresPointLedger = class {
459
521
  return row.id;
460
522
  }
461
523
  async resolveCreditByBurnTx(lockId, txHash) {
462
- await this.dataSource.transaction(async (tx) => {
463
- const credit = await tx.getRepository(PendingCreditEntity).findOne({ where: { id: lockId } });
524
+ await withDeadlockRetry(this.dataSource, async (tx) => {
525
+ const credit = await tx.getRepository(PendingCreditEntity).createQueryBuilder("credit").setLock("pessimistic_write").where("credit.id = :id", { id: lockId }).getOne();
464
526
  if (!credit) {
465
527
  throw new Error(
466
528
  `resolveCreditByBurnTx: unknown pending credit ${lockId}`
@@ -494,7 +556,7 @@ var PostgresPointLedger = class {
494
556
  );
495
557
  return;
496
558
  }
497
- const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
559
+ const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
498
560
  const next = (balance?.balance ?? 0n) + credit.amount;
499
561
  await tx.getRepository(UserBalanceEntity).upsert(
500
562
  { userAddress: user, tokenAddress: token, balance: next },
@@ -620,13 +682,120 @@ var PostgresCursorStore = class _PostgresCursorStore {
620
682
  }
621
683
  };
622
684
 
685
+ // src/postgresRedemptionHistoryStore.ts
686
+ import { getAddress as getAddress2 } from "viem";
687
+
688
+ // src/entities/redemption-history.entity.ts
689
+ import {
690
+ Column as Column6,
691
+ CreateDateColumn as CreateDateColumn4,
692
+ Entity as Entity6,
693
+ Index as Index4,
694
+ PrimaryGeneratedColumn as PrimaryGeneratedColumn4
695
+ } from "typeorm";
696
+ var RedemptionHistoryEntity = class {
697
+ id;
698
+ userAddress;
699
+ tokenAddress;
700
+ amountPt;
701
+ createdAtUnixSec;
702
+ reservationId;
703
+ rowCreatedAt;
704
+ };
705
+ __decorateClass([
706
+ PrimaryGeneratedColumn4("uuid")
707
+ ], RedemptionHistoryEntity.prototype, "id", 2);
708
+ __decorateClass([
709
+ Column6({ name: "user_address", type: "varchar", length: 42 })
710
+ ], RedemptionHistoryEntity.prototype, "userAddress", 2);
711
+ __decorateClass([
712
+ Column6({ name: "token_address", type: "varchar", length: 42, nullable: true })
713
+ ], RedemptionHistoryEntity.prototype, "tokenAddress", 2);
714
+ __decorateClass([
715
+ Column6({
716
+ name: "amount_pt",
717
+ type: "numeric",
718
+ precision: 78,
719
+ scale: 0,
720
+ transformer: {
721
+ to: (value) => value.toString(),
722
+ from: (value) => BigInt(value)
723
+ }
724
+ })
725
+ ], RedemptionHistoryEntity.prototype, "amountPt", 2);
726
+ __decorateClass([
727
+ Column6({ name: "created_at_unix_sec", type: "bigint" })
728
+ ], RedemptionHistoryEntity.prototype, "createdAtUnixSec", 2);
729
+ __decorateClass([
730
+ Column6({
731
+ name: "reservation_id",
732
+ type: "varchar",
733
+ length: 64,
734
+ nullable: true
735
+ })
736
+ ], RedemptionHistoryEntity.prototype, "reservationId", 2);
737
+ __decorateClass([
738
+ CreateDateColumn4({ name: "row_created_at", type: "timestamptz" })
739
+ ], RedemptionHistoryEntity.prototype, "rowCreatedAt", 2);
740
+ RedemptionHistoryEntity = __decorateClass([
741
+ Entity6({ name: "redemption_history" }),
742
+ Index4("idx_redemption_history_user_created", [
743
+ "userAddress",
744
+ "createdAtUnixSec"
745
+ ])
746
+ ], RedemptionHistoryEntity);
747
+
748
+ // src/postgresRedemptionHistoryStore.ts
749
+ var PostgresRedemptionHistoryStore = class {
750
+ constructor(dataSource) {
751
+ this.dataSource = dataSource;
752
+ }
753
+ dataSource;
754
+ async sumRedeemedSince(user, sinceUnixSec, pointTokenAddress) {
755
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
756
+ const qb = repo.createQueryBuilder("rh").select("COALESCE(SUM(rh.amount_pt), 0)", "sum").where("rh.user_address = :user", { user: getAddress2(user) }).andWhere("rh.created_at_unix_sec >= :since", { since: sinceUnixSec });
757
+ if (pointTokenAddress !== void 0) {
758
+ qb.andWhere("rh.token_address = :token", {
759
+ token: getAddress2(pointTokenAddress)
760
+ });
761
+ } else {
762
+ }
763
+ const row = await qb.getRawOne() ?? { sum: "0" };
764
+ return BigInt(row.sum ?? "0");
765
+ }
766
+ async getLastRedeemedAtUnixSec(user, pointTokenAddress) {
767
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
768
+ const qb = repo.createQueryBuilder("rh").select("rh.created_at_unix_sec", "ts").where("rh.user_address = :user", { user: getAddress2(user) }).orderBy("rh.created_at_unix_sec", "DESC").limit(1);
769
+ if (pointTokenAddress !== void 0) {
770
+ qb.andWhere("rh.token_address = :token", {
771
+ token: getAddress2(pointTokenAddress)
772
+ });
773
+ }
774
+ const row = await qb.getRawOne();
775
+ if (!row || row.ts === null) return null;
776
+ return Number(row.ts);
777
+ }
778
+ async recordRedemption(entry) {
779
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
780
+ const row = repo.create({
781
+ userAddress: getAddress2(entry.user),
782
+ tokenAddress: entry.pointTokenAddress ? getAddress2(entry.pointTokenAddress) : null,
783
+ amountPt: entry.amountPt,
784
+ createdAtUnixSec: String(entry.unixSec),
785
+ reservationId: entry.reservationId ?? null
786
+ });
787
+ await repo.save(row);
788
+ }
789
+ };
790
+
623
791
  // src/entities/index.ts
624
792
  var PAFI_ENTITIES = [
625
793
  LockedMintEntity,
626
794
  PendingCreditEntity,
627
795
  UserBalanceEntity,
628
796
  LedgerJournalEntity,
629
- IndexerCursorEntity
797
+ IndexerCursorEntity,
798
+ RedemptionHistoryEntity
630
799
  ];
631
800
 
632
801
  // src/migrations/1700000000000-InitialSchema.ts
@@ -732,9 +901,42 @@ var InitialSchema1700000000000 = class {
732
901
  }
733
902
  };
734
903
 
904
+ // src/migrations/1746230400001-CreateRedemptionHistory.ts
905
+ var CreateRedemptionHistory1746230400001 = class {
906
+ name = "CreateRedemptionHistory1746230400001";
907
+ async up(queryRunner) {
908
+ await queryRunner.query(`
909
+ CREATE TABLE IF NOT EXISTS redemption_history (
910
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
911
+ user_address varchar(42) NOT NULL,
912
+ token_address varchar(42),
913
+ amount_pt numeric(78, 0) NOT NULL,
914
+ created_at_unix_sec bigint NOT NULL,
915
+ reservation_id varchar(64),
916
+ row_created_at timestamptz NOT NULL DEFAULT NOW(),
917
+ CONSTRAINT redemption_history_amount_positive CHECK (amount_pt > 0)
918
+ )
919
+ `);
920
+ await queryRunner.query(`
921
+ CREATE INDEX IF NOT EXISTS idx_redemption_history_user_created
922
+ ON redemption_history (user_address, created_at_unix_sec DESC)
923
+ `);
924
+ }
925
+ async down(queryRunner) {
926
+ await queryRunner.query(
927
+ `DROP INDEX IF EXISTS idx_redemption_history_user_created`
928
+ );
929
+ await queryRunner.query(`DROP TABLE IF EXISTS redemption_history`);
930
+ }
931
+ };
932
+
735
933
  // src/migrations/index.ts
736
- var PAFI_MIGRATIONS = [InitialSchema1700000000000];
934
+ var PAFI_MIGRATIONS = [
935
+ InitialSchema1700000000000,
936
+ CreateRedemptionHistory1746230400001
937
+ ];
737
938
  export {
939
+ CreateRedemptionHistory1746230400001,
738
940
  IndexerCursorEntity,
739
941
  InitialSchema1700000000000,
740
942
  LedgerJournalEntity,
@@ -744,6 +946,8 @@ export {
744
946
  PendingCreditEntity,
745
947
  PostgresCursorStore,
746
948
  PostgresPointLedger,
949
+ PostgresRedemptionHistoryStore,
950
+ RedemptionHistoryEntity,
747
951
  UserBalanceEntity
748
952
  };
749
953
  //# sourceMappingURL=index.js.map