@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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../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":["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":["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"]}
1
+ {"version":3,"sources":["../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":["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":["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","getAddress","Column","CreateDateColumn","Entity","Index","PrimaryGeneratedColumn","PrimaryGeneratedColumn","Column","CreateDateColumn","Entity","Index","getAddress"]}
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/migrations/index.ts
21
21
  var migrations_exports = {};
22
22
  __export(migrations_exports, {
23
+ CreateRedemptionHistory1746230400001: () => CreateRedemptionHistory1746230400001,
23
24
  InitialSchema1700000000000: () => InitialSchema1700000000000,
24
25
  PAFI_MIGRATIONS: () => PAFI_MIGRATIONS
25
26
  });
@@ -128,10 +129,43 @@ var InitialSchema1700000000000 = class {
128
129
  }
129
130
  };
130
131
 
132
+ // src/migrations/1746230400001-CreateRedemptionHistory.ts
133
+ var CreateRedemptionHistory1746230400001 = class {
134
+ name = "CreateRedemptionHistory1746230400001";
135
+ async up(queryRunner) {
136
+ await queryRunner.query(`
137
+ CREATE TABLE IF NOT EXISTS redemption_history (
138
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
139
+ user_address varchar(42) NOT NULL,
140
+ token_address varchar(42),
141
+ amount_pt numeric(78, 0) NOT NULL,
142
+ created_at_unix_sec bigint NOT NULL,
143
+ reservation_id varchar(64),
144
+ row_created_at timestamptz NOT NULL DEFAULT NOW(),
145
+ CONSTRAINT redemption_history_amount_positive CHECK (amount_pt > 0)
146
+ )
147
+ `);
148
+ await queryRunner.query(`
149
+ CREATE INDEX IF NOT EXISTS idx_redemption_history_user_created
150
+ ON redemption_history (user_address, created_at_unix_sec DESC)
151
+ `);
152
+ }
153
+ async down(queryRunner) {
154
+ await queryRunner.query(
155
+ `DROP INDEX IF EXISTS idx_redemption_history_user_created`
156
+ );
157
+ await queryRunner.query(`DROP TABLE IF EXISTS redemption_history`);
158
+ }
159
+ };
160
+
131
161
  // src/migrations/index.ts
132
- var PAFI_MIGRATIONS = [InitialSchema1700000000000];
162
+ var PAFI_MIGRATIONS = [
163
+ InitialSchema1700000000000,
164
+ CreateRedemptionHistory1746230400001
165
+ ];
133
166
  // Annotate the CommonJS export names for ESM import in node:
134
167
  0 && (module.exports = {
168
+ CreateRedemptionHistory1746230400001,
135
169
  InitialSchema1700000000000,
136
170
  PAFI_MIGRATIONS
137
171
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/migrations/index.ts","../../src/migrations/1700000000000-InitialSchema.ts"],"sourcesContent":["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","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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACqBO,IAAM,6BAAN,MAA+D;AAAA,EACpE,OAAO;AAAA,EAEP,MAAa,GAAG,aAAyC;AACvD,UAAM,YAAY,MAAM,2CAA2C;AAGnE,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAavB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAcvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAOvB;AAAA,EACH;AAAA,EAEA,MAAa,KAAK,aAAyC;AACzD,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,6BAA6B;AACrD,UAAM,YAAY,MAAM,+CAA+C;AACvE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,2CAA2C;AACnE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,mCAAmC;AAC3D,UAAM,YAAY,MAAM,4BAA4B;AAAA,EACtD;AACF;;;ADtHO,IAAM,kBAAkB,CAAC,0BAA0B;","names":[]}
1
+ {"version":3,"sources":["../../src/migrations/index.ts","../../src/migrations/1700000000000-InitialSchema.ts","../../src/migrations/1746230400001-CreateRedemptionHistory.ts"],"sourcesContent":["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","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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACqBO,IAAM,6BAAN,MAA+D;AAAA,EACpE,OAAO;AAAA,EAEP,MAAa,GAAG,aAAyC;AACvD,UAAM,YAAY,MAAM,2CAA2C;AAGnE,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAavB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAcvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAOvB;AAAA,EACH;AAAA,EAEA,MAAa,KAAK,aAAyC;AACzD,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,6BAA6B;AACrD,UAAM,YAAY,MAAM,+CAA+C;AACvE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,2CAA2C;AACnE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,mCAAmC;AAC3D,UAAM,YAAY,MAAM,4BAA4B;AAAA,EACtD;AACF;;;AC1HO,IAAM,uCAAN,MAAyE;AAAA,EAC9E,OAAO;AAAA,EAEP,MAAa,GAAG,aAAyC;AACvD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWvB;AAED,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAAA,EACH;AAAA,EAEA,MAAa,KAAK,aAAyC;AACzD,UAAM,YAAY;AAAA,MAChB;AAAA,IACF;AACA,UAAM,YAAY,MAAM,yCAAyC;AAAA,EACnE;AACF;;;AFvBO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AACF;","names":[]}
@@ -25,6 +25,21 @@ declare class InitialSchema1700000000000 implements MigrationInterface {
25
25
  down(queryRunner: QueryRunner): Promise<void>;
26
26
  }
27
27
 
28
+ /**
29
+ * Adds the `redemption_history` table — append-only log of successful
30
+ * redemption initiates, indexed by (user, time) for the daily-limit
31
+ * SUM() query.
32
+ *
33
+ * This is INDEPENDENT from the main InitialSchema (different timestamp,
34
+ * later than 1700000000000). Issuers who already deployed InitialSchema
35
+ * apply this on top.
36
+ */
37
+ declare class CreateRedemptionHistory1746230400001 implements MigrationInterface {
38
+ name: string;
39
+ up(queryRunner: QueryRunner): Promise<void>;
40
+ down(queryRunner: QueryRunner): Promise<void>;
41
+ }
42
+
28
43
  /**
29
44
  * All shipped migrations in chronological order. Drop into TypeORM's
30
45
  * `migrations` config:
@@ -36,6 +51,6 @@ declare class InitialSchema1700000000000 implements MigrationInterface {
36
51
  * migrations: [...PAFI_MIGRATIONS, ...yourCustomMigrations],
37
52
  * });
38
53
  */
39
- declare const PAFI_MIGRATIONS: readonly [typeof InitialSchema1700000000000];
54
+ declare const PAFI_MIGRATIONS: readonly [typeof InitialSchema1700000000000, typeof CreateRedemptionHistory1746230400001];
40
55
 
41
- export { InitialSchema1700000000000, PAFI_MIGRATIONS };
56
+ export { CreateRedemptionHistory1746230400001, InitialSchema1700000000000, PAFI_MIGRATIONS };
@@ -25,6 +25,21 @@ declare class InitialSchema1700000000000 implements MigrationInterface {
25
25
  down(queryRunner: QueryRunner): Promise<void>;
26
26
  }
27
27
 
28
+ /**
29
+ * Adds the `redemption_history` table — append-only log of successful
30
+ * redemption initiates, indexed by (user, time) for the daily-limit
31
+ * SUM() query.
32
+ *
33
+ * This is INDEPENDENT from the main InitialSchema (different timestamp,
34
+ * later than 1700000000000). Issuers who already deployed InitialSchema
35
+ * apply this on top.
36
+ */
37
+ declare class CreateRedemptionHistory1746230400001 implements MigrationInterface {
38
+ name: string;
39
+ up(queryRunner: QueryRunner): Promise<void>;
40
+ down(queryRunner: QueryRunner): Promise<void>;
41
+ }
42
+
28
43
  /**
29
44
  * All shipped migrations in chronological order. Drop into TypeORM's
30
45
  * `migrations` config:
@@ -36,6 +51,6 @@ declare class InitialSchema1700000000000 implements MigrationInterface {
36
51
  * migrations: [...PAFI_MIGRATIONS, ...yourCustomMigrations],
37
52
  * });
38
53
  */
39
- declare const PAFI_MIGRATIONS: readonly [typeof InitialSchema1700000000000];
54
+ declare const PAFI_MIGRATIONS: readonly [typeof InitialSchema1700000000000, typeof CreateRedemptionHistory1746230400001];
40
55
 
41
- export { InitialSchema1700000000000, PAFI_MIGRATIONS };
56
+ export { CreateRedemptionHistory1746230400001, InitialSchema1700000000000, PAFI_MIGRATIONS };
@@ -101,9 +101,42 @@ var InitialSchema1700000000000 = class {
101
101
  }
102
102
  };
103
103
 
104
+ // src/migrations/1746230400001-CreateRedemptionHistory.ts
105
+ var CreateRedemptionHistory1746230400001 = class {
106
+ name = "CreateRedemptionHistory1746230400001";
107
+ async up(queryRunner) {
108
+ await queryRunner.query(`
109
+ CREATE TABLE IF NOT EXISTS redemption_history (
110
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
111
+ user_address varchar(42) NOT NULL,
112
+ token_address varchar(42),
113
+ amount_pt numeric(78, 0) NOT NULL,
114
+ created_at_unix_sec bigint NOT NULL,
115
+ reservation_id varchar(64),
116
+ row_created_at timestamptz NOT NULL DEFAULT NOW(),
117
+ CONSTRAINT redemption_history_amount_positive CHECK (amount_pt > 0)
118
+ )
119
+ `);
120
+ await queryRunner.query(`
121
+ CREATE INDEX IF NOT EXISTS idx_redemption_history_user_created
122
+ ON redemption_history (user_address, created_at_unix_sec DESC)
123
+ `);
124
+ }
125
+ async down(queryRunner) {
126
+ await queryRunner.query(
127
+ `DROP INDEX IF EXISTS idx_redemption_history_user_created`
128
+ );
129
+ await queryRunner.query(`DROP TABLE IF EXISTS redemption_history`);
130
+ }
131
+ };
132
+
104
133
  // src/migrations/index.ts
105
- var PAFI_MIGRATIONS = [InitialSchema1700000000000];
134
+ var PAFI_MIGRATIONS = [
135
+ InitialSchema1700000000000,
136
+ CreateRedemptionHistory1746230400001
137
+ ];
106
138
  export {
139
+ CreateRedemptionHistory1746230400001,
107
140
  InitialSchema1700000000000,
108
141
  PAFI_MIGRATIONS
109
142
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/migrations/1700000000000-InitialSchema.ts","../../src/migrations/index.ts"],"sourcesContent":["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":";AAqBO,IAAM,6BAAN,MAA+D;AAAA,EACpE,OAAO;AAAA,EAEP,MAAa,GAAG,aAAyC;AACvD,UAAM,YAAY,MAAM,2CAA2C;AAGnE,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAavB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAcvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAOvB;AAAA,EACH;AAAA,EAEA,MAAa,KAAK,aAAyC;AACzD,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,6BAA6B;AACrD,UAAM,YAAY,MAAM,+CAA+C;AACvE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,2CAA2C;AACnE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,mCAAmC;AAC3D,UAAM,YAAY,MAAM,4BAA4B;AAAA,EACtD;AACF;;;ACtHO,IAAM,kBAAkB,CAAC,0BAA0B;","names":[]}
1
+ {"version":3,"sources":["../../src/migrations/1700000000000-InitialSchema.ts","../../src/migrations/1746230400001-CreateRedemptionHistory.ts","../../src/migrations/index.ts"],"sourcesContent":["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":";AAqBO,IAAM,6BAAN,MAA+D;AAAA,EACpE,OAAO;AAAA,EAEP,MAAa,GAAG,aAAyC;AACvD,UAAM,YAAY,MAAM,2CAA2C;AAGnE,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAavB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAcvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWvB;AACD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAGD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAOvB;AAAA,EACH;AAAA,EAEA,MAAa,KAAK,aAAyC;AACzD,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,6BAA6B;AACrD,UAAM,YAAY,MAAM,+CAA+C;AACvE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,8CAA8C;AACtE,UAAM,YAAY,MAAM,8BAA8B;AACtD,UAAM,YAAY,MAAM,2CAA2C;AACnE,UAAM,YAAY,MAAM,0CAA0C;AAClE,UAAM,YAAY,MAAM,mCAAmC;AAC3D,UAAM,YAAY,MAAM,4BAA4B;AAAA,EACtD;AACF;;;AC1HO,IAAM,uCAAN,MAAyE;AAAA,EAC9E,OAAO;AAAA,EAEP,MAAa,GAAG,aAAyC;AACvD,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWvB;AAED,UAAM,YAAY,MAAM;AAAA;AAAA;AAAA,KAGvB;AAAA,EACH;AAAA,EAEA,MAAa,KAAK,aAAyC;AACzD,UAAM,YAAY;AAAA,MAChB;AAAA,IACF;AACA,UAAM,YAAY,MAAM,yCAAyC;AAAA,EACnE;AACF;;;ACvBO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pafi-dev/issuer-postgres",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Postgres-backed IPointLedger implementation for @pafi-dev/issuer — TypeORM entities + migrations + base service issuers can drop in directly",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -42,7 +42,7 @@
42
42
  "dist"
43
43
  ],
44
44
  "dependencies": {
45
- "@pafi-dev/issuer": "0.7.0"
45
+ "@pafi-dev/issuer": "0.8.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "typeorm": "^0.3.0",