@pafi-dev/issuer 0.5.30 → 0.5.32

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/README.md CHANGED
@@ -17,10 +17,10 @@ ledger, policy engine, EIP-712 issuer signing, relay submission, and mint/burn e
17
17
  - TypeScript >= 5.0
18
18
  - `viem` ^2.0.0 and `@pafi-dev/core` ^0.5.16 (peer dependencies)
19
19
 
20
- > **Latest:** `0.5.28` — adds bundler-receipt fallback for status polling
21
- > and propagates Pimlico's re-estimated gas + bundler-required gas price
22
- > through `SponsorshipResponse`. Required to fix `AA34` paymaster-sig
23
- > errors and the "status stuck PENDING" indexer race. See
20
+ > **Latest:** `0.5.31` — `RelayService.prepareMint` / `prepareBurn`
21
+ > now auto-quote the operator fee and auto-resolve the PAFI fee
22
+ > recipient. Drop `feeAmount` / `feeRecipient` from your call sites;
23
+ > SDK reads gas price + Chainlink + V4 subgraph internally. See
24
24
  > [Changelog](#changelog).
25
25
 
26
26
  ---
@@ -386,6 +386,43 @@ try {
386
386
 
387
387
  ## Changelog
388
388
 
389
+ ### 0.5.31
390
+
391
+ `RelayService.prepareMint` / `prepareBurn` now auto-quote the operator
392
+ fee + auto-resolve the PAFI fee recipient when the service is
393
+ constructed with `provider + chainId`. `createIssuerService` wires
394
+ this automatically — issuer integrations don't need to change
395
+ anything to pick it up.
396
+
397
+ **API changes** (additive, backwards-compatible):
398
+
399
+ - `RelayService` constructor accepts an optional `RelayServiceConfig`
400
+ with `provider` + `chainId`. When set, callers can drop `feeAmount`
401
+ / `feeRecipient` from `prepareMint` / `prepareBurn` calls — the
402
+ service runs `quoteOperatorFeePt` (Chainlink ETH/USD + V4 subgraph
403
+ PT/USDT spot price) internally and pulls the canonical recipient
404
+ from `getContractAddresses(chainId).pafiFeeRecipient`.
405
+ - `prepareBurn` is now `async` (was sync) — the auto-quote requires
406
+ RPC + subgraph reads. Callers must `await`. `PTRedeemHandler`
407
+ already does this.
408
+ - `feeAmount: 0n` explicit means "no fee transfer" (force unsponsored
409
+ fallback variant). `undefined` means "auto-quote".
410
+ - Existing callers that pass `feeAmount + feeRecipient` keep working
411
+ — those values override the auto-resolve.
412
+
413
+ **Migration**: in your controller, drop the manual fee fetch when
414
+ calling `relayService.prepareMint`:
415
+
416
+ ```diff
417
+ - const { pafiFeeRecipient: feeRecipient } = getContractAddresses(chainId);
418
+ - const feeAmount = await issuerService.fee.estimateGasFee();
419
+ const userOp = await relayService.prepareMint({
420
+ ...,
421
+ - feeAmount,
422
+ - feeRecipient,
423
+ });
424
+ ```
425
+
389
426
  ### 0.5.28
390
427
 
391
428
  `PafiBackendClient.getUserOpReceipt(userOpHash)` added.
package/dist/index.cjs CHANGED
@@ -438,6 +438,43 @@ var RelayError = class extends Error {
438
438
  var import_viem3 = require("viem");
439
439
  var import_core2 = require("@pafi-dev/core");
440
440
  var RelayService = class {
441
+ provider;
442
+ chainId;
443
+ constructor(config = {}) {
444
+ this.provider = config.provider;
445
+ this.chainId = config.chainId;
446
+ }
447
+ /**
448
+ * Resolve the fee recipient + amount applied to the next UserOp:
449
+ *
450
+ * - If caller passed an explicit `feeRecipient`, use it (testing
451
+ * only — sponsor-relayer's L1 will reject any non-canonical
452
+ * recipient with `INSUFFICIENT_FEE`). Otherwise, default to
453
+ * `getContractAddresses(chainId).pafiFeeRecipient` when the
454
+ * service has a `chainId` configured.
455
+ * - If caller passed `feeAmount`, use it. Otherwise, when the
456
+ * service has both `provider` + `chainId`, auto-quote via
457
+ * `quoteOperatorFeePt`.
458
+ * - When the service is unconfigured AND caller passed nothing,
459
+ * return `{ feeAmount: 0n, feeRecipient: undefined }` — legacy
460
+ * "no fee" behavior, caller must opt in for the gas-reimbursement
461
+ * transfer to be added to the batch.
462
+ */
463
+ async resolveFee(params) {
464
+ const feeRecipient = params.feeRecipient ?? (this.chainId !== void 0 ? (0, import_core2.getContractAddresses)(this.chainId).pafiFeeRecipient : void 0);
465
+ if (params.feeAmount !== void 0) {
466
+ return { feeAmount: params.feeAmount, feeRecipient };
467
+ }
468
+ if (this.provider && this.chainId !== void 0) {
469
+ const feeAmount = await (0, import_core2.quoteOperatorFeePt)({
470
+ provider: this.provider,
471
+ chainId: this.chainId,
472
+ pointTokenAddress: params.pointTokenAddress
473
+ });
474
+ return { feeAmount, feeRecipient };
475
+ }
476
+ return { feeAmount: 0n, feeRecipient };
477
+ }
441
478
  /**
442
479
  * Build an unsigned UserOp for Scenario 1 (Mint) — sig-gated
443
480
  * `PointToken.mint(to, amount, deadline, minterSig)`.
@@ -522,14 +559,19 @@ var RelayService = class {
522
559
  data: mintCallData
523
560
  }
524
561
  ];
525
- if (params.feeAmount && params.feeAmount > 0n) {
526
- if (!params.feeRecipient) {
562
+ const { feeAmount, feeRecipient } = await this.resolveFee({
563
+ feeAmount: params.feeAmount,
564
+ feeRecipient: params.feeRecipient,
565
+ pointTokenAddress: params.pointTokenAddress
566
+ });
567
+ if (feeAmount > 0n) {
568
+ if (!feeRecipient) {
527
569
  throw new RelayError(
528
570
  "ENCODE_FAILED",
529
- "prepareMint: feeRecipient required when feeAmount > 0"
571
+ "prepareMint: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`."
530
572
  );
531
573
  }
532
- if (params.feeRecipient === "0x0000000000000000000000000000000000000000") {
574
+ if (feeRecipient === "0x0000000000000000000000000000000000000000") {
533
575
  throw new RelayError(
534
576
  "ENCODE_FAILED",
535
577
  "prepareMint: feeRecipient must not be zero address"
@@ -541,7 +583,7 @@ var RelayService = class {
541
583
  data: (0, import_viem3.encodeFunctionData)({
542
584
  abi: import_viem3.erc20Abi,
543
585
  functionName: "transfer",
544
- args: [params.feeRecipient, params.feeAmount]
586
+ args: [feeRecipient, feeAmount]
545
587
  })
546
588
  });
547
589
  }
@@ -567,7 +609,7 @@ var RelayService = class {
567
609
  * burnerSig)`. Caller provides a pre-signed `BurnRequest` + sig
568
610
  * bytes (typically from `PTRedeemHandler`).
569
611
  */
570
- prepareBurn(params) {
612
+ async prepareBurn(params) {
571
613
  if (!params.pointTokenAddress) {
572
614
  throw new RelayError("ENCODE_FAILED", "prepareBurn: pointTokenAddress required");
573
615
  }
@@ -608,14 +650,19 @@ var RelayService = class {
608
650
  );
609
651
  }
610
652
  const operations = [];
611
- if (params.feeAmount && params.feeAmount > 0n) {
612
- if (!params.feeRecipient) {
653
+ const { feeAmount, feeRecipient } = await this.resolveFee({
654
+ feeAmount: params.feeAmount,
655
+ feeRecipient: params.feeRecipient,
656
+ pointTokenAddress: params.pointTokenAddress
657
+ });
658
+ if (feeAmount > 0n) {
659
+ if (!feeRecipient) {
613
660
  throw new RelayError(
614
661
  "ENCODE_FAILED",
615
- "prepareBurn: feeRecipient required when feeAmount > 0"
662
+ "prepareBurn: feeRecipient could not be resolved \u2014 pass `feeRecipient` explicitly or construct RelayService with a `chainId`."
616
663
  );
617
664
  }
618
- if (params.feeRecipient === "0x0000000000000000000000000000000000000000") {
665
+ if (feeRecipient === "0x0000000000000000000000000000000000000000") {
619
666
  throw new RelayError(
620
667
  "ENCODE_FAILED",
621
668
  "prepareBurn: feeRecipient must not be zero address"
@@ -627,7 +674,7 @@ var RelayService = class {
627
674
  data: (0, import_viem3.encodeFunctionData)({
628
675
  abi: import_viem3.erc20Abi,
629
676
  functionName: "transfer",
630
- args: [params.feeRecipient, params.feeAmount]
677
+ args: [feeRecipient, feeAmount]
631
678
  })
632
679
  });
633
680
  }
@@ -1461,7 +1508,7 @@ var PTRedeemHandler = class {
1461
1508
  this.redeemLockDurationMs,
1462
1509
  this.pointTokenAddress
1463
1510
  );
1464
- const sponsoredUserOp = this.relayService.prepareBurn({
1511
+ const sponsoredUserOp = await this.relayService.prepareBurn({
1465
1512
  mode: "burnWithSig",
1466
1513
  userAddress: request.userAddress,
1467
1514
  aaNonce: request.aaNonce,
@@ -1495,15 +1542,19 @@ var PTRedeemHandler = class {
1495
1542
  this.redeemLockDurationMs,
1496
1543
  this.pointTokenAddress
1497
1544
  );
1498
- const fallbackUserOp = this.relayService.prepareBurn({
1545
+ const fallbackUserOp = await this.relayService.prepareBurn({
1499
1546
  mode: "burnWithSig",
1500
1547
  userAddress: request.userAddress,
1501
1548
  aaNonce: request.aaNonce,
1502
1549
  pointTokenAddress: this.pointTokenAddress,
1503
1550
  batchExecutorAddress: this.batchExecutorAddress,
1504
1551
  burnRequest: fallbackBurnRequest,
1505
- burnerSignature: fallbackSig
1506
- // No feeAmount/feeRecipient — fallback is fee-free.
1552
+ burnerSignature: fallbackSig,
1553
+ // Explicit 0n — fallback is fee-free regardless of how
1554
+ // RelayService is configured. Without this, an
1555
+ // auto-quoting RelayService would try to quote a fee here
1556
+ // and re-add the PT.transfer we're trying to strip.
1557
+ feeAmount: 0n
1507
1558
  });
1508
1559
  fallback = {
1509
1560
  lockId: fallbackLockId,
@@ -2239,7 +2290,10 @@ function createIssuerService(config) {
2239
2290
  authServiceConfig.jwtExpiresIn = config.auth.jwtExpiresIn;
2240
2291
  }
2241
2292
  const authService = new AuthService(authServiceConfig);
2242
- const relayService = new RelayService();
2293
+ const relayService = new RelayService({
2294
+ provider: config.provider,
2295
+ chainId: config.chainId
2296
+ });
2243
2297
  let feeManager;
2244
2298
  if (config.fee) {
2245
2299
  feeManager = new FeeManager({