@pafi-dev/issuer 0.12.6 → 0.13.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/README.md CHANGED
@@ -4,26 +4,51 @@
4
4
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
5
 
6
6
  Backend SDK for PAFI issuer servers — claim, redeem, perp deposit,
7
- mobile prepare/submit, EIP-7702 delegation, status polling, and the
8
- `IssuerApiAdapter` that thins issuer controllers to one line per
9
- endpoint.
7
+ mobile prepare/submit, EIP-7702 delegation, status polling, redemption
8
+ restriction enforcement, and the `IssuerApiAdapter` that thins issuer
9
+ controllers to one line per endpoint.
10
10
 
11
11
  **Server-only.** Pulls in signer wallets, HTTP clients, ledger
12
12
  interfaces. Don't bundle into a browser app — use
13
- `@pafi-dev/trading` (FE swap/quote) or `@pafi-dev/core` (primitives)
14
- instead.
13
+ `@pafi-dev/trading` (FE swap/quote) or `@pafi-dev/core` (primitives).
15
14
 
16
15
  ---
17
16
 
18
- ## Requirements
17
+ ## What's new in 0.13.0 (breaking)
19
18
 
20
- - Node.js >= 18
21
- - TypeScript >= 5.0
22
- - `viem` ^2.0.0 (peer)
23
- - `@pafi-dev/core` (peer re-exported types)
19
+ Tracks `@pafi-dev/core@0.10.0`. The `ReceiverConsent` mint path was
20
+ removed from the entire SDK chain because the deployed `PointToken`
21
+ contract never had it — what was conceptually a "sponsored mint" is
22
+ actually the path-2 `MintForRequest` sig-gated mint with sponsor-relayer
23
+ paying gas.
24
+
25
+ | Change | Detail |
26
+ |---|---|
27
+ | `ApiUserResponse.receiverConsentNonce` removed | The `/user` endpoint no longer returns this field. Callers using it for signing should call the path-2 mint flow with `mintRequestNonce` instead. |
28
+ | `UserDto.receiverConsentNonce` removed | Wire DTO drops the field — `IssuerApiAdapter.user()` response is one field smaller. |
29
+ | Action required for issuer backends | Bump `@pafi-dev/core` to `0.10.0`, remove any `receiverConsentNonce` field from your own DTOs / mobile API surface, drop FE code that signed `ReceiverConsent` typed data. |
30
+
31
+ ### What's new in v1.6 / 0.12.x
32
+
33
+ | Change | Detail |
34
+ |---|---|
35
+ | `RelayService.prepareMint` branches direct vs wrapper | Auto-detects `MintFeeWrapper` from chainId. Direct: `pointToken.mint(...)`. Wrapper: `mintFeeWrapper.mintWithFee(...)` |
36
+ | `PTClaimHandler` auto-resolves wrapper | No env config needed — `getContractAddresses(chainId).mintFeeWrapper`. Override via `mintFeeWrapperAddress` for fork tests |
37
+ | `PointIndexer` wrapper mode | Listens to `MintFeeWrapper.MintWithFee(pointToken, to, gross, net, fee)` instead of `Transfer(0x0→user)` when wrapper is configured |
38
+ | `IssuerStateValidator` v1.6 struct | Reads 7-field Issuer struct + queries `MintingOracle.tokenCaps()` for caps |
39
+ | `/config` exposes `mintFeeBpsByToken` | FE can preview "you'll receive net = gross × (1-bps/10000)" before user signs |
40
+ | Operator fee fallback enabled by default | Mint not blocked when subgraph has no pool yet |
41
+ | `PafiBackendClient` error envelope fix | Reads nested `error.message` — exposes Pimlico AA codes instead of generic "HTTP 500" |
24
42
 
25
43
  ---
26
44
 
45
+ ## Requirements
46
+
47
+ - Node.js ≥ 18
48
+ - TypeScript ≥ 5.0
49
+ - `viem` ^2.0.0 (peer)
50
+ - `@pafi-dev/core` (transitive — re-exported)
51
+
27
52
  ## Installation
28
53
 
29
54
  ```bash
@@ -38,11 +63,9 @@ pnpm add @pafi-dev/issuer-postgres typeorm pg
38
63
 
39
64
  ```
40
65
  @pafi-dev/issuer
41
- ├── handlers — flow handlers (PTClaimHandler, PTRedeemHandler,
42
- │ PerpDepositHandler)
66
+ ├── handlers — PTClaimHandler, PTRedeemHandler, PerpDepositHandler
43
67
  ├── api/IssuerApiAdapter
44
- │ — single class, every endpoint is one line in
45
- │ your controller
68
+ │ — single class, every endpoint is one line in controller
46
69
  ├── api/handleMobilePrepare/Submit
47
70
  │ — mobile claim/redeem orchestrators
48
71
  ├── api/handleClaimStatus/handleRedeemStatus
@@ -55,20 +78,16 @@ pnpm add @pafi-dev/issuer-postgres typeorm pg
55
78
  ├── userop-store — IPendingUserOpStore + MemoryPendingUserOpStore
56
79
  ├── pafi-backend — sponsor-relayer client + relay/paymaster helpers
57
80
  ├── auth — ISessionStore, AuthService (SIWE), MemorySessionStore
58
- ├── relay — RelayService, FeeManager
81
+ ├── relay — RelayService (v1.6 wrapper-aware), FeeManager
59
82
  ├── pools — createSubgraphPoolsProvider, NativePtQuoter
60
83
  ├── policy — IPolicyEngine + DefaultPolicyEngine
61
84
  ├── balance — BalanceAggregator (off-chain + on-chain)
62
- ├── indexer — PointIndexer, BurnIndexer (singleton workers)
63
- ├── issuer-state — IssuerStateValidator (registry + cap pre-check)
85
+ ├── indexer — PointIndexer (wrapper + direct mode), BurnIndexer
86
+ ├── issuer-state — IssuerStateValidator (v1.6 struct + oracle.tokenCaps)
87
+ ├── redemption — RedemptionService (per-issuer policy enforcement)
64
88
  └── errors — PafiSdkError base class for typed errors
65
89
  ```
66
90
 
67
- > **Removed in 0.6.0** (2026-04-27): `SwapHandler`, `quotePointTokenToUsdt`,
68
- > and `IssuerApiAdapter.swap()/quote()` — moved to `@pafi-dev/trading`.
69
- > FE PAFI calls trading directly. `IssuerApiHandlers.handleClaim/handleRedeem`
70
- > legacy methods + `TopUpRedemptionHandler` (Variant B) also dropped.
71
-
72
91
  ---
73
92
 
74
93
  ## Architecture
@@ -80,23 +99,63 @@ Auth guard ← issuer-specific (SIWE / Privy / NextAuth)
80
99
 
81
100
  Controller ← thin: routing + DTO + auth context
82
101
 
83
- IssuerApiAdapter ← @pafi-dev/issuer (orchestrates flows)
102
+ IssuerApiAdapter ← orchestrates flows
84
103
 
85
- PTClaimHandler ← signs MintRequest, locks balance, builds UserOp
104
+ PTClaimHandler ← signs MintForRequest, locks balance, builds UserOp
105
+ (v1.6: routes to wrapper.mintWithFee)
86
106
  PTRedeemHandler ← signs BurnRequest, reserves credit, builds UserOp
87
107
  PerpDepositHandler ← Orderly Vault via PAFI Relay
88
108
 
89
- PostgresPointLedger @pafi-dev/issuer-postgres (TypeORM)
90
- RelayService ← UserOp builders for mint/burn
109
+ IPointLedger your DB impl (Postgres recommended)
110
+ RelayService ← UserOp builders (auto-branches direct vs wrapper)
91
111
  PafiBackendClient ← sponsor-relayer proxy
92
112
  ```
93
113
 
94
114
  ---
95
115
 
116
+ ## v1.6 wrapper-mediated mint flow
117
+
118
+ ```
119
+ [FE / mobile] → POST /claim/prepare
120
+
121
+ [gg56] IssuerApiAdapter.claimPrepare()
122
+
123
+ [gg56] PTClaimHandler:
124
+ 1. Lock off-chain balance (gross amount)
125
+ 2. Pre-validate via IssuerStateValidator (oracle cap check)
126
+ 3. Sign MintForRequest EIP-712 with receiver = wrapper
127
+ 4. Build UserOp callData:
128
+ BatchExecute([
129
+ { mintFeeWrapper, mintWithFee(pt, user, gross, deadline, sig) },
130
+ { pointToken, transfer(pafiFeeRecipient, operatorFeePT) }
131
+ ])
132
+
133
+ [gg56] PafiBackendClient.requestSponsorship() → sponsor-relayer
134
+
135
+ [sponsor-relayer] decode calldata, recognize MINT_WITH_FEE (0x5284d08b),
136
+ validate intent, forward to Pimlico paymaster
137
+
138
+ [gg56] returns { userOp, typedData, userOpHash, sponsored: true|false,
139
+ typedDataFallback, userOpHashFallback }
140
+
141
+ [FE / mobile] sign typedData via signTypedData_v4
142
+
143
+ [FE / mobile] POST /claim/submit { lockId, signature, variant }
144
+
145
+ [gg56] forwards signed UserOp to Pimlico bundler → on-chain mint
146
+
147
+ [chain] wrapper.mintWithFee → PointToken.mint(gross to wrapper) →
148
+ wrapper splits fee to recipients → transfers net to user
149
+
150
+ [gg56] PointIndexer (wrapper mode) catches MintWithFee event →
151
+ deduct off-chain balance, lock → MINTED
152
+ ```
153
+
154
+ ---
155
+
96
156
  ## Quick start (NestJS)
97
157
 
98
- Minimal reference at `examples/nestjs-issuer/` — full working backend
99
- with all 11 endpoints, ~940 LoC total.
158
+ Minimal reference at `examples/nestjs-issuer/` — full working backend.
100
159
 
101
160
  ### 1. Wire `IssuerApiAdapter`
102
161
 
@@ -109,7 +168,6 @@ import {
109
168
  PTRedeemHandler,
110
169
  PerpDepositHandler,
111
170
  MemoryPendingUserOpStore,
112
- createSubgraphPoolsProvider,
113
171
  type IssuerService,
114
172
  } from "@pafi-dev/issuer";
115
173
  import { PostgresPointLedger } from "@pafi-dev/issuer-postgres";
@@ -117,41 +175,48 @@ import { getContractAddresses } from "@pafi-dev/core";
117
175
 
118
176
  export const issuerApiAdapterProvider: Provider = {
119
177
  provide: ISSUER_API_ADAPTER,
120
- useFactory: (issuerService, provider, walletClient, dataSource) => {
178
+ useFactory: (issuerService, provider, walletClient, dataSource, config) => {
121
179
  const ledger = new PostgresPointLedger(dataSource);
122
180
  const { issuerRegistry, batchExecutor } = getContractAddresses(8453);
181
+ const chainId = config.get<number>("CHAIN_ID");
182
+ const pointToken = config.get<`0x${string}`>("POINT_TOKEN_ADDRESS");
123
183
 
124
184
  return new IssuerApiAdapter({
125
185
  issuerService,
126
186
  ledger,
127
187
  provider,
128
188
  issuerSignerWallet: walletClient,
129
- pafiIssuerId: process.env.PAFI_ISSUER_ID,
189
+ pafiIssuerId: config.get("PAFI_ISSUER_ID"),
130
190
 
131
191
  ptClaimHandler: new PTClaimHandler({
132
- ledger, relayService: issuerService.relay, provider,
192
+ ledger,
193
+ relayService: issuerService.relay,
194
+ provider,
133
195
  issuerSignerWallet: walletClient,
134
- pointTokenDomainName: "POINT",
196
+ pointTokenDomainName: config.get("POINT_TOKEN_DOMAIN_NAME"),
135
197
  feeService: issuerService.fee,
136
198
  issuerStateValidator: new IssuerStateValidator(provider, issuerRegistry),
199
+ // mintFeeWrapperAddress: optional override; SDK auto-resolves from chainId
137
200
  }),
138
201
 
139
202
  ptRedeemHandler: new PTRedeemHandler({
140
- ledger, relayService: issuerService.relay, provider,
141
- pointTokenAddress: POINT_TOKEN,
203
+ ledger,
204
+ relayService: issuerService.relay,
205
+ provider,
206
+ pointTokenAddress: pointToken,
142
207
  batchExecutorAddress: batchExecutor,
143
- chainId: 8453,
144
- domain: { name: "POINT", verifyingContract: POINT_TOKEN },
208
+ chainId,
209
+ domain: { name: config.get("POINT_TOKEN_DOMAIN_NAME"), verifyingContract: pointToken },
145
210
  burnerSignerWallet: walletClient,
146
211
  feeService: issuerService.fee,
147
212
  }),
148
213
 
149
214
  perpHandler: new PerpDepositHandler({
150
- provider, feeService: issuerService.fee, pointTokenAddress: POINT_TOKEN,
215
+ provider, feeService: issuerService.fee, pointTokenAddress: pointToken,
151
216
  }),
152
217
 
153
218
  pendingUserOpStore: new MemoryPendingUserOpStore(),
154
- pafiBackendClient, // optional — required for mobile submit / sponsor-relayer
219
+ pafiBackendClient, // optional — sponsor-relayer client for sponsored flows
155
220
  });
156
221
  },
157
222
  inject: [...],
@@ -183,7 +248,6 @@ export class IssuerController {
183
248
  aaNonce, mintRequestNonce,
184
249
  }));
185
250
  }
186
- // ... other endpoints similarly thin
187
251
  }
188
252
  ```
189
253
 
@@ -191,16 +255,12 @@ export class IssuerController {
191
255
 
192
256
  ```ts
193
257
  import { createSdkErrorMapper, type SdkErrorBody } from "@pafi-dev/issuer";
194
- import {
195
- NotFoundException, ForbiddenException,
196
- UnprocessableEntityException, ServiceUnavailableException,
197
- } from "@nestjs/common";
198
-
199
- const sdkErrorMapper: (err: unknown) => never = createSdkErrorMapper({
200
- notFound: (b: SdkErrorBody) => new NotFoundException(b),
201
- forbidden: (b: SdkErrorBody) => new ForbiddenException(b),
202
- unprocessable: (b: SdkErrorBody) => new UnprocessableEntityException(b),
203
- serviceUnavailable: (b: SdkErrorBody) => new ServiceUnavailableException(b),
258
+
259
+ const sdkErrorMapper = createSdkErrorMapper({
260
+ notFound: (b) => new NotFoundException(b),
261
+ forbidden: (b) => new ForbiddenException(b),
262
+ unprocessable: (b) => new UnprocessableEntityException(b),
263
+ serviceUnavailable: (b) => new ServiceUnavailableException(b),
204
264
  });
205
265
 
206
266
  async function wrap<T>(fn: () => Promise<T>): Promise<T> {
@@ -209,91 +269,148 @@ async function wrap<T>(fn: () => Promise<T>): Promise<T> {
209
269
  }
210
270
  ```
211
271
 
212
- Every typed SDK error (`PafiSdkError` subclass) routes through this
213
- funnel — `code`, `safeToRetry`, `details`, `httpStatus` come straight
214
- off the error class. No magic strings, no per-error mapping.
215
-
216
272
  ---
217
273
 
218
274
  ## `IssuerApiAdapter` methods
219
275
 
220
- | Method | HTTP route in your controller |
221
- | --- | --- |
222
- | `pools(authedAddr, chainId, pointToken)` | `GET /pools` |
223
- | `user(authedAddr, chainId, userAddr, pointToken)` | `GET /user` |
224
- | `claim({ ...nonces, ... })` | `POST /claim` (web sync `calls[]`) |
225
- | `redeem({ amount, aaNonce, ... })` | `POST /redeem` |
226
- | `perpDeposit({ amount, brokerId, aaNonce, ... })` | `POST /perp-deposit` |
227
- | `claimPrepare(...)` / `claimSubmit(...)` | Mobile claim flow |
228
- | `redeemPrepare(...)` / `redeemSubmit(...)` | Mobile redeem flow |
229
- | `claimStatus(authedAddr, lockId)` / `redeemStatus(...)` | Status polling |
230
- | `delegateStatus(authedAddr, chainId)` | EIP-7702 check delegation installed |
231
- | `delegatePrepare(authedAddr, chainId)` | EIP-7702 — build authorization hash |
232
- | `delegateSubmit({ authSig, delegationNonce, aaNonce, ... })` | EIP-7702 — relay empty-batch UserOp |
233
-
234
- > `swap()` and `quote()` removed in 0.6.0 — see `@pafi-dev/trading`.
235
- > `config()` and `gasFee()` are optional read endpoints; FE can call
236
- > `getContractAddresses()` and `quoteOperatorFeeUsdt()` from
237
- > `@pafi-dev/core` directly with no backend round-trip.
276
+ | Method | HTTP route | Notes |
277
+ | --- | --- | --- |
278
+ | `pools(authedAddr, chainId, pointToken)` | `GET /pools` | |
279
+ | `user(authedAddr, chainId, userAddr, pointToken)` | `GET /user` | |
280
+ | `config(chainId)` | `GET /config` | v1.6: includes `mintFeeBpsByToken` + `contracts.mintFeeWrapper` |
281
+ | `claim({ ...nonces })` | `POST /claim` (web — sync `calls[]`) | |
282
+ | `redeem({ amount, aaNonce, ... })` | `POST /redeem` | |
283
+ | `perpDeposit({ amount, brokerId, aaNonce, ... })` | `POST /perp-deposit` | |
284
+ | `claimPrepare(...)` / `claimSubmit(...)` | Mobile claim flow | v1.6 wrapper-aware |
285
+ | `redeemPrepare(...)` / `redeemSubmit(...)` | Mobile redeem flow | |
286
+ | `claimStatus(authedAddr, lockId)` / `redeemStatus(...)` | Status polling | |
287
+ | `delegateStatus(authedAddr, chainId)` | EIP-7702 — check delegation | |
288
+ | `delegatePrepare(authedAddr, chainId)` | EIP-7702 — build auth hash | |
289
+ | `delegateSubmit({ authSig, ... })` | EIP-7702 — relay empty-batch | |
238
290
 
239
291
  ---
240
292
 
241
- ## Mobile prepare/submit flow
293
+ ## Redemption restriction (v0.10+)
242
294
 
243
- Mobile clients (Privy expo) can't `useSign7702Authorization` they
244
- sign just the `userOpHash` via `personal_sign`. The adapter handles
245
- the orchestration:
295
+ Per-issuer policy enforced at `/redeem/prepare` time. Fetched from
296
+ PAFI issuer-api with 5-min cache + fail-open default.
246
297
 
247
298
  ```ts
248
- // 1. mobile sends POST /claim/prepare → backend runs:
249
- const prepared = await api.claimPrepare({ authenticatedAddress, chainId, pointTokenAddress, amount, aaNonce, mintRequestNonce });
250
- // returns: { lockId, userOpHash, typedData, userOpHashFallback?, typedDataFallback?, sponsored, needsDelegation, ... }
299
+ import { RedemptionService, PolicyProvider } from "@pafi-dev/issuer";
300
+ import { PostgresRedemptionHistoryStore } from "@pafi-dev/issuer-postgres";
301
+
302
+ const redemption = new RedemptionService({
303
+ policyProvider: new PolicyProvider({
304
+ chainId: 8453,
305
+ issuerId: "gg56",
306
+ apiKey: process.env.PAFI_API_KEY,
307
+ }),
308
+ historyStore: new PostgresRedemptionHistoryStore(dataSource),
309
+ });
310
+ ```
311
+
312
+ Wire to `createIssuerService({ redemption: {...} })`. Adapter exposes
313
+ `/redemption/preview` + `/redemption/evaluate` automatically.
251
314
 
252
- // 2. mobile signs prepared.userOpHash via Privy personal_sign
315
+ ---
316
+
317
+ ## Mobile prepare/submit flow
253
318
 
254
- // 3. mobile sends POST /claim/submit → backend runs:
255
- const result = await api.claimSubmit({ authenticatedAddress, lockId, signature, variant: "sponsored" | "fallback" });
319
+ ```ts
320
+ // 1. mobile POST /claim/prepare backend runs:
321
+ const prepared = await api.claimPrepare({
322
+ authenticatedAddress, chainId, pointTokenAddress, amount, aaNonce, mintRequestNonce,
323
+ });
324
+ // returns: {
325
+ // lockId, userOpHash, typedData,
326
+ // userOpHashFallback, typedDataFallback,
327
+ // sponsored, // true if paymaster signed; false → use fallback
328
+ // needsDelegation, // true if user EOA not delegated yet
329
+ // feeAmount, signatureDeadline, expiresInSeconds,
330
+ // }
331
+
332
+ // 2. mobile signs typedData via Privy/MetaMask signTypedData_v4
333
+ // (NOT personal_sign — Pimlico Simple7702Account does raw ecrecover)
334
+ // If `sponsored: false`, sign `typedDataFallback` instead
335
+
336
+ // 3. mobile → POST /claim/submit:
337
+ const result = await api.claimSubmit({
338
+ authenticatedAddress, lockId, signature,
339
+ variant: prepared.sponsored ? "sponsored" : "fallback",
340
+ });
256
341
  // returns: { userOpHash } — bundler hash for status polling
257
342
  ```
258
343
 
259
344
  `handleMobileSubmit` enforces ownership: `entry.sender ===
260
- authenticatedAddress`. Without this, user A could submit user B's
261
- pending UserOp once they leak/guess the lockId.
345
+ authenticatedAddress`. Lock has 15-min TTL.
262
346
 
263
347
  ---
264
348
 
265
- ## Status polling
349
+ ## PointIndexer modes
266
350
 
267
351
  ```ts
268
- // GET /claim/status/:lockId
269
- const status = await api.claimStatus(user.userAddress, lockId);
270
- // { lockId, status: 'PENDING' | 'MINTED' | 'EXPIRED' | 'FAILED', txHash, ... }
352
+ // Default — auto-resolves wrapper from chainId
353
+ const service = createIssuerService({
354
+ chainId: 8453,
355
+ // ...
356
+ indexer: { autoStart: true, pollIntervalMs: 5000 },
357
+ });
358
+
359
+ // Indexer listens to:
360
+ // - mode wrapper: MintFeeWrapper.MintWithFee filtered by pointToken
361
+ // (when wrapper is configured at chainId — v1.6 default)
362
+ // - mode direct: PointToken.Transfer(0x0 → user) (legacy / no wrapper)
271
363
  ```
272
364
 
273
- Falls back to bundler receipt when `status === 'PENDING'` and
274
- `userOpHash` is bound — bypasses `PointIndexer`'s amount-match race
275
- (multiple PENDING locks with the same amount can be resolved to the
276
- wrong tx_hash).
365
+ Override for fork tests:
366
+ ```ts
367
+ indexer: { mintFeeWrapperAddress: "0x000...dead" } // force direct mode
368
+ ```
277
369
 
278
370
  ---
279
371
 
280
372
  ## Error classes
281
373
 
282
- All SDK errors inherit `PafiSdkError`. Subclasses + their HTTP status:
374
+ All SDK errors inherit `PafiSdkError`. Subclasses + HTTP mapping:
283
375
 
284
376
  | Error | `httpStatus` | `safeToRetry` |
285
377
  | --- | --- | --- |
286
378
  | `PendingUserOpNotFoundError` | `not_found` | false |
287
379
  | `PendingUserOpForbiddenError` | `forbidden` | false |
288
380
  | `LockNotFoundError` | `not_found` | false |
289
- | `IssuerStateError` | `unprocessable` | true if `MINT_CAP_EXCEEDED` |
381
+ | `IssuerStateError` (`ISSUER_NOT_REGISTERED` / `ISSUER_INACTIVE` / `MINT_CAP_EXCEEDED`) | `unprocessable` | true if cap exceeded |
290
382
  | `PerpDepositError` | `unprocessable` | true if `RELAY_FEE_EXCEEDS_AMOUNT` |
291
383
  | `PTClaimError`, `PTRedeemError` | `unprocessable` | false |
292
384
  | `BundlerNotConfiguredError` | `service_unavailable` | false |
293
385
  | `BundlerRejectedError` | `unprocessable` | false |
386
+ | `RedemptionPolicyError` (`REDEMPTION_DENIED` / `RATE_LIMIT_EXCEEDED` / ...) | `unprocessable` | varies |
387
+
388
+ ---
389
+
390
+ ## v1.5 → v1.6 migration checklist
391
+
392
+ 1. `pnpm add @pafi-dev/issuer@latest @pafi-dev/core@latest` (≥ 0.12.7 + ≥ 0.9.6)
393
+ 2. Update env: drop `MINT_FEE_WRAPPER_ADDRESS` if previously set
394
+ (SDK auto-resolves from chainId)
395
+ 3. Re-deploy your issuer backend — `RelayService` now branches based on
396
+ wrapper presence; calldata changes from `mint(...)` to `mintWithFee(...)`
397
+ 4. Verify on-chain prerequisites (PAFI ops responsibility):
398
+ - `IssuerRegistry.addIssuer(...)` cascade configures wrapper recipients
399
+ - `MintingOracle.registerToken(pointToken, issuer)` sets cap
400
+ - `PointToken.addMinter(signerAddress)` whitelists your signer
401
+ 5. Indexer will pick up `MintWithFee` events automatically. If you wrote
402
+ custom Transfer-event consumers, switch to `MintWithFee`.
403
+ 6. `/config` response now includes `mintFeeBpsByToken` — FE can preview
404
+ fee without round-tripping `getMintFeeBps()` separately.
294
405
 
295
406
  ---
296
407
 
408
+ ## References
409
+
410
+ - Architecture: [`ARCHITECTURE.md`](../../ARCHITECTURE.md) at SDK root
411
+ - Fee flow & math: [`docs/FEE_FLOW.md`](../../../docs/FEE_FLOW.md)
412
+ - v1.6 SC commits: `cc26f62` (struct simplification), wrapper added
413
+
297
414
  ## License
298
415
 
299
416
  Apache-2.0
package/dist/index.cjs CHANGED
@@ -1846,16 +1846,14 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
1846
1846
  { requested: pointToken }
1847
1847
  );
1848
1848
  }
1849
- const [mintRequestNonce, receiverConsentNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
1849
+ const [mintRequestNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
1850
1850
  (0, import_core6.getMintRequestNonce)(this.provider, pointToken, normalizedAuthed),
1851
- (0, import_core6.getReceiverConsentNonce)(this.provider, pointToken, normalizedAuthed),
1852
1851
  this.ledger.getBalance(normalizedAuthed, pointToken),
1853
1852
  (0, import_core6.getPointTokenBalance)(this.provider, pointToken, normalizedAuthed),
1854
1853
  (0, import_core6.isMinter)(this.provider, pointToken, normalizedAuthed)
1855
1854
  ]);
1856
1855
  return {
1857
1856
  mintRequestNonce,
1858
- receiverConsentNonce,
1859
1857
  offChainBalance,
1860
1858
  onChainBalance,
1861
1859
  totalBalance: offChainBalance + onChainBalance,
@@ -3151,7 +3149,6 @@ var IssuerApiAdapter = class {
3151
3149
  );
3152
3150
  return {
3153
3151
  mintRequestNonce: result.mintRequestNonce.toString(),
3154
- receiverConsentNonce: result.receiverConsentNonce.toString(),
3155
3152
  offChainBalance: result.offChainBalance.toString(),
3156
3153
  onChainBalance: result.onChainBalance.toString(),
3157
3154
  totalBalance: result.totalBalance.toString(),
@@ -4739,7 +4736,7 @@ var MemoryRedemptionHistoryStore = class {
4739
4736
  };
4740
4737
 
4741
4738
  // src/index.ts
4742
- var PAFI_ISSUER_SDK_VERSION = true ? "0.12.5" : "dev";
4739
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.13.0" : "dev";
4743
4740
  // Annotate the CommonJS export names for ESM import in node:
4744
4741
  0 && (module.exports = {
4745
4742
  AdapterMisconfiguredError,