@pafi-dev/issuer 0.7.8 → 0.8.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.js CHANGED
@@ -85,9 +85,9 @@ var MemorySessionStore = class {
85
85
  nonceTtlMs;
86
86
  now;
87
87
  constructor(opts = {}) {
88
- if (process.env.NODE_ENV === "production") {
89
- console.error(
90
- "[PAFI] MemorySessionStore is not safe for multi-process/K8s deployments. Session revocations are NOT propagated across pods. Use a Redis-backed session store in production."
88
+ if (process.env.NODE_ENV === "production" && !opts.dangerouslyAllowMemoryStoreInProduction) {
89
+ throw new Error(
90
+ "[PAFI] MemorySessionStore refuses to start in production (NODE_ENV=production). Multi-pod K8s deploys do not share Map state, so sessions are not revocable across replicas \u2014 `logout` on pod A leaves the token valid on pod B until expiry. Use a Redis-backed session store (see RedisSessionStoreService in gg56-backend or implement your own ISessionStore). To bypass for a single-pod deploy, pass `dangerouslyAllowMemoryStoreInProduction: true`."
91
91
  );
92
92
  }
93
93
  this.nonceTtlMs = opts.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS;
@@ -214,6 +214,64 @@ var AuthError = class extends PafiSdkError {
214
214
  };
215
215
 
216
216
  // src/auth/loginVerifier.ts
217
+ function assertJwtSecretStrength(secret) {
218
+ if (!secret) {
219
+ throw new Error(
220
+ "AuthService: jwtSecret is required. Generate via `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"`"
221
+ );
222
+ }
223
+ if (secret.length < 32) {
224
+ throw new Error(
225
+ `AuthService: jwtSecret too short (${secret.length} chars; need \u2265 32). HS256 brute-force becomes feasible below this threshold.`
226
+ );
227
+ }
228
+ const uniqueChars = new Set(secret).size;
229
+ if (uniqueChars < 16) {
230
+ throw new Error(
231
+ `AuthService: jwtSecret has only ${uniqueChars} unique characters; need \u2265 16. A trivially weak secret (e.g. "aaaaa...") will pass length but offer almost no entropy. Use \`crypto.randomBytes(32).toString('hex')\`.`
232
+ );
233
+ }
234
+ const entropy = shannonEntropyBitsPerChar(secret);
235
+ if (entropy < 3.5) {
236
+ throw new Error(
237
+ `AuthService: jwtSecret entropy too low (${entropy.toFixed(2)} bits/char; need \u2265 3.5). Use \`crypto.randomBytes(32).toString('hex')\` (\u2248 4.0 bits/char).`
238
+ );
239
+ }
240
+ for (let period = 1; period <= secret.length / 4; period++) {
241
+ if (secret.length % period !== 0) continue;
242
+ const head = secret.slice(0, period);
243
+ if (secret === head.repeat(secret.length / period)) {
244
+ throw new Error(
245
+ `AuthService: jwtSecret is a repeating pattern of period ${period}. Use \`crypto.randomBytes(32).toString('hex')\`.`
246
+ );
247
+ }
248
+ }
249
+ }
250
+ function shannonEntropyBitsPerChar(s) {
251
+ const counts = /* @__PURE__ */ new Map();
252
+ for (const c of s) counts.set(c, (counts.get(c) ?? 0) + 1);
253
+ const len = s.length;
254
+ let h = 0;
255
+ for (const n of counts.values()) {
256
+ const p = n / len;
257
+ h -= p * Math.log2(p);
258
+ }
259
+ return h;
260
+ }
261
+ function decodeExpiredJwtJti(token) {
262
+ try {
263
+ const parts = token.split(".");
264
+ if (parts.length !== 3) return {};
265
+ const payloadB64 = parts[1];
266
+ if (!payloadB64) return {};
267
+ const padded = payloadB64.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((payloadB64.length + 3) % 4);
268
+ const json = Buffer.from(padded, "base64").toString("utf-8");
269
+ const claims = JSON.parse(json);
270
+ return typeof claims.jti === "string" ? { jti: claims.jti } : {};
271
+ } catch {
272
+ return {};
273
+ }
274
+ }
217
275
  var DEFAULT_EXPIRES_IN = "24h";
218
276
  var AuthService = class {
219
277
  sessionStore;
@@ -221,17 +279,19 @@ var AuthService = class {
221
279
  jwtExpiresIn;
222
280
  domain;
223
281
  chainId;
282
+ issuer;
283
+ audience;
224
284
  nonceManager;
225
285
  now;
226
286
  constructor(config) {
227
- if (!config.jwtSecret || config.jwtSecret.length < 32) {
228
- throw new Error("AuthService: jwtSecret must be at least 32 characters for HS256 security");
229
- }
287
+ assertJwtSecretStrength(config.jwtSecret);
230
288
  this.sessionStore = config.sessionStore;
231
289
  this.jwtSecret = new TextEncoder().encode(config.jwtSecret);
232
290
  this.jwtExpiresIn = config.jwtExpiresIn ?? DEFAULT_EXPIRES_IN;
233
291
  this.domain = config.domain;
234
292
  this.chainId = config.chainId;
293
+ this.issuer = config.issuer;
294
+ this.audience = config.audience;
235
295
  this.nonceManager = new NonceManager(config.sessionStore);
236
296
  this.now = config.now ?? (() => /* @__PURE__ */ new Date());
237
297
  }
@@ -308,29 +368,59 @@ var AuthService = class {
308
368
  expiresAt
309
369
  };
310
370
  await this.sessionStore.createSession(session);
311
- const token = await new SignJWT({
371
+ let signer = new SignJWT({
312
372
  userAddress,
313
373
  chainId: this.chainId
314
- }).setProtectedHeader({ alg: "HS256" }).setJti(tokenId).setIssuedAt(Math.floor(issuedAt.getTime() / 1e3)).setExpirationTime(Math.floor(expiresAt.getTime() / 1e3)).sign(this.jwtSecret);
374
+ }).setProtectedHeader({ alg: "HS256" }).setJti(tokenId).setIssuedAt(Math.floor(issuedAt.getTime() / 1e3)).setExpirationTime(Math.floor(expiresAt.getTime() / 1e3));
375
+ if (this.issuer) signer = signer.setIssuer(this.issuer);
376
+ if (this.audience) signer = signer.setAudience(this.audience);
377
+ const token = await signer.sign(this.jwtSecret);
315
378
  return { token, userAddress, tokenId, expiresAt };
316
379
  }
317
380
  /** Revoke the session backing the given JWT (logout). */
318
381
  async logout(token) {
382
+ let payload;
319
383
  try {
320
- const { payload } = await jwtVerify(token, this.jwtSecret, {
321
- clockTolerance: 60
384
+ const result = await jwtVerify(token, this.jwtSecret, {
385
+ clockTolerance: 60,
322
386
  // allow logout right after expiry
387
+ ...this.issuer ? { issuer: this.issuer } : {},
388
+ ...this.audience ? { audience: this.audience } : {}
323
389
  });
324
- if (payload.jti) {
325
- await this.sessionStore.revokeSession(payload.jti);
326
- }
390
+ payload = result.payload;
327
391
  } catch (err) {
328
- const msg = err instanceof Error ? err.message : String(err);
329
- if (!msg.includes("not found") && !msg.includes("expired")) {
330
- console.error("[PAFI] AuthService logout: session store error", err);
392
+ if (err instanceof joseErrors.JWTExpired) {
393
+ const decoded = decodeExpiredJwtJti(token);
394
+ if (decoded.jti) {
395
+ try {
396
+ await this.sessionStore.revokeSession(decoded.jti);
397
+ } catch (storeErr) {
398
+ this.logSessionStoreError(storeErr);
399
+ }
400
+ }
401
+ return;
402
+ }
403
+ if (err instanceof joseErrors.JWSSignatureVerificationFailed || err instanceof joseErrors.JWSInvalid || err instanceof joseErrors.JWTInvalid) {
404
+ throw new AuthError("TOKEN_INVALID", "JWT verification failed");
405
+ }
406
+ throw new AuthError(
407
+ "TOKEN_INVALID",
408
+ `JWT verification failed: ${err instanceof Error ? err.message : String(err)}`
409
+ );
410
+ }
411
+ if (payload.jti) {
412
+ try {
413
+ await this.sessionStore.revokeSession(payload.jti);
414
+ } catch (storeErr) {
415
+ this.logSessionStoreError(storeErr);
331
416
  }
332
417
  }
333
418
  }
419
+ logSessionStoreError(err) {
420
+ const msg = err instanceof Error ? err.message : String(err);
421
+ if (msg.includes("not found")) return;
422
+ console.error("[PAFI] AuthService logout: session store error", err);
423
+ }
334
424
  /**
335
425
  * Verify a JWT and return the authenticated user context. Throws an
336
426
  * `AuthError` if the token is missing, malformed, expired, revoked, or
@@ -339,7 +429,10 @@ var AuthService = class {
339
429
  async verifyToken(token) {
340
430
  let payload;
341
431
  try {
342
- const result = await jwtVerify(token, this.jwtSecret);
432
+ const result = await jwtVerify(token, this.jwtSecret, {
433
+ ...this.issuer ? { issuer: this.issuer } : {},
434
+ ...this.audience ? { audience: this.audience } : {}
435
+ });
343
436
  payload = result.payload;
344
437
  } catch (err) {
345
438
  if (err instanceof joseErrors.JWTExpired) {
@@ -405,6 +498,70 @@ async function authenticateRequest(authHeader, authService) {
405
498
  return authService.verifyToken(token);
406
499
  }
407
500
 
501
+ // src/auth/rateLimiter.ts
502
+ var DEFAULT_LIMITS = {
503
+ auth_nonce: { max: 30, windowMs: 6e4 },
504
+ // 30 nonces/min ≈ 1 per 2s
505
+ auth_login: { max: 5, windowMs: 6e4 }
506
+ // 5 logins/min
507
+ };
508
+ var MemoryRateLimiter = class {
509
+ buckets = /* @__PURE__ */ new Map();
510
+ limits;
511
+ now;
512
+ constructor(config = {}) {
513
+ if (process.env.NODE_ENV === "production" && !process.env.PAFI_ALLOW_MEMORY_RATE_LIMITER_IN_PROD) {
514
+ console.warn(
515
+ "[PAFI] MemoryRateLimiter not safe for multi-pod K8s deploys \u2014 rate counters are NOT shared across replicas, allowing round-robin bypass. Use a Redis-backed IRateLimiter in production."
516
+ );
517
+ }
518
+ this.limits = {
519
+ ...DEFAULT_LIMITS,
520
+ ...config.limits ?? {}
521
+ };
522
+ this.now = config.now ?? (() => Date.now());
523
+ }
524
+ async consume(key, action) {
525
+ const limit = this.limits[action];
526
+ if (!limit) return { allowed: true };
527
+ const bucketKey = `${action}:${key}`;
528
+ const now = this.now();
529
+ const bucket = this.buckets.get(bucketKey);
530
+ if (!bucket || now - bucket.windowStartedAt >= limit.windowMs) {
531
+ this.buckets.set(bucketKey, { count: 1, windowStartedAt: now });
532
+ return { allowed: true };
533
+ }
534
+ if (bucket.count < limit.max) {
535
+ bucket.count += 1;
536
+ return { allowed: true };
537
+ }
538
+ const retryAfterMs = Math.max(
539
+ 0,
540
+ bucket.windowStartedAt + limit.windowMs - now
541
+ );
542
+ return { allowed: false, retryAfterMs };
543
+ }
544
+ /**
545
+ * Test helper — clear all buckets. Not part of `IRateLimiter`; only
546
+ * exposed on the in-memory impl for unit tests.
547
+ */
548
+ reset() {
549
+ this.buckets.clear();
550
+ }
551
+ };
552
+ var NoopRateLimiter = class {
553
+ warned = false;
554
+ async consume() {
555
+ if (!this.warned && process.env.NODE_ENV === "production") {
556
+ console.warn(
557
+ "[PAFI] NoopRateLimiter active \u2014 `/auth/nonce` and `/auth/login` are NOT throttled. Wire a `MemoryRateLimiter` (dev) or Redis-backed impl (prod) via `IssuerApiHandlersConfig.rateLimiter`."
558
+ );
559
+ this.warned = true;
560
+ }
561
+ return { allowed: true };
562
+ }
563
+ };
564
+
408
565
  // src/relay/types.ts
409
566
  var RelayError = class extends PafiSdkError {
410
567
  httpStatus = "unprocessable";
@@ -761,6 +918,7 @@ var PointIndexer = class {
761
918
  confirmations;
762
919
  batchSize;
763
920
  pollIntervalMs;
921
+ onTickError;
764
922
  running = false;
765
923
  timer;
766
924
  constructor(config) {
@@ -776,6 +934,7 @@ var PointIndexer = class {
776
934
  this.confirmations = BigInt(config.confirmations ?? DEFAULT_CONFIRMATIONS);
777
935
  this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE));
778
936
  this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
937
+ if (config.onTickError) this.onTickError = config.onTickError;
779
938
  }
780
939
  // -------------------------------------------------------------------------
781
940
  // Lifecycle
@@ -784,7 +943,7 @@ var PointIndexer = class {
784
943
  start() {
785
944
  if (this.running) return;
786
945
  this.running = true;
787
- void this.tick();
946
+ this.tick().catch((err) => this.handleTickError(err));
788
947
  }
789
948
  /** Stop polling. Safe to call multiple times. */
790
949
  stop() {
@@ -816,13 +975,27 @@ var PointIndexer = class {
816
975
  }
817
976
  await this.processBlockRange(from, safeHead);
818
977
  } catch (err) {
819
- console.error("[PAFI] PointIndexer tick error:", err);
978
+ this.handleTickError(err);
820
979
  }
821
980
  this.scheduleNext();
822
981
  }
982
+ handleTickError(err) {
983
+ if (this.onTickError) {
984
+ try {
985
+ this.onTickError(err);
986
+ } catch {
987
+ console.error("[PAFI] PointIndexer onTickError threw:", err);
988
+ }
989
+ } else {
990
+ console.error("[PAFI] PointIndexer tick error:", err);
991
+ }
992
+ }
823
993
  scheduleNext() {
824
994
  if (!this.running) return;
825
- this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);
995
+ this.timer = setTimeout(
996
+ () => this.tick().catch((err) => this.handleTickError(err)),
997
+ this.pollIntervalMs
998
+ );
826
999
  }
827
1000
  // -------------------------------------------------------------------------
828
1001
  // Block scanning
@@ -942,6 +1115,7 @@ var BurnIndexer = class {
942
1115
  confirmations;
943
1116
  batchSize;
944
1117
  pollIntervalMs;
1118
+ onTickError;
945
1119
  matchLockId;
946
1120
  running = false;
947
1121
  timer;
@@ -960,6 +1134,7 @@ var BurnIndexer = class {
960
1134
  );
961
1135
  this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE2));
962
1136
  this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
1137
+ if (config.onTickError) this.onTickError = config.onTickError;
963
1138
  if (!config.matchLockId) {
964
1139
  throw new Error(
965
1140
  "BurnIndexer: matchLockId is required. Provide a function that maps a burn event to its pending credit lockId. Without it, no on-chain burns will ever grant off-chain credits."
@@ -970,7 +1145,7 @@ var BurnIndexer = class {
970
1145
  start() {
971
1146
  if (this.running) return;
972
1147
  this.running = true;
973
- void this.tick();
1148
+ this.tick().catch((err) => this.handleTickError(err));
974
1149
  }
975
1150
  stop() {
976
1151
  this.running = false;
@@ -996,13 +1171,27 @@ var BurnIndexer = class {
996
1171
  }
997
1172
  await this.processBlockRange(from, safeHead);
998
1173
  } catch (err) {
999
- console.error("[PAFI] BurnIndexer tick error:", err);
1174
+ this.handleTickError(err);
1000
1175
  }
1001
1176
  this.scheduleNext();
1002
1177
  }
1178
+ handleTickError(err) {
1179
+ if (this.onTickError) {
1180
+ try {
1181
+ this.onTickError(err);
1182
+ } catch {
1183
+ console.error("[PAFI] BurnIndexer onTickError threw:", err);
1184
+ }
1185
+ } else {
1186
+ console.error("[PAFI] BurnIndexer tick error:", err);
1187
+ }
1188
+ }
1003
1189
  scheduleNext() {
1004
1190
  if (!this.running) return;
1005
- this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);
1191
+ this.timer = setTimeout(
1192
+ () => this.tick().catch((err) => this.handleTickError(err)),
1193
+ this.pollIntervalMs
1194
+ );
1006
1195
  }
1007
1196
  /**
1008
1197
  * Scan `[from, to]` inclusive for burn events. Callers can drive this
@@ -1100,10 +1289,13 @@ var IssuerApiHandlers = class {
1100
1289
  pafiWebUrl;
1101
1290
  feeManager;
1102
1291
  poolsProvider;
1292
+ redemption;
1293
+ rateLimiter;
1103
1294
  constructor(config) {
1104
1295
  this.authService = config.authService;
1105
1296
  this.ledger = config.ledger;
1106
1297
  this.provider = config.provider;
1298
+ this.rateLimiter = config.rateLimiter ?? new NoopRateLimiter();
1107
1299
  const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
1108
1300
  if (raw.length === 0) {
1109
1301
  throw new Error(
@@ -1117,17 +1309,64 @@ var IssuerApiHandlers = class {
1117
1309
  if (config.pafiWebUrl) this.pafiWebUrl = config.pafiWebUrl;
1118
1310
  if (config.feeManager) this.feeManager = config.feeManager;
1119
1311
  if (config.poolsProvider) this.poolsProvider = config.poolsProvider;
1312
+ if (config.redemption) this.redemption = config.redemption;
1120
1313
  }
1121
1314
  // =========================================================================
1122
1315
  // Public handlers (no auth required)
1123
1316
  // =========================================================================
1124
- /** `GET /auth/nonce` */
1125
- async handleGetNonce() {
1317
+ /**
1318
+ * `GET /auth/nonce`
1319
+ *
1320
+ * @param rateLimitKey Caller-side rate-limit key (typically client IP).
1321
+ * The HTTP layer (controller/middleware) extracts
1322
+ * this from the request and passes it through.
1323
+ * When omitted, no rate limit applies — production
1324
+ * callers SHOULD always pass a key.
1325
+ */
1326
+ async handleGetNonce(rateLimitKey) {
1327
+ if (rateLimitKey) {
1328
+ const result = await this.rateLimiter.consume(
1329
+ rateLimitKey,
1330
+ "auth_nonce"
1331
+ );
1332
+ if (!result.allowed) {
1333
+ throw new ValidationError(
1334
+ "RATE_LIMIT_EXCEEDED",
1335
+ "handleGetNonce: too many requests",
1336
+ {
1337
+ retryAfterMs: result.retryAfterMs ?? 0,
1338
+ action: "auth_nonce"
1339
+ }
1340
+ );
1341
+ }
1342
+ }
1126
1343
  const nonce = await this.authService.getNonce();
1127
1344
  return { nonce };
1128
1345
  }
1129
- /** `POST /auth/login` */
1130
- async handleLogin(body) {
1346
+ /**
1347
+ * `POST /auth/login`
1348
+ *
1349
+ * @param body Login message + signature.
1350
+ * @param rateLimitKey Caller-side rate-limit key (typically client IP
1351
+ * or `body.userAddress` if known). See `handleGetNonce`.
1352
+ */
1353
+ async handleLogin(body, rateLimitKey) {
1354
+ if (rateLimitKey) {
1355
+ const result2 = await this.rateLimiter.consume(
1356
+ rateLimitKey,
1357
+ "auth_login"
1358
+ );
1359
+ if (!result2.allowed) {
1360
+ throw new ValidationError(
1361
+ "RATE_LIMIT_EXCEEDED",
1362
+ "handleLogin: too many requests",
1363
+ {
1364
+ retryAfterMs: result2.retryAfterMs ?? 0,
1365
+ action: "auth_login"
1366
+ }
1367
+ );
1368
+ }
1369
+ }
1131
1370
  if (!body || typeof body.message !== "string" || body.message.length === 0 || typeof body.signature !== "string" || body.signature.length <= 2) {
1132
1371
  throw new ValidationError(
1133
1372
  "INVALID_LOGIN_BODY",
@@ -1270,6 +1509,74 @@ var IssuerApiHandlers = class {
1270
1509
  // Note: legacy `handleClaim` (sync sponsored-claim returning calls[]) was
1271
1510
  // removed in 0.5.43 — callers should use `PTClaimHandler` directly or
1272
1511
  // wire `IssuerApiAdapter.claim()` which composes the full flow.
1512
+ /**
1513
+ * `GET /redemption/preview?pointToken=<addr>`
1514
+ *
1515
+ * Returns the headroom currently available to `userAddress` under the
1516
+ * configured RedemptionPolicy. Pure read — does not record anything.
1517
+ * Use this for UI to render "X PT redeemable now / next available at …".
1518
+ */
1519
+ async handleRedemptionPreview(userAddress, request) {
1520
+ if (!this.redemption) {
1521
+ throw new ConfigurationError(
1522
+ "REDEMPTION_NOT_CONFIGURED",
1523
+ "handleRedemptionPreview: redemption is not configured on this issuer"
1524
+ );
1525
+ }
1526
+ const tokenAddress = request.pointTokenAddress ? this.requireSupportedToken(getAddress5(request.pointTokenAddress), "handleRedemptionPreview") : void 0;
1527
+ const preview = await this.redemption.preview(
1528
+ getAddress5(userAddress),
1529
+ tokenAddress
1530
+ );
1531
+ return preview;
1532
+ }
1533
+ /**
1534
+ * `POST /redemption/evaluate`
1535
+ *
1536
+ * Pre-flight check before the issuer signs a BurnRequest. Returns
1537
+ * { allowed, denial?, preview }. Caller (the burn-orchestrator) MUST
1538
+ * re-check on the actual initiate path — evaluate is read-only and a
1539
+ * caller could race two requests under the same headroom. The intended
1540
+ * write path is: evaluate → sign BurnRequest → reserve pending credit
1541
+ * → call `service.redemption.recordSuccessfulInitiate()`.
1542
+ */
1543
+ async handleRedemptionEvaluate(userAddress, request) {
1544
+ if (!this.redemption) {
1545
+ throw new ConfigurationError(
1546
+ "REDEMPTION_NOT_CONFIGURED",
1547
+ "handleRedemptionEvaluate: redemption is not configured on this issuer"
1548
+ );
1549
+ }
1550
+ if (request.amountPt <= 0n) {
1551
+ throw new ValidationError(
1552
+ "INVALID_AMOUNT",
1553
+ "handleRedemptionEvaluate: amountPt must be positive",
1554
+ { amountPt: request.amountPt.toString() }
1555
+ );
1556
+ }
1557
+ const tokenAddress = request.pointTokenAddress ? this.requireSupportedToken(getAddress5(request.pointTokenAddress), "handleRedemptionEvaluate") : void 0;
1558
+ const decision = await this.redemption.evaluate(
1559
+ getAddress5(userAddress),
1560
+ request.amountPt,
1561
+ tokenAddress
1562
+ );
1563
+ const response = {
1564
+ allowed: decision.allowed,
1565
+ preview: decision.preview
1566
+ };
1567
+ if (decision.denial) response.denial = decision.denial;
1568
+ return response;
1569
+ }
1570
+ requireSupportedToken(pointToken, handler) {
1571
+ if (!this.supportedTokens.has(pointToken)) {
1572
+ throw new ValidationError(
1573
+ "UNSUPPORTED_POINT_TOKEN",
1574
+ `${handler}: unsupported pointToken ${pointToken}`,
1575
+ { requested: pointToken }
1576
+ );
1577
+ }
1578
+ return pointToken;
1579
+ }
1273
1580
  };
1274
1581
 
1275
1582
  // src/api/handlers/ptRedeemHandler.ts
@@ -1285,9 +1592,13 @@ var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
1285
1592
  var PTRedeemError = class extends PafiSdkError {
1286
1593
  httpStatus = "unprocessable";
1287
1594
  code;
1288
- constructor(code, message) {
1595
+ policyDenialCode;
1596
+ constructor(code, message, options) {
1289
1597
  super(message);
1290
1598
  this.code = code;
1599
+ if (options?.policyDenialCode) {
1600
+ this.policyDenialCode = options.policyDenialCode;
1601
+ }
1291
1602
  }
1292
1603
  };
1293
1604
  var PTRedeemHandler = class {
@@ -1303,6 +1614,7 @@ var PTRedeemHandler = class {
1303
1614
  redeemLockDurationMs;
1304
1615
  signatureDeadlineSeconds;
1305
1616
  now;
1617
+ redemptionService;
1306
1618
  /**
1307
1619
  * Per-user in-flight nonce guard (single-process only).
1308
1620
  *
@@ -1345,6 +1657,9 @@ var PTRedeemHandler = class {
1345
1657
  this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
1346
1658
  this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
1347
1659
  this.now = config.now ?? (() => Date.now());
1660
+ if (config.redemptionService) {
1661
+ this.redemptionService = config.redemptionService;
1662
+ }
1348
1663
  }
1349
1664
  async handle(request) {
1350
1665
  if (getAddress6(request.authenticatedAddress) !== getAddress6(request.userAddress)) {
@@ -1356,6 +1671,21 @@ var PTRedeemHandler = class {
1356
1671
  if (request.amount <= 0n) {
1357
1672
  throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
1358
1673
  }
1674
+ if (this.redemptionService) {
1675
+ const decision = await this.redemptionService.evaluate(
1676
+ request.userAddress,
1677
+ request.amount,
1678
+ this.pointTokenAddress
1679
+ );
1680
+ if (!decision.allowed) {
1681
+ const denial = decision.denial;
1682
+ throw new PTRedeemError(
1683
+ "REDEMPTION_POLICY_DENIED",
1684
+ `redemption denied: ${denial.message}`,
1685
+ { policyDenialCode: denial.code }
1686
+ );
1687
+ }
1688
+ }
1359
1689
  let burnNonce;
1360
1690
  try {
1361
1691
  burnNonce = await this.provider.readContract({
@@ -1509,6 +1839,15 @@ var PTRedeemHandler = class {
1509
1839
  netCreditAmount: request.amount
1510
1840
  };
1511
1841
  }
1842
+ if (this.redemptionService) {
1843
+ await this.redemptionService.recordSuccessfulInitiate({
1844
+ user: request.userAddress,
1845
+ amountPt: request.amount,
1846
+ pointTokenAddress: this.pointTokenAddress,
1847
+ reservationId: sponsoredLockId
1848
+ }).catch(() => {
1849
+ });
1850
+ }
1512
1851
  return {
1513
1852
  lockId: sponsoredLockId,
1514
1853
  userOp: sponsoredUserOp,
@@ -3267,6 +3606,7 @@ var BalanceAggregator = class {
3267
3606
  };
3268
3607
 
3269
3608
  // src/pafi-backend/client.ts
3609
+ import { getPafiServiceUrls } from "@pafi-dev/core";
3270
3610
  function serializeBigInt(_key, value) {
3271
3611
  return typeof value === "bigint" ? value.toString(10) : value;
3272
3612
  }
@@ -3275,10 +3615,13 @@ function sleep(ms) {
3275
3615
  }
3276
3616
  var PafiBackendClient = class {
3277
3617
  config;
3618
+ baseUrl;
3278
3619
  constructor(config) {
3279
- if (!config.url) throw new Error("PafiBackendClient: url is required");
3620
+ if (!config.chainId) throw new Error("PafiBackendClient: chainId is required");
3280
3621
  if (!config.issuerId) throw new Error("PafiBackendClient: issuerId is required");
3622
+ if (!config.apiKey) throw new Error("PafiBackendClient: apiKey is required");
3281
3623
  this.config = config;
3624
+ this.baseUrl = getPafiServiceUrls(config.chainId).sponsorRelayer;
3282
3625
  }
3283
3626
  async requestSponsorship(request) {
3284
3627
  const maxAttempts = this.config.retry?.maxAttempts ?? 1;
@@ -3313,7 +3656,7 @@ var PafiBackendClient = class {
3313
3656
  */
3314
3657
  async getUserOpReceipt(userOpHash) {
3315
3658
  const fetchFn = this.config.fetchImpl ?? fetch;
3316
- const url = `${this.config.url}/bundler/receipt`;
3659
+ const url = `${this.baseUrl}/bundler/receipt`;
3317
3660
  let response;
3318
3661
  try {
3319
3662
  response = await fetchFn(url, {
@@ -3352,7 +3695,7 @@ var PafiBackendClient = class {
3352
3695
  }
3353
3696
  async relayUserOperation(request) {
3354
3697
  const fetchFn = this.config.fetchImpl ?? fetch;
3355
- const url = `${this.config.url}/bundler/relay`;
3698
+ const url = `${this.baseUrl}/bundler/relay`;
3356
3699
  let response;
3357
3700
  try {
3358
3701
  response = await fetchFn(url, {
@@ -3386,7 +3729,7 @@ var PafiBackendClient = class {
3386
3729
  }
3387
3730
  async _doRequest(request) {
3388
3731
  const fetchFn = this.config.fetchImpl ?? fetch;
3389
- const url = `${this.config.url}/paymaster/sponsor`;
3732
+ const url = `${this.baseUrl}/paymaster/sponsor`;
3390
3733
  const body = JSON.stringify(request, serializeBigInt);
3391
3734
  let response;
3392
3735
  try {
@@ -3442,6 +3785,314 @@ var PafiBackendClient = class {
3442
3785
  // src/config.ts
3443
3786
  import { getAddress as getAddress11 } from "viem";
3444
3787
  import { getContractAddresses as getContractAddresses7 } from "@pafi-dev/core";
3788
+
3789
+ // src/redemption/evaluator.ts
3790
+ var SECONDS_PER_DAY = 24 * 60 * 60;
3791
+ function evaluateRedemption(input) {
3792
+ const { policy, history, amountPt, nowUnixSec, policySource } = input;
3793
+ const dailyRemaining = policy.dailyLimitPt > history.redeemedLast24hPt ? policy.dailyLimitPt - history.redeemedLast24hPt : 0n;
3794
+ const cooldownUntilUnixSec = history.lastRedeemedAtUnixSec !== null ? history.lastRedeemedAtUnixSec + policy.cooldownSec : null;
3795
+ const inCooldown = cooldownUntilUnixSec !== null && cooldownUntilUnixSec > nowUnixSec;
3796
+ const activeBlackout = findActiveBlackout(policy.blackoutWindows, nowUnixSec);
3797
+ const nextBlackoutEnd = nextBlackoutEndAfter(
3798
+ policy.blackoutWindows,
3799
+ nowUnixSec
3800
+ );
3801
+ let availableAmountPt = 0n;
3802
+ if (!inCooldown && !activeBlackout) {
3803
+ const headroom = dailyRemaining < policy.perTxMaxPt ? dailyRemaining : policy.perTxMaxPt;
3804
+ availableAmountPt = headroom >= policy.perTxMinPt ? headroom : 0n;
3805
+ }
3806
+ const preview = {
3807
+ availableAmountPt,
3808
+ dailyRemainingPt: dailyRemaining,
3809
+ cooldownUntilUnixSec: inCooldown ? cooldownUntilUnixSec : null,
3810
+ nextBlackoutEndsAtUnixSec: activeBlackout ? activeBlackout.endUnixSec : nextBlackoutEnd,
3811
+ perTxMinPt: policy.perTxMinPt,
3812
+ perTxMaxPt: policy.perTxMaxPt,
3813
+ policyVersion: policy.version,
3814
+ policySource
3815
+ };
3816
+ if (amountPt <= 0n) {
3817
+ return { allowed: false, preview, denial: rejectAmountBelowMin(policy) };
3818
+ }
3819
+ const denial = firstDenial({
3820
+ amountPt,
3821
+ policy,
3822
+ dailyRemaining,
3823
+ inCooldown,
3824
+ cooldownUntilUnixSec,
3825
+ activeBlackout
3826
+ });
3827
+ if (denial) return { allowed: false, denial, preview };
3828
+ return { allowed: true, preview };
3829
+ }
3830
+ function firstDenial(args) {
3831
+ const { amountPt, policy, dailyRemaining, inCooldown, cooldownUntilUnixSec, activeBlackout } = args;
3832
+ if (activeBlackout) {
3833
+ return {
3834
+ code: "BLACKOUT_WINDOW",
3835
+ message: `Redemption is blocked until ${new Date(
3836
+ activeBlackout.endUnixSec * 1e3
3837
+ ).toISOString()}${activeBlackout.reason ? ` (${activeBlackout.reason})` : ""}`
3838
+ };
3839
+ }
3840
+ if (inCooldown && cooldownUntilUnixSec !== null) {
3841
+ return {
3842
+ code: "COOLDOWN_ACTIVE",
3843
+ message: `Cooldown active until ${new Date(
3844
+ cooldownUntilUnixSec * 1e3
3845
+ ).toISOString()}`
3846
+ };
3847
+ }
3848
+ if (amountPt < policy.perTxMinPt) {
3849
+ return {
3850
+ code: "AMOUNT_BELOW_MIN",
3851
+ message: `amount ${amountPt} below per-tx minimum ${policy.perTxMinPt}`
3852
+ };
3853
+ }
3854
+ if (amountPt > policy.perTxMaxPt) {
3855
+ return {
3856
+ code: "AMOUNT_ABOVE_MAX",
3857
+ message: `amount ${amountPt} above per-tx maximum ${policy.perTxMaxPt}`
3858
+ };
3859
+ }
3860
+ if (amountPt > dailyRemaining) {
3861
+ return {
3862
+ code: "DAILY_LIMIT_EXCEEDED",
3863
+ message: `amount ${amountPt} exceeds daily remaining ${dailyRemaining}`
3864
+ };
3865
+ }
3866
+ return null;
3867
+ }
3868
+ function rejectAmountBelowMin(policy) {
3869
+ return {
3870
+ code: "AMOUNT_BELOW_MIN",
3871
+ message: `amount must be >= ${policy.perTxMinPt}`
3872
+ };
3873
+ }
3874
+ function findActiveBlackout(windows, nowUnixSec) {
3875
+ for (const w of windows) {
3876
+ if (w.startUnixSec <= nowUnixSec && nowUnixSec < w.endUnixSec) return w;
3877
+ }
3878
+ return null;
3879
+ }
3880
+ function nextBlackoutEndAfter(windows, nowUnixSec) {
3881
+ let earliest = null;
3882
+ for (const w of windows) {
3883
+ if (w.endUnixSec > nowUnixSec) {
3884
+ if (earliest === null || w.endUnixSec < earliest) earliest = w.endUnixSec;
3885
+ }
3886
+ }
3887
+ return earliest;
3888
+ }
3889
+ var REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;
3890
+
3891
+ // src/redemption/settlementClient.ts
3892
+ import {
3893
+ getPafiServiceUrls as getPafiServiceUrls2
3894
+ } from "@pafi-dev/core";
3895
+ var DEFAULT_TIMEOUT_MS = 1e3;
3896
+ var SettlementClient = class {
3897
+ config;
3898
+ constructor(config) {
3899
+ if (!config.chainId) throw new Error("SettlementClient: chainId is required");
3900
+ if (!config.issuerId) throw new Error("SettlementClient: issuerId is required");
3901
+ if (!config.apiKey) throw new Error("SettlementClient: apiKey is required");
3902
+ this.config = {
3903
+ baseUrl: getPafiServiceUrls2(config.chainId).issuerApi.replace(/\/+$/, ""),
3904
+ issuerId: config.issuerId,
3905
+ apiKey: config.apiKey,
3906
+ fetchTimeoutMs: config.fetchTimeoutMs ?? DEFAULT_TIMEOUT_MS,
3907
+ fetchImpl: config.fetchImpl
3908
+ };
3909
+ }
3910
+ async fetchPolicy() {
3911
+ const fetchFn = this.config.fetchImpl ?? fetch;
3912
+ const url = `${this.config.baseUrl}/issuers/${encodeURIComponent(this.config.issuerId)}/redemption-policy`;
3913
+ const controller = new AbortController();
3914
+ const timer = setTimeout(
3915
+ () => controller.abort(),
3916
+ this.config.fetchTimeoutMs
3917
+ );
3918
+ let response;
3919
+ try {
3920
+ response = await fetchFn(url, {
3921
+ method: "GET",
3922
+ headers: {
3923
+ "Content-Type": "application/json",
3924
+ Authorization: `Bearer ${this.config.apiKey}`,
3925
+ "X-Issuer-Id": this.config.issuerId
3926
+ },
3927
+ signal: controller.signal
3928
+ });
3929
+ } catch (err) {
3930
+ const isAbort = err instanceof Error && (err.name === "AbortError" || /aborted|timeout/i.test(err.message ?? ""));
3931
+ return { ok: false, reason: isAbort ? "TIMEOUT" : "NETWORK" };
3932
+ } finally {
3933
+ clearTimeout(timer);
3934
+ }
3935
+ if (response.status === 404) {
3936
+ return { ok: false, reason: "NOT_FOUND", status: 404 };
3937
+ }
3938
+ if (response.status === 401 || response.status === 403) {
3939
+ return { ok: false, reason: "UNAUTHORIZED", status: response.status };
3940
+ }
3941
+ if (!response.ok) {
3942
+ return { ok: false, reason: "SERVER_ERROR", status: response.status };
3943
+ }
3944
+ let raw;
3945
+ try {
3946
+ raw = await response.json();
3947
+ } catch {
3948
+ return { ok: false, reason: "INVALID_RESPONSE", status: response.status };
3949
+ }
3950
+ const parsed = parsePolicyDto(raw);
3951
+ if (!parsed) {
3952
+ return { ok: false, reason: "INVALID_RESPONSE", status: response.status };
3953
+ }
3954
+ return { ok: true, policy: parsed };
3955
+ }
3956
+ };
3957
+ function parsePolicyDto(raw) {
3958
+ if (!raw || typeof raw !== "object") return null;
3959
+ const dto = raw;
3960
+ if (typeof dto.issuerId !== "string" || typeof dto.dailyLimitPt !== "string" || typeof dto.cooldownSec !== "number" || typeof dto.perTxMinPt !== "string" || typeof dto.perTxMaxPt !== "string" || typeof dto.version !== "string" || !Array.isArray(dto.blackoutWindows)) {
3961
+ return null;
3962
+ }
3963
+ try {
3964
+ return {
3965
+ issuerId: dto.issuerId,
3966
+ dailyLimitPt: BigInt(dto.dailyLimitPt),
3967
+ cooldownSec: dto.cooldownSec,
3968
+ perTxMinPt: BigInt(dto.perTxMinPt),
3969
+ perTxMaxPt: BigInt(dto.perTxMaxPt),
3970
+ blackoutWindows: dto.blackoutWindows.map(normalizeBlackout).filter((b) => b !== null),
3971
+ version: dto.version
3972
+ };
3973
+ } catch {
3974
+ return null;
3975
+ }
3976
+ }
3977
+ function normalizeBlackout(raw) {
3978
+ if (!raw || typeof raw !== "object") return null;
3979
+ const win = raw;
3980
+ if (typeof win.startUnixSec !== "number" || typeof win.endUnixSec !== "number" || win.startUnixSec >= win.endUnixSec) {
3981
+ return null;
3982
+ }
3983
+ return {
3984
+ startUnixSec: win.startUnixSec,
3985
+ endUnixSec: win.endUnixSec,
3986
+ reason: typeof win.reason === "string" ? win.reason : void 0
3987
+ };
3988
+ }
3989
+
3990
+ // src/redemption/defaults.ts
3991
+ var PT_DECIMALS = 10n ** 18n;
3992
+ var DEFAULT_REDEMPTION_POLICY = {
3993
+ issuerId: "default",
3994
+ dailyLimitPt: 1000n * PT_DECIMALS,
3995
+ cooldownSec: 60,
3996
+ perTxMinPt: 1n * PT_DECIMALS,
3997
+ perTxMaxPt: 500n * PT_DECIMALS,
3998
+ blackoutWindows: [],
3999
+ version: "default-v1"
4000
+ };
4001
+ function defaultPolicyFor(issuerId) {
4002
+ return { ...DEFAULT_REDEMPTION_POLICY, issuerId };
4003
+ }
4004
+
4005
+ // src/redemption/policyProvider.ts
4006
+ var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
4007
+ var PolicyProvider = class {
4008
+ client;
4009
+ issuerId;
4010
+ cacheTtlMs;
4011
+ now;
4012
+ cache = null;
4013
+ inflight = null;
4014
+ constructor(config) {
4015
+ this.client = new SettlementClient(config);
4016
+ this.issuerId = config.issuerId;
4017
+ this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS3;
4018
+ this.now = config.now ?? (() => Date.now());
4019
+ }
4020
+ async getPolicy() {
4021
+ const fresh = this.readCache();
4022
+ if (fresh) return { policy: fresh, source: "cache" };
4023
+ if (this.inflight) return this.inflight;
4024
+ this.inflight = this.fetchAndStore().finally(() => {
4025
+ this.inflight = null;
4026
+ });
4027
+ return this.inflight;
4028
+ }
4029
+ /** Drop cached policy. Next getPolicy() will refetch. */
4030
+ invalidate() {
4031
+ this.cache = null;
4032
+ }
4033
+ readCache() {
4034
+ if (!this.cache) return null;
4035
+ if (this.cache.expiresAtMs <= this.now()) {
4036
+ this.cache = null;
4037
+ return null;
4038
+ }
4039
+ return this.cache.policy;
4040
+ }
4041
+ async fetchAndStore() {
4042
+ const result = await this.client.fetchPolicy();
4043
+ if (result.ok) {
4044
+ this.cache = {
4045
+ policy: result.policy,
4046
+ expiresAtMs: this.now() + this.cacheTtlMs
4047
+ };
4048
+ return { policy: result.policy, source: "settlement" };
4049
+ }
4050
+ return { policy: defaultPolicyFor(this.issuerId), source: "default" };
4051
+ }
4052
+ };
4053
+
4054
+ // src/redemption/service.ts
4055
+ var RedemptionService = class {
4056
+ policyProvider;
4057
+ historyStore;
4058
+ nowUnixSec;
4059
+ constructor(config) {
4060
+ this.policyProvider = config.policyProvider instanceof PolicyProvider ? config.policyProvider : new PolicyProvider(config.policyProvider);
4061
+ this.historyStore = config.historyStore;
4062
+ this.nowUnixSec = config.nowUnixSec ?? (() => Math.floor(Date.now() / 1e3));
4063
+ }
4064
+ async preview(user, pointTokenAddress) {
4065
+ const decision = await this.evaluate(user, 0n, pointTokenAddress);
4066
+ return decision.preview;
4067
+ }
4068
+ async evaluate(user, amountPt, pointTokenAddress) {
4069
+ const { policy, source } = await this.policyProvider.getPolicy();
4070
+ const now = this.nowUnixSec();
4071
+ const [redeemedLast24hPt, lastRedeemedAtUnixSec] = await Promise.all([
4072
+ this.historyStore.sumRedeemedSince(
4073
+ user,
4074
+ now - REDEMPTION_HISTORY_WINDOW_SEC,
4075
+ pointTokenAddress
4076
+ ),
4077
+ this.historyStore.getLastRedeemedAtUnixSec(user, pointTokenAddress)
4078
+ ]);
4079
+ return evaluateRedemption({
4080
+ policy,
4081
+ policySource: source,
4082
+ history: { redeemedLast24hPt, lastRedeemedAtUnixSec },
4083
+ amountPt,
4084
+ nowUnixSec: now
4085
+ });
4086
+ }
4087
+ async recordSuccessfulInitiate(entry) {
4088
+ await this.historyStore.recordRedemption({
4089
+ ...entry,
4090
+ unixSec: this.nowUnixSec()
4091
+ });
4092
+ }
4093
+ };
4094
+
4095
+ // src/config.ts
3445
4096
  function createIssuerService(config) {
3446
4097
  if (!config.provider) {
3447
4098
  throw new Error("createIssuerService: provider is required");
@@ -3520,6 +4171,25 @@ function createIssuerService(config) {
3520
4171
  pafiHook: chainAddresses.pafiHook,
3521
4172
  ...config.contracts
3522
4173
  };
4174
+ let redemption;
4175
+ if (config.redemption) {
4176
+ const policyConfig = {
4177
+ chainId: config.chainId,
4178
+ issuerId: config.redemption.issuerId,
4179
+ apiKey: config.redemption.apiKey
4180
+ };
4181
+ if (config.redemption.fetchImpl) policyConfig.fetchImpl = config.redemption.fetchImpl;
4182
+ if (config.redemption.fetchTimeoutMs !== void 0) {
4183
+ policyConfig.fetchTimeoutMs = config.redemption.fetchTimeoutMs;
4184
+ }
4185
+ if (config.redemption.cacheTtlMs !== void 0) {
4186
+ policyConfig.cacheTtlMs = config.redemption.cacheTtlMs;
4187
+ }
4188
+ redemption = new RedemptionService({
4189
+ policyProvider: new PolicyProvider(policyConfig),
4190
+ historyStore: config.redemption.historyStore
4191
+ });
4192
+ }
3523
4193
  const handlersConfig = {
3524
4194
  authService,
3525
4195
  ledger,
@@ -3530,6 +4200,7 @@ function createIssuerService(config) {
3530
4200
  };
3531
4201
  if (feeManager) handlersConfig.feeManager = feeManager;
3532
4202
  if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
4203
+ if (redemption) handlersConfig.redemption = redemption;
3533
4204
  const handlers = new IssuerApiHandlers(handlersConfig);
3534
4205
  if (config.indexer?.autoStart) {
3535
4206
  for (const idx of indexers.values()) {
@@ -3545,7 +4216,8 @@ function createIssuerService(config) {
3545
4216
  fee: feeManager,
3546
4217
  indexers,
3547
4218
  indexer: firstIndexer,
3548
- api: handlers
4219
+ api: handlers,
4220
+ redemption
3549
4221
  };
3550
4222
  }
3551
4223
 
@@ -3709,8 +4381,44 @@ var IssuerStateValidator = class _IssuerStateValidator {
3709
4381
  }
3710
4382
  };
3711
4383
 
4384
+ // src/redemption/memoryHistoryStore.ts
4385
+ var MemoryRedemptionHistoryStore = class {
4386
+ entries = [];
4387
+ async sumRedeemedSince(user, sinceUnixSec, pointTokenAddress) {
4388
+ const userKey = user.toLowerCase();
4389
+ const tokenKey = pointTokenAddress?.toLowerCase() ?? null;
4390
+ let total = 0n;
4391
+ for (const e of this.entries) {
4392
+ if (e.user !== userKey) continue;
4393
+ if (e.unixSec < sinceUnixSec) continue;
4394
+ if (tokenKey !== null && e.pointTokenAddress !== tokenKey) continue;
4395
+ total += e.amountPt;
4396
+ }
4397
+ return total;
4398
+ }
4399
+ async getLastRedeemedAtUnixSec(user, pointTokenAddress) {
4400
+ const userKey = user.toLowerCase();
4401
+ const tokenKey = pointTokenAddress?.toLowerCase() ?? null;
4402
+ let latest = null;
4403
+ for (const e of this.entries) {
4404
+ if (e.user !== userKey) continue;
4405
+ if (tokenKey !== null && e.pointTokenAddress !== tokenKey) continue;
4406
+ if (latest === null || e.unixSec > latest) latest = e.unixSec;
4407
+ }
4408
+ return latest;
4409
+ }
4410
+ async recordRedemption(entry) {
4411
+ this.entries.push({
4412
+ user: entry.user.toLowerCase(),
4413
+ amountPt: entry.amountPt,
4414
+ pointTokenAddress: entry.pointTokenAddress?.toLowerCase() ?? null,
4415
+ unixSec: entry.unixSec
4416
+ });
4417
+ }
4418
+ };
4419
+
3712
4420
  // src/index.ts
3713
- var PAFI_ISSUER_SDK_VERSION = true ? "0.7.8" : "dev";
4421
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.8.0" : "dev";
3714
4422
  export {
3715
4423
  AdapterMisconfiguredError,
3716
4424
  AuthError,
@@ -3720,6 +4428,7 @@ export {
3720
4428
  BundlerRejectedError,
3721
4429
  BurnIndexer,
3722
4430
  ConfigurationError,
4431
+ DEFAULT_REDEMPTION_POLICY,
3723
4432
  DefaultPolicyEngine,
3724
4433
  FeeManager,
3725
4434
  InMemoryCursorStore,
@@ -3729,8 +4438,11 @@ export {
3729
4438
  IssuerStateValidator,
3730
4439
  LockNotFoundError,
3731
4440
  MemoryPendingUserOpStore,
4441
+ MemoryRateLimiter,
4442
+ MemoryRedemptionHistoryStore,
3732
4443
  MemorySessionStore,
3733
4444
  NonceManager,
4445
+ NoopRateLimiter,
3734
4446
  PAFI_ISSUER_SDK_VERSION,
3735
4447
  PAFI_SUBGRAPH_URL,
3736
4448
  PTClaimError,
@@ -3745,8 +4457,12 @@ export {
3745
4457
  PerpDepositError,
3746
4458
  PerpDepositHandler,
3747
4459
  PointIndexer,
4460
+ PolicyProvider,
4461
+ REDEMPTION_HISTORY_WINDOW_SEC,
4462
+ RedemptionService,
3748
4463
  RelayError,
3749
4464
  RelayService,
4465
+ SettlementClient,
3750
4466
  ValidationError,
3751
4467
  authenticateRequest,
3752
4468
  createIssuerService,
@@ -3754,6 +4470,8 @@ export {
3754
4470
  createSdkErrorMapper,
3755
4471
  createSubgraphNativeUsdtQuoter,
3756
4472
  createSubgraphPoolsProvider,
4473
+ defaultPolicyFor,
4474
+ evaluateRedemption,
3757
4475
  handleClaimStatus,
3758
4476
  handleDelegateSubmit,
3759
4477
  handleMobilePrepare,