@pafi-dev/app-ui 0.3.1
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 +584 -0
- package/dist/index.cjs +2326 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1501 -0
- package/dist/index.d.ts +1501 -0
- package/dist/index.js +2326 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# @pafi-dev/app-ui
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@pafi-dev/app-ui)
|
|
4
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
5
|
+
|
|
6
|
+
Mobile SDK for PAFI-integrated issuer apps. Provides a thin HTTP client,
|
|
7
|
+
embedded Privy wallet management, and React hooks for React Native / Expo
|
|
8
|
+
apps to integrate claim (mint), redeem (burn), and PAFI Web handoff flows.
|
|
9
|
+
|
|
10
|
+
Issuer app developers do not need to understand blockchain, wallets, or
|
|
11
|
+
cryptographic signing — the SDK handles everything.
|
|
12
|
+
|
|
13
|
+
### Platform support
|
|
14
|
+
|
|
15
|
+
| Platform | Supported | Notes |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| iOS | ✅ | Requires Xcode 16+ |
|
|
18
|
+
| Android | ✅ | Requires API 26+ |
|
|
19
|
+
| Web | ❌ | Not supported — SDK depends on native Privy embedded wallet |
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Node.js ≥ 18
|
|
24
|
+
- TypeScript ≥ 5.0
|
|
25
|
+
- Expo SDK 52+ (SDK 54 recommended)
|
|
26
|
+
- React Native ≥ 0.73.0
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add @pafi-dev/app-ui
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Peer dependencies
|
|
35
|
+
|
|
36
|
+
Issuer apps must install the following as peer dependencies:
|
|
37
|
+
|
|
38
|
+
| Package | Version | Note |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `react` | `^18.0.0 \|\| ^19.0.0` | React 19 recommended for Expo 54+ |
|
|
41
|
+
| `react-native` | `>=0.73.0` | |
|
|
42
|
+
| `expo` | `>=52.0.0` | Expo SDK 54 recommended |
|
|
43
|
+
| `@privy-io/expo` | `^0.64.0 \|\| ^0.65.0` | Wallet management + signing |
|
|
44
|
+
| `react-native-get-random-values` | `>=1.0.0` | Required polyfill for `crypto.getRandomValues` |
|
|
45
|
+
|
|
46
|
+
> **No web3 libraries required.** The SDK does not depend on `viem`, `ethers`, `permissionless`, or any other blockchain library. The backend builds all UserOps and returns EIP-712 typed data; the mobile SDK signs it via `eth_signTypedData_v4`.
|
|
47
|
+
|
|
48
|
+
## At a glance
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { PafiProvider, usePafiAuth, usePafiUser, usePafiClaim } from "@pafi-dev/app-ui";
|
|
52
|
+
|
|
53
|
+
// ── App root ──────────────────────────────────────────────────────
|
|
54
|
+
export default function App() {
|
|
55
|
+
return (
|
|
56
|
+
<PafiProvider
|
|
57
|
+
baseUrl="https://api.example.com"
|
|
58
|
+
getIssuerAccessToken={getIssuerAccessToken}
|
|
59
|
+
>
|
|
60
|
+
<Main />
|
|
61
|
+
</PafiProvider>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Main screen ───────────────────────────────────────────────────
|
|
66
|
+
function Main() {
|
|
67
|
+
const { login, isAuthenticated, walletAddress } = usePafiAuth();
|
|
68
|
+
const { totalBalance, offChainBalance, onChainBalance, gasFeeUsdt } = usePafiUser();
|
|
69
|
+
const { claim, isSubmitting } = usePafiClaim();
|
|
70
|
+
|
|
71
|
+
if (!isAuthenticated) {
|
|
72
|
+
return <Button onPress={login} title="Login" />;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
<Text>Wallet: {walletAddress}</Text>
|
|
78
|
+
<Text>Total Balance: {totalBalance} PT</Text>
|
|
79
|
+
<Text>Claimable: {offChainBalance} PT</Text>
|
|
80
|
+
<Text>On-chain: {onChainBalance} PT</Text>
|
|
81
|
+
<Button
|
|
82
|
+
onPress={() => claim({ amount: offChainBalance! })}
|
|
83
|
+
title={isSubmitting ? "Claiming..." : "Claim"}
|
|
84
|
+
disabled={isSubmitting}
|
|
85
|
+
/>
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
That's the entire integration. No wallets, no UserOps, no signing.
|
|
92
|
+
|
|
93
|
+
## What you get
|
|
94
|
+
|
|
95
|
+
| Export | Type | Description |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| `PafiProvider` | Component | Root provider — wraps PrivyProvider + PafiClient context |
|
|
98
|
+
| `usePafiAuth` | Hook | Login / logout with embedded Privy wallet + SIWE |
|
|
99
|
+
| `usePafiUser` | Hook | Balances (off-chain, on-chain, total), pools, gas fee |
|
|
100
|
+
| `usePafiClaim` | Hook | 2-step claim (mint): prepare → sign → submit |
|
|
101
|
+
| `usePafiRedeem` | Hook | 2-step redeem (burn): prepare → sign → submit |
|
|
102
|
+
| `usePafiRedemptionPreview` | Hook | v1.6 redemption policy preview + client-side validation |
|
|
103
|
+
| `usePafiDelegation` | Hook | EIP-7702 delegation (handled automatically during claim/redeem) |
|
|
104
|
+
| `usePafiClaimStatus` | Hook | Poll for claim (mint) transaction status |
|
|
105
|
+
| `usePafiRedeemStatus` | Hook | Poll for redeem (burn) transaction status |
|
|
106
|
+
| `usePafiTransactions` | Hook | Paginated transaction history |
|
|
107
|
+
| `usePafiEmailLink` | Hook | Link email to Privy wallet (for PAFI Web handoff) |
|
|
108
|
+
| `usePafiWebHandoff` | Hook | Open PAFI Web for swap / perp deposit |
|
|
109
|
+
| `useTos` | Hook | Observe issuer-owned TOS acceptance state |
|
|
110
|
+
| `TosGate` | Component | Conditionally renders children based on TOS acceptance |
|
|
111
|
+
| `PafiClient` | Class | Thin HTTP client (use directly if not using React) |
|
|
112
|
+
| `PafiApiError` | Class | Typed error for all backend responses |
|
|
113
|
+
| `PafiTosDeclinedError` | Class | Thrown when user declines the TOS modal |
|
|
114
|
+
| `PafiTosRequiredError` | Class | Thrown when claim/redeem is called before TOS acceptance |
|
|
115
|
+
|
|
116
|
+
## The login flow
|
|
117
|
+
|
|
118
|
+
The issuer app calls `login()` — one function, no arguments. Internally:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
login()
|
|
122
|
+
├── 1. Privy custom auth → silent, returns privyUserId (no modal)
|
|
123
|
+
├── 2. TOS check / accept → uses privyUserId, built-in modal if not yet accepted
|
|
124
|
+
├── 3. Wait for wallet ready → reactive, max 30s timeout (creates embedded wallet)
|
|
125
|
+
├── 4. GET /auth/nonce → backend returns random nonce
|
|
126
|
+
├── 5. buildSiweMessage(nonce) → SDK builds EIP-4361 message
|
|
127
|
+
├── 6. wallet.signMessage(siwe) → Privy signs (personal_sign)
|
|
128
|
+
├── 7. POST /auth/login → backend verifies → JWT
|
|
129
|
+
└── 8. client.setJwt(token) → authenticated
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
TOS is checked **before** wallet creation because `privyUserId` is available
|
|
133
|
+
immediately after Privy custom auth, while the embedded wallet is created
|
|
134
|
+
asynchronously. This avoids blocking the user behind both flows simultaneously.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
const { login, logout, isAuthenticated, isReady, walletAddress, error } = usePafiAuth();
|
|
138
|
+
|
|
139
|
+
// Block UI until Privy SDK is initialized
|
|
140
|
+
if (!isReady) return <ActivityIndicator />;
|
|
141
|
+
|
|
142
|
+
// One call — SDK handles everything
|
|
143
|
+
<Button onPress={login} title="Login" />
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`logout()` calls `POST /auth/logout` to revoke the session server-side before
|
|
147
|
+
clearing the local JWT and Privy session.
|
|
148
|
+
|
|
149
|
+
## Terms of Service (TOS)
|
|
150
|
+
|
|
151
|
+
TOS is an issuer-owned legal state. The SDK ships a built-in modal and orchestrates
|
|
152
|
+
the flow; the issuer backend is the source of truth for whether a user has accepted
|
|
153
|
+
a given TOS version. Claim, redeem, and delegation actions throw
|
|
154
|
+
`PafiTosRequiredError` until TOS is accepted.
|
|
155
|
+
|
|
156
|
+
The issuer backend must host two endpoints:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
GET /tos/status?userId={privyUserId} → { accepted: boolean, version: string }
|
|
160
|
+
POST /tos/accept { privyUserId, version } → { success: boolean }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
<PafiProvider
|
|
165
|
+
baseUrl="https://api.example.com"
|
|
166
|
+
getIssuerAccessToken={getIssuerAccessToken}
|
|
167
|
+
tos={{
|
|
168
|
+
tosBaseUrl: "https://api.example.com",
|
|
169
|
+
version: "v1.0",
|
|
170
|
+
contentUrl: "https://api.example.com/legal/tos",
|
|
171
|
+
title: "Terms of Service",
|
|
172
|
+
branding: { accentColor: "#FF6B35" },
|
|
173
|
+
onDeclined: async () => {
|
|
174
|
+
// Optional issuer policy, e.g. navigate away.
|
|
175
|
+
},
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<TosGate fallback={<ActivityIndicator />} blocked={<TosRequired />}>
|
|
179
|
+
<PafiPoweredScreen />
|
|
180
|
+
</TosGate>
|
|
181
|
+
</PafiProvider>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The TOS check runs automatically inside `login()` (after Privy custom auth,
|
|
185
|
+
before wallet creation). If `tos` is omitted, the gate is disabled and all
|
|
186
|
+
operations proceed without a TOS check.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
const { isAccepted, isChecking, error, retry } = useTos();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The SDK does not hard-code any TOS copy or URL — `contentUrl` is owned by the
|
|
193
|
+
issuer and rendered as a tappable link inside the built-in modal.
|
|
194
|
+
|
|
195
|
+
## The claim flow
|
|
196
|
+
|
|
197
|
+
### One-shot (no confirmation screen)
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
const { claim, isSubmitting, error } = usePafiClaim();
|
|
201
|
+
|
|
202
|
+
// One call — prepare + sign + submit
|
|
203
|
+
const { userOpHash } = await claim({ amount: "1000000000000000000" });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### With confirmation screen
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
const { prepare, confirm, preparedData, isPreparing, isSubmitting } = usePafiClaim();
|
|
210
|
+
|
|
211
|
+
// Step 1 — get a summary for the UI
|
|
212
|
+
await prepare({ amount: "1000000000000000000" });
|
|
213
|
+
|
|
214
|
+
// Step 2 — show the user what they'll receive
|
|
215
|
+
// preparedData.summary: { amount, gasFee, netPtMinted, expiresAt }
|
|
216
|
+
<Text>Minting: {preparedData.summary.amount} PT</Text>
|
|
217
|
+
<Text>Gas fee: {preparedData.summary.gasFee} PT</Text>
|
|
218
|
+
<Text>Net: {preparedData.summary.netPtMinted} PT</Text>
|
|
219
|
+
|
|
220
|
+
// Step 3 — user confirms → SDK signs + submits
|
|
221
|
+
<Button onPress={() => confirm(preparedData.lockId)} title="Confirm" />
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### amount field
|
|
225
|
+
|
|
226
|
+
`amount` is a base-unit string (18 decimals). Pass `offChainBalance` from
|
|
227
|
+
`usePafiUser()` to claim the full claimable balance:
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
const { offChainBalance } = usePafiUser();
|
|
231
|
+
claim({ amount: offChainBalance! });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## The redeem flow
|
|
235
|
+
|
|
236
|
+
Redeem mirrors the claim flow — it burns on-chain PT and credits off-chain points.
|
|
237
|
+
|
|
238
|
+
### One-shot
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
const { redeem, isSubmitting, error } = usePafiRedeem();
|
|
242
|
+
|
|
243
|
+
const { userOpHash } = await redeem({ amount: "1000000000000000000" });
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### With confirmation screen
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
const { prepare, confirm, preparedData, isPreparing, isSubmitting } = usePafiRedeem();
|
|
250
|
+
|
|
251
|
+
// Step 1 — prepare
|
|
252
|
+
await prepare({ amount: "1000000000000000000" });
|
|
253
|
+
|
|
254
|
+
// Step 2 — show summary
|
|
255
|
+
// preparedData.summary: { amount, gasFee, netPtBurned, expiresAt }
|
|
256
|
+
<Text>Burning: {preparedData.summary.amount} PT</Text>
|
|
257
|
+
|
|
258
|
+
// Step 3 — user confirms
|
|
259
|
+
<Button onPress={() => confirm(preparedData.lockId)} title="Confirm" />
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Shortfall helper
|
|
263
|
+
|
|
264
|
+
For voucher redemption where the user may need to burn on-chain PT to cover a shortfall:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
const { redeem, calculateShortfall } = usePafiRedeem();
|
|
268
|
+
const { offChainBalance } = usePafiUser();
|
|
269
|
+
|
|
270
|
+
const shortfall = calculateShortfall(voucherCostWei, offChainBalance!);
|
|
271
|
+
if (shortfall !== "0") {
|
|
272
|
+
await redeem({ amount: shortfall });
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Email linking
|
|
277
|
+
|
|
278
|
+
Email linking is deferred — users can view balances and claim immediately
|
|
279
|
+
after login. Email is only linked when the user first needs PAFI Web access
|
|
280
|
+
(e.g., Swap / Invest), and only once.
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
const { sendCode, confirmCode, status, isEmailLinked } = usePafiEmailLink();
|
|
284
|
+
|
|
285
|
+
// Gate swap behind email linking
|
|
286
|
+
const handleSwap = async () => {
|
|
287
|
+
if (!isEmailLinked) {
|
|
288
|
+
await sendCode(userEmail); // Privy sends OTP
|
|
289
|
+
showOtpModal(); // Show your OTP input UI
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// Proceed to swap
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// After user enters OTP
|
|
296
|
+
const handleOtpSubmit = async (code: string) => {
|
|
297
|
+
await confirmCode({ code, email: userEmail });
|
|
298
|
+
// Email linked — proceed to swap
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
| `status` | Meaning |
|
|
303
|
+
| --- | --- |
|
|
304
|
+
| `idle` | Not started |
|
|
305
|
+
| `sending` | Sending OTP to the user's email |
|
|
306
|
+
| `awaiting_code` | OTP sent, waiting for user input |
|
|
307
|
+
| `confirming` | Verifying OTP code with Privy |
|
|
308
|
+
| `linked` | Email successfully linked |
|
|
309
|
+
| `error` | Something went wrong — check `error` |
|
|
310
|
+
|
|
311
|
+
## Transaction history
|
|
312
|
+
|
|
313
|
+
Paginated transaction history with infinite scroll support:
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
const { transactions, isLoading, hasMore, loadMore, refresh } = usePafiTransactions();
|
|
317
|
+
|
|
318
|
+
// Render list
|
|
319
|
+
<FlatList
|
|
320
|
+
data={transactions}
|
|
321
|
+
renderItem={({ item }) => (
|
|
322
|
+
<Text>{item.type}: {item.amount} PT — {item.status}</Text>
|
|
323
|
+
)}
|
|
324
|
+
onEndReached={() => hasMore && loadMore()}
|
|
325
|
+
refreshing={isLoading}
|
|
326
|
+
onRefresh={refresh}
|
|
327
|
+
/>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## PAFI Web handoff
|
|
331
|
+
|
|
332
|
+
Open PAFI Web so the user can swap, deposit, or manage their assets.
|
|
333
|
+
The SDK provides the URL — the issuer app decides when and how to open it.
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
const { openPafiWeb, webUrl } = usePafiWebHandoff();
|
|
337
|
+
|
|
338
|
+
// Open PAFI Web homepage
|
|
339
|
+
const url = await openPafiWeb();
|
|
340
|
+
await Linking.openURL(url);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Transaction polling
|
|
344
|
+
|
|
345
|
+
After a claim or redeem, the on-chain execution is asynchronous. You can use the dedicated status hooks to poll until the transaction reaches a terminal status:
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
import { usePafiClaimStatus } from "@pafi-dev/app-ui";
|
|
349
|
+
|
|
350
|
+
// lockId comes from the prepare() response, NOT userOpHash
|
|
351
|
+
const { status, txHash, isLoading, error } = usePafiClaimStatus(lockId);
|
|
352
|
+
|
|
353
|
+
if (status === "MINTED") {
|
|
354
|
+
console.log("Claim successful! txHash:", txHash);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
| Status | Meaning |
|
|
359
|
+
| --- | --- |
|
|
360
|
+
| `PENDING` | Tx submitted, awaiting on-chain confirmation |
|
|
361
|
+
| `MINTED` | Mint event indexed — balance deducted from ledger |
|
|
362
|
+
| `EXPIRED` | Prepare TTL expired before submit, or consent expired |
|
|
363
|
+
| `FAILED` | On-chain tx reverted |
|
|
364
|
+
|
|
365
|
+
## Error handling
|
|
366
|
+
|
|
367
|
+
All backend errors are wrapped in `PafiApiError`:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import { PafiApiError } from "@pafi-dev/app-ui";
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
await claim({ amount });
|
|
374
|
+
} catch (err) {
|
|
375
|
+
if (err instanceof PafiApiError) {
|
|
376
|
+
console.log(err.code); // "INSUFFICIENT_BALANCE", "POLICY_CAP_EXCEEDED", …
|
|
377
|
+
console.log(err.httpStatus); // 400, 401, 422, 500, …
|
|
378
|
+
console.log(err.safeToRetry); // false on claim errors — do NOT retry automatically
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Retry strategy (built into `PafiClient`)
|
|
384
|
+
|
|
385
|
+
| Condition | Action |
|
|
386
|
+
| --- | --- |
|
|
387
|
+
| `5xx` + `safeToRetry=true` | Retry up to 3× with exponential backoff (500ms base, 5s cap, ±25% jitter) |
|
|
388
|
+
| `5xx` + `safeToRetry=false` | **Never** retry — tx may be in mempool |
|
|
389
|
+
| `4xx` | Never retry — client error |
|
|
390
|
+
| `401` | Clear JWT + throw — app must call `login()` again |
|
|
391
|
+
| Network error | Retry with same backoff (transient) — **except** `/claim/submit`, `/redeem/submit`, `/delegate/submit`: throw immediately (`safeToRetry=false`) |
|
|
392
|
+
|
|
393
|
+
## `PafiConfig` reference
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
interface PafiConfig {
|
|
397
|
+
/** Issuer backend URL. Captured at mount — see "Config immutability" below. */
|
|
398
|
+
baseUrl: string;
|
|
399
|
+
/** Issuer JWT callback for Privy custom auth */
|
|
400
|
+
getIssuerAccessToken: () => Promise<string>;
|
|
401
|
+
/** Chain ID (default: 8453 — Base mainnet) */
|
|
402
|
+
chainId?: number;
|
|
403
|
+
/** Point token contract address */
|
|
404
|
+
pointTokenAddress?: string;
|
|
405
|
+
/** PAFI Web URL for swap/invest handoff */
|
|
406
|
+
pafiWebUrl?: string;
|
|
407
|
+
/** Custom fetch for testing / RN polyfills. Captured at mount. */
|
|
408
|
+
fetchFn?: typeof fetch;
|
|
409
|
+
/** Wallet mode. Default is "embedded". Note: only "embedded" is supported in production at this time. */
|
|
410
|
+
walletMode?: "embedded" | "external" | "both";
|
|
411
|
+
/** Additional Privy config overrides */
|
|
412
|
+
privyConfig?: Partial<PrivyClientConfig>;
|
|
413
|
+
/** Optional issuer-owned TOS gate (built-in modal + Issuer BE source of truth) */
|
|
414
|
+
tos?: TosConfig;
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Config immutability
|
|
419
|
+
|
|
420
|
+
`baseUrl` and `fetchFn` are captured **once at mount** when `PafiClient` is
|
|
421
|
+
constructed. Mutating them on the `PafiProvider` after mount has no effect —
|
|
422
|
+
the underlying client keeps using the original values. Re-mount the provider
|
|
423
|
+
(e.g., via `key={baseUrl}`) if you need to switch backends at runtime.
|
|
424
|
+
|
|
425
|
+
This is intentional: the JWT lifecycle is tied to a specific backend, and
|
|
426
|
+
swapping `baseUrl` mid-session would silently leak the JWT to a different host.
|
|
427
|
+
|
|
428
|
+
## Architecture constraints
|
|
429
|
+
|
|
430
|
+
**Mobile never builds UserOps or batch calls.** The backend is the single
|
|
431
|
+
source of truth for UserOp construction. Mobile sends `{ amount }`, receives
|
|
432
|
+
EIP-712 typed data, the SDK signs it via `eth_signTypedData_v4` internally,
|
|
433
|
+
and the backend handles all blockchain complexity (UserOp assembly, paymaster,
|
|
434
|
+
bundler submission).
|
|
435
|
+
|
|
436
|
+
**No runtime dependency on blockchain packages.** `@pafi-dev/core`,
|
|
437
|
+
`@pafi-dev/issuer`, `viem`, and `permissionless` are NOT in dependencies.
|
|
438
|
+
No ABI encoding, no RPC calls, no contract reads happen in the mobile bundle.
|
|
439
|
+
|
|
440
|
+
**JWT is stored in memory only.** The SDK does not persist the JWT to disk.
|
|
441
|
+
If your app needs to survive process restarts without re-login, persist
|
|
442
|
+
`token` from `LoginResponse` yourself (e.g. `expo-secure-store`) and call
|
|
443
|
+
`client.setJwt()` on startup.
|
|
444
|
+
|
|
445
|
+
## Exports
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
import {
|
|
449
|
+
// Provider + context
|
|
450
|
+
PafiProvider,
|
|
451
|
+
type PafiProviderProps,
|
|
452
|
+
usePafiClient,
|
|
453
|
+
|
|
454
|
+
// Hooks
|
|
455
|
+
usePafiAuth,
|
|
456
|
+
type UsePafiAuthReturn,
|
|
457
|
+
usePafiUser,
|
|
458
|
+
type UsePafiUserReturn,
|
|
459
|
+
usePafiClaim,
|
|
460
|
+
type UsePafiClaimReturn,
|
|
461
|
+
usePafiRedeem,
|
|
462
|
+
type UsePafiRedeemReturn,
|
|
463
|
+
usePafiRedemptionPreview,
|
|
464
|
+
type UsePafiRedemptionPreviewReturn,
|
|
465
|
+
usePafiDelegation,
|
|
466
|
+
type UsePafiDelegationReturn,
|
|
467
|
+
usePafiClaimStatus,
|
|
468
|
+
type UsePafiClaimStatusReturn,
|
|
469
|
+
usePafiRedeemStatus,
|
|
470
|
+
type UsePafiRedeemStatusReturn,
|
|
471
|
+
usePafiTransactions,
|
|
472
|
+
type UsePafiTransactionsReturn,
|
|
473
|
+
usePafiEmailLink,
|
|
474
|
+
type UsePafiEmailLinkReturn,
|
|
475
|
+
type EmailLinkStatus,
|
|
476
|
+
usePafiWebHandoff,
|
|
477
|
+
type UsePafiWebHandoffReturn,
|
|
478
|
+
|
|
479
|
+
// TOS
|
|
480
|
+
TosGate,
|
|
481
|
+
useTos,
|
|
482
|
+
PafiTosDeclinedError,
|
|
483
|
+
PafiTosRequiredError,
|
|
484
|
+
type TosConfig,
|
|
485
|
+
type TosBrandingConfig,
|
|
486
|
+
type TosStatus,
|
|
487
|
+
type TosGateProps,
|
|
488
|
+
type UseTosReturn,
|
|
489
|
+
type TosStatusResponse,
|
|
490
|
+
type TosAcceptResponse,
|
|
491
|
+
|
|
492
|
+
// HTTP client
|
|
493
|
+
PafiClient,
|
|
494
|
+
PafiApiError,
|
|
495
|
+
type PafiErrorType,
|
|
496
|
+
type PafiErrorPayload,
|
|
497
|
+
type PafiErrorMeta,
|
|
498
|
+
type PafiErrorResponse,
|
|
499
|
+
|
|
500
|
+
// Config types
|
|
501
|
+
type PafiConfig,
|
|
502
|
+
type PafiClientConfig,
|
|
503
|
+
|
|
504
|
+
// Wire types — auth
|
|
505
|
+
type NonceResponse,
|
|
506
|
+
type LoginRequest,
|
|
507
|
+
type LoginResponse,
|
|
508
|
+
|
|
509
|
+
// Wire types — user
|
|
510
|
+
type UserResponse,
|
|
511
|
+
type PoolKey,
|
|
512
|
+
type PoolsResponse,
|
|
513
|
+
|
|
514
|
+
// Wire types — claim (mint)
|
|
515
|
+
type PrepareClaimRequest,
|
|
516
|
+
type PrepareClaimResponse,
|
|
517
|
+
type ClaimSummary,
|
|
518
|
+
type SubmitClaimRequest,
|
|
519
|
+
type SubmitClaimResponse,
|
|
520
|
+
type ClaimStatusResponse,
|
|
521
|
+
|
|
522
|
+
// Wire types — redeem (burn)
|
|
523
|
+
type PrepareRedeemRequest,
|
|
524
|
+
type PrepareRedeemResponse,
|
|
525
|
+
type RedeemSummary,
|
|
526
|
+
type SubmitRedeemRequest,
|
|
527
|
+
type SubmitRedeemResponse,
|
|
528
|
+
type RedeemStatusResponse,
|
|
529
|
+
|
|
530
|
+
// Wire types — redemption policy (v1.6)
|
|
531
|
+
type RedemptionDenialCode,
|
|
532
|
+
type RedemptionClientValidationCode,
|
|
533
|
+
type RedemptionPreviewResponse,
|
|
534
|
+
type RedemptionEvaluateRequest,
|
|
535
|
+
type RedemptionEvaluateResponse,
|
|
536
|
+
type RedemptionDenial,
|
|
537
|
+
|
|
538
|
+
// Wire types — delegation
|
|
539
|
+
type DelegationStatusResponse,
|
|
540
|
+
type PrepareDelegationRequest,
|
|
541
|
+
type PrepareDelegationResponse,
|
|
542
|
+
type SubmitDelegationRequest,
|
|
543
|
+
type SubmitDelegationResponse,
|
|
544
|
+
|
|
545
|
+
// Wire types — web handoff
|
|
546
|
+
type WebHandoffResponse,
|
|
547
|
+
|
|
548
|
+
// Shared types
|
|
549
|
+
type EIP712TypedData,
|
|
550
|
+
type TransactionRecord,
|
|
551
|
+
type TransactionsResponse,
|
|
552
|
+
} from "@pafi-dev/app-ui";
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## Tests
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
pnpm --filter @pafi-dev/app-ui test
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
All tests are hermetic — no network, no on-chain state required.
|
|
562
|
+
|
|
563
|
+
## Changelog
|
|
564
|
+
|
|
565
|
+
### 0.2.1
|
|
566
|
+
|
|
567
|
+
- Added `usePafiClaimStatus` and `usePafiRedeemStatus` for transaction polling
|
|
568
|
+
- Added `usePafiTransactions` for paginated transaction history
|
|
569
|
+
- Added `usePafiRedemptionPreview` for v1.6 redemption policy preview
|
|
570
|
+
- Added `TosGate` component and `useTos` hook (replaces the wallet-consent
|
|
571
|
+
prototype from earlier drafts — the consent API is no longer exported)
|
|
572
|
+
- EIP-712 `signTypedData` for all signing (replaced `personal_sign`)
|
|
573
|
+
- Added EIP-7702 v normalization (27/28 → 0/1 yParity)
|
|
574
|
+
|
|
575
|
+
### 0.2.0
|
|
576
|
+
|
|
577
|
+
- Initial public release
|
|
578
|
+
- `PafiProvider`, `usePafiAuth`, `usePafiUser`, `usePafiClaim`, `usePafiRedeem`
|
|
579
|
+
- `usePafiDelegation`, `usePafiEmailLink`, `usePafiWebHandoff`
|
|
580
|
+
- `PafiClient` HTTP client with retry + exponential backoff
|
|
581
|
+
|
|
582
|
+
## License
|
|
583
|
+
|
|
584
|
+
Apache-2.0
|