@pafi-dev/issuer 0.2.0 → 0.3.0-beta.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
@@ -116,6 +116,54 @@ var MemoryPointLedger = class {
116
116
  if (txHash) lock.txHash = txHash;
117
117
  }
118
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
+ // -------------------------------------------------------------------------
119
167
  // Internal helpers
120
168
  // -------------------------------------------------------------------------
121
169
  /**
@@ -534,11 +582,15 @@ var RelayError = class extends Error {
534
582
  };
535
583
 
536
584
  // src/relay/relayService.ts
585
+ import { encodeFunctionData } from "viem";
537
586
  import {
538
587
  relayAbi,
539
588
  encodeMintAndSwap,
540
589
  simulateMintAndSwap as coreSimulateMintAndSwap,
541
- SimulationError
590
+ SimulationError,
591
+ RELAYER_V2_ABI,
592
+ POINT_TOKEN_V2_ABI,
593
+ buildPartialUserOperation
542
594
  } from "@pafi-dev/core";
543
595
  var DEFAULT_CONFIRMATION_TIMEOUT_MS = 6e4;
544
596
  var RelayService = class {
@@ -591,6 +643,11 @@ var RelayService = class {
591
643
  * decide whether to release the ledger lock (`SUBMIT_FAILED` and
592
644
  * `SIMULATION_FAILED` are safe to release; `TX_REVERTED` and `TIMEOUT`
593
645
  * need manual review because the tx may still land).
646
+ *
647
+ * @deprecated Since 0.3.0 — will be replaced by `prepareMint()` +
648
+ * `prepareBurn()` in the v1.4 sponsored-UserOp flow. The SC team
649
+ * still needs to finalize Relayer v2 ABI before the replacements
650
+ * can ship (blocker B1). Kept for v0.2.x consumers. Removed in 2.0.
594
651
  */
595
652
  async submitMintAndSwap(params) {
596
653
  if (this.simulateBeforeSubmit && this.provider) {
@@ -659,6 +716,154 @@ var RelayService = class {
659
716
  );
660
717
  }
661
718
  }
719
+ // ==========================================================================
720
+ // v1.4 — Sponsored UserOp preparation (beta with mocked SC contracts)
721
+ // ==========================================================================
722
+ //
723
+ // These two methods build unsigned `PartialUserOperation` payloads for
724
+ // the Frontend to sign (via Privy) and submit to the Bundler. The
725
+ // Issuer Backend no longer broadcasts — that's the Frontend's job.
726
+ //
727
+ // Uses mocked Relayer v2 + PointToken ABIs from `@pafi-dev/core/contracts`.
728
+ // When SC delivers real ABIs, the imports swap but these method bodies
729
+ // stay the same (calldata encoder is ABI-driven).
730
+ // ==========================================================================
731
+ /**
732
+ * Build an unsigned UserOp for Scenario 1 (Mint).
733
+ *
734
+ * Flow:
735
+ * 1. Encode `Relayer.mint(request, userSig, issuerSig)` as the inner call
736
+ * 2. Optionally append a PT fee transfer from user → feeRecipient
737
+ * (fee recovery happens on-chain via BatchExecutor, not via an
738
+ * operator wallet)
739
+ * 3. Wrap all inner calls into `BatchExecutor.execute(calls[])`
740
+ * 4. Return a `PartialUserOperation` ready for:
741
+ * - gas estimation (Bundler)
742
+ * - paymaster sponsorship (PAFI Backend)
743
+ * - user signature (Privy)
744
+ */
745
+ prepareMint(params) {
746
+ if (!params.relayerAddress) {
747
+ throw new RelayError("ENCODE_FAILED", "prepareMint: relayerAddress required");
748
+ }
749
+ if (!params.batchExecutorAddress) {
750
+ throw new RelayError(
751
+ "ENCODE_FAILED",
752
+ "prepareMint: batchExecutorAddress required"
753
+ );
754
+ }
755
+ if (!params.userAddress) {
756
+ throw new RelayError("ENCODE_FAILED", "prepareMint: userAddress required");
757
+ }
758
+ let mintCallData;
759
+ try {
760
+ mintCallData = encodeFunctionData({
761
+ abi: RELAYER_V2_ABI,
762
+ functionName: "mint",
763
+ args: [params.mintRequest, params.userSignature, params.issuerSignature]
764
+ });
765
+ } catch (err) {
766
+ throw new RelayError(
767
+ "ENCODE_FAILED",
768
+ `prepareMint: failed to encode Relayer.mint: ${errorMessage(err)}`,
769
+ err
770
+ );
771
+ }
772
+ const operations = [
773
+ {
774
+ target: params.relayerAddress,
775
+ value: 0n,
776
+ data: mintCallData
777
+ }
778
+ ];
779
+ if (params.mintRequest.feeAmount > 0n) {
780
+ operations.push({
781
+ target: params.pointTokenAddress,
782
+ value: 0n,
783
+ data: encodeFunctionData({
784
+ abi: POINT_TOKEN_V2_ABI,
785
+ functionName: "balanceOf",
786
+ // placeholder — real impl uses transfer
787
+ args: [params.mintRequest.feeRecipient]
788
+ })
789
+ });
790
+ }
791
+ return buildPartialUserOperation({
792
+ sender: params.userAddress,
793
+ nonce: params.aaNonce,
794
+ operations,
795
+ gasLimits: {
796
+ callGasLimit: params.callGasLimit ?? 500000n,
797
+ verificationGasLimit: params.verificationGasLimit ?? 150000n,
798
+ preVerificationGas: params.preVerificationGas ?? 50000n
799
+ }
800
+ });
801
+ }
802
+ /**
803
+ * Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
804
+ *
805
+ * Two modes:
806
+ * - `mode: 'burn'` — direct `PointToken.burn(amount)`; `msg.sender`
807
+ * via EIP-7702 delegation is the user, so no signature needed
808
+ * on-chain (the BurnConsent was already verified off-chain by
809
+ * the issuer backend before we got here)
810
+ * - `mode: 'burnWithSig'` — `PointToken.burnWithSig(consent, sig)`;
811
+ * used when the issuer hasn't verified the consent and the
812
+ * contract has to do it on-chain
813
+ */
814
+ prepareBurn(params) {
815
+ if (!params.pointTokenAddress) {
816
+ throw new RelayError("ENCODE_FAILED", "prepareBurn: pointTokenAddress required");
817
+ }
818
+ if (!params.batchExecutorAddress) {
819
+ throw new RelayError(
820
+ "ENCODE_FAILED",
821
+ "prepareBurn: batchExecutorAddress required"
822
+ );
823
+ }
824
+ let burnCallData;
825
+ try {
826
+ if (params.mode === "burnWithSig") {
827
+ if (!params.burnConsent || !params.consentSignature) {
828
+ throw new Error("burnWithSig requires burnConsent + consentSignature");
829
+ }
830
+ burnCallData = encodeFunctionData({
831
+ abi: POINT_TOKEN_V2_ABI,
832
+ functionName: "burnWithSig",
833
+ args: [params.burnConsent, params.consentSignature]
834
+ });
835
+ } else {
836
+ burnCallData = encodeFunctionData({
837
+ abi: POINT_TOKEN_V2_ABI,
838
+ functionName: "burn",
839
+ args: [params.amount]
840
+ });
841
+ }
842
+ } catch (err) {
843
+ throw new RelayError(
844
+ "ENCODE_FAILED",
845
+ `prepareBurn: failed to encode burn call: ${errorMessage(err)}`,
846
+ err
847
+ );
848
+ }
849
+ const operations = [
850
+ {
851
+ target: params.pointTokenAddress,
852
+ value: 0n,
853
+ data: burnCallData
854
+ }
855
+ ];
856
+ return buildPartialUserOperation({
857
+ sender: params.userAddress,
858
+ nonce: params.aaNonce,
859
+ operations,
860
+ gasLimits: {
861
+ callGasLimit: params.callGasLimit ?? 300000n,
862
+ verificationGasLimit: params.verificationGasLimit ?? 150000n,
863
+ preVerificationGas: params.preVerificationGas ?? 50000n
864
+ }
865
+ });
866
+ }
662
867
  };
663
868
  function errorMessage(err) {
664
869
  return err instanceof Error ? err.message : String(err);
@@ -669,84 +874,35 @@ var DEFAULT_GAS_UNITS = 500000n;
669
874
  var DEFAULT_PREMIUM_BPS = 12e3;
670
875
  var FeeManager = class {
671
876
  provider;
672
- operatorWallet;
673
- mintAndSwapGasUnits;
877
+ gasUnits;
674
878
  gasPremiumBps;
675
- quoteNativeToUsdt;
676
- rebalanceThresholdWei;
677
- rebalanceUsdtAmount;
678
- swapUsdtToNative;
879
+ quoteNativeToFee;
679
880
  constructor(config) {
680
881
  if (!config.provider) throw new Error("FeeManager: provider required");
681
- if (!config.operatorWallet)
682
- throw new Error("FeeManager: operatorWallet required");
683
- if (!config.quoteNativeToUsdt)
684
- throw new Error("FeeManager: quoteNativeToUsdt required");
882
+ if (!config.quoteNativeToFee)
883
+ throw new Error("FeeManager: quoteNativeToFee required");
685
884
  this.provider = config.provider;
686
- this.operatorWallet = config.operatorWallet;
687
- this.mintAndSwapGasUnits = config.mintAndSwapGasUnits ?? DEFAULT_GAS_UNITS;
885
+ this.gasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
688
886
  this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
689
- this.quoteNativeToUsdt = config.quoteNativeToUsdt;
690
- if (config.rebalanceThresholdWei !== void 0) {
691
- this.rebalanceThresholdWei = config.rebalanceThresholdWei;
692
- }
693
- if (config.rebalanceUsdtAmount !== void 0) {
694
- this.rebalanceUsdtAmount = config.rebalanceUsdtAmount;
695
- }
696
- if (config.swapUsdtToNative) {
697
- this.swapUsdtToNative = config.swapUsdtToNative;
698
- }
699
- const rebalanceFields = [
700
- config.rebalanceThresholdWei,
701
- config.rebalanceUsdtAmount,
702
- config.swapUsdtToNative
703
- ];
704
- const someSet = rebalanceFields.some((v) => v !== void 0);
705
- const allSet = rebalanceFields.every((v) => v !== void 0);
706
- if (someSet && !allSet) {
707
- throw new Error(
708
- "FeeManager: rebalanceThresholdWei, rebalanceUsdtAmount, and swapUsdtToNative must all be set together"
709
- );
710
- }
887
+ this.quoteNativeToFee = config.quoteNativeToFee;
711
888
  }
712
889
  /**
713
- * Estimate the USDT fee the operator should charge for a single
714
- * `mintAndSwap`. Computed as:
890
+ * Estimate the fee (in the caller's fee currency) to charge for the
891
+ * next sponsored UserOp:
892
+ *
893
+ * nativeCost = gasUnits × gasPrice
894
+ * withPremium = nativeCost × premiumBps / 10_000
895
+ * fee = quoteNativeToFee(withPremium)
715
896
  *
716
- * nativeCost = gasUnits × gasPrice
717
- * premiumNativeCost = nativeCost × premiumBps / 10_000
718
- * usdtFee = quoteNativeToUsdt(premiumNativeCost)
897
+ * For backward compatibility with v0.2.x code that reads `gasFeeUsdt`
898
+ * from the response, the name `estimateGasFee` is kept — but the
899
+ * currency depends on how the caller wired `quoteNativeToFee`.
719
900
  */
720
901
  async estimateGasFee() {
721
902
  const gasPrice = await this.provider.getGasPrice();
722
- const nativeCost = gasPrice * this.mintAndSwapGasUnits;
903
+ const nativeCost = gasPrice * this.gasUnits;
723
904
  const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
724
- return this.quoteNativeToUsdt(withPremium);
725
- }
726
- /**
727
- * Check the operator's native balance and, if it has dropped below the
728
- * configured threshold, trigger a USDT→native rebalance via the injected
729
- * `swapUsdtToNative` function.
730
- *
731
- * Returns `true` if a rebalance was performed, `false` otherwise.
732
- * Silently no-ops when rebalance is not configured.
733
- */
734
- async rebalanceIfNeeded() {
735
- if (this.rebalanceThresholdWei === void 0 || this.rebalanceUsdtAmount === void 0 || !this.swapUsdtToNative) {
736
- return false;
737
- }
738
- const operatorAddress = this.operatorWallet.account?.address;
739
- if (!operatorAddress) {
740
- throw new Error(
741
- "FeeManager: operator wallet has no account attached \u2014 cannot read balance"
742
- );
743
- }
744
- const balance = await this.provider.getBalance({ address: operatorAddress });
745
- if (balance >= this.rebalanceThresholdWei) {
746
- return false;
747
- }
748
- await this.swapUsdtToNative(this.rebalanceUsdtAmount);
749
- return true;
905
+ return this.quoteNativeToFee(withPremium);
750
906
  }
751
907
  };
752
908
 
@@ -795,6 +951,12 @@ var MintingGateway = class {
795
951
  this.now = config.now ?? (() => Date.now());
796
952
  this.defaultLockBufferMs = config.defaultLockBufferMs ?? DEFAULT_LOCK_BUFFER_MS;
797
953
  }
954
+ /**
955
+ * @deprecated Since 0.3.0 — will be renamed to `processMint()` once
956
+ * the SC team finalizes Relayer v2 ABI. The new flow drops the
957
+ * swap steps entirely (no more single-call mint+swap); users swap
958
+ * separately on PAFI Web. Kept here for v0.2.x consumers. Removed in 2.0.
959
+ */
798
960
  async processMintAndCashOut(request) {
799
961
  const { receiverConsent, receiverSignature } = request;
800
962
  if (!receiverConsent || !receiverSignature) {
@@ -1184,8 +1346,159 @@ function pickMatchingLock(locks, amount) {
1184
1346
  return best;
1185
1347
  }
1186
1348
 
1349
+ // src/indexer/burnIndexer.ts
1350
+ import { getAddress as getAddress5, parseAbiItem as parseAbiItem2 } from "viem";
1351
+ var TRANSFER_EVENT2 = parseAbiItem2(
1352
+ "event Transfer(address indexed from, address indexed to, uint256 value)"
1353
+ );
1354
+ var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
1355
+ var DEFAULT_CONFIRMATIONS2 = 3;
1356
+ var DEFAULT_BATCH_SIZE2 = 2000n;
1357
+ var DEFAULT_POLL_INTERVAL_MS2 = 5e3;
1358
+ var BurnIndexer = class {
1359
+ provider;
1360
+ pointTokenAddress;
1361
+ ledger;
1362
+ cursorStore;
1363
+ startBlock;
1364
+ confirmations;
1365
+ batchSize;
1366
+ pollIntervalMs;
1367
+ /**
1368
+ * Caller-supplied matcher. Return the lockId to resolve for a given
1369
+ * burn event, or `undefined` to skip. Runs synchronously via the
1370
+ * ledger's query path.
1371
+ *
1372
+ * Default: try `ledger.resolveCreditByBurnTx` keyed on a synthetic
1373
+ * lock id `burn-${from}-${amount}` — the in-memory ledger assigns
1374
+ * incrementing IDs so callers with the memory ledger must provide a
1375
+ * custom matcher. Real DB-backed ledgers override this to JOIN on
1376
+ * their `pending_credits` table.
1377
+ */
1378
+ matchLockId = async () => void 0;
1379
+ running = false;
1380
+ timer;
1381
+ constructor(config) {
1382
+ if (!config.provider) throw new Error("BurnIndexer: provider required");
1383
+ if (!config.pointTokenAddress)
1384
+ throw new Error("BurnIndexer: pointTokenAddress required");
1385
+ if (!config.ledger) throw new Error("BurnIndexer: ledger required");
1386
+ this.provider = config.provider;
1387
+ this.pointTokenAddress = config.pointTokenAddress;
1388
+ this.ledger = config.ledger;
1389
+ this.cursorStore = config.cursorStore ?? new InMemoryCursorStore();
1390
+ this.startBlock = config.fromBlock ?? 0n;
1391
+ this.confirmations = BigInt(
1392
+ config.confirmations ?? DEFAULT_CONFIRMATIONS2
1393
+ );
1394
+ this.batchSize = BigInt(config.batchSize ?? Number(DEFAULT_BATCH_SIZE2));
1395
+ this.pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
1396
+ }
1397
+ start() {
1398
+ if (this.running) return;
1399
+ this.running = true;
1400
+ void this.tick();
1401
+ }
1402
+ stop() {
1403
+ this.running = false;
1404
+ if (this.timer) {
1405
+ clearTimeout(this.timer);
1406
+ this.timer = void 0;
1407
+ }
1408
+ }
1409
+ async tick() {
1410
+ if (!this.running) return;
1411
+ try {
1412
+ const latest = await this.provider.getBlockNumber();
1413
+ const safeHead = latest - this.confirmations;
1414
+ if (safeHead < 0n) {
1415
+ this.scheduleNext();
1416
+ return;
1417
+ }
1418
+ const stored = await this.cursorStore.load();
1419
+ const from = stored ?? this.startBlock;
1420
+ if (from > safeHead) {
1421
+ this.scheduleNext();
1422
+ return;
1423
+ }
1424
+ await this.processBlockRange(from, safeHead);
1425
+ } catch {
1426
+ }
1427
+ this.scheduleNext();
1428
+ }
1429
+ scheduleNext() {
1430
+ if (!this.running) return;
1431
+ this.timer = setTimeout(() => void this.tick(), this.pollIntervalMs);
1432
+ }
1433
+ /**
1434
+ * Scan `[from, to]` inclusive for burn events. Callers can drive this
1435
+ * directly to backfill a specific range without `start()`. Cursor is
1436
+ * advanced to `to + 1` on completion.
1437
+ */
1438
+ async processBlockRange(from, to) {
1439
+ if (from > to) return;
1440
+ let cursor = from;
1441
+ while (cursor <= to) {
1442
+ const chunkEnd = cursor + this.batchSize - 1n > to ? to : cursor + this.batchSize - 1n;
1443
+ const logs = await this.provider.getLogs({
1444
+ address: this.pointTokenAddress,
1445
+ event: TRANSFER_EVENT2,
1446
+ args: { to: ZERO_ADDRESS2 },
1447
+ // filter: burn = transfer to zero
1448
+ fromBlock: cursor,
1449
+ toBlock: chunkEnd
1450
+ });
1451
+ const events = this.decodeBurnEvents(logs);
1452
+ events.sort((a, b) => {
1453
+ if (a.blockNumber !== b.blockNumber) {
1454
+ return a.blockNumber < b.blockNumber ? -1 : 1;
1455
+ }
1456
+ return a.logIndex - b.logIndex;
1457
+ });
1458
+ for (const evt of events) {
1459
+ await this.finalize(evt);
1460
+ }
1461
+ await this.cursorStore.save(chunkEnd + 1n);
1462
+ cursor = chunkEnd + 1n;
1463
+ }
1464
+ }
1465
+ decodeBurnEvents(logs) {
1466
+ const out = [];
1467
+ for (const log of logs) {
1468
+ const args = log.args;
1469
+ if (!args.from || !args.to || args.value === void 0) continue;
1470
+ if (getAddress5(args.to) !== ZERO_ADDRESS2) continue;
1471
+ if (log.blockNumber === null || log.transactionHash === null) continue;
1472
+ out.push({
1473
+ from: getAddress5(args.from),
1474
+ amount: args.value,
1475
+ blockNumber: log.blockNumber,
1476
+ txHash: log.transactionHash,
1477
+ logIndex: log.logIndex ?? 0
1478
+ });
1479
+ }
1480
+ return out;
1481
+ }
1482
+ /**
1483
+ * Resolve a matching pending credit for this burn event and call
1484
+ * `ledger.resolveCreditByBurnTx(lockId, txHash)`. If no match found,
1485
+ * log + skip.
1486
+ */
1487
+ async finalize(evt) {
1488
+ const lockId = await this.matchLockId(evt);
1489
+ if (!lockId) return;
1490
+ if (!this.ledger.resolveCreditByBurnTx) {
1491
+ return;
1492
+ }
1493
+ try {
1494
+ await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);
1495
+ } catch {
1496
+ }
1497
+ }
1498
+ };
1499
+
1187
1500
  // src/api/handlers.ts
1188
- import { getAddress as getAddress5 } from "viem";
1501
+ import { getAddress as getAddress6 } from "viem";
1189
1502
  import {
1190
1503
  getMintRequestNonce,
1191
1504
  getPointTokenBalance,
@@ -1210,6 +1523,7 @@ var IssuerApiHandlers = class {
1210
1523
  defaultToken;
1211
1524
  chainId;
1212
1525
  contracts;
1526
+ pafiWebUrl;
1213
1527
  feeManager;
1214
1528
  poolsProvider;
1215
1529
  constructor(config) {
@@ -1223,11 +1537,12 @@ var IssuerApiHandlers = class {
1223
1537
  "IssuerApiHandlers: pointTokenAddress or pointTokenAddresses required"
1224
1538
  );
1225
1539
  }
1226
- const normalized = raw.map((a) => getAddress5(a));
1540
+ const normalized = raw.map((a) => getAddress6(a));
1227
1541
  this.supportedTokens = new Set(normalized);
1228
1542
  this.defaultToken = normalized[0];
1229
1543
  this.chainId = config.chainId;
1230
1544
  this.contracts = config.contracts;
1545
+ if (config.pafiWebUrl) this.pafiWebUrl = config.pafiWebUrl;
1231
1546
  if (config.feeManager) this.feeManager = config.feeManager;
1232
1547
  if (config.poolsProvider) this.poolsProvider = config.poolsProvider;
1233
1548
  }
@@ -1263,7 +1578,16 @@ var IssuerApiHandlers = class {
1263
1578
  `handleConfig: unsupported chainId ${chainId}, issuer is configured for ${this.chainId}`
1264
1579
  );
1265
1580
  }
1266
- return { chainId: this.chainId, contracts: { ...this.contracts } };
1581
+ const contracts = {
1582
+ ...this.contracts,
1583
+ pointTokens: Array.from(this.supportedTokens)
1584
+ };
1585
+ const response = {
1586
+ chainId: this.chainId,
1587
+ contracts
1588
+ };
1589
+ if (this.pafiWebUrl) response.pafiWebUrl = this.pafiWebUrl;
1590
+ return response;
1267
1591
  }
1268
1592
  /** `GET /gas-fee` — quoted in USDT (6-decimal base units). */
1269
1593
  async handleGasFee() {
@@ -1314,14 +1638,14 @@ var IssuerApiHandlers = class {
1314
1638
  `handleUser: unsupported chainId ${request.chainId}`
1315
1639
  );
1316
1640
  }
1317
- const normalizedAuthed = getAddress5(userAddress);
1318
- const normalizedRequest = getAddress5(request.userAddress);
1641
+ const normalizedAuthed = getAddress6(userAddress);
1642
+ const normalizedRequest = getAddress6(request.userAddress);
1319
1643
  if (normalizedAuthed !== normalizedRequest) {
1320
1644
  throw new Error(
1321
1645
  "handleUser: request userAddress must match authenticated user"
1322
1646
  );
1323
1647
  }
1324
- const pointToken = getAddress5(request.pointTokenAddress);
1648
+ const pointToken = getAddress6(request.pointTokenAddress);
1325
1649
  if (!this.supportedTokens.has(pointToken)) {
1326
1650
  throw new Error(
1327
1651
  `handleUser: unsupported pointToken ${pointToken}`
@@ -1364,7 +1688,7 @@ var IssuerApiHandlers = class {
1364
1688
  `handleBuildConsentTypedData: unsupported chainId ${request.chainId}`
1365
1689
  );
1366
1690
  }
1367
- const pointToken = getAddress5(request.pointTokenAddress);
1691
+ const pointToken = getAddress6(request.pointTokenAddress);
1368
1692
  if (!this.supportedTokens.has(pointToken)) {
1369
1693
  throw new Error(
1370
1694
  `handleBuildConsentTypedData: unsupported pointToken ${pointToken}`
@@ -1389,8 +1713,14 @@ var IssuerApiHandlers = class {
1389
1713
  /**
1390
1714
  * `POST /claim-and-swap`
1391
1715
  *
1392
- * The terminal handler: forwards the verified consent to the
1393
- * MintingGateway, which runs the 11-step flow.
1716
+ * @deprecated Since 0.3.0 the single-call mint-then-swap flow is
1717
+ * retired in v1.4. Use the new `handleClaim()` (mint only) and let
1718
+ * the user swap separately on PAFI Web. See
1719
+ * [V1.4_V1.5_OVERVIEW.md §4] for the new scenario model. Will be
1720
+ * removed in 2.0.
1721
+ *
1722
+ * Legacy behavior: the terminal handler forwards the verified
1723
+ * consent to the MintingGateway, which runs the 11-step flow.
1394
1724
  */
1395
1725
  async handleClaimAndSwap(userAddress, request) {
1396
1726
  if (request.chainId !== this.chainId) {
@@ -1398,14 +1728,14 @@ var IssuerApiHandlers = class {
1398
1728
  `handleClaimAndSwap: unsupported chainId ${request.chainId}`
1399
1729
  );
1400
1730
  }
1401
- const pointToken = getAddress5(request.pointTokenAddress);
1731
+ const pointToken = getAddress6(request.pointTokenAddress);
1402
1732
  if (!this.supportedTokens.has(pointToken)) {
1403
1733
  throw new Error(
1404
1734
  `handleClaimAndSwap: unsupported pointToken ${pointToken}`
1405
1735
  );
1406
1736
  }
1407
1737
  const result = await this.gateway.processMintAndCashOut({
1408
- userAddress: getAddress5(userAddress),
1738
+ userAddress: getAddress6(userAddress),
1409
1739
  pointTokenAddress: pointToken,
1410
1740
  chainId: request.chainId,
1411
1741
  domain: request.domain,
@@ -1425,6 +1755,183 @@ var IssuerApiHandlers = class {
1425
1755
  }
1426
1756
  };
1427
1757
 
1758
+ // src/api/handlers/ptRedeemHandler.ts
1759
+ import { getAddress as getAddress7 } from "viem";
1760
+ import { verifyBurnConsent } from "@pafi-dev/core";
1761
+ var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
1762
+ var PTRedeemError = class extends Error {
1763
+ constructor(code, message) {
1764
+ super(message);
1765
+ this.code = code;
1766
+ this.name = "PTRedeemError";
1767
+ }
1768
+ code;
1769
+ };
1770
+ var PTRedeemHandler = class {
1771
+ ledger;
1772
+ relayService;
1773
+ pointTokenAddress;
1774
+ batchExecutorAddress;
1775
+ chainId;
1776
+ domain;
1777
+ redeemLockDurationMs;
1778
+ now;
1779
+ constructor(config) {
1780
+ if (!config.ledger.reservePendingCredit) {
1781
+ throw new PTRedeemError(
1782
+ "LEDGER_NOT_SUPPORTED",
1783
+ "PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
1784
+ );
1785
+ }
1786
+ this.ledger = config.ledger;
1787
+ this.relayService = config.relayService;
1788
+ this.pointTokenAddress = getAddress7(config.pointTokenAddress);
1789
+ this.batchExecutorAddress = getAddress7(config.batchExecutorAddress);
1790
+ this.chainId = config.chainId;
1791
+ this.domain = config.domain;
1792
+ this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
1793
+ this.now = config.now ?? (() => Date.now());
1794
+ }
1795
+ async handle(request) {
1796
+ if (request.amount <= 0n) {
1797
+ throw new PTRedeemError("INVALID_CONSENT", "redeem amount must be positive");
1798
+ }
1799
+ if (request.consent.amount !== request.amount) {
1800
+ throw new PTRedeemError(
1801
+ "AMOUNT_MISMATCH",
1802
+ `consent.amount (${request.consent.amount}) must match request.amount (${request.amount})`
1803
+ );
1804
+ }
1805
+ const nowSeconds = BigInt(Math.floor(this.now() / 1e3));
1806
+ if (request.consent.deadline <= nowSeconds) {
1807
+ throw new PTRedeemError(
1808
+ "EXPIRED_CONSENT",
1809
+ `consent deadline (${request.consent.deadline}) already passed`
1810
+ );
1811
+ }
1812
+ const verification = await verifyBurnConsent(
1813
+ {
1814
+ name: this.domain.name,
1815
+ chainId: this.chainId,
1816
+ verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
1817
+ },
1818
+ request.consent,
1819
+ request.consentSignature,
1820
+ request.userAddress
1821
+ );
1822
+ if (!verification.isValid) {
1823
+ throw new PTRedeemError(
1824
+ "SIGNATURE_MISMATCH",
1825
+ `signer mismatch \u2014 expected ${request.userAddress}, got ${verification.recoveredAddress}`
1826
+ );
1827
+ }
1828
+ const lockId = await this.ledger.reservePendingCredit(
1829
+ request.userAddress,
1830
+ request.amount,
1831
+ this.redeemLockDurationMs,
1832
+ this.pointTokenAddress
1833
+ );
1834
+ const userOp = this.relayService.prepareBurn({
1835
+ mode: "burnWithSig",
1836
+ userAddress: request.userAddress,
1837
+ aaNonce: request.aaNonce,
1838
+ pointTokenAddress: this.pointTokenAddress,
1839
+ batchExecutorAddress: this.batchExecutorAddress,
1840
+ burnConsent: request.consent,
1841
+ consentSignature: parseSigStruct(request.consentSignature)
1842
+ });
1843
+ return {
1844
+ lockId,
1845
+ userOp,
1846
+ expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3)
1847
+ };
1848
+ }
1849
+ };
1850
+ function parseSigStruct(serialized) {
1851
+ const raw = serialized.slice(2);
1852
+ if (raw.length !== 130) {
1853
+ throw new PTRedeemError(
1854
+ "INVALID_CONSENT",
1855
+ `signature must be 65 bytes, got ${raw.length / 2}`
1856
+ );
1857
+ }
1858
+ const r = `0x${raw.slice(0, 64)}`;
1859
+ const s = `0x${raw.slice(64, 128)}`;
1860
+ const v = parseInt(raw.slice(128, 130), 16);
1861
+ return { v, r, s };
1862
+ }
1863
+
1864
+ // src/api/handlers/topUpRedemptionHandler.ts
1865
+ import { getAddress as getAddress8 } from "viem";
1866
+ import { getPointTokenBalance as getPointTokenBalance2 } from "@pafi-dev/core";
1867
+ var TopUpRedemptionError = class extends Error {
1868
+ constructor(code, message) {
1869
+ super(message);
1870
+ this.code = code;
1871
+ this.name = "TopUpRedemptionError";
1872
+ }
1873
+ code;
1874
+ };
1875
+ var TopUpRedemptionHandler = class {
1876
+ ledger;
1877
+ ptRedeemHandler;
1878
+ provider;
1879
+ pointTokenAddress;
1880
+ constructor(config) {
1881
+ this.ledger = config.ledger;
1882
+ this.ptRedeemHandler = config.ptRedeemHandler;
1883
+ this.provider = config.provider;
1884
+ this.pointTokenAddress = getAddress8(config.pointTokenAddress);
1885
+ }
1886
+ async handle(request) {
1887
+ const offChainBalance = await this.ledger.getBalance(
1888
+ request.userAddress,
1889
+ this.pointTokenAddress
1890
+ );
1891
+ if (offChainBalance >= request.requiredAmount) {
1892
+ return { action: "NO_TOP_UP_NEEDED", offChainBalance };
1893
+ }
1894
+ const shortfall = request.requiredAmount - offChainBalance;
1895
+ const onChainBalance = await getPointTokenBalance2(
1896
+ this.provider,
1897
+ this.pointTokenAddress,
1898
+ request.userAddress
1899
+ );
1900
+ if (onChainBalance < shortfall) {
1901
+ return {
1902
+ action: "INSUFFICIENT_ONCHAIN",
1903
+ offChainBalance,
1904
+ onChainBalance,
1905
+ shortfall
1906
+ };
1907
+ }
1908
+ if (request.redeemRequest.consent.amount < shortfall) {
1909
+ throw new TopUpRedemptionError(
1910
+ "CONSENT_AMOUNT_TOO_LOW",
1911
+ `consent.amount (${request.redeemRequest.consent.amount}) must cover shortfall (${shortfall})`
1912
+ );
1913
+ }
1914
+ if (request.redeemRequest.consent.amount !== shortfall) {
1915
+ throw new TopUpRedemptionError(
1916
+ "CONSENT_AMOUNT_TOO_LOW",
1917
+ `consent.amount (${request.redeemRequest.consent.amount}) must equal shortfall (${shortfall}) exactly \u2014 re-sign with correct amount`
1918
+ );
1919
+ }
1920
+ const redeem = await this.ptRedeemHandler.handle({
1921
+ userAddress: request.userAddress,
1922
+ amount: shortfall,
1923
+ consent: request.redeemRequest.consent,
1924
+ consentSignature: request.redeemRequest.consentSignature,
1925
+ aaNonce: request.redeemRequest.aaNonce
1926
+ });
1927
+ return {
1928
+ action: "TOP_UP_STARTED",
1929
+ shortfall,
1930
+ redeem
1931
+ };
1932
+ }
1933
+ };
1934
+
1428
1935
  // src/pools/subgraphPoolsProvider.ts
1429
1936
  var DEFAULT_CACHE_TTL_MS = 3e4;
1430
1937
  var POOL_QUERY = `
@@ -1636,8 +2143,274 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
1636
2143
  return BigInt(whole + padded);
1637
2144
  }
1638
2145
 
2146
+ // src/balance/balanceAggregator.ts
2147
+ import { getPointTokenBalance as getPointTokenBalance3 } from "@pafi-dev/core";
2148
+ var BalanceAggregator = class {
2149
+ provider;
2150
+ ledger;
2151
+ constructor(config) {
2152
+ if (!config.provider) {
2153
+ throw new Error("BalanceAggregator: provider is required");
2154
+ }
2155
+ if (!config.ledger) {
2156
+ throw new Error("BalanceAggregator: ledger is required");
2157
+ }
2158
+ this.provider = config.provider;
2159
+ this.ledger = config.ledger;
2160
+ }
2161
+ /**
2162
+ * Combined balance for a single (user, token) pair. Fetches off-chain
2163
+ * + on-chain in parallel.
2164
+ */
2165
+ async getCombinedBalance(user, pointToken) {
2166
+ const [offChain, onChain] = await Promise.all([
2167
+ this.ledger.getBalance(user, pointToken),
2168
+ getPointTokenBalance3(this.provider, pointToken, user)
2169
+ ]);
2170
+ return {
2171
+ offChain,
2172
+ onChain,
2173
+ total: offChain + onChain
2174
+ };
2175
+ }
2176
+ /**
2177
+ * Combined balance for multiple tokens owned by the same user. Runs
2178
+ * all lookups in parallel. Returns a Map keyed by the token address
2179
+ * (same casing as supplied — caller should normalize if needed).
2180
+ */
2181
+ async getCombinedBalanceMulti(user, pointTokens) {
2182
+ const entries = await Promise.all(
2183
+ pointTokens.map(async (token) => {
2184
+ const balance = await this.getCombinedBalance(user, token);
2185
+ return [token, balance];
2186
+ })
2187
+ );
2188
+ return new Map(entries);
2189
+ }
2190
+ };
2191
+
2192
+ // src/pafi-backend/types.ts
2193
+ var PafiBackendError = class extends Error {
2194
+ constructor(code, message, httpStatus, details, opts) {
2195
+ super(message);
2196
+ this.code = code;
2197
+ this.httpStatus = httpStatus;
2198
+ this.details = details;
2199
+ this.name = "PafiBackendError";
2200
+ if (opts?.retryAfter !== void 0) this.retryAfter = opts.retryAfter;
2201
+ if (opts?.safeToRetry !== void 0) this.serverSafeToRetry = opts.safeToRetry;
2202
+ }
2203
+ code;
2204
+ httpStatus;
2205
+ details;
2206
+ /**
2207
+ * Seconds to wait before retry. Populated from the server body
2208
+ * (e.g. rate limit returns the number of seconds until UTC midnight).
2209
+ */
2210
+ retryAfter;
2211
+ /**
2212
+ * `safeToRetry` as reported by the server body. Prefer this over the
2213
+ * code-based heuristic when available — the server knows more about
2214
+ * whether the same request will succeed on retry.
2215
+ */
2216
+ serverSafeToRetry;
2217
+ /**
2218
+ * Whether the caller can safely retry the same request.
2219
+ *
2220
+ * If the server provided `safeToRetry` in the body, trust that.
2221
+ * Otherwise fall back to a code-based heuristic.
2222
+ */
2223
+ get safeToRetry() {
2224
+ if (this.serverSafeToRetry !== void 0) return this.serverSafeToRetry;
2225
+ switch (this.code) {
2226
+ case "PAYMASTER_UNAVAILABLE":
2227
+ case "PAYMASTER_TIMEOUT":
2228
+ case "RATE_LIMITER_UNAVAILABLE":
2229
+ case "INTERNAL_ERROR":
2230
+ case "TIMEOUT":
2231
+ case "NETWORK_ERROR":
2232
+ return true;
2233
+ case "RATE_LIMIT_EXCEEDED":
2234
+ case "RATE_LIMIT_EXCEEDED_DAILY":
2235
+ case "RATE_LIMIT_EXCEEDED_PER_USER":
2236
+ return true;
2237
+ // after retryAfter
2238
+ default:
2239
+ return false;
2240
+ }
2241
+ }
2242
+ };
2243
+
2244
+ // src/pafi-backend/pafiBackendClient.ts
2245
+ var DEFAULT_TIMEOUT_MS = 1e4;
2246
+ var RETRY_DEFAULTS = {
2247
+ maxAttempts: 1,
2248
+ initialDelayMs: 500,
2249
+ maxDelayMs: 1e4,
2250
+ maxRetryAfterMs: 3e4
2251
+ };
2252
+ var PafiBackendClient = class {
2253
+ url;
2254
+ issuerId;
2255
+ apiKey;
2256
+ fetchImpl;
2257
+ timeoutMs;
2258
+ retry;
2259
+ constructor(config) {
2260
+ if (!config.url) {
2261
+ throw new Error("PafiBackendClient: url is required");
2262
+ }
2263
+ if (!config.issuerId) {
2264
+ throw new Error("PafiBackendClient: issuerId is required");
2265
+ }
2266
+ if (!config.apiKey) {
2267
+ throw new Error("PafiBackendClient: apiKey is required");
2268
+ }
2269
+ this.url = config.url.replace(/\/+$/, "");
2270
+ this.issuerId = config.issuerId;
2271
+ this.apiKey = config.apiKey;
2272
+ this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
2273
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2274
+ this.retry = { ...RETRY_DEFAULTS, ...config.retry ?? {} };
2275
+ if (!this.fetchImpl) {
2276
+ throw new Error(
2277
+ "PafiBackendClient: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
2278
+ );
2279
+ }
2280
+ if (this.retry.maxAttempts < 1) {
2281
+ throw new Error("PafiBackendClient: retry.maxAttempts must be >= 1");
2282
+ }
2283
+ }
2284
+ /**
2285
+ * Request paymaster sponsorship for a pre-built UserOperation.
2286
+ * See [SPONSORED_PATH_FLOW.md §4.1] for the API contract.
2287
+ *
2288
+ * Retries automatically on transient failures (5xx, timeouts, network
2289
+ * errors, and errors the server flags with `safeToRetry: true`) up to
2290
+ * `retry.maxAttempts`. 4xx errors that are not `safeToRetry` fail fast.
2291
+ *
2292
+ * @throws PafiBackendError on final failure after exhausting retries
2293
+ */
2294
+ async requestSponsorship(req) {
2295
+ return this.postWithRetry(
2296
+ "/paymaster/sponsor",
2297
+ req
2298
+ );
2299
+ }
2300
+ // -------------------------------------------------------------------------
2301
+ // Internals
2302
+ // -------------------------------------------------------------------------
2303
+ async postWithRetry(path, body) {
2304
+ let lastError;
2305
+ for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt++) {
2306
+ try {
2307
+ return await this.post(path, body);
2308
+ } catch (err) {
2309
+ if (!(err instanceof PafiBackendError)) throw err;
2310
+ lastError = err;
2311
+ const isLastAttempt = attempt >= this.retry.maxAttempts;
2312
+ if (isLastAttempt || !err.safeToRetry) throw err;
2313
+ const delay = this.computeBackoff(attempt, err.retryAfter);
2314
+ if (delay === null) throw err;
2315
+ await this.sleep(delay);
2316
+ }
2317
+ }
2318
+ throw lastError;
2319
+ }
2320
+ /**
2321
+ * Pick the delay before the next retry.
2322
+ * - If the server sent `retryAfter` (seconds), honor it (capped by
2323
+ * `maxRetryAfterMs`) — returns null if the server wait exceeds the
2324
+ * cap, signalling the caller should give up.
2325
+ * - Otherwise: exponential backoff with ±20% jitter, capped at
2326
+ * `maxDelayMs`.
2327
+ */
2328
+ computeBackoff(attempt, retryAfter) {
2329
+ if (retryAfter !== void 0) {
2330
+ const serverMs = retryAfter * 1e3;
2331
+ if (serverMs > this.retry.maxRetryAfterMs) return null;
2332
+ return serverMs;
2333
+ }
2334
+ const exp = this.retry.initialDelayMs * 2 ** (attempt - 1);
2335
+ const capped = Math.min(exp, this.retry.maxDelayMs);
2336
+ const jitter = capped * (0.8 + Math.random() * 0.4);
2337
+ return Math.round(jitter);
2338
+ }
2339
+ sleep(ms) {
2340
+ return new Promise((resolve) => setTimeout(resolve, ms));
2341
+ }
2342
+ async post(path, body) {
2343
+ const controller = new AbortController();
2344
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
2345
+ let response;
2346
+ try {
2347
+ response = await this.fetchImpl(`${this.url}${path}`, {
2348
+ method: "POST",
2349
+ headers: {
2350
+ "Content-Type": "application/json",
2351
+ "Authorization": `Bearer ${this.apiKey}`,
2352
+ "X-Issuer-Id": this.issuerId
2353
+ },
2354
+ body: JSON.stringify(body, this.bigintReplacer),
2355
+ signal: controller.signal
2356
+ });
2357
+ } catch (err) {
2358
+ if (err.name === "AbortError") {
2359
+ throw new PafiBackendError(
2360
+ "TIMEOUT",
2361
+ `PAFI Backend request timed out after ${this.timeoutMs}ms`,
2362
+ 0
2363
+ );
2364
+ }
2365
+ throw new PafiBackendError(
2366
+ "NETWORK_ERROR",
2367
+ `PAFI Backend unreachable: ${err.message}`,
2368
+ 0
2369
+ );
2370
+ } finally {
2371
+ clearTimeout(timeoutId);
2372
+ }
2373
+ const text = await response.text();
2374
+ if (!response.ok) {
2375
+ let code = "INTERNAL_ERROR";
2376
+ let message = text || response.statusText;
2377
+ let details;
2378
+ let retryAfter;
2379
+ let serverSafeToRetry;
2380
+ try {
2381
+ const parsed = JSON.parse(text);
2382
+ code = parsed.code ?? code;
2383
+ message = parsed.message ?? message;
2384
+ details = parsed.details;
2385
+ if (typeof parsed.retryAfter === "number") retryAfter = parsed.retryAfter;
2386
+ if (typeof parsed.safeToRetry === "boolean") serverSafeToRetry = parsed.safeToRetry;
2387
+ } catch {
2388
+ }
2389
+ throw new PafiBackendError(code, message, response.status, details, {
2390
+ ...retryAfter !== void 0 ? { retryAfter } : {},
2391
+ ...serverSafeToRetry !== void 0 ? { safeToRetry: serverSafeToRetry } : {}
2392
+ });
2393
+ }
2394
+ return JSON.parse(text, this.bigintReviver);
2395
+ }
2396
+ /** JSON replacer that stringifies bigints. Paired with bigintReviver. */
2397
+ bigintReplacer = (_key, value) => {
2398
+ return typeof value === "bigint" ? value.toString() : value;
2399
+ };
2400
+ /**
2401
+ * JSON reviver that coerces specific numeric-string fields back to
2402
+ * bigint. The server must send these fields as decimal strings.
2403
+ */
2404
+ bigintReviver = (key, value) => {
2405
+ 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)) {
2406
+ return BigInt(value);
2407
+ }
2408
+ return value;
2409
+ };
2410
+ };
2411
+
1639
2412
  // src/config.ts
1640
- import { getAddress as getAddress6 } from "viem";
2413
+ import { getAddress as getAddress9 } from "viem";
1641
2414
  function createIssuerService(config) {
1642
2415
  if (!config.provider) {
1643
2416
  throw new Error("createIssuerService: provider is required");
@@ -1663,7 +2436,7 @@ function createIssuerService(config) {
1663
2436
  "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
1664
2437
  );
1665
2438
  }
1666
- const tokenAddresses = rawAddresses.map((a) => getAddress6(a));
2439
+ const tokenAddresses = rawAddresses.map((a) => getAddress9(a));
1667
2440
  const ledger = config.ledger ?? new MemoryPointLedger();
1668
2441
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
1669
2442
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -1693,8 +2466,7 @@ function createIssuerService(config) {
1693
2466
  if (config.fee) {
1694
2467
  feeManager = new FeeManager({
1695
2468
  ...config.fee,
1696
- provider: config.provider,
1697
- operatorWallet: config.operatorWallet
2469
+ provider: config.provider
1698
2470
  });
1699
2471
  }
1700
2472
  const gatewayConfig = {
@@ -1769,6 +2541,8 @@ var PAFI_ISSUER_SDK_VERSION = "0.1.0";
1769
2541
  export {
1770
2542
  AuthError,
1771
2543
  AuthService,
2544
+ BalanceAggregator,
2545
+ BurnIndexer,
1772
2546
  DefaultPolicyEngine,
1773
2547
  FeeManager,
1774
2548
  InMemoryCursorStore,
@@ -1779,10 +2553,16 @@ export {
1779
2553
  MintingGatewayError,
1780
2554
  NonceManager,
1781
2555
  PAFI_ISSUER_SDK_VERSION,
2556
+ PTRedeemError,
2557
+ PTRedeemHandler,
2558
+ PafiBackendClient,
2559
+ PafiBackendError,
1782
2560
  PointIndexer,
1783
2561
  PrivateKeySigner,
1784
2562
  RelayError,
1785
2563
  RelayService,
2564
+ TopUpRedemptionError,
2565
+ TopUpRedemptionHandler,
1786
2566
  authenticateRequest,
1787
2567
  createIssuerService,
1788
2568
  createSubgraphNativeUsdtQuoter,