@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.js
CHANGED
|
@@ -10,6 +10,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/postgresPointLedger.ts
|
|
13
|
+
import { MoreThan } from "typeorm";
|
|
13
14
|
import { getAddress } from "viem";
|
|
14
15
|
|
|
15
16
|
// src/entities/locked-mint.entity.ts
|
|
@@ -79,7 +80,22 @@ __decorateClass([
|
|
|
79
80
|
], LockedMintEntity.prototype, "userOpHash", 2);
|
|
80
81
|
LockedMintEntity = __decorateClass([
|
|
81
82
|
Entity({ name: "locked_mint_requests" }),
|
|
82
|
-
Index(
|
|
83
|
+
Index("IDX_locked_mint_user_token_status_expires", [
|
|
84
|
+
"userAddress",
|
|
85
|
+
"tokenAddress",
|
|
86
|
+
"status",
|
|
87
|
+
"expiresAt"
|
|
88
|
+
]),
|
|
89
|
+
Index("IDX_locked_mint_user_token_amount_status", [
|
|
90
|
+
"userAddress",
|
|
91
|
+
"tokenAddress",
|
|
92
|
+
"amount",
|
|
93
|
+
"status"
|
|
94
|
+
]),
|
|
95
|
+
Index("IDX_locked_mint_pending_expires", ["expiresAt"], {
|
|
96
|
+
where: `"status" = 'PENDING'`
|
|
97
|
+
}),
|
|
98
|
+
Index("IDX_locked_mint_user_op_hash", ["userOpHash"])
|
|
83
99
|
], LockedMintEntity);
|
|
84
100
|
|
|
85
101
|
// src/entities/pending-credit.entity.ts
|
|
@@ -244,11 +260,22 @@ __decorateClass([
|
|
|
244
260
|
], LedgerJournalEntity.prototype, "createdAt", 2);
|
|
245
261
|
LedgerJournalEntity = __decorateClass([
|
|
246
262
|
Entity4({ name: "ledger_journal" }),
|
|
247
|
-
Index3(["userAddress", "createdAt"])
|
|
263
|
+
Index3(["userAddress", "createdAt"]),
|
|
264
|
+
Index3(
|
|
265
|
+
"UQ_ledger_journal_user_token_tx_reason",
|
|
266
|
+
["userAddress", "tokenAddress", "txHash", "reason"],
|
|
267
|
+
{
|
|
268
|
+
unique: true,
|
|
269
|
+
where: '"tx_hash" IS NOT NULL'
|
|
270
|
+
}
|
|
271
|
+
)
|
|
248
272
|
], LedgerJournalEntity);
|
|
249
273
|
|
|
250
274
|
// src/postgresPointLedger.ts
|
|
251
275
|
var RETRIABLE_PG_CODES = /* @__PURE__ */ new Set(["40P01", "40001"]);
|
|
276
|
+
var UNIQUE_VIOLATION = "23505";
|
|
277
|
+
var JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_token_tx_reason";
|
|
278
|
+
var LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_tx_reason";
|
|
252
279
|
function isRetriablePgError(err) {
|
|
253
280
|
const e = err;
|
|
254
281
|
if (!e) return false;
|
|
@@ -258,6 +285,18 @@ function isRetriablePgError(err) {
|
|
|
258
285
|
}
|
|
259
286
|
return false;
|
|
260
287
|
}
|
|
288
|
+
function isJournalIdempotencyViolation(err) {
|
|
289
|
+
const e = err;
|
|
290
|
+
if (!e) return false;
|
|
291
|
+
const code = e.code ?? e.driverError?.code;
|
|
292
|
+
if (code !== UNIQUE_VIOLATION) return false;
|
|
293
|
+
const constraint = e.constraint ?? e.driverError?.constraint;
|
|
294
|
+
if (constraint === JOURNAL_IDEMPOTENCY_CONSTRAINT || constraint === LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
const message = e.message ?? e.driverError?.message ?? "";
|
|
298
|
+
return message.includes(JOURNAL_IDEMPOTENCY_CONSTRAINT) || message.includes(LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT);
|
|
299
|
+
}
|
|
261
300
|
async function withDeadlockRetry(dataSource, fn, maxAttempts = 3) {
|
|
262
301
|
let attempt = 0;
|
|
263
302
|
let delayMs = 25;
|
|
@@ -329,6 +368,40 @@ var PostgresPointLedger = class {
|
|
|
329
368
|
}
|
|
330
369
|
return swept;
|
|
331
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Audit PACI5-20 — symmetric counterpart of `markExpiredLocks` for
|
|
373
|
+
* the redeem/burn (PendingCredit) side. Marks all expired PENDING
|
|
374
|
+
* pending_credits as EXPIRED so abandoned reservations can no
|
|
375
|
+
* longer hijack later burns' attribution in `findPendingCreditLockId`.
|
|
376
|
+
*
|
|
377
|
+
* The README has always promised this sweep; the pre-fix code only
|
|
378
|
+
* shipped the mint-side `markExpiredLocks`, leaving credits to
|
|
379
|
+
* accumulate forever (storage bloat) and stay matchable past their
|
|
380
|
+
* deadline (attribution corruption). Wire it into the BurnIndexer
|
|
381
|
+
* tick the same way `markExpiredLocks` is wired into the
|
|
382
|
+
* MintIndexer tick — typically every 1-5 minutes.
|
|
383
|
+
*
|
|
384
|
+
* Defense-in-depth: `findPendingCreditLockId` also filters
|
|
385
|
+
* `expires_at > now()` so a missed-tick window can't reintroduce
|
|
386
|
+
* the hijack.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* @Interval(60_000)
|
|
391
|
+
* async sweep() {
|
|
392
|
+
* await this.ledger.markExpiredLocks();
|
|
393
|
+
* await this.ledger.markExpiredCredits();
|
|
394
|
+
* }
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
async markExpiredCredits() {
|
|
398
|
+
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();
|
|
399
|
+
const swept = result.affected ?? 0;
|
|
400
|
+
if (swept > 0) {
|
|
401
|
+
this.logger?.debug?.(`markExpiredCredits: swept ${swept} pending credits`);
|
|
402
|
+
}
|
|
403
|
+
return swept;
|
|
404
|
+
}
|
|
332
405
|
async getLockedRequests(userAddress, tokenAddress) {
|
|
333
406
|
const { user, token } = normalize(userAddress, tokenAddress);
|
|
334
407
|
const rows = await this.dataSource.getRepository(LockedMintEntity).find({
|
|
@@ -453,43 +526,72 @@ var PostgresPointLedger = class {
|
|
|
453
526
|
throw new Error("deductBalance: amount must be positive");
|
|
454
527
|
}
|
|
455
528
|
const { user, token } = normalize(userAddress, tokenAddress);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
529
|
+
try {
|
|
530
|
+
await withDeadlockRetry(this.dataSource, async (tx) => {
|
|
531
|
+
const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
|
|
532
|
+
const already = await tx.getRepository(LedgerJournalEntity).findOne({
|
|
533
|
+
where: {
|
|
534
|
+
userAddress: user,
|
|
535
|
+
tokenAddress: token,
|
|
536
|
+
txHash,
|
|
537
|
+
reason: "MINT_CONFIRMED"
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
if (already) {
|
|
541
|
+
this.logger?.debug?.(
|
|
542
|
+
`deductBalance: idempotent skip tx=${txHash} user=${user} token=${token}`
|
|
543
|
+
);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (!balance || balance.balance < amount) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
await tx.getRepository(UserBalanceEntity).update(
|
|
552
|
+
{ userAddress: user, tokenAddress: token },
|
|
553
|
+
{ balance: balance.balance - amount }
|
|
461
554
|
);
|
|
462
|
-
|
|
463
|
-
await tx.getRepository(UserBalanceEntity).update(
|
|
464
|
-
{ userAddress: user, tokenAddress: token },
|
|
465
|
-
{ balance: balance.balance - amount }
|
|
466
|
-
);
|
|
467
|
-
await tx.getRepository(LedgerJournalEntity).insert({
|
|
468
|
-
userAddress: user,
|
|
469
|
-
tokenAddress: token,
|
|
470
|
-
delta: -amount,
|
|
471
|
-
reason: "MINT_CONFIRMED",
|
|
472
|
-
txHash
|
|
473
|
-
});
|
|
474
|
-
const match = await tx.getRepository(LockedMintEntity).findOne({
|
|
475
|
-
where: {
|
|
555
|
+
await tx.getRepository(LedgerJournalEntity).insert({
|
|
476
556
|
userAddress: user,
|
|
477
557
|
tokenAddress: token,
|
|
478
|
-
amount,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
558
|
+
delta: -amount,
|
|
559
|
+
reason: "MINT_CONFIRMED",
|
|
560
|
+
txHash
|
|
561
|
+
});
|
|
562
|
+
const match = await tx.getRepository(LockedMintEntity).findOne({
|
|
563
|
+
where: {
|
|
564
|
+
userAddress: user,
|
|
565
|
+
tokenAddress: token,
|
|
566
|
+
amount,
|
|
567
|
+
status: "PENDING"
|
|
568
|
+
},
|
|
569
|
+
order: { createdAt: "ASC" }
|
|
570
|
+
});
|
|
571
|
+
if (match) {
|
|
572
|
+
await tx.getRepository(LockedMintEntity).update({ id: match.id }, { status: "MINTED", txHash });
|
|
573
|
+
}
|
|
482
574
|
});
|
|
483
|
-
|
|
484
|
-
|
|
575
|
+
} catch (err) {
|
|
576
|
+
if (isJournalIdempotencyViolation(err)) {
|
|
577
|
+
this.logger?.debug?.(
|
|
578
|
+
`deductBalance: idempotent (concurrent race) tx=${txHash} user=${user}`
|
|
579
|
+
);
|
|
580
|
+
return;
|
|
485
581
|
}
|
|
486
|
-
|
|
582
|
+
throw err;
|
|
583
|
+
}
|
|
487
584
|
this.logger?.log?.(`deduct ${user}[${token}] -${amount} tx=${txHash}`);
|
|
488
585
|
}
|
|
489
586
|
async updateMintStatus(lockId, status, txHash) {
|
|
490
587
|
const update = { status };
|
|
491
588
|
if (txHash) update.txHash = txHash;
|
|
492
|
-
await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId },
|
|
589
|
+
const result = await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set(update).where("id = :id", { id: lockId }).andWhere("status = :pending", { pending: "PENDING" }).execute();
|
|
590
|
+
if ((result.affected ?? 0) === 0) {
|
|
591
|
+
this.logger?.debug?.(
|
|
592
|
+
`updateMintStatus: lock ${lockId} not in PENDING (terminal state), no-op`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
493
595
|
}
|
|
494
596
|
async bindMintUserOpHash(lockId, userOpHash) {
|
|
495
597
|
await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, { userOpHash });
|
|
@@ -521,7 +623,7 @@ var PostgresPointLedger = class {
|
|
|
521
623
|
return row.id;
|
|
522
624
|
}
|
|
523
625
|
async resolveCreditByBurnTx(lockId, txHash) {
|
|
524
|
-
|
|
626
|
+
const run = async () => withDeadlockRetry(this.dataSource, async (tx) => {
|
|
525
627
|
const credit = await tx.getRepository(PendingCreditEntity).createQueryBuilder("credit").setLock("pessimistic_write").where("credit.id = :id", { id: lockId }).getOne();
|
|
526
628
|
if (!credit) {
|
|
527
629
|
throw new Error(
|
|
@@ -574,13 +676,46 @@ var PostgresPointLedger = class {
|
|
|
574
676
|
{ status: "RESOLVED", txHash, resolvedAt: /* @__PURE__ */ new Date() }
|
|
575
677
|
);
|
|
576
678
|
});
|
|
679
|
+
try {
|
|
680
|
+
await run();
|
|
681
|
+
} catch (err) {
|
|
682
|
+
if (isJournalIdempotencyViolation(err)) {
|
|
683
|
+
this.logger?.debug?.(
|
|
684
|
+
`resolveCreditByBurnTx: concurrent race on tx=${txHash} lock=${lockId}, replaying via sibling defense`
|
|
685
|
+
);
|
|
686
|
+
await run();
|
|
687
|
+
} else {
|
|
688
|
+
throw err;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
577
691
|
this.logger?.log?.(`resolve pending credit ${lockId} tx=${txHash}`);
|
|
578
692
|
}
|
|
579
693
|
/**
|
|
580
694
|
* Used by `BurnIndexer.matchLockId` to resolve an on-chain burn
|
|
581
|
-
* event back to a pending credit row. Returns the
|
|
582
|
-
* `(user, token, amount, status: PENDING
|
|
583
|
-
* when no match exists (unsolicited burn —
|
|
695
|
+
* event back to a pending credit row. Returns the **newest**
|
|
696
|
+
* matching `(user, token, amount, status: PENDING, expires_at > now)`
|
|
697
|
+
* lockId, or undefined when no match exists (unsolicited burn —
|
|
698
|
+
* indexer skips).
|
|
699
|
+
*
|
|
700
|
+
* Audit PACI5-20 — two filters narrowed since the pre-fix shipped:
|
|
701
|
+
*
|
|
702
|
+
* 1. `expires_at > now()` — abandoned PENDING reservations past
|
|
703
|
+
* their deadline no longer hijack a fresh burn's attribution.
|
|
704
|
+
* This is defense-in-depth in case `markExpiredCredits` hasn't
|
|
705
|
+
* run recently (missed-tick window).
|
|
706
|
+
*
|
|
707
|
+
* 2. `ORDER BY createdAt DESC` (was ASC) — when multiple PENDING
|
|
708
|
+
* reservations match the same (user, token, amount) within
|
|
709
|
+
* the validity window, prefer the most recent one. Matches
|
|
710
|
+
* user mental model: "I just submitted /redeem, my recent
|
|
711
|
+
* reservation should win" rather than resurrecting a stale
|
|
712
|
+
* one. With the sweep + filter the typical steady state has
|
|
713
|
+
* at most one matching row, so this only affects edge cases
|
|
714
|
+
* (very fast re-submit, or concurrent flows).
|
|
715
|
+
*
|
|
716
|
+
* Combined with the partial UNIQUE index on `(txHash, status =
|
|
717
|
+
* RESOLVED)` from PACI5-7, the attribution can no longer drift
|
|
718
|
+
* across sessions even under adversarial conditions.
|
|
584
719
|
*/
|
|
585
720
|
async findPendingCreditLockId(userAddress, amount, tokenAddress) {
|
|
586
721
|
const { user, token } = normalize(userAddress, tokenAddress);
|
|
@@ -589,9 +724,10 @@ var PostgresPointLedger = class {
|
|
|
589
724
|
userAddress: user,
|
|
590
725
|
tokenAddress: token,
|
|
591
726
|
amount,
|
|
592
|
-
status: "PENDING"
|
|
727
|
+
status: "PENDING",
|
|
728
|
+
expiresAt: MoreThan(/* @__PURE__ */ new Date())
|
|
593
729
|
},
|
|
594
|
-
order: { createdAt: "
|
|
730
|
+
order: { createdAt: "DESC" }
|
|
595
731
|
});
|
|
596
732
|
return row?.id;
|
|
597
733
|
}
|
|
@@ -930,10 +1066,103 @@ var CreateRedemptionHistory1746230400001 = class {
|
|
|
930
1066
|
}
|
|
931
1067
|
};
|
|
932
1068
|
|
|
1069
|
+
// src/migrations/1747500000000-AddJournalIdempotencyIndex.ts
|
|
1070
|
+
var AddJournalIdempotencyIndex1747500000000 = class {
|
|
1071
|
+
name = "AddJournalIdempotencyIndex1747500000000";
|
|
1072
|
+
async up(queryRunner) {
|
|
1073
|
+
await queryRunner.query(`
|
|
1074
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
|
|
1075
|
+
ON "ledger_journal" ("user_address", "tx_hash", "reason")
|
|
1076
|
+
WHERE "tx_hash" IS NOT NULL
|
|
1077
|
+
`);
|
|
1078
|
+
}
|
|
1079
|
+
async down(queryRunner) {
|
|
1080
|
+
await queryRunner.query(
|
|
1081
|
+
`DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// src/migrations/1747600000000-AddLockedMintCompositeIndexes.ts
|
|
1087
|
+
var AddLockedMintCompositeIndexes1747600000000 = class {
|
|
1088
|
+
name = "AddLockedMintCompositeIndexes1747600000000";
|
|
1089
|
+
/**
|
|
1090
|
+
* CONCURRENTLY index DDL cannot run inside a transaction. Tell
|
|
1091
|
+
* TypeORM to issue these statements directly.
|
|
1092
|
+
*/
|
|
1093
|
+
transaction = false;
|
|
1094
|
+
async up(queryRunner) {
|
|
1095
|
+
await queryRunner.query(`
|
|
1096
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
|
1097
|
+
"IDX_locked_mint_user_token_status_expires"
|
|
1098
|
+
ON "locked_mint_requests"
|
|
1099
|
+
("user_address", "token_address", "status", "expires_at")
|
|
1100
|
+
`);
|
|
1101
|
+
await queryRunner.query(`
|
|
1102
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
|
1103
|
+
"IDX_locked_mint_user_token_amount_status"
|
|
1104
|
+
ON "locked_mint_requests"
|
|
1105
|
+
("user_address", "token_address", "amount", "status")
|
|
1106
|
+
`);
|
|
1107
|
+
await queryRunner.query(`
|
|
1108
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS
|
|
1109
|
+
"IDX_locked_mint_pending_expires"
|
|
1110
|
+
ON "locked_mint_requests" ("expires_at")
|
|
1111
|
+
WHERE "status" = 'PENDING'
|
|
1112
|
+
`);
|
|
1113
|
+
await queryRunner.query(
|
|
1114
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_status"`
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
async down(queryRunner) {
|
|
1118
|
+
await queryRunner.query(`
|
|
1119
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS "IDX_locked_mint_user_status"
|
|
1120
|
+
ON "locked_mint_requests" ("user_address", "token_address", "status")
|
|
1121
|
+
`);
|
|
1122
|
+
await queryRunner.query(
|
|
1123
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_pending_expires"`
|
|
1124
|
+
);
|
|
1125
|
+
await queryRunner.query(
|
|
1126
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_amount_status"`
|
|
1127
|
+
);
|
|
1128
|
+
await queryRunner.query(
|
|
1129
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_status_expires"`
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
// src/migrations/1747700000000-FixIdempotencyAddTokenAddress.ts
|
|
1135
|
+
var FixIdempotencyAddTokenAddress1747700000000 = class {
|
|
1136
|
+
name = "FixIdempotencyAddTokenAddress1747700000000";
|
|
1137
|
+
async up(queryRunner) {
|
|
1138
|
+
await queryRunner.query(
|
|
1139
|
+
`DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
|
|
1140
|
+
);
|
|
1141
|
+
await queryRunner.query(`
|
|
1142
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_token_tx_reason"
|
|
1143
|
+
ON "ledger_journal" ("user_address", "token_address", "tx_hash", "reason")
|
|
1144
|
+
WHERE "tx_hash" IS NOT NULL
|
|
1145
|
+
`);
|
|
1146
|
+
}
|
|
1147
|
+
async down(queryRunner) {
|
|
1148
|
+
await queryRunner.query(
|
|
1149
|
+
`DROP INDEX IF EXISTS "UQ_ledger_journal_user_token_tx_reason"`
|
|
1150
|
+
);
|
|
1151
|
+
await queryRunner.query(`
|
|
1152
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
|
|
1153
|
+
ON "ledger_journal" ("user_address", "tx_hash", "reason")
|
|
1154
|
+
WHERE "tx_hash" IS NOT NULL
|
|
1155
|
+
`);
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
|
|
933
1159
|
// src/migrations/index.ts
|
|
934
1160
|
var PAFI_MIGRATIONS = [
|
|
935
1161
|
InitialSchema1700000000000,
|
|
936
|
-
CreateRedemptionHistory1746230400001
|
|
1162
|
+
CreateRedemptionHistory1746230400001,
|
|
1163
|
+
AddJournalIdempotencyIndex1747500000000,
|
|
1164
|
+
AddLockedMintCompositeIndexes1747600000000,
|
|
1165
|
+
FixIdempotencyAddTokenAddress1747700000000
|
|
937
1166
|
];
|
|
938
1167
|
export {
|
|
939
1168
|
CreateRedemptionHistory1746230400001,
|