@pafi-dev/issuer 0.21.0 → 0.22.1
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 +36 -0
- package/dist/index.cjs +77 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -3
- package/dist/index.d.ts +137 -3
- package/dist/index.js +76 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -983,6 +983,56 @@ declare class InMemoryCursorStore implements IIndexerCursorStore {
|
|
|
983
983
|
load(): Promise<bigint | undefined>;
|
|
984
984
|
save(blockNumber: bigint): Promise<void>;
|
|
985
985
|
}
|
|
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
|
+
interface SingletonLockHandle {
|
|
999
|
+
release(): Promise<void>;
|
|
1000
|
+
}
|
|
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
|
+
interface ISingletonLock {
|
|
1027
|
+
/**
|
|
1028
|
+
* Attempt to acquire the lock for `key`. Returns a handle on success
|
|
1029
|
+
* (caller is the leader), or `null` if another holder owns it.
|
|
1030
|
+
*
|
|
1031
|
+
* Implementations MUST be non-blocking — if the lock is held, return
|
|
1032
|
+
* null immediately. The factory polls / retries at a higher layer.
|
|
1033
|
+
*/
|
|
1034
|
+
acquire(key: string): Promise<SingletonLockHandle | null>;
|
|
1035
|
+
}
|
|
986
1036
|
|
|
987
1037
|
interface PointIndexerConfig {
|
|
988
1038
|
provider: PublicClient;
|
|
@@ -1190,7 +1240,11 @@ interface BurnIndexerConfig {
|
|
|
1190
1240
|
*/
|
|
1191
1241
|
declare class BurnIndexer {
|
|
1192
1242
|
private readonly provider;
|
|
1193
|
-
|
|
1243
|
+
/**
|
|
1244
|
+
* The PointToken this indexer watches. Exposed so callers can key
|
|
1245
|
+
* leader-election locks / cursor stores by token (audit H-04 fix).
|
|
1246
|
+
*/
|
|
1247
|
+
readonly pointTokenAddress: Address;
|
|
1194
1248
|
private readonly ledger;
|
|
1195
1249
|
private readonly cursorStore;
|
|
1196
1250
|
private readonly startBlock;
|
|
@@ -1222,6 +1276,60 @@ declare class BurnIndexer {
|
|
|
1222
1276
|
private finalize;
|
|
1223
1277
|
}
|
|
1224
1278
|
|
|
1279
|
+
/**
|
|
1280
|
+
* Minimal duck-typed adapter so we don't drag a TypeORM / pg dep into
|
|
1281
|
+
* the SDK. The caller passes anything that can execute a parameterised
|
|
1282
|
+
* query and yield `{ got: boolean }` rows — TypeORM `DataSource.query`,
|
|
1283
|
+
* a raw `pg` client, knex's `raw`, kysely, etc.
|
|
1284
|
+
*
|
|
1285
|
+
* The helper requires `int8` (signed 64-bit) lock IDs. We derive them
|
|
1286
|
+
* from the lock `key` via a deterministic hash so different replicas
|
|
1287
|
+
* agree on the int regardless of casing / whitespace drift.
|
|
1288
|
+
*/
|
|
1289
|
+
interface PostgresQueryRunner {
|
|
1290
|
+
query(sql: string, params?: unknown[]): Promise<unknown>;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Build an `ISingletonLock` backed by PostgreSQL session-level advisory
|
|
1294
|
+
* locks (`pg_try_advisory_lock`).
|
|
1295
|
+
*
|
|
1296
|
+
* Why advisory locks (vs. row-level locks or a leases table):
|
|
1297
|
+
* 1. **Non-blocking** — `pg_try_advisory_lock` returns immediately;
|
|
1298
|
+
* perfect for the "try then idle" pattern the SDK uses on boot.
|
|
1299
|
+
* 2. **Auto-release on connection drop** — if the leader pod crashes,
|
|
1300
|
+
* its PG connection closes, and the lock is freed automatically.
|
|
1301
|
+
* No timeout tuning, no lease renewal loops, no zombie locks.
|
|
1302
|
+
* 3. **Zero schema** — no extra table, no migration, no contention
|
|
1303
|
+
* with application reads.
|
|
1304
|
+
*
|
|
1305
|
+
* **Caveat — long-lived connection required.** Session-level locks are
|
|
1306
|
+
* held only for the lifetime of the PG connection that called
|
|
1307
|
+
* `pg_try_advisory_lock`. PgBouncer in `transaction` pooling mode will
|
|
1308
|
+
* release the lock between statements; you MUST run in `session`
|
|
1309
|
+
* pooling or skip the bouncer for the indexer process. The advisory
|
|
1310
|
+
* `transaction` variant exists but doesn't fit the "hold while polling"
|
|
1311
|
+
* pattern — for that you'd need a periodic re-acquire, which we don't
|
|
1312
|
+
* implement here.
|
|
1313
|
+
*
|
|
1314
|
+
* @example
|
|
1315
|
+
* import { DataSource } from "typeorm";
|
|
1316
|
+
* import { makePostgresSingletonLock } from "@pafi-dev/issuer";
|
|
1317
|
+
*
|
|
1318
|
+
* const dataSource = new DataSource({ ... });
|
|
1319
|
+
* const lock = makePostgresSingletonLock({
|
|
1320
|
+
* query: (sql, params) => dataSource.query(sql, params),
|
|
1321
|
+
* });
|
|
1322
|
+
*
|
|
1323
|
+
* createIssuerService({
|
|
1324
|
+
* ...,
|
|
1325
|
+
* indexer: {
|
|
1326
|
+
* autoStart: true,
|
|
1327
|
+
* singletonLock: lock,
|
|
1328
|
+
* },
|
|
1329
|
+
* });
|
|
1330
|
+
*/
|
|
1331
|
+
declare function makePostgresSingletonLock(runner: PostgresQueryRunner): ISingletonLock;
|
|
1332
|
+
|
|
1225
1333
|
interface ApiConfigResponse {
|
|
1226
1334
|
chainId: number;
|
|
1227
1335
|
contracts: {
|
|
@@ -2890,8 +2998,27 @@ interface IssuerServiceConfig {
|
|
|
2890
2998
|
/**
|
|
2891
2999
|
* If `true`, the factory calls `indexer.start()` before returning.
|
|
2892
3000
|
* Default: `false` — the caller decides when to begin polling.
|
|
3001
|
+
*
|
|
3002
|
+
* **SAFETY (H-04):** in a multi-replica deployment, ALWAYS pair
|
|
3003
|
+
* `autoStart: true` with `singletonLock` below. Without leader
|
|
3004
|
+
* election, every replica starts its own indexer fleet and races
|
|
3005
|
+
* against the others — see `ISingletonLock` docs for the
|
|
3006
|
+
* consequences.
|
|
2893
3007
|
*/
|
|
2894
3008
|
autoStart?: boolean;
|
|
3009
|
+
/**
|
|
3010
|
+
* Leader-election primitive. When provided, the factory wraps
|
|
3011
|
+
* `indexer.start()` with a `singletonLock.acquire(key)` call and
|
|
3012
|
+
* only starts the indexer if it wins the lock. Non-leaders stay
|
|
3013
|
+
* idle and take over on the next acquire attempt (when the leader
|
|
3014
|
+
* pod's connection drops, the lock auto-releases).
|
|
3015
|
+
*
|
|
3016
|
+
* The lock key includes the indexer kind + the PointToken address,
|
|
3017
|
+
* so different tokens can be sharded across replicas if desired.
|
|
3018
|
+
*
|
|
3019
|
+
* Recommended: `makePostgresSingletonLock(dataSource)`.
|
|
3020
|
+
*/
|
|
3021
|
+
singletonLock?: ISingletonLock;
|
|
2895
3022
|
/**
|
|
2896
3023
|
* Override the MintFeeWrapper address used by the indexer. When
|
|
2897
3024
|
* omitted, the factory auto-resolves from
|
|
@@ -2937,6 +3064,13 @@ interface IssuerService {
|
|
|
2937
3064
|
fee: FeeManager | undefined;
|
|
2938
3065
|
/** All indexers keyed by PointToken address. */
|
|
2939
3066
|
indexers: Map<Address, PointIndexer>;
|
|
3067
|
+
/**
|
|
3068
|
+
* Lock handles for the indexers this replica was elected leader for.
|
|
3069
|
+
* Empty when `autoStart` is false, or when no `singletonLock` was
|
|
3070
|
+
* provided. Call `release()` on each during graceful shutdown so
|
|
3071
|
+
* peers can take over without waiting for the connection to die.
|
|
3072
|
+
*/
|
|
3073
|
+
indexerLeaderLocks: SingletonLockHandle[];
|
|
2940
3074
|
/** Framework-agnostic HTTP handlers — wire into Express / Fastify / Hono. */
|
|
2941
3075
|
api: IssuerApiHandlers;
|
|
2942
3076
|
/**
|
|
@@ -2958,7 +3092,7 @@ interface IssuerService {
|
|
|
2958
3092
|
*
|
|
2959
3093
|
* Throws synchronously if any required field is missing.
|
|
2960
3094
|
*/
|
|
2961
|
-
declare function createIssuerService(config: IssuerServiceConfig): IssuerService
|
|
3095
|
+
declare function createIssuerService(config: IssuerServiceConfig): Promise<IssuerService>;
|
|
2962
3096
|
|
|
2963
3097
|
/**
|
|
2964
3098
|
* Adapter that absorbs every "framework-agnostic" endpoint body into a
|
|
@@ -3698,4 +3832,4 @@ declare class MemoryRedemptionHistoryStore implements IRedemptionHistoryStore {
|
|
|
3698
3832
|
|
|
3699
3833
|
declare const PAFI_ISSUER_SDK_VERSION: string;
|
|
3700
3834
|
|
|
3701
|
-
export { AdapterMisconfiguredError, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedemptionEvaluateRequest, type ApiRedemptionEvaluateResponse, type ApiRedemptionPreviewRequest, type ApiRedemptionPreviewResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, type BundlerEstimatorClient, BundlerNotConfiguredError, BundlerRejectedError, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type ClaimDto, type ConfigDto, ConfigurationError, DEFAULT_REDEMPTION_POLICY, type DecodedCallDto, DefaultPolicyEngine, type DefaultPolicyEngineOptions, type DelegatePrepareDto, type DelegateStatusDto, type EstimateGasFeeOptions, type EvaluateInput, FeeManager, type FeeManagerConfig, type FeeManagerMetrics, type FetchFailureReason, type FetchImpl, type FetchResult, type GasFeeDto, type GasFeeSource, type HandleDelegateSubmitParams, type HandleDelegateSubmitResult, type HandleMobilePrepareParams, type HandleMobilePrepareResult, type HandleMobileSubmitParams, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type IRateLimiter, type IRedemptionHistoryStore, type ISessionStore, InMemoryCursorStore, IssuerApiAdapter, type IssuerApiAdapterConfig, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemoryPendingUserOpStore, MemoryRateLimiter, MemoryRedemptionHistoryStore, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type MobilePrepareDto, type MobileSubmitDto, type NativePtQuoterConfig, NonceManager, NoopRateLimiter, PAFI_ISSUER_SDK_VERSION, PTClaimError, PTClaimHandler, type PTClaimHandlerConfig, type PTClaimRequest, type PTClaimResponse, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PafiEstimatorClientConfig, PafiEstimatorHttpError, type PaymasterGasEstimates, type PendingCredit, type PendingUserOpEntry, PendingUserOpForbiddenError, PendingUserOpNotFoundError, type PerpDepositDto, PerpDepositError, PerpDepositHandler, type PerpDepositHandlerConfig, type PerpDepositRequest, type PerpDepositResponse, PointIndexer, type PointIndexerConfig, PointTokenDomainResolver, type PointTokenDomainResolverConfig, type PolicyDecision, type PolicyEvalRequest, PolicyProvider, type PolicyProviderConfig, type PoolsDto, type PoolsProvider, type PreValidateMintResult, type PrepareBurnParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, type PreviewBurnParams, type PreviewMintParams, REDEMPTION_HISTORY_WINDOW_SEC, type RateLimitAction, type RateLimiterConfig, type RedeemDto, type RedeemPrepareDto, RedemptionService, type RedemptionServiceConfig, RelayError, type RelayErrorCode, RelayService, type RelayUserOpParams, type RelayUserOpRequest, type RelayUserOpResponse, type RequestPaymasterParams, type ResolvedPolicy, type RetryConfig, type SdkErrorBody, type SdkErrorMapperFactories, type SdkErrorStatus, type SerializedUserOpTypedData, type Session, SettlementClient, type SettlementClientConfig, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type UserDto, type UserHistory, applyPaymasterGasEstimates, authenticateRequest, buildSdkErrorBody, createIssuerService, createNativePtQuoter, createPafiEstimatorClient, createSdkErrorMapper, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, defaultPolicyFor, evaluateRedemption, handleClaimStatus, handleDelegateSubmit, handleMobilePrepare, handleMobileSubmit, handleRedeemStatus, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };
|
|
3835
|
+
export { AdapterMisconfiguredError, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedemptionEvaluateRequest, type ApiRedemptionEvaluateResponse, type ApiRedemptionPreviewRequest, type ApiRedemptionPreviewResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, type BundlerEstimatorClient, BundlerNotConfiguredError, BundlerRejectedError, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type ClaimDto, type ConfigDto, ConfigurationError, DEFAULT_REDEMPTION_POLICY, type DecodedCallDto, DefaultPolicyEngine, type DefaultPolicyEngineOptions, type DelegatePrepareDto, type DelegateStatusDto, type EstimateGasFeeOptions, type EvaluateInput, FeeManager, type FeeManagerConfig, type FeeManagerMetrics, type FetchFailureReason, type FetchImpl, type FetchResult, type GasFeeDto, type GasFeeSource, type HandleDelegateSubmitParams, type HandleDelegateSubmitResult, type HandleMobilePrepareParams, type HandleMobilePrepareResult, type HandleMobileSubmitParams, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type IRateLimiter, type IRedemptionHistoryStore, type ISessionStore, type ISingletonLock, InMemoryCursorStore, IssuerApiAdapter, type IssuerApiAdapterConfig, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemoryPendingUserOpStore, MemoryRateLimiter, MemoryRedemptionHistoryStore, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type MobilePrepareDto, type MobileSubmitDto, type NativePtQuoterConfig, NonceManager, NoopRateLimiter, PAFI_ISSUER_SDK_VERSION, PTClaimError, PTClaimHandler, type PTClaimHandlerConfig, type PTClaimRequest, type PTClaimResponse, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PafiEstimatorClientConfig, PafiEstimatorHttpError, type PaymasterGasEstimates, type PendingCredit, type PendingUserOpEntry, PendingUserOpForbiddenError, PendingUserOpNotFoundError, type PerpDepositDto, PerpDepositError, PerpDepositHandler, type PerpDepositHandlerConfig, type PerpDepositRequest, type PerpDepositResponse, PointIndexer, type PointIndexerConfig, PointTokenDomainResolver, type PointTokenDomainResolverConfig, type PolicyDecision, type PolicyEvalRequest, PolicyProvider, type PolicyProviderConfig, type PoolsDto, type PoolsProvider, type PostgresQueryRunner, type PreValidateMintResult, type PrepareBurnParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, type PreviewBurnParams, type PreviewMintParams, REDEMPTION_HISTORY_WINDOW_SEC, type RateLimitAction, type RateLimiterConfig, type RedeemDto, type RedeemPrepareDto, RedemptionService, type RedemptionServiceConfig, RelayError, type RelayErrorCode, RelayService, type RelayUserOpParams, type RelayUserOpRequest, type RelayUserOpResponse, type RequestPaymasterParams, type ResolvedPolicy, type RetryConfig, type SdkErrorBody, type SdkErrorMapperFactories, type SdkErrorStatus, type SerializedUserOpTypedData, type Session, SettlementClient, type SettlementClientConfig, type SingletonLockHandle, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type UserDto, type UserHistory, applyPaymasterGasEstimates, authenticateRequest, buildSdkErrorBody, createIssuerService, createNativePtQuoter, createPafiEstimatorClient, createSdkErrorMapper, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, defaultPolicyFor, evaluateRedemption, handleClaimStatus, handleDelegateSubmit, handleMobilePrepare, handleMobileSubmit, handleRedeemStatus, makePostgresSingletonLock, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };
|
package/dist/index.d.ts
CHANGED
|
@@ -983,6 +983,56 @@ declare class InMemoryCursorStore implements IIndexerCursorStore {
|
|
|
983
983
|
load(): Promise<bigint | undefined>;
|
|
984
984
|
save(blockNumber: bigint): Promise<void>;
|
|
985
985
|
}
|
|
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
|
+
interface SingletonLockHandle {
|
|
999
|
+
release(): Promise<void>;
|
|
1000
|
+
}
|
|
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
|
+
interface ISingletonLock {
|
|
1027
|
+
/**
|
|
1028
|
+
* Attempt to acquire the lock for `key`. Returns a handle on success
|
|
1029
|
+
* (caller is the leader), or `null` if another holder owns it.
|
|
1030
|
+
*
|
|
1031
|
+
* Implementations MUST be non-blocking — if the lock is held, return
|
|
1032
|
+
* null immediately. The factory polls / retries at a higher layer.
|
|
1033
|
+
*/
|
|
1034
|
+
acquire(key: string): Promise<SingletonLockHandle | null>;
|
|
1035
|
+
}
|
|
986
1036
|
|
|
987
1037
|
interface PointIndexerConfig {
|
|
988
1038
|
provider: PublicClient;
|
|
@@ -1190,7 +1240,11 @@ interface BurnIndexerConfig {
|
|
|
1190
1240
|
*/
|
|
1191
1241
|
declare class BurnIndexer {
|
|
1192
1242
|
private readonly provider;
|
|
1193
|
-
|
|
1243
|
+
/**
|
|
1244
|
+
* The PointToken this indexer watches. Exposed so callers can key
|
|
1245
|
+
* leader-election locks / cursor stores by token (audit H-04 fix).
|
|
1246
|
+
*/
|
|
1247
|
+
readonly pointTokenAddress: Address;
|
|
1194
1248
|
private readonly ledger;
|
|
1195
1249
|
private readonly cursorStore;
|
|
1196
1250
|
private readonly startBlock;
|
|
@@ -1222,6 +1276,60 @@ declare class BurnIndexer {
|
|
|
1222
1276
|
private finalize;
|
|
1223
1277
|
}
|
|
1224
1278
|
|
|
1279
|
+
/**
|
|
1280
|
+
* Minimal duck-typed adapter so we don't drag a TypeORM / pg dep into
|
|
1281
|
+
* the SDK. The caller passes anything that can execute a parameterised
|
|
1282
|
+
* query and yield `{ got: boolean }` rows — TypeORM `DataSource.query`,
|
|
1283
|
+
* a raw `pg` client, knex's `raw`, kysely, etc.
|
|
1284
|
+
*
|
|
1285
|
+
* The helper requires `int8` (signed 64-bit) lock IDs. We derive them
|
|
1286
|
+
* from the lock `key` via a deterministic hash so different replicas
|
|
1287
|
+
* agree on the int regardless of casing / whitespace drift.
|
|
1288
|
+
*/
|
|
1289
|
+
interface PostgresQueryRunner {
|
|
1290
|
+
query(sql: string, params?: unknown[]): Promise<unknown>;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Build an `ISingletonLock` backed by PostgreSQL session-level advisory
|
|
1294
|
+
* locks (`pg_try_advisory_lock`).
|
|
1295
|
+
*
|
|
1296
|
+
* Why advisory locks (vs. row-level locks or a leases table):
|
|
1297
|
+
* 1. **Non-blocking** — `pg_try_advisory_lock` returns immediately;
|
|
1298
|
+
* perfect for the "try then idle" pattern the SDK uses on boot.
|
|
1299
|
+
* 2. **Auto-release on connection drop** — if the leader pod crashes,
|
|
1300
|
+
* its PG connection closes, and the lock is freed automatically.
|
|
1301
|
+
* No timeout tuning, no lease renewal loops, no zombie locks.
|
|
1302
|
+
* 3. **Zero schema** — no extra table, no migration, no contention
|
|
1303
|
+
* with application reads.
|
|
1304
|
+
*
|
|
1305
|
+
* **Caveat — long-lived connection required.** Session-level locks are
|
|
1306
|
+
* held only for the lifetime of the PG connection that called
|
|
1307
|
+
* `pg_try_advisory_lock`. PgBouncer in `transaction` pooling mode will
|
|
1308
|
+
* release the lock between statements; you MUST run in `session`
|
|
1309
|
+
* pooling or skip the bouncer for the indexer process. The advisory
|
|
1310
|
+
* `transaction` variant exists but doesn't fit the "hold while polling"
|
|
1311
|
+
* pattern — for that you'd need a periodic re-acquire, which we don't
|
|
1312
|
+
* implement here.
|
|
1313
|
+
*
|
|
1314
|
+
* @example
|
|
1315
|
+
* import { DataSource } from "typeorm";
|
|
1316
|
+
* import { makePostgresSingletonLock } from "@pafi-dev/issuer";
|
|
1317
|
+
*
|
|
1318
|
+
* const dataSource = new DataSource({ ... });
|
|
1319
|
+
* const lock = makePostgresSingletonLock({
|
|
1320
|
+
* query: (sql, params) => dataSource.query(sql, params),
|
|
1321
|
+
* });
|
|
1322
|
+
*
|
|
1323
|
+
* createIssuerService({
|
|
1324
|
+
* ...,
|
|
1325
|
+
* indexer: {
|
|
1326
|
+
* autoStart: true,
|
|
1327
|
+
* singletonLock: lock,
|
|
1328
|
+
* },
|
|
1329
|
+
* });
|
|
1330
|
+
*/
|
|
1331
|
+
declare function makePostgresSingletonLock(runner: PostgresQueryRunner): ISingletonLock;
|
|
1332
|
+
|
|
1225
1333
|
interface ApiConfigResponse {
|
|
1226
1334
|
chainId: number;
|
|
1227
1335
|
contracts: {
|
|
@@ -2890,8 +2998,27 @@ interface IssuerServiceConfig {
|
|
|
2890
2998
|
/**
|
|
2891
2999
|
* If `true`, the factory calls `indexer.start()` before returning.
|
|
2892
3000
|
* Default: `false` — the caller decides when to begin polling.
|
|
3001
|
+
*
|
|
3002
|
+
* **SAFETY (H-04):** in a multi-replica deployment, ALWAYS pair
|
|
3003
|
+
* `autoStart: true` with `singletonLock` below. Without leader
|
|
3004
|
+
* election, every replica starts its own indexer fleet and races
|
|
3005
|
+
* against the others — see `ISingletonLock` docs for the
|
|
3006
|
+
* consequences.
|
|
2893
3007
|
*/
|
|
2894
3008
|
autoStart?: boolean;
|
|
3009
|
+
/**
|
|
3010
|
+
* Leader-election primitive. When provided, the factory wraps
|
|
3011
|
+
* `indexer.start()` with a `singletonLock.acquire(key)` call and
|
|
3012
|
+
* only starts the indexer if it wins the lock. Non-leaders stay
|
|
3013
|
+
* idle and take over on the next acquire attempt (when the leader
|
|
3014
|
+
* pod's connection drops, the lock auto-releases).
|
|
3015
|
+
*
|
|
3016
|
+
* The lock key includes the indexer kind + the PointToken address,
|
|
3017
|
+
* so different tokens can be sharded across replicas if desired.
|
|
3018
|
+
*
|
|
3019
|
+
* Recommended: `makePostgresSingletonLock(dataSource)`.
|
|
3020
|
+
*/
|
|
3021
|
+
singletonLock?: ISingletonLock;
|
|
2895
3022
|
/**
|
|
2896
3023
|
* Override the MintFeeWrapper address used by the indexer. When
|
|
2897
3024
|
* omitted, the factory auto-resolves from
|
|
@@ -2937,6 +3064,13 @@ interface IssuerService {
|
|
|
2937
3064
|
fee: FeeManager | undefined;
|
|
2938
3065
|
/** All indexers keyed by PointToken address. */
|
|
2939
3066
|
indexers: Map<Address, PointIndexer>;
|
|
3067
|
+
/**
|
|
3068
|
+
* Lock handles for the indexers this replica was elected leader for.
|
|
3069
|
+
* Empty when `autoStart` is false, or when no `singletonLock` was
|
|
3070
|
+
* provided. Call `release()` on each during graceful shutdown so
|
|
3071
|
+
* peers can take over without waiting for the connection to die.
|
|
3072
|
+
*/
|
|
3073
|
+
indexerLeaderLocks: SingletonLockHandle[];
|
|
2940
3074
|
/** Framework-agnostic HTTP handlers — wire into Express / Fastify / Hono. */
|
|
2941
3075
|
api: IssuerApiHandlers;
|
|
2942
3076
|
/**
|
|
@@ -2958,7 +3092,7 @@ interface IssuerService {
|
|
|
2958
3092
|
*
|
|
2959
3093
|
* Throws synchronously if any required field is missing.
|
|
2960
3094
|
*/
|
|
2961
|
-
declare function createIssuerService(config: IssuerServiceConfig): IssuerService
|
|
3095
|
+
declare function createIssuerService(config: IssuerServiceConfig): Promise<IssuerService>;
|
|
2962
3096
|
|
|
2963
3097
|
/**
|
|
2964
3098
|
* Adapter that absorbs every "framework-agnostic" endpoint body into a
|
|
@@ -3698,4 +3832,4 @@ declare class MemoryRedemptionHistoryStore implements IRedemptionHistoryStore {
|
|
|
3698
3832
|
|
|
3699
3833
|
declare const PAFI_ISSUER_SDK_VERSION: string;
|
|
3700
3834
|
|
|
3701
|
-
export { AdapterMisconfiguredError, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedemptionEvaluateRequest, type ApiRedemptionEvaluateResponse, type ApiRedemptionPreviewRequest, type ApiRedemptionPreviewResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, type BundlerEstimatorClient, BundlerNotConfiguredError, BundlerRejectedError, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type ClaimDto, type ConfigDto, ConfigurationError, DEFAULT_REDEMPTION_POLICY, type DecodedCallDto, DefaultPolicyEngine, type DefaultPolicyEngineOptions, type DelegatePrepareDto, type DelegateStatusDto, type EstimateGasFeeOptions, type EvaluateInput, FeeManager, type FeeManagerConfig, type FeeManagerMetrics, type FetchFailureReason, type FetchImpl, type FetchResult, type GasFeeDto, type GasFeeSource, type HandleDelegateSubmitParams, type HandleDelegateSubmitResult, type HandleMobilePrepareParams, type HandleMobilePrepareResult, type HandleMobileSubmitParams, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type IRateLimiter, type IRedemptionHistoryStore, type ISessionStore, InMemoryCursorStore, IssuerApiAdapter, type IssuerApiAdapterConfig, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemoryPendingUserOpStore, MemoryRateLimiter, MemoryRedemptionHistoryStore, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type MobilePrepareDto, type MobileSubmitDto, type NativePtQuoterConfig, NonceManager, NoopRateLimiter, PAFI_ISSUER_SDK_VERSION, PTClaimError, PTClaimHandler, type PTClaimHandlerConfig, type PTClaimRequest, type PTClaimResponse, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PafiEstimatorClientConfig, PafiEstimatorHttpError, type PaymasterGasEstimates, type PendingCredit, type PendingUserOpEntry, PendingUserOpForbiddenError, PendingUserOpNotFoundError, type PerpDepositDto, PerpDepositError, PerpDepositHandler, type PerpDepositHandlerConfig, type PerpDepositRequest, type PerpDepositResponse, PointIndexer, type PointIndexerConfig, PointTokenDomainResolver, type PointTokenDomainResolverConfig, type PolicyDecision, type PolicyEvalRequest, PolicyProvider, type PolicyProviderConfig, type PoolsDto, type PoolsProvider, type PreValidateMintResult, type PrepareBurnParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, type PreviewBurnParams, type PreviewMintParams, REDEMPTION_HISTORY_WINDOW_SEC, type RateLimitAction, type RateLimiterConfig, type RedeemDto, type RedeemPrepareDto, RedemptionService, type RedemptionServiceConfig, RelayError, type RelayErrorCode, RelayService, type RelayUserOpParams, type RelayUserOpRequest, type RelayUserOpResponse, type RequestPaymasterParams, type ResolvedPolicy, type RetryConfig, type SdkErrorBody, type SdkErrorMapperFactories, type SdkErrorStatus, type SerializedUserOpTypedData, type Session, SettlementClient, type SettlementClientConfig, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type UserDto, type UserHistory, applyPaymasterGasEstimates, authenticateRequest, buildSdkErrorBody, createIssuerService, createNativePtQuoter, createPafiEstimatorClient, createSdkErrorMapper, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, defaultPolicyFor, evaluateRedemption, handleClaimStatus, handleDelegateSubmit, handleMobilePrepare, handleMobileSubmit, handleRedeemStatus, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };
|
|
3835
|
+
export { AdapterMisconfiguredError, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedemptionEvaluateRequest, type ApiRedemptionEvaluateResponse, type ApiRedemptionPreviewRequest, type ApiRedemptionPreviewResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, type BundlerEstimatorClient, BundlerNotConfiguredError, BundlerRejectedError, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type ClaimDto, type ConfigDto, ConfigurationError, DEFAULT_REDEMPTION_POLICY, type DecodedCallDto, DefaultPolicyEngine, type DefaultPolicyEngineOptions, type DelegatePrepareDto, type DelegateStatusDto, type EstimateGasFeeOptions, type EvaluateInput, FeeManager, type FeeManagerConfig, type FeeManagerMetrics, type FetchFailureReason, type FetchImpl, type FetchResult, type GasFeeDto, type GasFeeSource, type HandleDelegateSubmitParams, type HandleDelegateSubmitResult, type HandleMobilePrepareParams, type HandleMobilePrepareResult, type HandleMobileSubmitParams, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type IRateLimiter, type IRedemptionHistoryStore, type ISessionStore, type ISingletonLock, InMemoryCursorStore, IssuerApiAdapter, type IssuerApiAdapterConfig, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemoryPendingUserOpStore, MemoryRateLimiter, MemoryRedemptionHistoryStore, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type MobilePrepareDto, type MobileSubmitDto, type NativePtQuoterConfig, NonceManager, NoopRateLimiter, PAFI_ISSUER_SDK_VERSION, PTClaimError, PTClaimHandler, type PTClaimHandlerConfig, type PTClaimRequest, type PTClaimResponse, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PafiEstimatorClientConfig, PafiEstimatorHttpError, type PaymasterGasEstimates, type PendingCredit, type PendingUserOpEntry, PendingUserOpForbiddenError, PendingUserOpNotFoundError, type PerpDepositDto, PerpDepositError, PerpDepositHandler, type PerpDepositHandlerConfig, type PerpDepositRequest, type PerpDepositResponse, PointIndexer, type PointIndexerConfig, PointTokenDomainResolver, type PointTokenDomainResolverConfig, type PolicyDecision, type PolicyEvalRequest, PolicyProvider, type PolicyProviderConfig, type PoolsDto, type PoolsProvider, type PostgresQueryRunner, type PreValidateMintResult, type PrepareBurnParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, type PreviewBurnParams, type PreviewMintParams, REDEMPTION_HISTORY_WINDOW_SEC, type RateLimitAction, type RateLimiterConfig, type RedeemDto, type RedeemPrepareDto, RedemptionService, type RedemptionServiceConfig, RelayError, type RelayErrorCode, RelayService, type RelayUserOpParams, type RelayUserOpRequest, type RelayUserOpResponse, type RequestPaymasterParams, type ResolvedPolicy, type RetryConfig, type SdkErrorBody, type SdkErrorMapperFactories, type SdkErrorStatus, type SerializedUserOpTypedData, type Session, SettlementClient, type SettlementClientConfig, type SingletonLockHandle, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type UserDto, type UserHistory, applyPaymasterGasEstimates, authenticateRequest, buildSdkErrorBody, createIssuerService, createNativePtQuoter, createPafiEstimatorClient, createSdkErrorMapper, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, defaultPolicyFor, evaluateRedemption, handleClaimStatus, handleDelegateSubmit, handleMobilePrepare, handleMobileSubmit, handleRedeemStatus, makePostgresSingletonLock, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };
|
package/dist/index.js
CHANGED
|
@@ -1391,6 +1391,10 @@ var DEFAULT_BATCH_SIZE2 = 2000n;
|
|
|
1391
1391
|
var DEFAULT_POLL_INTERVAL_MS2 = 5e3;
|
|
1392
1392
|
var BurnIndexer = class {
|
|
1393
1393
|
provider;
|
|
1394
|
+
/**
|
|
1395
|
+
* The PointToken this indexer watches. Exposed so callers can key
|
|
1396
|
+
* leader-election locks / cursor stores by token (audit H-04 fix).
|
|
1397
|
+
*/
|
|
1394
1398
|
pointTokenAddress;
|
|
1395
1399
|
ledger;
|
|
1396
1400
|
cursorStore;
|
|
@@ -1550,6 +1554,45 @@ var BurnIndexer = class {
|
|
|
1550
1554
|
}
|
|
1551
1555
|
};
|
|
1552
1556
|
|
|
1557
|
+
// src/indexer/postgresSingletonLock.ts
|
|
1558
|
+
function makePostgresSingletonLock(runner) {
|
|
1559
|
+
return {
|
|
1560
|
+
async acquire(key) {
|
|
1561
|
+
const lockId = hashKeyToInt64(key);
|
|
1562
|
+
const rows = await runner.query(
|
|
1563
|
+
"SELECT pg_try_advisory_lock($1::bigint) AS got",
|
|
1564
|
+
[lockId]
|
|
1565
|
+
);
|
|
1566
|
+
const got = rows[0]?.got === true;
|
|
1567
|
+
if (!got) return null;
|
|
1568
|
+
return {
|
|
1569
|
+
async release() {
|
|
1570
|
+
try {
|
|
1571
|
+
await runner.query("SELECT pg_advisory_unlock($1::bigint)", [
|
|
1572
|
+
lockId
|
|
1573
|
+
]);
|
|
1574
|
+
} catch {
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
function hashKeyToInt64(key) {
|
|
1582
|
+
const FNV_OFFSET = 0xcbf29ce484222325n;
|
|
1583
|
+
const FNV_PRIME = 0x100000001b3n;
|
|
1584
|
+
const MASK_64 = (1n << 64n) - 1n;
|
|
1585
|
+
let hash = FNV_OFFSET;
|
|
1586
|
+
for (let i = 0; i < key.length; i++) {
|
|
1587
|
+
hash ^= BigInt(key.charCodeAt(i));
|
|
1588
|
+
hash = hash * FNV_PRIME & MASK_64;
|
|
1589
|
+
}
|
|
1590
|
+
const SIGNED_MAX = (1n << 63n) - 1n;
|
|
1591
|
+
const TWO64 = 1n << 64n;
|
|
1592
|
+
const signed = hash > SIGNED_MAX ? hash - TWO64 : hash;
|
|
1593
|
+
return signed.toString();
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1553
1596
|
// src/api/handlers.ts
|
|
1554
1597
|
import { getAddress as getAddress5 } from "viem";
|
|
1555
1598
|
import {
|
|
@@ -2861,8 +2904,16 @@ var PTClaimHandler = class {
|
|
|
2861
2904
|
domain,
|
|
2862
2905
|
mintRequestNonce: request.mintRequestNonce,
|
|
2863
2906
|
deadline: signatureDeadline,
|
|
2864
|
-
mintFeeWrapperAddress: resolvedWrapper
|
|
2865
|
-
//
|
|
2907
|
+
mintFeeWrapperAddress: resolvedWrapper,
|
|
2908
|
+
// Pass the bundler-estimated `feeAmount` explicitly so the
|
|
2909
|
+
// RelayService skips its legacy `quoteOperatorFeePt` path
|
|
2910
|
+
// (which uses the SDK's old 12_000 bps premium default).
|
|
2911
|
+
// Without this, the response's `feeAmount` (from FeeManager,
|
|
2912
|
+
// 100% premium on top of PAFI's 110% server-side estimate)
|
|
2913
|
+
// would diverge from the actual PT.transfer amount in the
|
|
2914
|
+
// UserOp batch (`quoteOperatorFeePt`'s 120%), and the user
|
|
2915
|
+
// would see one value while the wallet transferred another.
|
|
2916
|
+
feeAmount
|
|
2866
2917
|
});
|
|
2867
2918
|
} catch (err) {
|
|
2868
2919
|
throw new PTClaimError(
|
|
@@ -4469,7 +4520,7 @@ var RedemptionService = class {
|
|
|
4469
4520
|
};
|
|
4470
4521
|
|
|
4471
4522
|
// src/config.ts
|
|
4472
|
-
function createIssuerService(config) {
|
|
4523
|
+
async function createIssuerService(config) {
|
|
4473
4524
|
if (!config.provider) {
|
|
4474
4525
|
throw new Error("createIssuerService: provider is required");
|
|
4475
4526
|
}
|
|
@@ -4588,9 +4639,26 @@ function createIssuerService(config) {
|
|
|
4588
4639
|
handlersConfig.mintFeeWrapperAddress = resolvedWrapperAddress;
|
|
4589
4640
|
}
|
|
4590
4641
|
const handlers = new IssuerApiHandlers(handlersConfig);
|
|
4642
|
+
const indexerLeaderLocks = [];
|
|
4591
4643
|
if (config.indexer?.autoStart) {
|
|
4592
|
-
|
|
4593
|
-
|
|
4644
|
+
const lock = config.indexer.singletonLock;
|
|
4645
|
+
if (!lock) {
|
|
4646
|
+
console.warn(
|
|
4647
|
+
"[@pafi-dev/issuer] indexer.autoStart=true without singletonLock \u2014 this is UNSAFE in multi-replica deployments (audit finding H-04). Either set replicas=1 + INDEXER_AUTOSTART=false on non-leader pods, or pass `singletonLock: makePostgresSingletonLock(dataSource)`. This permissive path will be removed in a future major release."
|
|
4648
|
+
);
|
|
4649
|
+
for (const idx of indexers.values()) {
|
|
4650
|
+
idx.start();
|
|
4651
|
+
}
|
|
4652
|
+
} else {
|
|
4653
|
+
for (const [tokenAddr, idx] of indexers.entries()) {
|
|
4654
|
+
const key = `pafi-issuer:point-indexer:${tokenAddr.toLowerCase()}`;
|
|
4655
|
+
const handle = await lock.acquire(key);
|
|
4656
|
+
if (!handle) {
|
|
4657
|
+
continue;
|
|
4658
|
+
}
|
|
4659
|
+
idx.start();
|
|
4660
|
+
indexerLeaderLocks.push(handle);
|
|
4661
|
+
}
|
|
4594
4662
|
}
|
|
4595
4663
|
}
|
|
4596
4664
|
return {
|
|
@@ -4601,6 +4669,7 @@ function createIssuerService(config) {
|
|
|
4601
4669
|
relay: relayService,
|
|
4602
4670
|
fee: feeManager,
|
|
4603
4671
|
indexers,
|
|
4672
|
+
indexerLeaderLocks,
|
|
4604
4673
|
api: handlers,
|
|
4605
4674
|
redemption
|
|
4606
4675
|
};
|
|
@@ -4813,7 +4882,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
4813
4882
|
};
|
|
4814
4883
|
|
|
4815
4884
|
// src/index.ts
|
|
4816
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.
|
|
4885
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.22.1" : "dev";
|
|
4817
4886
|
export {
|
|
4818
4887
|
AdapterMisconfiguredError,
|
|
4819
4888
|
AuthError,
|
|
@@ -4879,6 +4948,7 @@ export {
|
|
|
4879
4948
|
handleMobilePrepare,
|
|
4880
4949
|
handleMobileSubmit,
|
|
4881
4950
|
handleRedeemStatus,
|
|
4951
|
+
makePostgresSingletonLock,
|
|
4882
4952
|
mergePaymasterFields,
|
|
4883
4953
|
payloadFromGenericError,
|
|
4884
4954
|
payloadFromHttpException,
|