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