@pafi-dev/issuer 0.22.0 → 0.23.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 +28 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -38
- package/dist/index.d.ts +33 -38
- package/dist/index.js +28 -5
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.d.cts
CHANGED
|
@@ -967,12 +967,37 @@ interface BurnEvent {
|
|
|
967
967
|
* number it is about to process so the caller can write it to Redis /
|
|
968
968
|
* Postgres / a file. The SDK does not own persistence because every
|
|
969
969
|
* issuer has their own storage stack.
|
|
970
|
+
*
|
|
971
|
+
* **Per-token keying (audit finding H-05).** When multiple PointTokens
|
|
972
|
+
* are wired through a single `createIssuerService` call, each
|
|
973
|
+
* `PointIndexer` MUST get its own cursor — otherwise token A advances
|
|
974
|
+
* the shared cursor past token B's events and token B's mints are
|
|
975
|
+
* never finalized (off-chain balance never deducts → on-chain PT
|
|
976
|
+
* supply for token B exceeds off-chain backing). Combined with the
|
|
977
|
+
* H-04 monotonic-save fix this becomes a catastrophic-and-stable
|
|
978
|
+
* state where token B's cursor can never catch up. Implementations
|
|
979
|
+
* SHOULD expose `forKey(key)` returning a derived store keyed under
|
|
980
|
+
* a distinct namespace; the SDK factory calls it once per token.
|
|
981
|
+
*
|
|
982
|
+
* When `forKey` is absent, the SDK falls back to the bare store and
|
|
983
|
+
* emits a runtime warning if more than one token is configured. New
|
|
984
|
+
* implementations are strongly encouraged to add `forKey`.
|
|
970
985
|
*/
|
|
971
986
|
interface IIndexerCursorStore {
|
|
972
987
|
/** Return the last persisted cursor (`undefined` on first run). */
|
|
973
988
|
load(): Promise<bigint | undefined>;
|
|
974
989
|
/** Persist a new cursor value. Called after each successful batch. */
|
|
975
990
|
save(blockNumber: bigint): Promise<void>;
|
|
991
|
+
/**
|
|
992
|
+
* Return a derived store keyed under `key`. The returned store is
|
|
993
|
+
* an independent `IIndexerCursorStore` so the same persistence
|
|
994
|
+
* backend can serve N indexers with N distinct cursors.
|
|
995
|
+
*
|
|
996
|
+
* Optional for backwards compatibility, but REQUIRED in practice
|
|
997
|
+
* whenever more than one PointToken is wired through the SDK
|
|
998
|
+
* factory (see H-05).
|
|
999
|
+
*/
|
|
1000
|
+
forKey?(key: string): IIndexerCursorStore;
|
|
976
1001
|
}
|
|
977
1002
|
/**
|
|
978
1003
|
* No-op cursor store. Useful when the caller wants to drive the cursor
|
|
@@ -980,49 +1005,19 @@ interface IIndexerCursorStore {
|
|
|
980
1005
|
*/
|
|
981
1006
|
declare class InMemoryCursorStore implements IIndexerCursorStore {
|
|
982
1007
|
private cursor;
|
|
1008
|
+
/**
|
|
1009
|
+
* Child stores keyed by `forKey()`. Each child has its own cursor
|
|
1010
|
+
* (the H-05 fix), so a single InMemoryCursorStore can back N
|
|
1011
|
+
* PointIndexers in tests / single-process callers.
|
|
1012
|
+
*/
|
|
1013
|
+
private readonly children;
|
|
983
1014
|
load(): Promise<bigint | undefined>;
|
|
984
1015
|
save(blockNumber: bigint): Promise<void>;
|
|
1016
|
+
forKey(key: string): IIndexerCursorStore;
|
|
985
1017
|
}
|
|
986
|
-
/**
|
|
987
|
-
* Lock handle returned by a successful `ISingletonLock.acquire()`.
|
|
988
|
-
*
|
|
989
|
-
* The holder is the leader for the keyed indexer; non-holders MUST NOT
|
|
990
|
-
* call `indexer.start()` (else they race against the leader's polling
|
|
991
|
-
* loop and last-writer-wins cursor save corrupts replay state).
|
|
992
|
-
*
|
|
993
|
-
* `release()` is called by the leader on graceful shutdown. If the
|
|
994
|
-
* leader crashes without calling release, the lock implementation
|
|
995
|
-
* MUST drop the lock automatically (e.g. Postgres advisory locks
|
|
996
|
-
* auto-release on connection close).
|
|
997
|
-
*/
|
|
998
1018
|
interface SingletonLockHandle {
|
|
999
1019
|
release(): Promise<void>;
|
|
1000
1020
|
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Leader-election primitive for indexer singletons.
|
|
1003
|
-
*
|
|
1004
|
-
* **Why this exists (audit finding H-04):** the `BurnIndexer` and
|
|
1005
|
-
* `PointIndexer` classes are stateful polling loops with a cursor
|
|
1006
|
-
* stored in an external store. Running them on multiple replicas
|
|
1007
|
-
* simultaneously causes:
|
|
1008
|
-
*
|
|
1009
|
-
* 1. Double-credit — both replicas see the same Transfer event
|
|
1010
|
-
* and call `ledger.resolveCreditByBurnTx` twice.
|
|
1011
|
-
* 2. Cursor rewind — last-writer-wins `save()` causes the newer
|
|
1012
|
-
* cursor to be overwritten by a lagging replica's older value,
|
|
1013
|
-
* causing the next poll to replay events.
|
|
1014
|
-
* 3. Skipped blocks — chunked range reads can leave gaps when two
|
|
1015
|
-
* replicas race-advance the cursor.
|
|
1016
|
-
*
|
|
1017
|
-
* **Adoption:** pass `singletonLock` into `IssuerServiceConfig.indexer`
|
|
1018
|
-
* (or wire it directly in your provider). The factory will only call
|
|
1019
|
-
* `indexer.start()` on the indexers it successfully acquires a lock
|
|
1020
|
-
* for; the rest stay idle and take over instantly on lock release.
|
|
1021
|
-
*
|
|
1022
|
-
* **Implementations:** `makePostgresSingletonLock(dataSource)` (recommended
|
|
1023
|
-
* — uses `pg_try_advisory_lock`, auto-releases on connection close).
|
|
1024
|
-
* You can also bring your own: Redis SETNX with TTL, etcd lease, etc.
|
|
1025
|
-
*/
|
|
1026
1021
|
interface ISingletonLock {
|
|
1027
1022
|
/**
|
|
1028
1023
|
* Attempt to acquire the lock for `key`. Returns a handle on success
|
|
@@ -1242,7 +1237,7 @@ declare class BurnIndexer {
|
|
|
1242
1237
|
private readonly provider;
|
|
1243
1238
|
/**
|
|
1244
1239
|
* The PointToken this indexer watches. Exposed so callers can key
|
|
1245
|
-
* leader-election locks / cursor stores by token
|
|
1240
|
+
* leader-election locks / cursor stores by token
|
|
1246
1241
|
*/
|
|
1247
1242
|
readonly pointTokenAddress: Address;
|
|
1248
1243
|
private readonly ledger;
|
package/dist/index.d.ts
CHANGED
|
@@ -967,12 +967,37 @@ interface BurnEvent {
|
|
|
967
967
|
* number it is about to process so the caller can write it to Redis /
|
|
968
968
|
* Postgres / a file. The SDK does not own persistence because every
|
|
969
969
|
* issuer has their own storage stack.
|
|
970
|
+
*
|
|
971
|
+
* **Per-token keying (audit finding H-05).** When multiple PointTokens
|
|
972
|
+
* are wired through a single `createIssuerService` call, each
|
|
973
|
+
* `PointIndexer` MUST get its own cursor — otherwise token A advances
|
|
974
|
+
* the shared cursor past token B's events and token B's mints are
|
|
975
|
+
* never finalized (off-chain balance never deducts → on-chain PT
|
|
976
|
+
* supply for token B exceeds off-chain backing). Combined with the
|
|
977
|
+
* H-04 monotonic-save fix this becomes a catastrophic-and-stable
|
|
978
|
+
* state where token B's cursor can never catch up. Implementations
|
|
979
|
+
* SHOULD expose `forKey(key)` returning a derived store keyed under
|
|
980
|
+
* a distinct namespace; the SDK factory calls it once per token.
|
|
981
|
+
*
|
|
982
|
+
* When `forKey` is absent, the SDK falls back to the bare store and
|
|
983
|
+
* emits a runtime warning if more than one token is configured. New
|
|
984
|
+
* implementations are strongly encouraged to add `forKey`.
|
|
970
985
|
*/
|
|
971
986
|
interface IIndexerCursorStore {
|
|
972
987
|
/** Return the last persisted cursor (`undefined` on first run). */
|
|
973
988
|
load(): Promise<bigint | undefined>;
|
|
974
989
|
/** Persist a new cursor value. Called after each successful batch. */
|
|
975
990
|
save(blockNumber: bigint): Promise<void>;
|
|
991
|
+
/**
|
|
992
|
+
* Return a derived store keyed under `key`. The returned store is
|
|
993
|
+
* an independent `IIndexerCursorStore` so the same persistence
|
|
994
|
+
* backend can serve N indexers with N distinct cursors.
|
|
995
|
+
*
|
|
996
|
+
* Optional for backwards compatibility, but REQUIRED in practice
|
|
997
|
+
* whenever more than one PointToken is wired through the SDK
|
|
998
|
+
* factory (see H-05).
|
|
999
|
+
*/
|
|
1000
|
+
forKey?(key: string): IIndexerCursorStore;
|
|
976
1001
|
}
|
|
977
1002
|
/**
|
|
978
1003
|
* No-op cursor store. Useful when the caller wants to drive the cursor
|
|
@@ -980,49 +1005,19 @@ interface IIndexerCursorStore {
|
|
|
980
1005
|
*/
|
|
981
1006
|
declare class InMemoryCursorStore implements IIndexerCursorStore {
|
|
982
1007
|
private cursor;
|
|
1008
|
+
/**
|
|
1009
|
+
* Child stores keyed by `forKey()`. Each child has its own cursor
|
|
1010
|
+
* (the H-05 fix), so a single InMemoryCursorStore can back N
|
|
1011
|
+
* PointIndexers in tests / single-process callers.
|
|
1012
|
+
*/
|
|
1013
|
+
private readonly children;
|
|
983
1014
|
load(): Promise<bigint | undefined>;
|
|
984
1015
|
save(blockNumber: bigint): Promise<void>;
|
|
1016
|
+
forKey(key: string): IIndexerCursorStore;
|
|
985
1017
|
}
|
|
986
|
-
/**
|
|
987
|
-
* Lock handle returned by a successful `ISingletonLock.acquire()`.
|
|
988
|
-
*
|
|
989
|
-
* The holder is the leader for the keyed indexer; non-holders MUST NOT
|
|
990
|
-
* call `indexer.start()` (else they race against the leader's polling
|
|
991
|
-
* loop and last-writer-wins cursor save corrupts replay state).
|
|
992
|
-
*
|
|
993
|
-
* `release()` is called by the leader on graceful shutdown. If the
|
|
994
|
-
* leader crashes without calling release, the lock implementation
|
|
995
|
-
* MUST drop the lock automatically (e.g. Postgres advisory locks
|
|
996
|
-
* auto-release on connection close).
|
|
997
|
-
*/
|
|
998
1018
|
interface SingletonLockHandle {
|
|
999
1019
|
release(): Promise<void>;
|
|
1000
1020
|
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Leader-election primitive for indexer singletons.
|
|
1003
|
-
*
|
|
1004
|
-
* **Why this exists (audit finding H-04):** the `BurnIndexer` and
|
|
1005
|
-
* `PointIndexer` classes are stateful polling loops with a cursor
|
|
1006
|
-
* stored in an external store. Running them on multiple replicas
|
|
1007
|
-
* simultaneously causes:
|
|
1008
|
-
*
|
|
1009
|
-
* 1. Double-credit — both replicas see the same Transfer event
|
|
1010
|
-
* and call `ledger.resolveCreditByBurnTx` twice.
|
|
1011
|
-
* 2. Cursor rewind — last-writer-wins `save()` causes the newer
|
|
1012
|
-
* cursor to be overwritten by a lagging replica's older value,
|
|
1013
|
-
* causing the next poll to replay events.
|
|
1014
|
-
* 3. Skipped blocks — chunked range reads can leave gaps when two
|
|
1015
|
-
* replicas race-advance the cursor.
|
|
1016
|
-
*
|
|
1017
|
-
* **Adoption:** pass `singletonLock` into `IssuerServiceConfig.indexer`
|
|
1018
|
-
* (or wire it directly in your provider). The factory will only call
|
|
1019
|
-
* `indexer.start()` on the indexers it successfully acquires a lock
|
|
1020
|
-
* for; the rest stay idle and take over instantly on lock release.
|
|
1021
|
-
*
|
|
1022
|
-
* **Implementations:** `makePostgresSingletonLock(dataSource)` (recommended
|
|
1023
|
-
* — uses `pg_try_advisory_lock`, auto-releases on connection close).
|
|
1024
|
-
* You can also bring your own: Redis SETNX with TTL, etcd lease, etc.
|
|
1025
|
-
*/
|
|
1026
1021
|
interface ISingletonLock {
|
|
1027
1022
|
/**
|
|
1028
1023
|
* Attempt to acquire the lock for `key`. Returns a handle on success
|
|
@@ -1242,7 +1237,7 @@ declare class BurnIndexer {
|
|
|
1242
1237
|
private readonly provider;
|
|
1243
1238
|
/**
|
|
1244
1239
|
* The PointToken this indexer watches. Exposed so callers can key
|
|
1245
|
-
* leader-election locks / cursor stores by token
|
|
1240
|
+
* leader-election locks / cursor stores by token
|
|
1246
1241
|
*/
|
|
1247
1242
|
readonly pointTokenAddress: Address;
|
|
1248
1243
|
private readonly ledger;
|
package/dist/index.js
CHANGED
|
@@ -1122,14 +1122,28 @@ function createPafiEstimatorClient(config) {
|
|
|
1122
1122
|
}
|
|
1123
1123
|
|
|
1124
1124
|
// src/indexer/types.ts
|
|
1125
|
-
var InMemoryCursorStore = class {
|
|
1125
|
+
var InMemoryCursorStore = class _InMemoryCursorStore {
|
|
1126
1126
|
cursor;
|
|
1127
|
+
/**
|
|
1128
|
+
* Child stores keyed by `forKey()`. Each child has its own cursor
|
|
1129
|
+
* (the H-05 fix), so a single InMemoryCursorStore can back N
|
|
1130
|
+
* PointIndexers in tests / single-process callers.
|
|
1131
|
+
*/
|
|
1132
|
+
children = /* @__PURE__ */ new Map();
|
|
1127
1133
|
async load() {
|
|
1128
1134
|
return this.cursor;
|
|
1129
1135
|
}
|
|
1130
1136
|
async save(blockNumber) {
|
|
1131
1137
|
this.cursor = blockNumber;
|
|
1132
1138
|
}
|
|
1139
|
+
forKey(key) {
|
|
1140
|
+
let child = this.children.get(key);
|
|
1141
|
+
if (!child) {
|
|
1142
|
+
child = new _InMemoryCursorStore();
|
|
1143
|
+
this.children.set(key, child);
|
|
1144
|
+
}
|
|
1145
|
+
return child;
|
|
1146
|
+
}
|
|
1133
1147
|
};
|
|
1134
1148
|
|
|
1135
1149
|
// src/indexer/pointIndexer.ts
|
|
@@ -1393,7 +1407,7 @@ var BurnIndexer = class {
|
|
|
1393
1407
|
provider;
|
|
1394
1408
|
/**
|
|
1395
1409
|
* The PointToken this indexer watches. Exposed so callers can key
|
|
1396
|
-
* leader-election locks / cursor stores by token
|
|
1410
|
+
* leader-election locks / cursor stores by token
|
|
1397
1411
|
*/
|
|
1398
1412
|
pointTokenAddress;
|
|
1399
1413
|
ledger;
|
|
@@ -4567,6 +4581,13 @@ async function createIssuerService(config) {
|
|
|
4567
4581
|
const sdkWrapperAddress = getContractAddresses7(config.chainId).mintFeeWrapper;
|
|
4568
4582
|
const wrapperOverride = config.indexer?.mintFeeWrapperAddress;
|
|
4569
4583
|
const resolvedWrapperAddress = wrapperOverride !== void 0 ? wrapperOverride : sdkWrapperAddress;
|
|
4584
|
+
const baseCursorStore = config.indexer?.cursorStore;
|
|
4585
|
+
const sharedCursorWithMultipleTokens = baseCursorStore !== void 0 && typeof baseCursorStore.forKey !== "function" && tokenAddresses.length > 1;
|
|
4586
|
+
if (sharedCursorWithMultipleTokens) {
|
|
4587
|
+
console.warn(
|
|
4588
|
+
`[@pafi-dev/issuer] cursorStore lacks forKey() and ${tokenAddresses.length} PointTokens are configured. All PointIndexers will share one cursor row, causing token-skipping (audit finding H-05). Implement IIndexerCursorStore.forKey to return per-token derived stores. This permissive path will be removed in a future major release.`
|
|
4589
|
+
);
|
|
4590
|
+
}
|
|
4570
4591
|
const indexers = /* @__PURE__ */ new Map();
|
|
4571
4592
|
for (const tokenAddress of tokenAddresses) {
|
|
4572
4593
|
const indexerConfig = {
|
|
@@ -4580,8 +4601,10 @@ async function createIssuerService(config) {
|
|
|
4580
4601
|
if (config.indexer?.fromBlock !== void 0) {
|
|
4581
4602
|
indexerConfig.fromBlock = config.indexer.fromBlock;
|
|
4582
4603
|
}
|
|
4583
|
-
if (
|
|
4584
|
-
indexerConfig.cursorStore =
|
|
4604
|
+
if (baseCursorStore) {
|
|
4605
|
+
indexerConfig.cursorStore = typeof baseCursorStore.forKey === "function" ? baseCursorStore.forKey(
|
|
4606
|
+
`point-indexer:${tokenAddress.toLowerCase()}`
|
|
4607
|
+
) : baseCursorStore;
|
|
4585
4608
|
}
|
|
4586
4609
|
if (config.indexer?.confirmations !== void 0) {
|
|
4587
4610
|
indexerConfig.confirmations = config.indexer.confirmations;
|
|
@@ -4882,7 +4905,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
4882
4905
|
};
|
|
4883
4906
|
|
|
4884
4907
|
// src/index.ts
|
|
4885
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.
|
|
4908
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.23.0" : "dev";
|
|
4886
4909
|
export {
|
|
4887
4910
|
AdapterMisconfiguredError,
|
|
4888
4911
|
AuthError,
|