@pafi-dev/issuer 0.3.0-beta.3 → 0.3.0-beta.5

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
@@ -587,177 +587,16 @@ import {
587
587
  erc20Abi
588
588
  } from "viem";
589
589
  import {
590
- relayAbi,
591
- encodeMintAndSwap,
592
- simulateMintAndSwap as coreSimulateMintAndSwap,
593
- SimulationError,
594
590
  POINT_TOKEN_V2_ABI,
595
- buildPartialUserOperation
591
+ buildPartialUserOperation,
592
+ signMintRequest as signMintRequest2
596
593
  } from "@pafi-dev/core";
597
- var DEFAULT_CONFIRMATION_TIMEOUT_MS = 6e4;
598
594
  var RelayService = class {
599
- relayAddress;
600
- operatorWallet;
601
- provider;
602
- confirmationTimeoutMs;
603
- simulateBeforeSubmit;
604
- constructor(config) {
605
- if (!config.relayAddress) {
606
- throw new Error("RelayService: relayAddress is required");
607
- }
608
- if (!config.operatorWallet) {
609
- throw new Error("RelayService: operatorWallet is required");
610
- }
611
- this.relayAddress = config.relayAddress;
612
- this.operatorWallet = config.operatorWallet;
613
- if (config.provider) this.provider = config.provider;
614
- this.confirmationTimeoutMs = config.confirmationTimeoutMs ?? DEFAULT_CONFIRMATION_TIMEOUT_MS;
615
- this.simulateBeforeSubmit = config.simulateBeforeSubmit ?? config.provider !== void 0;
616
- }
617
- /** Address the operator wallet is broadcasting from (for logging). */
618
- operatorAddress() {
619
- return this.operatorWallet.account?.address;
620
- }
621
- /**
622
- * Build calldata for the Relay `mintAndSwap` function. Kept public so
623
- * callers (e.g. the MintingGateway) can log or persist the encoded call
624
- * for audit before broadcasting.
625
- */
626
- encodeCall(params) {
627
- try {
628
- return encodeMintAndSwap(params.mint, params.swap);
629
- } catch (err) {
630
- throw new RelayError(
631
- "ENCODE_FAILED",
632
- `Failed to encode mintAndSwap calldata: ${errorMessage(err)}`,
633
- err
634
- );
635
- }
636
- }
637
- /**
638
- * Submit a `mintAndSwap` transaction. Flow:
639
- *
640
- * 1. (optional) pre-flight simulate via provider
641
- * 2. writeContract through the operator wallet
642
- * 3. (optional) wait for the receipt and surface gasUsed / status
643
- *
644
- * Throws a typed `RelayError` on any failure so the MintingGateway can
645
- * decide whether to release the ledger lock (`SUBMIT_FAILED` and
646
- * `SIMULATION_FAILED` are safe to release; `TX_REVERTED` and `TIMEOUT`
647
- * need manual review because the tx may still land).
648
- *
649
- * @deprecated Since 0.3.0 — will be replaced by `prepareMint()` +
650
- * `prepareBurn()` in the v1.4 sponsored-UserOp flow. The SC team
651
- * still needs to finalize Relayer v2 ABI before the replacements
652
- * can ship (blocker B1). Kept for v0.2.x consumers. Removed in 2.0.
653
- */
654
- async submitMintAndSwap(params) {
655
- if (this.simulateBeforeSubmit && this.provider) {
656
- const operatorAddr = this.operatorWallet.account?.address;
657
- if (operatorAddr) {
658
- try {
659
- await coreSimulateMintAndSwap(
660
- this.provider,
661
- this.relayAddress,
662
- params.mint,
663
- params.swap,
664
- operatorAddr
665
- );
666
- } catch (err) {
667
- const reason = err instanceof SimulationError ? err.reason : errorMessage(err);
668
- throw new RelayError(
669
- "SIMULATION_FAILED",
670
- `mintAndSwap would revert: ${reason}`,
671
- err
672
- );
673
- }
674
- }
675
- }
676
- let txHash;
677
- try {
678
- txHash = await this.operatorWallet.writeContract({
679
- address: this.relayAddress,
680
- abi: relayAbi,
681
- functionName: "mintAndSwap",
682
- args: [params.mint, params.swap],
683
- ...this.operatorWallet.account ? { account: this.operatorWallet.account } : {}
684
- });
685
- } catch (err) {
686
- throw new RelayError(
687
- "SUBMIT_FAILED",
688
- `Failed to broadcast mintAndSwap: ${errorMessage(err)}`,
689
- err
690
- );
691
- }
692
- if (!this.provider) {
693
- return { txHash };
694
- }
695
- try {
696
- const receipt = await this.provider.waitForTransactionReceipt({
697
- hash: txHash,
698
- timeout: this.confirmationTimeoutMs
699
- });
700
- if (receipt.status !== "success") {
701
- throw new RelayError(
702
- "TX_REVERTED",
703
- `mintAndSwap reverted on-chain (tx=${txHash})`
704
- );
705
- }
706
- return {
707
- txHash,
708
- blockNumber: receipt.blockNumber,
709
- gasUsed: receipt.gasUsed,
710
- status: receipt.status
711
- };
712
- } catch (err) {
713
- if (err instanceof RelayError) throw err;
714
- throw new RelayError(
715
- "TIMEOUT",
716
- `Timed out waiting for mintAndSwap receipt (tx=${txHash}): ${errorMessage(err)}`,
717
- err
718
- );
719
- }
720
- }
721
- // ==========================================================================
722
- // v1.4 — Sponsored UserOp preparation (beta with mocked SC contracts)
723
- // ==========================================================================
724
- //
725
- // These two methods build unsigned `PartialUserOperation` payloads for
726
- // the Frontend to sign (via Privy) and submit to the Bundler. The
727
- // Issuer Backend no longer broadcasts — that's the Frontend's job.
728
- //
729
- // Uses mocked Relayer v2 + PointToken ABIs from `@pafi-dev/core/contracts`.
730
- // When SC delivers real ABIs, the imports swap but these method bodies
731
- // stay the same (calldata encoder is ABI-driven).
732
- // ==========================================================================
733
- /**
734
- * Build an unsigned UserOp for Scenario 1 (Mint).
735
- *
736
- * Flow:
737
- * 1. Encode `Relayer.mint(request, userSig, issuerSig)` as the inner call
738
- * 2. Optionally append a PT fee transfer from user → feeRecipient
739
- * (fee recovery happens on-chain via BatchExecutor, not via an
740
- * operator wallet)
741
- * 3. Wrap all inner calls into `BatchExecutor.execute(calls[])`
742
- * 4. Return a `PartialUserOperation` ready for:
743
- * - gas estimation (Bundler)
744
- * - paymaster sponsorship (PAFI Backend)
745
- * - user signature (Privy)
746
- */
747
595
  /**
748
- * Build an unsigned UserOp for Scenario 1 (Mint) — direct
749
- * `PointToken.mint(amount)` flow (v1.4 post-Relayer-removal).
750
- *
751
- * `msg.sender` inside the batch = user EOA via EIP-7702 delegation.
752
- * `PointToken.mint` checks the caller is on its `minters` allowlist
753
- * (gg56 BE pre-validates this off-chain to avoid wasted gas).
754
- *
755
- * Optional PT fee transfer is appended after the mint when
756
- * `feeAmount > 0` — this is application-level fee recovery (no
757
- * Relayer doing it for us). The user must end up with `amount - fee`
758
- * net PT after the batch executes.
596
+ * Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
597
+ * `PointToken.mint(to, amount, deadline, minterSig)`.
759
598
  */
760
- prepareMint(params) {
599
+ async prepareMint(params) {
761
600
  if (!params.batchExecutorAddress) {
762
601
  throw new RelayError(
763
602
  "ENCODE_FAILED",
@@ -776,12 +615,41 @@ var RelayService = class {
776
615
  if (params.amount <= 0n) {
777
616
  throw new RelayError("ENCODE_FAILED", "prepareMint: amount must be positive");
778
617
  }
618
+ if (!params.issuerSignerWallet) {
619
+ throw new RelayError(
620
+ "ENCODE_FAILED",
621
+ "prepareMint: issuerSignerWallet required (for MintRequest EIP-712 signature)"
622
+ );
623
+ }
624
+ if (params.deadline <= 0n) {
625
+ throw new RelayError("ENCODE_FAILED", "prepareMint: deadline must be positive");
626
+ }
627
+ let minterSig;
628
+ try {
629
+ const sig = await signMintRequest2(
630
+ params.issuerSignerWallet,
631
+ params.domain,
632
+ {
633
+ to: params.userAddress,
634
+ amount: params.amount,
635
+ nonce: params.mintRequestNonce,
636
+ deadline: params.deadline
637
+ }
638
+ );
639
+ minterSig = sig.serialized;
640
+ } catch (err) {
641
+ throw new RelayError(
642
+ "ENCODE_FAILED",
643
+ `prepareMint: failed to sign MintRequest: ${errorMessage(err)}`,
644
+ err
645
+ );
646
+ }
779
647
  let mintCallData;
780
648
  try {
781
649
  mintCallData = encodeFunctionData({
782
650
  abi: POINT_TOKEN_V2_ABI,
783
651
  functionName: "mint",
784
- args: [params.amount]
652
+ args: [params.userAddress, params.amount, params.deadline, minterSig]
785
653
  });
786
654
  } catch (err) {
787
655
  throw new RelayError(
@@ -829,13 +697,12 @@ var RelayService = class {
829
697
  * Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
830
698
  *
831
699
  * Two modes:
832
- * - `mode: 'burn'` — direct `PointToken.burn(amount)`; `msg.sender`
833
- * via EIP-7702 delegation is the user, so no signature needed
834
- * on-chain (the BurnConsent was already verified off-chain by
835
- * the issuer backend before we got here)
836
- * - `mode: 'burnWithSig'` `PointToken.burnWithSig(consent, sig)`;
837
- * used when the issuer hasn't verified the consent and the
838
- * contract has to do it on-chain
700
+ * - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only
701
+ * usable if the caller (via EIP-7702) is whitelisted as a burner.
702
+ * Rare in v1.4; kept for admin/operator tools.
703
+ * - `mode: 'burnWithSig'` `PointToken.burn(from, amount, deadline,
704
+ * burnerSig)`. Caller provides a pre-signed `BurnRequest` + sig
705
+ * bytes (typically from `PTRedeemHandler`).
839
706
  */
840
707
  prepareBurn(params) {
841
708
  if (!params.pointTokenAddress) {
@@ -850,19 +717,24 @@ var RelayService = class {
850
717
  let burnCallData;
851
718
  try {
852
719
  if (params.mode === "burnWithSig") {
853
- if (!params.burnConsent || !params.consentSignature) {
854
- throw new Error("burnWithSig requires burnConsent + consentSignature");
720
+ if (!params.burnRequest || !params.burnerSignature) {
721
+ throw new Error("burnWithSig requires burnRequest + burnerSignature");
855
722
  }
856
723
  burnCallData = encodeFunctionData({
857
724
  abi: POINT_TOKEN_V2_ABI,
858
- functionName: "burnWithSig",
859
- args: [params.burnConsent, params.consentSignature]
725
+ functionName: "burn",
726
+ args: [
727
+ params.burnRequest.from,
728
+ params.burnRequest.amount,
729
+ params.burnRequest.deadline,
730
+ params.burnerSignature
731
+ ]
860
732
  });
861
733
  } else {
862
734
  burnCallData = encodeFunctionData({
863
735
  abi: POINT_TOKEN_V2_ABI,
864
736
  functionName: "burn",
865
- args: [params.amount]
737
+ args: [params.userAddress, params.amount]
866
738
  });
867
739
  }
868
740
  } catch (err) {
@@ -932,255 +804,6 @@ var FeeManager = class {
932
804
  }
933
805
  };
934
806
 
935
- // src/gateway/types.ts
936
- var MintingGatewayError = class extends Error {
937
- code;
938
- /**
939
- * True if the ledger lock was released before this error was thrown,
940
- * meaning the user can safely retry. False means the funds are still
941
- * locked (e.g. tx may still land on-chain) and retry would double-spend.
942
- */
943
- safeToRetry;
944
- cause;
945
- constructor(code, message, opts) {
946
- super(message);
947
- this.name = "MintingGatewayError";
948
- this.code = code;
949
- this.safeToRetry = opts.safeToRetry;
950
- if (opts.cause !== void 0) this.cause = opts.cause;
951
- }
952
- };
953
-
954
- // src/gateway/mintingGateway.ts
955
- import {
956
- verifyReceiverConsent,
957
- encodeExtData
958
- } from "@pafi-dev/core";
959
- var DEFAULT_LOCK_BUFFER_MS = 6e4;
960
- var MintingGateway = class {
961
- ledger;
962
- policy;
963
- signer;
964
- relayService;
965
- now;
966
- defaultLockBufferMs;
967
- constructor(config) {
968
- if (!config.ledger) throw new Error("MintingGateway: ledger required");
969
- if (!config.policy) throw new Error("MintingGateway: policy required");
970
- if (!config.signer) throw new Error("MintingGateway: signer required");
971
- if (!config.relayService)
972
- throw new Error("MintingGateway: relayService required");
973
- this.ledger = config.ledger;
974
- this.policy = config.policy;
975
- this.signer = config.signer;
976
- this.relayService = config.relayService;
977
- this.now = config.now ?? (() => Date.now());
978
- this.defaultLockBufferMs = config.defaultLockBufferMs ?? DEFAULT_LOCK_BUFFER_MS;
979
- }
980
- /**
981
- * @deprecated Since 0.3.0 — will be renamed to `processMint()` once
982
- * the SC team finalizes Relayer v2 ABI. The new flow drops the
983
- * swap steps entirely (no more single-call mint+swap); users swap
984
- * separately on PAFI Web. Kept here for v0.2.x consumers. Removed in 2.0.
985
- */
986
- async processMintAndCashOut(request) {
987
- const { receiverConsent, receiverSignature } = request;
988
- if (!receiverConsent || !receiverSignature) {
989
- throw new MintingGatewayError(
990
- "INVALID_REQUEST",
991
- "receiverConsent and receiverSignature are required",
992
- { safeToRetry: true }
993
- );
994
- }
995
- if (receiverConsent.amount <= 0n) {
996
- throw new MintingGatewayError(
997
- "INVALID_REQUEST",
998
- "consent amount must be positive",
999
- { safeToRetry: true }
1000
- );
1001
- }
1002
- if (receiverConsent.originalReceiver !== request.userAddress) {
1003
- throw new MintingGatewayError(
1004
- "INVALID_REQUEST",
1005
- "consent.originalReceiver must equal request.userAddress",
1006
- { safeToRetry: true }
1007
- );
1008
- }
1009
- const nowSec = BigInt(Math.floor(this.now() / 1e3));
1010
- if (receiverConsent.deadline <= nowSec) {
1011
- throw new MintingGatewayError(
1012
- "CONSENT_EXPIRED",
1013
- "ReceiverConsent deadline has already passed",
1014
- { safeToRetry: true }
1015
- );
1016
- }
1017
- const consentResult = await verifyReceiverConsent(
1018
- request.domain,
1019
- receiverConsent,
1020
- receiverSignature,
1021
- request.userAddress
1022
- );
1023
- if (!consentResult.isValid) {
1024
- throw new MintingGatewayError(
1025
- "INVALID_CONSENT_SIGNATURE",
1026
- `ReceiverConsent signature did not recover to ${request.userAddress}`,
1027
- { safeToRetry: true }
1028
- );
1029
- }
1030
- const policyDecision = await this.policy.evaluate({
1031
- userAddress: request.userAddress,
1032
- amount: receiverConsent.amount,
1033
- pointTokenAddress: request.pointTokenAddress,
1034
- chainId: request.chainId
1035
- });
1036
- if (!policyDecision.approved) {
1037
- const code = policyDecision.reason?.toLowerCase().includes("insufficient") ? "INSUFFICIENT_BALANCE" : "POLICY_REJECTED";
1038
- throw new MintingGatewayError(
1039
- code,
1040
- policyDecision.reason ?? "Minting request rejected by policy engine",
1041
- { safeToRetry: true }
1042
- );
1043
- }
1044
- const lockDurationMs = request.lockDurationMs ?? this.computeLockDurationMs(receiverConsent.deadline);
1045
- let lockId;
1046
- try {
1047
- lockId = await this.ledger.lockForMinting(
1048
- request.userAddress,
1049
- receiverConsent.amount,
1050
- lockDurationMs,
1051
- request.pointTokenAddress
1052
- );
1053
- } catch (err) {
1054
- throw new MintingGatewayError(
1055
- "INSUFFICIENT_BALANCE",
1056
- `Failed to lock ledger balance: ${errorMessage2(err)}`,
1057
- { safeToRetry: true, cause: err }
1058
- );
1059
- }
1060
- try {
1061
- let minterSignature;
1062
- try {
1063
- minterSignature = await this.signer.signMintRequest(request.domain, {
1064
- to: request.userAddress,
1065
- amount: receiverConsent.amount,
1066
- nonce: receiverConsent.nonce,
1067
- deadline: receiverConsent.deadline
1068
- });
1069
- } catch (err) {
1070
- await this.releaseLockSafely(lockId);
1071
- throw new MintingGatewayError(
1072
- "SIGNER_FAILED",
1073
- `Issuer signer failed: ${errorMessage2(err)}`,
1074
- { safeToRetry: true, cause: err }
1075
- );
1076
- }
1077
- const mintParams = {
1078
- pointToken: request.pointTokenAddress,
1079
- receiver: request.userAddress,
1080
- amount: receiverConsent.amount,
1081
- deadline: receiverConsent.deadline,
1082
- minterSig: minterSignature.serialized,
1083
- receiverSig: receiverSignature,
1084
- extData: receiverConsent.extData
1085
- };
1086
- const swapParams = {
1087
- path: request.swapPath,
1088
- deadline: request.swapDeadline
1089
- };
1090
- let relayResult;
1091
- try {
1092
- relayResult = await this.relayService.submitMintAndSwap({
1093
- mint: mintParams,
1094
- swap: swapParams
1095
- });
1096
- } catch (err) {
1097
- await this.handleRelayFailure(err, lockId);
1098
- }
1099
- const result = {
1100
- txHash: relayResult.txHash,
1101
- lockId
1102
- };
1103
- if (relayResult.blockNumber !== void 0) {
1104
- result.blockNumber = relayResult.blockNumber;
1105
- }
1106
- if (relayResult.gasUsed !== void 0) {
1107
- result.gasUsed = relayResult.gasUsed;
1108
- }
1109
- return result;
1110
- } catch (err) {
1111
- if (err instanceof MintingGatewayError) throw err;
1112
- await this.releaseLockSafely(lockId);
1113
- throw new MintingGatewayError(
1114
- "RELAY_SUBMIT_FAILED",
1115
- `Unexpected error: ${errorMessage2(err)}`,
1116
- { safeToRetry: true, cause: err }
1117
- );
1118
- }
1119
- }
1120
- // ---------------------------------------------------------------------------
1121
- // Internals
1122
- // ---------------------------------------------------------------------------
1123
- computeLockDurationMs(consentDeadlineSec) {
1124
- const nowMs = this.now();
1125
- const deadlineMs = Number(consentDeadlineSec) * 1e3;
1126
- const remaining = Math.max(0, deadlineMs - nowMs);
1127
- return remaining + this.defaultLockBufferMs;
1128
- }
1129
- /**
1130
- * Map a RelayError to a MintingGatewayError, releasing the lock only
1131
- * when the tx definitely did not land. `TX_REVERTED` and `TIMEOUT`
1132
- * leave the lock in place because the tx may still be in the mempool
1133
- * or already mined — releasing would enable a double-spend on retry.
1134
- * Always throws.
1135
- */
1136
- async handleRelayFailure(err, lockId) {
1137
- if (err instanceof RelayError) {
1138
- switch (err.code) {
1139
- case "ENCODE_FAILED":
1140
- case "SIMULATION_FAILED":
1141
- case "SUBMIT_FAILED":
1142
- case "NOT_CONFIGURED":
1143
- await this.releaseLockSafely(lockId);
1144
- throw new MintingGatewayError(
1145
- err.code === "SIMULATION_FAILED" ? "RELAY_SIMULATION_FAILED" : "RELAY_SUBMIT_FAILED",
1146
- err.message,
1147
- { safeToRetry: true, cause: err }
1148
- );
1149
- case "TX_REVERTED":
1150
- throw new MintingGatewayError("RELAY_REVERTED", err.message, {
1151
- safeToRetry: false,
1152
- cause: err
1153
- });
1154
- case "TIMEOUT":
1155
- throw new MintingGatewayError("RELAY_TIMEOUT", err.message, {
1156
- safeToRetry: false,
1157
- cause: err
1158
- });
1159
- }
1160
- }
1161
- await this.releaseLockSafely(lockId);
1162
- throw new MintingGatewayError(
1163
- "RELAY_SUBMIT_FAILED",
1164
- `Unexpected relay error: ${errorMessage2(err)}`,
1165
- { safeToRetry: true, cause: err }
1166
- );
1167
- }
1168
- /**
1169
- * Release a lock, swallowing any secondary error. We never want a lock
1170
- * release failure to mask the original error — the lock will auto-expire
1171
- * via TTL anyway.
1172
- */
1173
- async releaseLockSafely(lockId) {
1174
- try {
1175
- await this.ledger.releaseLock(lockId);
1176
- } catch {
1177
- }
1178
- }
1179
- };
1180
- function errorMessage2(err) {
1181
- return err instanceof Error ? err.message : String(err);
1182
- }
1183
-
1184
807
  // src/indexer/types.ts
1185
808
  var InMemoryCursorStore = class {
1186
809
  cursor;
@@ -1535,7 +1158,6 @@ import {
1535
1158
  } from "@pafi-dev/core";
1536
1159
  var IssuerApiHandlers = class {
1537
1160
  authService;
1538
- gateway;
1539
1161
  ledger;
1540
1162
  provider;
1541
1163
  /**
@@ -1554,7 +1176,6 @@ var IssuerApiHandlers = class {
1554
1176
  poolsProvider;
1555
1177
  constructor(config) {
1556
1178
  this.authService = config.authService;
1557
- this.gateway = config.gateway;
1558
1179
  this.ledger = config.ledger;
1559
1180
  this.provider = config.provider;
1560
1181
  const raw = config.pointTokenAddresses && config.pointTokenAddresses.length > 0 ? config.pointTokenAddresses : config.pointTokenAddress ? [config.pointTokenAddress] : [];
@@ -1736,55 +1357,13 @@ var IssuerApiHandlers = class {
1736
1357
  }
1737
1358
  };
1738
1359
  }
1739
- /**
1740
- * `POST /claim-and-swap`
1741
- *
1742
- * @deprecated Since 0.3.0 — the single-call mint-then-swap flow is
1743
- * retired in v1.4. Use the new `handleClaim()` (mint only) and let
1744
- * the user swap separately on PAFI Web. See
1745
- * [V1.4_V1.5_OVERVIEW.md §4] for the new scenario model. Will be
1746
- * removed in 2.0.
1747
- *
1748
- * Legacy behavior: the terminal handler forwards the verified
1749
- * consent to the MintingGateway, which runs the 11-step flow.
1750
- */
1751
- async handleClaimAndSwap(userAddress, request) {
1752
- if (request.chainId !== this.chainId) {
1753
- throw new Error(
1754
- `handleClaimAndSwap: unsupported chainId ${request.chainId}`
1755
- );
1756
- }
1757
- const pointToken = getAddress6(request.pointTokenAddress);
1758
- if (!this.supportedTokens.has(pointToken)) {
1759
- throw new Error(
1760
- `handleClaimAndSwap: unsupported pointToken ${pointToken}`
1761
- );
1762
- }
1763
- const result = await this.gateway.processMintAndCashOut({
1764
- userAddress: getAddress6(userAddress),
1765
- pointTokenAddress: pointToken,
1766
- chainId: request.chainId,
1767
- domain: request.domain,
1768
- receiverConsent: request.receiverConsent,
1769
- receiverSignature: request.receiverSignature,
1770
- swapPath: request.swapPath,
1771
- swapDeadline: request.swapDeadline
1772
- });
1773
- const response = {
1774
- txHash: result.txHash,
1775
- lockId: result.lockId
1776
- };
1777
- if (result.blockNumber !== void 0)
1778
- response.blockNumber = result.blockNumber;
1779
- if (result.gasUsed !== void 0) response.gasUsed = result.gasUsed;
1780
- return response;
1781
- }
1782
1360
  };
1783
1361
 
1784
1362
  // src/api/handlers/ptRedeemHandler.ts
1785
1363
  import { getAddress as getAddress7 } from "viem";
1786
- import { verifyBurnConsent } from "@pafi-dev/core";
1364
+ import { signBurnRequest, POINT_TOKEN_V2_ABI as POINT_TOKEN_V2_ABI2 } from "@pafi-dev/core";
1787
1365
  var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
1366
+ var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
1788
1367
  var PTRedeemError = class extends Error {
1789
1368
  constructor(code, message) {
1790
1369
  super(message);
@@ -1796,11 +1375,14 @@ var PTRedeemError = class extends Error {
1796
1375
  var PTRedeemHandler = class {
1797
1376
  ledger;
1798
1377
  relayService;
1378
+ provider;
1799
1379
  pointTokenAddress;
1800
1380
  batchExecutorAddress;
1801
1381
  chainId;
1802
1382
  domain;
1383
+ burnerSignerWallet;
1803
1384
  redeemLockDurationMs;
1385
+ signatureDeadlineSeconds;
1804
1386
  now;
1805
1387
  constructor(config) {
1806
1388
  if (!config.ledger.reservePendingCredit) {
@@ -1809,46 +1391,68 @@ var PTRedeemHandler = class {
1809
1391
  "PTRedeemHandler requires a ledger that implements reservePendingCredit() (v0.3.0+)"
1810
1392
  );
1811
1393
  }
1394
+ if (!config.burnerSignerWallet) {
1395
+ throw new PTRedeemError(
1396
+ "SIGNING_FAILED",
1397
+ "PTRedeemHandler requires burnerSignerWallet (issuer burner signer)"
1398
+ );
1399
+ }
1812
1400
  this.ledger = config.ledger;
1813
1401
  this.relayService = config.relayService;
1402
+ this.provider = config.provider;
1814
1403
  this.pointTokenAddress = getAddress7(config.pointTokenAddress);
1815
1404
  this.batchExecutorAddress = getAddress7(config.batchExecutorAddress);
1816
1405
  this.chainId = config.chainId;
1817
1406
  this.domain = config.domain;
1407
+ this.burnerSignerWallet = config.burnerSignerWallet;
1818
1408
  this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
1409
+ this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
1819
1410
  this.now = config.now ?? (() => Date.now());
1820
1411
  }
1821
1412
  async handle(request) {
1822
1413
  if (request.amount <= 0n) {
1823
- throw new PTRedeemError("INVALID_CONSENT", "redeem amount must be positive");
1824
- }
1825
- if (request.consent.amount !== request.amount) {
1826
- throw new PTRedeemError(
1827
- "AMOUNT_MISMATCH",
1828
- `consent.amount (${request.consent.amount}) must match request.amount (${request.amount})`
1829
- );
1414
+ throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
1830
1415
  }
1831
- const nowSeconds = BigInt(Math.floor(this.now() / 1e3));
1832
- if (request.consent.deadline <= nowSeconds) {
1416
+ let burnNonce;
1417
+ try {
1418
+ burnNonce = await this.provider.readContract({
1419
+ address: this.pointTokenAddress,
1420
+ abi: POINT_TOKEN_V2_ABI2,
1421
+ functionName: "burnRequestNonces",
1422
+ args: [request.userAddress]
1423
+ });
1424
+ } catch (err) {
1833
1425
  throw new PTRedeemError(
1834
- "EXPIRED_CONSENT",
1835
- `consent deadline (${request.consent.deadline}) already passed`
1426
+ "NONCE_READ_FAILED",
1427
+ `failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
1836
1428
  );
1837
1429
  }
1838
- const verification = await verifyBurnConsent(
1839
- {
1840
- name: this.domain.name,
1841
- chainId: this.chainId,
1842
- verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
1843
- },
1844
- request.consent,
1845
- request.consentSignature,
1846
- request.userAddress
1430
+ const deadline = BigInt(
1431
+ Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
1847
1432
  );
1848
- if (!verification.isValid) {
1433
+ const domain = {
1434
+ name: this.domain.name,
1435
+ chainId: this.chainId,
1436
+ verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
1437
+ };
1438
+ const burnRequest = {
1439
+ from: request.userAddress,
1440
+ amount: request.amount,
1441
+ nonce: burnNonce,
1442
+ deadline
1443
+ };
1444
+ let burnerSignature;
1445
+ try {
1446
+ const sig = await signBurnRequest(
1447
+ this.burnerSignerWallet,
1448
+ domain,
1449
+ burnRequest
1450
+ );
1451
+ burnerSignature = sig.serialized;
1452
+ } catch (err) {
1849
1453
  throw new PTRedeemError(
1850
- "SIGNATURE_MISMATCH",
1851
- `signer mismatch \u2014 expected ${request.userAddress}, got ${verification.recoveredAddress}`
1454
+ "SIGNING_FAILED",
1455
+ `failed to sign BurnRequest: ${err instanceof Error ? err.message : String(err)}`
1852
1456
  );
1853
1457
  }
1854
1458
  const lockId = await this.ledger.reservePendingCredit(
@@ -1863,29 +1467,17 @@ var PTRedeemHandler = class {
1863
1467
  aaNonce: request.aaNonce,
1864
1468
  pointTokenAddress: this.pointTokenAddress,
1865
1469
  batchExecutorAddress: this.batchExecutorAddress,
1866
- burnConsent: request.consent,
1867
- consentSignature: parseSigStruct(request.consentSignature)
1470
+ burnRequest,
1471
+ burnerSignature
1868
1472
  });
1869
1473
  return {
1870
1474
  lockId,
1871
1475
  userOp,
1872
- expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3)
1476
+ expiresInSeconds: Math.floor(this.redeemLockDurationMs / 1e3),
1477
+ signatureDeadline: deadline
1873
1478
  };
1874
1479
  }
1875
1480
  };
1876
- function parseSigStruct(serialized) {
1877
- const raw = serialized.slice(2);
1878
- if (raw.length !== 130) {
1879
- throw new PTRedeemError(
1880
- "INVALID_CONSENT",
1881
- `signature must be 65 bytes, got ${raw.length / 2}`
1882
- );
1883
- }
1884
- const r = `0x${raw.slice(0, 64)}`;
1885
- const s = `0x${raw.slice(64, 128)}`;
1886
- const v = parseInt(raw.slice(128, 130), 16);
1887
- return { v, r, s };
1888
- }
1889
1481
 
1890
1482
  // src/api/handlers/topUpRedemptionHandler.ts
1891
1483
  import { getAddress as getAddress8 } from "viem";
@@ -1931,24 +1523,10 @@ var TopUpRedemptionHandler = class {
1931
1523
  shortfall
1932
1524
  };
1933
1525
  }
1934
- if (request.redeemRequest.consent.amount < shortfall) {
1935
- throw new TopUpRedemptionError(
1936
- "CONSENT_AMOUNT_TOO_LOW",
1937
- `consent.amount (${request.redeemRequest.consent.amount}) must cover shortfall (${shortfall})`
1938
- );
1939
- }
1940
- if (request.redeemRequest.consent.amount !== shortfall) {
1941
- throw new TopUpRedemptionError(
1942
- "CONSENT_AMOUNT_TOO_LOW",
1943
- `consent.amount (${request.redeemRequest.consent.amount}) must equal shortfall (${shortfall}) exactly \u2014 re-sign with correct amount`
1944
- );
1945
- }
1946
1526
  const redeem = await this.ptRedeemHandler.handle({
1947
1527
  userAddress: request.userAddress,
1948
1528
  amount: shortfall,
1949
- consent: request.redeemRequest.consent,
1950
- consentSignature: request.redeemRequest.consentSignature,
1951
- aaNonce: request.redeemRequest.aaNonce
1529
+ aaNonce: request.aaNonce
1952
1530
  });
1953
1531
  return {
1954
1532
  action: "TOP_UP_STARTED",
@@ -2441,15 +2019,6 @@ function createIssuerService(config) {
2441
2019
  if (!config.provider) {
2442
2020
  throw new Error("createIssuerService: provider is required");
2443
2021
  }
2444
- if (!config.operatorWallet) {
2445
- throw new Error("createIssuerService: operatorWallet is required");
2446
- }
2447
- if (!config.signer) {
2448
- throw new Error("createIssuerService: signer is required");
2449
- }
2450
- if (!config.relayAddress) {
2451
- throw new Error("createIssuerService: relayAddress is required");
2452
- }
2453
2022
  if (!config.auth?.jwtSecret) {
2454
2023
  throw new Error("createIssuerService: auth.jwtSecret is required");
2455
2024
  }
@@ -2476,18 +2045,7 @@ function createIssuerService(config) {
2476
2045
  authServiceConfig.jwtExpiresIn = config.auth.jwtExpiresIn;
2477
2046
  }
2478
2047
  const authService = new AuthService(authServiceConfig);
2479
- const relayServiceConfig = {
2480
- relayAddress: config.relayAddress,
2481
- operatorWallet: config.operatorWallet,
2482
- provider: config.provider
2483
- };
2484
- if (config.relay?.simulateBeforeSubmit !== void 0) {
2485
- relayServiceConfig.simulateBeforeSubmit = config.relay.simulateBeforeSubmit;
2486
- }
2487
- if (config.relay?.confirmationTimeoutMs !== void 0) {
2488
- relayServiceConfig.confirmationTimeoutMs = config.relay.confirmationTimeoutMs;
2489
- }
2490
- const relayService = new RelayService(relayServiceConfig);
2048
+ const relayService = new RelayService();
2491
2049
  let feeManager;
2492
2050
  if (config.fee) {
2493
2051
  feeManager = new FeeManager({
@@ -2495,16 +2053,6 @@ function createIssuerService(config) {
2495
2053
  provider: config.provider
2496
2054
  });
2497
2055
  }
2498
- const gatewayConfig = {
2499
- ledger,
2500
- policy,
2501
- signer: config.signer,
2502
- relayService
2503
- };
2504
- if (config.gateway?.defaultLockBufferMs !== void 0) {
2505
- gatewayConfig.defaultLockBufferMs = config.gateway.defaultLockBufferMs;
2506
- }
2507
- const gateway = new MintingGateway(gatewayConfig);
2508
2056
  const indexers = /* @__PURE__ */ new Map();
2509
2057
  for (const tokenAddress of tokenAddresses) {
2510
2058
  const indexerConfig = {
@@ -2532,7 +2080,6 @@ function createIssuerService(config) {
2532
2080
  const firstIndexer = indexers.get(tokenAddresses[0]);
2533
2081
  const handlersConfig = {
2534
2082
  authService,
2535
- gateway,
2536
2083
  ledger,
2537
2084
  provider: config.provider,
2538
2085
  pointTokenAddresses: tokenAddresses,
@@ -2552,10 +2099,8 @@ function createIssuerService(config) {
2552
2099
  sessionStore,
2553
2100
  ledger,
2554
2101
  policy,
2555
- signer: config.signer,
2556
2102
  relayService,
2557
2103
  feeManager,
2558
- gateway,
2559
2104
  indexers,
2560
2105
  indexer: firstIndexer,
2561
2106
  handlers
@@ -2575,8 +2120,6 @@ export {
2575
2120
  IssuerApiHandlers,
2576
2121
  MemoryPointLedger,
2577
2122
  MemorySessionStore,
2578
- MintingGateway,
2579
- MintingGatewayError,
2580
2123
  NonceManager,
2581
2124
  PAFI_ISSUER_SDK_VERSION,
2582
2125
  PTRedeemError,
@@ -2592,7 +2135,6 @@ export {
2592
2135
  authenticateRequest,
2593
2136
  createIssuerService,
2594
2137
  createSubgraphNativeUsdtQuoter,
2595
- createSubgraphPoolsProvider,
2596
- encodeExtData
2138
+ createSubgraphPoolsProvider
2597
2139
  };
2598
2140
  //# sourceMappingURL=index.js.map