@pafi-dev/issuer 0.1.2 → 0.3.0-alpha.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
@@ -11,19 +11,21 @@ var MemoryPointLedger = class {
11
11
  // -------------------------------------------------------------------------
12
12
  // Read
13
13
  // -------------------------------------------------------------------------
14
- async getBalance(userAddress) {
15
- const key = getAddress(userAddress);
14
+ async getBalance(userAddress, tokenAddress) {
15
+ const user = getAddress(userAddress);
16
+ const token = normalizeToken(tokenAddress);
16
17
  this.purgeExpired();
17
- const total = this.balances.get(key) ?? 0n;
18
- const locked = this.lockedTotalFor(key);
18
+ const total = this.balances.get(balanceKey(user, token)) ?? 0n;
19
+ const locked = this.lockedTotalFor(user, token);
19
20
  return total - locked;
20
21
  }
21
- async getLockedRequests(userAddress) {
22
- const key = getAddress(userAddress);
22
+ async getLockedRequests(userAddress, tokenAddress) {
23
+ const user = getAddress(userAddress);
24
+ const token = normalizeToken(tokenAddress);
23
25
  this.purgeExpired();
24
26
  const out = [];
25
27
  for (const lock of this.locks.values()) {
26
- if (lock.userAddress === key && lock.status === "PENDING") {
28
+ if (lock.userAddress === user && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
27
29
  out.push({ ...lock });
28
30
  }
29
31
  }
@@ -32,25 +34,28 @@ var MemoryPointLedger = class {
32
34
  // -------------------------------------------------------------------------
33
35
  // Write
34
36
  // -------------------------------------------------------------------------
35
- async creditBalance(userAddress, amount, _reason) {
37
+ async creditBalance(userAddress, amount, _reason, tokenAddress) {
36
38
  if (amount <= 0n) {
37
39
  throw new Error("MemoryPointLedger: credit amount must be positive");
38
40
  }
39
- const key = getAddress(userAddress);
41
+ const user = getAddress(userAddress);
42
+ const token = normalizeToken(tokenAddress);
43
+ const key = balanceKey(user, token);
40
44
  const current = this.balances.get(key) ?? 0n;
41
45
  this.balances.set(key, current + amount);
42
46
  }
43
- async lockForMinting(userAddress, amount, lockDurationMs) {
47
+ async lockForMinting(userAddress, amount, lockDurationMs, tokenAddress) {
44
48
  if (amount <= 0n) {
45
49
  throw new Error("MemoryPointLedger: lock amount must be positive");
46
50
  }
47
51
  if (lockDurationMs <= 0) {
48
52
  throw new Error("MemoryPointLedger: lockDurationMs must be positive");
49
53
  }
50
- const key = getAddress(userAddress);
54
+ const user = getAddress(userAddress);
55
+ const token = normalizeToken(tokenAddress);
51
56
  this.purgeExpired();
52
- const total = this.balances.get(key) ?? 0n;
53
- const alreadyLocked = this.lockedTotalFor(key);
57
+ const total = this.balances.get(balanceKey(user, token)) ?? 0n;
58
+ const alreadyLocked = this.lockedTotalFor(user, token);
54
59
  const available = total - alreadyLocked;
55
60
  if (available < amount) {
56
61
  throw new Error(
@@ -59,14 +64,18 @@ var MemoryPointLedger = class {
59
64
  }
60
65
  const lockId = `lock-${this.nextLockId++}`;
61
66
  const now = this.now();
62
- this.locks.set(lockId, {
67
+ const lock = {
63
68
  lockId,
64
- userAddress: key,
69
+ userAddress: user,
65
70
  amount,
66
71
  status: "PENDING",
67
72
  createdAt: now,
68
73
  expiresAt: now + lockDurationMs
69
- });
74
+ };
75
+ if (tokenAddress !== void 0) {
76
+ lock.tokenAddress = getAddress(tokenAddress);
77
+ }
78
+ this.locks.set(lockId, lock);
70
79
  return lockId;
71
80
  }
72
81
  async releaseLock(lockId) {
@@ -76,11 +85,13 @@ var MemoryPointLedger = class {
76
85
  this.locks.delete(lockId);
77
86
  }
78
87
  }
79
- async deductBalance(userAddress, amount, txHash) {
88
+ async deductBalance(userAddress, amount, txHash, tokenAddress) {
80
89
  if (amount <= 0n) {
81
90
  throw new Error("MemoryPointLedger: deduct amount must be positive");
82
91
  }
83
- const key = getAddress(userAddress);
92
+ const user = getAddress(userAddress);
93
+ const token = normalizeToken(tokenAddress);
94
+ const key = balanceKey(user, token);
84
95
  const current = this.balances.get(key) ?? 0n;
85
96
  if (current < amount) {
86
97
  throw new Error(
@@ -89,7 +100,7 @@ var MemoryPointLedger = class {
89
100
  }
90
101
  this.balances.set(key, current - amount);
91
102
  for (const lock of this.locks.values()) {
92
- if (lock.userAddress === key && lock.status === "PENDING" && lock.amount === amount) {
103
+ if (lock.userAddress === user && lock.status === "PENDING" && lock.amount === amount && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === token) {
93
104
  lock.status = "MINTED";
94
105
  lock.txHash = txHash;
95
106
  return;
@@ -105,6 +116,54 @@ var MemoryPointLedger = class {
105
116
  if (txHash) lock.txHash = txHash;
106
117
  }
107
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
+ // -------------------------------------------------------------------------
108
167
  // Internal helpers
109
168
  // -------------------------------------------------------------------------
110
169
  /**
@@ -119,16 +178,23 @@ var MemoryPointLedger = class {
119
178
  }
120
179
  }
121
180
  }
122
- lockedTotalFor(userAddress) {
181
+ lockedTotalFor(userAddress, tokenKey) {
123
182
  let total = 0n;
124
183
  for (const lock of this.locks.values()) {
125
- if (lock.userAddress === userAddress && lock.status === "PENDING") {
184
+ if (lock.userAddress === userAddress && lock.status === "PENDING" && (lock.tokenAddress ?? DEFAULT_TOKEN_KEY) === tokenKey) {
126
185
  total += lock.amount;
127
186
  }
128
187
  }
129
188
  return total;
130
189
  }
131
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
+ }
132
198
 
133
199
  // src/policy/defaultPolicy.ts
134
200
  var DefaultPolicyEngine = class {
@@ -150,7 +216,10 @@ var DefaultPolicyEngine = class {
150
216
  if (request.amount <= 0n) {
151
217
  return { approved: false, reason: "Amount must be positive" };
152
218
  }
153
- const available = await this.ledger.getBalance(request.userAddress);
219
+ const available = await this.ledger.getBalance(
220
+ request.userAddress,
221
+ request.pointTokenAddress
222
+ );
154
223
  if (available < request.amount) {
155
224
  return {
156
225
  approved: false,
@@ -570,6 +639,11 @@ var RelayService = class {
570
639
  * decide whether to release the ledger lock (`SUBMIT_FAILED` and
571
640
  * `SIMULATION_FAILED` are safe to release; `TX_REVERTED` and `TIMEOUT`
572
641
  * need manual review because the tx may still land).
642
+ *
643
+ * @deprecated Since 0.3.0 — will be replaced by `prepareMint()` +
644
+ * `prepareBurn()` in the v1.4 sponsored-UserOp flow. The SC team
645
+ * still needs to finalize Relayer v2 ABI before the replacements
646
+ * can ship (blocker B1). Kept for v0.2.x consumers. Removed in 2.0.
573
647
  */
574
648
  async submitMintAndSwap(params) {
575
649
  if (this.simulateBeforeSubmit && this.provider) {
@@ -648,84 +722,35 @@ var DEFAULT_GAS_UNITS = 500000n;
648
722
  var DEFAULT_PREMIUM_BPS = 12e3;
649
723
  var FeeManager = class {
650
724
  provider;
651
- operatorWallet;
652
- mintAndSwapGasUnits;
725
+ gasUnits;
653
726
  gasPremiumBps;
654
- quoteNativeToUsdt;
655
- rebalanceThresholdWei;
656
- rebalanceUsdtAmount;
657
- swapUsdtToNative;
727
+ quoteNativeToFee;
658
728
  constructor(config) {
659
729
  if (!config.provider) throw new Error("FeeManager: provider required");
660
- if (!config.operatorWallet)
661
- throw new Error("FeeManager: operatorWallet required");
662
- if (!config.quoteNativeToUsdt)
663
- throw new Error("FeeManager: quoteNativeToUsdt required");
730
+ if (!config.quoteNativeToFee)
731
+ throw new Error("FeeManager: quoteNativeToFee required");
664
732
  this.provider = config.provider;
665
- this.operatorWallet = config.operatorWallet;
666
- this.mintAndSwapGasUnits = config.mintAndSwapGasUnits ?? DEFAULT_GAS_UNITS;
733
+ this.gasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
667
734
  this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
668
- this.quoteNativeToUsdt = config.quoteNativeToUsdt;
669
- if (config.rebalanceThresholdWei !== void 0) {
670
- this.rebalanceThresholdWei = config.rebalanceThresholdWei;
671
- }
672
- if (config.rebalanceUsdtAmount !== void 0) {
673
- this.rebalanceUsdtAmount = config.rebalanceUsdtAmount;
674
- }
675
- if (config.swapUsdtToNative) {
676
- this.swapUsdtToNative = config.swapUsdtToNative;
677
- }
678
- const rebalanceFields = [
679
- config.rebalanceThresholdWei,
680
- config.rebalanceUsdtAmount,
681
- config.swapUsdtToNative
682
- ];
683
- const someSet = rebalanceFields.some((v) => v !== void 0);
684
- const allSet = rebalanceFields.every((v) => v !== void 0);
685
- if (someSet && !allSet) {
686
- throw new Error(
687
- "FeeManager: rebalanceThresholdWei, rebalanceUsdtAmount, and swapUsdtToNative must all be set together"
688
- );
689
- }
735
+ this.quoteNativeToFee = config.quoteNativeToFee;
690
736
  }
691
737
  /**
692
- * Estimate the USDT fee the operator should charge for a single
693
- * `mintAndSwap`. Computed as:
738
+ * Estimate the fee (in the caller's fee currency) to charge for the
739
+ * next sponsored UserOp:
740
+ *
741
+ * nativeCost = gasUnits × gasPrice
742
+ * withPremium = nativeCost × premiumBps / 10_000
743
+ * fee = quoteNativeToFee(withPremium)
694
744
  *
695
- * nativeCost = gasUnits × gasPrice
696
- * premiumNativeCost = nativeCost × premiumBps / 10_000
697
- * usdtFee = quoteNativeToUsdt(premiumNativeCost)
745
+ * For backward compatibility with v0.2.x code that reads `gasFeeUsdt`
746
+ * from the response, the name `estimateGasFee` is kept — but the
747
+ * currency depends on how the caller wired `quoteNativeToFee`.
698
748
  */
699
749
  async estimateGasFee() {
700
750
  const gasPrice = await this.provider.getGasPrice();
701
- const nativeCost = gasPrice * this.mintAndSwapGasUnits;
751
+ const nativeCost = gasPrice * this.gasUnits;
702
752
  const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
703
- return this.quoteNativeToUsdt(withPremium);
704
- }
705
- /**
706
- * Check the operator's native balance and, if it has dropped below the
707
- * configured threshold, trigger a USDT→native rebalance via the injected
708
- * `swapUsdtToNative` function.
709
- *
710
- * Returns `true` if a rebalance was performed, `false` otherwise.
711
- * Silently no-ops when rebalance is not configured.
712
- */
713
- async rebalanceIfNeeded() {
714
- if (this.rebalanceThresholdWei === void 0 || this.rebalanceUsdtAmount === void 0 || !this.swapUsdtToNative) {
715
- return false;
716
- }
717
- const operatorAddress = this.operatorWallet.account?.address;
718
- if (!operatorAddress) {
719
- throw new Error(
720
- "FeeManager: operator wallet has no account attached \u2014 cannot read balance"
721
- );
722
- }
723
- const balance = await this.provider.getBalance({ address: operatorAddress });
724
- if (balance >= this.rebalanceThresholdWei) {
725
- return false;
726
- }
727
- await this.swapUsdtToNative(this.rebalanceUsdtAmount);
728
- return true;
753
+ return this.quoteNativeToFee(withPremium);
729
754
  }
730
755
  };
731
756
 
@@ -774,6 +799,12 @@ var MintingGateway = class {
774
799
  this.now = config.now ?? (() => Date.now());
775
800
  this.defaultLockBufferMs = config.defaultLockBufferMs ?? DEFAULT_LOCK_BUFFER_MS;
776
801
  }
802
+ /**
803
+ * @deprecated Since 0.3.0 — will be renamed to `processMint()` once
804
+ * the SC team finalizes Relayer v2 ABI. The new flow drops the
805
+ * swap steps entirely (no more single-call mint+swap); users swap
806
+ * separately on PAFI Web. Kept here for v0.2.x consumers. Removed in 2.0.
807
+ */
777
808
  async processMintAndCashOut(request) {
778
809
  const { receiverConsent, receiverSignature } = request;
779
810
  if (!receiverConsent || !receiverSignature) {
@@ -838,7 +869,8 @@ var MintingGateway = class {
838
869
  lockId = await this.ledger.lockForMinting(
839
870
  request.userAddress,
840
871
  receiverConsent.amount,
841
- lockDurationMs
872
+ lockDurationMs,
873
+ request.pointTokenAddress
842
874
  );
843
875
  } catch (err) {
844
876
  throw new MintingGatewayError(
@@ -1128,11 +1160,19 @@ var PointIndexer = class {
1128
1160
  * issuer to mint without going through the gateway.
1129
1161
  */
1130
1162
  async finalize(evt) {
1131
- const locks = await this.ledger.getLockedRequests(evt.to);
1163
+ const locks = await this.ledger.getLockedRequests(
1164
+ evt.to,
1165
+ this.pointTokenAddress
1166
+ );
1132
1167
  const match = pickMatchingLock(locks, evt.amount);
1133
1168
  if (!match) return;
1134
1169
  try {
1135
- await this.ledger.deductBalance(evt.to, evt.amount, evt.txHash);
1170
+ await this.ledger.deductBalance(
1171
+ evt.to,
1172
+ evt.amount,
1173
+ evt.txHash,
1174
+ this.pointTokenAddress
1175
+ );
1136
1176
  } catch {
1137
1177
  return;
1138
1178
  }
@@ -1154,8 +1194,159 @@ function pickMatchingLock(locks, amount) {
1154
1194
  return best;
1155
1195
  }
1156
1196
 
1197
+ // src/indexer/burnIndexer.ts
1198
+ import { getAddress as getAddress5, parseAbiItem as parseAbiItem2 } from "viem";
1199
+ var TRANSFER_EVENT2 = parseAbiItem2(
1200
+ "event Transfer(address indexed from, address indexed to, uint256 value)"
1201
+ );
1202
+ var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
1203
+ var DEFAULT_CONFIRMATIONS2 = 3;
1204
+ var DEFAULT_BATCH_SIZE2 = 2000n;
1205
+ var DEFAULT_POLL_INTERVAL_MS2 = 5e3;
1206
+ var BurnIndexer = class {
1207
+ provider;
1208
+ pointTokenAddress;
1209
+ ledger;
1210
+ cursorStore;
1211
+ startBlock;
1212
+ confirmations;
1213
+ batchSize;
1214
+ pollIntervalMs;
1215
+ /**
1216
+ * Caller-supplied matcher. Return the lockId to resolve for a given
1217
+ * burn event, or `undefined` to skip. Runs synchronously via the
1218
+ * ledger's query path.
1219
+ *
1220
+ * Default: try `ledger.resolveCreditByBurnTx` keyed on a synthetic
1221
+ * lock id `burn-${from}-${amount}` — the in-memory ledger assigns
1222
+ * incrementing IDs so callers with the memory ledger must provide a
1223
+ * custom matcher. Real DB-backed ledgers override this to JOIN on
1224
+ * their `pending_credits` table.
1225
+ */
1226
+ matchLockId = async () => void 0;
1227
+ running = false;
1228
+ timer;
1229
+ constructor(config) {
1230
+ if (!config.provider) throw new Error("BurnIndexer: provider required");
1231
+ if (!config.pointTokenAddress)
1232
+ throw new Error("BurnIndexer: pointTokenAddress required");
1233
+ if (!config.ledger) throw new Error("BurnIndexer: ledger required");
1234
+ this.provider = config.provider;
1235
+ this.pointTokenAddress = config.pointTokenAddress;
1236
+ this.ledger = config.ledger;
1237
+ this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();
1238
+ this.startBlock = config.fromBlock ?? 0n;
1239
+ this.confirmations = BigInt(
1240
+ config.confirmations ?? DEFAULT_CONFIRMATIONS2
1241
+ );
1242
+ this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE2));
1243
+ this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
1244
+ }
1245
+ start() {
1246
+ if (this.running) return;
1247
+ this.running = true;
1248
+ void this.tick();
1249
+ }
1250
+ stop() {
1251
+ this.running = false;
1252
+ if (this.timer) {
1253
+ clearTimeout(this.timer);
1254
+ this.timer = void 0;
1255
+ }
1256
+ }
1257
+ async tick() {
1258
+ if (!this.running) return;
1259
+ try {
1260
+ const latest = await this.provider.getBlockNumber();
1261
+ const safeHead = latest - this.confirmations;
1262
+ if (safeHead < 0n) {
1263
+ this.scheduleNext();
1264
+ return;
1265
+ }
1266
+ const stored = await this.cursorStore.load();
1267
+ const from = stored ?? this.startBlock;
1268
+ if (from > safeHead) {
1269
+ this.scheduleNext();
1270
+ return;
1271
+ }
1272
+ await this.processBlockRange(from, safeHead);
1273
+ } catch {
1274
+ }
1275
+ this.scheduleNext();
1276
+ }
1277
+ scheduleNext() {
1278
+ if (!this.running) return;
1279
+ this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);
1280
+ }
1281
+ /**
1282
+ * Scan `[from, to]` inclusive for burn events. Callers can drive this
1283
+ * directly to backfill a specific range without `start()`. Cursor is
1284
+ * advanced to `to + 1` on completion.
1285
+ */
1286
+ async processBlockRange(from, to) {
1287
+ if (from > to) return;
1288
+ let cursor = from;
1289
+ while (cursor <= to) {
1290
+ const chunkEnd = cursor + this.batchSize - 1n > to ? to : cursor + this.batchSize - 1n;
1291
+ const logs = await this.provider.getLogs({
1292
+ address: this.pointTokenAddress,
1293
+ event: TRANSFER_EVENT2,
1294
+ args: { to: ZERO_ADDRESS2 },
1295
+ // filter: burn = transfer to zero
1296
+ fromBlock: cursor,
1297
+ toBlock: chunkEnd
1298
+ });
1299
+ const events = this.decodeBurnEvents(logs);
1300
+ events.sort((a, b) => {
1301
+ if (a.blockNumber !== b.blockNumber) {
1302
+ return a.blockNumber < b.blockNumber ? -1 : 1;
1303
+ }
1304
+ return a.logIndex - b.logIndex;
1305
+ });
1306
+ for (const evt of events) {
1307
+ await this.finalize(evt);
1308
+ }
1309
+ await this.cursorStore.save(chunkEnd + 1n);
1310
+ cursor = chunkEnd + 1n;
1311
+ }
1312
+ }
1313
+ decodeBurnEvents(logs) {
1314
+ const out = [];
1315
+ for (const log of logs) {
1316
+ const args = log.args;
1317
+ if (!args.from || !args.to || args.value === void 0) continue;
1318
+ if (getAddress5(args.to) !== ZERO_ADDRESS2) continue;
1319
+ if (log.blockNumber === null || log.transactionHash === null) continue;
1320
+ out.push({
1321
+ from: getAddress5(args.from),
1322
+ amount: args.value,
1323
+ blockNumber: log.blockNumber,
1324
+ txHash: log.transactionHash,
1325
+ logIndex: log.logIndex ?? 0
1326
+ });
1327
+ }
1328
+ return out;
1329
+ }
1330
+ /**
1331
+ * Resolve a matching pending credit for this burn event and call
1332
+ * `ledger.resolveCreditByBurnTx(lockId, txHash)`. If no match found,
1333
+ * log + skip.
1334
+ */
1335
+ async finalize(evt) {
1336
+ const lockId = await this.matchLockId(evt);
1337
+ if (!lockId) return;
1338
+ if (!this.ledger.resolveCreditByBurnTx) {
1339
+ return;
1340
+ }
1341
+ try {
1342
+ await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);
1343
+ } catch {
1344
+ }
1345
+ }
1346
+ };
1347
+
1157
1348
  // src/api/handlers.ts
1158
- import { getAddress as getAddress5 } from "viem";
1349
+ import { getAddress as getAddress6 } from "viem";
1159
1350
  import {
1160
1351
  getMintRequestNonce,
1161
1352
  getPointTokenBalance,
@@ -1169,9 +1360,18 @@ var IssuerApiHandlers = class {
1169
1360
  gateway;
1170
1361
  ledger;
1171
1362
  provider;
1172
- pointTokenAddress;
1363
+ /**
1364
+ * Set of supported PointToken addresses (checksum-normalized). Handlers
1365
+ * validate the request's `pointTokenAddress` against this set.
1366
+ */
1367
+ supportedTokens;
1368
+ /** First supported token — used as default when a handler doesn't
1369
+ * receive a `pointTokenAddress` in the request (shouldn't happen in
1370
+ * practice, but keeps type-narrowing happy). */
1371
+ defaultToken;
1173
1372
  chainId;
1174
1373
  contracts;
1374
+ pafiWebUrl;
1175
1375
  feeManager;
1176
1376
  poolsProvider;
1177
1377
  constructor(config) {
@@ -1179,9 +1379,18 @@ var IssuerApiHandlers = class {
1179
1379
  this.gateway = config.gateway;
1180
1380
  this.ledger = config.ledger;
1181
1381
  this.provider = config.provider;
1182
- this.pointTokenAddress = config.pointTokenAddress;
1382
+ const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
1383
+ if (raw.length === 0) {
1384
+ throw new Error(
1385
+ "IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
1386
+ );
1387
+ }
1388
+ const normalized = raw.map((a) => getAddress6(a));
1389
+ this.supportedTokens = new Set(normalized);
1390
+ this.defaultToken = normalized[0];
1183
1391
  this.chainId = config.chainId;
1184
1392
  this.contracts = config.contracts;
1393
+ if (config.pafiWebUrl) this.pafiWebUrl = config.pafiWebUrl;
1185
1394
  if (config.feeManager) this.feeManager = config.feeManager;
1186
1395
  if (config.poolsProvider) this.poolsProvider = config.poolsProvider;
1187
1396
  }
@@ -1217,7 +1426,16 @@ var IssuerApiHandlers = class {
1217
1426
  `handleConfig: unsupported chainId ${chainId}, issuer is configured for ${this.chainId}`
1218
1427
  );
1219
1428
  }
1220
- return { chainId: this.chainId, contracts: { ...this.contracts } };
1429
+ const contracts = {
1430
+ ...this.contracts,
1431
+ pointTokens: Array.from(this.supportedTokens)
1432
+ };
1433
+ const response = {
1434
+ chainId: this.chainId,
1435
+ contracts
1436
+ };
1437
+ if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;
1438
+ return response;
1221
1439
  }
1222
1440
  /** `GET /gas-fee` — quoted in USDT (6-decimal base units). */
1223
1441
  async handleGasFee() {
@@ -1268,15 +1486,15 @@ var IssuerApiHandlers = class {
1268
1486
  `handleUser: unsupported chainId ${request.chainId}`
1269
1487
  );
1270
1488
  }
1271
- const normalizedAuthed = getAddress5(userAddress);
1272
- const normalizedRequest = getAddress5(request.userAddress);
1489
+ const normalizedAuthed = getAddress6(userAddress);
1490
+ const normalizedRequest = getAddress6(request.userAddress);
1273
1491
  if (normalizedAuthed !== normalizedRequest) {
1274
1492
  throw new Error(
1275
1493
  "handleUser: request userAddress must match authenticated user"
1276
1494
  );
1277
1495
  }
1278
- const pointToken = getAddress5(request.pointTokenAddress);
1279
- if (pointToken !== this.pointTokenAddress) {
1496
+ const pointToken = getAddress6(request.pointTokenAddress);
1497
+ if (!this.supportedTokens.has(pointToken)) {
1280
1498
  throw new Error(
1281
1499
  `handleUser: unsupported pointToken ${pointToken}`
1282
1500
  );
@@ -1284,7 +1502,7 @@ var IssuerApiHandlers = class {
1284
1502
  const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
1285
1503
  getMintRequestNonce(this.provider, pointToken, normalizedAuthed),
1286
1504
  getReceiverConsentNonce(this.provider, pointToken, normalizedAuthed),
1287
- this.ledger.getBalance(normalizedAuthed),
1505
+ this.ledger.getBalance(normalizedAuthed, pointToken),
1288
1506
  getPointTokenBalance(this.provider, pointToken, normalizedAuthed),
1289
1507
  isMinter(this.provider, pointToken, normalizedAuthed)
1290
1508
  ]);
@@ -1318,8 +1536,8 @@ var IssuerApiHandlers = class {
1318
1536
  `handleBuildConsentTypedData: unsupported chainId ${request.chainId}`
1319
1537
  );
1320
1538
  }
1321
- const pointToken = getAddress5(request.pointTokenAddress);
1322
- if (pointToken !== this.pointTokenAddress) {
1539
+ const pointToken = getAddress6(request.pointTokenAddress);
1540
+ if (!this.supportedTokens.has(pointToken)) {
1323
1541
  throw new Error(
1324
1542
  `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
1325
1543
  );
@@ -1343,8 +1561,14 @@ var IssuerApiHandlers = class {
1343
1561
  /**
1344
1562
  * `POST /claim-and-swap`
1345
1563
  *
1346
- * The terminal handler: forwards the verified consent to the
1347
- * MintingGateway, which runs the 11-step flow.
1564
+ * @deprecated Since 0.3.0 the single-call mint-then-swap flow is
1565
+ * retired in v1.4. Use the new `handleClaim()` (mint only) and let
1566
+ * the user swap separately on PAFI Web. See
1567
+ * [V1.4_V1.5_OVERVIEW.md §4] for the new scenario model. Will be
1568
+ * removed in 2.0.
1569
+ *
1570
+ * Legacy behavior: the terminal handler forwards the verified
1571
+ * consent to the MintingGateway, which runs the 11-step flow.
1348
1572
  */
1349
1573
  async handleClaimAndSwap(userAddress, request) {
1350
1574
  if (request.chainId !== this.chainId) {
@@ -1352,14 +1576,14 @@ var IssuerApiHandlers = class {
1352
1576
  `handleClaimAndSwap: unsupported chainId ${request.chainId}`
1353
1577
  );
1354
1578
  }
1355
- const pointToken = getAddress5(request.pointTokenAddress);
1356
- if (pointToken !== this.pointTokenAddress) {
1579
+ const pointToken = getAddress6(request.pointTokenAddress);
1580
+ if (!this.supportedTokens.has(pointToken)) {
1357
1581
  throw new Error(
1358
1582
  `handleClaimAndSwap: unsupported pointToken ${pointToken}`
1359
1583
  );
1360
1584
  }
1361
1585
  const result = await this.gateway.processMintAndCashOut({
1362
- userAddress: getAddress5(userAddress),
1586
+ userAddress: getAddress6(userAddress),
1363
1587
  pointTokenAddress: pointToken,
1364
1588
  chainId: request.chainId,
1365
1589
  domain: request.domain,
@@ -1590,7 +1814,274 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
1590
1814
  return BigInt(whole + padded);
1591
1815
  }
1592
1816
 
1817
+ // src/balance/balanceAggregator.ts
1818
+ import { getPointTokenBalance as getPointTokenBalance2 } from "@pafi-dev/core";
1819
+ var BalanceAggregator = class {
1820
+ provider;
1821
+ ledger;
1822
+ constructor(config) {
1823
+ if (!config.provider) {
1824
+ throw new Error("BalanceAggregator: provider is required");
1825
+ }
1826
+ if (!config.ledger) {
1827
+ throw new Error("BalanceAggregator: ledger is required");
1828
+ }
1829
+ this.provider = config.provider;
1830
+ this.ledger = config.ledger;
1831
+ }
1832
+ /**
1833
+ * Combined balance for a single (user, token) pair. Fetches off-chain
1834
+ * + on-chain in parallel.
1835
+ */
1836
+ async getCombinedBalance(user, pointToken) {
1837
+ const [offChain, onChain] = await Promise.all([
1838
+ this.ledger.getBalance(user, pointToken),
1839
+ getPointTokenBalance2(this.provider, pointToken, user)
1840
+ ]);
1841
+ return {
1842
+ offChain,
1843
+ onChain,
1844
+ total: offChain + onChain
1845
+ };
1846
+ }
1847
+ /**
1848
+ * Combined balance for multiple tokens owned by the same user. Runs
1849
+ * all lookups in parallel. Returns a Map keyed by the token address
1850
+ * (same casing as supplied — caller should normalize if needed).
1851
+ */
1852
+ async getCombinedBalanceMulti(user, pointTokens) {
1853
+ const entries = await Promise.all(
1854
+ pointTokens.map(async (token) => {
1855
+ const balance = await this.getCombinedBalance(user, token);
1856
+ return [token, balance];
1857
+ })
1858
+ );
1859
+ return new Map(entries);
1860
+ }
1861
+ };
1862
+
1863
+ // src/pafi-backend/types.ts
1864
+ var PafiBackendError = class extends Error {
1865
+ constructor(code, message, httpStatus, details, opts) {
1866
+ super(message);
1867
+ this.code = code;
1868
+ this.httpStatus = httpStatus;
1869
+ this.details = details;
1870
+ this.name = "PafiBackendError";
1871
+ if (opts?.retryAfter !== void 0) this.retryAfter = opts.retryAfter;
1872
+ if (opts?.safeToRetry !== void 0) this.serverSafeToRetry = opts.safeToRetry;
1873
+ }
1874
+ code;
1875
+ httpStatus;
1876
+ details;
1877
+ /**
1878
+ * Seconds to wait before retry. Populated from the server body
1879
+ * (e.g. rate limit returns the number of seconds until UTC midnight).
1880
+ */
1881
+ retryAfter;
1882
+ /**
1883
+ * `safeToRetry` as reported by the server body. Prefer this over the
1884
+ * code-based heuristic when available — the server knows more about
1885
+ * whether the same request will succeed on retry.
1886
+ */
1887
+ serverSafeToRetry;
1888
+ /**
1889
+ * Whether the caller can safely retry the same request.
1890
+ *
1891
+ * If the server provided `safeToRetry` in the body, trust that.
1892
+ * Otherwise fall back to a code-based heuristic.
1893
+ */
1894
+ get safeToRetry() {
1895
+ if (this.serverSafeToRetry !== void 0) return this.serverSafeToRetry;
1896
+ switch (this.code) {
1897
+ case "PAYMASTER_UNAVAILABLE":
1898
+ case "PAYMASTER_TIMEOUT":
1899
+ case "RATE_LIMITER_UNAVAILABLE":
1900
+ case "INTERNAL_ERROR":
1901
+ case "TIMEOUT":
1902
+ case "NETWORK_ERROR":
1903
+ return true;
1904
+ case "RATE_LIMIT_EXCEEDED":
1905
+ case "RATE_LIMIT_EXCEEDED_DAILY":
1906
+ case "RATE_LIMIT_EXCEEDED_PER_USER":
1907
+ return true;
1908
+ // after retryAfter
1909
+ default:
1910
+ return false;
1911
+ }
1912
+ }
1913
+ };
1914
+
1915
+ // src/pafi-backend/pafiBackendClient.ts
1916
+ var DEFAULT_TIMEOUT_MS = 1e4;
1917
+ var RETRY_DEFAULTS = {
1918
+ maxAttempts: 1,
1919
+ initialDelayMs: 500,
1920
+ maxDelayMs: 1e4,
1921
+ maxRetryAfterMs: 3e4
1922
+ };
1923
+ var PafiBackendClient = class {
1924
+ url;
1925
+ issuerId;
1926
+ apiKey;
1927
+ fetchImpl;
1928
+ timeoutMs;
1929
+ retry;
1930
+ constructor(config) {
1931
+ if (!config.url) {
1932
+ throw new Error("PafiBackendClient: url is required");
1933
+ }
1934
+ if (!config.issuerId) {
1935
+ throw new Error("PafiBackendClient: issuerId is required");
1936
+ }
1937
+ if (!config.apiKey) {
1938
+ throw new Error("PafiBackendClient: apiKey is required");
1939
+ }
1940
+ this.url = config.url.replace(/\/+$/, "");
1941
+ this.issuerId = config.issuerId;
1942
+ this.apiKey = config.apiKey;
1943
+ this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
1944
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1945
+ this.retry = { ...RETRY_DEFAULTS, ...config.retry ?? {} };
1946
+ if (!this.fetchImpl) {
1947
+ throw new Error(
1948
+ "PafiBackendClient: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
1949
+ );
1950
+ }
1951
+ if (this.retry.maxAttempts < 1) {
1952
+ throw new Error("PafiBackendClient: retry.maxAttempts must be >= 1");
1953
+ }
1954
+ }
1955
+ /**
1956
+ * Request paymaster sponsorship for a pre-built UserOperation.
1957
+ * See [SPONSORED_PATH_FLOW.md §4.1] for the API contract.
1958
+ *
1959
+ * Retries automatically on transient failures (5xx, timeouts, network
1960
+ * errors, and errors the server flags with `safeToRetry: true`) up to
1961
+ * `retry.maxAttempts`. 4xx errors that are not `safeToRetry` fail fast.
1962
+ *
1963
+ * @throws PafiBackendError on final failure after exhausting retries
1964
+ */
1965
+ async requestSponsorship(req) {
1966
+ return this.postWithRetry(
1967
+ "/paymaster/sponsor",
1968
+ req
1969
+ );
1970
+ }
1971
+ // -------------------------------------------------------------------------
1972
+ // Internals
1973
+ // -------------------------------------------------------------------------
1974
+ async postWithRetry(path, body) {
1975
+ let lastError;
1976
+ for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt++) {
1977
+ try {
1978
+ return await this.post(path, body);
1979
+ } catch (err) {
1980
+ if (!(err instanceof PafiBackendError)) throw err;
1981
+ lastError = err;
1982
+ const isLastAttempt = attempt >= this.retry.maxAttempts;
1983
+ if (isLastAttempt || !err.safeToRetry) throw err;
1984
+ const delay = this.computeBackoff(attempt, err.retryAfter);
1985
+ if (delay === null) throw err;
1986
+ await this.sleep(delay);
1987
+ }
1988
+ }
1989
+ throw lastError;
1990
+ }
1991
+ /**
1992
+ * Pick the delay before the next retry.
1993
+ * - If the server sent `retryAfter` (seconds), honor it (capped by
1994
+ * `maxRetryAfterMs`) — returns null if the server wait exceeds the
1995
+ * cap, signalling the caller should give up.
1996
+ * - Otherwise: exponential backoff with ±20% jitter, capped at
1997
+ * `maxDelayMs`.
1998
+ */
1999
+ computeBackoff(attempt, retryAfter) {
2000
+ if (retryAfter !== void 0) {
2001
+ const serverMs = retryAfter * 1e3;
2002
+ if (serverMs > this.retry.maxRetryAfterMs) return null;
2003
+ return serverMs;
2004
+ }
2005
+ const exp = this.retry.initialDelayMs * 2 ** (attempt - 1);
2006
+ const capped = Math.min(exp, this.retry.maxDelayMs);
2007
+ const jitter = capped * (0.8 + Math.random() * 0.4);
2008
+ return Math.round(jitter);
2009
+ }
2010
+ sleep(ms) {
2011
+ return new Promise((resolve) => setTimeout(resolve, ms));
2012
+ }
2013
+ async post(path, body) {
2014
+ const controller = new AbortController();
2015
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
2016
+ let response;
2017
+ try {
2018
+ response = await this.fetchImpl(`${this.url}${path}`, {
2019
+ method: "POST",
2020
+ headers: {
2021
+ "Content-Type": "application/json",
2022
+ "Authorization": `Bearer ${this.apiKey}`,
2023
+ "X-Issuer-Id": this.issuerId
2024
+ },
2025
+ body: JSON.stringify(body, this.bigintReplacer),
2026
+ signal: controller.signal
2027
+ });
2028
+ } catch (err) {
2029
+ if (err.name === "AbortError") {
2030
+ throw new PafiBackendError(
2031
+ "TIMEOUT",
2032
+ `PAFI Backend request timed out after ${this.timeoutMs}ms`,
2033
+ 0
2034
+ );
2035
+ }
2036
+ throw new PafiBackendError(
2037
+ "NETWORK_ERROR",
2038
+ `PAFI Backend unreachable: ${err.message}`,
2039
+ 0
2040
+ );
2041
+ } finally {
2042
+ clearTimeout(timeoutId);
2043
+ }
2044
+ const text = await response.text();
2045
+ if (!response.ok) {
2046
+ let code = "INTERNAL_ERROR";
2047
+ let message = text || response.statusText;
2048
+ let details;
2049
+ let retryAfter;
2050
+ let serverSafeToRetry;
2051
+ try {
2052
+ const parsed = JSON.parse(text);
2053
+ code = parsed.code ?? code;
2054
+ message = parsed.message ?? message;
2055
+ details = parsed.details;
2056
+ if (typeof parsed.retryAfter === "number") retryAfter = parsed.retryAfter;
2057
+ if (typeof parsed.safeToRetry === "boolean") serverSafeToRetry = parsed.safeToRetry;
2058
+ } catch {
2059
+ }
2060
+ throw new PafiBackendError(code, message, response.status, details, {
2061
+ ...retryAfter !== void 0 ? { retryAfter } : {},
2062
+ ...serverSafeToRetry !== void 0 ? { safeToRetry: serverSafeToRetry } : {}
2063
+ });
2064
+ }
2065
+ return JSON.parse(text, this.bigintReviver);
2066
+ }
2067
+ /** JSON replacer that stringifies bigints. Paired with bigintReviver. */
2068
+ bigintReplacer = (_key, value) => {
2069
+ return typeof value === "bigint" ? value.toString() : value;
2070
+ };
2071
+ /**
2072
+ * JSON reviver that coerces specific numeric-string fields back to
2073
+ * bigint. The server must send these fields as decimal strings.
2074
+ */
2075
+ bigintReviver = (key, value) => {
2076
+ if (typeof value === "string" && (key.endsWith("GasLimit") || key === "nonce" || key === "callGasLimit" || key === "verificationGasLimit" || key === "preVerificationGas" || key === "maxFeePerGas" || key === "maxPriorityFeePerGas" || key === "paymasterVerificationGasLimit" || key === "paymasterPostOpGasLimit") && /^\d+$/.test(value)) {
2077
+ return BigInt(value);
2078
+ }
2079
+ return value;
2080
+ };
2081
+ };
2082
+
1593
2083
  // src/config.ts
2084
+ import { getAddress as getAddress7 } from "viem";
1594
2085
  function createIssuerService(config) {
1595
2086
  if (!config.provider) {
1596
2087
  throw new Error("createIssuerService: provider is required");
@@ -1601,9 +2092,6 @@ function createIssuerService(config) {
1601
2092
  if (!config.signer) {
1602
2093
  throw new Error("createIssuerService: signer is required");
1603
2094
  }
1604
- if (!config.pointTokenAddress) {
1605
- throw new Error("createIssuerService: pointTokenAddress is required");
1606
- }
1607
2095
  if (!config.relayAddress) {
1608
2096
  throw new Error("createIssuerService: relayAddress is required");
1609
2097
  }
@@ -1613,6 +2101,13 @@ function createIssuerService(config) {
1613
2101
  if (!config.auth?.domain) {
1614
2102
  throw new Error("createIssuerService: auth.domain is required");
1615
2103
  }
2104
+ const rawAddresses = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
2105
+ if (rawAddresses.length === 0) {
2106
+ throw new Error(
2107
+ "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
2108
+ );
2109
+ }
2110
+ const tokenAddresses = rawAddresses.map((a) => getAddress7(a));
1616
2111
  const ledger = config.ledger ?? new MemoryPointLedger();
1617
2112
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
1618
2113
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -1642,8 +2137,7 @@ function createIssuerService(config) {
1642
2137
  if (config.fee) {
1643
2138
  feeManager = new FeeManager({
1644
2139
  ...config.fee,
1645
- provider: config.provider,
1646
- operatorWallet: config.operatorWallet
2140
+ provider: config.provider
1647
2141
  });
1648
2142
  }
1649
2143
  const gatewayConfig = {
@@ -1656,33 +2150,37 @@ function createIssuerService(config) {
1656
2150
  gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;
1657
2151
  }
1658
2152
  const gateway = new MintingGateway(gatewayConfig);
1659
- const indexerConfig = {
1660
- provider: config.provider,
1661
- pointTokenAddress: config.pointTokenAddress,
1662
- ledger
1663
- };
1664
- if (config.indexer?.fromBlock !== void 0) {
1665
- indexerConfig.fromBlock = config.indexer.fromBlock;
1666
- }
1667
- if (config.indexer?.cursorStore) {
1668
- indexerConfig.cursorStore = config.indexer.cursorStore;
1669
- }
1670
- if (config.indexer?.confirmations !== void 0) {
1671
- indexerConfig.confirmations = config.indexer.confirmations;
1672
- }
1673
- if (config.indexer?.batchSize !== void 0) {
1674
- indexerConfig.batchSize = config.indexer.batchSize;
1675
- }
1676
- if (config.indexer?.pollIntervalMs !== void 0) {
1677
- indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;
2153
+ const indexers = /* @__PURE__ */ new Map();
2154
+ for (const tokenAddress of tokenAddresses) {
2155
+ const indexerConfig = {
2156
+ provider: config.provider,
2157
+ pointTokenAddress: tokenAddress,
2158
+ ledger
2159
+ };
2160
+ if (config.indexer?.fromBlock !== void 0) {
2161
+ indexerConfig.fromBlock = config.indexer.fromBlock;
2162
+ }
2163
+ if (config.indexer?.cursorStore) {
2164
+ indexerConfig.cursorStore = config.indexer.cursorStore;
2165
+ }
2166
+ if (config.indexer?.confirmations !== void 0) {
2167
+ indexerConfig.confirmations = config.indexer.confirmations;
2168
+ }
2169
+ if (config.indexer?.batchSize !== void 0) {
2170
+ indexerConfig.batchSize = config.indexer.batchSize;
2171
+ }
2172
+ if (config.indexer?.pollIntervalMs !== void 0) {
2173
+ indexerConfig.pollIntervalMs = config.indexer.pollIntervalMs;
2174
+ }
2175
+ indexers.set(tokenAddress, new PointIndexer(indexerConfig));
1678
2176
  }
1679
- const indexer = new PointIndexer(indexerConfig);
2177
+ const firstIndexer = indexers.get(tokenAddresses[0]);
1680
2178
  const handlersConfig = {
1681
2179
  authService,
1682
2180
  gateway,
1683
2181
  ledger,
1684
2182
  provider: config.provider,
1685
- pointTokenAddress: config.pointTokenAddress,
2183
+ pointTokenAddresses: tokenAddresses,
1686
2184
  chainId: config.chainId,
1687
2185
  contracts: config.contracts
1688
2186
  };
@@ -1690,7 +2188,9 @@ function createIssuerService(config) {
1690
2188
  if (config.poolsProvider) handlersConfig.poolsProvider = config.poolsProvider;
1691
2189
  const handlers = new IssuerApiHandlers(handlersConfig);
1692
2190
  if (config.indexer?.autoStart) {
1693
- indexer.start();
2191
+ for (const idx of indexers.values()) {
2192
+ idx.start();
2193
+ }
1694
2194
  }
1695
2195
  return {
1696
2196
  authService,
@@ -1701,7 +2201,8 @@ function createIssuerService(config) {
1701
2201
  relayService,
1702
2202
  feeManager,
1703
2203
  gateway,
1704
- indexer,
2204
+ indexers,
2205
+ indexer: firstIndexer,
1705
2206
  handlers
1706
2207
  };
1707
2208
  }
@@ -1711,6 +2212,8 @@ var PAFI_ISSUER_SDK_VERSION = "0.1.0";
1711
2212
  export {
1712
2213
  AuthError,
1713
2214
  AuthService,
2215
+ BalanceAggregator,
2216
+ BurnIndexer,
1714
2217
  DefaultPolicyEngine,
1715
2218
  FeeManager,
1716
2219
  InMemoryCursorStore,
@@ -1721,6 +2224,8 @@ export {
1721
2224
  MintingGatewayError,
1722
2225
  NonceManager,
1723
2226
  PAFI_ISSUER_SDK_VERSION,
2227
+ PafiBackendClient,
2228
+ PafiBackendError,
1724
2229
  PointIndexer,
1725
2230
  PrivateKeySigner,
1726
2231
  RelayError,