@pafi-dev/issuer 0.3.0-beta.8 → 0.4.0

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, WalletClient } from 'viem';
2
- import { PointTokenDomainConfig, MintRequest, EIP712Signature, PartialUserOperation, BurnRequest, ReceiverConsent, PathKey, PoolKey, SponsorAuthScenario, SponsorAuthPayload, SponsorshipScenario } from '@pafi-dev/core';
1
+ import { Address, Hex, PublicClient, WalletClient } from 'viem';
2
+ import { PointTokenDomainConfig, PartialUserOperation, BurnRequest, ReceiverConsent, PathKey, PoolKey } from '@pafi-dev/core';
3
3
 
4
4
  /**
5
5
  * Lifecycle of a minting request as tracked by the issuer's point ledger.
@@ -62,11 +62,7 @@ interface IPointLedger {
62
62
  lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number, tokenAddress?: Address): Promise<string>;
63
63
  /** Release a previously created lock (e.g. on tx failure / cancel). */
64
64
  releaseLock(lockId: string): Promise<void>;
65
- /**
66
- * Permanently deduct an amount from a user's balance after the on-chain
67
- * mint has been observed by the indexer. Should also resolve any matching
68
- * lock so the funds aren't double-counted.
69
- */
65
+ /** Deduct balance after a confirmed on-chain mint. Idempotent on `txHash`. */
70
66
  deductBalance(userAddress: Address, amount: bigint, txHash: Hex, tokenAddress?: Address): Promise<void>;
71
67
  /** Credit points to a user's balance (e.g. from merchant activity). */
72
68
  creditBalance(userAddress: Address, amount: bigint, reason: string, tokenAddress?: Address): Promise<void>;
@@ -97,47 +93,6 @@ interface IPointLedger {
97
93
  resolveCreditByBurnTx?(lockId: string, txHash: Hex): Promise<void>;
98
94
  }
99
95
 
100
- /**
101
- * In-memory IPointLedger implementation for development and tests.
102
- *
103
- * NOT for production — state is lost on restart. Issuers should ship their
104
- * own database-backed implementation.
105
- *
106
- * Concurrency model: single-process, single-threaded (Node.js event loop).
107
- * The lock check + insert is atomic within a tick because no awaits sit
108
- * between balance read and lock write.
109
- *
110
- * **Multi-token (0.2.0):** Balances are keyed by `(user, token)`. If callers
111
- * omit `tokenAddress`, the literal string "default" is used — that keeps
112
- * single-token usage working exactly like 0.1.x.
113
- */
114
- declare class MemoryPointLedger implements IPointLedger {
115
- private balances;
116
- private locks;
117
- private nextLockId;
118
- private now;
119
- constructor(opts?: {
120
- now?: () => number;
121
- });
122
- getBalance(userAddress: Address, tokenAddress?: Address): Promise<bigint>;
123
- getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
124
- creditBalance(userAddress: Address, amount: bigint, _reason: string, tokenAddress?: Address): Promise<void>;
125
- lockForMinting(userAddress: Address, amount: bigint, lockDurationMs: number, tokenAddress?: Address): Promise<string>;
126
- releaseLock(lockId: string): Promise<void>;
127
- deductBalance(userAddress: Address, amount: bigint, txHash: Hex, tokenAddress?: Address): Promise<void>;
128
- updateMintStatus(lockId: string, status: MintingStatus, txHash?: Hex): Promise<void>;
129
- private pendingCredits;
130
- private nextCreditId;
131
- reservePendingCredit(userAddress: Address, amount: bigint, durationMs: number, tokenAddress?: Address): Promise<string>;
132
- resolveCreditByBurnTx(lockId: string, txHash: Hex): Promise<void>;
133
- /**
134
- * Auto-expire any PENDING lock past its expiry. Called lazily on every
135
- * read/write so the in-memory state stays self-cleaning without a timer.
136
- */
137
- private purgeExpired;
138
- private lockedTotalFor;
139
- }
140
-
141
96
  /**
142
97
  * Input to a policy evaluation. Policy engines use this to decide whether
143
98
  * a user's mint request should be approved.
@@ -213,56 +168,6 @@ declare class DefaultPolicyEngine implements IPolicyEngine {
213
168
  evaluate(request: PolicyEvalRequest): Promise<PolicyDecision>;
214
169
  }
215
170
 
216
- /**
217
- * Issuer signer — produces the MintRequest EIP-712 signature that the Relay
218
- * contract verifies against the issuer's on-chain authorized minter.
219
- *
220
- * This is a trust boundary: the default `PrivateKeySigner` holds the key in
221
- * process memory and is intended for local development only. Production
222
- * issuers replace this with an HSM/KMS/MPC integration.
223
- */
224
- interface IIssuerSigner {
225
- /**
226
- * Sign a `MintRequest` message against the point token's EIP-712 domain.
227
- * The returned signature is what the Relay contract passes to
228
- * `PointToken.verify` during `mintAndSwap`.
229
- */
230
- signMintRequest(domain: PointTokenDomainConfig, message: MintRequest): Promise<EIP712Signature>;
231
- /** Get the signer's on-chain address (used for verification / logging). */
232
- getAddress(): Promise<Address>;
233
- }
234
-
235
- interface PrivateKeySignerOptions {
236
- /** 0x-prefixed 32-byte hex private key */
237
- privateKey: Hex;
238
- /**
239
- * Chain metadata for the viem WalletClient. Only the chain id is actually
240
- * used for signing; a minimal stub is acceptable (it does not need to
241
- * match the deployed chain config beyond id).
242
- */
243
- chain: Chain;
244
- /**
245
- * Optional RPC URL. `signTypedData` is offline, so this can usually be
246
- * left unset — viem only requires a transport to construct the client.
247
- */
248
- rpcUrl?: string;
249
- }
250
- /**
251
- * Local-key implementation of `IIssuerSigner`. Wraps viem's `signTypedData`
252
- * via the shared `@pafi/core` `signMintRequest` helper.
253
- *
254
- * ⚠️ **NOT for production use.** The private key lives in process memory
255
- * and is trivially extractable from a compromised host. Replace with an
256
- * HSM/KMS/MPC-backed `IIssuerSigner` before deployment.
257
- */
258
- declare class PrivateKeySigner implements IIssuerSigner {
259
- private readonly account;
260
- private readonly walletClient;
261
- constructor(opts: PrivateKeySignerOptions);
262
- signMintRequest(domain: PointTokenDomainConfig, message: MintRequest): Promise<EIP712Signature>;
263
- getAddress(): Promise<Address>;
264
- }
265
-
266
171
  /**
267
172
  * A server-issued session created after a successful wallet login. The
268
173
  * token id is embedded in the JWT so sessions can be revoked without
@@ -609,6 +514,9 @@ declare class FeeManager {
609
514
  private readonly gasUnits;
610
515
  private readonly gasPremiumBps;
611
516
  private readonly quoteNativeToFee;
517
+ private cachedFee;
518
+ private cacheExpiresAt;
519
+ private static readonly CACHE_TTL_MS;
612
520
  constructor(config: FeeManagerConfig);
613
521
  /**
614
522
  * Estimate the fee (in the caller's fee currency) to charge for the
@@ -784,6 +692,15 @@ interface BurnIndexerConfig {
784
692
  /** Polling interval (ms). Default: 5000. */
785
693
  pollIntervalMs?: number;
786
694
  now?: () => number;
695
+ /**
696
+ * Map a burn event to the pending credit lockId that should be resolved.
697
+ * Return `undefined` to skip this burn event (no credit granted).
698
+ *
699
+ * REQUIRED — there is no default implementation. Issuers with a Postgres
700
+ * ledger typically JOIN on `(from, amount, status=PENDING)`. The in-memory
701
+ * ledger uses a lookup by lockId supplied out-of-band from the claim flow.
702
+ */
703
+ matchLockId: (event: BurnEvent) => Promise<string | undefined>;
787
704
  }
788
705
  /**
789
706
  * Mirror of `PointIndexer` for the reverse direction — watches
@@ -814,17 +731,6 @@ declare class BurnIndexer {
814
731
  private readonly confirmations;
815
732
  private readonly batchSize;
816
733
  private readonly pollIntervalMs;
817
- /**
818
- * Caller-supplied matcher. Return the lockId to resolve for a given
819
- * burn event, or `undefined` to skip. Runs synchronously via the
820
- * ledger's query path.
821
- *
822
- * Default: try `ledger.resolveCreditByBurnTx` keyed on a synthetic
823
- * lock id `burn-${from}-${amount}` — the in-memory ledger assigns
824
- * incrementing IDs so callers with the memory ledger must provide a
825
- * custom matcher. Real DB-backed ledgers override this to JOIN on
826
- * their `pending_credits` table.
827
- */
828
734
  matchLockId: (event: BurnEvent) => Promise<string | undefined>;
829
735
  private running;
830
736
  private timer;
@@ -974,6 +880,27 @@ interface ApiClaimAndSwapResponse {
974
880
  blockNumber?: bigint;
975
881
  gasUsed?: bigint;
976
882
  }
883
+ interface ApiClaimRequest {
884
+ chainId: number;
885
+ pointTokenAddress: Address;
886
+ /** PT amount to mint. */
887
+ amount: bigint;
888
+ /** ERC-4337 account nonce for the user's EOA (from EntryPoint). */
889
+ aaNonce: bigint;
890
+ /** Unix seconds — when the MintRequest signature expires. */
891
+ deadline: bigint;
892
+ /** Optional operator fee (PT) deducted inside the same UserOp batch. */
893
+ feeAmount?: bigint;
894
+ feeRecipient?: Address;
895
+ }
896
+ interface ApiClaimResponse {
897
+ /** Off-chain lock id — poll `/user` to track PENDING → MINTED. */
898
+ lockId: string;
899
+ /** Unsigned UserOp — attach paymaster data + user signature, then submit. */
900
+ userOp: PartialUserOperation;
901
+ /** Seconds until the off-chain lock expires if the UserOp is not submitted. */
902
+ expiresInSeconds: number;
903
+ }
977
904
  interface ApiBuildConsentTypedDataRequest {
978
905
  chainId: number;
979
906
  pointTokenAddress: Address;
@@ -1024,6 +951,20 @@ interface IssuerApiHandlersConfig {
1024
951
  feeManager?: FeeManager;
1025
952
  /** Required by `handlePools`; omit to disable the endpoint. */
1026
953
  poolsProvider?: PoolsProvider;
954
+ /**
955
+ * Required by `handleClaim`; omit to disable the endpoint.
956
+ * Wires policy evaluation + ledger locking + MintRequest signing
957
+ * into a single atomic handler so callers cannot accidentally skip
958
+ * the policy check.
959
+ */
960
+ claim?: {
961
+ policy: IPolicyEngine;
962
+ relayService: RelayService;
963
+ issuerSignerWallet: WalletClient;
964
+ batchExecutorAddress: Address;
965
+ /** How long to hold the off-chain lock while the UserOp is in flight. Default: 15 min. */
966
+ lockDurationMs?: number;
967
+ };
1027
968
  }
1028
969
  /**
1029
970
  * Framework-agnostic HTTP handlers that match the endpoints a `PafiSDK`
@@ -1045,15 +986,12 @@ declare class IssuerApiHandlers {
1045
986
  * validate the request's `pointTokenAddress` against this set.
1046
987
  */
1047
988
  private readonly supportedTokens;
1048
- /** First supported token — used as default when a handler doesn't
1049
- * receive a `pointTokenAddress` in the request (shouldn't happen in
1050
- * practice, but keeps type-narrowing happy). */
1051
- private readonly defaultToken;
1052
989
  private readonly chainId;
1053
990
  private readonly contracts;
1054
991
  private readonly pafiWebUrl?;
1055
992
  private readonly feeManager?;
1056
993
  private readonly poolsProvider?;
994
+ private readonly claim?;
1057
995
  constructor(config: IssuerApiHandlersConfig);
1058
996
  /** `GET /auth/nonce` */
1059
997
  handleGetNonce(): Promise<ApiNonceResponse>;
@@ -1099,6 +1037,22 @@ declare class IssuerApiHandlers {
1099
1037
  * mobile apps — no app store review needed.
1100
1038
  */
1101
1039
  handleBuildConsentTypedData(userAddress: Address, request: ApiBuildConsentTypedDataRequest): Promise<ApiBuildConsentTypedDataResponse>;
1040
+ /**
1041
+ * `POST /claim`
1042
+ *
1043
+ * Policy gate + ledger lock + MintRequest signing in a single atomic
1044
+ * step. Returns an unsigned UserOp the frontend attaches paymaster data
1045
+ * to and submits via EIP-7702 + Bundler.
1046
+ *
1047
+ * Order of operations:
1048
+ * 1. Validate request fields.
1049
+ * 2. policy.evaluate() — throws if denied; cannot be bypassed.
1050
+ * 3. ledger.lockForMinting() — reserves the balance.
1051
+ * 4. Read on-chain mintRequestNonce + token name in parallel.
1052
+ * 5. relayService.prepareMint() — sign MintRequest + encode UserOp.
1053
+ * 6. On any error after step 3, release the lock before re-throwing.
1054
+ */
1055
+ handleClaim(userAddress: Address, request: ApiClaimRequest): Promise<ApiClaimResponse>;
1102
1056
  }
1103
1057
 
1104
1058
  /**
@@ -1167,6 +1121,8 @@ interface PTRedeemHandlerConfig {
1167
1121
  now?: () => number;
1168
1122
  }
1169
1123
  interface PTRedeemRequest {
1124
+ /** Address extracted from the verified JWT — must match `userAddress`. */
1125
+ authenticatedAddress: Address;
1170
1126
  userAddress: Address;
1171
1127
  amount: bigint;
1172
1128
  /** ERC-4337 account nonce for the user's EOA. */
@@ -1183,8 +1139,8 @@ interface PTRedeemResponse {
1183
1139
  signatureDeadline: bigint;
1184
1140
  }
1185
1141
  declare class PTRedeemError extends Error {
1186
- code: "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED";
1187
- constructor(code: "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED", message: string);
1142
+ code: "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED";
1143
+ constructor(code: "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED", message: string);
1188
1144
  }
1189
1145
  declare class PTRedeemHandler {
1190
1146
  private readonly ledger;
@@ -1232,6 +1188,8 @@ interface TopUpRedemptionHandlerConfig {
1232
1188
  pointTokenAddress: Address;
1233
1189
  }
1234
1190
  interface TopUpRedemptionRequest {
1191
+ /** Address extracted from the verified JWT — must match `userAddress`. */
1192
+ authenticatedAddress: Address;
1235
1193
  userAddress: Address;
1236
1194
  /** Total points the voucher redemption requires off-chain. */
1237
1195
  requiredAmount: bigint;
@@ -1252,8 +1210,8 @@ type TopUpRedemptionResponse = {
1252
1210
  redeem: PTRedeemResponse;
1253
1211
  };
1254
1212
  declare class TopUpRedemptionError extends Error {
1255
- code: "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED";
1256
- constructor(code: "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED", message: string);
1213
+ code: "UNAUTHORIZED" | "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED";
1214
+ constructor(code: "UNAUTHORIZED" | "INSUFFICIENT_ONCHAIN_BALANCE" | "LEDGER_NOT_SUPPORTED", message: string);
1257
1215
  }
1258
1216
  declare class TopUpRedemptionHandler {
1259
1217
  private readonly ledger;
@@ -1438,221 +1396,66 @@ declare class BalanceAggregator {
1438
1396
  }
1439
1397
 
1440
1398
  interface RetryConfig {
1441
- /**
1442
- * Max total attempts including the first try. Default: 1 (no retry).
1443
- * Set to 3 to get 2 retries after the initial call.
1444
- *
1445
- * Only applies when the server error body carries `safeToRetry: true`
1446
- * or the failure is a transient network/timeout error.
1447
- */
1448
1399
  maxAttempts?: number;
1449
- /**
1450
- * Initial backoff delay in ms. Default: 500. Each subsequent retry
1451
- * doubles this (exponential backoff) and adds ±20% jitter.
1452
- */
1453
1400
  initialDelayMs?: number;
1454
- /**
1455
- * Hard ceiling for a single backoff (ms). Default: 10_000.
1456
- */
1457
1401
  maxDelayMs?: number;
1458
- /**
1459
- * Upper bound on `retryAfter` from the server. If the server asks us
1460
- * to wait longer than this (e.g. rate limit until UTC midnight), the
1461
- * client gives up rather than blocking. Default: 30_000.
1462
- */
1463
1402
  maxRetryAfterMs?: number;
1464
1403
  }
1465
1404
  interface PafiBackendConfig {
1466
- /**
1467
- * PAFI Backend API base URL. Example:
1468
- * https://api.pacificfinance.org
1469
- * https://staging-api.pacificfinance.org
1470
- */
1471
1405
  url: string;
1472
- /** PAFI-assigned issuer ID (e.g., "gg56"). Sent in X-Issuer-Id header. */
1473
1406
  issuerId: string;
1474
- /** Per-issuer API key (or JWT) for the Authorization header. */
1475
1407
  apiKey: string;
1476
- /** Optional fetch override for tests. */
1477
1408
  fetchImpl?: typeof fetch;
1478
- /**
1479
- * Timeout (ms) for each request. Default: 10_000. PAFI Backend should
1480
- * respond in <1s for the happy path; this is just the sanity bound.
1481
- */
1482
1409
  timeoutMs?: number;
1483
- /**
1484
- * Retry policy for transient failures (5xx, 429, timeouts, network).
1485
- * Omit or pass `{ maxAttempts: 1 }` to disable retry entirely.
1486
- */
1487
1410
  retry?: RetryConfig;
1488
1411
  }
1489
- /**
1490
- * @deprecated Coinbase paymaster path — replaced by SponsorAuth in beta.8.
1491
- * Kept for back-compat with consumers still on Coinbase. Will be removed
1492
- * in 1.0.
1493
- */
1494
- interface SponsorshipRequest {
1495
- chainId: number;
1496
- scenario: SponsorshipScenario;
1497
- userOp: PartialUserOperation;
1498
- target: {
1499
- contract: Address;
1500
- function: string;
1501
- pointToken?: Address;
1502
- };
1503
- }
1504
- /** @deprecated See `SponsorshipRequest`. Use `SponsorAuthResponse` in beta.8+. */
1505
- interface SponsorshipResponse {
1506
- paymaster: Address;
1507
- paymasterData: Hex;
1508
- paymasterVerificationGasLimit: bigint;
1509
- paymasterPostOpGasLimit: bigint;
1510
- expiresAt: number;
1511
- }
1512
-
1513
- /**
1514
- * Request body for `POST /sponsor-auth` against PAFI sponsor-relayer.
1515
- * See `pafi-backend/docs/SPONSOR_AUTH_DESIGN.md` for the full spec.
1516
- *
1517
- * The endpoint:
1518
- * 1. Authenticates user (JWT) + issuer (API key)
1519
- * 2. Per-(user, scenario) rate-limits
1520
- * 3. Per-issuer daily budget check
1521
- * 4. Scenario-specific intent validation (mint cap, KYC, etc.)
1522
- * 5. Allocates a unique nonce
1523
- * 6. Signs the SponsorAuth payload via KMS-backed PAFI key
1524
- * 7. Returns `{ sponsorAuth, payload }` for FE to forward to Privy
1525
- */
1526
- interface SponsorAuthRequest {
1527
- chainId: number;
1528
- scenario: SponsorAuthScenario;
1529
- /** The exact UserOp the FE intends to submit. callDataHash binds to this. */
1530
- userOp: PartialUserOperation;
1531
- /** Issuer ID this sponsorship is billed against (e.g. "gg56"). */
1532
- issuerId: string;
1533
- /**
1534
- * Scenario-specific context for intent validation. The relayer reads
1535
- * these to apply scenario rules — e.g. preValidateMint reads
1536
- * pointToken + amount to check the issuer cap.
1537
- */
1538
- context?: {
1539
- pointToken?: Address;
1540
- /** Decimal string representation (handles bigint over JSON). */
1541
- amount?: string;
1542
- brokerHash?: Hex;
1543
- };
1544
- }
1545
- /**
1546
- * Response from `POST /sponsor-auth`.
1547
- *
1548
- * `payload` is returned alongside `sponsorAuth` so the FE doesn't have
1549
- * to reconstruct the message — just forwards both to Privy. Privy
1550
- * verifier checks `signature` recovers to PAFI's registered pubkey
1551
- * over `payload`.
1552
- */
1553
- interface SponsorAuthResponse {
1554
- /** EIP-712 signature, hex-encoded 65 bytes. */
1555
- sponsorAuth: Hex;
1556
- /** The payload that was signed. Pass to Privy alongside the signature. */
1557
- payload: SponsorAuthPayload;
1558
- }
1559
- /**
1560
- * Machine-readable error codes returned by PAFI sponsor-relayer.
1561
- *
1562
- * Source of truth: `apps/sponsor-relayer/src/**` (renamed from
1563
- * paymaster-proxy in beta.8). Keep in sync.
1564
- */
1565
- type PafiBackendErrorCode = "MISSING_ISSUER_ID" | "MISSING_API_KEY" | "ISSUER_UNAUTHORIZED" | "USER_UNAUTHORIZED" | "INTENT_REJECTED" | "MINT_CAP_EXCEEDED" | "ISSUER_INACTIVE" | "BROKER_NOT_WHITELISTED" | "RATE_LIMIT_EXCEEDED" | "RATE_LIMIT_EXCEEDED_DAILY" | "RATE_LIMIT_EXCEEDED_PER_USER" | "ISSUER_BUDGET_EXCEEDED" | "RATE_LIMITER_UNAVAILABLE" | "KMS_UNAVAILABLE" | "SPONSOR_AUTH_SIGNING_FAILED" | "PAYMASTER_REJECTED" | "PAYMASTER_UNAVAILABLE" | "PAYMASTER_TIMEOUT" | "CALLDATA_INVALID" | "CALLDATA_EMPTY_BATCH" | "TARGET_NOT_ALLOWLISTED" | "FUNCTION_NOT_ALLOWED" | "SCENARIO_MISMATCH" | "SCENARIO_DISABLED" | "BAD_REQUEST" | "INTERNAL_ERROR" | "TIMEOUT" | "NETWORK_ERROR";
1412
+ type PafiBackendErrorCode = "MISSING_ISSUER_ID" | "MISSING_API_KEY" | "ISSUER_UNAUTHORIZED" | "USER_UNAUTHORIZED" | "INTENT_REJECTED" | "MINT_CAP_EXCEEDED" | "ISSUER_INACTIVE" | "BROKER_NOT_WHITELISTED" | "RATE_LIMIT_EXCEEDED" | "RATE_LIMIT_EXCEEDED_DAILY" | "RATE_LIMIT_EXCEEDED_PER_USER" | "ISSUER_BUDGET_EXCEEDED" | "RATE_LIMITER_UNAVAILABLE" | "PAYMASTER_UNAVAILABLE" | "TARGET_NOT_ALLOWLISTED" | "BAD_REQUEST" | "INTERNAL_ERROR" | "TIMEOUT" | "NETWORK_ERROR" | (string & {});
1566
1413
  declare class PafiBackendError extends Error {
1567
1414
  code: PafiBackendErrorCode;
1568
1415
  httpStatus: number;
1569
1416
  details?: unknown | undefined;
1570
- /**
1571
- * Seconds to wait before retry. Populated from the server body
1572
- * (e.g. rate limit returns the number of seconds until UTC midnight).
1573
- */
1574
1417
  readonly retryAfter?: number;
1575
- /**
1576
- * `safeToRetry` as reported by the server body. Prefer this over the
1577
- * code-based heuristic when available — the server knows more about
1578
- * whether the same request will succeed on retry.
1579
- */
1580
1418
  private readonly serverSafeToRetry?;
1581
1419
  constructor(code: PafiBackendErrorCode, message: string, httpStatus: number, details?: unknown | undefined, opts?: {
1582
1420
  retryAfter?: number;
1583
1421
  safeToRetry?: boolean;
1584
1422
  });
1585
- /**
1586
- * Whether the caller can safely retry the same request.
1587
- *
1588
- * If the server provided `safeToRetry` in the body, trust that.
1589
- * Otherwise fall back to a code-based heuristic.
1590
- */
1591
1423
  get safeToRetry(): boolean;
1592
1424
  }
1593
1425
 
1594
- /**
1595
- * HTTP client for the PAFI Backend paymaster proxy service. See
1596
- * [SPONSORED_PATH_FLOW.md] for the full flow + API contract.
1597
- *
1598
- * This client sits between `@pafi/issuer`'s RelayService and the
1599
- * PAFI Backend. It does NOT talk to Coinbase Paymaster directly —
1600
- * PAFI Backend holds that integration.
1601
- */
1426
+ interface SponsorshipUserOp {
1427
+ sender: Address;
1428
+ nonce: bigint;
1429
+ callData: Hex;
1430
+ callGasLimit: bigint;
1431
+ verificationGasLimit: bigint;
1432
+ preVerificationGas: bigint;
1433
+ maxFeePerGas: bigint;
1434
+ maxPriorityFeePerGas: bigint;
1435
+ }
1436
+ interface SponsorshipTarget {
1437
+ contract: Address;
1438
+ function: string;
1439
+ pointToken: Address;
1440
+ }
1441
+ interface SponsorshipRequest {
1442
+ chainId: number;
1443
+ scenario: string;
1444
+ userOp: SponsorshipUserOp;
1445
+ target: SponsorshipTarget;
1446
+ }
1447
+ interface SponsorshipResponse {
1448
+ paymaster: Address;
1449
+ paymasterData: Hex;
1450
+ paymasterVerificationGasLimit: bigint;
1451
+ paymasterPostOpGasLimit: bigint;
1452
+ expiresAt: number;
1453
+ }
1602
1454
  declare class PafiBackendClient {
1603
- private readonly url;
1604
- private readonly issuerId;
1605
- private readonly apiKey;
1606
- private readonly fetchImpl;
1607
- private readonly timeoutMs;
1608
- private readonly retry;
1455
+ private readonly config;
1609
1456
  constructor(config: PafiBackendConfig);
1610
- /**
1611
- * Request a SponsorAuth signature from PAFI sponsor-relayer (beta.8+).
1612
- *
1613
- * The relayer:
1614
- * 1. Authenticates user (JWT) + issuer (API key)
1615
- * 2. Per-(user, scenario) rate limit + per-issuer daily budget
1616
- * 3. Scenario-specific intent validation (mint cap, KYC, etc.)
1617
- * 4. Allocates nonce + signs SponsorAuth payload via KMS PAFI key
1618
- * 5. Returns `{ sponsorAuth, payload }` for the FE to forward to
1619
- * Privy's `signUserOperation({ sponsorAuth, payload })`.
1620
- *
1621
- * Retries on transient failures (5xx, timeouts, KMS unavailable,
1622
- * rate-limit-with-retryAfter). 4xx that are not `safeToRetry` fail fast.
1623
- *
1624
- * See `pafi-backend/docs/SPONSOR_AUTH_DESIGN.md` for the full spec.
1625
- *
1626
- * @throws PafiBackendError on final failure after exhausting retries
1627
- */
1628
- requestSponsorAuth(req: SponsorAuthRequest): Promise<SponsorAuthResponse>;
1629
- /**
1630
- * @deprecated Coinbase paymaster path — replaced by `requestSponsorAuth`
1631
- * in beta.8. Will be removed in 1.0. Migrate by:
1632
- * 1. Switch to `requestSponsorAuth` returning `{ sponsorAuth, payload }`
1633
- * 2. Pass both to Privy `signUserOperation` instead of merging
1634
- * paymasterData into the UserOp callData
1635
- */
1636
- requestSponsorship(req: SponsorshipRequest): Promise<SponsorshipResponse>;
1637
- private postWithRetry;
1638
- /**
1639
- * Pick the delay before the next retry.
1640
- * - If the server sent `retryAfter` (seconds), honor it (capped by
1641
- * `maxRetryAfterMs`) — returns null if the server wait exceeds the
1642
- * cap, signalling the caller should give up.
1643
- * - Otherwise: exponential backoff with ±20% jitter, capped at
1644
- * `maxDelayMs`.
1645
- */
1646
- private computeBackoff;
1647
- private sleep;
1648
- private post;
1649
- /** JSON replacer that stringifies bigints. Paired with bigintReviver. */
1650
- private bigintReplacer;
1651
- /**
1652
- * JSON reviver that coerces specific numeric-string fields back to
1653
- * bigint. The server must send these fields as decimal strings.
1654
- */
1655
- private bigintReviver;
1457
+ requestSponsorship(request: SponsorshipRequest): Promise<SponsorshipResponse>;
1458
+ private _doRequest;
1656
1459
  }
1657
1460
 
1658
1461
  /**
@@ -1691,8 +1494,20 @@ interface IssuerServiceConfig {
1691
1494
  /** Passed straight to `jose` (`"24h"`, `"7d"`, …). Default `"24h"`. */
1692
1495
  jwtExpiresIn?: string;
1693
1496
  };
1694
- ledger?: IPointLedger;
1497
+ /**
1498
+ * Off-chain point ledger — the source of truth for user balances and
1499
+ * in-flight minting locks. Every issuer provides their own database-backed
1500
+ * implementation (Postgres, Redis, etc.) that implements `IPointLedger`.
1501
+ * The SDK does not ship a production ledger; each issuer's data model and
1502
+ * infrastructure are different.
1503
+ */
1504
+ ledger: IPointLedger;
1505
+ /**
1506
+ * Policy engine — optional, defaults to `DefaultPolicyEngine` which checks
1507
+ * off-chain balance. Extend or replace to add KYC, volume caps, etc.
1508
+ */
1695
1509
  policy?: IPolicyEngine;
1510
+ /** Session store — optional, defaults to `MemorySessionStore` (dev/test only). */
1696
1511
  sessionStore?: ISessionStore;
1697
1512
  /**
1698
1513
  * Fee management config. If omitted the `handleGasFee` endpoint will
@@ -1704,6 +1519,16 @@ interface IssuerServiceConfig {
1704
1519
  * throws "not configured" at request time.
1705
1520
  */
1706
1521
  poolsProvider?: PoolsProvider;
1522
+ /**
1523
+ * Enables `handleClaim`. The factory combines these with the shared
1524
+ * `policy` + `relayService` instances already wired by the factory.
1525
+ * Omit to disable the `/claim` endpoint.
1526
+ */
1527
+ claim?: {
1528
+ issuerSignerWallet: WalletClient;
1529
+ batchExecutorAddress: Address;
1530
+ lockDurationMs?: number;
1531
+ };
1707
1532
  indexer?: {
1708
1533
  fromBlock?: bigint;
1709
1534
  cursorStore?: IIndexerCursorStore;
@@ -1737,8 +1562,7 @@ interface IssuerService {
1737
1562
  * Wire a fully-functional issuer service from a single config object.
1738
1563
  *
1739
1564
  * Defaults:
1740
- * - `ledger` → `MemoryPointLedger`
1741
- * - `sessionStore` → `MemorySessionStore`
1565
+ * - `sessionStore` → `MemorySessionStore` (dev/test only — replace in prod)
1742
1566
  * - `policy` → `DefaultPolicyEngine({ ledger })`
1743
1567
  * - `feeManager` → undefined (handleGasFee throws until configured)
1744
1568
  * - `poolsProvider` → undefined (handlePools throws until configured)
@@ -1751,4 +1575,4 @@ declare function createIssuerService(config: IssuerServiceConfig): IssuerService
1751
1575
  /** SDK package version — bumped on every release */
1752
1576
  declare const PAFI_ISSUER_SDK_VERSION = "0.1.0";
1753
1577
 
1754
- 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 };
1578
+ export { type ApiBuildConsentTypedDataRequest, type ApiBuildConsentTypedDataResponse, type ApiClaimAndSwapRequest, type ApiClaimAndSwapResponse, type ApiClaimRequest, type ApiClaimResponse, 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 IPointLedger, type IPolicyEngine, type ISessionStore, InMemoryCursorStore, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerService, type IssuerServiceConfig, type LockedMintRequest, type LoginResult, 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, RelayError, type RelayErrorCode, RelayService, type RetryConfig, type Session, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, TopUpRedemptionError, TopUpRedemptionHandler, type TopUpRedemptionHandlerConfig, type TopUpRedemptionRequest, type TopUpRedemptionResponse, authenticateRequest, createIssuerService, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider };