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

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,5 +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';
1
+ import { Address, Hex, PublicClient, Chain, WalletClient } from 'viem';
2
+ import { PointTokenDomainConfig, MintRequest, EIP712Signature, MintParams, SwapParams, PartialUserOperation, BurnRequest, ReceiverConsent, PathKey, PoolKey, SponsorshipScenario } from '@pafi-dev/core';
3
3
  export { encodeExtData } from '@pafi-dev/core';
4
4
 
5
5
  /**
@@ -523,14 +523,18 @@ interface RelayServiceConfig {
523
523
  simulateBeforeSubmit?: boolean;
524
524
  }
525
525
  /**
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.
526
+ * Submits `mintAndSwap` transactions to the Relay contract (legacy
527
+ * v0.2 path) and builds unsigned UserOps for the v1.4 sponsored flow.
530
528
  *
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.
529
+ * v1.4 flow (post-Relayer-removal):
530
+ * - `prepareMint` signs `MintRequest` with the issuer signer and
531
+ * encodes `PointToken.mint(to, amount, deadline, minterSig)` into
532
+ * a UserOp the user submits via EIP-7702 + Paymaster.
533
+ * - `prepareBurn` — mirrors on the burn side using `BurnRequest` +
534
+ * `PointToken.burn(from, amount, deadline, burnerSig)`.
535
+ *
536
+ * Legacy v0.2 `submitMintAndSwap` still broadcasts via operator wallet
537
+ * for the deprecated `/claim-and-swap` endpoint.
534
538
  */
535
539
  declare class RelayService {
536
540
  private readonly relayAddress;
@@ -548,76 +552,51 @@ declare class RelayService {
548
552
  */
549
553
  encodeCall(params: SubmitMintAndSwapParams): Hex;
550
554
  /**
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).
555
+ * Submit a `mintAndSwap` transaction (legacy v0.2 `/claim-and-swap`).
561
556
  *
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.
557
+ * @deprecated Since 0.3.0 — replaced by `prepareMint` / `prepareBurn`
558
+ * in the v1.4 sponsored-UserOp flow. Kept for v0.2.x consumers;
559
+ * scheduled removal in 2.0.
566
560
  */
567
561
  submitMintAndSwap(params: SubmitMintAndSwapParams): Promise<RelayResult>;
568
562
  /**
569
- * Build an unsigned UserOp for Scenario 1 (Mint).
563
+ * Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
564
+ * `PointToken.mint(to, amount, deadline, minterSig)`.
570
565
  *
571
566
  * 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;
567
+ * 1. Issuer backend signs `MintRequest(to=user, amount, nonce, deadline)`
568
+ * with its minter signer (HSM/KMS)`minterSig`.
569
+ * 2. Encode `PointToken.mint(user, amount, deadline, minterSig)`.
570
+ * On-chain, `msg.sender` must equal `to` — satisfied by EIP-7702
571
+ * delegating the user EOA to BatchExecutor.
572
+ * 3. Optional PT fee transfer appended after mint (application-level
573
+ * fee recovery since Relayer v2 no longer exists).
574
+ * 4. Return `PartialUserOperation` ready for Bundler gas estimate +
575
+ * Paymaster sponsorship + user signature.
576
+ */
577
+ prepareMint(params: PrepareMintParams): Promise<PartialUserOperation>;
596
578
  /**
597
579
  * Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
598
580
  *
599
581
  * 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
582
+ * - `mode: 'burn'` — direct `PointToken.burn(from, amount)`; only
583
+ * usable if the user is a whitelisted burner. Not the typical
584
+ * v1.4 path (users aren't burners); kept for admin/operator tools.
585
+ * - `mode: 'burnWithSig'` `PointToken.burn(from, amount, deadline,
586
+ * burnerSig)`. Issuer signs `BurnRequest` off-chain; user submits
587
+ * via EIP-7702. `msg.sender == from` enforced on-chain. This is
588
+ * the user-initiated redeem path in v1.4.
607
589
  */
608
590
  prepareBurn(params: PrepareBurnParams): PartialUserOperation;
609
591
  }
610
592
  /**
611
- * v1.4 — direct PointToken.mint() flow (no Relayer, no MintRequest sig).
593
+ * v1.4 — sig-gated `PointToken.mint(to, amount, deadline, minterSig)`.
612
594
  *
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.
595
+ * The issuer backend validates off-chain (balance, policy, KYC), signs
596
+ * a `MintRequest` EIP-712 with its minter signer, and packages the
597
+ * whole thing into a UserOp for the user to submit via EIP-7702 +
598
+ * Paymaster. On confirmation, PointIndexer watches `Transfer(0x0, user,
599
+ * amount)` and resolves the ledger lock.
621
600
  */
622
601
  interface PrepareMintParams {
623
602
  /** User EOA that will send the UserOp (via EIP-7702 delegation). */
@@ -626,14 +605,25 @@ interface PrepareMintParams {
626
605
  aaNonce: bigint;
627
606
  /** BatchExecutor delegation target (chain-specific). */
628
607
  batchExecutorAddress: Address;
629
- /** PointToken contract — the call target. */
608
+ /** PointToken contract — the call target + EIP-712 verifying contract. */
630
609
  pointTokenAddress: Address;
631
- /** PT amount to mint to `userAddress` (= msg.sender via EIP-7702). */
610
+ /** PT amount to mint to `userAddress`. */
632
611
  amount: bigint;
612
+ /**
613
+ * Issuer minter signer wallet — signs the `MintRequest` EIP-712.
614
+ * Must be added to `PointToken.minters[]` via `addMinter(signerAddr)`
615
+ * at provisioning time. Typically HSM/KMS-backed in prod.
616
+ */
617
+ issuerSignerWallet: WalletClient;
618
+ /** EIP-712 domain for MintRequest (name + chainId + verifyingContract). */
619
+ domain: PointTokenDomainConfig;
620
+ /** Current `mintRequestNonces[userAddress]` — caller reads from contract. */
621
+ mintRequestNonce: bigint;
622
+ /** Unix timestamp after which the signature expires. */
623
+ deadline: bigint;
633
624
  /**
634
625
  * Optional — application-level fee transfer appended after mint.
635
- * Set both `feeAmount` and `feeRecipient` together. Skipped when
636
- * `feeAmount` is undefined or 0.
626
+ * Set both `feeAmount` and `feeRecipient` together.
637
627
  */
638
628
  feeAmount?: bigint;
639
629
  feeRecipient?: Address;
@@ -658,8 +648,10 @@ interface PrepareBurnDirectParams extends PrepareBurnCommonParams {
658
648
  }
659
649
  interface PrepareBurnWithSigParams extends PrepareBurnCommonParams {
660
650
  mode: "burnWithSig";
661
- burnConsent: BurnConsent;
662
- consentSignature: SignatureStruct;
651
+ /** BurnRequest message the issuer burner signer signed. */
652
+ burnRequest: BurnRequest;
653
+ /** Serialized EIP-712 signature (bytes) over `burnRequest`. */
654
+ burnerSignature: Hex;
663
655
  }
664
656
 
665
657
  interface FeeManagerConfig {
@@ -1356,17 +1348,29 @@ declare class IssuerApiHandlers {
1356
1348
  }
1357
1349
 
1358
1350
  /**
1359
- * v1.4 reverse flow — **Variant A**: user-initiated PT redeem.
1351
+ * v1.4 reverse flow — user-initiated PT redeem.
1352
+ *
1353
+ * User has on-chain PT, wants to convert back to off-chain points. The
1354
+ * issuer backend signs a `BurnRequest` with its burner signer, reserves
1355
+ * an off-chain pending credit, and returns an unsigned UserOp. The FE
1356
+ * submits the UserOp via EIP-7702 + Coinbase Paymaster. On confirmation,
1357
+ * `Transfer(user → 0x0)` is emitted; `BurnIndexer` resolves the pending
1358
+ * credit to a real off-chain credit.
1360
1359
  *
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.
1360
+ * Contract path (mock ABI matches deployed PointToken):
1366
1361
  *
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.
1362
+ * burn(address from, uint256 amount, uint256 deadline, bytes burnerSig)
1363
+ *
1364
+ * On-chain checks:
1365
+ * - msg.sender == from (enforced via EIP-7702 delegation on user EOA)
1366
+ * - burnerSig signer ∈ burners[]
1367
+ * - nonce == burnRequestNonces[from]
1368
+ * - now <= deadline
1369
+ *
1370
+ * The user never signs an EIP-712 message in this flow. Their only
1371
+ * signature is the ERC-4337 UserOp signature, which the AA wallet
1372
+ * handles. Consent is implicit: by submitting the UserOp they authorize
1373
+ * the burn.
1370
1374
  */
1371
1375
  interface PTRedeemHandlerConfig {
1372
1376
  ledger: IPointLedger;
@@ -1376,32 +1380,41 @@ interface PTRedeemHandlerConfig {
1376
1380
  pointTokenAddress: Address;
1377
1381
  /** BatchExecutor delegation target (chain-specific). */
1378
1382
  batchExecutorAddress: Address;
1379
- /** Chain id — used for domain separator when verifying BurnConsent. */
1383
+ /** Chain id — used for the BurnRequest EIP-712 domain. */
1380
1384
  chainId: number;
1381
1385
  /**
1382
1386
  * 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`.
1387
+ * separator, or on-chain signature recovery fails. `name` is
1388
+ * typically the PointToken's ERC-20 name. `verifyingContract`
1389
+ * defaults to `pointTokenAddress`.
1386
1390
  */
1387
1391
  domain: {
1388
1392
  name: string;
1389
1393
  verifyingContract?: Address;
1390
1394
  };
1395
+ /**
1396
+ * Issuer burner signer wallet — signs the `BurnRequest` EIP-712.
1397
+ * Must be whitelisted via `PointToken.addBurner(signerAddr)` at
1398
+ * provisioning time. Typically HSM/KMS-backed in prod.
1399
+ */
1400
+ burnerSignerWallet: WalletClient;
1391
1401
  /**
1392
1402
  * How long the pending credit stays reserved if the burn never lands.
1393
1403
  * Default: 15 min — long enough for a bundler submission + confirmation.
1394
1404
  */
1395
1405
  redeemLockDurationMs?: number;
1406
+ /**
1407
+ * How far ahead of `now` to set the BurnRequest deadline. Default:
1408
+ * 15 min. Must be long enough for Bundler + EntryPoint to execute;
1409
+ * short enough to prevent replay if the UserOp is abandoned.
1410
+ */
1411
+ signatureDeadlineSeconds?: number;
1396
1412
  /** Clock injection for tests; defaults to `Date.now`. */
1397
1413
  now?: () => number;
1398
1414
  }
1399
1415
  interface PTRedeemRequest {
1400
1416
  userAddress: Address;
1401
1417
  amount: bigint;
1402
- /** Serialized EIP-712 signature over the BurnConsent. */
1403
- consentSignature: Hex;
1404
- consent: BurnConsent;
1405
1418
  /** ERC-4337 account nonce for the user's EOA. */
1406
1419
  aaNonce: bigint;
1407
1420
  }
@@ -1412,19 +1425,24 @@ interface PTRedeemResponse {
1412
1425
  userOp: PartialUserOperation;
1413
1426
  /** Seconds until the lock expires if the burn doesn't land. */
1414
1427
  expiresInSeconds: number;
1428
+ /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
1429
+ signatureDeadline: bigint;
1415
1430
  }
1416
1431
  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);
1432
+ code: "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED";
1433
+ constructor(code: "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED", message: string);
1419
1434
  }
1420
1435
  declare class PTRedeemHandler {
1421
1436
  private readonly ledger;
1422
1437
  private readonly relayService;
1438
+ private readonly provider;
1423
1439
  private readonly pointTokenAddress;
1424
1440
  private readonly batchExecutorAddress;
1425
1441
  private readonly chainId;
1426
1442
  private readonly domain;
1443
+ private readonly burnerSignerWallet;
1427
1444
  private readonly redeemLockDurationMs;
1445
+ private readonly signatureDeadlineSeconds;
1428
1446
  private readonly now;
1429
1447
  constructor(config: PTRedeemHandlerConfig);
1430
1448
  handle(request: PTRedeemRequest): Promise<PTRedeemResponse>;
@@ -1445,8 +1463,12 @@ declare class PTRedeemHandler {
1445
1463
  * → burn 200 PT, credit 200 off-chain, voucher proceeds with 500
1446
1464
  *
1447
1465
  * Delegates the actual burn construction to {@link PTRedeemHandler}
1448
- * — this handler is pure business logic (calculating shortfall +
1449
- * checking on-chain balance) on top.
1466
+ * — this handler is pure business logic (shortfall math + on-chain
1467
+ * balance check) on top.
1468
+ *
1469
+ * v1.4 note: user no longer pre-signs a `BurnConsent`. The issuer
1470
+ * backend signs a `BurnRequest` itself (see `PTRedeemHandler`), so
1471
+ * this handler only needs `userAddress + requiredAmount + aaNonce`.
1450
1472
  */
1451
1473
  interface TopUpRedemptionHandlerConfig {
1452
1474
  ledger: IPointLedger;
@@ -1459,12 +1481,8 @@ interface TopUpRedemptionRequest {
1459
1481
  userAddress: Address;
1460
1482
  /** Total points the voucher redemption requires off-chain. */
1461
1483
  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">;
1484
+ /** ERC-4337 account nonce for the user's EOA. */
1485
+ aaNonce: bigint;
1468
1486
  }
1469
1487
  type TopUpRedemptionResponse = {
1470
1488
  action: "NO_TOP_UP_NEEDED";
@@ -1480,8 +1498,8 @@ type TopUpRedemptionResponse = {
1480
1498
  redeem: PTRedeemResponse;
1481
1499
  };
1482
1500
  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);
1501
+ code: "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED";
1502
+ constructor(code: "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED", message: string);
1485
1503
  }
1486
1504
  declare class TopUpRedemptionHandler {
1487
1505
  private readonly ledger;