@pafi-dev/issuer-postgres 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -14
- package/dist/entities/index.cjs +25 -2
- package/dist/entities/index.cjs.map +1 -1
- package/dist/entities/index.d.cts +32 -2
- package/dist/entities/index.d.ts +32 -2
- package/dist/entities/index.js +25 -2
- package/dist/entities/index.js.map +1 -1
- package/dist/index.cjs +280 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -4
- package/dist/index.d.ts +52 -4
- package/dist/index.js +265 -36
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.cjs +100 -1
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +136 -6
- package/dist/migrations/index.d.ts +136 -6
- package/dist/migrations/index.js +97 -1
- package/dist/migrations/index.js.map +1 -1
- package/package.json +16 -5
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,7 @@ __export(src_exports, {
|
|
|
45
45
|
module.exports = __toCommonJS(src_exports);
|
|
46
46
|
|
|
47
47
|
// src/postgresPointLedger.ts
|
|
48
|
+
var import_typeorm5 = require("typeorm");
|
|
48
49
|
var import_viem = require("viem");
|
|
49
50
|
|
|
50
51
|
// src/entities/locked-mint.entity.ts
|
|
@@ -108,7 +109,22 @@ __decorateClass([
|
|
|
108
109
|
], LockedMintEntity.prototype, "userOpHash", 2);
|
|
109
110
|
LockedMintEntity = __decorateClass([
|
|
110
111
|
(0, import_typeorm.Entity)({ name: "locked_mint_requests" }),
|
|
111
|
-
(0, import_typeorm.Index)(
|
|
112
|
+
(0, import_typeorm.Index)("IDX_locked_mint_user_token_status_expires", [
|
|
113
|
+
"userAddress",
|
|
114
|
+
"tokenAddress",
|
|
115
|
+
"status",
|
|
116
|
+
"expiresAt"
|
|
117
|
+
]),
|
|
118
|
+
(0, import_typeorm.Index)("IDX_locked_mint_user_token_amount_status", [
|
|
119
|
+
"userAddress",
|
|
120
|
+
"tokenAddress",
|
|
121
|
+
"amount",
|
|
122
|
+
"status"
|
|
123
|
+
]),
|
|
124
|
+
(0, import_typeorm.Index)("IDX_locked_mint_pending_expires", ["expiresAt"], {
|
|
125
|
+
where: `"status" = 'PENDING'`
|
|
126
|
+
}),
|
|
127
|
+
(0, import_typeorm.Index)("IDX_locked_mint_user_op_hash", ["userOpHash"])
|
|
112
128
|
], LockedMintEntity);
|
|
113
129
|
|
|
114
130
|
// src/entities/pending-credit.entity.ts
|
|
@@ -261,11 +277,22 @@ __decorateClass([
|
|
|
261
277
|
], LedgerJournalEntity.prototype, "createdAt", 2);
|
|
262
278
|
LedgerJournalEntity = __decorateClass([
|
|
263
279
|
(0, import_typeorm4.Entity)({ name: "ledger_journal" }),
|
|
264
|
-
(0, import_typeorm4.Index)(["userAddress", "createdAt"])
|
|
280
|
+
(0, import_typeorm4.Index)(["userAddress", "createdAt"]),
|
|
281
|
+
(0, import_typeorm4.Index)(
|
|
282
|
+
"UQ_ledger_journal_user_token_tx_reason",
|
|
283
|
+
["userAddress", "tokenAddress", "txHash", "reason"],
|
|
284
|
+
{
|
|
285
|
+
unique: true,
|
|
286
|
+
where: '"tx_hash" IS NOT NULL'
|
|
287
|
+
}
|
|
288
|
+
)
|
|
265
289
|
], LedgerJournalEntity);
|
|
266
290
|
|
|
267
291
|
// src/postgresPointLedger.ts
|
|
268
292
|
var RETRIABLE_PG_CODES = /* @__PURE__ */ new Set(["40P01", "40001"]);
|
|
293
|
+
var UNIQUE_VIOLATION = "23505";
|
|
294
|
+
var JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_token_tx_reason";
|
|
295
|
+
var LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_tx_reason";
|
|
269
296
|
function isRetriablePgError(err) {
|
|
270
297
|
const e = err;
|
|
271
298
|
if (!e) return false;
|
|
@@ -275,6 +302,18 @@ function isRetriablePgError(err) {
|
|
|
275
302
|
}
|
|
276
303
|
return false;
|
|
277
304
|
}
|
|
305
|
+
function isJournalIdempotencyViolation(err) {
|
|
306
|
+
const e = err;
|
|
307
|
+
if (!e) return false;
|
|
308
|
+
const code = e.code ?? e.driverError?.code;
|
|
309
|
+
if (code !== UNIQUE_VIOLATION) return false;
|
|
310
|
+
const constraint = e.constraint ?? e.driverError?.constraint;
|
|
311
|
+
if (constraint === JOURNAL_IDEMPOTENCY_CONSTRAINT || constraint === LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
const message = e.message ?? e.driverError?.message ?? "";
|
|
315
|
+
return message.includes(JOURNAL_IDEMPOTENCY_CONSTRAINT) || message.includes(LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT);
|
|
316
|
+
}
|
|
278
317
|
async function withDeadlockRetry(dataSource, fn, maxAttempts = 3) {
|
|
279
318
|
let attempt = 0;
|
|
280
319
|
let delayMs = 25;
|
|
@@ -346,6 +385,40 @@ var PostgresPointLedger = class {
|
|
|
346
385
|
}
|
|
347
386
|
return swept;
|
|
348
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Audit PACI5-20 — symmetric counterpart of `markExpiredLocks` for
|
|
390
|
+
* the redeem/burn (PendingCredit) side. Marks all expired PENDING
|
|
391
|
+
* pending_credits as EXPIRED so abandoned reservations can no
|
|
392
|
+
* longer hijack later burns' attribution in `findPendingCreditLockId`.
|
|
393
|
+
*
|
|
394
|
+
* The README has always promised this sweep; the pre-fix code only
|
|
395
|
+
* shipped the mint-side `markExpiredLocks`, leaving credits to
|
|
396
|
+
* accumulate forever (storage bloat) and stay matchable past their
|
|
397
|
+
* deadline (attribution corruption). Wire it into the BurnIndexer
|
|
398
|
+
* tick the same way `markExpiredLocks` is wired into the
|
|
399
|
+
* MintIndexer tick — typically every 1-5 minutes.
|
|
400
|
+
*
|
|
401
|
+
* Defense-in-depth: `findPendingCreditLockId` also filters
|
|
402
|
+
* `expires_at > now()` so a missed-tick window can't reintroduce
|
|
403
|
+
* the hijack.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* @Interval(60_000)
|
|
408
|
+
* async sweep() {
|
|
409
|
+
* await this.ledger.markExpiredLocks();
|
|
410
|
+
* await this.ledger.markExpiredCredits();
|
|
411
|
+
* }
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
async markExpiredCredits() {
|
|
415
|
+
const result = await this.dataSource.getRepository(PendingCreditEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
|
|
416
|
+
const swept = result.affected ?? 0;
|
|
417
|
+
if (swept > 0) {
|
|
418
|
+
this.logger?.debug?.(`markExpiredCredits: swept ${swept} pending credits`);
|
|
419
|
+
}
|
|
420
|
+
return swept;
|
|
421
|
+
}
|
|
349
422
|
async getLockedRequests(userAddress, tokenAddress) {
|
|
350
423
|
const { user, token } = normalize(userAddress, tokenAddress);
|
|
351
424
|
const rows = await this.dataSource.getRepository(LockedMintEntity).find({
|
|
@@ -470,43 +543,72 @@ var PostgresPointLedger = class {
|
|
|
470
543
|
throw new Error("deductBalance: amount must be positive");
|
|
471
544
|
}
|
|
472
545
|
const { user, token } = normalize(userAddress, tokenAddress);
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
546
|
+
try {
|
|
547
|
+
await withDeadlockRetry(this.dataSource, async (tx) => {
|
|
548
|
+
const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
|
|
549
|
+
const already = await tx.getRepository(LedgerJournalEntity).findOne({
|
|
550
|
+
where: {
|
|
551
|
+
userAddress: user,
|
|
552
|
+
tokenAddress: token,
|
|
553
|
+
txHash,
|
|
554
|
+
reason: "MINT_CONFIRMED"
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
if (already) {
|
|
558
|
+
this.logger?.debug?.(
|
|
559
|
+
`deductBalance: idempotent skip tx=${txHash} user=${user} token=${token}`
|
|
560
|
+
);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (!balance || balance.balance < amount) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
`Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
await tx.getRepository(UserBalanceEntity).update(
|
|
569
|
+
{ userAddress: user, tokenAddress: token },
|
|
570
|
+
{ balance: balance.balance - amount }
|
|
478
571
|
);
|
|
479
|
-
|
|
480
|
-
await tx.getRepository(UserBalanceEntity).update(
|
|
481
|
-
{ userAddress: user, tokenAddress: token },
|
|
482
|
-
{ balance: balance.balance - amount }
|
|
483
|
-
);
|
|
484
|
-
await tx.getRepository(LedgerJournalEntity).insert({
|
|
485
|
-
userAddress: user,
|
|
486
|
-
tokenAddress: token,
|
|
487
|
-
delta: -amount,
|
|
488
|
-
reason: "MINT_CONFIRMED",
|
|
489
|
-
txHash
|
|
490
|
-
});
|
|
491
|
-
const match = await tx.getRepository(LockedMintEntity).findOne({
|
|
492
|
-
where: {
|
|
572
|
+
await tx.getRepository(LedgerJournalEntity).insert({
|
|
493
573
|
userAddress: user,
|
|
494
574
|
tokenAddress: token,
|
|
495
|
-
amount,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
575
|
+
delta: -amount,
|
|
576
|
+
reason: "MINT_CONFIRMED",
|
|
577
|
+
txHash
|
|
578
|
+
});
|
|
579
|
+
const match = await tx.getRepository(LockedMintEntity).findOne({
|
|
580
|
+
where: {
|
|
581
|
+
userAddress: user,
|
|
582
|
+
tokenAddress: token,
|
|
583
|
+
amount,
|
|
584
|
+
status: "PENDING"
|
|
585
|
+
},
|
|
586
|
+
order: { createdAt: "ASC" }
|
|
587
|
+
});
|
|
588
|
+
if (match) {
|
|
589
|
+
await tx.getRepository(LockedMintEntity).update({ id: match.id }, { status: "MINTED", txHash });
|
|
590
|
+
}
|
|
499
591
|
});
|
|
500
|
-
|
|
501
|
-
|
|
592
|
+
} catch (err) {
|
|
593
|
+
if (isJournalIdempotencyViolation(err)) {
|
|
594
|
+
this.logger?.debug?.(
|
|
595
|
+
`deductBalance: idempotent (concurrent race) tx=${txHash} user=${user}`
|
|
596
|
+
);
|
|
597
|
+
return;
|
|
502
598
|
}
|
|
503
|
-
|
|
599
|
+
throw err;
|
|
600
|
+
}
|
|
504
601
|
this.logger?.log?.(`deduct ${user}[${token}] -${amount} tx=${txHash}`);
|
|
505
602
|
}
|
|
506
603
|
async updateMintStatus(lockId, status, txHash) {
|
|
507
604
|
const update = { status };
|
|
508
605
|
if (txHash) update.txHash = txHash;
|
|
509
|
-
await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId },
|
|
606
|
+
const result = await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set(update).where("id = :id", { id: lockId }).andWhere("status = :pending", { pending: "PENDING" }).execute();
|
|
607
|
+
if ((result.affected ?? 0) === 0) {
|
|
608
|
+
this.logger?.debug?.(
|
|
609
|
+
`updateMintStatus: lock ${lockId} not in PENDING (terminal state), no-op`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
510
612
|
}
|
|
511
613
|
async bindMintUserOpHash(lockId, userOpHash) {
|
|
512
614
|
await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, { userOpHash });
|
|
@@ -538,7 +640,7 @@ var PostgresPointLedger = class {
|
|
|
538
640
|
return row.id;
|
|
539
641
|
}
|
|
540
642
|
async resolveCreditByBurnTx(lockId, txHash) {
|
|
541
|
-
|
|
643
|
+
const run = async () => withDeadlockRetry(this.dataSource, async (tx) => {
|
|
542
644
|
const credit = await tx.getRepository(PendingCreditEntity).createQueryBuilder("credit").setLock("pessimistic_write").where("credit.id = :id", { id: lockId }).getOne();
|
|
543
645
|
if (!credit) {
|
|
544
646
|
throw new Error(
|
|
@@ -591,13 +693,46 @@ var PostgresPointLedger = class {
|
|
|
591
693
|
{ status: "RESOLVED", txHash, resolvedAt: /* @__PURE__ */ new Date() }
|
|
592
694
|
);
|
|
593
695
|
});
|
|
696
|
+
try {
|
|
697
|
+
await run();
|
|
698
|
+
} catch (err) {
|
|
699
|
+
if (isJournalIdempotencyViolation(err)) {
|
|
700
|
+
this.logger?.debug?.(
|
|
701
|
+
`resolveCreditByBurnTx: concurrent race on tx=${txHash} lock=${lockId}, replaying via sibling defense`
|
|
702
|
+
);
|
|
703
|
+
await run();
|
|
704
|
+
} else {
|
|
705
|
+
throw err;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
594
708
|
this.logger?.log?.(`resolve pending credit ${lockId} tx=${txHash}`);
|
|
595
709
|
}
|
|
596
710
|
/**
|
|
597
711
|
* Used by `BurnIndexer.matchLockId` to resolve an on-chain burn
|
|
598
|
-
* event back to a pending credit row. Returns the
|
|
599
|
-
* `(user, token, amount, status: PENDING
|
|
600
|
-
* when no match exists (unsolicited burn —
|
|
712
|
+
* event back to a pending credit row. Returns the **newest**
|
|
713
|
+
* matching `(user, token, amount, status: PENDING, expires_at > now)`
|
|
714
|
+
* lockId, or undefined when no match exists (unsolicited burn —
|
|
715
|
+
* indexer skips).
|
|
716
|
+
*
|
|
717
|
+
* Audit PACI5-20 — two filters narrowed since the pre-fix shipped:
|
|
718
|
+
*
|
|
719
|
+
* 1. `expires_at > now()` — abandoned PENDING reservations past
|
|
720
|
+
* their deadline no longer hijack a fresh burn's attribution.
|
|
721
|
+
* This is defense-in-depth in case `markExpiredCredits` hasn't
|
|
722
|
+
* run recently (missed-tick window).
|
|
723
|
+
*
|
|
724
|
+
* 2. `ORDER BY createdAt DESC` (was ASC) — when multiple PENDING
|
|
725
|
+
* reservations match the same (user, token, amount) within
|
|
726
|
+
* the validity window, prefer the most recent one. Matches
|
|
727
|
+
* user mental model: "I just submitted /redeem, my recent
|
|
728
|
+
* reservation should win" rather than resurrecting a stale
|
|
729
|
+
* one. With the sweep + filter the typical steady state has
|
|
730
|
+
* at most one matching row, so this only affects edge cases
|
|
731
|
+
* (very fast re-submit, or concurrent flows).
|
|
732
|
+
*
|
|
733
|
+
* Combined with the partial UNIQUE index on `(txHash, status =
|
|
734
|
+
* RESOLVED)` from PACI5-7, the attribution can no longer drift
|
|
735
|
+
* across sessions even under adversarial conditions.
|
|
601
736
|
*/
|
|
602
737
|
async findPendingCreditLockId(userAddress, amount, tokenAddress) {
|
|
603
738
|
const { user, token } = normalize(userAddress, tokenAddress);
|
|
@@ -606,9 +741,10 @@ var PostgresPointLedger = class {
|
|
|
606
741
|
userAddress: user,
|
|
607
742
|
tokenAddress: token,
|
|
608
743
|
amount,
|
|
609
|
-
status: "PENDING"
|
|
744
|
+
status: "PENDING",
|
|
745
|
+
expiresAt: (0, import_typeorm5.MoreThan)(/* @__PURE__ */ new Date())
|
|
610
746
|
},
|
|
611
|
-
order: { createdAt: "
|
|
747
|
+
order: { createdAt: "DESC" }
|
|
612
748
|
});
|
|
613
749
|
return row?.id;
|
|
614
750
|
}
|
|
@@ -647,17 +783,17 @@ function normalize(userAddress, tokenAddress) {
|
|
|
647
783
|
}
|
|
648
784
|
|
|
649
785
|
// src/entities/indexer-cursor.entity.ts
|
|
650
|
-
var
|
|
786
|
+
var import_typeorm6 = require("typeorm");
|
|
651
787
|
var IndexerCursorEntity = class {
|
|
652
788
|
id;
|
|
653
789
|
nextBlock;
|
|
654
790
|
updatedAt;
|
|
655
791
|
};
|
|
656
792
|
__decorateClass([
|
|
657
|
-
(0,
|
|
793
|
+
(0, import_typeorm6.PrimaryColumn)({ type: "varchar", length: 64 })
|
|
658
794
|
], IndexerCursorEntity.prototype, "id", 2);
|
|
659
795
|
__decorateClass([
|
|
660
|
-
(0,
|
|
796
|
+
(0, import_typeorm6.Column)({
|
|
661
797
|
name: "next_block",
|
|
662
798
|
type: "numeric",
|
|
663
799
|
precision: 78,
|
|
@@ -669,10 +805,10 @@ __decorateClass([
|
|
|
669
805
|
})
|
|
670
806
|
], IndexerCursorEntity.prototype, "nextBlock", 2);
|
|
671
807
|
__decorateClass([
|
|
672
|
-
(0,
|
|
808
|
+
(0, import_typeorm6.UpdateDateColumn)({ name: "updated_at" })
|
|
673
809
|
], IndexerCursorEntity.prototype, "updatedAt", 2);
|
|
674
810
|
IndexerCursorEntity = __decorateClass([
|
|
675
|
-
(0,
|
|
811
|
+
(0, import_typeorm6.Entity)({ name: "indexer_cursors" })
|
|
676
812
|
], IndexerCursorEntity);
|
|
677
813
|
|
|
678
814
|
// src/postgresCursorStore.ts
|
|
@@ -703,7 +839,7 @@ var PostgresCursorStore = class _PostgresCursorStore {
|
|
|
703
839
|
var import_viem2 = require("viem");
|
|
704
840
|
|
|
705
841
|
// src/entities/redemption-history.entity.ts
|
|
706
|
-
var
|
|
842
|
+
var import_typeorm7 = require("typeorm");
|
|
707
843
|
var RedemptionHistoryEntity = class {
|
|
708
844
|
id;
|
|
709
845
|
userAddress;
|
|
@@ -714,16 +850,16 @@ var RedemptionHistoryEntity = class {
|
|
|
714
850
|
rowCreatedAt;
|
|
715
851
|
};
|
|
716
852
|
__decorateClass([
|
|
717
|
-
(0,
|
|
853
|
+
(0, import_typeorm7.PrimaryGeneratedColumn)("uuid")
|
|
718
854
|
], RedemptionHistoryEntity.prototype, "id", 2);
|
|
719
855
|
__decorateClass([
|
|
720
|
-
(0,
|
|
856
|
+
(0, import_typeorm7.Column)({ name: "user_address", type: "varchar", length: 42 })
|
|
721
857
|
], RedemptionHistoryEntity.prototype, "userAddress", 2);
|
|
722
858
|
__decorateClass([
|
|
723
|
-
(0,
|
|
859
|
+
(0, import_typeorm7.Column)({ name: "token_address", type: "varchar", length: 42, nullable: true })
|
|
724
860
|
], RedemptionHistoryEntity.prototype, "tokenAddress", 2);
|
|
725
861
|
__decorateClass([
|
|
726
|
-
(0,
|
|
862
|
+
(0, import_typeorm7.Column)({
|
|
727
863
|
name: "amount_pt",
|
|
728
864
|
type: "numeric",
|
|
729
865
|
precision: 78,
|
|
@@ -735,10 +871,10 @@ __decorateClass([
|
|
|
735
871
|
})
|
|
736
872
|
], RedemptionHistoryEntity.prototype, "amountPt", 2);
|
|
737
873
|
__decorateClass([
|
|
738
|
-
(0,
|
|
874
|
+
(0, import_typeorm7.Column)({ name: "created_at_unix_sec", type: "bigint" })
|
|
739
875
|
], RedemptionHistoryEntity.prototype, "createdAtUnixSec", 2);
|
|
740
876
|
__decorateClass([
|
|
741
|
-
(0,
|
|
877
|
+
(0, import_typeorm7.Column)({
|
|
742
878
|
name: "reservation_id",
|
|
743
879
|
type: "varchar",
|
|
744
880
|
length: 64,
|
|
@@ -746,11 +882,11 @@ __decorateClass([
|
|
|
746
882
|
})
|
|
747
883
|
], RedemptionHistoryEntity.prototype, "reservationId", 2);
|
|
748
884
|
__decorateClass([
|
|
749
|
-
(0,
|
|
885
|
+
(0, import_typeorm7.CreateDateColumn)({ name: "row_created_at", type: "timestamptz" })
|
|
750
886
|
], RedemptionHistoryEntity.prototype, "rowCreatedAt", 2);
|
|
751
887
|
RedemptionHistoryEntity = __decorateClass([
|
|
752
|
-
(0,
|
|
753
|
-
(0,
|
|
888
|
+
(0, import_typeorm7.Entity)({ name: "redemption_history" }),
|
|
889
|
+
(0, import_typeorm7.Index)("idx_redemption_history_user_created", [
|
|
754
890
|
"userAddress",
|
|
755
891
|
"createdAtUnixSec"
|
|
756
892
|
])
|
|
@@ -941,10 +1077,103 @@ var CreateRedemptionHistory1746230400001 = class {
|
|
|
941
1077
|
}
|
|
942
1078
|
};
|
|
943
1079
|
|
|
1080
|
+
// src/migrations/1747500000000-AddJournalIdempotencyIndex.ts
|
|
1081
|
+
var AddJournalIdempotencyIndex1747500000000 = class {
|
|
1082
|
+
name = "AddJournalIdempotencyIndex1747500000000";
|
|
1083
|
+
async up(queryRunner) {
|
|
1084
|
+
await queryRunner.query(`
|
|
1085
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
|
|
1086
|
+
ON "ledger_journal" ("user_address", "tx_hash", "reason")
|
|
1087
|
+
WHERE "tx_hash" IS NOT NULL
|
|
1088
|
+
`);
|
|
1089
|
+
}
|
|
1090
|
+
async down(queryRunner) {
|
|
1091
|
+
await queryRunner.query(
|
|
1092
|
+
`DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
// src/migrations/1747600000000-AddLockedMintCompositeIndexes.ts
|
|
1098
|
+
var AddLockedMintCompositeIndexes1747600000000 = class {
|
|
1099
|
+
name = "AddLockedMintCompositeIndexes1747600000000";
|
|
1100
|
+
/**
|
|
1101
|
+
* CONCURRENTLY index DDL cannot run inside a transaction. Tell
|
|
1102
|
+
* TypeORM to issue these statements directly.
|
|
1103
|
+
*/
|
|
1104
|
+
transaction = false;
|
|
1105
|
+
async up(queryRunner) {
|
|
1106
|
+
await queryRunner.query(`
|
|
1107
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
|
1108
|
+
"IDX_locked_mint_user_token_status_expires"
|
|
1109
|
+
ON "locked_mint_requests"
|
|
1110
|
+
("user_address", "token_address", "status", "expires_at")
|
|
1111
|
+
`);
|
|
1112
|
+
await queryRunner.query(`
|
|
1113
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
|
1114
|
+
"IDX_locked_mint_user_token_amount_status"
|
|
1115
|
+
ON "locked_mint_requests"
|
|
1116
|
+
("user_address", "token_address", "amount", "status")
|
|
1117
|
+
`);
|
|
1118
|
+
await queryRunner.query(`
|
|
1119
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
|
1120
|
+
"IDX_locked_mint_pending_expires"
|
|
1121
|
+
ON "locked_mint_requests" ("expires_at")
|
|
1122
|
+
WHERE "status" = 'PENDING'
|
|
1123
|
+
`);
|
|
1124
|
+
await queryRunner.query(
|
|
1125
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_status"`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
async down(queryRunner) {
|
|
1129
|
+
await queryRunner.query(`
|
|
1130
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS "IDX_locked_mint_user_status"
|
|
1131
|
+
ON "locked_mint_requests" ("user_address", "token_address", "status")
|
|
1132
|
+
`);
|
|
1133
|
+
await queryRunner.query(
|
|
1134
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_pending_expires"`
|
|
1135
|
+
);
|
|
1136
|
+
await queryRunner.query(
|
|
1137
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_amount_status"`
|
|
1138
|
+
);
|
|
1139
|
+
await queryRunner.query(
|
|
1140
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_status_expires"`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
// src/migrations/1747700000000-FixIdempotencyAddTokenAddress.ts
|
|
1146
|
+
var FixIdempotencyAddTokenAddress1747700000000 = class {
|
|
1147
|
+
name = "FixIdempotencyAddTokenAddress1747700000000";
|
|
1148
|
+
async up(queryRunner) {
|
|
1149
|
+
await queryRunner.query(
|
|
1150
|
+
`DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
|
|
1151
|
+
);
|
|
1152
|
+
await queryRunner.query(`
|
|
1153
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_token_tx_reason"
|
|
1154
|
+
ON "ledger_journal" ("user_address", "token_address", "tx_hash", "reason")
|
|
1155
|
+
WHERE "tx_hash" IS NOT NULL
|
|
1156
|
+
`);
|
|
1157
|
+
}
|
|
1158
|
+
async down(queryRunner) {
|
|
1159
|
+
await queryRunner.query(
|
|
1160
|
+
`DROP INDEX IF EXISTS "UQ_ledger_journal_user_token_tx_reason"`
|
|
1161
|
+
);
|
|
1162
|
+
await queryRunner.query(`
|
|
1163
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
|
|
1164
|
+
ON "ledger_journal" ("user_address", "tx_hash", "reason")
|
|
1165
|
+
WHERE "tx_hash" IS NOT NULL
|
|
1166
|
+
`);
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
944
1170
|
// src/migrations/index.ts
|
|
945
1171
|
var PAFI_MIGRATIONS = [
|
|
946
1172
|
InitialSchema1700000000000,
|
|
947
|
-
CreateRedemptionHistory1746230400001
|
|
1173
|
+
CreateRedemptionHistory1746230400001,
|
|
1174
|
+
AddJournalIdempotencyIndex1747500000000,
|
|
1175
|
+
AddLockedMintCompositeIndexes1747600000000,
|
|
1176
|
+
FixIdempotencyAddTokenAddress1747700000000
|
|
948
1177
|
];
|
|
949
1178
|
// Annotate the CommonJS export names for ESM import in node:
|
|
950
1179
|
0 && (module.exports = {
|