@pafi-dev/issuer 0.3.0-beta.9 → 0.5.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
@@ -1,201 +1,3 @@
1
- // src/ledger/memoryLedger.ts
2
- import { getAddress } from "viem";
3
- var MemoryPointLedger = class {
4
- balances = /* @__PURE__ */ new Map();
5
- locks = /* @__PURE__ */ new Map();
6
- nextLockId = 1;
7
- now;
8
- constructor(opts = {}) {
9
- this.now = opts.now ?? (() => Date.now());
10
- }
11
- // -------------------------------------------------------------------------
12
- // Read
13
- // -------------------------------------------------------------------------
14
- async getBalance(userAddress, tokenAddress) {
15
- const user = getAddress(userAddress);
16
- const token = normalizeToken(tokenAddress);
17
- this.purgeExpired();
18
- const total = this.balances.get(balanceKey(user, token)) ?? 0n;
19
- const locked = this.lockedTotalFor(user, token);
20
- return total - locked;
21
- }
22
- async getLockedRequests(userAddress, tokenAddress) {
23
- const user = getAddress(userAddress);
24
- const token = normalizeToken(tokenAddress);
25
- this.purgeExpired();
26
- const out = [];
27
- for (const lock of this.locks.values()) {
28
- if (lock.userAddress === user && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
29
- out.push({ ...lock });
30
- }
31
- }
32
- return out;
33
- }
34
- // -------------------------------------------------------------------------
35
- // Write
36
- // -------------------------------------------------------------------------
37
- async creditBalance(userAddress, amount, _reason, tokenAddress) {
38
- if (amount <= 0n) {
39
- throw new Error("MemoryPointLedger: credit amount must be positive");
40
- }
41
- const user = getAddress(userAddress);
42
- const token = normalizeToken(tokenAddress);
43
- const key = balanceKey(user, token);
44
- const current = this.balances.get(key) ?? 0n;
45
- this.balances.set(key, current + amount);
46
- }
47
- async lockForMinting(userAddress, amount, lockDurationMs, tokenAddress) {
48
- if (amount <= 0n) {
49
- throw new Error("MemoryPointLedger: lock amount must be positive");
50
- }
51
- if (lockDurationMs <= 0) {
52
- throw new Error("MemoryPointLedger: lockDurationMs must be positive");
53
- }
54
- const user = getAddress(userAddress);
55
- const token = normalizeToken(tokenAddress);
56
- this.purgeExpired();
57
- const total = this.balances.get(balanceKey(user, token)) ?? 0n;
58
- const alreadyLocked = this.lockedTotalFor(user, token);
59
- const available = total - alreadyLocked;
60
- if (available < amount) {
61
- throw new Error(
62
- `MemoryPointLedger: insufficient balance \u2014 available=${available}, requested=${amount}`
63
- );
64
- }
65
- const lockId = `lock-${this.nextLockId++}`;
66
- const now = this.now();
67
- const lock = {
68
- lockId,
69
- userAddress: user,
70
- amount,
71
- status: "PENDING",
72
- createdAt: now,
73
- expiresAt: now + lockDurationMs
74
- };
75
- if (tokenAddress !== void 0) {
76
- lock.tokenAddress = getAddress(tokenAddress);
77
- }
78
- this.locks.set(lockId, lock);
79
- return lockId;
80
- }
81
- async releaseLock(lockId) {
82
- const lock = this.locks.get(lockId);
83
- if (!lock) return;
84
- if (lock.status === "PENDING") {
85
- this.locks.delete(lockId);
86
- }
87
- }
88
- async deductBalance(userAddress, amount, txHash, tokenAddress) {
89
- if (amount <= 0n) {
90
- throw new Error("MemoryPointLedger: deduct amount must be positive");
91
- }
92
- const user = getAddress(userAddress);
93
- const token = normalizeToken(tokenAddress);
94
- const key = balanceKey(user, token);
95
- const current = this.balances.get(key) ?? 0n;
96
- if (current < amount) {
97
- throw new Error(
98
- `MemoryPointLedger: cannot deduct ${amount} from balance ${current}`
99
- );
100
- }
101
- this.balances.set(key, current - amount);
102
- for (const lock of this.locks.values()) {
103
- if (lock.userAddress === user && lock.status === "PENDING" && lock.amount === amount && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
104
- lock.status = "MINTED";
105
- lock.txHash = txHash;
106
- return;
107
- }
108
- }
109
- }
110
- async updateMintStatus(lockId, status, txHash) {
111
- const lock = this.locks.get(lockId);
112
- if (!lock) {
113
- throw new Error(`MemoryPointLedger: unknown lockId ${lockId}`);
114
- }
115
- lock.status = status;
116
- if (txHash) lock.txHash = txHash;
117
- }
118
- // -------------------------------------------------------------------------
119
- // v1.4 — Reverse flow (PT burn → off-chain credit)
120
- // -------------------------------------------------------------------------
121
- pendingCredits = /* @__PURE__ */ new Map();
122
- nextCreditId = 1;
123
- async reservePendingCredit(userAddress, amount, durationMs, tokenAddress) {
124
- if (amount <= 0n) {
125
- throw new Error(
126
- "MemoryPointLedger: pending credit amount must be positive"
127
- );
128
- }
129
- if (durationMs <= 0) {
130
- throw new Error("MemoryPointLedger: durationMs must be positive");
131
- }
132
- const user = getAddress(userAddress);
133
- const lockId = `credit-${this.nextCreditId++}`;
134
- const now = this.now();
135
- this.pendingCredits.set(lockId, {
136
- lockId,
137
- userAddress: user,
138
- amount,
139
- tokenAddress: tokenAddress !== void 0 ? getAddress(tokenAddress) : void 0,
140
- createdAt: now,
141
- expiresAt: now + durationMs,
142
- status: "PENDING"
143
- });
144
- return lockId;
145
- }
146
- async resolveCreditByBurnTx(lockId, txHash) {
147
- const credit = this.pendingCredits.get(lockId);
148
- if (!credit) {
149
- throw new Error(
150
- `MemoryPointLedger: unknown pending credit lockId ${lockId}`
151
- );
152
- }
153
- if (credit.status === "RESOLVED") {
154
- if (credit.txHash === txHash) return;
155
- throw new Error(
156
- `MemoryPointLedger: credit ${lockId} already resolved with a different txHash`
157
- );
158
- }
159
- const token = normalizeToken(credit.tokenAddress);
160
- const key = balanceKey(credit.userAddress, token);
161
- const current = this.balances.get(key) ?? 0n;
162
- this.balances.set(key, current + credit.amount);
163
- credit.status = "RESOLVED";
164
- credit.txHash = txHash;
165
- }
166
- // -------------------------------------------------------------------------
167
- // Internal helpers
168
- // -------------------------------------------------------------------------
169
- /**
170
- * Auto-expire any PENDING lock past its expiry. Called lazily on every
171
- * read/write so the in-memory state stays self-cleaning without a timer.
172
- */
173
- purgeExpired() {
174
- const now = this.now();
175
- for (const lock of this.locks.values()) {
176
- if (lock.status === "PENDING" && lock.expiresAt <= now) {
177
- lock.status = "EXPIRED";
178
- }
179
- }
180
- }
181
- lockedTotalFor(userAddress, tokenKey) {
182
- let total = 0n;
183
- for (const lock of this.locks.values()) {
184
- if (lock.userAddress === userAddress && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === tokenKey) {
185
- total += lock.amount;
186
- }
187
- }
188
- return total;
189
- }
190
- };
191
- var DEFAULT_TOKEN_KEY = "default";
192
- function normalizeToken(tokenAddress) {
193
- return tokenAddress === void 0 ? DEFAULT_TOKEN_KEY : getAddress(tokenAddress);
194
- }
195
- function balanceKey(user, tokenKey) {
196
- return `${user}|${tokenKey}`;
197
- }
198
-
199
1
  // src/policy/defaultPolicy.ts
200
2
  var DefaultPolicyEngine = class {
201
3
  ledger;
@@ -211,6 +13,13 @@ var DefaultPolicyEngine = class {
211
13
  }
212
14
  if (opts.verifyMintCap) this.verifyMintCap = opts.verifyMintCap;
213
15
  if (opts.resolveIssuer) this.resolveIssuer = opts.resolveIssuer;
16
+ if (!opts.mintingOracleAddress || !opts.provider || !opts.verifyMintCap || !opts.resolveIssuer) {
17
+ if (process.env.NODE_ENV === "production") {
18
+ throw new Error(
19
+ "[PAFI] DefaultPolicyEngine: on-chain MintingOracle cap check is required in production. Configure mintingOracleAddress, provider, verifyMintCap, and resolveIssuer."
20
+ );
21
+ }
22
+ }
214
23
  }
215
24
  async evaluate(request) {
216
25
  if (request.amount <= 0n) {
@@ -247,34 +56,9 @@ var DefaultPolicyEngine = class {
247
56
  }
248
57
  };
249
58
 
250
- // src/signer/privateKeySigner.ts
251
- import { createWalletClient, http } from "viem";
252
- import { privateKeyToAccount } from "viem/accounts";
253
- import {
254
- signMintRequest
255
- } from "@pafi-dev/core";
256
- var PrivateKeySigner = class {
257
- account;
258
- walletClient;
259
- constructor(opts) {
260
- this.account = privateKeyToAccount(opts.privateKey);
261
- this.walletClient = createWalletClient({
262
- account: this.account,
263
- chain: opts.chain,
264
- transport: http(opts.rpcUrl)
265
- });
266
- }
267
- async signMintRequest(domain, message) {
268
- return signMintRequest(this.walletClient, domain, message);
269
- }
270
- async getAddress() {
271
- return this.account.address;
272
- }
273
- };
274
-
275
59
  // src/auth/memorySessionStore.ts
276
60
  import { randomBytes } from "crypto";
277
- import { getAddress as getAddress2 } from "viem";
61
+ import { getAddress } from "viem";
278
62
  var DEFAULT_NONCE_TTL_MS = 5 * 60 * 1e3;
279
63
  var MemorySessionStore = class {
280
64
  nonces = /* @__PURE__ */ new Map();
@@ -284,6 +68,11 @@ var MemorySessionStore = class {
284
68
  nonceTtlMs;
285
69
  now;
286
70
  constructor(opts = {}) {
71
+ if (process.env.NODE_ENV === "production") {
72
+ console.error(
73
+ "[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."
74
+ );
75
+ }
287
76
  this.nonceTtlMs = opts.nonceTtlMs ?? DEFAULT_NONCE_TTL_MS;
288
77
  this.now = opts.now ?? (() => Date.now());
289
78
  }
@@ -310,7 +99,7 @@ var MemorySessionStore = class {
310
99
  this.purgeExpiredSessions();
311
100
  const normalized = {
312
101
  ...session,
313
- userAddress: getAddress2(session.userAddress)
102
+ userAddress: getAddress(session.userAddress)
314
103
  };
315
104
  this.sessions.set(session.tokenId, normalized);
316
105
  }
@@ -328,7 +117,7 @@ var MemorySessionStore = class {
328
117
  this.sessions.delete(tokenId);
329
118
  }
330
119
  async revokeAllSessions(userAddress) {
331
- const key = getAddress2(userAddress);
120
+ const key = getAddress(userAddress);
332
121
  for (const [tokenId, session] of this.sessions.entries()) {
333
122
  if (session.userAddress === key) {
334
123
  this.sessions.delete(tokenId);
@@ -374,7 +163,7 @@ var NonceManager = class {
374
163
  // src/auth/loginVerifier.ts
375
164
  import { randomBytes as randomBytes2 } from "crypto";
376
165
  import { SignJWT, jwtVerify, errors as joseErrors } from "jose";
377
- import { getAddress as getAddress3 } from "viem";
166
+ import { getAddress as getAddress2 } from "viem";
378
167
  import { parseLoginMessage, verifyLoginMessage } from "@pafi-dev/core";
379
168
 
380
169
  // src/auth/errors.ts
@@ -398,8 +187,8 @@ var AuthService = class {
398
187
  nonceManager;
399
188
  now;
400
189
  constructor(config) {
401
- if (!config.jwtSecret || config.jwtSecret.length < 16) {
402
- throw new Error("AuthService: jwtSecret must be at least 16 chars");
190
+ if (!config.jwtSecret || config.jwtSecret.length < 32) {
191
+ throw new Error("AuthService: jwtSecret must be at least 32 characters for HS256 security");
403
192
  }
404
193
  this.sessionStore = config.sessionStore;
405
194
  this.jwtSecret = new TextEncoder().encode(config.jwtSecret);
@@ -428,6 +217,12 @@ var AuthService = class {
428
217
  const msg = err instanceof Error ? err.message : String(err);
429
218
  throw new AuthError("INVALID_MESSAGE", `Could not parse login message: ${msg}`);
430
219
  }
220
+ if (parsed.expirationTime == null) {
221
+ throw new AuthError(
222
+ "INVALID_MESSAGE",
223
+ "login message must include expirationTime"
224
+ );
225
+ }
431
226
  if (parsed.domain !== this.domain) {
432
227
  throw new AuthError(
433
228
  "DOMAIN_MISMATCH",
@@ -464,7 +259,7 @@ var AuthService = class {
464
259
  "Nonce is unknown, expired, or already used"
465
260
  );
466
261
  }
467
- const userAddress = getAddress3(verifyResult.address);
262
+ const userAddress = getAddress2(verifyResult.address);
468
263
  const tokenId = randomBytes2(16).toString("hex");
469
264
  const issuedAt = now;
470
265
  const expiresAt = parseExpiry(issuedAt, this.jwtExpiresIn);
@@ -492,7 +287,11 @@ var AuthService = class {
492
287
  if (payload.jti) {
493
288
  await this.sessionStore.revokeSession(payload.jti);
494
289
  }
495
- } catch {
290
+ } catch (err) {
291
+ const msg = err instanceof Error ? err.message : String(err);
292
+ if (!msg.includes("not found") && !msg.includes("expired")) {
293
+ console.error("[PAFI] AuthService logout: session store error", err);
294
+ }
496
295
  }
497
296
  }
498
297
  /**
@@ -531,7 +330,7 @@ var AuthService = class {
531
330
  throw new AuthError("TOKEN_INVALID", "JWT payload is malformed");
532
331
  }
533
332
  return {
534
- userAddress: getAddress3(userAddress),
333
+ userAddress: getAddress2(userAddress),
535
334
  chainId,
536
335
  tokenId
537
336
  };
@@ -589,7 +388,7 @@ import {
589
388
  import {
590
389
  POINT_TOKEN_V2_ABI,
591
390
  buildPartialUserOperation,
592
- signMintRequest as signMintRequest2
391
+ signMintRequest
593
392
  } from "@pafi-dev/core";
594
393
  var RelayService = class {
595
394
  /**
@@ -624,9 +423,20 @@ var RelayService = class {
624
423
  if (params.deadline <= 0n) {
625
424
  throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
626
425
  }
426
+ const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
427
+ if (params.deadline <= nowSecs) {
428
+ throw new RelayError("ENCODE_FAILED", "prepareMint: deadline is in the past");
429
+ }
430
+ const MAX_DEADLINE_WINDOW = 3600n;
431
+ if (params.deadline > nowSecs + MAX_DEADLINE_WINDOW) {
432
+ throw new RelayError(
433
+ "ENCODE_FAILED",
434
+ "prepareMint: deadline exceeds maximum allowed window (1 hour)"
435
+ );
436
+ }
627
437
  let minterSig;
628
438
  try {
629
- const sig = await signMintRequest2(
439
+ const sig = await signMintRequest(
630
440
  params.issuerSignerWallet,
631
441
  params.domain,
632
442
  {
@@ -672,6 +482,12 @@ var RelayService = class {
672
482
  "prepareMint: feeRecipient required when feeAmount > 0"
673
483
  );
674
484
  }
485
+ if (params.feeRecipient === "0x0000000000000000000000000000000000000000") {
486
+ throw new RelayError(
487
+ "ENCODE_FAILED",
488
+ "prepareMint: feeRecipient must not be zero address"
489
+ );
490
+ }
675
491
  operations.push({
676
492
  target: params.pointTokenAddress,
677
493
  value: 0n,
@@ -770,11 +586,14 @@ function errorMessage(err) {
770
586
  // src/relay/feeManager.ts
771
587
  var DEFAULT_GAS_UNITS = 500000n;
772
588
  var DEFAULT_PREMIUM_BPS = 12e3;
773
- var FeeManager = class {
589
+ var FeeManager = class _FeeManager {
774
590
  provider;
775
591
  gasUnits;
776
592
  gasPremiumBps;
777
593
  quoteNativeToFee;
594
+ cachedFee = null;
595
+ cacheExpiresAt = 0;
596
+ static CACHE_TTL_MS = 1e4;
778
597
  constructor(config) {
779
598
  if (!config.provider) throw new Error("FeeManager: provider required");
780
599
  if (!config.quoteNativeToFee)
@@ -797,10 +616,17 @@ var FeeManager = class {
797
616
  * currency depends on how the caller wired `quoteNativeToFee`.
798
617
  */
799
618
  async estimateGasFee() {
619
+ const now = Date.now();
620
+ if (this.cachedFee !== null && now < this.cacheExpiresAt) {
621
+ return this.cachedFee;
622
+ }
800
623
  const gasPrice = await this.provider.getGasPrice();
801
624
  const nativeCost = gasPrice * this.gasUnits;
802
625
  const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
803
- return this.quoteNativeToFee(withPremium);
626
+ const fee = await this.quoteNativeToFee(withPremium);
627
+ this.cachedFee = fee;
628
+ this.cacheExpiresAt = now + _FeeManager.CACHE_TTL_MS;
629
+ return fee;
804
630
  }
805
631
  };
806
632
 
@@ -816,7 +642,7 @@ var InMemoryCursorStore = class {
816
642
  };
817
643
 
818
644
  // src/indexer/pointIndexer.ts
819
- import { getAddress as getAddress4, parseAbiItem } from "viem";
645
+ import { getAddress as getAddress3, parseAbiItem } from "viem";
820
646
  var TRANSFER_EVENT = parseAbiItem(
821
647
  "event Transfer(address indexed from, address indexed to, uint256 value)"
822
648
  );
@@ -887,7 +713,8 @@ var PointIndexer = class {
887
713
  return;
888
714
  }
889
715
  await this.processBlockRange(from, safeHead);
890
- } catch {
716
+ } catch (err) {
717
+ console.error("[PAFI] PointIndexer tick error:", err);
891
718
  }
892
719
  this.scheduleNext();
893
720
  }
@@ -937,10 +764,10 @@ var PointIndexer = class {
937
764
  for (const log of logs) {
938
765
  const args = log.args;
939
766
  if (!args.from || !args.to || args.value === void 0) continue;
940
- if (getAddress4(args.from) !== ZERO_ADDRESS) continue;
767
+ if (getAddress3(args.from) !== ZERO_ADDRESS) continue;
941
768
  if (log.blockNumber === null || log.transactionHash === null) continue;
942
769
  out.push({
943
- to: getAddress4(args.to),
770
+ to: getAddress3(args.to),
944
771
  amount: args.value,
945
772
  blockNumber: log.blockNumber,
946
773
  txHash: log.transactionHash,
@@ -996,7 +823,7 @@ function pickMatchingLock(locks, amount) {
996
823
  }
997
824
 
998
825
  // src/indexer/burnIndexer.ts
999
- import { getAddress as getAddress5, parseAbiItem as parseAbiItem2 } from "viem";
826
+ import { getAddress as getAddress4, parseAbiItem as parseAbiItem2 } from "viem";
1000
827
  var TRANSFER_EVENT2 = parseAbiItem2(
1001
828
  "event Transfer(address indexed from, address indexed to, uint256 value)"
1002
829
  );
@@ -1013,18 +840,7 @@ var BurnIndexer = class {
1013
840
  confirmations;
1014
841
  batchSize;
1015
842
  pollIntervalMs;
1016
- /**
1017
- * Caller-supplied matcher. Return the lockId to resolve for a given
1018
- * burn event, or `undefined` to skip. Runs synchronously via the
1019
- * ledger's query path.
1020
- *
1021
- * Default: try `ledger.resolveCreditByBurnTx` keyed on a synthetic
1022
- * lock id `burn-${from}-${amount}` — the in-memory ledger assigns
1023
- * incrementing IDs so callers with the memory ledger must provide a
1024
- * custom matcher. Real DB-backed ledgers override this to JOIN on
1025
- * their `pending_credits` table.
1026
- */
1027
- matchLockId = async () => void 0;
843
+ matchLockId;
1028
844
  running = false;
1029
845
  timer;
1030
846
  constructor(config) {
@@ -1042,6 +858,12 @@ var BurnIndexer = class {
1042
858
  );
1043
859
  this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE2));
1044
860
  this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
861
+ if (!config.matchLockId) {
862
+ throw new Error(
863
+ "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."
864
+ );
865
+ }
866
+ this.matchLockId = config.matchLockId;
1045
867
  }
1046
868
  start() {
1047
869
  if (this.running) return;
@@ -1071,7 +893,8 @@ var BurnIndexer = class {
1071
893
  return;
1072
894
  }
1073
895
  await this.processBlockRange(from, safeHead);
1074
- } catch {
896
+ } catch (err) {
897
+ console.error("[PAFI] BurnIndexer tick error:", err);
1075
898
  }
1076
899
  this.scheduleNext();
1077
900
  }
@@ -1116,10 +939,10 @@ var BurnIndexer = class {
1116
939
  for (const log of logs) {
1117
940
  const args = log.args;
1118
941
  if (!args.from || !args.to || args.value === void 0) continue;
1119
- if (getAddress5(args.to) !== ZERO_ADDRESS2) continue;
942
+ if (getAddress4(args.to) !== ZERO_ADDRESS2) continue;
1120
943
  if (log.blockNumber === null || log.transactionHash === null) continue;
1121
944
  out.push({
1122
- from: getAddress5(args.from),
945
+ from: getAddress4(args.from),
1123
946
  amount: args.value,
1124
947
  blockNumber: log.blockNumber,
1125
948
  txHash: log.transactionHash,
@@ -1134,20 +957,27 @@ var BurnIndexer = class {
1134
957
  * log + skip.
1135
958
  */
1136
959
  async finalize(evt) {
960
+ const txHash = evt.txHash;
1137
961
  const lockId = await this.matchLockId(evt);
1138
- if (!lockId) return;
962
+ if (lockId === void 0) {
963
+ console.warn(
964
+ "[PAFI] BurnIndexer: matchLockId returned undefined for burn tx " + txHash + ". This burn will NOT be credited. Implement matchLockId to map burn events to lock IDs."
965
+ );
966
+ return;
967
+ }
1139
968
  if (!this.ledger.resolveCreditByBurnTx) {
1140
969
  return;
1141
970
  }
1142
971
  try {
1143
972
  await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);
1144
- } catch {
973
+ } catch (err) {
974
+ console.error("[PAFI] BurnIndexer finalize error \u2014 credit may be lost:", err);
1145
975
  }
1146
976
  }
1147
977
  };
1148
978
 
1149
979
  // src/api/handlers.ts
1150
- import { getAddress as getAddress6 } from "viem";
980
+ import { getAddress as getAddress5 } from "viem";
1151
981
  import {
1152
982
  getMintRequestNonce,
1153
983
  getPointTokenBalance,
@@ -1165,15 +995,12 @@ var IssuerApiHandlers = class {
1165
995
  * validate the request's `pointTokenAddress` against this set.
1166
996
  */
1167
997
  supportedTokens;
1168
- /** First supported token — used as default when a handler doesn't
1169
- * receive a `pointTokenAddress` in the request (shouldn't happen in
1170
- * practice, but keeps type-narrowing happy). */
1171
- defaultToken;
1172
998
  chainId;
1173
999
  contracts;
1174
1000
  pafiWebUrl;
1175
1001
  feeManager;
1176
1002
  poolsProvider;
1003
+ claim;
1177
1004
  constructor(config) {
1178
1005
  this.authService = config.authService;
1179
1006
  this.ledger = config.ledger;
@@ -1184,14 +1011,14 @@ var IssuerApiHandlers = class {
1184
1011
  "IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
1185
1012
  );
1186
1013
  }
1187
- const normalized = raw.map((a) => getAddress6(a));
1014
+ const normalized = raw.map((a) => getAddress5(a));
1188
1015
  this.supportedTokens = new Set(normalized);
1189
- this.defaultToken = normalized[0];
1190
1016
  this.chainId = config.chainId;
1191
1017
  this.contracts = config.contracts;
1192
1018
  if (config.pafiWebUrl) this.pafiWebUrl = config.pafiWebUrl;
1193
1019
  if (config.feeManager) this.feeManager = config.feeManager;
1194
1020
  if (config.poolsProvider) this.poolsProvider = config.poolsProvider;
1021
+ if (config.claim) this.claim = config.claim;
1195
1022
  }
1196
1023
  // =========================================================================
1197
1024
  // Public handlers (no auth required)
@@ -1206,6 +1033,12 @@ var IssuerApiHandlers = class {
1206
1033
  if (!body || typeof body.message !== "string" || body.message.length === 0 || typeof body.signature !== "string" || body.signature.length <= 2) {
1207
1034
  throw new Error("handleLogin: message and signature are required");
1208
1035
  }
1036
+ if (body.message.length > 4096) {
1037
+ throw new Error("message too long");
1038
+ }
1039
+ if (body.signature.length > 260) {
1040
+ throw new Error("signature too long");
1041
+ }
1209
1042
  const result = await this.authService.login(body.message, body.signature);
1210
1043
  return {
1211
1044
  token: result.token,
@@ -1220,9 +1053,12 @@ var IssuerApiHandlers = class {
1220
1053
  * needs to build EIP-712 messages and interact with on-chain.
1221
1054
  */
1222
1055
  async handleConfig(chainId) {
1056
+ if (!Number.isInteger(chainId) || chainId <= 0) {
1057
+ throw new Error("invalid chainId");
1058
+ }
1223
1059
  if (chainId !== this.chainId) {
1224
1060
  throw new Error(
1225
- `handleConfig: unsupported chainId ${chainId}, issuer is configured for ${this.chainId}`
1061
+ `handleConfig: unsupported chainId ${chainId}`
1226
1062
  );
1227
1063
  }
1228
1064
  const contracts = {
@@ -1285,14 +1121,14 @@ var IssuerApiHandlers = class {
1285
1121
  `handleUser: unsupported chainId ${request.chainId}`
1286
1122
  );
1287
1123
  }
1288
- const normalizedAuthed = getAddress6(userAddress);
1289
- const normalizedRequest = getAddress6(request.userAddress);
1124
+ const normalizedAuthed = getAddress5(userAddress);
1125
+ const normalizedRequest = getAddress5(request.userAddress);
1290
1126
  if (normalizedAuthed !== normalizedRequest) {
1291
1127
  throw new Error(
1292
1128
  "handleUser: request userAddress must match authenticated user"
1293
1129
  );
1294
1130
  }
1295
- const pointToken = getAddress6(request.pointTokenAddress);
1131
+ const pointToken = getAddress5(request.pointTokenAddress);
1296
1132
  if (!this.supportedTokens.has(pointToken)) {
1297
1133
  throw new Error(
1298
1134
  `handleUser: unsupported pointToken ${pointToken}`
@@ -1335,19 +1171,32 @@ var IssuerApiHandlers = class {
1335
1171
  `handleBuildConsentTypedData: unsupported chainId ${request.chainId}`
1336
1172
  );
1337
1173
  }
1338
- const pointToken = getAddress6(request.pointTokenAddress);
1174
+ const pointToken = getAddress5(request.pointTokenAddress);
1339
1175
  if (!this.supportedTokens.has(pointToken)) {
1340
1176
  throw new Error(
1341
1177
  `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
1342
1178
  );
1343
1179
  }
1180
+ const consent = request.receiverConsent;
1181
+ if (getAddress5(consent.originalReceiver) !== getAddress5(userAddress)) {
1182
+ throw new Error(
1183
+ "handleBuildConsentTypedData: receiverConsent.originalReceiver must match authenticated user"
1184
+ );
1185
+ }
1186
+ if (consent.amount <= 0n) {
1187
+ throw new Error("handleBuildConsentTypedData: amount must be positive");
1188
+ }
1189
+ const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
1190
+ if (consent.deadline <= nowSecs) {
1191
+ throw new Error("handleBuildConsentTypedData: deadline is in the past");
1192
+ }
1344
1193
  const name = await getTokenName(this.provider, pointToken);
1345
1194
  const domain = {
1346
1195
  name,
1347
1196
  verifyingContract: pointToken,
1348
1197
  chainId: this.chainId
1349
1198
  };
1350
- const typedData = buildReceiverConsentTypedData(domain, request.receiverConsent);
1199
+ const typedData = buildReceiverConsentTypedData(domain, consent);
1351
1200
  return {
1352
1201
  typedData: {
1353
1202
  domain: typedData.domain,
@@ -1357,11 +1206,96 @@ var IssuerApiHandlers = class {
1357
1206
  }
1358
1207
  };
1359
1208
  }
1209
+ /**
1210
+ * `POST /claim`
1211
+ *
1212
+ * Policy gate + ledger lock + MintRequest signing in a single atomic
1213
+ * step. Returns an unsigned UserOp the frontend attaches paymaster data
1214
+ * to and submits via EIP-7702 + Bundler.
1215
+ *
1216
+ * Order of operations:
1217
+ * 1. Validate request fields.
1218
+ * 2. policy.evaluate() — throws if denied; cannot be bypassed.
1219
+ * 3. ledger.lockForMinting() — reserves the balance.
1220
+ * 4. Read on-chain mintRequestNonce + token name in parallel.
1221
+ * 5. relayService.prepareMint() — sign MintRequest + encode UserOp.
1222
+ * 6. On any error after step 3, release the lock before re-throwing.
1223
+ */
1224
+ async handleClaim(userAddress, request) {
1225
+ if (!this.claim) {
1226
+ throw new Error("handleClaim: claim is not configured on this issuer");
1227
+ }
1228
+ if (request.chainId !== this.chainId) {
1229
+ throw new Error(`handleClaim: unsupported chainId ${request.chainId}`);
1230
+ }
1231
+ const pointToken = getAddress5(request.pointTokenAddress);
1232
+ if (!this.supportedTokens.has(pointToken)) {
1233
+ throw new Error(`handleClaim: unsupported pointToken ${pointToken}`);
1234
+ }
1235
+ if (request.amount <= 0n) {
1236
+ throw new Error("handleClaim: amount must be positive");
1237
+ }
1238
+ const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
1239
+ if (request.deadline <= nowSecs) {
1240
+ throw new Error("handleClaim: deadline is in the past");
1241
+ }
1242
+ const { policy, relayService, issuerSignerWallet, batchExecutorAddress } = this.claim;
1243
+ const lockDurationMs = this.claim.lockDurationMs ?? 15 * 60 * 1e3;
1244
+ const normalizedUser = getAddress5(userAddress);
1245
+ const decision = await policy.evaluate({
1246
+ userAddress: normalizedUser,
1247
+ amount: request.amount,
1248
+ pointTokenAddress: pointToken,
1249
+ chainId: this.chainId
1250
+ });
1251
+ if (!decision.approved) {
1252
+ throw new Error(`handleClaim: policy denied \u2014 ${decision.reason ?? "no reason given"}`);
1253
+ }
1254
+ const lockId = await this.ledger.lockForMinting(
1255
+ normalizedUser,
1256
+ request.amount,
1257
+ lockDurationMs,
1258
+ pointToken
1259
+ );
1260
+ try {
1261
+ const [mintRequestNonce, tokenName] = await Promise.all([
1262
+ getMintRequestNonce(this.provider, pointToken, normalizedUser),
1263
+ getTokenName(this.provider, pointToken)
1264
+ ]);
1265
+ const domain = {
1266
+ name: tokenName,
1267
+ verifyingContract: pointToken,
1268
+ chainId: this.chainId
1269
+ };
1270
+ const userOp = await relayService.prepareMint({
1271
+ userAddress: normalizedUser,
1272
+ aaNonce: request.aaNonce,
1273
+ batchExecutorAddress,
1274
+ pointTokenAddress: pointToken,
1275
+ amount: request.amount,
1276
+ issuerSignerWallet,
1277
+ domain,
1278
+ mintRequestNonce,
1279
+ deadline: request.deadline,
1280
+ feeAmount: request.feeAmount,
1281
+ feeRecipient: request.feeRecipient
1282
+ });
1283
+ return {
1284
+ lockId,
1285
+ userOp,
1286
+ expiresInSeconds: Math.floor(lockDurationMs / 1e3)
1287
+ };
1288
+ } catch (err) {
1289
+ await this.ledger.releaseLock(lockId).catch(() => {
1290
+ });
1291
+ throw err;
1292
+ }
1293
+ }
1360
1294
  };
1361
1295
 
1362
1296
  // src/api/handlers/ptRedeemHandler.ts
1363
- import { getAddress as getAddress7 } from "viem";
1364
- import { signBurnRequest, POINT_TOKEN_V2_ABI as POINT_TOKEN_V2_ABI2 } from "@pafi-dev/core";
1297
+ import { getAddress as getAddress6 } from "viem";
1298
+ import { signBurnRequest, POINT_TOKEN_V2_ABI as POINT_TOKEN_V2_ABI2, getPointTokenBalance as getPointTokenBalance2 } from "@pafi-dev/core";
1365
1299
  var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
1366
1300
  var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
1367
1301
  var PTRedeemError = class extends Error {
@@ -1400,16 +1334,25 @@ var PTRedeemHandler = class {
1400
1334
  this.ledger = config.ledger;
1401
1335
  this.relayService = config.relayService;
1402
1336
  this.provider = config.provider;
1403
- this.pointTokenAddress = getAddress7(config.pointTokenAddress);
1404
- this.batchExecutorAddress = getAddress7(config.batchExecutorAddress);
1337
+ this.pointTokenAddress = getAddress6(config.pointTokenAddress);
1338
+ this.batchExecutorAddress = getAddress6(config.batchExecutorAddress);
1405
1339
  this.chainId = config.chainId;
1406
1340
  this.domain = config.domain;
1407
1341
  this.burnerSignerWallet = config.burnerSignerWallet;
1342
+ if (this.burnerSignerWallet?.account?.type === "local") {
1343
+ console.warn("[PAFI] PTRedeemHandler: burnerSignerWallet uses a local (private key) account. Use a KMS-backed signer in production.");
1344
+ }
1408
1345
  this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
1409
1346
  this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
1410
1347
  this.now = config.now ?? (() => Date.now());
1411
1348
  }
1412
1349
  async handle(request) {
1350
+ if (getAddress6(request.authenticatedAddress) !== getAddress6(request.userAddress)) {
1351
+ throw new PTRedeemError(
1352
+ "UNAUTHORIZED",
1353
+ `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
1354
+ );
1355
+ }
1413
1356
  if (request.amount <= 0n) {
1414
1357
  throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
1415
1358
  }
@@ -1427,6 +1370,17 @@ var PTRedeemHandler = class {
1427
1370
  `failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
1428
1371
  );
1429
1372
  }
1373
+ const onChainBalance = await getPointTokenBalance2(
1374
+ this.provider,
1375
+ this.pointTokenAddress,
1376
+ request.userAddress
1377
+ );
1378
+ if (onChainBalance < request.amount) {
1379
+ throw new PTRedeemError(
1380
+ "INVALID_AMOUNT",
1381
+ `insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
1382
+ );
1383
+ }
1430
1384
  const deadline = BigInt(
1431
1385
  Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
1432
1386
  );
@@ -1480,8 +1434,8 @@ var PTRedeemHandler = class {
1480
1434
  };
1481
1435
 
1482
1436
  // src/api/handlers/topUpRedemptionHandler.ts
1483
- import { getAddress as getAddress8 } from "viem";
1484
- import { getPointTokenBalance as getPointTokenBalance2 } from "@pafi-dev/core";
1437
+ import { getAddress as getAddress7 } from "viem";
1438
+ import { getPointTokenBalance as getPointTokenBalance3 } from "@pafi-dev/core";
1485
1439
  var TopUpRedemptionError = class extends Error {
1486
1440
  constructor(code, message) {
1487
1441
  super(message);
@@ -1499,9 +1453,15 @@ var TopUpRedemptionHandler = class {
1499
1453
  this.ledger = config.ledger;
1500
1454
  this.ptRedeemHandler = config.ptRedeemHandler;
1501
1455
  this.provider = config.provider;
1502
- this.pointTokenAddress = getAddress8(config.pointTokenAddress);
1456
+ this.pointTokenAddress = getAddress7(config.pointTokenAddress);
1503
1457
  }
1504
1458
  async handle(request) {
1459
+ if (getAddress7(request.authenticatedAddress) !== getAddress7(request.userAddress)) {
1460
+ throw new TopUpRedemptionError(
1461
+ "UNAUTHORIZED",
1462
+ `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
1463
+ );
1464
+ }
1505
1465
  const offChainBalance = await this.ledger.getBalance(
1506
1466
  request.userAddress,
1507
1467
  this.pointTokenAddress
@@ -1510,7 +1470,7 @@ var TopUpRedemptionHandler = class {
1510
1470
  return { action: "NO_TOP_UP_NEEDED", offChainBalance };
1511
1471
  }
1512
1472
  const shortfall = request.requiredAmount - offChainBalance;
1513
- const onChainBalance = await getPointTokenBalance2(
1473
+ const onChainBalance = await getPointTokenBalance3(
1514
1474
  this.provider,
1515
1475
  this.pointTokenAddress,
1516
1476
  request.userAddress
@@ -1524,6 +1484,7 @@ var TopUpRedemptionHandler = class {
1524
1484
  };
1525
1485
  }
1526
1486
  const redeem = await this.ptRedeemHandler.handle({
1487
+ authenticatedAddress: request.authenticatedAddress,
1527
1488
  userAddress: request.userAddress,
1528
1489
  amount: shortfall,
1529
1490
  aaNonce: request.aaNonce
@@ -1537,6 +1498,7 @@ var TopUpRedemptionHandler = class {
1537
1498
  };
1538
1499
 
1539
1500
  // src/pools/subgraphPoolsProvider.ts
1501
+ import { isAddress } from "viem";
1540
1502
  var DEFAULT_CACHE_TTL_MS = 3e4;
1541
1503
  var POOL_QUERY = `
1542
1504
  query GetPoolForPointToken($id: ID!) {
@@ -1559,6 +1521,19 @@ function createSubgraphPoolsProvider(config) {
1559
1521
  "createSubgraphPoolsProvider: subgraphUrl is required"
1560
1522
  );
1561
1523
  }
1524
+ try {
1525
+ const parsed = new URL(config.subgraphUrl);
1526
+ if (process.env.NODE_ENV === "production" && parsed.protocol !== "https:") {
1527
+ throw new Error("subgraphUrl must use HTTPS in production");
1528
+ }
1529
+ } catch (err) {
1530
+ if (err instanceof TypeError) {
1531
+ throw new Error(
1532
+ `subgraphPoolsProvider: invalid subgraphUrl: ${config.subgraphUrl}`
1533
+ );
1534
+ }
1535
+ throw err;
1536
+ }
1562
1537
  const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
1563
1538
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
1564
1539
  const now = config.now ?? (() => Date.now());
@@ -1627,6 +1602,26 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress)
1627
1602
  return [];
1628
1603
  }
1629
1604
  const { pool } = token;
1605
+ if (!isAddress(pool.hooks)) {
1606
+ console.error(
1607
+ "[PAFI] SubgraphPoolsProvider: invalid hooks address in response:",
1608
+ pool.hooks,
1609
+ "\u2014 skipping pool"
1610
+ );
1611
+ return [];
1612
+ }
1613
+ if (!isAddress(pool.token0.id) || !isAddress(pool.token1.id)) {
1614
+ console.error(
1615
+ "[PAFI] SubgraphPoolsProvider: invalid token address in response \u2014 skipping pool"
1616
+ );
1617
+ return [];
1618
+ }
1619
+ if (!Number.isFinite(Number(pool.feeTier)) || !Number.isFinite(Number(pool.tickSpacing))) {
1620
+ console.error(
1621
+ "[PAFI] SubgraphPoolsProvider: invalid feeTier/tickSpacing \u2014 skipping pool"
1622
+ );
1623
+ return [];
1624
+ }
1630
1625
  const [currency0, currency1] = sortCurrencies(
1631
1626
  pool.token0.id,
1632
1627
  pool.token1.id
@@ -1662,6 +1657,19 @@ function createSubgraphNativeUsdtQuoter(config) {
1662
1657
  "createSubgraphNativeUsdtQuoter: subgraphUrl is required"
1663
1658
  );
1664
1659
  }
1660
+ try {
1661
+ const parsed = new URL(config.subgraphUrl);
1662
+ if (process.env.NODE_ENV === "production" && parsed.protocol !== "https:") {
1663
+ throw new Error("subgraphUrl must use HTTPS in production");
1664
+ }
1665
+ } catch (err) {
1666
+ if (err instanceof TypeError) {
1667
+ throw new Error(
1668
+ `subgraphPoolsProvider: invalid subgraphUrl: ${config.subgraphUrl}`
1669
+ );
1670
+ }
1671
+ throw err;
1672
+ }
1665
1673
  const usdtDecimals = config.usdtDecimals ?? DEFAULT_USDT_DECIMALS;
1666
1674
  const nativeDecimals = config.nativeDecimals ?? DEFAULT_NATIVE_DECIMALS;
1667
1675
  const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
@@ -1738,6 +1746,14 @@ async function fetchEthPriceFromSubgraph(fetchImpl, subgraphUrl) {
1738
1746
  );
1739
1747
  return null;
1740
1748
  }
1749
+ const MIN_REASONABLE_ETH_PRICE = 100;
1750
+ const MAX_REASONABLE_ETH_PRICE = 1e5;
1751
+ if (parsed < MIN_REASONABLE_ETH_PRICE || parsed > MAX_REASONABLE_ETH_PRICE) {
1752
+ console.warn(
1753
+ `[PAFI] SubgraphNativeUsdtQuoter: ETH/USD price ${parsed} is outside reasonable range. Using fallback.`
1754
+ );
1755
+ return null;
1756
+ }
1741
1757
  return parsed;
1742
1758
  }
1743
1759
  function toUsdtPerNative(priceFloat, usdtDecimals) {
@@ -1748,7 +1764,7 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
1748
1764
  }
1749
1765
 
1750
1766
  // src/balance/balanceAggregator.ts
1751
- import { getPointTokenBalance as getPointTokenBalance3 } from "@pafi-dev/core";
1767
+ import { getPointTokenBalance as getPointTokenBalance4 } from "@pafi-dev/core";
1752
1768
  var BalanceAggregator = class {
1753
1769
  provider;
1754
1770
  ledger;
@@ -1769,7 +1785,7 @@ var BalanceAggregator = class {
1769
1785
  async getCombinedBalance(user, pointToken) {
1770
1786
  const [offChain, onChain] = await Promise.all([
1771
1787
  this.ledger.getBalance(user, pointToken),
1772
- getPointTokenBalance3(this.provider, pointToken, user)
1788
+ getPointTokenBalance4(this.provider, pointToken, user)
1773
1789
  ]);
1774
1790
  return {
1775
1791
  offChain,
@@ -1828,12 +1844,104 @@ var PafiBackendError = class extends Error {
1828
1844
  }
1829
1845
  };
1830
1846
 
1847
+ // src/pafi-backend/client.ts
1848
+ function serializeBigInt(_key, value) {
1849
+ return typeof value === "bigint" ? value.toString(10) : value;
1850
+ }
1851
+ function sleep(ms) {
1852
+ return new Promise((resolve) => setTimeout(resolve, ms));
1853
+ }
1854
+ var PafiBackendClient = class {
1855
+ config;
1856
+ constructor(config) {
1857
+ if (!config.url) throw new Error("PafiBackendClient: url is required");
1858
+ if (!config.issuerId) throw new Error("PafiBackendClient: issuerId is required");
1859
+ this.config = config;
1860
+ }
1861
+ async requestSponsorship(request) {
1862
+ const maxAttempts = this.config.retry?.maxAttempts ?? 1;
1863
+ const initialDelayMs = this.config.retry?.initialDelayMs ?? 100;
1864
+ const maxDelayMs = this.config.retry?.maxDelayMs ?? 1e4;
1865
+ const maxRetryAfterMs = this.config.retry?.maxRetryAfterMs;
1866
+ let lastError;
1867
+ let delay = initialDelayMs;
1868
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1869
+ try {
1870
+ return await this._doRequest(request);
1871
+ } catch (err) {
1872
+ if (!(err instanceof PafiBackendError)) throw err;
1873
+ lastError = err;
1874
+ if (attempt >= maxAttempts) break;
1875
+ if (!err.safeToRetry) break;
1876
+ const retryAfterMs = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : void 0;
1877
+ if (maxRetryAfterMs !== void 0 && retryAfterMs !== void 0 && retryAfterMs > maxRetryAfterMs) {
1878
+ break;
1879
+ }
1880
+ await sleep(retryAfterMs ?? delay);
1881
+ delay = Math.min(delay * 2, maxDelayMs);
1882
+ }
1883
+ }
1884
+ throw lastError;
1885
+ }
1886
+ async _doRequest(request) {
1887
+ const fetchFn = this.config.fetchImpl ?? fetch;
1888
+ const url = `${this.config.url}/paymaster/sponsor`;
1889
+ const body = JSON.stringify(request, serializeBigInt);
1890
+ let response;
1891
+ try {
1892
+ response = await fetchFn(url, {
1893
+ method: "POST",
1894
+ headers: {
1895
+ "Content-Type": "application/json",
1896
+ Authorization: `Bearer ${this.config.apiKey}`,
1897
+ "X-Issuer-Id": this.config.issuerId
1898
+ },
1899
+ body
1900
+ });
1901
+ } catch (err) {
1902
+ throw new PafiBackendError(
1903
+ "NETWORK_ERROR",
1904
+ `Network error: ${err instanceof Error ? err.message : String(err)}`,
1905
+ 0
1906
+ );
1907
+ }
1908
+ const text = await response.text();
1909
+ let json = {};
1910
+ try {
1911
+ json = JSON.parse(text);
1912
+ } catch {
1913
+ }
1914
+ if (!response.ok) {
1915
+ const code = json.code ?? "INTERNAL_ERROR";
1916
+ const message = json.message ?? `HTTP ${response.status}`;
1917
+ const retryAfter = typeof json.retryAfter === "number" ? json.retryAfter : void 0;
1918
+ const safeToRetry = typeof json.safeToRetry === "boolean" ? json.safeToRetry : void 0;
1919
+ throw new PafiBackendError(code, message, response.status, json, {
1920
+ retryAfter,
1921
+ safeToRetry
1922
+ });
1923
+ }
1924
+ return {
1925
+ paymaster: json.paymaster,
1926
+ paymasterData: json.paymasterData,
1927
+ paymasterVerificationGasLimit: BigInt(
1928
+ json.paymasterVerificationGasLimit
1929
+ ),
1930
+ paymasterPostOpGasLimit: BigInt(json.paymasterPostOpGasLimit),
1931
+ expiresAt: json.expiresAt
1932
+ };
1933
+ }
1934
+ };
1935
+
1831
1936
  // src/config.ts
1832
- import { getAddress as getAddress9 } from "viem";
1937
+ import { getAddress as getAddress8 } from "viem";
1833
1938
  function createIssuerService(config) {
1834
1939
  if (!config.provider) {
1835
1940
  throw new Error("createIssuerService: provider is required");
1836
1941
  }
1942
+ if (!config.ledger) {
1943
+ throw new Error("createIssuerService: ledger is required");
1944
+ }
1837
1945
  if (!config.auth?.jwtSecret) {
1838
1946
  throw new Error("createIssuerService: auth.jwtSecret is required");
1839
1947
  }
@@ -1846,8 +1954,8 @@ function createIssuerService(config) {
1846
1954
  "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
1847
1955
  );
1848
1956
  }
1849
- const tokenAddresses = rawAddresses.map((a) => getAddress9(a));
1850
- const ledger = config.ledger ?? new MemoryPointLedger();
1957
+ const tokenAddresses = rawAddresses.map((a) => getAddress8(a));
1958
+ const ledger = config.ledger;
1851
1959
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
1852
1960
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
1853
1961
  const authServiceConfig = {
@@ -1903,6 +2011,15 @@ function createIssuerService(config) {
1903
2011
  };
1904
2012
  if (feeManager) handlersConfig.feeManager = feeManager;
1905
2013
  if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
2014
+ if (config.claim) {
2015
+ handlersConfig.claim = {
2016
+ policy,
2017
+ relayService,
2018
+ issuerSignerWallet: config.claim.issuerSignerWallet,
2019
+ batchExecutorAddress: config.claim.batchExecutorAddress,
2020
+ lockDurationMs: config.claim.lockDurationMs
2021
+ };
2022
+ }
1906
2023
  const handlers = new IssuerApiHandlers(handlersConfig);
1907
2024
  if (config.indexer?.autoStart) {
1908
2025
  for (const idx of indexers.values()) {
@@ -1910,20 +2027,20 @@ function createIssuerService(config) {
1910
2027
  }
1911
2028
  }
1912
2029
  return {
1913
- authService,
1914
- sessionStore,
2030
+ auth: authService,
2031
+ session: sessionStore,
1915
2032
  ledger,
1916
2033
  policy,
1917
- relayService,
1918
- feeManager,
2034
+ relay: relayService,
2035
+ fee: feeManager,
1919
2036
  indexers,
1920
2037
  indexer: firstIndexer,
1921
- handlers
2038
+ api: handlers
1922
2039
  };
1923
2040
  }
1924
2041
 
1925
2042
  // src/index.ts
1926
- var PAFI_ISSUER_SDK_VERSION = "0.1.0";
2043
+ var PAFI_ISSUER_SDK_VERSION = "0.4.0";
1927
2044
  export {
1928
2045
  AuthError,
1929
2046
  AuthService,
@@ -1933,15 +2050,14 @@ export {
1933
2050
  FeeManager,
1934
2051
  InMemoryCursorStore,
1935
2052
  IssuerApiHandlers,
1936
- MemoryPointLedger,
1937
2053
  MemorySessionStore,
1938
2054
  NonceManager,
1939
2055
  PAFI_ISSUER_SDK_VERSION,
1940
2056
  PTRedeemError,
1941
2057
  PTRedeemHandler,
2058
+ PafiBackendClient,
1942
2059
  PafiBackendError,
1943
2060
  PointIndexer,
1944
- PrivateKeySigner,
1945
2061
  RelayError,
1946
2062
  RelayService,
1947
2063
  TopUpRedemptionError,