@pafi-dev/issuer-postgres 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @pafi-dev/issuer-postgres
2
+
3
+ [![npm](https://img.shields.io/npm/v/@pafi-dev/issuer-postgres)](https://www.npmjs.com/package/@pafi-dev/issuer-postgres)
4
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+
6
+ Postgres-backed `IPointLedger` implementation for `@pafi-dev/issuer`.
7
+ TypeORM entities, migrations, and a fully-baked `PostgresPointLedger`
8
+ service issuers can drop in directly — no need to reverse-engineer the
9
+ schema from the reference issuer.
10
+
11
+ > **Server-only.** Pulls in TypeORM (peer-dep). Do not bundle into a browser app.
12
+
13
+ ---
14
+
15
+ ## Why this exists
16
+
17
+ Every issuer needs the same ledger schema:
18
+
19
+ - `user_balances` (per `(user, token)`)
20
+ - `locked_mint_requests` (PENDING reservations during mint)
21
+ - `pending_credits` (reverse flow — burn → off-chain credit)
22
+ - `ledger_journal` (append-only audit trail)
23
+ - `indexer_cursors` (block cursors for `PointIndexer` / `BurnIndexer`)
24
+
25
+ Plus the same race-safe transactions, expired-lock sweeps,
26
+ multi-token guards, and `userOpHash` binding for bundler-receipt
27
+ fallback. This package ships all of it.
28
+
29
+ ---
30
+
31
+ ## Requirements
32
+
33
+ - Node.js >= 18
34
+ - TypeScript >= 5.0
35
+ - `typeorm` ^0.3.0 (peer)
36
+ - `viem` ^2.0.0 (peer, transitively via `@pafi-dev/issuer`)
37
+ - Postgres 13+ (for `gen_random_uuid()` / `pgcrypto`)
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pnpm add @pafi-dev/issuer-postgres @pafi-dev/issuer typeorm viem
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Quick start (NestJS)
50
+
51
+ ```ts
52
+ import { TypeOrmModule } from "@nestjs/typeorm";
53
+ import {
54
+ PostgresPointLedger,
55
+ PostgresCursorStore,
56
+ PAFI_ENTITIES,
57
+ PAFI_MIGRATIONS,
58
+ } from "@pafi-dev/issuer-postgres";
59
+
60
+ @Module({
61
+ imports: [
62
+ TypeOrmModule.forRoot({
63
+ type: "postgres",
64
+ url: process.env.DATABASE_URL,
65
+ entities: [...PAFI_ENTITIES /*, ...yourCustomEntities */],
66
+ migrations: [...PAFI_MIGRATIONS /*, ...yourCustomMigrations */],
67
+ migrationsRun: true,
68
+ }),
69
+ ],
70
+ providers: [
71
+ {
72
+ provide: "POINT_LEDGER",
73
+ useFactory: (dataSource: DataSource) =>
74
+ new PostgresPointLedger(dataSource, { logger: console }),
75
+ inject: [DataSource],
76
+ },
77
+ {
78
+ provide: "INDEXER_CURSOR_STORE",
79
+ useFactory: (dataSource: DataSource) =>
80
+ new PostgresCursorStore(dataSource),
81
+ inject: [DataSource],
82
+ },
83
+ ],
84
+ })
85
+ export class LedgerModule {}
86
+ ```
87
+
88
+ Then wire `POINT_LEDGER` into `createIssuerService({ ledger, ... })`
89
+ from `@pafi-dev/issuer`.
90
+
91
+ ---
92
+
93
+ ## What you get
94
+
95
+ ### `PostgresPointLedger`
96
+
97
+ Implements every method on `IPointLedger` from `@pafi-dev/issuer`:
98
+
99
+ - **Reads** — `getBalance`, `getLockedRequests`, `getMintLock`,
100
+ `getPendingCredit`, `listUserTransactions`
101
+ - **Writes** — `lockForMinting`, `releaseLock`, `deductBalance`,
102
+ `creditBalance`, `updateMintStatus`
103
+ - **Reverse flow** — `reservePendingCredit`, `resolveCreditByBurnTx`,
104
+ `findPendingCreditLockId`
105
+ - **Mobile flow** — `bindMintUserOpHash`, `bindCreditUserOpHash`
106
+ (bundler-receipt fallback against `PointIndexer`'s amount race)
107
+
108
+ All mutating methods run inside a TypeORM `transaction()` so balance
109
+ + lock + journal updates land atomically.
110
+
111
+ ### `PostgresCursorStore`
112
+
113
+ Implements `IIndexerCursorStore`. Default key is `"default"` for the
114
+ primary mint indexer. Use `.forKey(id)` to derive sibling stores for
115
+ the burn indexer or per-token shards:
116
+
117
+ ```ts
118
+ const mintCursor = new PostgresCursorStore(dataSource);
119
+ const burnCursor = mintCursor.forKey(`burn:${pointTokenAddress.toLowerCase()}`);
120
+ ```
121
+
122
+ ### Entities + migrations
123
+
124
+ Drop into your TypeORM config:
125
+
126
+ ```ts
127
+ import { PAFI_ENTITIES, PAFI_MIGRATIONS } from "@pafi-dev/issuer-postgres";
128
+
129
+ new DataSource({
130
+ entities: [...PAFI_ENTITIES, ...yourCustomEntities],
131
+ migrations: [...PAFI_MIGRATIONS, ...yourCustomMigrations],
132
+ });
133
+ ```
134
+
135
+ `InitialSchema1700000000000` is one consolidated migration covering
136
+ all five tables + indexes (including the `user_op_hash` columns and
137
+ the bundler-fallback indexes). For schema changes after this baseline,
138
+ generate a follow-up migration in your own repo — never edit shipped
139
+ migrations in place.
140
+
141
+ ---
142
+
143
+ ## Multi-token
144
+
145
+ Every mutating method requires `tokenAddress`. There is no "default
146
+ token" bucket — single-token issuers pass the same address every
147
+ call. The package throws if you forget, with a clear error message:
148
+
149
+ ```
150
+ PostgresPointLedger: tokenAddress is required on every call (multi-token ledger)
151
+ ```
152
+
153
+ Balances are keyed by composite `(userAddress, tokenAddress)` PK.
154
+
155
+ ---
156
+
157
+ ## Issuer-specific extensions
158
+
159
+ Don't subclass `PostgresPointLedger`. Instead:
160
+
161
+ 1. Add your tables in your own repo (`campaign_rules`,
162
+ `kyc_status`, etc.) with their own TypeORM entities and a follow-up
163
+ migration.
164
+ 2. Wrap or compose `PostgresPointLedger` if you need to layer business
165
+ rules on top of `creditBalance` / `lockForMinting`.
166
+ 3. For audit fields specific to your issuer (e.g. partner_id,
167
+ campaign_id), extend `LedgerJournalEntity` via inheritance in your
168
+ own entity, register it in your `entities[]`, and add a migration
169
+ that ALTERs the column.
170
+
171
+ The shipped entities use TypeORM Discriminator-friendly defaults —
172
+ extending without breaking the schema is straightforward.
173
+
174
+ ---
175
+
176
+ ## License
177
+
178
+ Apache-2.0
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __decorateClass = (decorators, target, key, kind) => {
20
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
21
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
22
+ if (decorator = decorators[i])
23
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
24
+ if (kind && result) __defProp(target, key, result);
25
+ return result;
26
+ };
27
+
28
+ // src/entities/index.ts
29
+ var entities_exports = {};
30
+ __export(entities_exports, {
31
+ IndexerCursorEntity: () => IndexerCursorEntity,
32
+ LedgerJournalEntity: () => LedgerJournalEntity,
33
+ LockedMintEntity: () => LockedMintEntity,
34
+ PAFI_ENTITIES: () => PAFI_ENTITIES,
35
+ PendingCreditEntity: () => PendingCreditEntity,
36
+ UserBalanceEntity: () => UserBalanceEntity
37
+ });
38
+ module.exports = __toCommonJS(entities_exports);
39
+
40
+ // src/entities/locked-mint.entity.ts
41
+ var import_typeorm = require("typeorm");
42
+ var LockedMintEntity = class {
43
+ id;
44
+ userAddress;
45
+ tokenAddress;
46
+ amount;
47
+ status;
48
+ createdAt;
49
+ expiresAt;
50
+ txHash;
51
+ userOpHash;
52
+ };
53
+ __decorateClass([
54
+ (0, import_typeorm.PrimaryGeneratedColumn)("uuid")
55
+ ], LockedMintEntity.prototype, "id", 2);
56
+ __decorateClass([
57
+ (0, import_typeorm.Column)({ name: "user_address", type: "varchar", length: 42 })
58
+ ], LockedMintEntity.prototype, "userAddress", 2);
59
+ __decorateClass([
60
+ (0, import_typeorm.Column)({ name: "token_address", type: "varchar", length: 42 })
61
+ ], LockedMintEntity.prototype, "tokenAddress", 2);
62
+ __decorateClass([
63
+ (0, import_typeorm.Column)({
64
+ name: "amount",
65
+ type: "numeric",
66
+ precision: 78,
67
+ scale: 0,
68
+ transformer: {
69
+ to: (value) => value.toString(),
70
+ from: (value) => BigInt(value)
71
+ }
72
+ })
73
+ ], LockedMintEntity.prototype, "amount", 2);
74
+ __decorateClass([
75
+ (0, import_typeorm.Column)({
76
+ name: "status",
77
+ type: "varchar",
78
+ length: 16,
79
+ default: "PENDING"
80
+ })
81
+ ], LockedMintEntity.prototype, "status", 2);
82
+ __decorateClass([
83
+ (0, import_typeorm.CreateDateColumn)({ name: "created_at" })
84
+ ], LockedMintEntity.prototype, "createdAt", 2);
85
+ __decorateClass([
86
+ (0, import_typeorm.Column)({ name: "expires_at", type: "timestamp with time zone" })
87
+ ], LockedMintEntity.prototype, "expiresAt", 2);
88
+ __decorateClass([
89
+ (0, import_typeorm.Column)({ name: "tx_hash", type: "varchar", length: 66, nullable: true })
90
+ ], LockedMintEntity.prototype, "txHash", 2);
91
+ __decorateClass([
92
+ (0, import_typeorm.Column)({
93
+ name: "user_op_hash",
94
+ type: "varchar",
95
+ length: 66,
96
+ nullable: true
97
+ })
98
+ ], LockedMintEntity.prototype, "userOpHash", 2);
99
+ LockedMintEntity = __decorateClass([
100
+ (0, import_typeorm.Entity)({ name: "locked_mint_requests" }),
101
+ (0, import_typeorm.Index)(["userAddress", "status"])
102
+ ], LockedMintEntity);
103
+
104
+ // src/entities/pending-credit.entity.ts
105
+ var import_typeorm2 = require("typeorm");
106
+ var PendingCreditEntity = class {
107
+ id;
108
+ userAddress;
109
+ tokenAddress;
110
+ amount;
111
+ status;
112
+ txHash;
113
+ userOpHash;
114
+ createdAt;
115
+ expiresAt;
116
+ resolvedAt;
117
+ };
118
+ __decorateClass([
119
+ (0, import_typeorm2.PrimaryGeneratedColumn)("uuid")
120
+ ], PendingCreditEntity.prototype, "id", 2);
121
+ __decorateClass([
122
+ (0, import_typeorm2.Column)({ name: "user_address", type: "varchar", length: 42 })
123
+ ], PendingCreditEntity.prototype, "userAddress", 2);
124
+ __decorateClass([
125
+ (0, import_typeorm2.Column)({ name: "token_address", type: "varchar", length: 42 })
126
+ ], PendingCreditEntity.prototype, "tokenAddress", 2);
127
+ __decorateClass([
128
+ (0, import_typeorm2.Column)({
129
+ name: "amount",
130
+ type: "numeric",
131
+ precision: 78,
132
+ scale: 0,
133
+ transformer: {
134
+ to: (value) => value.toString(),
135
+ from: (value) => BigInt(value)
136
+ }
137
+ })
138
+ ], PendingCreditEntity.prototype, "amount", 2);
139
+ __decorateClass([
140
+ (0, import_typeorm2.Column)({
141
+ name: "status",
142
+ type: "varchar",
143
+ length: 16,
144
+ default: "PENDING"
145
+ })
146
+ ], PendingCreditEntity.prototype, "status", 2);
147
+ __decorateClass([
148
+ (0, import_typeorm2.Column)({ name: "tx_hash", type: "varchar", length: 66, nullable: true })
149
+ ], PendingCreditEntity.prototype, "txHash", 2);
150
+ __decorateClass([
151
+ (0, import_typeorm2.Column)({
152
+ name: "user_op_hash",
153
+ type: "varchar",
154
+ length: 66,
155
+ nullable: true
156
+ })
157
+ ], PendingCreditEntity.prototype, "userOpHash", 2);
158
+ __decorateClass([
159
+ (0, import_typeorm2.CreateDateColumn)({ name: "created_at" })
160
+ ], PendingCreditEntity.prototype, "createdAt", 2);
161
+ __decorateClass([
162
+ (0, import_typeorm2.Column)({ name: "expires_at", type: "timestamp with time zone" })
163
+ ], PendingCreditEntity.prototype, "expiresAt", 2);
164
+ __decorateClass([
165
+ (0, import_typeorm2.Column)({
166
+ name: "resolved_at",
167
+ type: "timestamp with time zone",
168
+ nullable: true
169
+ })
170
+ ], PendingCreditEntity.prototype, "resolvedAt", 2);
171
+ PendingCreditEntity = __decorateClass([
172
+ (0, import_typeorm2.Entity)({ name: "pending_credits" }),
173
+ (0, import_typeorm2.Index)(["userAddress", "status"]),
174
+ (0, import_typeorm2.Index)(["txHash"])
175
+ ], PendingCreditEntity);
176
+
177
+ // src/entities/user-balance.entity.ts
178
+ var import_typeorm3 = require("typeorm");
179
+ var UserBalanceEntity = class {
180
+ userAddress;
181
+ tokenAddress;
182
+ balance;
183
+ updatedAt;
184
+ };
185
+ __decorateClass([
186
+ (0, import_typeorm3.PrimaryColumn)({ name: "user_address", type: "varchar", length: 42 })
187
+ ], UserBalanceEntity.prototype, "userAddress", 2);
188
+ __decorateClass([
189
+ (0, import_typeorm3.PrimaryColumn)({ name: "token_address", type: "varchar", length: 42 })
190
+ ], UserBalanceEntity.prototype, "tokenAddress", 2);
191
+ __decorateClass([
192
+ (0, import_typeorm3.Column)({
193
+ name: "balance",
194
+ type: "numeric",
195
+ precision: 78,
196
+ scale: 0,
197
+ default: 0,
198
+ transformer: {
199
+ to: (value) => value.toString(),
200
+ from: (value) => BigInt(value)
201
+ }
202
+ })
203
+ ], UserBalanceEntity.prototype, "balance", 2);
204
+ __decorateClass([
205
+ (0, import_typeorm3.UpdateDateColumn)({ name: "updated_at" })
206
+ ], UserBalanceEntity.prototype, "updatedAt", 2);
207
+ UserBalanceEntity = __decorateClass([
208
+ (0, import_typeorm3.Entity)({ name: "user_balances" })
209
+ ], UserBalanceEntity);
210
+
211
+ // src/entities/ledger-journal.entity.ts
212
+ var import_typeorm4 = require("typeorm");
213
+ var LedgerJournalEntity = class {
214
+ id;
215
+ userAddress;
216
+ tokenAddress;
217
+ delta;
218
+ reason;
219
+ txHash;
220
+ createdAt;
221
+ };
222
+ __decorateClass([
223
+ (0, import_typeorm4.PrimaryGeneratedColumn)("uuid")
224
+ ], LedgerJournalEntity.prototype, "id", 2);
225
+ __decorateClass([
226
+ (0, import_typeorm4.Column)({ name: "user_address", type: "varchar", length: 42 })
227
+ ], LedgerJournalEntity.prototype, "userAddress", 2);
228
+ __decorateClass([
229
+ (0, import_typeorm4.Column)({ name: "token_address", type: "varchar", length: 42 })
230
+ ], LedgerJournalEntity.prototype, "tokenAddress", 2);
231
+ __decorateClass([
232
+ (0, import_typeorm4.Column)({
233
+ name: "delta",
234
+ type: "numeric",
235
+ precision: 78,
236
+ scale: 0,
237
+ transformer: {
238
+ to: (value) => value.toString(),
239
+ from: (value) => BigInt(value)
240
+ }
241
+ })
242
+ ], LedgerJournalEntity.prototype, "delta", 2);
243
+ __decorateClass([
244
+ (0, import_typeorm4.Column)({ name: "reason", type: "varchar", length: 128 })
245
+ ], LedgerJournalEntity.prototype, "reason", 2);
246
+ __decorateClass([
247
+ (0, import_typeorm4.Column)({ name: "tx_hash", type: "varchar", length: 66, nullable: true })
248
+ ], LedgerJournalEntity.prototype, "txHash", 2);
249
+ __decorateClass([
250
+ (0, import_typeorm4.CreateDateColumn)({ name: "created_at" })
251
+ ], LedgerJournalEntity.prototype, "createdAt", 2);
252
+ LedgerJournalEntity = __decorateClass([
253
+ (0, import_typeorm4.Entity)({ name: "ledger_journal" }),
254
+ (0, import_typeorm4.Index)(["userAddress", "createdAt"])
255
+ ], LedgerJournalEntity);
256
+
257
+ // src/entities/indexer-cursor.entity.ts
258
+ var import_typeorm5 = require("typeorm");
259
+ var IndexerCursorEntity = class {
260
+ id;
261
+ nextBlock;
262
+ updatedAt;
263
+ };
264
+ __decorateClass([
265
+ (0, import_typeorm5.PrimaryColumn)({ type: "varchar", length: 64 })
266
+ ], IndexerCursorEntity.prototype, "id", 2);
267
+ __decorateClass([
268
+ (0, import_typeorm5.Column)({
269
+ name: "next_block",
270
+ type: "numeric",
271
+ precision: 78,
272
+ scale: 0,
273
+ transformer: {
274
+ to: (value) => value.toString(),
275
+ from: (value) => BigInt(value)
276
+ }
277
+ })
278
+ ], IndexerCursorEntity.prototype, "nextBlock", 2);
279
+ __decorateClass([
280
+ (0, import_typeorm5.UpdateDateColumn)({ name: "updated_at" })
281
+ ], IndexerCursorEntity.prototype, "updatedAt", 2);
282
+ IndexerCursorEntity = __decorateClass([
283
+ (0, import_typeorm5.Entity)({ name: "indexer_cursors" })
284
+ ], IndexerCursorEntity);
285
+
286
+ // src/entities/index.ts
287
+ var PAFI_ENTITIES = [
288
+ LockedMintEntity,
289
+ PendingCreditEntity,
290
+ UserBalanceEntity,
291
+ LedgerJournalEntity,
292
+ IndexerCursorEntity
293
+ ];
294
+ // Annotate the CommonJS export names for ESM import in node:
295
+ 0 && (module.exports = {
296
+ IndexerCursorEntity,
297
+ LedgerJournalEntity,
298
+ LockedMintEntity,
299
+ PAFI_ENTITIES,
300
+ PendingCreditEntity,
301
+ UserBalanceEntity
302
+ });
303
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/entities/index.ts","../../src/entities/locked-mint.entity.ts","../../src/entities/pending-credit.entity.ts","../../src/entities/user-balance.entity.ts","../../src/entities/ledger-journal.entity.ts","../../src/entities/indexer-cursor.entity.ts"],"sourcesContent":["export { LockedMintEntity } from \"./locked-mint.entity\";\nexport {\n PendingCreditEntity,\n type PendingCreditStatus,\n} from \"./pending-credit.entity\";\nexport { UserBalanceEntity } from \"./user-balance.entity\";\nexport { LedgerJournalEntity } from \"./ledger-journal.entity\";\nexport { IndexerCursorEntity } from \"./indexer-cursor.entity\";\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 {\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"],"mappings":"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","names":["import_typeorm","import_typeorm","import_typeorm","import_typeorm"]}
@@ -0,0 +1,122 @@
1
+ import { MintingStatus } from '@pafi-dev/issuer';
2
+
3
+ /**
4
+ * A reservation against a user's off-chain balance.
5
+ *
6
+ * Lifecycle:
7
+ * PENDING ── PointIndexer matches Mint event ──▶ MINTED
8
+ * │
9
+ * ├── deadline elapsed ────────────────────▶ EXPIRED
10
+ * │
11
+ * └── tx reverted ─────────────────────────▶ FAILED
12
+ *
13
+ * The `(userAddress, status)` composite index keeps the "sum all
14
+ * PENDING locks" hot path of `lockForMinting()` fast under load.
15
+ */
16
+ declare class LockedMintEntity {
17
+ id: string;
18
+ userAddress: string;
19
+ tokenAddress: string;
20
+ amount: bigint;
21
+ status: MintingStatus;
22
+ createdAt: Date;
23
+ expiresAt: Date;
24
+ txHash?: string | null;
25
+ /**
26
+ * ERC-4337 userOpHash returned by the bundler at /claim/submit.
27
+ * Bound to the lock so `/claim/status` can fall back to the bundler
28
+ * receipt — required when multiple PENDING locks share the same
29
+ * `amount` (PointIndexer matches by amount and can pick the wrong
30
+ * sibling lock).
31
+ */
32
+ userOpHash?: string | null;
33
+ }
34
+
35
+ type PendingCreditStatus = "PENDING" | "RESOLVED" | "EXPIRED";
36
+ /**
37
+ * Reverse flow — user burns on-chain PT, `BurnIndexer` observes
38
+ * `Transfer(user → 0x0)`, the credit is settled to the off-chain ledger.
39
+ *
40
+ * Lifecycle:
41
+ * PENDING ── Burn tx observed by indexer ──▶ RESOLVED
42
+ * │
43
+ * └── deadline elapsed ────────────────▶ EXPIRED
44
+ *
45
+ * The credit is reserved BEFORE the UserOp is submitted so the
46
+ * indexer can correlate `(user, amount, token)` back to the off-chain
47
+ * row when the burn lands.
48
+ */
49
+ declare class PendingCreditEntity {
50
+ id: string;
51
+ userAddress: string;
52
+ tokenAddress: string;
53
+ amount: bigint;
54
+ status: PendingCreditStatus;
55
+ /** On-chain burn tx that settled this credit. Null until indexer resolves. */
56
+ txHash?: string | null;
57
+ /** ERC-4337 userOpHash bound at /redeem/submit. See `LockedMintEntity`. */
58
+ userOpHash?: string | null;
59
+ createdAt: Date;
60
+ expiresAt: Date;
61
+ resolvedAt?: Date | null;
62
+ }
63
+
64
+ /**
65
+ * Off-chain point balance per `(userAddress, tokenAddress)`.
66
+ *
67
+ * `balance` is the **total** owned; pending reservations live in
68
+ * `LockedMintEntity`. Available balance = total − sum(PENDING locks)
69
+ * — `getBalance` in `PostgresPointLedger` does this subtraction
70
+ * inside a transaction so reads are race-free.
71
+ *
72
+ * All amounts are `numeric(78, 0)` for full bigint precision (uint256
73
+ * fits in 78 decimal digits). TypeORM transforms bigint ↔ string at
74
+ * the boundary; in JS/TS code we always deal with `bigint`.
75
+ */
76
+ declare class UserBalanceEntity {
77
+ userAddress: string;
78
+ tokenAddress: string;
79
+ balance: bigint;
80
+ updatedAt: Date;
81
+ }
82
+
83
+ /**
84
+ * Append-only audit trail for every balance mutation. Used for
85
+ * reconciliation, customer support, and regulatory reporting.
86
+ *
87
+ * Sign convention:
88
+ * - positive `delta` — credit (merchant award, refund, manual top-up)
89
+ * - negative `delta` — debit (mint confirmation against the off-chain
90
+ * balance; `txHash` references the on-chain Mint event)
91
+ */
92
+ declare class LedgerJournalEntity {
93
+ id: string;
94
+ userAddress: string;
95
+ tokenAddress: string;
96
+ delta: bigint;
97
+ reason: string;
98
+ txHash?: string | null;
99
+ createdAt: Date;
100
+ }
101
+
102
+ /**
103
+ * Persistent cursor for `PointIndexer` / `BurnIndexer`. Multiple rows
104
+ * coexist keyed by `id` (e.g. `default` for the mint indexer,
105
+ * `burn:0x...` for each per-token burn indexer).
106
+ *
107
+ * Stores the **next** block to scan, not the last processed one.
108
+ * Indexer reads on startup and resumes from there.
109
+ */
110
+ declare class IndexerCursorEntity {
111
+ id: string;
112
+ nextBlock: bigint;
113
+ updatedAt: Date;
114
+ }
115
+
116
+ /**
117
+ * All entities in one array — drop into TypeORM's `entities` config or
118
+ * NestJS's `TypeOrmModule.forFeature(PAFI_ENTITIES)`.
119
+ */
120
+ declare const PAFI_ENTITIES: readonly [typeof LockedMintEntity, typeof PendingCreditEntity, typeof UserBalanceEntity, typeof LedgerJournalEntity, typeof IndexerCursorEntity];
121
+
122
+ export { IndexerCursorEntity, LedgerJournalEntity, LockedMintEntity, PAFI_ENTITIES, PendingCreditEntity, type PendingCreditStatus, UserBalanceEntity };