@pafi-dev/issuer-postgres 0.2.0 → 0.4.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/README.md CHANGED
@@ -10,10 +10,9 @@ schema from the reference issuer.
10
10
 
11
11
  > **Server-only.** Pulls in TypeORM (peer-dep). Do not bundle into a browser app.
12
12
 
13
- > **Compatible with `@pafi-dev/issuer@0.6.x`.** Schema baseline includes
13
+ > **Compatible with `@pafi-dev/issuer`.** Schema baseline includes
14
14
  > `user_op_hash` columns + indexes for the mobile prepare/submit
15
- > bundler-receipt fallback. No migration needed when bumping issuer
16
- > SDK within the 0.6 line.
15
+ > bundler-receipt fallback.
17
16
 
18
17
  ---
19
18
 
@@ -70,6 +69,11 @@ import {
70
69
  entities: [...PAFI_ENTITIES /*, ...yourCustomEntities */],
71
70
  migrations: [...PAFI_MIGRATIONS /*, ...yourCustomMigrations */],
72
71
  migrationsRun: true,
72
+ // Required — some shipped migrations use `CREATE INDEX
73
+ // CONCURRENTLY` which cannot run inside a transaction.
74
+ // The default "all" mode wraps every migration in one tx
75
+ // and rejects per-migration `transaction = false` opt-outs.
76
+ migrationsTransactionMode: "each",
73
77
  }),
74
78
  ],
75
79
  providers: [
@@ -255,33 +259,68 @@ export class LockSweepService {
255
259
  @Interval(5 * 60 * 1000)
256
260
  async sweep() {
257
261
  try {
258
- const swept = await this.ledger.markExpiredLocks();
259
- if (swept > 0) this.logger.debug(`expired ${swept} mint locks`);
262
+ const sweptLocks = await this.ledger.markExpiredLocks();
263
+ const sweptCredits = await this.ledger.markExpiredCredits();
264
+ if (sweptLocks > 0 || sweptCredits > 0) {
265
+ this.logger.debug(
266
+ `expired ${sweptLocks} mint locks, ${sweptCredits} pending credits`,
267
+ );
268
+ }
260
269
  } catch (err) {
261
- this.logger.error("lock sweep failed", err);
270
+ this.logger.error("ledger sweep failed", err);
262
271
  }
263
272
  }
264
273
  }
265
274
  ```
266
275
 
267
- Also wire `markExpiredCredits()` (PendingCredit equivalent) the same way.
276
+ **Audit PACI5-20** — both `markExpiredLocks()` (mint side) and
277
+ `markExpiredCredits()` (burn/credit side) MUST be wired. Without the
278
+ credit-side sweep, abandoned PendingCredit reservations accumulate
279
+ forever and `findPendingCreditLockId` would risk attributing fresh
280
+ on-chain burns to stale (user, amount) rows. As of
281
+ `@pafi-dev/issuer-postgres@0.4.0` the lookup also filters
282
+ `expires_at > now()` and orders newest-first as defense-in-depth in
283
+ case the sweep tick is delayed.
268
284
 
269
285
  ### 4. Required indexes
270
286
 
271
- The base migration ships with these indexes — verify they're applied:
287
+ Shipped migrations (`InitialSchema` + `AddLockedMintCompositeIndexes`)
288
+ create these indexes on `locked_mint_requests`. Verify after migration:
289
+
290
+ ```sql
291
+ SELECT indexname, indexdef
292
+ FROM pg_indexes
293
+ WHERE tablename = 'locked_mint_requests'
294
+ ORDER BY indexname;
295
+ ```
296
+
297
+ Expected output:
272
298
 
273
299
  ```sql
274
300
  -- Hot path: sumPendingLocks (during getBalance + lockForMinting)
275
- CREATE INDEX IF NOT EXISTS idx_locked_mint_user_token_status_expires
301
+ CREATE INDEX "IDX_locked_mint_user_token_status_expires"
276
302
  ON locked_mint_requests (user_address, token_address, status, expires_at);
277
303
 
278
- -- Hot path: PointIndexer matching by (user, amount, status=PENDING)
279
- CREATE INDEX IF NOT EXISTS idx_locked_mint_user_token_amount_status
304
+ -- Hot path: PointIndexer.pickMatchingLock + deductBalance findOne
305
+ CREATE INDEX "IDX_locked_mint_user_token_amount_status"
280
306
  ON locked_mint_requests (user_address, token_address, amount, status);
281
307
 
282
- -- Sweep path: markExpiredLocks UPDATE
283
- CREATE INDEX IF NOT EXISTS idx_locked_mint_status_expires
284
- ON locked_mint_requests (status, expires_at) WHERE status = 'PENDING';
308
+ -- Sweep path: markExpiredLocks UPDATE (partial — only PENDING rows)
309
+ CREATE INDEX "IDX_locked_mint_pending_expires"
310
+ ON locked_mint_requests (expires_at) WHERE status = 'PENDING';
311
+
312
+ -- Bundler-receipt fallback in statusHandlers
313
+ CREATE INDEX "IDX_locked_mint_user_op_hash"
314
+ ON locked_mint_requests (user_op_hash);
315
+ ```
316
+
317
+ Plus the partial unique index on `ledger_journal` that enforces
318
+ indexer idempotency (`MINT_CONFIRMED`, `BURN_FOR_CREDIT`):
319
+
320
+ ```sql
321
+ CREATE UNIQUE INDEX "UQ_ledger_journal_user_tx_reason"
322
+ ON ledger_journal (user_address, tx_hash, reason)
323
+ WHERE tx_hash IS NOT NULL;
285
324
  ```
286
325
 
287
326
  ### 5. Monitoring queries
@@ -99,7 +99,22 @@ __decorateClass([
99
99
  ], LockedMintEntity.prototype, "userOpHash", 2);
100
100
  LockedMintEntity = __decorateClass([
101
101
  (0, import_typeorm.Entity)({ name: "locked_mint_requests" }),
102
- (0, import_typeorm.Index)(["userAddress", "status"])
102
+ (0, import_typeorm.Index)("IDX_locked_mint_user_token_status_expires", [
103
+ "userAddress",
104
+ "tokenAddress",
105
+ "status",
106
+ "expiresAt"
107
+ ]),
108
+ (0, import_typeorm.Index)("IDX_locked_mint_user_token_amount_status", [
109
+ "userAddress",
110
+ "tokenAddress",
111
+ "amount",
112
+ "status"
113
+ ]),
114
+ (0, import_typeorm.Index)("IDX_locked_mint_pending_expires", ["expiresAt"], {
115
+ where: `"status" = 'PENDING'`
116
+ }),
117
+ (0, import_typeorm.Index)("IDX_locked_mint_user_op_hash", ["userOpHash"])
103
118
  ], LockedMintEntity);
104
119
 
105
120
  // src/entities/pending-credit.entity.ts
@@ -252,7 +267,15 @@ __decorateClass([
252
267
  ], LedgerJournalEntity.prototype, "createdAt", 2);
253
268
  LedgerJournalEntity = __decorateClass([
254
269
  (0, import_typeorm4.Entity)({ name: "ledger_journal" }),
255
- (0, import_typeorm4.Index)(["userAddress", "createdAt"])
270
+ (0, import_typeorm4.Index)(["userAddress", "createdAt"]),
271
+ (0, import_typeorm4.Index)(
272
+ "UQ_ledger_journal_user_token_tx_reason",
273
+ ["userAddress", "tokenAddress", "txHash", "reason"],
274
+ {
275
+ unique: true,
276
+ where: '"tx_hash" IS NOT NULL'
277
+ }
278
+ )
256
279
  ], LedgerJournalEntity);
257
280
 
258
281
  // src/entities/indexer-cursor.entity.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/entities/index.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/entities/redemption-history.entity.ts"],"sourcesContent":["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 {\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 {\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"],"mappings":"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","names":["import_typeorm","import_typeorm","import_typeorm","import_typeorm","import_typeorm"]}
1
+ {"version":3,"sources":["../../src/entities/index.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/entities/redemption-history.entity.ts"],"sourcesContent":["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 {\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 * Index policy — the three composite indexes below mirror the three\n * query shapes the SDK runs against this table. They are the source\n * of truth for the schema; the migrations create indexes with the\n * exact same names so `synchronize: false` deployments do not drift.\n *\n * IDX_locked_mint_user_token_status_expires\n * Hot path: `sumPendingLocks` inside `getBalance` + `lockForMinting`.\n * 4-col covering index — `expires_at` in the range scan instead\n * of post-filtered from the heap.\n *\n * IDX_locked_mint_user_token_amount_status\n * Hot path: `PointIndexer.pickMatchingLock` + the lock-resolution\n * `findOne` inside `deductBalance`. `amount` is the\n * selectivity-critical predicate.\n *\n * IDX_locked_mint_pending_expires\n * Sweep path: `markExpiredLocks` UPDATE. Partial\n * `WHERE status = 'PENDING'` keeps the index small — only the\n * rows the sweep can touch live in the index.\n *\n * IDX_locked_mint_user_op_hash\n * Bundler-receipt fallback in `statusHandlers` — point lookup\n * by userOpHash.\n */\n@Entity({ name: \"locked_mint_requests\" })\n@Index(\"IDX_locked_mint_user_token_status_expires\", [\n \"userAddress\",\n \"tokenAddress\",\n \"status\",\n \"expiresAt\",\n])\n@Index(\"IDX_locked_mint_user_token_amount_status\", [\n \"userAddress\",\n \"tokenAddress\",\n \"amount\",\n \"status\",\n])\n@Index(\"IDX_locked_mint_pending_expires\", [\"expiresAt\"], {\n where: \"\\\"status\\\" = 'PENDING'\",\n})\n@Index(\"IDX_locked_mint_user_op_hash\", [\"userOpHash\"])\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 * Idempotency invariant: for any indexer-driven reason (e.g.\n * `MINT_CONFIRMED`, `BURN_FOR_CREDIT`) the tuple\n * `(user_address, token_address, tx_hash, reason)` is unique. Enforced\n * by a partial unique index that only fires when `tx_hash IS NOT NULL`,\n * so off-chain reasons (e.g. `AIRDROP`) without a tx hash are\n * unaffected. Last-line defense against double-deduct / double-credit\n * when the indexer replays an event (reorg, restart, duplicate pod).\n *\n */\n@Entity({ name: \"ledger_journal\" })\n@Index([\"userAddress\", \"createdAt\"])\n@Index(\n \"UQ_ledger_journal_user_token_tx_reason\",\n [\"userAddress\", \"tokenAddress\", \"txHash\", \"reason\"],\n {\n unique: true,\n where: '\"tx_hash\" IS NOT NULL',\n },\n)\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 {\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"],"mappings":"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","names":["import_typeorm","import_typeorm","import_typeorm","import_typeorm","import_typeorm"]}
@@ -10,8 +10,29 @@ import { MintingStatus } from '@pafi-dev/issuer';
10
10
  * │
11
11
  * └── tx reverted ─────────────────────────▶ FAILED
12
12
  *
13
- * The `(userAddress, status)` composite index keeps the "sum all
14
- * PENDING locks" hot path of `lockForMinting()` fast under load.
13
+ * Index policy the three composite indexes below mirror the three
14
+ * query shapes the SDK runs against this table. They are the source
15
+ * of truth for the schema; the migrations create indexes with the
16
+ * exact same names so `synchronize: false` deployments do not drift.
17
+ *
18
+ * IDX_locked_mint_user_token_status_expires
19
+ * Hot path: `sumPendingLocks` inside `getBalance` + `lockForMinting`.
20
+ * 4-col covering index — `expires_at` in the range scan instead
21
+ * of post-filtered from the heap.
22
+ *
23
+ * IDX_locked_mint_user_token_amount_status
24
+ * Hot path: `PointIndexer.pickMatchingLock` + the lock-resolution
25
+ * `findOne` inside `deductBalance`. `amount` is the
26
+ * selectivity-critical predicate.
27
+ *
28
+ * IDX_locked_mint_pending_expires
29
+ * Sweep path: `markExpiredLocks` UPDATE. Partial
30
+ * `WHERE status = 'PENDING'` keeps the index small — only the
31
+ * rows the sweep can touch live in the index.
32
+ *
33
+ * IDX_locked_mint_user_op_hash
34
+ * Bundler-receipt fallback in `statusHandlers` — point lookup
35
+ * by userOpHash.
15
36
  */
16
37
  declare class LockedMintEntity {
17
38
  id: string;
@@ -88,6 +109,15 @@ declare class UserBalanceEntity {
88
109
  * - positive `delta` — credit (merchant award, refund, manual top-up)
89
110
  * - negative `delta` — debit (mint confirmation against the off-chain
90
111
  * balance; `txHash` references the on-chain Mint event)
112
+ *
113
+ * Idempotency invariant: for any indexer-driven reason (e.g.
114
+ * `MINT_CONFIRMED`, `BURN_FOR_CREDIT`) the tuple
115
+ * `(user_address, token_address, tx_hash, reason)` is unique. Enforced
116
+ * by a partial unique index that only fires when `tx_hash IS NOT NULL`,
117
+ * so off-chain reasons (e.g. `AIRDROP`) without a tx hash are
118
+ * unaffected. Last-line defense against double-deduct / double-credit
119
+ * when the indexer replays an event (reorg, restart, duplicate pod).
120
+ *
91
121
  */
92
122
  declare class LedgerJournalEntity {
93
123
  id: string;
@@ -10,8 +10,29 @@ import { MintingStatus } from '@pafi-dev/issuer';
10
10
  * │
11
11
  * └── tx reverted ─────────────────────────▶ FAILED
12
12
  *
13
- * The `(userAddress, status)` composite index keeps the "sum all
14
- * PENDING locks" hot path of `lockForMinting()` fast under load.
13
+ * Index policy the three composite indexes below mirror the three
14
+ * query shapes the SDK runs against this table. They are the source
15
+ * of truth for the schema; the migrations create indexes with the
16
+ * exact same names so `synchronize: false` deployments do not drift.
17
+ *
18
+ * IDX_locked_mint_user_token_status_expires
19
+ * Hot path: `sumPendingLocks` inside `getBalance` + `lockForMinting`.
20
+ * 4-col covering index — `expires_at` in the range scan instead
21
+ * of post-filtered from the heap.
22
+ *
23
+ * IDX_locked_mint_user_token_amount_status
24
+ * Hot path: `PointIndexer.pickMatchingLock` + the lock-resolution
25
+ * `findOne` inside `deductBalance`. `amount` is the
26
+ * selectivity-critical predicate.
27
+ *
28
+ * IDX_locked_mint_pending_expires
29
+ * Sweep path: `markExpiredLocks` UPDATE. Partial
30
+ * `WHERE status = 'PENDING'` keeps the index small — only the
31
+ * rows the sweep can touch live in the index.
32
+ *
33
+ * IDX_locked_mint_user_op_hash
34
+ * Bundler-receipt fallback in `statusHandlers` — point lookup
35
+ * by userOpHash.
15
36
  */
16
37
  declare class LockedMintEntity {
17
38
  id: string;
@@ -88,6 +109,15 @@ declare class UserBalanceEntity {
88
109
  * - positive `delta` — credit (merchant award, refund, manual top-up)
89
110
  * - negative `delta` — debit (mint confirmation against the off-chain
90
111
  * balance; `txHash` references the on-chain Mint event)
112
+ *
113
+ * Idempotency invariant: for any indexer-driven reason (e.g.
114
+ * `MINT_CONFIRMED`, `BURN_FOR_CREDIT`) the tuple
115
+ * `(user_address, token_address, tx_hash, reason)` is unique. Enforced
116
+ * by a partial unique index that only fires when `tx_hash IS NOT NULL`,
117
+ * so off-chain reasons (e.g. `AIRDROP`) without a tx hash are
118
+ * unaffected. Last-line defense against double-deduct / double-credit
119
+ * when the indexer replays an event (reorg, restart, duplicate pod).
120
+ *
91
121
  */
92
122
  declare class LedgerJournalEntity {
93
123
  id: string;
@@ -76,7 +76,22 @@ __decorateClass([
76
76
  ], LockedMintEntity.prototype, "userOpHash", 2);
77
77
  LockedMintEntity = __decorateClass([
78
78
  Entity({ name: "locked_mint_requests" }),
79
- Index(["userAddress", "status"])
79
+ Index("IDX_locked_mint_user_token_status_expires", [
80
+ "userAddress",
81
+ "tokenAddress",
82
+ "status",
83
+ "expiresAt"
84
+ ]),
85
+ Index("IDX_locked_mint_user_token_amount_status", [
86
+ "userAddress",
87
+ "tokenAddress",
88
+ "amount",
89
+ "status"
90
+ ]),
91
+ Index("IDX_locked_mint_pending_expires", ["expiresAt"], {
92
+ where: `"status" = 'PENDING'`
93
+ }),
94
+ Index("IDX_locked_mint_user_op_hash", ["userOpHash"])
80
95
  ], LockedMintEntity);
81
96
 
82
97
  // src/entities/pending-credit.entity.ts
@@ -241,7 +256,15 @@ __decorateClass([
241
256
  ], LedgerJournalEntity.prototype, "createdAt", 2);
242
257
  LedgerJournalEntity = __decorateClass([
243
258
  Entity4({ name: "ledger_journal" }),
244
- Index3(["userAddress", "createdAt"])
259
+ Index3(["userAddress", "createdAt"]),
260
+ Index3(
261
+ "UQ_ledger_journal_user_token_tx_reason",
262
+ ["userAddress", "tokenAddress", "txHash", "reason"],
263
+ {
264
+ unique: true,
265
+ where: '"tx_hash" IS NOT NULL'
266
+ }
267
+ )
245
268
  ], LedgerJournalEntity);
246
269
 
247
270
  // src/entities/indexer-cursor.entity.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../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/entities/redemption-history.entity.ts","../../src/entities/index.ts"],"sourcesContent":["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 {\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"],"mappings":"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","names":["Column","CreateDateColumn","Entity","Index","PrimaryGeneratedColumn","PrimaryGeneratedColumn","Column","CreateDateColumn","Entity","Index","Column","Entity","Column","Entity","Column","CreateDateColumn","Entity","Index","PrimaryGeneratedColumn","PrimaryGeneratedColumn","Column","CreateDateColumn","Entity","Index","Column","Entity","PrimaryColumn","UpdateDateColumn","PrimaryColumn","Column","UpdateDateColumn","Entity","Column","CreateDateColumn","Entity","Index","PrimaryGeneratedColumn","PrimaryGeneratedColumn","Column","CreateDateColumn","Entity","Index"]}
1
+ {"version":3,"sources":["../../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/entities/redemption-history.entity.ts","../../src/entities/index.ts"],"sourcesContent":["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 * Index policy — the three composite indexes below mirror the three\n * query shapes the SDK runs against this table. They are the source\n * of truth for the schema; the migrations create indexes with the\n * exact same names so `synchronize: false` deployments do not drift.\n *\n * IDX_locked_mint_user_token_status_expires\n * Hot path: `sumPendingLocks` inside `getBalance` + `lockForMinting`.\n * 4-col covering index — `expires_at` in the range scan instead\n * of post-filtered from the heap.\n *\n * IDX_locked_mint_user_token_amount_status\n * Hot path: `PointIndexer.pickMatchingLock` + the lock-resolution\n * `findOne` inside `deductBalance`. `amount` is the\n * selectivity-critical predicate.\n *\n * IDX_locked_mint_pending_expires\n * Sweep path: `markExpiredLocks` UPDATE. Partial\n * `WHERE status = 'PENDING'` keeps the index small — only the\n * rows the sweep can touch live in the index.\n *\n * IDX_locked_mint_user_op_hash\n * Bundler-receipt fallback in `statusHandlers` — point lookup\n * by userOpHash.\n */\n@Entity({ name: \"locked_mint_requests\" })\n@Index(\"IDX_locked_mint_user_token_status_expires\", [\n \"userAddress\",\n \"tokenAddress\",\n \"status\",\n \"expiresAt\",\n])\n@Index(\"IDX_locked_mint_user_token_amount_status\", [\n \"userAddress\",\n \"tokenAddress\",\n \"amount\",\n \"status\",\n])\n@Index(\"IDX_locked_mint_pending_expires\", [\"expiresAt\"], {\n where: \"\\\"status\\\" = 'PENDING'\",\n})\n@Index(\"IDX_locked_mint_user_op_hash\", [\"userOpHash\"])\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 * Idempotency invariant: for any indexer-driven reason (e.g.\n * `MINT_CONFIRMED`, `BURN_FOR_CREDIT`) the tuple\n * `(user_address, token_address, tx_hash, reason)` is unique. Enforced\n * by a partial unique index that only fires when `tx_hash IS NOT NULL`,\n * so off-chain reasons (e.g. `AIRDROP`) without a tx hash are\n * unaffected. Last-line defense against double-deduct / double-credit\n * when the indexer replays an event (reorg, restart, duplicate pod).\n *\n */\n@Entity({ name: \"ledger_journal\" })\n@Index([\"userAddress\", \"createdAt\"])\n@Index(\n \"UQ_ledger_journal_user_token_tx_reason\",\n [\"userAddress\", \"tokenAddress\", \"txHash\", \"reason\"],\n {\n unique: true,\n where: '\"tx_hash\" IS NOT NULL',\n },\n)\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 {\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"],"mappings":"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","names":["Column","CreateDateColumn","Entity","Index","PrimaryGeneratedColumn","PrimaryGeneratedColumn","Column","CreateDateColumn","Entity","Index","Column","Entity","Column","Entity","Column","CreateDateColumn","Entity","Index","PrimaryGeneratedColumn","PrimaryGeneratedColumn","Column","CreateDateColumn","Entity","Index","Column","Entity","PrimaryColumn","UpdateDateColumn","PrimaryColumn","Column","UpdateDateColumn","Entity","Column","CreateDateColumn","Entity","Index","PrimaryGeneratedColumn","PrimaryGeneratedColumn","Column","CreateDateColumn","Entity","Index"]}