@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.d.cts CHANGED
@@ -1,6 +1,5 @@
1
- import { Address, Hex, PublicClient, Chain } from 'viem';
2
- import { PointTokenDomainConfig, MintRequest, EIP712Signature, MintParams, SwapParams, PartialUserOperation, BurnConsent, SignatureStruct, ReceiverConsent, PathKey, PoolKey, SponsorshipScenario } from '@pafi-dev/core';
3
- export { encodeExtData } from '@pafi-dev/core';
1
+ import { Address, Hex, PublicClient, Chain, WalletClient } from 'viem';
2
+ import { PointTokenDomainConfig, MintRequest, EIP712Signature, PartialUserOperation, BurnRequest, ReceiverConsent, PathKey, PoolKey, SponsorshipScenario } from '@pafi-dev/core';
4
3
 
5
4
  /**
6
5
  * Lifecycle of a minting request as tracked by the issuer's point ledger.
@@ -449,175 +448,63 @@ declare class AuthError extends Error {
449
448
  }
450
449
 
451
450
  /**
452
- * Parameters for a single `mintAndSwap` relay submission. This is the
453
- * exact shape the Relay contract expects, with the two structs the
454
- * `@pafi/core` calldata helpers build. The RelayService wires these
455
- * through viem's `writeContract`.
456
- */
457
- interface SubmitMintAndSwapParams {
458
- mint: MintParams;
459
- swap: SwapParams;
460
- }
461
- /**
462
- * Result of a relay submission. `txHash` is returned immediately after
463
- * the tx is broadcast; `receipt` is present only when the caller opted to
464
- * wait for confirmation.
465
- */
466
- interface RelayResult {
467
- txHash: Hex;
468
- blockNumber?: bigint;
469
- gasUsed?: bigint;
470
- status?: "success" | "reverted";
471
- }
472
- /**
473
- * Errors raised by RelayService carry a `code` so the MintingGateway (or
474
- * any caller) can decide whether to release the ledger lock.
451
+ * Errors raised by RelayService carry a `code` so callers (handlers /
452
+ * gateway-less HTTP wrappers) can decide how to map them to HTTP status.
453
+ *
454
+ * v1.4 trimmed the error space to just encoding failures — the service
455
+ * no longer broadcasts transactions, so `SUBMIT_FAILED`, `TX_REVERTED`,
456
+ * `SIMULATION_FAILED`, and `TIMEOUT` all went away with the operator
457
+ * wallet. Paymaster/Bundler errors surface out-of-band on the FE.
475
458
  */
476
- type RelayErrorCode = "NOT_CONFIGURED" | "ENCODE_FAILED" | "SIMULATION_FAILED" | "SUBMIT_FAILED" | "TX_REVERTED" | "TIMEOUT";
459
+ type RelayErrorCode = "NOT_CONFIGURED" | "ENCODE_FAILED";
477
460
  declare class RelayError extends Error {
478
461
  readonly code: RelayErrorCode;
479
462
  readonly cause?: unknown;
480
463
  constructor(code: RelayErrorCode, message: string, cause?: unknown);
481
464
  }
482
- /**
483
- * Interface an operator wallet must satisfy for the RelayService. Matches
484
- * the subset of viem's `WalletClient` we actually call, so tests can pass
485
- * a minimal mock instead of a full client.
486
- */
487
- interface OperatorWalletLike {
488
- writeContract: (args: {
489
- address: Address;
490
- abi: readonly unknown[];
491
- functionName: string;
492
- args: readonly unknown[];
493
- account?: {
494
- address: Address;
495
- };
496
- }) => Promise<Hex>;
497
- account?: {
498
- address: Address;
499
- } | undefined;
500
- }
501
465
 
502
- interface RelayServiceConfig {
503
- /** Address of the deployed Relay contract (chain-specific). */
504
- relayAddress: Address;
505
- /** Operator wallet that pays gas and receives the operator fee. */
506
- operatorWallet: OperatorWalletLike;
507
- /**
508
- * Provider used for pre-flight simulation and receipt waiting. Optional —
509
- * if omitted, the service still broadcasts but returns only `txHash`
510
- * (caller is responsible for confirmation tracking).
511
- */
512
- provider?: PublicClient;
513
- /**
514
- * If a provider is supplied, wait up to this many milliseconds for a
515
- * receipt before timing out. Default: 60_000 (one minute).
516
- */
517
- confirmationTimeoutMs?: number;
518
- /**
519
- * Whether to run `simulateContract` before submitting. Catches most
520
- * reverts locally without wasting gas. Default: true (when provider is
521
- * supplied).
522
- */
523
- simulateBeforeSubmit?: boolean;
524
- }
525
466
  /**
526
- * Submits `mintAndSwap` transactions to the Relay contract on behalf of
527
- * the issuer. This is the single place the operator wallet is touched; the
528
- * MintingGateway calls into `submitMintAndSwap()` after it has signed the
529
- * MintRequest and verified the ReceiverConsent.
467
+ * Builds unsigned `PartialUserOperation` payloads for the v1.4 sponsored
468
+ * flow. The service is stateless and HTTP-client-free:
530
469
  *
531
- * The service is intentionally thin: calldata encoding stays in `@pafi/core`
532
- * (`encodeMintAndSwap`), so on-chain ABI changes only ripple through one
533
- * package.
470
+ * - `prepareMint` signs a `MintRequest` EIP-712 with the caller-supplied
471
+ * issuer signer wallet, then encodes `PointToken.mint(to, amount,
472
+ * deadline, minterSig)` into a UserOp the frontend submits via
473
+ * EIP-7702 + Paymaster.
474
+ * - `prepareBurn` mirrors the above on the burn side using a
475
+ * pre-signed `BurnRequest` + `PointToken.burn(from, amount,
476
+ * deadline, burnerSig)`.
477
+ *
478
+ * There is no broadcasting, no operator wallet, no simulation — those
479
+ * concerns moved to the Bundler + Paymaster in v1.4.
534
480
  */
535
481
  declare class RelayService {
536
- private readonly relayAddress;
537
- private readonly operatorWallet;
538
- private readonly provider?;
539
- private readonly confirmationTimeoutMs;
540
- private readonly simulateBeforeSubmit;
541
- constructor(config: RelayServiceConfig);
542
- /** Address the operator wallet is broadcasting from (for logging). */
543
- operatorAddress(): Address | undefined;
544
482
  /**
545
- * Build calldata for the Relay `mintAndSwap` function. Kept public so
546
- * callers (e.g. the MintingGateway) can log or persist the encoded call
547
- * for audit before broadcasting.
483
+ * Build an unsigned UserOp for Scenario 1 (Mint) sig-gated
484
+ * `PointToken.mint(to, amount, deadline, minterSig)`.
548
485
  */
549
- encodeCall(params: SubmitMintAndSwapParams): Hex;
550
- /**
551
- * Submit a `mintAndSwap` transaction. Flow:
552
- *
553
- * 1. (optional) pre-flight simulate via provider
554
- * 2. writeContract through the operator wallet
555
- * 3. (optional) wait for the receipt and surface gasUsed / status
556
- *
557
- * Throws a typed `RelayError` on any failure so the MintingGateway can
558
- * decide whether to release the ledger lock (`SUBMIT_FAILED` and
559
- * `SIMULATION_FAILED` are safe to release; `TX_REVERTED` and `TIMEOUT`
560
- * need manual review because the tx may still land).
561
- *
562
- * @deprecated Since 0.3.0 — will be replaced by `prepareMint()` +
563
- * `prepareBurn()` in the v1.4 sponsored-UserOp flow. The SC team
564
- * still needs to finalize Relayer v2 ABI before the replacements
565
- * can ship (blocker B1). Kept for v0.2.x consumers. Removed in 2.0.
566
- */
567
- submitMintAndSwap(params: SubmitMintAndSwapParams): Promise<RelayResult>;
568
- /**
569
- * Build an unsigned UserOp for Scenario 1 (Mint).
570
- *
571
- * Flow:
572
- * 1. Encode `Relayer.mint(request, userSig, issuerSig)` as the inner call
573
- * 2. Optionally append a PT fee transfer from user → feeRecipient
574
- * (fee recovery happens on-chain via BatchExecutor, not via an
575
- * operator wallet)
576
- * 3. Wrap all inner calls into `BatchExecutor.execute(calls[])`
577
- * 4. Return a `PartialUserOperation` ready for:
578
- * - gas estimation (Bundler)
579
- * - paymaster sponsorship (PAFI Backend)
580
- * - user signature (Privy)
581
- */
582
- /**
583
- * Build an unsigned UserOp for Scenario 1 (Mint) — direct
584
- * `PointToken.mint(amount)` flow (v1.4 post-Relayer-removal).
585
- *
586
- * `msg.sender` inside the batch = user EOA via EIP-7702 delegation.
587
- * `PointToken.mint` checks the caller is on its `minters` allowlist
588
- * (gg56 BE pre-validates this off-chain to avoid wasted gas).
589
- *
590
- * Optional PT fee transfer is appended after the mint when
591
- * `feeAmount > 0` — this is application-level fee recovery (no
592
- * Relayer doing it for us). The user must end up with `amount - fee`
593
- * net PT after the batch executes.
594
- */
595
- prepareMint(params: PrepareMintParams): PartialUserOperation;
486
+ prepareMint(params: PrepareMintParams): Promise<PartialUserOperation>;
596
487
  /**
597
488
  * Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
598
489
  *
599
490
  * Two modes:
600
- * - `mode: 'burn'` — direct `PointToken.burn(amount)`; `msg.sender`
601
- * via EIP-7702 delegation is the user, so no signature needed
602
- * on-chain (the BurnConsent was already verified off-chain by
603
- * the issuer backend before we got here)
604
- * - `mode: 'burnWithSig'` `PointToken.burnWithSig(consent, sig)`;
605
- * used when the issuer hasn't verified the consent and the
606
- * contract has to do it on-chain
491
+ * - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only
492
+ * usable if the caller (via EIP-7702) is whitelisted as a burner.
493
+ * Rare in v1.4; kept for admin/operator tools.
494
+ * - `mode: 'burnWithSig'` `PointToken.burn(from, amount, deadline,
495
+ * burnerSig)`. Caller provides a pre-signed `BurnRequest` + sig
496
+ * bytes (typically from `PTRedeemHandler`).
607
497
  */
608
498
  prepareBurn(params: PrepareBurnParams): PartialUserOperation;
609
499
  }
610
500
  /**
611
- * v1.4 — direct PointToken.mint() flow (no Relayer, no MintRequest sig).
501
+ * v1.4 — sig-gated `PointToken.mint(to, amount, deadline, minterSig)`.
612
502
  *
613
- * Backend (gg56) validates off-chain:
614
- * - User is on `PointToken.minters[]` allowlist
615
- * - User has enough off-chain points to claim
616
- * - Policy passes (KYC, cap, cooldown)
617
- *
618
- * Then locks the off-chain balance and returns this UserOp for the FE
619
- * to sponsor + submit. On confirmation, PointIndexer watches the
620
- * `Transfer(0x0, user, amount)` event and resolves the lock.
503
+ * The issuer backend validates off-chain (balance, policy, KYC), signs
504
+ * a `MintRequest` EIP-712 with its minter signer, and packages the
505
+ * whole thing into a UserOp for the user to submit via EIP-7702 +
506
+ * Paymaster. On confirmation, PointIndexer watches `Transfer(0x0, user,
507
+ * amount)` and resolves the ledger lock.
621
508
  */
622
509
  interface PrepareMintParams {
623
510
  /** User EOA that will send the UserOp (via EIP-7702 delegation). */
@@ -626,14 +513,25 @@ interface PrepareMintParams {
626
513
  aaNonce: bigint;
627
514
  /** BatchExecutor delegation target (chain-specific). */
628
515
  batchExecutorAddress: Address;
629
- /** PointToken contract — the call target. */
516
+ /** PointToken contract — the call target + EIP-712 verifying contract. */
630
517
  pointTokenAddress: Address;
631
- /** PT amount to mint to `userAddress` (= msg.sender via EIP-7702). */
518
+ /** PT amount to mint to `userAddress`. */
632
519
  amount: bigint;
520
+ /**
521
+ * Issuer minter signer wallet — signs the `MintRequest` EIP-712.
522
+ * Must be added to `PointToken.minters[]` via `addMinter(signerAddr)`
523
+ * at provisioning time. Typically HSM/KMS-backed in prod.
524
+ */
525
+ issuerSignerWallet: WalletClient;
526
+ /** EIP-712 domain for MintRequest. */
527
+ domain: PointTokenDomainConfig;
528
+ /** Current `mintRequestNonces[userAddress]` — caller reads from contract. */
529
+ mintRequestNonce: bigint;
530
+ /** Unix timestamp after which the signature expires. */
531
+ deadline: bigint;
633
532
  /**
634
533
  * Optional — application-level fee transfer appended after mint.
635
- * Set both `feeAmount` and `feeRecipient` together. Skipped when
636
- * `feeAmount` is undefined or 0.
534
+ * Set both `feeAmount` and `feeRecipient` together.
637
535
  */
638
536
  feeAmount?: bigint;
639
537
  feeRecipient?: Address;
@@ -658,8 +556,10 @@ interface PrepareBurnDirectParams extends PrepareBurnCommonParams {
658
556
  }
659
557
  interface PrepareBurnWithSigParams extends PrepareBurnCommonParams {
660
558
  mode: "burnWithSig";
661
- burnConsent: BurnConsent;
662
- consentSignature: SignatureStruct;
559
+ /** BurnRequest message the issuer burner signer signed. */
560
+ burnRequest: BurnRequest;
561
+ /** Serialized EIP-712 signature (bytes) over `burnRequest`. */
562
+ burnerSignature: Hex;
663
563
  }
664
564
 
665
565
  interface FeeManagerConfig {
@@ -725,156 +625,6 @@ declare class FeeManager {
725
625
  estimateGasFee(): Promise<bigint>;
726
626
  }
727
627
 
728
- /**
729
- * End-user request for a full "burn points → receive USDT" flow. The
730
- * receiver has already signed the `ReceiverConsent` EIP-712 message on
731
- * the frontend; the issuer backend runs everything else.
732
- */
733
- interface MintAndCashOutRequest {
734
- /** Address owning the off-chain points (== receiver in the consent). */
735
- userAddress: Address;
736
- /** Point token contract to mint. */
737
- pointTokenAddress: Address;
738
- /** Chain id the relay will submit on. */
739
- chainId: number;
740
- /**
741
- * EIP-712 domain for `pointTokenAddress`. The gateway passes this
742
- * straight through to `@pafi/core` `verifyReceiverConsent` and the
743
- * issuer signer. Callers typically fetch it once via
744
- * `PafiSDK.getDomain()` and cache it.
745
- */
746
- domain: PointTokenDomainConfig;
747
- /**
748
- * Receiver consent message + signature, pre-built by the frontend. The
749
- * message specifies `onBehalfOf = relay contract` and
750
- * `originalReceiver = userAddress`, plus the amount, nonce, deadline,
751
- * and encoded extData.
752
- */
753
- receiverConsent: ReceiverConsent;
754
- receiverSignature: Hex;
755
- /**
756
- * Swap path for the USDT output. Empty array = "no swap" (rare — only
757
- * useful for testing). Normally a single hop pointToken → USDT.
758
- */
759
- swapPath: PathKey[];
760
- /** Swap deadline (unix seconds). */
761
- swapDeadline: bigint;
762
- /**
763
- * Lock TTL (ms) to apply to the off-chain balance. The gateway computes
764
- * a safe default from `receiverConsent.deadline` if omitted.
765
- */
766
- lockDurationMs?: number;
767
- }
768
- /**
769
- * Result returned to the caller after a successful `processMintAndCashOut`.
770
- * The `lockId` is preserved so the indexer can correlate the on-chain
771
- * Mint event back to the ledger row (though that correlation is typically
772
- * done by `(userAddress, amount)` in the default `MemoryPointLedger`).
773
- */
774
- interface MintAndCashOutResponse {
775
- txHash: Hex;
776
- lockId: string;
777
- blockNumber?: bigint;
778
- gasUsed?: bigint;
779
- }
780
- /**
781
- * Error codes a MintingGateway can surface. Callers (API handlers) map
782
- * these to HTTP status codes. The `SAFE_TO_RETRY` field tells the caller
783
- * whether the underlying lock was released — if not, the user should
784
- * wait before retrying to avoid double-spend.
785
- */
786
- type MintingGatewayErrorCode = "INVALID_REQUEST" | "INVALID_CONSENT_SIGNATURE" | "CONSENT_EXPIRED" | "POLICY_REJECTED" | "INSUFFICIENT_BALANCE" | "SIGNER_FAILED" | "RELAY_SIMULATION_FAILED" | "RELAY_SUBMIT_FAILED" | "RELAY_REVERTED" | "RELAY_TIMEOUT";
787
- declare class MintingGatewayError extends Error {
788
- readonly code: MintingGatewayErrorCode;
789
- /**
790
- * True if the ledger lock was released before this error was thrown,
791
- * meaning the user can safely retry. False means the funds are still
792
- * locked (e.g. tx may still land on-chain) and retry would double-spend.
793
- */
794
- readonly safeToRetry: boolean;
795
- readonly cause?: unknown;
796
- constructor(code: MintingGatewayErrorCode, message: string, opts: {
797
- safeToRetry: boolean;
798
- cause?: unknown;
799
- });
800
- }
801
-
802
- interface MintingGatewayConfig {
803
- ledger: IPointLedger;
804
- policy: IPolicyEngine;
805
- signer: IIssuerSigner;
806
- relayService: RelayService;
807
- /**
808
- * Clock override for tests. Defaults to `() => Date.now()`. Used to
809
- * compute safe lock TTLs and to check consent deadlines.
810
- */
811
- now?: () => number;
812
- /**
813
- * Extra buffer (ms) added on top of `(consent.deadline - now)` when the
814
- * caller doesn't supply a `lockDurationMs`. Default: 60_000 (one minute
815
- * — roughly 2× the Base L2 block confirmation time).
816
- */
817
- defaultLockBufferMs?: number;
818
- }
819
- /**
820
- * The MintingGateway is the central orchestrator that turns a user's
821
- * signed ReceiverConsent into an on-chain `mintAndSwap` tx and a
822
- * consistent off-chain ledger update.
823
- *
824
- * 11-step flow (per `PAFI_ISSUER_SDK_SPEC.md` §4.3):
825
- *
826
- * 1. Field validation (cheap rejects before any crypto)
827
- * 2. Verify ReceiverConsent signature via `@pafi/core`
828
- * 3. Check off-chain balance via ledger
829
- * 4. Check locked requests via ledger
830
- * 5. Run policy engine (balance + on-chain cap + issuer rules)
831
- * 6. Lock the requested amount in the ledger
832
- * 7. Sign MintRequest as issuer
833
- * 8. Build MintParams + SwapParams
834
- * 9. Submit via RelayService (encode + simulate + broadcast + wait)
835
- * 10. Return { txHash, lockId, receipt fields }
836
- * 11. On any failure, release the lock IF it's safe to retry — i.e. we
837
- * know the tx cannot still land on-chain. If the tx may have made
838
- * it (`TX_REVERTED` or `TIMEOUT`), we leave the lock alone and
839
- * surface `safeToRetry: false` so the caller doesn't double-spend.
840
- *
841
- * The gateway deliberately does NOT deduct the balance here. Deduction
842
- * happens in the `PointIndexer` when the on-chain Mint event is observed,
843
- * which is what makes the system crash-safe: if the gateway dies between
844
- * broadcast and receipt, the indexer will still finalize the ledger.
845
- */
846
- declare class MintingGateway {
847
- private readonly ledger;
848
- private readonly policy;
849
- private readonly signer;
850
- private readonly relayService;
851
- private readonly now;
852
- private readonly defaultLockBufferMs;
853
- constructor(config: MintingGatewayConfig);
854
- /**
855
- * @deprecated Since 0.3.0 — will be renamed to `processMint()` once
856
- * the SC team finalizes Relayer v2 ABI. The new flow drops the
857
- * swap steps entirely (no more single-call mint+swap); users swap
858
- * separately on PAFI Web. Kept here for v0.2.x consumers. Removed in 2.0.
859
- */
860
- processMintAndCashOut(request: MintAndCashOutRequest): Promise<MintAndCashOutResponse>;
861
- private computeLockDurationMs;
862
- /**
863
- * Map a RelayError to a MintingGatewayError, releasing the lock only
864
- * when the tx definitely did not land. `TX_REVERTED` and `TIMEOUT`
865
- * leave the lock in place because the tx may still be in the mempool
866
- * or already mined — releasing would enable a double-spend on retry.
867
- * Always throws.
868
- */
869
- private handleRelayFailure;
870
- /**
871
- * Release a lock, swallowing any secondary error. We never want a lock
872
- * release failure to mask the original error — the lock will auto-expire
873
- * via TTL anyway.
874
- */
875
- private releaseLockSafely;
876
- }
877
-
878
628
  /** Decoded Transfer(from=0x0 → to) event used to finalize a mint. */
879
629
  interface MintEvent {
880
630
  /** Destination address (the user who received the minted points) */
@@ -1237,7 +987,6 @@ type PoolsProvider = (request: ApiPoolsRequest) => Promise<ApiPoolsResponse>;
1237
987
 
1238
988
  interface IssuerApiHandlersConfig {
1239
989
  authService: AuthService;
1240
- gateway: MintingGateway;
1241
990
  ledger: IPointLedger;
1242
991
  /** Used by `handleUser` to read on-chain nonces and minter status. */
1243
992
  provider: PublicClient;
@@ -1268,8 +1017,7 @@ interface IssuerApiHandlersConfig {
1268
1017
  * Framework-agnostic HTTP handlers that match the endpoints a `PafiSDK`
1269
1018
  * frontend expects to call. Issuers wrap these in Express / Fastify /
1270
1019
  * Hono / whatever — the handlers take plain inputs and return plain
1271
- * outputs, with `AuthError` / `MintingGatewayError` surfaced as typed
1272
- * exceptions.
1020
+ * outputs, with `AuthError` surfaced as typed exceptions.
1273
1021
  *
1274
1022
  * Every protected handler takes a pre-verified `userAddress` as its first
1275
1023
  * argument. The issuer's middleware wraps `authenticateRequest()` from
@@ -1278,7 +1026,6 @@ interface IssuerApiHandlersConfig {
1278
1026
  */
1279
1027
  declare class IssuerApiHandlers {
1280
1028
  private readonly authService;
1281
- private readonly gateway;
1282
1029
  private readonly ledger;
1283
1030
  private readonly provider;
1284
1031
  /**
@@ -1340,33 +1087,32 @@ declare class IssuerApiHandlers {
1340
1087
  * mobile apps — no app store review needed.
1341
1088
  */
1342
1089
  handleBuildConsentTypedData(userAddress: Address, request: ApiBuildConsentTypedDataRequest): Promise<ApiBuildConsentTypedDataResponse>;
1343
- /**
1344
- * `POST /claim-and-swap`
1345
- *
1346
- * @deprecated Since 0.3.0 — the single-call mint-then-swap flow is
1347
- * retired in v1.4. Use the new `handleClaim()` (mint only) and let
1348
- * the user swap separately on PAFI Web. See
1349
- * [V1.4_V1.5_OVERVIEW.md §4] for the new scenario model. Will be
1350
- * removed in 2.0.
1351
- *
1352
- * Legacy behavior: the terminal handler forwards the verified
1353
- * consent to the MintingGateway, which runs the 11-step flow.
1354
- */
1355
- handleClaimAndSwap(userAddress: Address, request: ApiClaimAndSwapRequest): Promise<ApiClaimAndSwapResponse>;
1356
1090
  }
1357
1091
 
1358
1092
  /**
1359
- * v1.4 reverse flow — **Variant A**: user-initiated PT redeem.
1093
+ * v1.4 reverse flow — user-initiated PT redeem.
1094
+ *
1095
+ * User has on-chain PT, wants to convert back to off-chain points. The
1096
+ * issuer backend signs a `BurnRequest` with its burner signer, reserves
1097
+ * an off-chain pending credit, and returns an unsigned UserOp. The FE
1098
+ * submits the UserOp via EIP-7702 + Coinbase Paymaster. On confirmation,
1099
+ * `Transfer(user → 0x0)` is emitted; `BurnIndexer` resolves the pending
1100
+ * credit to a real off-chain credit.
1360
1101
  *
1361
- * User has on-chain PT, wants to convert back to off-chain points. They
1362
- * sign a `BurnConsent`, the issuer backend verifies it, reserves an
1363
- * off-chain credit, and returns an unsigned UserOp that the frontend
1364
- * submits via the Bundler. When the burn lands, the `BurnIndexer`
1365
- * (elsewhere) resolves the credit.
1102
+ * Contract path (mock ABI matches deployed PointToken):
1366
1103
  *
1367
- * **Mocked SC contracts** this handler compiles + wires end-to-end
1368
- * against `@pafi-dev/core/contracts` mock ABIs. When SC ships real
1369
- * ABIs, no changes here — only `contracts/index.ts` re-export flips.
1104
+ * burn(address from, uint256 amount, uint256 deadline, bytes burnerSig)
1105
+ *
1106
+ * On-chain checks:
1107
+ * - msg.sender == from (enforced via EIP-7702 delegation on user EOA)
1108
+ * - burnerSig signer ∈ burners[]
1109
+ * - nonce == burnRequestNonces[from]
1110
+ * - now <= deadline
1111
+ *
1112
+ * The user never signs an EIP-712 message in this flow. Their only
1113
+ * signature is the ERC-4337 UserOp signature, which the AA wallet
1114
+ * handles. Consent is implicit: by submitting the UserOp they authorize
1115
+ * the burn.
1370
1116
  */
1371
1117
  interface PTRedeemHandlerConfig {
1372
1118
  ledger: IPointLedger;
@@ -1376,32 +1122,41 @@ interface PTRedeemHandlerConfig {
1376
1122
  pointTokenAddress: Address;
1377
1123
  /** BatchExecutor delegation target (chain-specific). */
1378
1124
  batchExecutorAddress: Address;
1379
- /** Chain id — used for domain separator when verifying BurnConsent. */
1125
+ /** Chain id — used for the BurnRequest EIP-712 domain. */
1380
1126
  chainId: number;
1381
1127
  /**
1382
1128
  * EIP-712 domain fields. Must match the on-chain PointToken's domain
1383
- * separator exactly, or signature verification fails. `name` is
1384
- * typically the PointToken's ERC-20 name (e.g. "PAFI Starbucks
1385
- * Points"). `verifyingContract` defaults to `pointTokenAddress`.
1129
+ * separator, or on-chain signature recovery fails. `name` is
1130
+ * typically the PointToken's ERC-20 name. `verifyingContract`
1131
+ * defaults to `pointTokenAddress`.
1386
1132
  */
1387
1133
  domain: {
1388
1134
  name: string;
1389
1135
  verifyingContract?: Address;
1390
1136
  };
1137
+ /**
1138
+ * Issuer burner signer wallet — signs the `BurnRequest` EIP-712.
1139
+ * Must be whitelisted via `PointToken.addBurner(signerAddr)` at
1140
+ * provisioning time. Typically HSM/KMS-backed in prod.
1141
+ */
1142
+ burnerSignerWallet: WalletClient;
1391
1143
  /**
1392
1144
  * How long the pending credit stays reserved if the burn never lands.
1393
1145
  * Default: 15 min — long enough for a bundler submission + confirmation.
1394
1146
  */
1395
1147
  redeemLockDurationMs?: number;
1148
+ /**
1149
+ * How far ahead of `now` to set the BurnRequest deadline. Default:
1150
+ * 15 min. Must be long enough for Bundler + EntryPoint to execute;
1151
+ * short enough to prevent replay if the UserOp is abandoned.
1152
+ */
1153
+ signatureDeadlineSeconds?: number;
1396
1154
  /** Clock injection for tests; defaults to `Date.now`. */
1397
1155
  now?: () => number;
1398
1156
  }
1399
1157
  interface PTRedeemRequest {
1400
1158
  userAddress: Address;
1401
1159
  amount: bigint;
1402
- /** Serialized EIP-712 signature over the BurnConsent. */
1403
- consentSignature: Hex;
1404
- consent: BurnConsent;
1405
1160
  /** ERC-4337 account nonce for the user's EOA. */
1406
1161
  aaNonce: bigint;
1407
1162
  }
@@ -1412,19 +1167,24 @@ interface PTRedeemResponse {
1412
1167
  userOp: PartialUserOperation;
1413
1168
  /** Seconds until the lock expires if the burn doesn't land. */
1414
1169
  expiresInSeconds: number;
1170
+ /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
1171
+ signatureDeadline: bigint;
1415
1172
  }
1416
1173
  declare class PTRedeemError extends Error {
1417
- code: "INVALID_CONSENT" | "SIGNATURE_MISMATCH" | "AMOUNT_MISMATCH" | "EXPIRED_CONSENT" | "LEDGER_NOT_SUPPORTED";
1418
- constructor(code: "INVALID_CONSENT" | "SIGNATURE_MISMATCH" | "AMOUNT_MISMATCH" | "EXPIRED_CONSENT" | "LEDGER_NOT_SUPPORTED", message: string);
1174
+ code: "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED";
1175
+ constructor(code: "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED", message: string);
1419
1176
  }
1420
1177
  declare class PTRedeemHandler {
1421
1178
  private readonly ledger;
1422
1179
  private readonly relayService;
1180
+ private readonly provider;
1423
1181
  private readonly pointTokenAddress;
1424
1182
  private readonly batchExecutorAddress;
1425
1183
  private readonly chainId;
1426
1184
  private readonly domain;
1185
+ private readonly burnerSignerWallet;
1427
1186
  private readonly redeemLockDurationMs;
1187
+ private readonly signatureDeadlineSeconds;
1428
1188
  private readonly now;
1429
1189
  constructor(config: PTRedeemHandlerConfig);
1430
1190
  handle(request: PTRedeemRequest): Promise<PTRedeemResponse>;
@@ -1445,8 +1205,12 @@ declare class PTRedeemHandler {
1445
1205
  * → burn 200 PT, credit 200 off-chain, voucher proceeds with 500
1446
1206
  *
1447
1207
  * Delegates the actual burn construction to {@link PTRedeemHandler}
1448
- * — this handler is pure business logic (calculating shortfall +
1449
- * checking on-chain balance) on top.
1208
+ * — this handler is pure business logic (shortfall math + on-chain
1209
+ * balance check) on top.
1210
+ *
1211
+ * v1.4 note: user no longer pre-signs a `BurnConsent`. The issuer
1212
+ * backend signs a `BurnRequest` itself (see `PTRedeemHandler`), so
1213
+ * this handler only needs `userAddress + requiredAmount + aaNonce`.
1450
1214
  */
1451
1215
  interface TopUpRedemptionHandlerConfig {
1452
1216
  ledger: IPointLedger;
@@ -1459,12 +1223,8 @@ interface TopUpRedemptionRequest {
1459
1223
  userAddress: Address;
1460
1224
  /** Total points the voucher redemption requires off-chain. */
1461
1225
  requiredAmount: bigint;
1462
- /**
1463
- * The user's pre-signed `BurnConsent` + signature, prepared by the FE
1464
- * with amount set to a worst-case upper bound. Handler inspects the
1465
- * shortfall and uses the consent if the shortfall ≤ consent.amount.
1466
- */
1467
- redeemRequest: Pick<PTRedeemRequest, "consent" | "consentSignature" | "aaNonce">;
1226
+ /** ERC-4337 account nonce for the user's EOA. */
1227
+ aaNonce: bigint;
1468
1228
  }
1469
1229
  type TopUpRedemptionResponse = {
1470
1230
  action: "NO_TOP_UP_NEEDED";
@@ -1480,8 +1240,8 @@ type TopUpRedemptionResponse = {
1480
1240
  redeem: PTRedeemResponse;
1481
1241
  };
1482
1242
  declare class TopUpRedemptionError extends Error {
1483
- code: "INSUFFICIENT_ONCHAIN_BALANCE" | "CONSENT_AMOUNT_TOO_LOW" | "LEDGER_NOT_SUPPORTED";
1484
- constructor(code: "INSUFFICIENT_ONCHAIN_BALANCE" | "CONSENT_AMOUNT_TOO_LOW" | "LEDGER_NOT_SUPPORTED", message: string);
1243
+ code: "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED";
1244
+ constructor(code: "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED", message: string);
1485
1245
  }
1486
1246
  declare class TopUpRedemptionHandler {
1487
1247
  private readonly ledger;
@@ -1820,31 +1580,24 @@ declare class PafiBackendClient {
1820
1580
  }
1821
1581
 
1822
1582
  /**
1823
- * Top-level configuration for `createIssuerService`. Everything except
1824
- * the chain metadata, wallets, auth secret, and `signer` is optional and
1825
- * falls back to the in-memory dev defaults — that makes the happy path
1826
- * a single-call wire-up while still letting production issuers plug in
1827
- * their own ledger, session store, policy engine, and KMS signer.
1583
+ * Top-level configuration for `createIssuerService`.
1828
1584
  *
1829
- * **Multi-token (0.2.0):** Pass `pointTokenAddresses: Address[]` to
1585
+ * In v1.4 the SDK is HTTP-client-free: it signs EIP-712 messages, reads
1586
+ * on-chain state, builds unsigned UserOperations, and maintains the
1587
+ * off-chain ledger. It never broadcasts transactions — that's the
1588
+ * frontend's responsibility via Bundler + Paymaster.
1589
+ *
1590
+ * **Multi-token (0.2.0+):** Pass `pointTokenAddresses: Address[]` to
1830
1591
  * support multiple PointTokens under a single issuer backend. Legacy
1831
1592
  * `pointTokenAddress: Address` still works for single-token deployments.
1832
1593
  * When both are provided, `pointTokenAddresses` takes precedence.
1833
1594
  */
1834
1595
  interface IssuerServiceConfig {
1835
1596
  chainId: number;
1836
- /**
1837
- * Address of the deployed PointToken. Legacy single-token shortcut;
1838
- * prefer `pointTokenAddresses` for multi-token issuers.
1839
- */
1597
+ /** Legacy single-token shortcut; prefer `pointTokenAddresses`. */
1840
1598
  pointTokenAddress?: Address;
1841
- /**
1842
- * All PointToken addresses this issuer supports. Takes precedence over
1843
- * `pointTokenAddress`. Factory creates one `PointIndexer` per address.
1844
- */
1599
+ /** All PointToken addresses this issuer supports. */
1845
1600
  pointTokenAddresses?: Address[];
1846
- /** Address of the deployed Relay contract. */
1847
- relayAddress: Address;
1848
1601
  /**
1849
1602
  * Full contract address bundle returned verbatim by `handleConfig` so
1850
1603
  * the frontend SDK can pick them up.
@@ -1855,8 +1608,6 @@ interface IssuerServiceConfig {
1855
1608
  * polling, and gas-price lookups. Must be pointed at the target chain.
1856
1609
  */
1857
1610
  provider: PublicClient;
1858
- /** Operator wallet — pays gas, receives the operator fee. */
1859
- operatorWallet: OperatorWalletLike;
1860
1611
  auth: {
1861
1612
  jwtSecret: string;
1862
1613
  /** SIWE-style login-message domain, e.g. `"app.example.com"`. */
@@ -1864,19 +1615,12 @@ interface IssuerServiceConfig {
1864
1615
  /** Passed straight to `jose` (`"24h"`, `"7d"`, …). Default `"24h"`. */
1865
1616
  jwtExpiresIn?: string;
1866
1617
  };
1867
- /**
1868
- * Issuer signer. No default — production issuers MUST plug in an
1869
- * HSM/KMS-backed implementation. For local development use
1870
- * `PrivateKeySigner` directly.
1871
- */
1872
- signer: IIssuerSigner;
1873
1618
  ledger?: IPointLedger;
1874
1619
  policy?: IPolicyEngine;
1875
1620
  sessionStore?: ISessionStore;
1876
1621
  /**
1877
1622
  * Fee management config. If omitted the `handleGasFee` endpoint will
1878
- * throw "not configured" at request time. Pass any subset of fields
1879
- * to opt in — `provider` is inherited from the outer config.
1623
+ * throw "not configured" at request time.
1880
1624
  */
1881
1625
  fee?: Omit<FeeManagerConfig, "provider">;
1882
1626
  /**
@@ -1896,32 +1640,18 @@ interface IssuerServiceConfig {
1896
1640
  */
1897
1641
  autoStart?: boolean;
1898
1642
  };
1899
- relay?: {
1900
- simulateBeforeSubmit?: boolean;
1901
- confirmationTimeoutMs?: number;
1902
- };
1903
- gateway?: {
1904
- /** Extra lock TTL buffer beyond consent deadline. Default 60_000 ms. */
1905
- defaultLockBufferMs?: number;
1906
- };
1907
1643
  }
1908
1644
  interface IssuerService {
1909
1645
  authService: AuthService;
1910
1646
  sessionStore: ISessionStore;
1911
1647
  ledger: IPointLedger;
1912
1648
  policy: IPolicyEngine;
1913
- signer: IIssuerSigner;
1914
1649
  relayService: RelayService;
1915
1650
  feeManager: FeeManager | undefined;
1916
- gateway: MintingGateway;
1917
- /**
1918
- * All indexers keyed by PointToken address. For multi-token issuers there
1919
- * is one per configured token. Single-token issuers will find one entry.
1920
- */
1651
+ /** All indexers keyed by PointToken address. */
1921
1652
  indexers: Map<Address, PointIndexer>;
1922
1653
  /**
1923
- * First indexer. Kept for backward compat with 0.1.x callers that
1924
- * expected `service.indexer` to be a single instance.
1654
+ * First indexer. Kept for backward compat with 0.1.x callers.
1925
1655
  * @deprecated use `indexers.get(tokenAddress)` for multi-token.
1926
1656
  */
1927
1657
  indexer: PointIndexer;
@@ -1929,8 +1659,6 @@ interface IssuerService {
1929
1659
  }
1930
1660
  /**
1931
1661
  * Wire a fully-functional issuer service from a single config object.
1932
- * Returns every constructed collaborator so the caller can also use the
1933
- * indexer or relay service directly outside the HTTP layer.
1934
1662
  *
1935
1663
  * Defaults:
1936
1664
  * - `ledger` → `MemoryPointLedger`
@@ -1940,12 +1668,11 @@ interface IssuerService {
1940
1668
  * - `poolsProvider` → undefined (handlePools throws until configured)
1941
1669
  * - `indexer.autoStart` → false
1942
1670
  *
1943
- * Throws synchronously if any required field (`signer`, `provider`,
1944
- * `operatorWallet`, `auth.jwtSecret`, at least one point token) is missing.
1671
+ * Throws synchronously if any required field is missing.
1945
1672
  */
1946
1673
  declare function createIssuerService(config: IssuerServiceConfig): IssuerService;
1947
1674
 
1948
1675
  /** SDK package version — bumped on every release */
1949
1676
  declare const PAFI_ISSUER_SDK_VERSION = "0.1.0";
1950
1677
 
1951
- export { type ApiBuildConsentTypedDataRequest, type ApiBuildConsentTypedDataResponse, type ApiClaimAndSwapRequest, type ApiClaimAndSwapResponse, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, BalanceAggregator, type BalanceAggregatorConfig, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type CombinedBalance, DefaultPolicyEngine, type DefaultPolicyEngineOptions, FeeManager, type FeeManagerConfig, type IIndexerCursorStore, type IIssuerSigner, type IPointLedger, type IPolicyEngine, type ISessionStore, InMemoryCursorStore, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerService, type IssuerServiceConfig, type LockedMintRequest, type LoginResult, MemoryPointLedger, MemorySessionStore, type MemorySessionStoreOptions, type MintAndCashOutRequest, type MintAndCashOutResponse, type MintEvent, MintingGateway, type MintingGatewayConfig, MintingGatewayError, type MintingGatewayErrorCode, type MintingStatus, NonceManager, type OperatorWalletLike, PAFI_ISSUER_SDK_VERSION, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, PointIndexer, type PointIndexerConfig, type PolicyDecision, type PolicyEvalRequest, type PoolsProvider, PrivateKeySigner, type PrivateKeySignerOptions, RelayError, type RelayErrorCode, type RelayResult, RelayService, type RelayServiceConfig, type Session, type SponsorshipRequest, type SponsorshipResponse, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type SubmitMintAndSwapParams, TopUpRedemptionError, TopUpRedemptionHandler, type TopUpRedemptionHandlerConfig, type TopUpRedemptionRequest, type TopUpRedemptionResponse, authenticateRequest, createIssuerService, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider };
1678
+ export { type ApiBuildConsentTypedDataRequest, type ApiBuildConsentTypedDataResponse, type ApiClaimAndSwapRequest, type ApiClaimAndSwapResponse, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, BalanceAggregator, type BalanceAggregatorConfig, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type CombinedBalance, DefaultPolicyEngine, type DefaultPolicyEngineOptions, FeeManager, type FeeManagerConfig, type IIndexerCursorStore, type IIssuerSigner, type IPointLedger, type IPolicyEngine, type ISessionStore, InMemoryCursorStore, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerService, type IssuerServiceConfig, type LockedMintRequest, type LoginResult, MemoryPointLedger, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintingStatus, NonceManager, PAFI_ISSUER_SDK_VERSION, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, PointIndexer, type PointIndexerConfig, type PolicyDecision, type PolicyEvalRequest, type PoolsProvider, type PrepareBurnDirectParams, type PrepareBurnParams, type PrepareBurnWithSigParams, type PrepareMintParams, PrivateKeySigner, type PrivateKeySignerOptions, RelayError, type RelayErrorCode, RelayService, type Session, type SponsorshipRequest, type SponsorshipResponse, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, TopUpRedemptionError, TopUpRedemptionHandler, type TopUpRedemptionHandlerConfig, type TopUpRedemptionRequest, type TopUpRedemptionResponse, authenticateRequest, createIssuerService, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider };