@pafi-dev/issuer-postgres 0.1.2 → 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.cjs CHANGED
@@ -28,6 +28,7 @@ var __decorateClass = (decorators, target, key, kind) => {
28
28
  // src/index.ts
29
29
  var src_exports = {};
30
30
  __export(src_exports, {
31
+ CreateRedemptionHistory1746230400001: () => CreateRedemptionHistory1746230400001,
31
32
  IndexerCursorEntity: () => IndexerCursorEntity,
32
33
  InitialSchema1700000000000: () => InitialSchema1700000000000,
33
34
  LedgerJournalEntity: () => LedgerJournalEntity,
@@ -37,6 +38,8 @@ __export(src_exports, {
37
38
  PendingCreditEntity: () => PendingCreditEntity,
38
39
  PostgresCursorStore: () => PostgresCursorStore,
39
40
  PostgresPointLedger: () => PostgresPointLedger,
41
+ PostgresRedemptionHistoryStore: () => PostgresRedemptionHistoryStore,
42
+ RedemptionHistoryEntity: () => RedemptionHistoryEntity,
40
43
  UserBalanceEntity: () => UserBalanceEntity
41
44
  });
42
45
  module.exports = __toCommonJS(src_exports);
@@ -262,6 +265,34 @@ LedgerJournalEntity = __decorateClass([
262
265
  ], LedgerJournalEntity);
263
266
 
264
267
  // src/postgresPointLedger.ts
268
+ var RETRIABLE_PG_CODES = /* @__PURE__ */ new Set(["40P01", "40001"]);
269
+ function isRetriablePgError(err) {
270
+ const e = err;
271
+ if (!e) return false;
272
+ if (e.code && RETRIABLE_PG_CODES.has(e.code)) return true;
273
+ if (e.message && /deadlock detected|could not serialize/i.test(e.message)) {
274
+ return true;
275
+ }
276
+ return false;
277
+ }
278
+ async function withDeadlockRetry(dataSource, fn, maxAttempts = 3) {
279
+ let attempt = 0;
280
+ let delayMs = 25;
281
+ for (; ; ) {
282
+ attempt++;
283
+ try {
284
+ return await dataSource.transaction(fn);
285
+ } catch (err) {
286
+ if (attempt >= maxAttempts || !isRetriablePgError(err)) {
287
+ throw err;
288
+ }
289
+ await new Promise(
290
+ (r) => setTimeout(r, delayMs + Math.floor(Math.random() * delayMs))
291
+ );
292
+ delayMs *= 2;
293
+ }
294
+ }
295
+ }
265
296
  var PostgresPointLedger = class {
266
297
  constructor(dataSource, options = {}) {
267
298
  this.dataSource = dataSource;
@@ -274,13 +305,47 @@ var PostgresPointLedger = class {
274
305
  // ---------------------------------------------------------------------
275
306
  async getBalance(userAddress, tokenAddress) {
276
307
  const { user, token } = normalize(userAddress, tokenAddress);
277
- await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
278
- const balanceRow = await this.dataSource.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
308
+ const [balanceRow, locked] = await Promise.all([
309
+ this.dataSource.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } }),
310
+ this.sumPendingLocks(user, token)
311
+ ]);
279
312
  const total = balanceRow?.balance ?? 0n;
280
- const locked = await this.sumPendingLocks(user, token);
281
313
  const available = total - locked;
282
314
  return available < 0n ? 0n : available;
283
315
  }
316
+ /**
317
+ * Background sweep — marks all expired PENDING locks as EXPIRED in
318
+ * a single UPDATE. Issuers SHOULD call this periodically (e.g.
319
+ * every 1-5 minutes via a cron / NestJS `@Interval`) to keep the
320
+ * lock table from growing unbounded.
321
+ *
322
+ * A single sweep amortizes the write cost vs `getBalance` doing a
323
+ * wide UPDATE on every read. Returns the number of rows transitioned.
324
+ *
325
+ * @example
326
+ * ```ts
327
+ * import { Interval } from "@nestjs/schedule";
328
+ *
329
+ * @Injectable()
330
+ * export class LockSweepService {
331
+ * constructor(private readonly ledger: PostgresPointLedger) {}
332
+ *
333
+ * @Interval(5 * 60 * 1000) // 5 minutes
334
+ * async sweep() {
335
+ * const swept = await this.ledger.markExpiredLocks();
336
+ * this.logger.debug(`expired ${swept} mint locks`);
337
+ * }
338
+ * }
339
+ * ```
340
+ */
341
+ async markExpiredLocks() {
342
+ 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();
343
+ const swept = result.affected ?? 0;
344
+ if (swept > 0) {
345
+ this.logger?.debug?.(`markExpiredLocks: swept ${swept} mint locks`);
346
+ }
347
+ return swept;
348
+ }
284
349
  async getLockedRequests(userAddress, tokenAddress) {
285
350
  const { user, token } = normalize(userAddress, tokenAddress);
286
351
  const rows = await this.dataSource.getRepository(LockedMintEntity).find({
@@ -346,8 +411,8 @@ var PostgresPointLedger = class {
346
411
  throw new Error("creditBalance: amount must be positive");
347
412
  }
348
413
  const { user, token } = normalize(userAddress, tokenAddress);
349
- await this.dataSource.transaction(async (tx) => {
350
- const existing = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
414
+ await withDeadlockRetry(this.dataSource, async (tx) => {
415
+ const existing = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
351
416
  const next = (existing?.balance ?? 0n) + amount;
352
417
  await tx.getRepository(UserBalanceEntity).upsert(
353
418
  { userAddress: user, tokenAddress: token, balance: next },
@@ -370,8 +435,8 @@ var PostgresPointLedger = class {
370
435
  throw new Error("lockForMinting: lockDurationMs must be positive");
371
436
  }
372
437
  const { user, token } = normalize(userAddress, tokenAddress);
373
- return this.dataSource.transaction(async (tx) => {
374
- const balanceRow = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
438
+ return withDeadlockRetry(this.dataSource, async (tx) => {
439
+ const balanceRow = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
375
440
  const total = balanceRow?.balance ?? 0n;
376
441
  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();
377
442
  const locked = pendingTotal ? BigInt(pendingTotal.sum) : 0n;
@@ -405,8 +470,8 @@ var PostgresPointLedger = class {
405
470
  throw new Error("deductBalance: amount must be positive");
406
471
  }
407
472
  const { user, token } = normalize(userAddress, tokenAddress);
408
- await this.dataSource.transaction(async (tx) => {
409
- const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
473
+ await withDeadlockRetry(this.dataSource, async (tx) => {
474
+ const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
410
475
  if (!balance || balance.balance < amount) {
411
476
  throw new Error(
412
477
  `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
@@ -473,8 +538,8 @@ var PostgresPointLedger = class {
473
538
  return row.id;
474
539
  }
475
540
  async resolveCreditByBurnTx(lockId, txHash) {
476
- await this.dataSource.transaction(async (tx) => {
477
- const credit = await tx.getRepository(PendingCreditEntity).findOne({ where: { id: lockId } });
541
+ await withDeadlockRetry(this.dataSource, async (tx) => {
542
+ const credit = await tx.getRepository(PendingCreditEntity).createQueryBuilder("credit").setLock("pessimistic_write").where("credit.id = :id", { id: lockId }).getOne();
478
543
  if (!credit) {
479
544
  throw new Error(
480
545
  `resolveCreditByBurnTx: unknown pending credit ${lockId}`
@@ -508,7 +573,7 @@ var PostgresPointLedger = class {
508
573
  );
509
574
  return;
510
575
  }
511
- const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
576
+ const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
512
577
  const next = (balance?.balance ?? 0n) + credit.amount;
513
578
  await tx.getRepository(UserBalanceEntity).upsert(
514
579
  { userAddress: user, tokenAddress: token, balance: next },
@@ -634,13 +699,114 @@ var PostgresCursorStore = class _PostgresCursorStore {
634
699
  }
635
700
  };
636
701
 
702
+ // src/postgresRedemptionHistoryStore.ts
703
+ var import_viem2 = require("viem");
704
+
705
+ // src/entities/redemption-history.entity.ts
706
+ var import_typeorm6 = require("typeorm");
707
+ var RedemptionHistoryEntity = class {
708
+ id;
709
+ userAddress;
710
+ tokenAddress;
711
+ amountPt;
712
+ createdAtUnixSec;
713
+ reservationId;
714
+ rowCreatedAt;
715
+ };
716
+ __decorateClass([
717
+ (0, import_typeorm6.PrimaryGeneratedColumn)("uuid")
718
+ ], RedemptionHistoryEntity.prototype, "id", 2);
719
+ __decorateClass([
720
+ (0, import_typeorm6.Column)({ name: "user_address", type: "varchar", length: 42 })
721
+ ], RedemptionHistoryEntity.prototype, "userAddress", 2);
722
+ __decorateClass([
723
+ (0, import_typeorm6.Column)({ name: "token_address", type: "varchar", length: 42, nullable: true })
724
+ ], RedemptionHistoryEntity.prototype, "tokenAddress", 2);
725
+ __decorateClass([
726
+ (0, import_typeorm6.Column)({
727
+ name: "amount_pt",
728
+ type: "numeric",
729
+ precision: 78,
730
+ scale: 0,
731
+ transformer: {
732
+ to: (value) => value.toString(),
733
+ from: (value) => BigInt(value)
734
+ }
735
+ })
736
+ ], RedemptionHistoryEntity.prototype, "amountPt", 2);
737
+ __decorateClass([
738
+ (0, import_typeorm6.Column)({ name: "created_at_unix_sec", type: "bigint" })
739
+ ], RedemptionHistoryEntity.prototype, "createdAtUnixSec", 2);
740
+ __decorateClass([
741
+ (0, import_typeorm6.Column)({
742
+ name: "reservation_id",
743
+ type: "varchar",
744
+ length: 64,
745
+ nullable: true
746
+ })
747
+ ], RedemptionHistoryEntity.prototype, "reservationId", 2);
748
+ __decorateClass([
749
+ (0, import_typeorm6.CreateDateColumn)({ name: "row_created_at", type: "timestamptz" })
750
+ ], RedemptionHistoryEntity.prototype, "rowCreatedAt", 2);
751
+ RedemptionHistoryEntity = __decorateClass([
752
+ (0, import_typeorm6.Entity)({ name: "redemption_history" }),
753
+ (0, import_typeorm6.Index)("idx_redemption_history_user_created", [
754
+ "userAddress",
755
+ "createdAtUnixSec"
756
+ ])
757
+ ], RedemptionHistoryEntity);
758
+
759
+ // src/postgresRedemptionHistoryStore.ts
760
+ var PostgresRedemptionHistoryStore = class {
761
+ constructor(dataSource) {
762
+ this.dataSource = dataSource;
763
+ }
764
+ dataSource;
765
+ async sumRedeemedSince(user, sinceUnixSec, pointTokenAddress) {
766
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
767
+ const qb = repo.createQueryBuilder("rh").select("COALESCE(SUM(rh.amount_pt), 0)", "sum").where("rh.user_address = :user", { user: (0, import_viem2.getAddress)(user) }).andWhere("rh.created_at_unix_sec >= :since", { since: sinceUnixSec });
768
+ if (pointTokenAddress !== void 0) {
769
+ qb.andWhere("rh.token_address = :token", {
770
+ token: (0, import_viem2.getAddress)(pointTokenAddress)
771
+ });
772
+ } else {
773
+ }
774
+ const row = await qb.getRawOne() ?? { sum: "0" };
775
+ return BigInt(row.sum ?? "0");
776
+ }
777
+ async getLastRedeemedAtUnixSec(user, pointTokenAddress) {
778
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
779
+ const qb = repo.createQueryBuilder("rh").select("rh.created_at_unix_sec", "ts").where("rh.user_address = :user", { user: (0, import_viem2.getAddress)(user) }).orderBy("rh.created_at_unix_sec", "DESC").limit(1);
780
+ if (pointTokenAddress !== void 0) {
781
+ qb.andWhere("rh.token_address = :token", {
782
+ token: (0, import_viem2.getAddress)(pointTokenAddress)
783
+ });
784
+ }
785
+ const row = await qb.getRawOne();
786
+ if (!row || row.ts === null) return null;
787
+ return Number(row.ts);
788
+ }
789
+ async recordRedemption(entry) {
790
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
791
+ const row = repo.create({
792
+ userAddress: (0, import_viem2.getAddress)(entry.user),
793
+ tokenAddress: entry.pointTokenAddress ? (0, import_viem2.getAddress)(entry.pointTokenAddress) : null,
794
+ amountPt: entry.amountPt,
795
+ createdAtUnixSec: String(entry.unixSec),
796
+ reservationId: entry.reservationId ?? null
797
+ });
798
+ await repo.save(row);
799
+ }
800
+ };
801
+
637
802
  // src/entities/index.ts
638
803
  var PAFI_ENTITIES = [
639
804
  LockedMintEntity,
640
805
  PendingCreditEntity,
641
806
  UserBalanceEntity,
642
807
  LedgerJournalEntity,
643
- IndexerCursorEntity
808
+ IndexerCursorEntity,
809
+ RedemptionHistoryEntity
644
810
  ];
645
811
 
646
812
  // src/migrations/1700000000000-InitialSchema.ts
@@ -746,10 +912,43 @@ var InitialSchema1700000000000 = class {
746
912
  }
747
913
  };
748
914
 
915
+ // src/migrations/1746230400001-CreateRedemptionHistory.ts
916
+ var CreateRedemptionHistory1746230400001 = class {
917
+ name = "CreateRedemptionHistory1746230400001";
918
+ async up(queryRunner) {
919
+ await queryRunner.query(`
920
+ CREATE TABLE IF NOT EXISTS redemption_history (
921
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
922
+ user_address varchar(42) NOT NULL,
923
+ token_address varchar(42),
924
+ amount_pt numeric(78, 0) NOT NULL,
925
+ created_at_unix_sec bigint NOT NULL,
926
+ reservation_id varchar(64),
927
+ row_created_at timestamptz NOT NULL DEFAULT NOW(),
928
+ CONSTRAINT redemption_history_amount_positive CHECK (amount_pt > 0)
929
+ )
930
+ `);
931
+ await queryRunner.query(`
932
+ CREATE INDEX IF NOT EXISTS idx_redemption_history_user_created
933
+ ON redemption_history (user_address, created_at_unix_sec DESC)
934
+ `);
935
+ }
936
+ async down(queryRunner) {
937
+ await queryRunner.query(
938
+ `DROP INDEX IF EXISTS idx_redemption_history_user_created`
939
+ );
940
+ await queryRunner.query(`DROP TABLE IF EXISTS redemption_history`);
941
+ }
942
+ };
943
+
749
944
  // src/migrations/index.ts
750
- var PAFI_MIGRATIONS = [InitialSchema1700000000000];
945
+ var PAFI_MIGRATIONS = [
946
+ InitialSchema1700000000000,
947
+ CreateRedemptionHistory1746230400001
948
+ ];
751
949
  // Annotate the CommonJS export names for ESM import in node:
752
950
  0 && (module.exports = {
951
+ CreateRedemptionHistory1746230400001,
753
952
  IndexerCursorEntity,
754
953
  InitialSchema1700000000000,
755
954
  LedgerJournalEntity,
@@ -759,6 +958,8 @@ var PAFI_MIGRATIONS = [InitialSchema1700000000000];
759
958
  PendingCreditEntity,
760
959
  PostgresCursorStore,
761
960
  PostgresPointLedger,
961
+ PostgresRedemptionHistoryStore,
962
+ RedemptionHistoryEntity,
762
963
  UserBalanceEntity
763
964
  });
764
965
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/postgresPointLedger.ts","../src/entities/locked-mint.entity.ts","../src/entities/pending-credit.entity.ts","../src/entities/user-balance.entity.ts","../src/entities/ledger-journal.entity.ts","../src/entities/indexer-cursor.entity.ts","../src/postgresCursorStore.ts","../src/entities/index.ts","../src/migrations/1700000000000-InitialSchema.ts","../src/migrations/index.ts"],"sourcesContent":["// Main implementations.\nexport { PostgresPointLedger } from \"./postgresPointLedger\";\nexport type { PostgresPointLedgerOptions } from \"./postgresPointLedger\";\nexport { PostgresCursorStore } from \"./postgresCursorStore\";\n\n// Re-export entities + migrations from the package root for issuers\n// who don't want a separate import. Subpaths\n// (`@pafi-dev/issuer-postgres/entities`) remain available for\n// cleaner barrel imports in NestJS modules.\nexport {\n LockedMintEntity,\n PendingCreditEntity,\n UserBalanceEntity,\n LedgerJournalEntity,\n IndexerCursorEntity,\n PAFI_ENTITIES,\n type PendingCreditStatus,\n} from \"./entities\";\n\nexport { InitialSchema1700000000000, PAFI_MIGRATIONS } from \"./migrations\";\n","import type { DataSource } from \"typeorm\";\nimport { getAddress, type Address, type Hex } from \"viem\";\nimport type {\n IPointLedger,\n LockedMintRequest,\n MintingStatus,\n PendingCredit,\n} from \"@pafi-dev/issuer\";\n\nimport { LockedMintEntity } from \"./entities/locked-mint.entity\";\nimport { PendingCreditEntity } from \"./entities/pending-credit.entity\";\nimport { UserBalanceEntity } from \"./entities/user-balance.entity\";\nimport { LedgerJournalEntity } from \"./entities/ledger-journal.entity\";\n\nexport interface PostgresPointLedgerOptions {\n /**\n * Optional logger. When omitted, the service is silent. Pass a Nest\n * `Logger` / pino instance / `console` to surface debug + info\n * lines.\n */\n logger?: {\n debug?: (msg: string) => void;\n log?: (msg: string) => void;\n warn?: (msg: string) => void;\n };\n}\n\n/**\n * Postgres-backed `IPointLedger` — the reference impl every issuer\n * starts from. Framework-agnostic: takes a TypeORM `DataSource` in the\n * constructor, no NestJS decorators or DI tokens. Wrap in your\n * favorite injector.\n *\n * Implements every required + optional method from `IPointLedger`,\n * including the v1.4 reverse flow (`reservePendingCredit`,\n * `resolveCreditByBurnTx`) and the bundler-receipt fallback hooks\n * (`bindMintUserOpHash`, `bindCreditUserOpHash`, `getMintLock`,\n * `getPendingCredit`).\n *\n * Multi-token: every method requires `tokenAddress` — there is no\n * \"default token\" bucket. Single-token issuers pass the same address\n * everywhere.\n */\nexport class PostgresPointLedger implements IPointLedger {\n private readonly logger: PostgresPointLedgerOptions[\"logger\"];\n\n constructor(\n private readonly dataSource: DataSource,\n options: PostgresPointLedgerOptions = {},\n ) {\n this.logger = options.logger;\n }\n\n // ---------------------------------------------------------------------\n // Read\n // ---------------------------------------------------------------------\n\n async getBalance(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<bigint> {\n const { user, token } = normalize(userAddress, tokenAddress);\n\n // Lazy sweep: mark any expired PENDING lock as EXPIRED before\n // computing available. Keeps the lock table self-cleaning without\n // needing a background cron.\n await this.dataSource\n .getRepository(LockedMintEntity)\n .createQueryBuilder()\n .update()\n .set({ status: \"EXPIRED\" })\n .where(\"status = :pending\", { pending: \"PENDING\" })\n .andWhere(\"expires_at <= :now\", { now: new Date() })\n .execute();\n\n const balanceRow = await this.dataSource\n .getRepository(UserBalanceEntity)\n .findOne({ where: { userAddress: user, tokenAddress: token } });\n const total = balanceRow?.balance ?? 0n;\n const locked = await this.sumPendingLocks(user, token);\n const available = total - locked;\n return available < 0n ? 0n : available;\n }\n\n async getLockedRequests(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<LockedMintRequest[]> {\n const { user, token } = normalize(userAddress, tokenAddress);\n const rows = await this.dataSource\n .getRepository(LockedMintEntity)\n .find({\n where: { userAddress: user, tokenAddress: token, status: \"PENDING\" },\n order: { createdAt: \"ASC\" },\n });\n return rows.map((row) => this.toSdkLock(row));\n }\n\n async getMintLock(\n lockId: string,\n userAddress?: Address,\n ): Promise<LockedMintRequest | null> {\n const row = await this.dataSource\n .getRepository(LockedMintEntity)\n .findOne({ where: { id: lockId } });\n if (!row) return null;\n if (\n userAddress &&\n row.userAddress.toLowerCase() !== userAddress.toLowerCase()\n ) {\n return null;\n }\n return this.toSdkLock(row);\n }\n\n /** Raw TypeORM row — escape hatch for callers that need entity fields. */\n async getMintLockEntity(lockId: string): Promise<LockedMintEntity | null> {\n return this.dataSource\n .getRepository(LockedMintEntity)\n .findOne({ where: { id: lockId } });\n }\n\n async getPendingCredit(\n lockId: string,\n userAddress?: Address,\n ): Promise<PendingCredit | null> {\n const row = await this.dataSource\n .getRepository(PendingCreditEntity)\n .findOne({ where: { id: lockId } });\n if (!row) return null;\n if (\n userAddress &&\n row.userAddress.toLowerCase() !== userAddress.toLowerCase()\n ) {\n return null;\n }\n return {\n lockId: row.id,\n userAddress: getAddress(row.userAddress) as Address,\n amount: row.amount,\n tokenAddress: row.tokenAddress\n ? (getAddress(row.tokenAddress) as Address)\n : undefined,\n status: row.status as PendingCredit[\"status\"],\n createdAt: row.createdAt.getTime(),\n expiresAt: row.expiresAt.getTime(),\n txHash: (row.txHash as Hex | null) ?? undefined,\n resolvedAt: row.resolvedAt?.getTime(),\n userOpHash: (row.userOpHash as Hex | null) ?? undefined,\n };\n }\n\n /** Raw TypeORM row escape hatch for credits. */\n async getPendingCreditEntity(\n lockId: string,\n ): Promise<PendingCreditEntity | null> {\n return this.dataSource\n .getRepository(PendingCreditEntity)\n .findOne({ where: { id: lockId } });\n }\n\n /**\n * Paginated list of a user's mint requests across all statuses and\n * all tokens — used by `GET /user/transactions` reference endpoint.\n */\n async listUserTransactions(\n userAddress: Address,\n limit: number,\n offset: number,\n ): Promise<{ rows: LockedMintEntity[]; total: number }> {\n const user = getAddress(userAddress);\n const [rows, total] = await this.dataSource\n .getRepository(LockedMintEntity)\n .findAndCount({\n where: { userAddress: user },\n order: { createdAt: \"DESC\" },\n take: limit,\n skip: offset,\n });\n return { rows, total };\n }\n\n // ---------------------------------------------------------------------\n // Write\n // ---------------------------------------------------------------------\n\n async creditBalance(\n userAddress: Address,\n amount: bigint,\n reason: string,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"creditBalance: amount must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n await this.dataSource.transaction(async (tx) => {\n const existing = await tx\n .getRepository(UserBalanceEntity)\n .findOne({ where: { userAddress: user, tokenAddress: token } });\n const next = (existing?.balance ?? 0n) + amount;\n\n await tx\n .getRepository(UserBalanceEntity)\n .upsert(\n { userAddress: user, tokenAddress: token, balance: next },\n { conflictPaths: [\"userAddress\", \"tokenAddress\"] },\n );\n\n await tx.getRepository(LedgerJournalEntity).insert({\n userAddress: user,\n tokenAddress: token,\n delta: amount,\n reason,\n });\n });\n\n this.logger?.debug?.(`credit ${user}[${token}] +${amount} (${reason})`);\n }\n\n async lockForMinting(\n userAddress: Address,\n amount: bigint,\n lockDurationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\"lockForMinting: amount must be positive\");\n }\n if (lockDurationMs <= 0) {\n throw new Error(\"lockForMinting: lockDurationMs must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n return this.dataSource.transaction(async (tx) => {\n const balanceRow = await tx\n .getRepository(UserBalanceEntity)\n .findOne({ where: { userAddress: user, tokenAddress: token } });\n const total = balanceRow?.balance ?? 0n;\n\n const pendingTotal = await tx\n .getRepository(LockedMintEntity)\n .createQueryBuilder(\"lock\")\n .select(\"COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)\", \"sum\")\n .where(\"lock.user_address = :user\", { user })\n .andWhere(\"lock.token_address = :token\", { token })\n .andWhere(\"lock.status = :pending\", { pending: \"PENDING\" })\n .andWhere(\"lock.expires_at > :now\", { now: new Date() })\n .getRawOne<{ sum: string }>();\n\n const locked = pendingTotal ? BigInt(pendingTotal.sum) : 0n;\n const available = total - locked;\n if (available < amount) {\n throw new Error(\n `Insufficient balance: available=${available}, requested=${amount}`,\n );\n }\n\n const lock = await tx.getRepository(LockedMintEntity).save({\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n expiresAt: new Date(Date.now() + lockDurationMs),\n });\n\n this.logger?.debug?.(\n `lock ${lock.id} ${user}[${token}] amount=${amount}`,\n );\n return lock.id;\n });\n }\n\n async releaseLock(lockId: string): Promise<void> {\n const result = await this.dataSource\n .getRepository(LockedMintEntity)\n .delete({ id: lockId, status: \"PENDING\" });\n\n if ((result.affected ?? 0) > 0) {\n this.logger?.debug?.(`release lock ${lockId}`);\n }\n }\n\n async deductBalance(\n userAddress: Address,\n amount: bigint,\n txHash: Hex,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"deductBalance: amount must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n await this.dataSource.transaction(async (tx) => {\n const balance = await tx\n .getRepository(UserBalanceEntity)\n .findOne({ where: { userAddress: user, tokenAddress: token } });\n if (!balance || balance.balance < amount) {\n throw new Error(\n `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`,\n );\n }\n\n await tx.getRepository(UserBalanceEntity).update(\n { userAddress: user, tokenAddress: token },\n { balance: balance.balance - amount },\n );\n\n await tx.getRepository(LedgerJournalEntity).insert({\n userAddress: user,\n tokenAddress: token,\n delta: -amount,\n reason: \"MINT_CONFIRMED\",\n txHash,\n });\n\n // Resolve the oldest matching PENDING lock atomically.\n const match = await tx.getRepository(LockedMintEntity).findOne({\n where: {\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n },\n order: { createdAt: \"ASC\" },\n });\n if (match) {\n await tx\n .getRepository(LockedMintEntity)\n .update({ id: match.id }, { status: \"MINTED\", txHash });\n }\n });\n\n this.logger?.log?.(`deduct ${user}[${token}] -${amount} tx=${txHash}`);\n }\n\n async updateMintStatus(\n lockId: string,\n status: MintingStatus,\n txHash?: Hex,\n ): Promise<void> {\n const update: Partial<LockedMintEntity> = { status };\n if (txHash) update.txHash = txHash;\n\n await this.dataSource\n .getRepository(LockedMintEntity)\n .update({ id: lockId }, update);\n }\n\n async bindMintUserOpHash(lockId: string, userOpHash: Hex): Promise<void> {\n await this.dataSource\n .getRepository(LockedMintEntity)\n .update({ id: lockId }, { userOpHash });\n }\n\n async bindCreditUserOpHash(lockId: string, userOpHash: Hex): Promise<void> {\n await this.dataSource\n .getRepository(PendingCreditEntity)\n .update({ id: lockId }, { userOpHash });\n }\n\n // ---------------------------------------------------------------------\n // Reverse flow (burn → off-chain credit)\n // ---------------------------------------------------------------------\n\n async reservePendingCredit(\n userAddress: Address,\n amount: bigint,\n durationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\"reservePendingCredit: amount must be positive\");\n }\n if (durationMs <= 0) {\n throw new Error(\"reservePendingCredit: durationMs must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n const row = await this.dataSource\n .getRepository(PendingCreditEntity)\n .save({\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n expiresAt: new Date(Date.now() + durationMs),\n });\n\n this.logger?.debug?.(\n `reserve pending credit ${row.id} ${user}[${token}] +${amount}`,\n );\n return row.id;\n }\n\n async resolveCreditByBurnTx(lockId: string, txHash: Hex): Promise<void> {\n await this.dataSource.transaction(async (tx) => {\n const credit = await tx\n .getRepository(PendingCreditEntity)\n .findOne({ where: { id: lockId } });\n\n if (!credit) {\n throw new Error(\n `resolveCreditByBurnTx: unknown pending credit ${lockId}`,\n );\n }\n\n if (credit.status === \"RESOLVED\") {\n if (credit.txHash === txHash) return; // idempotent replay\n throw new Error(\n `resolveCreditByBurnTx: credit ${lockId} already resolved with a different txHash`,\n );\n }\n\n if (credit.status === \"EXPIRED\") {\n throw new Error(\n `resolveCreditByBurnTx: credit ${lockId} already expired — burn landed too late`,\n );\n }\n\n const user = credit.userAddress as Address;\n const token = credit.tokenAddress as Address;\n\n // Defense-in-depth — same `txHash` already credited a sibling\n // credit for the same (user, token). Mark this credit resolved\n // without re-applying balance.\n const alreadyResolved = await tx\n .getRepository(PendingCreditEntity)\n .findOne({\n where: {\n userAddress: user,\n tokenAddress: token,\n txHash,\n status: \"RESOLVED\",\n },\n });\n if (alreadyResolved) {\n await tx\n .getRepository(PendingCreditEntity)\n .update(\n { id: lockId },\n { status: \"RESOLVED\", txHash, resolvedAt: new Date() },\n );\n return;\n }\n\n const balance = await tx\n .getRepository(UserBalanceEntity)\n .findOne({ where: { userAddress: user, tokenAddress: token } });\n const next = (balance?.balance ?? 0n) + credit.amount;\n\n await tx\n .getRepository(UserBalanceEntity)\n .upsert(\n { userAddress: user, tokenAddress: token, balance: next },\n { conflictPaths: [\"userAddress\", \"tokenAddress\"] },\n );\n\n await tx.getRepository(LedgerJournalEntity).insert({\n userAddress: user,\n tokenAddress: token,\n delta: credit.amount,\n reason: \"BURN_FOR_CREDIT\",\n txHash,\n });\n\n await tx\n .getRepository(PendingCreditEntity)\n .update(\n { id: lockId },\n { status: \"RESOLVED\", txHash, resolvedAt: new Date() },\n );\n });\n\n this.logger?.log?.(`resolve pending credit ${lockId} tx=${txHash}`);\n }\n\n /**\n * Used by `BurnIndexer.matchLockId` to resolve an on-chain burn\n * event back to a pending credit row. Returns the oldest matching\n * `(user, token, amount, status: PENDING)` lockId, or undefined\n * when no match exists (unsolicited burn — indexer skips).\n */\n async findPendingCreditLockId(\n userAddress: Address,\n amount: bigint,\n tokenAddress: Address,\n ): Promise<string | undefined> {\n const { user, token } = normalize(userAddress, tokenAddress);\n const row = await this.dataSource\n .getRepository(PendingCreditEntity)\n .findOne({\n where: {\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n },\n order: { createdAt: \"ASC\" },\n });\n return row?.id;\n }\n\n // ---------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------\n\n private async sumPendingLocks(\n userAddress: Address,\n tokenAddress: Address,\n ): Promise<bigint> {\n const row = await this.dataSource\n .getRepository(LockedMintEntity)\n .createQueryBuilder(\"lock\")\n .select(\"COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)\", \"sum\")\n .where(\"lock.user_address = :user\", { user: userAddress })\n .andWhere(\"lock.token_address = :token\", { token: tokenAddress })\n .andWhere(\"lock.status = :pending\", { pending: \"PENDING\" })\n .andWhere(\"lock.expires_at > :now\", { now: new Date() })\n .getRawOne<{ sum: string }>();\n return row ? BigInt(row.sum) : 0n;\n }\n\n private toSdkLock(row: LockedMintEntity): LockedMintRequest {\n const out: LockedMintRequest = {\n lockId: row.id,\n userAddress: row.userAddress as Address,\n tokenAddress: row.tokenAddress as Address,\n amount: row.amount,\n status: row.status,\n createdAt: row.createdAt.getTime(),\n expiresAt: row.expiresAt.getTime(),\n };\n if (row.txHash) out.txHash = row.txHash as Hex;\n if (row.userOpHash) out.userOpHash = row.userOpHash as Hex;\n return out;\n }\n}\n\n/**\n * Multi-token guard — throw if `tokenAddress` is missing on any\n * mutating call. Single-token issuers must still pass their token\n * address explicitly so reads + writes never fall into a \"default\"\n * bucket the application never queries.\n */\nfunction normalize(\n userAddress: Address,\n tokenAddress: Address | undefined,\n): { user: Address; token: Address } {\n if (!tokenAddress) {\n throw new Error(\n \"PostgresPointLedger: tokenAddress is required on every call (multi-token ledger)\",\n );\n }\n return {\n user: getAddress(userAddress),\n token: getAddress(tokenAddress),\n };\n}\n","import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n PrimaryGeneratedColumn,\n} from \"typeorm\";\nimport type { MintingStatus } from \"@pafi-dev/issuer\";\n\n/**\n * A reservation against a user's off-chain balance.\n *\n * Lifecycle:\n * PENDING ── PointIndexer matches Mint event ──▶ MINTED\n * │\n * ├── deadline elapsed ────────────────────▶ EXPIRED\n * │\n * └── tx reverted ─────────────────────────▶ FAILED\n *\n * The `(userAddress, status)` composite index keeps the \"sum all\n * PENDING locks\" hot path of `lockForMinting()` fast under load.\n */\n@Entity({ name: \"locked_mint_requests\" })\n@Index([\"userAddress\", \"status\"])\nexport class LockedMintEntity {\n @PrimaryGeneratedColumn(\"uuid\")\n id!: string;\n\n @Column({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @Column({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"amount\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n amount!: bigint;\n\n @Column({\n name: \"status\",\n type: \"varchar\",\n length: 16,\n default: \"PENDING\",\n })\n status!: MintingStatus;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt!: Date;\n\n @Column({ name: \"expires_at\", type: \"timestamp with time zone\" })\n expiresAt!: Date;\n\n @Column({ name: \"tx_hash\", type: \"varchar\", length: 66, nullable: true })\n txHash?: string | null;\n\n /**\n * ERC-4337 userOpHash returned by the bundler at /claim/submit.\n * Bound to the lock so `/claim/status` can fall back to the bundler\n * receipt — required when multiple PENDING locks share the same\n * `amount` (PointIndexer matches by amount and can pick the wrong\n * sibling lock).\n */\n @Column({\n name: \"user_op_hash\",\n type: \"varchar\",\n length: 66,\n nullable: true,\n })\n userOpHash?: string | null;\n}\n","import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n PrimaryGeneratedColumn,\n} from \"typeorm\";\n\nexport type PendingCreditStatus = \"PENDING\" | \"RESOLVED\" | \"EXPIRED\";\n\n/**\n * Reverse flow — user burns on-chain PT, `BurnIndexer` observes\n * `Transfer(user → 0x0)`, the credit is settled to the off-chain ledger.\n *\n * Lifecycle:\n * PENDING ── Burn tx observed by indexer ──▶ RESOLVED\n * │\n * └── deadline elapsed ────────────────▶ EXPIRED\n *\n * The credit is reserved BEFORE the UserOp is submitted so the\n * indexer can correlate `(user, amount, token)` back to the off-chain\n * row when the burn lands.\n */\n@Entity({ name: \"pending_credits\" })\n@Index([\"userAddress\", \"status\"])\n@Index([\"txHash\"])\nexport class PendingCreditEntity {\n @PrimaryGeneratedColumn(\"uuid\")\n id!: string;\n\n @Column({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @Column({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"amount\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n amount!: bigint;\n\n @Column({\n name: \"status\",\n type: \"varchar\",\n length: 16,\n default: \"PENDING\",\n })\n status!: PendingCreditStatus;\n\n /** On-chain burn tx that settled this credit. Null until indexer resolves. */\n @Column({ name: \"tx_hash\", type: \"varchar\", length: 66, nullable: true })\n txHash?: string | null;\n\n /** ERC-4337 userOpHash bound at /redeem/submit. See `LockedMintEntity`. */\n @Column({\n name: \"user_op_hash\",\n type: \"varchar\",\n length: 66,\n nullable: true,\n })\n userOpHash?: string | null;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt!: Date;\n\n @Column({ name: \"expires_at\", type: \"timestamp with time zone\" })\n expiresAt!: Date;\n\n @Column({\n name: \"resolved_at\",\n type: \"timestamp with time zone\",\n nullable: true,\n })\n resolvedAt?: Date | null;\n}\n","import { Column, Entity, PrimaryColumn, UpdateDateColumn } from \"typeorm\";\n\n/**\n * Off-chain point balance per `(userAddress, tokenAddress)`.\n *\n * `balance` is the **total** owned; pending reservations live in\n * `LockedMintEntity`. Available balance = total − sum(PENDING locks)\n * — `getBalance` in `PostgresPointLedger` does this subtraction\n * inside a transaction so reads are race-free.\n *\n * All amounts are `numeric(78, 0)` for full bigint precision (uint256\n * fits in 78 decimal digits). TypeORM transforms bigint ↔ string at\n * the boundary; in JS/TS code we always deal with `bigint`.\n */\n@Entity({ name: \"user_balances\" })\nexport class UserBalanceEntity {\n @PrimaryColumn({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @PrimaryColumn({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"balance\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n default: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n balance!: bigint;\n\n @UpdateDateColumn({ name: \"updated_at\" })\n updatedAt!: Date;\n}\n","import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n PrimaryGeneratedColumn,\n} from \"typeorm\";\n\n/**\n * Append-only audit trail for every balance mutation. Used for\n * reconciliation, customer support, and regulatory reporting.\n *\n * Sign convention:\n * - positive `delta` — credit (merchant award, refund, manual top-up)\n * - negative `delta` — debit (mint confirmation against the off-chain\n * balance; `txHash` references the on-chain Mint event)\n */\n@Entity({ name: \"ledger_journal\" })\n@Index([\"userAddress\", \"createdAt\"])\nexport class LedgerJournalEntity {\n @PrimaryGeneratedColumn(\"uuid\")\n id!: string;\n\n @Column({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @Column({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"delta\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n delta!: bigint;\n\n @Column({ name: \"reason\", type: \"varchar\", length: 128 })\n reason!: string;\n\n @Column({ name: \"tx_hash\", type: \"varchar\", length: 66, nullable: true })\n txHash?: string | null;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt!: Date;\n}\n","import { Column, Entity, PrimaryColumn, UpdateDateColumn } from \"typeorm\";\n\n/**\n * Persistent cursor for `PointIndexer` / `BurnIndexer`. Multiple rows\n * coexist keyed by `id` (e.g. `default` for the mint indexer,\n * `burn:0x...` for each per-token burn indexer).\n *\n * Stores the **next** block to scan, not the last processed one.\n * Indexer reads on startup and resumes from there.\n */\n@Entity({ name: \"indexer_cursors\" })\nexport class IndexerCursorEntity {\n @PrimaryColumn({ type: \"varchar\", length: 64 })\n id!: string;\n\n @Column({\n name: \"next_block\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n nextBlock!: bigint;\n\n @UpdateDateColumn({ name: \"updated_at\" })\n updatedAt!: Date;\n}\n","import type { DataSource } from \"typeorm\";\nimport type { IIndexerCursorStore } from \"@pafi-dev/issuer\";\n\nimport { IndexerCursorEntity } from \"./entities/indexer-cursor.entity\";\n\n/**\n * Postgres-backed indexer cursor store. Lets indexers survive\n * restarts — on boot, the indexer reads the last persisted block and\n * resumes scanning from there.\n *\n * Multiple indexers (e.g. `PointIndexer` for Mint events +\n * `BurnIndexer` per token for Transfer→0x0) share the same table via\n * different `cursorId`s. Construct with `cursorId = \"default\"` for\n * the primary mint indexer, or call `forKey(id)` to derive a sibling\n * store for a secondary indexer.\n */\nexport class PostgresCursorStore implements IIndexerCursorStore {\n constructor(\n private readonly dataSource: DataSource,\n private readonly cursorId: string = \"default\",\n ) {}\n\n async load(): Promise<bigint | undefined> {\n const row = await this.dataSource\n .getRepository(IndexerCursorEntity)\n .findOne({ where: { id: this.cursorId } });\n return row?.nextBlock;\n }\n\n async save(blockNumber: bigint): Promise<void> {\n await this.dataSource\n .getRepository(IndexerCursorEntity)\n .upsert(\n { id: this.cursorId, nextBlock: blockNumber },\n { conflictPaths: [\"id\"] },\n );\n }\n\n /** Derived store keyed by a different `id` — for sibling indexers. */\n forKey(cursorId: string): IIndexerCursorStore {\n return new PostgresCursorStore(this.dataSource, cursorId);\n }\n}\n","export { LockedMintEntity } from \"./locked-mint.entity\";\nexport {\n PendingCreditEntity,\n type PendingCreditStatus,\n} from \"./pending-credit.entity\";\nexport { UserBalanceEntity } from \"./user-balance.entity\";\nexport { LedgerJournalEntity } from \"./ledger-journal.entity\";\nexport { IndexerCursorEntity } from \"./indexer-cursor.entity\";\n\nimport { LockedMintEntity } from \"./locked-mint.entity\";\nimport { PendingCreditEntity } from \"./pending-credit.entity\";\nimport { UserBalanceEntity } from \"./user-balance.entity\";\nimport { LedgerJournalEntity } from \"./ledger-journal.entity\";\nimport { IndexerCursorEntity } from \"./indexer-cursor.entity\";\n\n/**\n * All entities in one array — drop into TypeORM's `entities` config or\n * NestJS's `TypeOrmModule.forFeature(PAFI_ENTITIES)`.\n */\nexport const PAFI_ENTITIES = [\n LockedMintEntity,\n PendingCreditEntity,\n UserBalanceEntity,\n LedgerJournalEntity,\n IndexerCursorEntity,\n] as const;\n","import type { MigrationInterface, QueryRunner } from \"typeorm\";\n\n/**\n * Single consolidated initial schema for `@pafi-dev/issuer-postgres`.\n *\n * Combines what gg56 split into two migrations (`InitialSchema` +\n * `AddPendingCredits`) plus the `user_op_hash` column, since this is\n * the v1.4-and-after baseline. Issuers adopting the SDK from scratch\n * apply this once.\n *\n * Tables:\n * user_balances — off-chain point balance per (user, token)\n * locked_mint_requests — reservations during mint flow\n * pending_credits — reserved credits during burn/redeem flow\n * ledger_journal — append-only audit trail of every delta\n * indexer_cursors — PointIndexer / BurnIndexer block cursors\n *\n * Issuer-specific extensions (campaign rules, KYC tables, custom\n * scenarios) belong in a follow-up migration — never edit this file\n * in place once it ships.\n */\nexport class InitialSchema1700000000000 implements MigrationInterface {\n name = \"InitialSchema1700000000000\";\n\n public async up(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"`);\n\n // ─── user_balances ──────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"user_balances\" (\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"balance\" numeric(78, 0) NOT NULL DEFAULT 0,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT now(),\n CONSTRAINT \"PK_user_balances\" PRIMARY KEY (\"user_address\", \"token_address\")\n )\n `);\n\n // ─── locked_mint_requests ───────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"locked_mint_requests\" (\n \"id\" uuid NOT NULL DEFAULT gen_random_uuid(),\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"amount\" numeric(78, 0) NOT NULL,\n \"status\" varchar(16) NOT NULL DEFAULT 'PENDING',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT now(),\n \"expires_at\" TIMESTAMP WITH TIME ZONE NOT NULL,\n \"tx_hash\" varchar(66),\n \"user_op_hash\" varchar(66),\n CONSTRAINT \"PK_locked_mint_requests\" PRIMARY KEY (\"id\")\n )\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_locked_mint_user_status\"\n ON \"locked_mint_requests\" (\"user_address\", \"token_address\", \"status\")\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_locked_mint_user_op_hash\"\n ON \"locked_mint_requests\" (\"user_op_hash\")\n `);\n\n // ─── pending_credits ────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"pending_credits\" (\n \"id\" uuid NOT NULL DEFAULT gen_random_uuid(),\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"amount\" numeric(78, 0) NOT NULL,\n \"status\" varchar(16) NOT NULL DEFAULT 'PENDING',\n \"tx_hash\" varchar(66),\n \"user_op_hash\" varchar(66),\n \"created_at\" TIMESTAMP NOT NULL DEFAULT now(),\n \"expires_at\" TIMESTAMP WITH TIME ZONE NOT NULL,\n \"resolved_at\" TIMESTAMP WITH TIME ZONE,\n CONSTRAINT \"PK_pending_credits\" PRIMARY KEY (\"id\")\n )\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_pending_credits_user_status\"\n ON \"pending_credits\" (\"user_address\", \"token_address\", \"status\")\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_pending_credits_tx_hash\"\n ON \"pending_credits\" (\"tx_hash\")\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_pending_credits_user_op_hash\"\n ON \"pending_credits\" (\"user_op_hash\")\n `);\n\n // ─── ledger_journal ─────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"ledger_journal\" (\n \"id\" uuid NOT NULL DEFAULT gen_random_uuid(),\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"delta\" numeric(78, 0) NOT NULL,\n \"reason\" varchar(128) NOT NULL,\n \"tx_hash\" varchar(66),\n \"created_at\" TIMESTAMP NOT NULL DEFAULT now(),\n CONSTRAINT \"PK_ledger_journal\" PRIMARY KEY (\"id\")\n )\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_ledger_journal_user_created\"\n ON \"ledger_journal\" (\"user_address\", \"token_address\", \"created_at\")\n `);\n\n // ─── indexer_cursors ────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"indexer_cursors\" (\n \"id\" varchar(64) NOT NULL,\n \"next_block\" numeric(78, 0) NOT NULL,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT now(),\n CONSTRAINT \"PK_indexer_cursors\" PRIMARY KEY (\"id\")\n )\n `);\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`DROP TABLE \"indexer_cursors\"`);\n await queryRunner.query(`DROP INDEX \"IDX_ledger_journal_user_created\"`);\n await queryRunner.query(`DROP TABLE \"ledger_journal\"`);\n await queryRunner.query(`DROP INDEX \"IDX_pending_credits_user_op_hash\"`);\n await queryRunner.query(`DROP INDEX \"IDX_pending_credits_tx_hash\"`);\n await queryRunner.query(`DROP INDEX \"IDX_pending_credits_user_status\"`);\n await queryRunner.query(`DROP TABLE \"pending_credits\"`);\n await queryRunner.query(`DROP INDEX \"IDX_locked_mint_user_op_hash\"`);\n await queryRunner.query(`DROP INDEX \"IDX_locked_mint_user_status\"`);\n await queryRunner.query(`DROP TABLE \"locked_mint_requests\"`);\n await queryRunner.query(`DROP TABLE \"user_balances\"`);\n }\n}\n","export { InitialSchema1700000000000 } from \"./1700000000000-InitialSchema\";\n\nimport { InitialSchema1700000000000 } from \"./1700000000000-InitialSchema\";\n\n/**\n * All shipped migrations in chronological order. Drop into TypeORM's\n * `migrations` config:\n *\n * import { PAFI_MIGRATIONS } from \"@pafi-dev/issuer-postgres/migrations\";\n *\n * new DataSource({\n * entities: [...PAFI_ENTITIES, ...yourCustomEntities],\n * migrations: [...PAFI_MIGRATIONS, ...yourCustomMigrations],\n * });\n */\nexport const PAFI_MIGRATIONS = [InitialSchema1700000000000] as const;\n"],"mappings":"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","names":["import_typeorm","import_typeorm","import_typeorm","import_typeorm"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/postgresPointLedger.ts","../src/entities/locked-mint.entity.ts","../src/entities/pending-credit.entity.ts","../src/entities/user-balance.entity.ts","../src/entities/ledger-journal.entity.ts","../src/entities/indexer-cursor.entity.ts","../src/postgresCursorStore.ts","../src/postgresRedemptionHistoryStore.ts","../src/entities/redemption-history.entity.ts","../src/entities/index.ts","../src/migrations/1700000000000-InitialSchema.ts","../src/migrations/1746230400001-CreateRedemptionHistory.ts","../src/migrations/index.ts"],"sourcesContent":["// Main implementations.\nexport { PostgresPointLedger } from \"./postgresPointLedger\";\nexport type { PostgresPointLedgerOptions } from \"./postgresPointLedger\";\nexport { PostgresCursorStore } from \"./postgresCursorStore\";\nexport { PostgresRedemptionHistoryStore } from \"./postgresRedemptionHistoryStore\";\n\n// Re-export entities + migrations from the package root for issuers\n// who don't want a separate import. Subpaths\n// (`@pafi-dev/issuer-postgres/entities`) remain available for\n// cleaner barrel imports in NestJS modules.\nexport {\n LockedMintEntity,\n PendingCreditEntity,\n UserBalanceEntity,\n LedgerJournalEntity,\n IndexerCursorEntity,\n RedemptionHistoryEntity,\n PAFI_ENTITIES,\n type PendingCreditStatus,\n} from \"./entities\";\n\nexport {\n InitialSchema1700000000000,\n CreateRedemptionHistory1746230400001,\n PAFI_MIGRATIONS,\n} from \"./migrations\";\n","import type { DataSource, EntityManager } from \"typeorm\";\nimport { getAddress, type Address, type Hex } from \"viem\";\nimport type {\n IPointLedger,\n LockedMintRequest,\n MintingStatus,\n PendingCredit,\n} from \"@pafi-dev/issuer\";\n\nimport { LockedMintEntity } from \"./entities/locked-mint.entity\";\nimport { PendingCreditEntity } from \"./entities/pending-credit.entity\";\nimport { UserBalanceEntity } from \"./entities/user-balance.entity\";\nimport { LedgerJournalEntity } from \"./entities/ledger-journal.entity\";\n\n/**\n * Postgres SQLSTATE codes that indicate a transient transaction conflict\n * which is safe to retry. `40P01` = deadlock detected;\n * `40001` = serialization failure (only seen at SERIALIZABLE isolation\n * but harmless to retry at READ COMMITTED too).\n */\nconst RETRIABLE_PG_CODES = new Set([\"40P01\", \"40001\"]);\n\ninterface PgError {\n code?: string;\n message?: string;\n}\n\nfunction isRetriablePgError(err: unknown): boolean {\n const e = err as PgError | undefined;\n if (!e) return false;\n if (e.code && RETRIABLE_PG_CODES.has(e.code)) return true;\n // Some drivers attach the code on a nested cause / driverError. Defensive:\n // string-match the message as a last resort.\n if (e.message && /deadlock detected|could not serialize/i.test(e.message)) {\n return true;\n }\n return false;\n}\n\n/**\n * Wrap a transaction body in a deadlock-retry loop. Postgres can raise\n * `40P01` when concurrent transactions take row locks in incompatible\n * orders — once `lockForMinting`/`deductBalance` use `FOR UPDATE`,\n * deadlocks become a normal occurrence under contention. Without retry\n * the issuer surfaces them as 500s instead of absorbing them transparently.\n */\nasync function withDeadlockRetry<T>(\n dataSource: DataSource,\n fn: (tx: EntityManager) => Promise<T>,\n maxAttempts = 3,\n): Promise<T> {\n let attempt = 0;\n let delayMs = 25;\n for (;;) {\n attempt++;\n try {\n return await dataSource.transaction(fn);\n } catch (err) {\n if (attempt >= maxAttempts || !isRetriablePgError(err)) {\n throw err;\n }\n // Exponential backoff with jitter. 25ms → 50ms → 100ms typical.\n await new Promise((r) =>\n setTimeout(r, delayMs + Math.floor(Math.random() * delayMs)),\n );\n delayMs *= 2;\n }\n }\n}\n\nexport interface PostgresPointLedgerOptions {\n /**\n * Optional logger. When omitted, the service is silent. Pass a Nest\n * `Logger` / pino instance / `console` to surface debug + info\n * lines.\n */\n logger?: {\n debug?: (msg: string) => void;\n log?: (msg: string) => void;\n warn?: (msg: string) => void;\n };\n}\n\n/**\n * Postgres-backed `IPointLedger` — the reference impl every issuer\n * starts from. Framework-agnostic: takes a TypeORM `DataSource` in the\n * constructor, no NestJS decorators or DI tokens. Wrap in your\n * favorite injector.\n *\n * Implements every required + optional method from `IPointLedger`,\n * including the v1.4 reverse flow (`reservePendingCredit`,\n * `resolveCreditByBurnTx`) and the bundler-receipt fallback hooks\n * (`bindMintUserOpHash`, `bindCreditUserOpHash`, `getMintLock`,\n * `getPendingCredit`).\n *\n * Multi-token: every method requires `tokenAddress` — there is no\n * \"default token\" bucket. Single-token issuers pass the same address\n * everywhere.\n */\nexport class PostgresPointLedger implements IPointLedger {\n private readonly logger: PostgresPointLedgerOptions[\"logger\"];\n\n constructor(\n private readonly dataSource: DataSource,\n options: PostgresPointLedgerOptions = {},\n ) {\n this.logger = options.logger;\n }\n\n // ---------------------------------------------------------------------\n // Read\n // ---------------------------------------------------------------------\n\n async getBalance(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<bigint> {\n const { user, token } = normalize(userAddress, tokenAddress);\n\n // Pure read — `sumPendingLocks` filters `expires_at > now`, so\n // expired locks are naturally excluded without writing a status\n // update. The lock table grows monotonically until a periodic\n // background sweep runs `markExpiredLocks()` (see below). The\n // read query stays cheap because it filters by the composite\n // index `(user_address, token_address, status, expires_at)`.\n const [balanceRow, locked] = await Promise.all([\n this.dataSource\n .getRepository(UserBalanceEntity)\n .findOne({ where: { userAddress: user, tokenAddress: token } }),\n this.sumPendingLocks(user, token),\n ]);\n const total = balanceRow?.balance ?? 0n;\n const available = total - locked;\n return available < 0n ? 0n : available;\n }\n\n /**\n * Background sweep — marks all expired PENDING locks as EXPIRED in\n * a single UPDATE. Issuers SHOULD call this periodically (e.g.\n * every 1-5 minutes via a cron / NestJS `@Interval`) to keep the\n * lock table from growing unbounded.\n *\n * A single sweep amortizes the write cost vs `getBalance` doing a\n * wide UPDATE on every read. Returns the number of rows transitioned.\n *\n * @example\n * ```ts\n * import { Interval } from \"@nestjs/schedule\";\n *\n * @Injectable()\n * export class LockSweepService {\n * constructor(private readonly ledger: PostgresPointLedger) {}\n *\n * @Interval(5 * 60 * 1000) // 5 minutes\n * async sweep() {\n * const swept = await this.ledger.markExpiredLocks();\n * this.logger.debug(`expired ${swept} mint locks`);\n * }\n * }\n * ```\n */\n async markExpiredLocks(): Promise<number> {\n const result = await this.dataSource\n .getRepository(LockedMintEntity)\n .createQueryBuilder()\n .update()\n .set({ status: \"EXPIRED\" })\n .where(\"status = :pending\", { pending: \"PENDING\" })\n .andWhere(\"expires_at <= :now\", { now: new Date() })\n .execute();\n const swept = result.affected ?? 0;\n if (swept > 0) {\n this.logger?.debug?.(`markExpiredLocks: swept ${swept} mint locks`);\n }\n return swept;\n }\n\n async getLockedRequests(\n userAddress: Address,\n tokenAddress?: Address,\n ): Promise<LockedMintRequest[]> {\n const { user, token } = normalize(userAddress, tokenAddress);\n const rows = await this.dataSource\n .getRepository(LockedMintEntity)\n .find({\n where: { userAddress: user, tokenAddress: token, status: \"PENDING\" },\n order: { createdAt: \"ASC\" },\n });\n return rows.map((row) => this.toSdkLock(row));\n }\n\n async getMintLock(\n lockId: string,\n userAddress?: Address,\n ): Promise<LockedMintRequest | null> {\n const row = await this.dataSource\n .getRepository(LockedMintEntity)\n .findOne({ where: { id: lockId } });\n if (!row) return null;\n if (\n userAddress &&\n row.userAddress.toLowerCase() !== userAddress.toLowerCase()\n ) {\n return null;\n }\n return this.toSdkLock(row);\n }\n\n /** Raw TypeORM row — escape hatch for callers that need entity fields. */\n async getMintLockEntity(lockId: string): Promise<LockedMintEntity | null> {\n return this.dataSource\n .getRepository(LockedMintEntity)\n .findOne({ where: { id: lockId } });\n }\n\n async getPendingCredit(\n lockId: string,\n userAddress?: Address,\n ): Promise<PendingCredit | null> {\n const row = await this.dataSource\n .getRepository(PendingCreditEntity)\n .findOne({ where: { id: lockId } });\n if (!row) return null;\n if (\n userAddress &&\n row.userAddress.toLowerCase() !== userAddress.toLowerCase()\n ) {\n return null;\n }\n return {\n lockId: row.id,\n userAddress: getAddress(row.userAddress) as Address,\n amount: row.amount,\n tokenAddress: row.tokenAddress\n ? (getAddress(row.tokenAddress) as Address)\n : undefined,\n status: row.status as PendingCredit[\"status\"],\n createdAt: row.createdAt.getTime(),\n expiresAt: row.expiresAt.getTime(),\n txHash: (row.txHash as Hex | null) ?? undefined,\n resolvedAt: row.resolvedAt?.getTime(),\n userOpHash: (row.userOpHash as Hex | null) ?? undefined,\n };\n }\n\n /** Raw TypeORM row escape hatch for credits. */\n async getPendingCreditEntity(\n lockId: string,\n ): Promise<PendingCreditEntity | null> {\n return this.dataSource\n .getRepository(PendingCreditEntity)\n .findOne({ where: { id: lockId } });\n }\n\n /**\n * Paginated list of a user's mint requests across all statuses and\n * all tokens — used by `GET /user/transactions` reference endpoint.\n */\n async listUserTransactions(\n userAddress: Address,\n limit: number,\n offset: number,\n ): Promise<{ rows: LockedMintEntity[]; total: number }> {\n const user = getAddress(userAddress);\n const [rows, total] = await this.dataSource\n .getRepository(LockedMintEntity)\n .findAndCount({\n where: { userAddress: user },\n order: { createdAt: \"DESC\" },\n take: limit,\n skip: offset,\n });\n return { rows, total };\n }\n\n // ---------------------------------------------------------------------\n // Write\n // ---------------------------------------------------------------------\n\n async creditBalance(\n userAddress: Address,\n amount: bigint,\n reason: string,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"creditBalance: amount must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n await withDeadlockRetry(this.dataSource, async (tx) => {\n // FOR UPDATE on the existing balance row — avoids lost-update under\n // concurrent credits (each tx reads stale total + writes incremented\n // value at READ COMMITTED, dropping increments). UPSERT cannot lock\n // a row that doesn't exist yet, so first-credit case relies on the\n // unique constraint (user_address, token_address) to serialize.\n const existing = await tx\n .getRepository(UserBalanceEntity)\n .createQueryBuilder(\"balance\")\n .setLock(\"pessimistic_write\")\n .where(\"balance.user_address = :user\", { user })\n .andWhere(\"balance.token_address = :token\", { token })\n .getOne();\n const next = (existing?.balance ?? 0n) + amount;\n\n await tx\n .getRepository(UserBalanceEntity)\n .upsert(\n { userAddress: user, tokenAddress: token, balance: next },\n { conflictPaths: [\"userAddress\", \"tokenAddress\"] },\n );\n\n await tx.getRepository(LedgerJournalEntity).insert({\n userAddress: user,\n tokenAddress: token,\n delta: amount,\n reason,\n });\n });\n\n this.logger?.debug?.(`credit ${user}[${token}] +${amount} (${reason})`);\n }\n\n async lockForMinting(\n userAddress: Address,\n amount: bigint,\n lockDurationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\"lockForMinting: amount must be positive\");\n }\n if (lockDurationMs <= 0) {\n throw new Error(\"lockForMinting: lockDurationMs must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n return withDeadlockRetry(this.dataSource, async (tx) => {\n // SELECT … FOR UPDATE on the user's balance row to prevent TOCTOU.\n // Without this, two concurrent lockForMinting() at READ COMMITTED can\n // both see the same `available` and both insert lock rows, allowing\n // the issuer to over-commit ledger balance against on-chain mints.\n const balanceRow = await tx\n .getRepository(UserBalanceEntity)\n .createQueryBuilder(\"balance\")\n .setLock(\"pessimistic_write\")\n .where(\"balance.user_address = :user\", { user })\n .andWhere(\"balance.token_address = :token\", { token })\n .getOne();\n const total = balanceRow?.balance ?? 0n;\n\n // Sum PENDING locks while holding the balance row lock — any\n // concurrent insert would serialize behind us on the same row.\n const pendingTotal = await tx\n .getRepository(LockedMintEntity)\n .createQueryBuilder(\"lock\")\n .select(\"COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)\", \"sum\")\n .where(\"lock.user_address = :user\", { user })\n .andWhere(\"lock.token_address = :token\", { token })\n .andWhere(\"lock.status = :pending\", { pending: \"PENDING\" })\n .andWhere(\"lock.expires_at > :now\", { now: new Date() })\n .getRawOne<{ sum: string }>();\n\n const locked = pendingTotal ? BigInt(pendingTotal.sum) : 0n;\n const available = total - locked;\n if (available < amount) {\n throw new Error(\n `Insufficient balance: available=${available}, requested=${amount}`,\n );\n }\n\n const lock = await tx.getRepository(LockedMintEntity).save({\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n expiresAt: new Date(Date.now() + lockDurationMs),\n });\n\n this.logger?.debug?.(\n `lock ${lock.id} ${user}[${token}] amount=${amount}`,\n );\n return lock.id;\n });\n }\n\n async releaseLock(lockId: string): Promise<void> {\n const result = await this.dataSource\n .getRepository(LockedMintEntity)\n .delete({ id: lockId, status: \"PENDING\" });\n\n if ((result.affected ?? 0) > 0) {\n this.logger?.debug?.(`release lock ${lockId}`);\n }\n }\n\n async deductBalance(\n userAddress: Address,\n amount: bigint,\n txHash: Hex,\n tokenAddress?: Address,\n ): Promise<void> {\n if (amount <= 0n) {\n throw new Error(\"deductBalance: amount must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n await withDeadlockRetry(this.dataSource, async (tx) => {\n // SELECT … FOR UPDATE — same TOCTOU concern as lockForMinting.\n // Concurrent deductBalance() (e.g. chain reorg + duplicate finalize\n // event) at READ COMMITTED could both pass the `balance >= amount`\n // check and produce a negative balance.\n const balance = await tx\n .getRepository(UserBalanceEntity)\n .createQueryBuilder(\"balance\")\n .setLock(\"pessimistic_write\")\n .where(\"balance.user_address = :user\", { user })\n .andWhere(\"balance.token_address = :token\", { token })\n .getOne();\n if (!balance || balance.balance < amount) {\n throw new Error(\n `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`,\n );\n }\n\n await tx.getRepository(UserBalanceEntity).update(\n { userAddress: user, tokenAddress: token },\n { balance: balance.balance - amount },\n );\n\n await tx.getRepository(LedgerJournalEntity).insert({\n userAddress: user,\n tokenAddress: token,\n delta: -amount,\n reason: \"MINT_CONFIRMED\",\n txHash,\n });\n\n // Resolve the oldest matching PENDING lock atomically.\n const match = await tx.getRepository(LockedMintEntity).findOne({\n where: {\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n },\n order: { createdAt: \"ASC\" },\n });\n if (match) {\n await tx\n .getRepository(LockedMintEntity)\n .update({ id: match.id }, { status: \"MINTED\", txHash });\n }\n });\n\n this.logger?.log?.(`deduct ${user}[${token}] -${amount} tx=${txHash}`);\n }\n\n async updateMintStatus(\n lockId: string,\n status: MintingStatus,\n txHash?: Hex,\n ): Promise<void> {\n const update: Partial<LockedMintEntity> = { status };\n if (txHash) update.txHash = txHash;\n\n await this.dataSource\n .getRepository(LockedMintEntity)\n .update({ id: lockId }, update);\n }\n\n async bindMintUserOpHash(lockId: string, userOpHash: Hex): Promise<void> {\n await this.dataSource\n .getRepository(LockedMintEntity)\n .update({ id: lockId }, { userOpHash });\n }\n\n async bindCreditUserOpHash(lockId: string, userOpHash: Hex): Promise<void> {\n await this.dataSource\n .getRepository(PendingCreditEntity)\n .update({ id: lockId }, { userOpHash });\n }\n\n // ---------------------------------------------------------------------\n // Reverse flow (burn → off-chain credit)\n // ---------------------------------------------------------------------\n\n async reservePendingCredit(\n userAddress: Address,\n amount: bigint,\n durationMs: number,\n tokenAddress?: Address,\n ): Promise<string> {\n if (amount <= 0n) {\n throw new Error(\"reservePendingCredit: amount must be positive\");\n }\n if (durationMs <= 0) {\n throw new Error(\"reservePendingCredit: durationMs must be positive\");\n }\n const { user, token } = normalize(userAddress, tokenAddress);\n\n const row = await this.dataSource\n .getRepository(PendingCreditEntity)\n .save({\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n expiresAt: new Date(Date.now() + durationMs),\n });\n\n this.logger?.debug?.(\n `reserve pending credit ${row.id} ${user}[${token}] +${amount}`,\n );\n return row.id;\n }\n\n async resolveCreditByBurnTx(lockId: string, txHash: Hex): Promise<void> {\n await withDeadlockRetry(this.dataSource, async (tx) => {\n // FOR UPDATE on the credit row — burn-side mirror of mint resolution.\n // Concurrent calls (chain reorg / duplicate event) at READ COMMITTED\n // could both pass the status check and double-credit balance.\n const credit = await tx\n .getRepository(PendingCreditEntity)\n .createQueryBuilder(\"credit\")\n .setLock(\"pessimistic_write\")\n .where(\"credit.id = :id\", { id: lockId })\n .getOne();\n\n if (!credit) {\n throw new Error(\n `resolveCreditByBurnTx: unknown pending credit ${lockId}`,\n );\n }\n\n if (credit.status === \"RESOLVED\") {\n if (credit.txHash === txHash) return; // idempotent replay\n throw new Error(\n `resolveCreditByBurnTx: credit ${lockId} already resolved with a different txHash`,\n );\n }\n\n if (credit.status === \"EXPIRED\") {\n throw new Error(\n `resolveCreditByBurnTx: credit ${lockId} already expired — burn landed too late`,\n );\n }\n\n const user = credit.userAddress as Address;\n const token = credit.tokenAddress as Address;\n\n // Defense-in-depth — same `txHash` already credited a sibling\n // credit for the same (user, token). Mark this credit resolved\n // without re-applying balance.\n const alreadyResolved = await tx\n .getRepository(PendingCreditEntity)\n .findOne({\n where: {\n userAddress: user,\n tokenAddress: token,\n txHash,\n status: \"RESOLVED\",\n },\n });\n if (alreadyResolved) {\n await tx\n .getRepository(PendingCreditEntity)\n .update(\n { id: lockId },\n { status: \"RESOLVED\", txHash, resolvedAt: new Date() },\n );\n return;\n }\n\n // FOR UPDATE on balance row prevents lost-update with concurrent\n // creditBalance / resolveCreditByBurnTx (both increment).\n const balance = await tx\n .getRepository(UserBalanceEntity)\n .createQueryBuilder(\"balance\")\n .setLock(\"pessimistic_write\")\n .where(\"balance.user_address = :user\", { user })\n .andWhere(\"balance.token_address = :token\", { token })\n .getOne();\n const next = (balance?.balance ?? 0n) + credit.amount;\n\n await tx\n .getRepository(UserBalanceEntity)\n .upsert(\n { userAddress: user, tokenAddress: token, balance: next },\n { conflictPaths: [\"userAddress\", \"tokenAddress\"] },\n );\n\n await tx.getRepository(LedgerJournalEntity).insert({\n userAddress: user,\n tokenAddress: token,\n delta: credit.amount,\n reason: \"BURN_FOR_CREDIT\",\n txHash,\n });\n\n await tx\n .getRepository(PendingCreditEntity)\n .update(\n { id: lockId },\n { status: \"RESOLVED\", txHash, resolvedAt: new Date() },\n );\n });\n\n this.logger?.log?.(`resolve pending credit ${lockId} tx=${txHash}`);\n }\n\n /**\n * Used by `BurnIndexer.matchLockId` to resolve an on-chain burn\n * event back to a pending credit row. Returns the oldest matching\n * `(user, token, amount, status: PENDING)` lockId, or undefined\n * when no match exists (unsolicited burn — indexer skips).\n */\n async findPendingCreditLockId(\n userAddress: Address,\n amount: bigint,\n tokenAddress: Address,\n ): Promise<string | undefined> {\n const { user, token } = normalize(userAddress, tokenAddress);\n const row = await this.dataSource\n .getRepository(PendingCreditEntity)\n .findOne({\n where: {\n userAddress: user,\n tokenAddress: token,\n amount,\n status: \"PENDING\",\n },\n order: { createdAt: \"ASC\" },\n });\n return row?.id;\n }\n\n // ---------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------\n\n private async sumPendingLocks(\n userAddress: Address,\n tokenAddress: Address,\n ): Promise<bigint> {\n const row = await this.dataSource\n .getRepository(LockedMintEntity)\n .createQueryBuilder(\"lock\")\n .select(\"COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)\", \"sum\")\n .where(\"lock.user_address = :user\", { user: userAddress })\n .andWhere(\"lock.token_address = :token\", { token: tokenAddress })\n .andWhere(\"lock.status = :pending\", { pending: \"PENDING\" })\n .andWhere(\"lock.expires_at > :now\", { now: new Date() })\n .getRawOne<{ sum: string }>();\n return row ? BigInt(row.sum) : 0n;\n }\n\n private toSdkLock(row: LockedMintEntity): LockedMintRequest {\n const out: LockedMintRequest = {\n lockId: row.id,\n userAddress: row.userAddress as Address,\n tokenAddress: row.tokenAddress as Address,\n amount: row.amount,\n status: row.status,\n createdAt: row.createdAt.getTime(),\n expiresAt: row.expiresAt.getTime(),\n };\n if (row.txHash) out.txHash = row.txHash as Hex;\n if (row.userOpHash) out.userOpHash = row.userOpHash as Hex;\n return out;\n }\n}\n\n/**\n * Multi-token guard — throw if `tokenAddress` is missing on any\n * mutating call. Single-token issuers must still pass their token\n * address explicitly so reads + writes never fall into a \"default\"\n * bucket the application never queries.\n */\nfunction normalize(\n userAddress: Address,\n tokenAddress: Address | undefined,\n): { user: Address; token: Address } {\n if (!tokenAddress) {\n throw new Error(\n \"PostgresPointLedger: tokenAddress is required on every call (multi-token ledger)\",\n );\n }\n return {\n user: getAddress(userAddress),\n token: getAddress(tokenAddress),\n };\n}\n","import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n PrimaryGeneratedColumn,\n} from \"typeorm\";\nimport type { MintingStatus } from \"@pafi-dev/issuer\";\n\n/**\n * A reservation against a user's off-chain balance.\n *\n * Lifecycle:\n * PENDING ── PointIndexer matches Mint event ──▶ MINTED\n * │\n * ├── deadline elapsed ────────────────────▶ EXPIRED\n * │\n * └── tx reverted ─────────────────────────▶ FAILED\n *\n * The `(userAddress, status)` composite index keeps the \"sum all\n * PENDING locks\" hot path of `lockForMinting()` fast under load.\n */\n@Entity({ name: \"locked_mint_requests\" })\n@Index([\"userAddress\", \"status\"])\nexport class LockedMintEntity {\n @PrimaryGeneratedColumn(\"uuid\")\n id!: string;\n\n @Column({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @Column({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"amount\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n amount!: bigint;\n\n @Column({\n name: \"status\",\n type: \"varchar\",\n length: 16,\n default: \"PENDING\",\n })\n status!: MintingStatus;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt!: Date;\n\n @Column({ name: \"expires_at\", type: \"timestamp with time zone\" })\n expiresAt!: Date;\n\n @Column({ name: \"tx_hash\", type: \"varchar\", length: 66, nullable: true })\n txHash?: string | null;\n\n /**\n * ERC-4337 userOpHash returned by the bundler at /claim/submit.\n * Bound to the lock so `/claim/status` can fall back to the bundler\n * receipt — required when multiple PENDING locks share the same\n * `amount` (PointIndexer matches by amount and can pick the wrong\n * sibling lock).\n */\n @Column({\n name: \"user_op_hash\",\n type: \"varchar\",\n length: 66,\n nullable: true,\n })\n userOpHash?: string | null;\n}\n","import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n PrimaryGeneratedColumn,\n} from \"typeorm\";\n\nexport type PendingCreditStatus = \"PENDING\" | \"RESOLVED\" | \"EXPIRED\";\n\n/**\n * Reverse flow — user burns on-chain PT, `BurnIndexer` observes\n * `Transfer(user → 0x0)`, the credit is settled to the off-chain ledger.\n *\n * Lifecycle:\n * PENDING ── Burn tx observed by indexer ──▶ RESOLVED\n * │\n * └── deadline elapsed ────────────────▶ EXPIRED\n *\n * The credit is reserved BEFORE the UserOp is submitted so the\n * indexer can correlate `(user, amount, token)` back to the off-chain\n * row when the burn lands.\n */\n@Entity({ name: \"pending_credits\" })\n@Index([\"userAddress\", \"status\"])\n@Index([\"txHash\"])\nexport class PendingCreditEntity {\n @PrimaryGeneratedColumn(\"uuid\")\n id!: string;\n\n @Column({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @Column({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"amount\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n amount!: bigint;\n\n @Column({\n name: \"status\",\n type: \"varchar\",\n length: 16,\n default: \"PENDING\",\n })\n status!: PendingCreditStatus;\n\n /** On-chain burn tx that settled this credit. Null until indexer resolves. */\n @Column({ name: \"tx_hash\", type: \"varchar\", length: 66, nullable: true })\n txHash?: string | null;\n\n /** ERC-4337 userOpHash bound at /redeem/submit. See `LockedMintEntity`. */\n @Column({\n name: \"user_op_hash\",\n type: \"varchar\",\n length: 66,\n nullable: true,\n })\n userOpHash?: string | null;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt!: Date;\n\n @Column({ name: \"expires_at\", type: \"timestamp with time zone\" })\n expiresAt!: Date;\n\n @Column({\n name: \"resolved_at\",\n type: \"timestamp with time zone\",\n nullable: true,\n })\n resolvedAt?: Date | null;\n}\n","import { Column, Entity, PrimaryColumn, UpdateDateColumn } from \"typeorm\";\n\n/**\n * Off-chain point balance per `(userAddress, tokenAddress)`.\n *\n * `balance` is the **total** owned; pending reservations live in\n * `LockedMintEntity`. Available balance = total − sum(PENDING locks)\n * — `getBalance` in `PostgresPointLedger` does this subtraction\n * inside a transaction so reads are race-free.\n *\n * All amounts are `numeric(78, 0)` for full bigint precision (uint256\n * fits in 78 decimal digits). TypeORM transforms bigint ↔ string at\n * the boundary; in JS/TS code we always deal with `bigint`.\n */\n@Entity({ name: \"user_balances\" })\nexport class UserBalanceEntity {\n @PrimaryColumn({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @PrimaryColumn({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"balance\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n default: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n balance!: bigint;\n\n @UpdateDateColumn({ name: \"updated_at\" })\n updatedAt!: Date;\n}\n","import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n PrimaryGeneratedColumn,\n} from \"typeorm\";\n\n/**\n * Append-only audit trail for every balance mutation. Used for\n * reconciliation, customer support, and regulatory reporting.\n *\n * Sign convention:\n * - positive `delta` — credit (merchant award, refund, manual top-up)\n * - negative `delta` — debit (mint confirmation against the off-chain\n * balance; `txHash` references the on-chain Mint event)\n */\n@Entity({ name: \"ledger_journal\" })\n@Index([\"userAddress\", \"createdAt\"])\nexport class LedgerJournalEntity {\n @PrimaryGeneratedColumn(\"uuid\")\n id!: string;\n\n @Column({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @Column({ name: \"token_address\", type: \"varchar\", length: 42 })\n tokenAddress!: string;\n\n @Column({\n name: \"delta\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n delta!: bigint;\n\n @Column({ name: \"reason\", type: \"varchar\", length: 128 })\n reason!: string;\n\n @Column({ name: \"tx_hash\", type: \"varchar\", length: 66, nullable: true })\n txHash?: string | null;\n\n @CreateDateColumn({ name: \"created_at\" })\n createdAt!: Date;\n}\n","import { Column, Entity, PrimaryColumn, UpdateDateColumn } from \"typeorm\";\n\n/**\n * Persistent cursor for `PointIndexer` / `BurnIndexer`. Multiple rows\n * coexist keyed by `id` (e.g. `default` for the mint indexer,\n * `burn:0x...` for each per-token burn indexer).\n *\n * Stores the **next** block to scan, not the last processed one.\n * Indexer reads on startup and resumes from there.\n */\n@Entity({ name: \"indexer_cursors\" })\nexport class IndexerCursorEntity {\n @PrimaryColumn({ type: \"varchar\", length: 64 })\n id!: string;\n\n @Column({\n name: \"next_block\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n nextBlock!: bigint;\n\n @UpdateDateColumn({ name: \"updated_at\" })\n updatedAt!: Date;\n}\n","import type { DataSource } from \"typeorm\";\nimport type { IIndexerCursorStore } from \"@pafi-dev/issuer\";\n\nimport { IndexerCursorEntity } from \"./entities/indexer-cursor.entity\";\n\n/**\n * Postgres-backed indexer cursor store. Lets indexers survive\n * restarts — on boot, the indexer reads the last persisted block and\n * resumes scanning from there.\n *\n * Multiple indexers (e.g. `PointIndexer` for Mint events +\n * `BurnIndexer` per token for Transfer→0x0) share the same table via\n * different `cursorId`s. Construct with `cursorId = \"default\"` for\n * the primary mint indexer, or call `forKey(id)` to derive a sibling\n * store for a secondary indexer.\n */\nexport class PostgresCursorStore implements IIndexerCursorStore {\n constructor(\n private readonly dataSource: DataSource,\n private readonly cursorId: string = \"default\",\n ) {}\n\n async load(): Promise<bigint | undefined> {\n const row = await this.dataSource\n .getRepository(IndexerCursorEntity)\n .findOne({ where: { id: this.cursorId } });\n return row?.nextBlock;\n }\n\n async save(blockNumber: bigint): Promise<void> {\n await this.dataSource\n .getRepository(IndexerCursorEntity)\n .upsert(\n { id: this.cursorId, nextBlock: blockNumber },\n { conflictPaths: [\"id\"] },\n );\n }\n\n /** Derived store keyed by a different `id` — for sibling indexers. */\n forKey(cursorId: string): IIndexerCursorStore {\n return new PostgresCursorStore(this.dataSource, cursorId);\n }\n}\n","import type { DataSource } from \"typeorm\";\nimport { getAddress, type Address } from \"viem\";\nimport type { IRedemptionHistoryStore } from \"@pafi-dev/issuer\";\n\nimport { RedemptionHistoryEntity } from \"./entities/redemption-history.entity\";\n\n/**\n * Postgres-backed IRedemptionHistoryStore. Append-only — every\n * `recordRedemption` writes a new row. Reads (`sumRedeemedSince` /\n * `getLastRedeemedAtUnixSec`) hit the (user, time) composite index.\n *\n * Addresses are normalized to checksum form on write and lower-cased\n * on read so the index works regardless of casing inconsistency. The\n * entity stores the canonical (checksummed) form.\n */\nexport class PostgresRedemptionHistoryStore implements IRedemptionHistoryStore {\n constructor(private readonly dataSource: DataSource) {}\n\n async sumRedeemedSince(\n user: Address,\n sinceUnixSec: number,\n pointTokenAddress?: Address,\n ): Promise<bigint> {\n const repo = this.dataSource.getRepository(RedemptionHistoryEntity);\n const qb = repo\n .createQueryBuilder(\"rh\")\n .select(\"COALESCE(SUM(rh.amount_pt), 0)\", \"sum\")\n .where(\"rh.user_address = :user\", { user: getAddress(user) })\n .andWhere(\"rh.created_at_unix_sec >= :since\", { since: sinceUnixSec });\n\n if (pointTokenAddress !== undefined) {\n qb.andWhere(\"rh.token_address = :token\", {\n token: getAddress(pointTokenAddress),\n });\n } else {\n // When the caller didn't scope by token, sum across all tokens\n // for that user. Don't filter on token_address IS NULL — that\n // would silently miss entries that DID record a token.\n }\n\n const row = (await qb.getRawOne<{ sum: string | null }>()) ?? { sum: \"0\" };\n return BigInt(row.sum ?? \"0\");\n }\n\n async getLastRedeemedAtUnixSec(\n user: Address,\n pointTokenAddress?: Address,\n ): Promise<number | null> {\n const repo = this.dataSource.getRepository(RedemptionHistoryEntity);\n const qb = repo\n .createQueryBuilder(\"rh\")\n .select(\"rh.created_at_unix_sec\", \"ts\")\n .where(\"rh.user_address = :user\", { user: getAddress(user) })\n .orderBy(\"rh.created_at_unix_sec\", \"DESC\")\n .limit(1);\n\n if (pointTokenAddress !== undefined) {\n qb.andWhere(\"rh.token_address = :token\", {\n token: getAddress(pointTokenAddress),\n });\n }\n\n const row = await qb.getRawOne<{ ts: string | null }>();\n if (!row || row.ts === null) return null;\n return Number(row.ts);\n }\n\n async recordRedemption(entry: {\n user: Address;\n amountPt: bigint;\n pointTokenAddress?: Address;\n unixSec: number;\n reservationId?: string;\n }): Promise<void> {\n const repo = this.dataSource.getRepository(RedemptionHistoryEntity);\n const row = repo.create({\n userAddress: getAddress(entry.user),\n tokenAddress: entry.pointTokenAddress\n ? getAddress(entry.pointTokenAddress)\n : null,\n amountPt: entry.amountPt,\n createdAtUnixSec: String(entry.unixSec),\n reservationId: entry.reservationId ?? null,\n });\n await repo.save(row);\n }\n}\n","import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n PrimaryGeneratedColumn,\n} from \"typeorm\";\n\n/**\n * Per-user redemption history row. One row per successful initiate\n * (call to `RedemptionService.recordSuccessfulInitiate`).\n *\n * `sumRedeemedSince` does a SUM(amount) WHERE created_at >= :since\n * which uses the (user_address, created_at) composite index. We do\n * NOT prune old rows automatically — they're cheap and useful for\n * audit. Issuers can add a periodic VACUUM/partition policy if the\n * table grows past ~100M rows.\n *\n * Amounts are `numeric(78, 0)` for full bigint precision.\n */\n@Entity({ name: \"redemption_history\" })\n@Index(\"idx_redemption_history_user_created\", [\n \"userAddress\",\n \"createdAtUnixSec\",\n])\nexport class RedemptionHistoryEntity {\n @PrimaryGeneratedColumn(\"uuid\")\n id!: string;\n\n @Column({ name: \"user_address\", type: \"varchar\", length: 42 })\n userAddress!: string;\n\n @Column({ name: \"token_address\", type: \"varchar\", length: 42, nullable: true })\n tokenAddress!: string | null;\n\n @Column({\n name: \"amount_pt\",\n type: \"numeric\",\n precision: 78,\n scale: 0,\n transformer: {\n to: (value: bigint) => value.toString(),\n from: (value: string) => BigInt(value),\n },\n })\n amountPt!: bigint;\n\n /**\n * Caller-controlled timestamp (unix seconds). Stored as integer, not\n * timestamptz, because the evaluator works in unix seconds and we want\n * the same time domain on read + write — no surprise tz conversions.\n */\n @Column({ name: \"created_at_unix_sec\", type: \"bigint\" })\n createdAtUnixSec!: string;\n\n /**\n * Optional pointer back to the burn-flow reservation (PendingCredit.id).\n * Lets ops trace a redemption-history row to the underlying lock.\n */\n @Column({\n name: \"reservation_id\",\n type: \"varchar\",\n length: 64,\n nullable: true,\n })\n reservationId!: string | null;\n\n @CreateDateColumn({ name: \"row_created_at\", type: \"timestamptz\" })\n rowCreatedAt!: Date;\n}\n","export { LockedMintEntity } from \"./locked-mint.entity\";\nexport {\n PendingCreditEntity,\n type PendingCreditStatus,\n} from \"./pending-credit.entity\";\nexport { UserBalanceEntity } from \"./user-balance.entity\";\nexport { LedgerJournalEntity } from \"./ledger-journal.entity\";\nexport { IndexerCursorEntity } from \"./indexer-cursor.entity\";\nexport { RedemptionHistoryEntity } from \"./redemption-history.entity\";\n\nimport { LockedMintEntity } from \"./locked-mint.entity\";\nimport { PendingCreditEntity } from \"./pending-credit.entity\";\nimport { UserBalanceEntity } from \"./user-balance.entity\";\nimport { LedgerJournalEntity } from \"./ledger-journal.entity\";\nimport { IndexerCursorEntity } from \"./indexer-cursor.entity\";\nimport { RedemptionHistoryEntity } from \"./redemption-history.entity\";\n\n/**\n * All entities in one array — drop into TypeORM's `entities` config or\n * NestJS's `TypeOrmModule.forFeature(PAFI_ENTITIES)`.\n */\nexport const PAFI_ENTITIES = [\n LockedMintEntity,\n PendingCreditEntity,\n UserBalanceEntity,\n LedgerJournalEntity,\n IndexerCursorEntity,\n RedemptionHistoryEntity,\n] as const;\n","import type { MigrationInterface, QueryRunner } from \"typeorm\";\n\n/**\n * Single consolidated initial schema for `@pafi-dev/issuer-postgres`.\n *\n * Combines what gg56 split into two migrations (`InitialSchema` +\n * `AddPendingCredits`) plus the `user_op_hash` column, since this is\n * the v1.4-and-after baseline. Issuers adopting the SDK from scratch\n * apply this once.\n *\n * Tables:\n * user_balances — off-chain point balance per (user, token)\n * locked_mint_requests — reservations during mint flow\n * pending_credits — reserved credits during burn/redeem flow\n * ledger_journal — append-only audit trail of every delta\n * indexer_cursors — PointIndexer / BurnIndexer block cursors\n *\n * Issuer-specific extensions (campaign rules, KYC tables, custom\n * scenarios) belong in a follow-up migration — never edit this file\n * in place once it ships.\n */\nexport class InitialSchema1700000000000 implements MigrationInterface {\n name = \"InitialSchema1700000000000\";\n\n public async up(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"`);\n\n // ─── user_balances ──────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"user_balances\" (\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"balance\" numeric(78, 0) NOT NULL DEFAULT 0,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT now(),\n CONSTRAINT \"PK_user_balances\" PRIMARY KEY (\"user_address\", \"token_address\")\n )\n `);\n\n // ─── locked_mint_requests ───────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"locked_mint_requests\" (\n \"id\" uuid NOT NULL DEFAULT gen_random_uuid(),\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"amount\" numeric(78, 0) NOT NULL,\n \"status\" varchar(16) NOT NULL DEFAULT 'PENDING',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT now(),\n \"expires_at\" TIMESTAMP WITH TIME ZONE NOT NULL,\n \"tx_hash\" varchar(66),\n \"user_op_hash\" varchar(66),\n CONSTRAINT \"PK_locked_mint_requests\" PRIMARY KEY (\"id\")\n )\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_locked_mint_user_status\"\n ON \"locked_mint_requests\" (\"user_address\", \"token_address\", \"status\")\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_locked_mint_user_op_hash\"\n ON \"locked_mint_requests\" (\"user_op_hash\")\n `);\n\n // ─── pending_credits ────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"pending_credits\" (\n \"id\" uuid NOT NULL DEFAULT gen_random_uuid(),\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"amount\" numeric(78, 0) NOT NULL,\n \"status\" varchar(16) NOT NULL DEFAULT 'PENDING',\n \"tx_hash\" varchar(66),\n \"user_op_hash\" varchar(66),\n \"created_at\" TIMESTAMP NOT NULL DEFAULT now(),\n \"expires_at\" TIMESTAMP WITH TIME ZONE NOT NULL,\n \"resolved_at\" TIMESTAMP WITH TIME ZONE,\n CONSTRAINT \"PK_pending_credits\" PRIMARY KEY (\"id\")\n )\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_pending_credits_user_status\"\n ON \"pending_credits\" (\"user_address\", \"token_address\", \"status\")\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_pending_credits_tx_hash\"\n ON \"pending_credits\" (\"tx_hash\")\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_pending_credits_user_op_hash\"\n ON \"pending_credits\" (\"user_op_hash\")\n `);\n\n // ─── ledger_journal ─────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"ledger_journal\" (\n \"id\" uuid NOT NULL DEFAULT gen_random_uuid(),\n \"user_address\" varchar(42) NOT NULL,\n \"token_address\" varchar(42) NOT NULL,\n \"delta\" numeric(78, 0) NOT NULL,\n \"reason\" varchar(128) NOT NULL,\n \"tx_hash\" varchar(66),\n \"created_at\" TIMESTAMP NOT NULL DEFAULT now(),\n CONSTRAINT \"PK_ledger_journal\" PRIMARY KEY (\"id\")\n )\n `);\n await queryRunner.query(`\n CREATE INDEX \"IDX_ledger_journal_user_created\"\n ON \"ledger_journal\" (\"user_address\", \"token_address\", \"created_at\")\n `);\n\n // ─── indexer_cursors ────────────────────────────────────────────\n await queryRunner.query(`\n CREATE TABLE \"indexer_cursors\" (\n \"id\" varchar(64) NOT NULL,\n \"next_block\" numeric(78, 0) NOT NULL,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT now(),\n CONSTRAINT \"PK_indexer_cursors\" PRIMARY KEY (\"id\")\n )\n `);\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`DROP TABLE \"indexer_cursors\"`);\n await queryRunner.query(`DROP INDEX \"IDX_ledger_journal_user_created\"`);\n await queryRunner.query(`DROP TABLE \"ledger_journal\"`);\n await queryRunner.query(`DROP INDEX \"IDX_pending_credits_user_op_hash\"`);\n await queryRunner.query(`DROP INDEX \"IDX_pending_credits_tx_hash\"`);\n await queryRunner.query(`DROP INDEX \"IDX_pending_credits_user_status\"`);\n await queryRunner.query(`DROP TABLE \"pending_credits\"`);\n await queryRunner.query(`DROP INDEX \"IDX_locked_mint_user_op_hash\"`);\n await queryRunner.query(`DROP INDEX \"IDX_locked_mint_user_status\"`);\n await queryRunner.query(`DROP TABLE \"locked_mint_requests\"`);\n await queryRunner.query(`DROP TABLE \"user_balances\"`);\n }\n}\n","import { MigrationInterface, QueryRunner } from \"typeorm\";\n\n/**\n * Adds the `redemption_history` table — append-only log of successful\n * redemption initiates, indexed by (user, time) for the daily-limit\n * SUM() query.\n *\n * This is INDEPENDENT from the main InitialSchema (different timestamp,\n * later than 1700000000000). Issuers who already deployed InitialSchema\n * apply this on top.\n */\nexport class CreateRedemptionHistory1746230400001 implements MigrationInterface {\n name = \"CreateRedemptionHistory1746230400001\";\n\n public async up(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(`\n CREATE TABLE IF NOT EXISTS redemption_history (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n user_address varchar(42) NOT NULL,\n token_address varchar(42),\n amount_pt numeric(78, 0) NOT NULL,\n created_at_unix_sec bigint NOT NULL,\n reservation_id varchar(64),\n row_created_at timestamptz NOT NULL DEFAULT NOW(),\n CONSTRAINT redemption_history_amount_positive CHECK (amount_pt > 0)\n )\n `);\n\n await queryRunner.query(`\n CREATE INDEX IF NOT EXISTS idx_redemption_history_user_created\n ON redemption_history (user_address, created_at_unix_sec DESC)\n `);\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.query(\n `DROP INDEX IF EXISTS idx_redemption_history_user_created`,\n );\n await queryRunner.query(`DROP TABLE IF EXISTS redemption_history`);\n }\n}\n","export { InitialSchema1700000000000 } from \"./1700000000000-InitialSchema\";\nexport { CreateRedemptionHistory1746230400001 } from \"./1746230400001-CreateRedemptionHistory\";\n\nimport { InitialSchema1700000000000 } from \"./1700000000000-InitialSchema\";\nimport { CreateRedemptionHistory1746230400001 } from \"./1746230400001-CreateRedemptionHistory\";\n\n/**\n * All shipped migrations in chronological order. Drop into TypeORM's\n * `migrations` config:\n *\n * import { PAFI_MIGRATIONS } from \"@pafi-dev/issuer-postgres/migrations\";\n *\n * new DataSource({\n * entities: [...PAFI_ENTITIES, ...yourCustomEntities],\n * migrations: [...PAFI_MIGRATIONS, ...yourCustomMigrations],\n * });\n */\nexport const PAFI_MIGRATIONS = [\n InitialSchema1700000000000,\n CreateRedemptionHistory1746230400001,\n] as const;\n"],"mappings":"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","names":["import_typeorm","import_typeorm","import_typeorm","import_typeorm","import_viem","import_typeorm"]}