@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/dist/index.js ADDED
@@ -0,0 +1,749 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // src/postgresPointLedger.ts
13
+ import { getAddress } from "viem";
14
+
15
+ // src/entities/locked-mint.entity.ts
16
+ import {
17
+ Column,
18
+ CreateDateColumn,
19
+ Entity,
20
+ Index,
21
+ PrimaryGeneratedColumn
22
+ } from "typeorm";
23
+ var LockedMintEntity = class {
24
+ id;
25
+ userAddress;
26
+ tokenAddress;
27
+ amount;
28
+ status;
29
+ createdAt;
30
+ expiresAt;
31
+ txHash;
32
+ userOpHash;
33
+ };
34
+ __decorateClass([
35
+ PrimaryGeneratedColumn("uuid")
36
+ ], LockedMintEntity.prototype, "id", 2);
37
+ __decorateClass([
38
+ Column({ name: "user_address", type: "varchar", length: 42 })
39
+ ], LockedMintEntity.prototype, "userAddress", 2);
40
+ __decorateClass([
41
+ Column({ name: "token_address", type: "varchar", length: 42 })
42
+ ], LockedMintEntity.prototype, "tokenAddress", 2);
43
+ __decorateClass([
44
+ Column({
45
+ name: "amount",
46
+ type: "numeric",
47
+ precision: 78,
48
+ scale: 0,
49
+ transformer: {
50
+ to: (value) => value.toString(),
51
+ from: (value) => BigInt(value)
52
+ }
53
+ })
54
+ ], LockedMintEntity.prototype, "amount", 2);
55
+ __decorateClass([
56
+ Column({
57
+ name: "status",
58
+ type: "varchar",
59
+ length: 16,
60
+ default: "PENDING"
61
+ })
62
+ ], LockedMintEntity.prototype, "status", 2);
63
+ __decorateClass([
64
+ CreateDateColumn({ name: "created_at" })
65
+ ], LockedMintEntity.prototype, "createdAt", 2);
66
+ __decorateClass([
67
+ Column({ name: "expires_at", type: "timestamp with time zone" })
68
+ ], LockedMintEntity.prototype, "expiresAt", 2);
69
+ __decorateClass([
70
+ Column({ name: "tx_hash", type: "varchar", length: 66, nullable: true })
71
+ ], LockedMintEntity.prototype, "txHash", 2);
72
+ __decorateClass([
73
+ Column({
74
+ name: "user_op_hash",
75
+ type: "varchar",
76
+ length: 66,
77
+ nullable: true
78
+ })
79
+ ], LockedMintEntity.prototype, "userOpHash", 2);
80
+ LockedMintEntity = __decorateClass([
81
+ Entity({ name: "locked_mint_requests" }),
82
+ Index(["userAddress", "status"])
83
+ ], LockedMintEntity);
84
+
85
+ // src/entities/pending-credit.entity.ts
86
+ import {
87
+ Column as Column2,
88
+ CreateDateColumn as CreateDateColumn2,
89
+ Entity as Entity2,
90
+ Index as Index2,
91
+ PrimaryGeneratedColumn as PrimaryGeneratedColumn2
92
+ } from "typeorm";
93
+ var PendingCreditEntity = class {
94
+ id;
95
+ userAddress;
96
+ tokenAddress;
97
+ amount;
98
+ status;
99
+ txHash;
100
+ userOpHash;
101
+ createdAt;
102
+ expiresAt;
103
+ resolvedAt;
104
+ };
105
+ __decorateClass([
106
+ PrimaryGeneratedColumn2("uuid")
107
+ ], PendingCreditEntity.prototype, "id", 2);
108
+ __decorateClass([
109
+ Column2({ name: "user_address", type: "varchar", length: 42 })
110
+ ], PendingCreditEntity.prototype, "userAddress", 2);
111
+ __decorateClass([
112
+ Column2({ name: "token_address", type: "varchar", length: 42 })
113
+ ], PendingCreditEntity.prototype, "tokenAddress", 2);
114
+ __decorateClass([
115
+ Column2({
116
+ name: "amount",
117
+ type: "numeric",
118
+ precision: 78,
119
+ scale: 0,
120
+ transformer: {
121
+ to: (value) => value.toString(),
122
+ from: (value) => BigInt(value)
123
+ }
124
+ })
125
+ ], PendingCreditEntity.prototype, "amount", 2);
126
+ __decorateClass([
127
+ Column2({
128
+ name: "status",
129
+ type: "varchar",
130
+ length: 16,
131
+ default: "PENDING"
132
+ })
133
+ ], PendingCreditEntity.prototype, "status", 2);
134
+ __decorateClass([
135
+ Column2({ name: "tx_hash", type: "varchar", length: 66, nullable: true })
136
+ ], PendingCreditEntity.prototype, "txHash", 2);
137
+ __decorateClass([
138
+ Column2({
139
+ name: "user_op_hash",
140
+ type: "varchar",
141
+ length: 66,
142
+ nullable: true
143
+ })
144
+ ], PendingCreditEntity.prototype, "userOpHash", 2);
145
+ __decorateClass([
146
+ CreateDateColumn2({ name: "created_at" })
147
+ ], PendingCreditEntity.prototype, "createdAt", 2);
148
+ __decorateClass([
149
+ Column2({ name: "expires_at", type: "timestamp with time zone" })
150
+ ], PendingCreditEntity.prototype, "expiresAt", 2);
151
+ __decorateClass([
152
+ Column2({
153
+ name: "resolved_at",
154
+ type: "timestamp with time zone",
155
+ nullable: true
156
+ })
157
+ ], PendingCreditEntity.prototype, "resolvedAt", 2);
158
+ PendingCreditEntity = __decorateClass([
159
+ Entity2({ name: "pending_credits" }),
160
+ Index2(["userAddress", "status"]),
161
+ Index2(["txHash"])
162
+ ], PendingCreditEntity);
163
+
164
+ // src/entities/user-balance.entity.ts
165
+ import { Column as Column3, Entity as Entity3, PrimaryColumn, UpdateDateColumn } from "typeorm";
166
+ var UserBalanceEntity = class {
167
+ userAddress;
168
+ tokenAddress;
169
+ balance;
170
+ updatedAt;
171
+ };
172
+ __decorateClass([
173
+ PrimaryColumn({ name: "user_address", type: "varchar", length: 42 })
174
+ ], UserBalanceEntity.prototype, "userAddress", 2);
175
+ __decorateClass([
176
+ PrimaryColumn({ name: "token_address", type: "varchar", length: 42 })
177
+ ], UserBalanceEntity.prototype, "tokenAddress", 2);
178
+ __decorateClass([
179
+ Column3({
180
+ name: "balance",
181
+ type: "numeric",
182
+ precision: 78,
183
+ scale: 0,
184
+ default: 0,
185
+ transformer: {
186
+ to: (value) => value.toString(),
187
+ from: (value) => BigInt(value)
188
+ }
189
+ })
190
+ ], UserBalanceEntity.prototype, "balance", 2);
191
+ __decorateClass([
192
+ UpdateDateColumn({ name: "updated_at" })
193
+ ], UserBalanceEntity.prototype, "updatedAt", 2);
194
+ UserBalanceEntity = __decorateClass([
195
+ Entity3({ name: "user_balances" })
196
+ ], UserBalanceEntity);
197
+
198
+ // src/entities/ledger-journal.entity.ts
199
+ import {
200
+ Column as Column4,
201
+ CreateDateColumn as CreateDateColumn3,
202
+ Entity as Entity4,
203
+ Index as Index3,
204
+ PrimaryGeneratedColumn as PrimaryGeneratedColumn3
205
+ } from "typeorm";
206
+ var LedgerJournalEntity = class {
207
+ id;
208
+ userAddress;
209
+ tokenAddress;
210
+ delta;
211
+ reason;
212
+ txHash;
213
+ createdAt;
214
+ };
215
+ __decorateClass([
216
+ PrimaryGeneratedColumn3("uuid")
217
+ ], LedgerJournalEntity.prototype, "id", 2);
218
+ __decorateClass([
219
+ Column4({ name: "user_address", type: "varchar", length: 42 })
220
+ ], LedgerJournalEntity.prototype, "userAddress", 2);
221
+ __decorateClass([
222
+ Column4({ name: "token_address", type: "varchar", length: 42 })
223
+ ], LedgerJournalEntity.prototype, "tokenAddress", 2);
224
+ __decorateClass([
225
+ Column4({
226
+ name: "delta",
227
+ type: "numeric",
228
+ precision: 78,
229
+ scale: 0,
230
+ transformer: {
231
+ to: (value) => value.toString(),
232
+ from: (value) => BigInt(value)
233
+ }
234
+ })
235
+ ], LedgerJournalEntity.prototype, "delta", 2);
236
+ __decorateClass([
237
+ Column4({ name: "reason", type: "varchar", length: 128 })
238
+ ], LedgerJournalEntity.prototype, "reason", 2);
239
+ __decorateClass([
240
+ Column4({ name: "tx_hash", type: "varchar", length: 66, nullable: true })
241
+ ], LedgerJournalEntity.prototype, "txHash", 2);
242
+ __decorateClass([
243
+ CreateDateColumn3({ name: "created_at" })
244
+ ], LedgerJournalEntity.prototype, "createdAt", 2);
245
+ LedgerJournalEntity = __decorateClass([
246
+ Entity4({ name: "ledger_journal" }),
247
+ Index3(["userAddress", "createdAt"])
248
+ ], LedgerJournalEntity);
249
+
250
+ // src/postgresPointLedger.ts
251
+ var PostgresPointLedger = class {
252
+ constructor(dataSource, options = {}) {
253
+ this.dataSource = dataSource;
254
+ this.logger = options.logger;
255
+ }
256
+ dataSource;
257
+ logger;
258
+ // ---------------------------------------------------------------------
259
+ // Read
260
+ // ---------------------------------------------------------------------
261
+ async getBalance(userAddress, tokenAddress) {
262
+ const { user, token } = normalize(userAddress, tokenAddress);
263
+ await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
264
+ const balanceRow = await this.dataSource.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
265
+ const total = balanceRow?.balance ?? 0n;
266
+ const locked = await this.sumPendingLocks(user, token);
267
+ const available = total - locked;
268
+ return available < 0n ? 0n : available;
269
+ }
270
+ async getLockedRequests(userAddress, tokenAddress) {
271
+ const { user, token } = normalize(userAddress, tokenAddress);
272
+ const rows = await this.dataSource.getRepository(LockedMintEntity).find({
273
+ where: { userAddress: user, tokenAddress: token, status: "PENDING" },
274
+ order: { createdAt: "ASC" }
275
+ });
276
+ return rows.map((row) => this.toSdkLock(row));
277
+ }
278
+ async getMintLock(lockId, userAddress) {
279
+ const row = await this.dataSource.getRepository(LockedMintEntity).findOne({ where: { id: lockId } });
280
+ if (!row) return null;
281
+ if (userAddress && row.userAddress.toLowerCase() !== userAddress.toLowerCase()) {
282
+ return null;
283
+ }
284
+ return this.toSdkLock(row);
285
+ }
286
+ /** Raw TypeORM row — escape hatch for callers that need entity fields. */
287
+ async getMintLockEntity(lockId) {
288
+ return this.dataSource.getRepository(LockedMintEntity).findOne({ where: { id: lockId } });
289
+ }
290
+ async getPendingCredit(lockId, userAddress) {
291
+ const row = await this.dataSource.getRepository(PendingCreditEntity).findOne({ where: { id: lockId } });
292
+ if (!row) return null;
293
+ if (userAddress && row.userAddress.toLowerCase() !== userAddress.toLowerCase()) {
294
+ return null;
295
+ }
296
+ return {
297
+ lockId: row.id,
298
+ userAddress: getAddress(row.userAddress),
299
+ amount: row.amount,
300
+ tokenAddress: row.tokenAddress ? getAddress(row.tokenAddress) : void 0,
301
+ status: row.status,
302
+ createdAt: row.createdAt.getTime(),
303
+ expiresAt: row.expiresAt.getTime(),
304
+ txHash: row.txHash ?? void 0,
305
+ resolvedAt: row.resolvedAt?.getTime(),
306
+ userOpHash: row.userOpHash ?? void 0
307
+ };
308
+ }
309
+ /** Raw TypeORM row escape hatch for credits. */
310
+ async getPendingCreditEntity(lockId) {
311
+ return this.dataSource.getRepository(PendingCreditEntity).findOne({ where: { id: lockId } });
312
+ }
313
+ /**
314
+ * Paginated list of a user's mint requests across all statuses and
315
+ * all tokens — used by `GET /user/transactions` reference endpoint.
316
+ */
317
+ async listUserTransactions(userAddress, limit, offset) {
318
+ const user = getAddress(userAddress);
319
+ const [rows, total] = await this.dataSource.getRepository(LockedMintEntity).findAndCount({
320
+ where: { userAddress: user },
321
+ order: { createdAt: "DESC" },
322
+ take: limit,
323
+ skip: offset
324
+ });
325
+ return { rows, total };
326
+ }
327
+ // ---------------------------------------------------------------------
328
+ // Write
329
+ // ---------------------------------------------------------------------
330
+ async creditBalance(userAddress, amount, reason, tokenAddress) {
331
+ if (amount <= 0n) {
332
+ throw new Error("creditBalance: amount must be positive");
333
+ }
334
+ const { user, token } = normalize(userAddress, tokenAddress);
335
+ await this.dataSource.transaction(async (tx) => {
336
+ const existing = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
337
+ const next = (existing?.balance ?? 0n) + amount;
338
+ await tx.getRepository(UserBalanceEntity).upsert(
339
+ { userAddress: user, tokenAddress: token, balance: next },
340
+ { conflictPaths: ["userAddress", "tokenAddress"] }
341
+ );
342
+ await tx.getRepository(LedgerJournalEntity).insert({
343
+ userAddress: user,
344
+ tokenAddress: token,
345
+ delta: amount,
346
+ reason
347
+ });
348
+ });
349
+ this.logger?.debug?.(`credit ${user}[${token}] +${amount} (${reason})`);
350
+ }
351
+ async lockForMinting(userAddress, amount, lockDurationMs, tokenAddress) {
352
+ if (amount <= 0n) {
353
+ throw new Error("lockForMinting: amount must be positive");
354
+ }
355
+ if (lockDurationMs <= 0) {
356
+ throw new Error("lockForMinting: lockDurationMs must be positive");
357
+ }
358
+ const { user, token } = normalize(userAddress, tokenAddress);
359
+ return this.dataSource.transaction(async (tx) => {
360
+ const balanceRow = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
361
+ const total = balanceRow?.balance ?? 0n;
362
+ const pendingTotal = await tx.getRepository(LockedMintEntity).createQueryBuilder("lock").select("COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)", "sum").where("lock.user_address = :user", { user }).andWhere("lock.token_address = :token", { token }).andWhere("lock.status = :pending", { pending: "PENDING" }).andWhere("lock.expires_at > :now", { now: /* @__PURE__ */ new Date() }).getRawOne();
363
+ const locked = pendingTotal ? BigInt(pendingTotal.sum) : 0n;
364
+ const available = total - locked;
365
+ if (available < amount) {
366
+ throw new Error(
367
+ `Insufficient balance: available=${available}, requested=${amount}`
368
+ );
369
+ }
370
+ const lock = await tx.getRepository(LockedMintEntity).save({
371
+ userAddress: user,
372
+ tokenAddress: token,
373
+ amount,
374
+ status: "PENDING",
375
+ expiresAt: new Date(Date.now() + lockDurationMs)
376
+ });
377
+ this.logger?.debug?.(
378
+ `lock ${lock.id} ${user}[${token}] amount=${amount}`
379
+ );
380
+ return lock.id;
381
+ });
382
+ }
383
+ async releaseLock(lockId) {
384
+ const result = await this.dataSource.getRepository(LockedMintEntity).delete({ id: lockId, status: "PENDING" });
385
+ if ((result.affected ?? 0) > 0) {
386
+ this.logger?.debug?.(`release lock ${lockId}`);
387
+ }
388
+ }
389
+ async deductBalance(userAddress, amount, txHash, tokenAddress) {
390
+ if (amount <= 0n) {
391
+ throw new Error("deductBalance: amount must be positive");
392
+ }
393
+ const { user, token } = normalize(userAddress, tokenAddress);
394
+ await this.dataSource.transaction(async (tx) => {
395
+ const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
396
+ if (!balance || balance.balance < amount) {
397
+ throw new Error(
398
+ `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
399
+ );
400
+ }
401
+ await tx.getRepository(UserBalanceEntity).update(
402
+ { userAddress: user, tokenAddress: token },
403
+ { balance: balance.balance - amount }
404
+ );
405
+ await tx.getRepository(LedgerJournalEntity).insert({
406
+ userAddress: user,
407
+ tokenAddress: token,
408
+ delta: -amount,
409
+ reason: "MINT_CONFIRMED",
410
+ txHash
411
+ });
412
+ const match = await tx.getRepository(LockedMintEntity).findOne({
413
+ where: {
414
+ userAddress: user,
415
+ tokenAddress: token,
416
+ amount,
417
+ status: "PENDING"
418
+ },
419
+ order: { createdAt: "ASC" }
420
+ });
421
+ if (match) {
422
+ await tx.getRepository(LockedMintEntity).update({ id: match.id }, { status: "MINTED", txHash });
423
+ }
424
+ });
425
+ this.logger?.log?.(`deduct ${user}[${token}] -${amount} tx=${txHash}`);
426
+ }
427
+ async updateMintStatus(lockId, status, txHash) {
428
+ const update = { status };
429
+ if (txHash) update.txHash = txHash;
430
+ await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, update);
431
+ }
432
+ async bindMintUserOpHash(lockId, userOpHash) {
433
+ await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, { userOpHash });
434
+ }
435
+ async bindCreditUserOpHash(lockId, userOpHash) {
436
+ await this.dataSource.getRepository(PendingCreditEntity).update({ id: lockId }, { userOpHash });
437
+ }
438
+ // ---------------------------------------------------------------------
439
+ // Reverse flow (burn → off-chain credit)
440
+ // ---------------------------------------------------------------------
441
+ async reservePendingCredit(userAddress, amount, durationMs, tokenAddress) {
442
+ if (amount <= 0n) {
443
+ throw new Error("reservePendingCredit: amount must be positive");
444
+ }
445
+ if (durationMs <= 0) {
446
+ throw new Error("reservePendingCredit: durationMs must be positive");
447
+ }
448
+ const { user, token } = normalize(userAddress, tokenAddress);
449
+ const row = await this.dataSource.getRepository(PendingCreditEntity).save({
450
+ userAddress: user,
451
+ tokenAddress: token,
452
+ amount,
453
+ status: "PENDING",
454
+ expiresAt: new Date(Date.now() + durationMs)
455
+ });
456
+ this.logger?.debug?.(
457
+ `reserve pending credit ${row.id} ${user}[${token}] +${amount}`
458
+ );
459
+ return row.id;
460
+ }
461
+ async resolveCreditByBurnTx(lockId, txHash) {
462
+ await this.dataSource.transaction(async (tx) => {
463
+ const credit = await tx.getRepository(PendingCreditEntity).findOne({ where: { id: lockId } });
464
+ if (!credit) {
465
+ throw new Error(
466
+ `resolveCreditByBurnTx: unknown pending credit ${lockId}`
467
+ );
468
+ }
469
+ if (credit.status === "RESOLVED") {
470
+ if (credit.txHash === txHash) return;
471
+ throw new Error(
472
+ `resolveCreditByBurnTx: credit ${lockId} already resolved with a different txHash`
473
+ );
474
+ }
475
+ if (credit.status === "EXPIRED") {
476
+ throw new Error(
477
+ `resolveCreditByBurnTx: credit ${lockId} already expired \u2014 burn landed too late`
478
+ );
479
+ }
480
+ const user = credit.userAddress;
481
+ const token = credit.tokenAddress;
482
+ const alreadyResolved = await tx.getRepository(PendingCreditEntity).findOne({
483
+ where: {
484
+ userAddress: user,
485
+ tokenAddress: token,
486
+ txHash,
487
+ status: "RESOLVED"
488
+ }
489
+ });
490
+ if (alreadyResolved) {
491
+ await tx.getRepository(PendingCreditEntity).update(
492
+ { id: lockId },
493
+ { status: "RESOLVED", txHash, resolvedAt: /* @__PURE__ */ new Date() }
494
+ );
495
+ return;
496
+ }
497
+ const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
498
+ const next = (balance?.balance ?? 0n) + credit.amount;
499
+ await tx.getRepository(UserBalanceEntity).upsert(
500
+ { userAddress: user, tokenAddress: token, balance: next },
501
+ { conflictPaths: ["userAddress", "tokenAddress"] }
502
+ );
503
+ await tx.getRepository(LedgerJournalEntity).insert({
504
+ userAddress: user,
505
+ tokenAddress: token,
506
+ delta: credit.amount,
507
+ reason: "BURN_FOR_CREDIT",
508
+ txHash
509
+ });
510
+ await tx.getRepository(PendingCreditEntity).update(
511
+ { id: lockId },
512
+ { status: "RESOLVED", txHash, resolvedAt: /* @__PURE__ */ new Date() }
513
+ );
514
+ });
515
+ this.logger?.log?.(`resolve pending credit ${lockId} tx=${txHash}`);
516
+ }
517
+ /**
518
+ * Used by `BurnIndexer.matchLockId` to resolve an on-chain burn
519
+ * event back to a pending credit row. Returns the oldest matching
520
+ * `(user, token, amount, status: PENDING)` lockId, or undefined
521
+ * when no match exists (unsolicited burn — indexer skips).
522
+ */
523
+ async findPendingCreditLockId(userAddress, amount, tokenAddress) {
524
+ const { user, token } = normalize(userAddress, tokenAddress);
525
+ const row = await this.dataSource.getRepository(PendingCreditEntity).findOne({
526
+ where: {
527
+ userAddress: user,
528
+ tokenAddress: token,
529
+ amount,
530
+ status: "PENDING"
531
+ },
532
+ order: { createdAt: "ASC" }
533
+ });
534
+ return row?.id;
535
+ }
536
+ // ---------------------------------------------------------------------
537
+ // Internals
538
+ // ---------------------------------------------------------------------
539
+ async sumPendingLocks(userAddress, tokenAddress) {
540
+ const row = await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder("lock").select("COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)", "sum").where("lock.user_address = :user", { user: userAddress }).andWhere("lock.token_address = :token", { token: tokenAddress }).andWhere("lock.status = :pending", { pending: "PENDING" }).andWhere("lock.expires_at > :now", { now: /* @__PURE__ */ new Date() }).getRawOne();
541
+ return row ? BigInt(row.sum) : 0n;
542
+ }
543
+ toSdkLock(row) {
544
+ const out = {
545
+ lockId: row.id,
546
+ userAddress: row.userAddress,
547
+ tokenAddress: row.tokenAddress,
548
+ amount: row.amount,
549
+ status: row.status,
550
+ createdAt: row.createdAt.getTime(),
551
+ expiresAt: row.expiresAt.getTime()
552
+ };
553
+ if (row.txHash) out.txHash = row.txHash;
554
+ if (row.userOpHash) out.userOpHash = row.userOpHash;
555
+ return out;
556
+ }
557
+ };
558
+ function normalize(userAddress, tokenAddress) {
559
+ if (!tokenAddress) {
560
+ throw new Error(
561
+ "PostgresPointLedger: tokenAddress is required on every call (multi-token ledger)"
562
+ );
563
+ }
564
+ return {
565
+ user: getAddress(userAddress),
566
+ token: getAddress(tokenAddress)
567
+ };
568
+ }
569
+
570
+ // src/entities/indexer-cursor.entity.ts
571
+ import { Column as Column5, Entity as Entity5, PrimaryColumn as PrimaryColumn2, UpdateDateColumn as UpdateDateColumn2 } from "typeorm";
572
+ var IndexerCursorEntity = class {
573
+ id;
574
+ nextBlock;
575
+ updatedAt;
576
+ };
577
+ __decorateClass([
578
+ PrimaryColumn2({ type: "varchar", length: 64 })
579
+ ], IndexerCursorEntity.prototype, "id", 2);
580
+ __decorateClass([
581
+ Column5({
582
+ name: "next_block",
583
+ type: "numeric",
584
+ precision: 78,
585
+ scale: 0,
586
+ transformer: {
587
+ to: (value) => value.toString(),
588
+ from: (value) => BigInt(value)
589
+ }
590
+ })
591
+ ], IndexerCursorEntity.prototype, "nextBlock", 2);
592
+ __decorateClass([
593
+ UpdateDateColumn2({ name: "updated_at" })
594
+ ], IndexerCursorEntity.prototype, "updatedAt", 2);
595
+ IndexerCursorEntity = __decorateClass([
596
+ Entity5({ name: "indexer_cursors" })
597
+ ], IndexerCursorEntity);
598
+
599
+ // src/postgresCursorStore.ts
600
+ var PostgresCursorStore = class _PostgresCursorStore {
601
+ constructor(dataSource, cursorId = "default") {
602
+ this.dataSource = dataSource;
603
+ this.cursorId = cursorId;
604
+ }
605
+ dataSource;
606
+ cursorId;
607
+ async load() {
608
+ const row = await this.dataSource.getRepository(IndexerCursorEntity).findOne({ where: { id: this.cursorId } });
609
+ return row?.nextBlock;
610
+ }
611
+ async save(blockNumber) {
612
+ await this.dataSource.getRepository(IndexerCursorEntity).upsert(
613
+ { id: this.cursorId, nextBlock: blockNumber },
614
+ { conflictPaths: ["id"] }
615
+ );
616
+ }
617
+ /** Derived store keyed by a different `id` — for sibling indexers. */
618
+ forKey(cursorId) {
619
+ return new _PostgresCursorStore(this.dataSource, cursorId);
620
+ }
621
+ };
622
+
623
+ // src/entities/index.ts
624
+ var PAFI_ENTITIES = [
625
+ LockedMintEntity,
626
+ PendingCreditEntity,
627
+ UserBalanceEntity,
628
+ LedgerJournalEntity,
629
+ IndexerCursorEntity
630
+ ];
631
+
632
+ // src/migrations/1700000000000-InitialSchema.ts
633
+ var InitialSchema1700000000000 = class {
634
+ name = "InitialSchema1700000000000";
635
+ async up(queryRunner) {
636
+ await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "pgcrypto"`);
637
+ await queryRunner.query(`
638
+ CREATE TABLE "user_balances" (
639
+ "user_address" varchar(42) NOT NULL,
640
+ "token_address" varchar(42) NOT NULL,
641
+ "balance" numeric(78, 0) NOT NULL DEFAULT 0,
642
+ "updated_at" TIMESTAMP NOT NULL DEFAULT now(),
643
+ CONSTRAINT "PK_user_balances" PRIMARY KEY ("user_address", "token_address")
644
+ )
645
+ `);
646
+ await queryRunner.query(`
647
+ CREATE TABLE "locked_mint_requests" (
648
+ "id" uuid NOT NULL DEFAULT gen_random_uuid(),
649
+ "user_address" varchar(42) NOT NULL,
650
+ "token_address" varchar(42) NOT NULL,
651
+ "amount" numeric(78, 0) NOT NULL,
652
+ "status" varchar(16) NOT NULL DEFAULT 'PENDING',
653
+ "created_at" TIMESTAMP NOT NULL DEFAULT now(),
654
+ "expires_at" TIMESTAMP WITH TIME ZONE NOT NULL,
655
+ "tx_hash" varchar(66),
656
+ "user_op_hash" varchar(66),
657
+ CONSTRAINT "PK_locked_mint_requests" PRIMARY KEY ("id")
658
+ )
659
+ `);
660
+ await queryRunner.query(`
661
+ CREATE INDEX "IDX_locked_mint_user_status"
662
+ ON "locked_mint_requests" ("user_address", "token_address", "status")
663
+ `);
664
+ await queryRunner.query(`
665
+ CREATE INDEX "IDX_locked_mint_user_op_hash"
666
+ ON "locked_mint_requests" ("user_op_hash")
667
+ `);
668
+ await queryRunner.query(`
669
+ CREATE TABLE "pending_credits" (
670
+ "id" uuid NOT NULL DEFAULT gen_random_uuid(),
671
+ "user_address" varchar(42) NOT NULL,
672
+ "token_address" varchar(42) NOT NULL,
673
+ "amount" numeric(78, 0) NOT NULL,
674
+ "status" varchar(16) NOT NULL DEFAULT 'PENDING',
675
+ "tx_hash" varchar(66),
676
+ "user_op_hash" varchar(66),
677
+ "created_at" TIMESTAMP NOT NULL DEFAULT now(),
678
+ "expires_at" TIMESTAMP WITH TIME ZONE NOT NULL,
679
+ "resolved_at" TIMESTAMP WITH TIME ZONE,
680
+ CONSTRAINT "PK_pending_credits" PRIMARY KEY ("id")
681
+ )
682
+ `);
683
+ await queryRunner.query(`
684
+ CREATE INDEX "IDX_pending_credits_user_status"
685
+ ON "pending_credits" ("user_address", "token_address", "status")
686
+ `);
687
+ await queryRunner.query(`
688
+ CREATE INDEX "IDX_pending_credits_tx_hash"
689
+ ON "pending_credits" ("tx_hash")
690
+ `);
691
+ await queryRunner.query(`
692
+ CREATE INDEX "IDX_pending_credits_user_op_hash"
693
+ ON "pending_credits" ("user_op_hash")
694
+ `);
695
+ await queryRunner.query(`
696
+ CREATE TABLE "ledger_journal" (
697
+ "id" uuid NOT NULL DEFAULT gen_random_uuid(),
698
+ "user_address" varchar(42) NOT NULL,
699
+ "token_address" varchar(42) NOT NULL,
700
+ "delta" numeric(78, 0) NOT NULL,
701
+ "reason" varchar(128) NOT NULL,
702
+ "tx_hash" varchar(66),
703
+ "created_at" TIMESTAMP NOT NULL DEFAULT now(),
704
+ CONSTRAINT "PK_ledger_journal" PRIMARY KEY ("id")
705
+ )
706
+ `);
707
+ await queryRunner.query(`
708
+ CREATE INDEX "IDX_ledger_journal_user_created"
709
+ ON "ledger_journal" ("user_address", "token_address", "created_at")
710
+ `);
711
+ await queryRunner.query(`
712
+ CREATE TABLE "indexer_cursors" (
713
+ "id" varchar(64) NOT NULL,
714
+ "next_block" numeric(78, 0) NOT NULL,
715
+ "updated_at" TIMESTAMP NOT NULL DEFAULT now(),
716
+ CONSTRAINT "PK_indexer_cursors" PRIMARY KEY ("id")
717
+ )
718
+ `);
719
+ }
720
+ async down(queryRunner) {
721
+ await queryRunner.query(`DROP TABLE "indexer_cursors"`);
722
+ await queryRunner.query(`DROP INDEX "IDX_ledger_journal_user_created"`);
723
+ await queryRunner.query(`DROP TABLE "ledger_journal"`);
724
+ await queryRunner.query(`DROP INDEX "IDX_pending_credits_user_op_hash"`);
725
+ await queryRunner.query(`DROP INDEX "IDX_pending_credits_tx_hash"`);
726
+ await queryRunner.query(`DROP INDEX "IDX_pending_credits_user_status"`);
727
+ await queryRunner.query(`DROP TABLE "pending_credits"`);
728
+ await queryRunner.query(`DROP INDEX "IDX_locked_mint_user_op_hash"`);
729
+ await queryRunner.query(`DROP INDEX "IDX_locked_mint_user_status"`);
730
+ await queryRunner.query(`DROP TABLE "locked_mint_requests"`);
731
+ await queryRunner.query(`DROP TABLE "user_balances"`);
732
+ }
733
+ };
734
+
735
+ // src/migrations/index.ts
736
+ var PAFI_MIGRATIONS = [InitialSchema1700000000000];
737
+ export {
738
+ IndexerCursorEntity,
739
+ InitialSchema1700000000000,
740
+ LedgerJournalEntity,
741
+ LockedMintEntity,
742
+ PAFI_ENTITIES,
743
+ PAFI_MIGRATIONS,
744
+ PendingCreditEntity,
745
+ PostgresCursorStore,
746
+ PostgresPointLedger,
747
+ UserBalanceEntity
748
+ };
749
+ //# sourceMappingURL=index.js.map