@pafi-dev/issuer 0.3.0-beta.9 → 0.5.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 +254 -283
- package/dist/index.cjs +433 -316
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -128
- package/dist/index.d.ts +147 -128
- package/dist/index.js +408 -292
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,352 +1,323 @@
|
|
|
1
1
|
# @pafi-dev/issuer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
of the mint-and-cash-out flow: authentication, point ledger, policy
|
|
6
|
-
engine, EIP-712 issuer signing, relay submission, mint event indexing,
|
|
7
|
-
and framework-agnostic HTTP handlers.
|
|
8
|
-
|
|
9
|
-
## What's new in 0.3.0-alpha.0 — sponsored UserOp flow (v1.4 preview)
|
|
10
|
-
|
|
11
|
-
Preview release that wires the SDK into the new **sponsored path**:
|
|
12
|
-
user EOAs delegate to a BatchExecutor via EIP-7702, UserOps are
|
|
13
|
-
signed by the user and submitted to a Bundler, and gas is paid by
|
|
14
|
-
Coinbase Paymaster — proxied through the new PAFI-hosted
|
|
15
|
-
`paymaster-proxy` service.
|
|
16
|
-
|
|
17
|
-
**What ships in this alpha:**
|
|
18
|
-
|
|
19
|
-
- `PafiBackendClient` — HTTP client for `paymaster-proxy` with retry +
|
|
20
|
-
backoff on `safeToRetry` errors (opt-in via `retry.maxAttempts`)
|
|
21
|
-
- `BalanceAggregator` — merge off-chain + on-chain balance into one number
|
|
22
|
-
- `BurnIndexer` — watches `Transfer(user → 0x0)` events, finalizes
|
|
23
|
-
pending off-chain credits for the reverse flow
|
|
24
|
-
- `IPointLedger.reservePendingCredit()` / `resolveCreditByBurnTx()` —
|
|
25
|
-
additive ledger methods for burn→credit flow
|
|
26
|
-
- `ApiConfigResponse.pafiWebUrl` + `contracts.pointTokens[]` — new
|
|
27
|
-
fields surfaced by `handleConfig()`
|
|
28
|
-
- `FeeManager` slimmed: no more `operatorWallet` /
|
|
29
|
-
`usdtAddress` / `nativeWrappedAddress`; rebalancing gone (operator
|
|
30
|
-
doesn't hold ETH anymore); `quoteNativeToFee` replaces
|
|
31
|
-
`quoteNativeToUsdt` so fee currency is caller-chosen (PT for
|
|
32
|
-
mint/burn, USDT for swap/perp_deposit)
|
|
33
|
-
|
|
34
|
-
**What's still blocked (comes in 0.3.0-beta / stable):**
|
|
35
|
-
|
|
36
|
-
- `processMint()` / `processBurn()` — blocked on SC Relayer v2 ABI
|
|
37
|
-
(blocker B1). Current `processMintAndCashOut()` is `@deprecated`
|
|
38
|
-
but still works
|
|
39
|
-
- `PTRedeemHandler` + `TopUpRedemptionHandler` — blocked on B1 +
|
|
40
|
-
`paymaster-proxy` staging env being live
|
|
41
|
-
- Deletion of legacy `claimAndSwap` flow — kept for v0.2.x consumers
|
|
42
|
-
until 2.0
|
|
43
|
-
|
|
44
|
-
**Install the alpha:**
|
|
3
|
+
[](https://www.npmjs.com/package/@pafi-dev/issuer)
|
|
4
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
45
5
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
6
|
+
Issuer backend SDK for the PAFI point token system. Wraps `@pafi-dev/core` with everything
|
|
7
|
+
a partner needs to run the off-chain side of the cashout flow: SIWE authentication, point
|
|
8
|
+
ledger, policy engine, EIP-712 issuer signing, relay submission, and mint/burn event indexing.
|
|
49
9
|
|
|
50
|
-
|
|
10
|
+
> **Server-only.** This package pulls in `jose` and `node:crypto`. Do not bundle into a browser app.
|
|
51
11
|
|
|
52
|
-
|
|
12
|
+
---
|
|
53
13
|
|
|
54
|
-
|
|
55
|
-
service instance. Pass `pointTokenAddresses: Address[]` in
|
|
56
|
-
`IssuerServiceConfig`; the factory creates one `PointIndexer` per token
|
|
57
|
-
and handlers validate the requested `pointTokenAddress` against the
|
|
58
|
-
configured set.
|
|
14
|
+
## Requirements
|
|
59
15
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
16
|
+
- Node.js >= 18
|
|
17
|
+
- TypeScript >= 5.0
|
|
18
|
+
- `viem` ^2.0.0 and `@pafi-dev/core` ^0.4.0 (peer dependencies)
|
|
63
19
|
|
|
64
|
-
|
|
65
|
-
one-element list. No code change is required if you only have one token.
|
|
20
|
+
---
|
|
66
21
|
|
|
67
|
-
##
|
|
22
|
+
## Installation
|
|
68
23
|
|
|
69
24
|
```bash
|
|
25
|
+
npm install @pafi-dev/issuer @pafi-dev/core viem
|
|
26
|
+
# or
|
|
70
27
|
pnpm add @pafi-dev/issuer @pafi-dev/core viem
|
|
71
28
|
```
|
|
72
29
|
|
|
73
|
-
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Modules
|
|
33
|
+
|
|
34
|
+
| Module | What it provides |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `relay/` | `RelayService` — build unsigned UserOps for mint + burn |
|
|
37
|
+
| `auth/` | `AuthService` — SIWE verification + JWT issuance; `authenticateRequest` middleware |
|
|
38
|
+
| `ledger/` | `IPointLedger` interface — implement against your own database |
|
|
39
|
+
| `policy/` | `IPolicyEngine`, `DefaultPolicyEngine` — off-chain balance gate |
|
|
40
|
+
| `indexer/` | `PointIndexer` (mint events) + `BurnIndexer` (burn events) |
|
|
41
|
+
| `balance/` | `BalanceAggregator` — merge off-chain ledger + on-chain `balanceOf` |
|
|
42
|
+
| `api/` | `IssuerApiHandlers` — framework-agnostic HTTP handlers |
|
|
43
|
+
| `pafi-backend/` | `PafiBackendClient` — HTTP client for PAFI paymaster backend |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## What you must bring
|
|
74
48
|
|
|
75
|
-
|
|
49
|
+
The SDK deliberately does not ship production implementations for:
|
|
50
|
+
|
|
51
|
+
| What | Why you own it |
|
|
52
|
+
|---|---|
|
|
53
|
+
| **`IPointLedger`** | Your database, your schema, your row-level locking strategy |
|
|
54
|
+
| **`ISessionStore`** | Must be shared across pods — use Redis or Postgres |
|
|
55
|
+
| **Signing wallet** | Private key must never touch server memory — use KMS |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
76
60
|
|
|
77
61
|
```ts
|
|
78
|
-
import { createIssuerService
|
|
62
|
+
import { createIssuerService } from "@pafi-dev/issuer";
|
|
63
|
+
import { getContractAddresses } from "@pafi-dev/core";
|
|
79
64
|
import { createPublicClient, createWalletClient, http } from "viem";
|
|
80
|
-
import { privateKeyToAccount } from "viem/accounts";
|
|
81
65
|
import { base } from "viem/chains";
|
|
66
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
67
|
+
|
|
68
|
+
const addrs = getContractAddresses(8453);
|
|
82
69
|
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
account: privateKeyToAccount(
|
|
70
|
+
// Production: replace with a KMS-backed WalletClient (see "Production signing" below)
|
|
71
|
+
const issuerSignerWallet = createWalletClient({
|
|
72
|
+
account: privateKeyToAccount(process.env.MINTER_PRIVATE_KEY as `0x${string}`),
|
|
86
73
|
chain: base,
|
|
87
|
-
transport: http(RPC_URL),
|
|
74
|
+
transport: http(process.env.RPC_URL),
|
|
88
75
|
});
|
|
89
76
|
|
|
90
77
|
const service = createIssuerService({
|
|
91
78
|
chainId: 8453,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
pointTokenAddresses: [addrs.pointToken],
|
|
80
|
+
contracts: {
|
|
81
|
+
relay: "0x92327F5c9383796Dd46D43E0995cc938038A98c4",
|
|
82
|
+
issuerRegistry: addrs.issuerRegistry,
|
|
83
|
+
usdt: addrs.usdt,
|
|
84
|
+
batchExecutor: addrs.batchExecutor,
|
|
85
|
+
},
|
|
86
|
+
provider: createPublicClient({ chain: base, transport: http(process.env.RPC_URL) }),
|
|
97
87
|
auth: {
|
|
98
88
|
jwtSecret: process.env.JWT_SECRET!,
|
|
99
89
|
domain: "app.example.com",
|
|
100
90
|
},
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
ledger: new YourPostgresLedger(), // implements IPointLedger
|
|
92
|
+
sessionStore: new YourRedisSessionStore(), // implements ISessionStore
|
|
93
|
+
claim: {
|
|
94
|
+
issuerSignerWallet,
|
|
95
|
+
batchExecutorAddress: addrs.batchExecutor,
|
|
96
|
+
},
|
|
103
97
|
});
|
|
104
98
|
|
|
105
|
-
//
|
|
106
|
-
|
|
99
|
+
// Mount into your framework (Express, Fastify, NestJS, Hono...)
|
|
100
|
+
app.get("/auth/nonce", () => service.handlers.handleGetNonce());
|
|
101
|
+
app.post("/auth/login", (req) => service.handlers.handleLogin(req.body));
|
|
102
|
+
app.get("/user", (req) => service.handlers.handleUser(req.user, req.query));
|
|
103
|
+
app.post("/claim", (req) => service.handlers.handleClaim(req.user, req.body));
|
|
107
104
|
```
|
|
108
105
|
|
|
109
|
-
|
|
106
|
+
---
|
|
110
107
|
|
|
111
|
-
|
|
112
|
-
| ----------------------- | --------------------- | ------------------------------------- |
|
|
113
|
-
| `IPointLedger` | `MemoryPointLedger` | ✅ Postgres / Prisma |
|
|
114
|
-
| `ISessionStore` | `MemorySessionStore` | ✅ Redis |
|
|
115
|
-
| `IPolicyEngine` | `DefaultPolicyEngine` | ➕ extend for KYC / caps / claim budget |
|
|
116
|
-
| `IIssuerSigner` | `PrivateKeySigner` | 🚨 **MUST replace** — KMS / HSM / MPC |
|
|
117
|
-
| `AuthService` | ✅ built-in | — |
|
|
118
|
-
| `RelayService` | ✅ built-in | — |
|
|
119
|
-
| `FeeManager` | ✅ built-in (opt-in) | injection-based — no DEX lock-in |
|
|
120
|
-
| `MintingGateway` | ✅ built-in | — |
|
|
121
|
-
| `PointIndexer` | ✅ built-in (polling) | — |
|
|
122
|
-
| `IssuerApiHandlers` | ✅ built-in | — |
|
|
108
|
+
## Implementing IPointLedger
|
|
123
109
|
|
|
124
|
-
|
|
125
|
-
implementations of the three interfaces you must replace.
|
|
110
|
+
Every issuer provides their own database-backed implementation:
|
|
126
111
|
|
|
127
|
-
|
|
112
|
+
```ts
|
|
113
|
+
import type { IPointLedger, LockedMintRequest, MintingStatus } from "@pafi-dev/issuer";
|
|
114
|
+
import type { Address, Hex } from "viem";
|
|
115
|
+
|
|
116
|
+
export class PostgresPointLedger implements IPointLedger {
|
|
117
|
+
async getBalance(user: Address, tokenAddress?: Address): Promise<bigint> { ... }
|
|
118
|
+
|
|
119
|
+
// Must use a row-level lock (SELECT ... FOR UPDATE) to prevent
|
|
120
|
+
// concurrent pods from reading the same available balance and double-spending.
|
|
121
|
+
async lockForMinting(user: Address, amount: bigint, durationMs: number, tokenAddress?: Address): Promise<string> { ... }
|
|
122
|
+
|
|
123
|
+
async releaseLock(lockId: string): Promise<void> { ... }
|
|
124
|
+
async deductBalance(user: Address, amount: bigint, txHash: Hex, tokenAddress?: Address): Promise<void> { ... }
|
|
125
|
+
async creditBalance(user: Address, amount: bigint, reason: string, tokenAddress?: Address): Promise<void> { ... }
|
|
126
|
+
async getLockedRequests(user: Address, tokenAddress?: Address): Promise<LockedMintRequest[]> { ... }
|
|
127
|
+
async updateMintStatus(lockId: string, status: MintingStatus, txHash?: Hex): Promise<void> { ... }
|
|
128
|
+
|
|
129
|
+
// Burn flow: reserve → resolve when burn tx confirmed
|
|
130
|
+
async reservePendingCredit(user: Address, amount: bigint, durationMs: number, tokenAddress?: Address): Promise<string> { ... }
|
|
131
|
+
async resolveCreditByBurnTx(lockId: string, txHash: Hex): Promise<void> { ... }
|
|
132
|
+
}
|
|
133
|
+
```
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
which runs the full 11-step flow documented in
|
|
131
|
-
[`docs/PAFI_ISSUER_SDK_SPEC.md`](../../../pafi-backend/docs/PAFI_ISSUER_SDK_SPEC.md):
|
|
135
|
+
---
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
1. Validate request fields (cheap rejects)
|
|
135
|
-
2. Verify ReceiverConsent signature via @pafi-dev/core
|
|
136
|
-
3. Check off-chain balance via ledger
|
|
137
|
-
4. Check locked requests via ledger
|
|
138
|
-
5. Run policy engine (balance + cap + issuer rules)
|
|
139
|
-
6. Lock the requested amount in the ledger
|
|
140
|
-
7. Sign MintRequest as the issuer (via IIssuerSigner)
|
|
141
|
-
8. Build Relay calldata (MintParams + SwapParams)
|
|
142
|
-
9. Submit via RelayService (simulate → write → receipt)
|
|
143
|
-
10. Return { txHash, lockId, block/gas }
|
|
144
|
-
11. PointIndexer finalizes the ledger on the Mint event (out of band)
|
|
145
|
-
```
|
|
137
|
+
## Production signing — KMS
|
|
146
138
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
139
|
+
In production the `issuerSignerWallet` must be backed by a hardware key.
|
|
140
|
+
The SDK accepts any viem `WalletClient` — wire it to AWS KMS via a custom account:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { KMSClient } from "@aws-sdk/client-kms";
|
|
144
|
+
import { createWalletClient, http } from "viem";
|
|
145
|
+
import { base } from "viem/chains";
|
|
146
|
+
|
|
147
|
+
// Custom viem account that delegates signTypedData to KMS.
|
|
148
|
+
// Full DER→(r,s) conversion + v-byte recovery omitted for brevity.
|
|
149
|
+
const kmsAccount = toAccount({
|
|
150
|
+
address: process.env.KMS_MINTER_ADDRESS as `0x${string}`,
|
|
151
|
+
async signTypedData(typedData) {
|
|
152
|
+
const digest = hashTypedData(typedData);
|
|
153
|
+
const { Signature } = await kms.send(new SignCommand({
|
|
154
|
+
KeyId: process.env.KMS_MINTER_KEY_ID!,
|
|
155
|
+
Message: digest,
|
|
156
|
+
MessageType: "DIGEST",
|
|
157
|
+
SigningAlgorithm: "ECDSA_SHA_256",
|
|
158
|
+
}));
|
|
159
|
+
return derToViem(Signature!); // convert DER → 65-byte viem hex signature
|
|
160
|
+
},
|
|
161
|
+
});
|
|
152
162
|
|
|
153
|
-
|
|
163
|
+
const issuerSignerWallet = createWalletClient({
|
|
164
|
+
account: kmsAccount,
|
|
165
|
+
chain: base,
|
|
166
|
+
transport: http(process.env.RPC_URL),
|
|
167
|
+
});
|
|
168
|
+
```
|
|
154
169
|
|
|
155
|
-
|
|
156
|
-
tells the API layer whether the underlying ledger lock was released:
|
|
170
|
+
---
|
|
157
171
|
|
|
158
|
-
|
|
159
|
-
| --------------------------- | -------------- | ------------- |
|
|
160
|
-
| `INVALID_REQUEST` | never locked | `true` |
|
|
161
|
-
| `INVALID_CONSENT_SIGNATURE` | never locked | `true` |
|
|
162
|
-
| `CONSENT_EXPIRED` | never locked | `true` |
|
|
163
|
-
| `POLICY_REJECTED` | never locked | `true` |
|
|
164
|
-
| `INSUFFICIENT_BALANCE` | never locked | `true` |
|
|
165
|
-
| `SIGNER_FAILED` | ✅ released | `true` |
|
|
166
|
-
| `RELAY_SIMULATION_FAILED` | ✅ released | `true` |
|
|
167
|
-
| `RELAY_SUBMIT_FAILED` | ✅ released | `true` |
|
|
168
|
-
| `RELAY_REVERTED` | 🔒 kept | `false` |
|
|
169
|
-
| `RELAY_TIMEOUT` | 🔒 kept | `false` |
|
|
172
|
+
## Event indexers
|
|
170
173
|
|
|
171
|
-
|
|
172
|
-
still be in the mempool or already mined — releasing would enable a
|
|
173
|
-
double-spend on retry. Your API should surface `409 Conflict` in these
|
|
174
|
-
cases and require manual reconciliation.
|
|
174
|
+
Indexers sync on-chain mint/burn events back to the off-chain ledger.
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
```ts
|
|
177
|
+
import { PointIndexer, BurnIndexer } from "@pafi-dev/issuer";
|
|
178
|
+
import { getContractAddresses } from "@pafi-dev/core";
|
|
179
|
+
|
|
180
|
+
const addrs = getContractAddresses(8453);
|
|
181
|
+
|
|
182
|
+
// Mint indexer: Transfer(0x0 → user) → deducts off-chain locked balance
|
|
183
|
+
const mintIndexer = new PointIndexer({
|
|
184
|
+
provider: publicClient,
|
|
185
|
+
pointTokenAddress: addrs.pointToken,
|
|
186
|
+
ledger,
|
|
187
|
+
cursorStore, // persists last-processed block; use Postgres or Redis
|
|
188
|
+
fromBlock: 28_000_000n,
|
|
189
|
+
confirmations: 2,
|
|
190
|
+
pollIntervalMs: 3_000,
|
|
191
|
+
});
|
|
177
192
|
|
|
178
|
-
|
|
193
|
+
// Burn indexer: Transfer(user → 0x0) → resolves pending off-chain credit
|
|
194
|
+
const burnIndexer = new BurnIndexer({
|
|
195
|
+
provider: publicClient,
|
|
196
|
+
pointTokenAddress: addrs.pointToken,
|
|
197
|
+
ledger,
|
|
198
|
+
cursorStore,
|
|
199
|
+
matchLockId: async (evt) => {
|
|
200
|
+
// Return the lockId from reservePendingCredit() that matches this burn.
|
|
201
|
+
// Typically a DB lookup by (from, amount, status=PENDING).
|
|
202
|
+
return db.findPendingCreditLockId(evt.from, evt.amount);
|
|
203
|
+
},
|
|
204
|
+
});
|
|
179
205
|
|
|
206
|
+
mintIndexer.start();
|
|
207
|
+
burnIndexer.start();
|
|
180
208
|
```
|
|
181
|
-
Frontend Issuer backend
|
|
182
|
-
──────── ──────────────
|
|
183
|
-
1. GET /auth/nonce → authService.getNonce()
|
|
184
|
-
← { nonce }
|
|
185
|
-
2. Build EIP-4361 message with
|
|
186
|
-
nonce (via PafiSDK or
|
|
187
|
-
@pafi-dev/core createLoginMessage)
|
|
188
|
-
3. Sign with wallet
|
|
189
|
-
4. POST /auth/login → authService.login(message, sig)
|
|
190
|
-
{ message, signature } → parse → verify → consume nonce
|
|
191
|
-
→ create session → issue JWT
|
|
192
|
-
← { token, userAddress, expiresAt }
|
|
193
|
-
5. Authorization: Bearer <jwt> → authenticateRequest()
|
|
194
|
-
→ verify JWT + session
|
|
195
|
-
→ pass { userAddress, chainId } to handlers
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Key property: a failed signature does **not** burn the nonce. Users can
|
|
199
|
-
retry with the correct wallet on the same nonce without hitting the
|
|
200
|
-
backend again.
|
|
201
209
|
|
|
202
|
-
|
|
210
|
+
> **Indexers must be singletons.** Run exactly one instance per token per deployment.
|
|
211
|
+
> Use a Kubernetes `Deployment` with `replicas: 1`.
|
|
203
212
|
|
|
204
|
-
|
|
205
|
-
inputs and return plain outputs. Wrap them in Express / Fastify / Hono /
|
|
206
|
-
whatever you use. See `examples/express-issuer/server.ts` for the full
|
|
207
|
-
wiring.
|
|
213
|
+
---
|
|
208
214
|
|
|
209
|
-
|
|
210
|
-
| ------ | ---------------- | ---- | -------------------- |
|
|
211
|
-
| GET | /auth/nonce | no | `handleGetNonce` |
|
|
212
|
-
| POST | /auth/login | no | `handleLogin` |
|
|
213
|
-
| GET | /config | no | `handleConfig` |
|
|
214
|
-
| GET | /gas-fee | no | `handleGasFee` |
|
|
215
|
-
| POST | /auth/logout | yes | `handleLogout` |
|
|
216
|
-
| GET | /pools | yes | `handlePools` |
|
|
217
|
-
| GET | /user | yes | `handleUser` |
|
|
218
|
-
| POST | /claim-and-swap | yes | `handleClaimAndSwap` |
|
|
215
|
+
## PafiBackendClient
|
|
219
216
|
|
|
220
|
-
|
|
217
|
+
HTTP client for requesting paymaster sponsorship from the PAFI backend:
|
|
221
218
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
- 🕐 **The default `MemorySessionStore` does not survive restarts.** Nonces and sessions are both lost. Use `examples/adapters/redis-session-store.ts` for anything past local development.
|
|
225
|
-
- 🔒 **The `MintingGatewayError.safeToRetry` flag is load-bearing.** Do not ignore it in your API layer — surfacing `RELAY_REVERTED` as a retryable 400 will cause double-spend the moment a tx lands after the first response.
|
|
219
|
+
```ts
|
|
220
|
+
import { PafiBackendClient } from "@pafi-dev/issuer";
|
|
226
221
|
|
|
227
|
-
|
|
222
|
+
const pafiClient = new PafiBackendClient({
|
|
223
|
+
url: "https://api-dev.pacificfinance.org/api/sponsor",
|
|
224
|
+
issuerId: "gg56",
|
|
225
|
+
apiKey: process.env.PAFI_API_KEY!,
|
|
226
|
+
retry: { maxAttempts: 3, maxRetryAfterMs: 5_000 },
|
|
227
|
+
});
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
const sponsorship = await pafiClient.requestSponsorship({
|
|
230
|
+
chainId: 8453,
|
|
231
|
+
scenario: "mint",
|
|
232
|
+
userOp: {
|
|
233
|
+
sender: userAddress,
|
|
234
|
+
callData: userOp.callData,
|
|
235
|
+
// ...gas fields
|
|
236
|
+
},
|
|
237
|
+
target: {
|
|
238
|
+
contractAddress: addrs.pointToken,
|
|
239
|
+
functionSelector: "0x...",
|
|
240
|
+
},
|
|
241
|
+
});
|
|
231
242
|
|
|
232
|
-
|
|
233
|
-
|
|
243
|
+
// sponsorship.paymaster
|
|
244
|
+
// sponsorship.paymasterData
|
|
245
|
+
// sponsorship.paymasterVerificationGasLimit
|
|
246
|
+
// sponsorship.paymasterPostOpGasLimit
|
|
247
|
+
// sponsorship.expiresAt
|
|
234
248
|
```
|
|
235
249
|
|
|
236
|
-
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Error handling
|
|
253
|
+
|
|
254
|
+
### AuthError
|
|
237
255
|
|
|
238
|
-
|
|
256
|
+
Thrown by `AuthService` and `authenticateRequest` middleware:
|
|
239
257
|
|
|
240
258
|
```ts
|
|
241
|
-
import {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
type IPolicyEngine,
|
|
256
|
-
type PolicyDecision,
|
|
257
|
-
type PolicyEvalRequest,
|
|
258
|
-
|
|
259
|
-
// Signer
|
|
260
|
-
PrivateKeySigner, // DEV ONLY
|
|
261
|
-
type IIssuerSigner,
|
|
262
|
-
|
|
263
|
-
// Auth
|
|
264
|
-
AuthService,
|
|
265
|
-
AuthError,
|
|
266
|
-
type AuthErrorCode,
|
|
267
|
-
type AuthContext,
|
|
268
|
-
MemorySessionStore,
|
|
269
|
-
type ISessionStore,
|
|
270
|
-
type Session,
|
|
271
|
-
NonceManager,
|
|
272
|
-
authenticateRequest,
|
|
273
|
-
|
|
274
|
-
// Relay + fees
|
|
275
|
-
RelayService,
|
|
276
|
-
RelayError,
|
|
277
|
-
type RelayErrorCode,
|
|
278
|
-
type SubmitMintAndSwapParams,
|
|
279
|
-
type RelayResult,
|
|
280
|
-
type OperatorWalletLike,
|
|
281
|
-
FeeManager,
|
|
282
|
-
type FeeManagerConfig,
|
|
283
|
-
|
|
284
|
-
// Gateway
|
|
285
|
-
MintingGateway,
|
|
286
|
-
MintingGatewayError,
|
|
287
|
-
type MintingGatewayErrorCode,
|
|
288
|
-
type MintAndCashOutRequest,
|
|
289
|
-
type MintAndCashOutResponse,
|
|
290
|
-
encodeExtData,
|
|
291
|
-
|
|
292
|
-
// Indexer
|
|
293
|
-
PointIndexer,
|
|
294
|
-
type PointIndexerConfig,
|
|
295
|
-
type MintEvent,
|
|
296
|
-
type IIndexerCursorStore,
|
|
297
|
-
InMemoryCursorStore,
|
|
298
|
-
|
|
299
|
-
// API handlers + HTTP contract (source of truth — FE imports these type-only)
|
|
300
|
-
IssuerApiHandlers,
|
|
301
|
-
type IssuerApiHandlersConfig,
|
|
302
|
-
type ApiConfigResponse,
|
|
303
|
-
type ApiNonceResponse,
|
|
304
|
-
type ApiLoginRequest,
|
|
305
|
-
type ApiLoginResponse,
|
|
306
|
-
type ApiGasFeeResponse,
|
|
307
|
-
type ApiPoolsRequest,
|
|
308
|
-
type ApiPoolsResponse,
|
|
309
|
-
type ApiUserRequest,
|
|
310
|
-
type ApiUserResponse,
|
|
311
|
-
type ApiClaimAndSwapRequest,
|
|
312
|
-
type ApiClaimAndSwapResponse,
|
|
313
|
-
type PoolsProvider,
|
|
314
|
-
} from "@pafi-dev/issuer";
|
|
259
|
+
import { AuthError, type AuthErrorCode } from "@pafi-dev/issuer";
|
|
260
|
+
|
|
261
|
+
// AuthErrorCode:
|
|
262
|
+
// "INVALID_MESSAGE" | "INVALID_SIGNATURE" | "NONCE_MISMATCH"
|
|
263
|
+
// "NONCE_EXPIRED" | "TOKEN_EXPIRED" | "TOKEN_INVALID"
|
|
264
|
+
// "SESSION_NOT_FOUND" | "DOMAIN_MISMATCH" | "CHAIN_MISMATCH"
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const user = await authService.login(message, signature);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (err instanceof AuthError) {
|
|
270
|
+
console.log(err.code); // AuthErrorCode
|
|
271
|
+
}
|
|
272
|
+
}
|
|
315
273
|
```
|
|
316
274
|
|
|
317
|
-
###
|
|
275
|
+
### PafiBackendError
|
|
318
276
|
|
|
319
|
-
|
|
320
|
-
between the issuer backend and any frontend / mobile / SDK consumer. The
|
|
321
|
-
request/response types above are defined once here, then imported
|
|
322
|
-
**type-only** by frontend code so browser bundles never pull in server
|
|
323
|
-
code (`jose`, `node:crypto`, indexer polling loops, etc):
|
|
277
|
+
Thrown by `PafiBackendClient`:
|
|
324
278
|
|
|
325
279
|
```ts
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
280
|
+
import { PafiBackendError, type PafiBackendErrorCode } from "@pafi-dev/issuer";
|
|
281
|
+
|
|
282
|
+
// PafiBackendErrorCode:
|
|
283
|
+
// "RATE_LIMIT_EXCEEDED" | "RATE_LIMIT_EXCEEDED_DAILY" | "RATE_LIMIT_EXCEEDED_PER_USER"
|
|
284
|
+
// "RATE_LIMITER_UNAVAILABLE"
|
|
285
|
+
// "INTENT_REJECTED" | "MINT_CAP_EXCEEDED" | "ISSUER_INACTIVE"
|
|
286
|
+
// "ISSUER_UNAUTHORIZED" | "USER_UNAUTHORIZED"
|
|
287
|
+
// "PAYMASTER_UNAVAILABLE" | "TARGET_NOT_ALLOWLISTED"
|
|
288
|
+
// "BAD_REQUEST" | "INTERNAL_ERROR" | "TIMEOUT" | "NETWORK_ERROR"
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
await pafiClient.requestSponsorship(request);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
if (err instanceof PafiBackendError) {
|
|
294
|
+
if (err.code === "RATE_LIMIT_EXCEEDED") {
|
|
295
|
+
// err.retryAfter — seconds to wait before retrying
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
343
299
|
```
|
|
344
300
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Changelog
|
|
304
|
+
|
|
305
|
+
### 0.4.0
|
|
306
|
+
- `PafiBackendClient` added — HTTP client for PAFI paymaster backend with retry logic and bigint serialization
|
|
307
|
+
- `SponsorAuth` support — issuer signs EIP-712 authorization for FE to request paymaster sponsorship
|
|
308
|
+
- `PAYMASTER_UNAVAILABLE`, `TARGET_NOT_ALLOWLISTED` added to `PafiBackendErrorCode`
|
|
309
|
+
|
|
310
|
+
### 0.3.0-beta.10
|
|
311
|
+
- `MemoryPointLedger` removed from public exports — each issuer must implement `IPointLedger`
|
|
312
|
+
- `PrivateKeySigner` / `IIssuerSigner` removed — SDK accepts viem `WalletClient` directly
|
|
313
|
+
- `ledger` is now a required field in `IssuerServiceConfig`
|
|
314
|
+
- `handleClaim` added to `IssuerApiHandlers`
|
|
315
|
+
|
|
316
|
+
### 0.3.0-beta.9
|
|
317
|
+
- `PTRedeemHandler` enforces on-chain balance check before signing `BurnRequest`
|
|
349
318
|
|
|
350
|
-
|
|
319
|
+
### 0.3.0-beta.8
|
|
320
|
+
- `BalanceAggregator`, `BurnIndexer`, `PTRedeemHandler`, `TopUpRedemptionHandler`
|
|
351
321
|
|
|
352
|
-
|
|
322
|
+
### 0.3.0-alpha.0
|
|
323
|
+
- Initial `RelayService`, `PointIndexer`, `AuthService`, `IssuerApiHandlers`
|