@pafi-dev/issuer 0.3.0-beta.2 → 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.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Address, Hex, PublicClient, Chain } from 'viem';
2
- import { PointTokenDomainConfig, MintRequest, EIP712Signature, MintParams, SwapParams, MintRequestV2, SignatureStruct, PartialUserOperation, BurnConsent, 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,69 +552,81 @@ 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
555
+ * Submit a `mintAndSwap` transaction (legacy v0.2 `/claim-and-swap`).
556
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.
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
- 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>;
583
578
  /**
584
579
  * Build an unsigned UserOp for Scenario 2 (Burn/Redeem).
585
580
  *
586
581
  * Two modes:
587
- * - `mode: 'burn'` — direct `PointToken.burn(amount)`; `msg.sender`
588
- * via EIP-7702 delegation is the user, so no signature needed
589
- * on-chain (the BurnConsent was already verified off-chain by
590
- * the issuer backend before we got here)
591
- * - `mode: 'burnWithSig'` `PointToken.burnWithSig(consent, sig)`;
592
- * used when the issuer hasn't verified the consent and the
593
- * 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.
594
589
  */
595
590
  prepareBurn(params: PrepareBurnParams): PartialUserOperation;
596
591
  }
592
+ /**
593
+ * v1.4 — sig-gated `PointToken.mint(to, amount, deadline, minterSig)`.
594
+ *
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.
600
+ */
597
601
  interface PrepareMintParams {
598
602
  /** User EOA that will send the UserOp (via EIP-7702 delegation). */
599
603
  userAddress: Address;
600
- /** ERC-4337 account nonce (not the MintRequest nonce — different namespace). */
604
+ /** ERC-4337 account nonce. Caller fetches from EntryPoint v0.7. */
601
605
  aaNonce: bigint;
602
- /** Deployed Relayer v2 contract address (chain-specific). */
603
- relayerAddress: Address;
604
606
  /** BatchExecutor delegation target (chain-specific). */
605
607
  batchExecutorAddress: Address;
606
- /** PointToken being minted used for optional fee transfer call. */
608
+ /** PointToken contractthe call target + EIP-712 verifying contract. */
607
609
  pointTokenAddress: Address;
608
- /** EIP-712-signed MintRequest fields. */
609
- mintRequest: MintRequestV2;
610
- /** User's EIP-712 signature over `mintRequest`. */
611
- userSignature: SignatureStruct;
612
- /** Issuer's EIP-712 signature over `mintRequest`. */
613
- issuerSignature: SignatureStruct;
610
+ /** PT amount to mint to `userAddress`. */
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;
624
+ /**
625
+ * Optional — application-level fee transfer appended after mint.
626
+ * Set both `feeAmount` and `feeRecipient` together.
627
+ */
628
+ feeAmount?: bigint;
629
+ feeRecipient?: Address;
614
630
  /** Gas limits — defaults are conservative; caller can tighten. */
615
631
  callGasLimit?: bigint;
616
632
  verificationGasLimit?: bigint;
@@ -632,8 +648,10 @@ interface PrepareBurnDirectParams extends PrepareBurnCommonParams {
632
648
  }
633
649
  interface PrepareBurnWithSigParams extends PrepareBurnCommonParams {
634
650
  mode: "burnWithSig";
635
- burnConsent: BurnConsent;
636
- consentSignature: SignatureStruct;
651
+ /** BurnRequest message the issuer burner signer signed. */
652
+ burnRequest: BurnRequest;
653
+ /** Serialized EIP-712 signature (bytes) over `burnRequest`. */
654
+ burnerSignature: Hex;
637
655
  }
638
656
 
639
657
  interface FeeManagerConfig {
@@ -1330,17 +1348,29 @@ declare class IssuerApiHandlers {
1330
1348
  }
1331
1349
 
1332
1350
  /**
1333
- * v1.4 reverse flow — **Variant A**: user-initiated PT redeem.
1351
+ * v1.4 reverse flow — user-initiated PT redeem.
1334
1352
  *
1335
- * User has on-chain PT, wants to convert back to off-chain points. They
1336
- * sign a `BurnConsent`, the issuer backend verifies it, reserves an
1337
- * off-chain credit, and returns an unsigned UserOp that the frontend
1338
- * submits via the Bundler. When the burn lands, the `BurnIndexer`
1339
- * (elsewhere) resolves the credit.
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.
1340
1359
  *
1341
- * **Mocked SC contracts**this handler compiles + wires end-to-end
1342
- * against `@pafi-dev/core/contracts` mock ABIs. When SC ships real
1343
- * ABIs, no changes here only `contracts/index.ts` re-export flips.
1360
+ * Contract path (mock ABI matches deployed PointToken):
1361
+ *
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.
1344
1374
  */
1345
1375
  interface PTRedeemHandlerConfig {
1346
1376
  ledger: IPointLedger;
@@ -1350,32 +1380,41 @@ interface PTRedeemHandlerConfig {
1350
1380
  pointTokenAddress: Address;
1351
1381
  /** BatchExecutor delegation target (chain-specific). */
1352
1382
  batchExecutorAddress: Address;
1353
- /** Chain id — used for domain separator when verifying BurnConsent. */
1383
+ /** Chain id — used for the BurnRequest EIP-712 domain. */
1354
1384
  chainId: number;
1355
1385
  /**
1356
1386
  * EIP-712 domain fields. Must match the on-chain PointToken's domain
1357
- * separator exactly, or signature verification fails. `name` is
1358
- * typically the PointToken's ERC-20 name (e.g. "PAFI Starbucks
1359
- * 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`.
1360
1390
  */
1361
1391
  domain: {
1362
1392
  name: string;
1363
1393
  verifyingContract?: Address;
1364
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;
1365
1401
  /**
1366
1402
  * How long the pending credit stays reserved if the burn never lands.
1367
1403
  * Default: 15 min — long enough for a bundler submission + confirmation.
1368
1404
  */
1369
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;
1370
1412
  /** Clock injection for tests; defaults to `Date.now`. */
1371
1413
  now?: () => number;
1372
1414
  }
1373
1415
  interface PTRedeemRequest {
1374
1416
  userAddress: Address;
1375
1417
  amount: bigint;
1376
- /** Serialized EIP-712 signature over the BurnConsent. */
1377
- consentSignature: Hex;
1378
- consent: BurnConsent;
1379
1418
  /** ERC-4337 account nonce for the user's EOA. */
1380
1419
  aaNonce: bigint;
1381
1420
  }
@@ -1386,19 +1425,24 @@ interface PTRedeemResponse {
1386
1425
  userOp: PartialUserOperation;
1387
1426
  /** Seconds until the lock expires if the burn doesn't land. */
1388
1427
  expiresInSeconds: number;
1428
+ /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
1429
+ signatureDeadline: bigint;
1389
1430
  }
1390
1431
  declare class PTRedeemError extends Error {
1391
- code: "INVALID_CONSENT" | "SIGNATURE_MISMATCH" | "AMOUNT_MISMATCH" | "EXPIRED_CONSENT" | "LEDGER_NOT_SUPPORTED";
1392
- 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);
1393
1434
  }
1394
1435
  declare class PTRedeemHandler {
1395
1436
  private readonly ledger;
1396
1437
  private readonly relayService;
1438
+ private readonly provider;
1397
1439
  private readonly pointTokenAddress;
1398
1440
  private readonly batchExecutorAddress;
1399
1441
  private readonly chainId;
1400
1442
  private readonly domain;
1443
+ private readonly burnerSignerWallet;
1401
1444
  private readonly redeemLockDurationMs;
1445
+ private readonly signatureDeadlineSeconds;
1402
1446
  private readonly now;
1403
1447
  constructor(config: PTRedeemHandlerConfig);
1404
1448
  handle(request: PTRedeemRequest): Promise<PTRedeemResponse>;
@@ -1419,8 +1463,12 @@ declare class PTRedeemHandler {
1419
1463
  * → burn 200 PT, credit 200 off-chain, voucher proceeds with 500
1420
1464
  *
1421
1465
  * Delegates the actual burn construction to {@link PTRedeemHandler}
1422
- * — this handler is pure business logic (calculating shortfall +
1423
- * 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`.
1424
1472
  */
1425
1473
  interface TopUpRedemptionHandlerConfig {
1426
1474
  ledger: IPointLedger;
@@ -1433,12 +1481,8 @@ interface TopUpRedemptionRequest {
1433
1481
  userAddress: Address;
1434
1482
  /** Total points the voucher redemption requires off-chain. */
1435
1483
  requiredAmount: bigint;
1436
- /**
1437
- * The user's pre-signed `BurnConsent` + signature, prepared by the FE
1438
- * with amount set to a worst-case upper bound. Handler inspects the
1439
- * shortfall and uses the consent if the shortfall ≤ consent.amount.
1440
- */
1441
- redeemRequest: Pick<PTRedeemRequest, "consent" | "consentSignature" | "aaNonce">;
1484
+ /** ERC-4337 account nonce for the user's EOA. */
1485
+ aaNonce: bigint;
1442
1486
  }
1443
1487
  type TopUpRedemptionResponse = {
1444
1488
  action: "NO_TOP_UP_NEEDED";
@@ -1454,8 +1498,8 @@ type TopUpRedemptionResponse = {
1454
1498
  redeem: PTRedeemResponse;
1455
1499
  };
1456
1500
  declare class TopUpRedemptionError extends Error {
1457
- code: "INSUFFICIENT_ONCHAIN_BALANCE" | "CONSENT_AMOUNT_TOO_LOW" | "LEDGER_NOT_SUPPORTED";
1458
- 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);
1459
1503
  }
1460
1504
  declare class TopUpRedemptionHandler {
1461
1505
  private readonly ledger;