@pafi-dev/core 0.3.0-beta.9 → 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/README.md CHANGED
@@ -1,1189 +1,170 @@
1
1
  # @pafi-dev/core
2
2
 
3
- The core TypeScript SDK for the PAFI point token system. Provides EIP-712
4
- signing and verification, EIP-4361 (Sign-In with Ethereum) helpers,
5
- contract ABIs, Relay calldata encoding, on-chain quoting, and swap
6
- building.
3
+ Core TypeScript SDK for the PAFI point token system. Provides EIP-712 signing/verification, contract ABIs + addresses, UserOp building, swap/perp calldata encoding, and on-chain quoting.
7
4
 
8
- ## What's new in 0.3.0-alpha.0UserOp primitives (v1.4 preview)
9
-
10
- Preview release adding the **sponsored UserOp building blocks** for
11
- EIP-7702 + ERC-4337 flows. No breaking changes — all v0.2.x exports
12
- remain. Additions:
13
-
14
- - `userop/types.ts` — `UserOperation`, `PartialUserOperation`,
15
- `Operation`, `ENTRY_POINT_V07` constant
16
- - `userop/operations.ts` — `erc20TransferOp`, `erc20ApproveOp`,
17
- `erc20BurnOp`, `rawCallOp`
18
- - `userop/batchExecute.ts` — `encodeBatchExecute()` +
19
- `BATCH_EXECUTOR_ABI` (EIP-7702 delegation target)
20
- - `userop/buildUserOperation.ts` — `buildPartialUserOperation()` +
21
- `assembleUserOperation()`
22
- - `paymaster/` — `PaymasterConfig`, `SponsorshipScenario`, module-level
23
- `setPaymasterConfig` / `getPaymasterConfig`
24
- - `utils/checkEthAndBranch.ts` — decide sponsored-vs-normal path based
25
- on the user's native balance
26
-
27
- **Deprecated:** `buildSwapFromQuote`, `encodeMintAndSwap`,
28
- `buildRelaySwapParams` — retired from the app-side flow. Still shipped
29
- for v0.2.x consumers; removed in 2.0.
30
-
31
- **Not yet ready** (blocked on SC team): `MintRequest` v2 + `BurnConsent`
32
- EIP-712 builders. Comes in 0.3.0-beta.
33
-
34
- ```bash
35
- pnpm add @pafi-dev/core@0.3.0-alpha.0
36
- ```
5
+ **Version:** `0.3.0-beta.10`Base mainnet ready.
37
6
 
38
7
  ---
39
8
 
40
- **`@pafi-dev/core` is HTTP-client-free on purpose.** It covers only the
41
- primitives that need a signer or a provider (or neither) — never the
42
- HTTP wire. The HTTP contract (routes, request/response types) is owned
43
- by [`@pafi/issuer`](../issuer/README.md) as the single source of truth,
44
- and frontends import those types `type`-only so browser bundles never
45
- pull in server code like `jose` or `node:crypto`.
46
-
47
- | Runs on | Purpose |
48
- |---|---|
49
- | Frontend (web + mobile) | Sign EIP-712 messages, build calldata, quote swaps, construct login messages |
50
- | Issuer backend | Verify EIP-712 signatures, decode calldata, read on-chain nonces / minter status |
51
-
52
9
  ## Installation
53
10
 
54
11
  ```bash
55
12
  pnpm add @pafi-dev/core viem
13
+ # viem ^2.0.0 is a required peer dependency
56
14
  ```
57
15
 
58
- `viem ^2.0.0` is a peer dependency and must be installed alongside this package.
59
-
60
16
  ---
61
17
 
62
- ## Quick Start
63
-
64
- ```ts
65
- import { createPublicClient, createWalletClient, http } from "viem";
66
- import { privateKeyToAccount } from "viem/accounts";
67
- import { PafiSDK } from "@pafi-dev/core";
68
-
69
- const account = privateKeyToAccount("0x...");
70
-
71
- const sdk = new PafiSDK({
72
- chainId: 8453,
73
- rpcUrl: "https://base-mainnet.g.alchemy.com/v2/...",
74
- signer: createWalletClient({
75
- account,
76
- transport: http("https://base-mainnet.g.alchemy.com/v2/..."),
77
- }),
78
- pointTokenAddress: "0xPointToken...",
79
- relayContractAddress: "0xRelay...",
80
- });
81
-
82
- // Fetch on-chain nonce then sign a mint request
83
- const nonce = await sdk.getMintRequestNonce(account.address);
84
-
85
- const sig = await sdk.signMintRequest({
86
- to: account.address,
87
- amount: 1_000_000n,
88
- nonce,
89
- deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
90
- });
91
-
92
- console.log(sig.serialized);
93
- ```
94
-
95
- ---
18
+ ## What this package covers
96
19
 
97
- ## Sub-path Imports
98
-
99
- Every module is available both from the root entry and via its own sub-path export. Use sub-paths to reduce bundle size in tree-shaking-aware environments.
100
-
101
- | Sub-path | Contents |
20
+ | Module | What it does |
102
21
  |---|---|
103
- | `@pafi-dev/core` | All exports (PafiSDK class + everything below) |
104
- | `@pafi-dev/core/eip712` | `buildMintRequestTypedData`, `buildReceiverConsentTypedData`, `signMintRequest`, `verifyMintRequest`, `signReceiverConsent`, `verifyReceiverConsent`, `buildDomain` |
105
- | `@pafi-dev/core/relay` | `encodeMintAndSwap`, `decodeMintAndSwap`, `buildRelaySwapParams`, `encodeExtData`, `decodeExtData` |
106
- | `@pafi-dev/core/contract` | `getMintRequestNonce`, `getReceiverConsentNonce`, `isMinter`, `getTokenName`, `getIssuer`, `isActiveIssuer`, `verifyMintCap`, `getPointTokenIssuer`, `getFeeBasisPoints`, `getSlippageBasisPoints`, `getUsdt` |
107
- | `@pafi-dev/core/quoting` | `quoteExactInput`, `quoteExactInputSingle`, `quoteBestRoute`, `findBestQuote`, `buildAllPaths`, `combineRoutes` |
108
- | `@pafi-dev/core/swap` | `checkAllowance`, `buildErc20ApprovalCalldata`, `buildPermit2ApprovalCalldata`, `buildUniversalRouterExecuteArgs`, `buildSwapFromQuote`, `buildV4SwapInput` |
109
- | `@pafi-dev/core/auth` | `createLoginMessage`, `parseLoginMessage`, `verifyLoginMessage` (EIP-4361) |
110
- | `@pafi-dev/core/abi` | All contract ABIs |
111
-
112
- ---
113
-
114
- ## API Reference
115
-
116
- ### PafiSDK Class
117
-
118
- The `PafiSDK` class is a convenience wrapper around all pure functions. All methods delegate to the same pure functions exported from the sub-path modules.
119
-
120
- #### Constructor
121
-
122
- ```ts
123
- import { PafiSDK } from "@pafi-dev/core";
124
-
125
- const sdk = new PafiSDK(config: PafiSDKConfig);
126
- ```
127
-
128
- ```ts
129
- interface PafiSDKConfig {
130
- chainId?: number;
131
- pointTokenAddress?: Address;
132
- relayContractAddress?: Address;
133
- signer?: WalletClient; // viem WalletClient
134
- provider?: PublicClient; // viem PublicClient (takes precedence over rpcUrl)
135
- rpcUrl?: string; // creates a PublicClient automatically if provider is omitted
136
- }
137
- ```
138
-
139
- All fields are optional at construction time. Missing fields are validated lazily when the relevant method is called, throwing a `ConfigurationError` if a required field is absent.
140
-
141
- #### Setters
142
-
143
- ```ts
144
- sdk.setPointTokenAddress(address: Address): void
145
- sdk.setRelayContractAddress(address: Address): void
146
- sdk.setSigner(signer: WalletClient): void
147
- sdk.setProvider(provider: PublicClient): void
148
- ```
149
-
150
- #### Domain
151
-
152
- ```ts
153
- // Resolves the EIP-712 domain by reading the token name on-chain.
154
- // Requires provider, pointTokenAddress, and chainId.
155
- await sdk.getDomain(): Promise<PointTokenDomainConfig>
156
- ```
157
-
158
- ---
159
-
160
- ### EIP-712 Signing
161
-
162
- #### Pure functions (from `@pafi-dev/core/eip712`)
163
-
164
- ```ts
165
- import {
166
- buildDomain,
167
- signMintRequest,
168
- verifyMintRequest,
169
- signReceiverConsent,
170
- verifyReceiverConsent,
171
- } from "@pafi-dev/core/eip712";
172
- ```
173
-
174
- ##### `buildDomain`
175
-
176
- ```ts
177
- function buildDomain(config: PointTokenDomainConfig): {
178
- name: string;
179
- version: "1";
180
- chainId: number;
181
- verifyingContract: Address;
182
- }
183
- ```
184
-
185
- Constructs the EIP-712 domain object. Version is always `"1"`.
186
-
187
- ##### `signMintRequest`
188
-
189
- ```ts
190
- async function signMintRequest(
191
- walletClient: WalletClient,
192
- domain: PointTokenDomainConfig,
193
- message: MintRequest,
194
- ): Promise<EIP712Signature>
195
- ```
196
-
197
- Signs a `MintRequest` struct with the connected wallet.
198
-
199
- ##### `verifyMintRequest`
200
-
201
- ```ts
202
- async function verifyMintRequest(
203
- domain: PointTokenDomainConfig,
204
- message: MintRequest,
205
- signature: Hex,
206
- expectedMinter: Address,
207
- ): Promise<SignatureVerification>
208
- ```
209
-
210
- Recovers the signer from a `MintRequest` signature and compares against `expectedMinter`.
211
-
212
- ##### `signReceiverConsent`
213
-
214
- ```ts
215
- async function signReceiverConsent(
216
- walletClient: WalletClient,
217
- domain: PointTokenDomainConfig,
218
- message: ReceiverConsent,
219
- ): Promise<EIP712Signature>
220
- ```
221
-
222
- Signs a `ReceiverConsent` struct.
223
-
224
- ##### `verifyReceiverConsent`
225
-
226
- ```ts
227
- async function verifyReceiverConsent(
228
- domain: PointTokenDomainConfig,
229
- message: ReceiverConsent,
230
- signature: Hex,
231
- expectedReceiver: Address,
232
- ): Promise<SignatureVerification>
233
- ```
234
-
235
- Recovers and validates a `ReceiverConsent` signature.
236
-
237
- #### PafiSDK methods
238
-
239
- ```ts
240
- await sdk.signMintRequest(message: MintRequest): Promise<EIP712Signature>
241
- await sdk.verifyMintRequest(message: MintRequest, signature: Hex, expectedMinter: Address): Promise<SignatureVerification>
242
- await sdk.signReceiverConsent(message: ReceiverConsent): Promise<EIP712Signature>
243
- await sdk.verifyReceiverConsent(message: ReceiverConsent, signature: Hex, expectedReceiver: Address): Promise<SignatureVerification>
244
- ```
245
-
246
- All four methods automatically resolve the domain from on-chain state via `getDomain()`.
247
-
248
- ---
249
-
250
- ### Relay Calldata
251
-
252
- #### Pure functions (from `@pafi-dev/core/relay`)
253
-
254
- ```ts
255
- import {
256
- encodeMintAndSwap,
257
- decodeMintAndSwap,
258
- encodeMintAndSwapV2,
259
- decodeMintAndSwapV2,
260
- } from "@pafi-dev/core/relay";
261
- ```
22
+ | `eip712/` | Build + sign + verify `MintRequest` and `BurnRequest` EIP-712 typed data |
23
+ | `contracts/` | Deployed contract addresses (Base mainnet 8453) + ABIs |
24
+ | `userop/` | `encodeBatchExecute()`, `buildPartialUserOperation()`, `BATCH_EXECUTOR_ABI` |
25
+ | `swap/` | `buildSwapWithGasDeduction()` PT→USDT via UniversalRouter |
26
+ | `perp/` | `buildPerpDepositWithGasDeduction()` USDC deposit to Orderly Vault |
27
+ | `quoting/` | `findBestQuote()` on-chain V4 Quoter multicall |
28
+ | `auth/` | `createLoginMessage()` EIP-4361 SIWE message builder |
29
+ | `utils/` | `computeAccountId()` Orderly perp account ID |
262
30
 
263
- ##### V1 (Relay.sol)
264
-
265
- ```ts
266
- // ABI-encode a mintAndSwap(MintParams, SwapParams) call for Relay.sol
267
- function encodeMintAndSwap(mint: MintParams, swap: SwapParams): Hex
268
-
269
- // Decode calldata produced by encodeMintAndSwap
270
- function decodeMintAndSwap(calldata: Hex): { mint: MintParams; swap: SwapParams }
271
- ```
272
-
273
- ##### V2 (RelayV2.sol)
274
-
275
- ```ts
276
- // ABI-encode a mintAndSwap(MintParams, SwapParams) call for RelayV2.sol
277
- function encodeMintAndSwapV2(mint: MintParams, swap: SwapParams): Hex
278
-
279
- // Decode calldata produced by encodeMintAndSwapV2
280
- function decodeMintAndSwapV2(calldata: Hex): { mint: MintParams; swap: SwapParams }
281
- ```
282
-
283
- #### Example
284
-
285
- ```ts
286
- import { encodeMintAndSwap } from "@pafi-dev/core/relay";
287
-
288
- const calldata = encodeMintAndSwap(
289
- {
290
- pointToken: "0xPointToken...",
291
- receiver: "0xReceiver...",
292
- amount: 1_000_000n,
293
- deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
294
- minterSig: "0x...",
295
- receiverSig: "0x...",
296
- },
297
- {
298
- path: [
299
- {
300
- intermediateCurrency: "0xUSDT...",
301
- fee: 500,
302
- tickSpacing: 10,
303
- hooks: "0x0000000000000000000000000000000000000000",
304
- hookData: "0x",
305
- },
306
- ],
307
- deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
308
- },
309
- );
310
- ```
311
-
312
- #### PafiSDK methods
313
-
314
- ```ts
315
- sdk.encodeMintAndSwap(mint: MintParams, swap: SwapParams): Hex
316
- sdk.decodeMintAndSwap(calldata: Hex): { mint: MintParams; swap: SwapParams }
317
- sdk.encodeExtData(minAmountOut: bigint, feeInUsdt: bigint): Hex
318
- sdk.buildRelaySwapParams({ quote, minAmountOut, feeInUsdt, deadline }): { swapParams, extData }
319
- ```
320
-
321
- ---
322
-
323
- ### Contract Reads
324
-
325
- #### PointToken (from `@pafi-dev/core/contract`)
326
-
327
- ```ts
328
- import {
329
- getMintRequestNonce,
330
- getReceiverConsentNonce,
331
- isMinter,
332
- getTokenName,
333
- getPointTokenIssuerAddress,
334
- } from "@pafi-dev/core/contract";
335
- ```
336
-
337
- | Function | Signature | Description |
338
- |---|---|---|
339
- | `getMintRequestNonce` | `(client, pointToken, receiver) => Promise<bigint>` | Current `MintRequest` nonce for `receiver` |
340
- | `getReceiverConsentNonce` | `(client, pointToken, receiver) => Promise<bigint>` | Current `ReceiverConsent` nonce for `receiver` |
341
- | `isMinter` | `(client, pointToken, account) => Promise<boolean>` | Returns `true` if `account` has the minter role |
342
- | `getTokenName` | `(client, pointToken) => Promise<string>` | ERC-20 `name()` — used as the EIP-712 domain name |
343
- | `getPointTokenIssuerAddress` | `(client, pointToken) => Promise<Address>` | Issuer address registered on the token |
344
-
345
- #### IssuerRegistry
346
-
347
- ```ts
348
- import { getIssuer, isActiveIssuer } from "@pafi-dev/core/contract";
349
- ```
350
-
351
- | Function | Signature | Description |
352
- |---|---|---|
353
- | `getIssuer` | `(client, registryAddress, issuer) => Promise<Issuer>` | Full `Issuer` struct for the given address |
354
- | `isActiveIssuer` | `(client, registryAddress, issuer) => Promise<boolean>` | Whether the issuer is currently active |
355
-
356
- #### MintingOracle
357
-
358
- ```ts
359
- import { verifyMintCap, getPointTokenIssuer } from "@pafi-dev/core/contract";
360
- ```
361
-
362
- | Function | Signature | Description |
363
- |---|---|---|
364
- | `verifyMintCap` | `(client, oracleAddress, issuer, amount) => Promise<void>` | Reverts if `amount` would exceed the issuer's mint cap |
365
- | `getPointTokenIssuer` | `(client, oracleAddress, pointToken) => Promise<Address>` | Maps a point token address back to its issuer |
366
-
367
- #### Relay helpers
368
-
369
- ```ts
370
- import { getFeeBasisPoints, getSlippageBasisPoints, getUsdt } from "@pafi-dev/core/contract";
371
- ```
372
-
373
- | Function | Signature | Description |
374
- |---|---|---|
375
- | `getFeeBasisPoints` | `(client, relayAddress) => Promise<bigint>` | Protocol fee in basis points |
376
- | `getSlippageBasisPoints` | `(client, relayAddress) => Promise<bigint>` | Slippage tolerance in basis points |
377
- | `getUsdt` | `(client, relayAddress) => Promise<Address>` | USDT address used by the Relay contract |
378
-
379
- #### PafiSDK methods
380
-
381
- ```ts
382
- await sdk.getMintRequestNonce(receiver: Address): Promise<bigint>
383
- await sdk.getReceiverConsentNonce(receiver: Address): Promise<bigint>
384
- ```
385
-
386
- #### PafiSDK quoting + swap methods
387
-
388
- ```ts
389
- // Quote — finds the best route via multicall
390
- await sdk.findBestQuote(tokenIn, tokenOut, amount, pools?, quoterAddress?): Promise<BestQuote>
391
-
392
- // Build UniversalRouter execute args from a quote
393
- sdk.buildSwapFromQuote({ quote, currencyIn, currencyOut, amountIn, minAmountOut }): { commands, inputs }
394
-
395
- // Build Relay SwapParams + extData from a quote
396
- sdk.buildRelaySwapParams({ quote, minAmountOut, feeInUsdt, deadline }): { swapParams, extData }
397
-
398
- // Encode extData for ReceiverConsent signing
399
- sdk.encodeExtData(minAmountOut, feeInUsdt): Hex
400
- ```
31
+ **This package is HTTP-client-free.** It covers signing, calldata encoding, and on-chain reads only — no fetch calls. Use on both frontend and issuer backend.
401
32
 
402
33
  ---
403
34
 
404
- ### Quoting
405
-
406
- #### Pure functions (from `@pafi-dev/core/quoting`)
407
-
408
- ```ts
409
- import {
410
- quoteExactInput,
411
- quoteExactInputSingle,
412
- quoteBestRoute,
413
- findBestQuote,
414
- buildAllPaths,
415
- combineRoutes,
416
- } from "@pafi-dev/core/quoting";
417
- ```
418
-
419
- ##### `quoteExactInput`
420
-
421
- ```ts
422
- async function quoteExactInput(
423
- client: PublicClient,
424
- quoterAddress: Address,
425
- exactCurrency: Address,
426
- path: PathKey[],
427
- exactAmount: bigint,
428
- ): Promise<QuoteResult>
429
- ```
430
-
431
- Quotes a multi-hop exact-input swap against the V4 quoter.
432
-
433
- ##### `quoteExactInputSingle`
35
+ ## Contract addresses (Base mainnet 8453)
434
36
 
435
37
  ```ts
436
- async function quoteExactInputSingle(
437
- client: PublicClient,
438
- quoterAddress: Address,
439
- poolKey: PoolKey,
440
- zeroForOne: boolean,
441
- exactAmount: bigint,
442
- hookData: Hex,
443
- ): Promise<{ amountOut: bigint; gasEstimate: bigint }>
444
- ```
445
-
446
- Quotes a single-hop swap given an explicit pool key and swap direction.
38
+ import { getContractAddresses, POINT_TOKEN_FACTORY_ADDRESSES, POINT_TOKEN_IMPL_ADDRESSES } from "@pafi-dev/core";
447
39
 
448
- ##### `quoteBestRoute`
40
+ const addrs = getContractAddresses(8453);
41
+ // addrs.issuerRegistry = "0xda2D3338CF70F462Ac175F5f2edfa45660CA4f31"
42
+ // addrs.mintingOracle = "0xD85165939C700E51c8a45099316C6482634C2Ab9"
43
+ // addrs.pafiHook = "0x870cAF9882d3160602AaC1769C2B264A2d8EC044"
44
+ // addrs.usdt = "0x5d313485Ba59C3bb91e1A9C0C11782F0b83d5dcd"
45
+ // addrs.batchExecutor = "0x7702cb554e6bFb442cb743A7dF23154544a7176C"
46
+ // addrs.pointToken = "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e"
449
47
 
450
- ```ts
451
- async function quoteBestRoute(
452
- client: PublicClient,
453
- quoterAddress: Address,
454
- exactCurrency: Address,
455
- routes: PathKey[][],
456
- exactAmount: bigint,
457
- ): Promise<BestQuote>
458
- ```
459
-
460
- Quotes multiple routes via a single `multicall` RPC call and returns the one with the highest `amountOut`. Routes that fail (e.g. pool does not exist) are silently dropped. Throws if no routes succeed.
461
-
462
- ##### `findBestQuote`
463
-
464
- ```ts
465
- async function findBestQuote(
466
- client: PublicClient,
467
- chainId: number,
468
- tokenIn: Address,
469
- tokenOut: Address,
470
- exactAmount: bigint,
471
- pools?: PoolKey[],
472
- quoterAddress?: Address,
473
- maxHops?: number,
474
- ): Promise<BestQuote>
475
- ```
476
-
477
- One-call quoting helper. Merges caller-provided `pools` with `COMMON_POOLS[chainId]`, builds all possible paths via `buildAllPaths`, then quotes them all via multicall. Uses `V4_QUOTER_ADDRESSES[chainId]` unless `quoterAddress` is provided.
478
-
479
- ##### `buildAllPaths`
480
-
481
- ```ts
482
- function buildAllPaths(
483
- pools: PoolKey[],
484
- tokenIn: Address,
485
- tokenOut: Address,
486
- maxHops?: number,
487
- ): PathKey[][]
488
- ```
489
-
490
- Builds all possible swap paths from `tokenIn` to `tokenOut` using DFS through the given pools. Each pool is used at most once per path. Returns an array of `PathKey[]` routes (up to `maxHops`, default 3).
491
-
492
- ##### `combineRoutes`
493
-
494
- ```ts
495
- function combineRoutes(
496
- chainId: number,
497
- pointTokenAddress: Address,
498
- ): PoolKey[]
499
- ```
500
-
501
- Merges `POINT_TOKEN_POOLS[chainId][pointTokenAddress]` and `COMMON_POOLS[chainId]` into a single pool list, with point token pools listed first.
502
-
503
- #### Example
504
-
505
- ```ts
506
- import { findBestQuote } from "@pafi-dev/core/quoting";
507
-
508
- // Provide only your token-specific pools — COMMON_POOLS are merged automatically
509
- const pointTokenPools = [
510
- { currency0: USDC, currency1: pointToken, fee: 3000, tickSpacing: 60, hooks: ZERO },
511
- ];
512
-
513
- const { bestRoute, allRoutes } = await findBestQuote(
514
- client,
515
- 8453, // Base chain ID — auto-resolves quoter address
516
- pointToken,
517
- USDC,
518
- 1_000_000n,
519
- pointTokenPools, // merged with COMMON_POOLS[8453]
520
- );
521
-
522
- console.log(bestRoute.amountOut, bestRoute.gasEstimate);
523
- console.log(`Found ${allRoutes.length} valid routes`);
48
+ // POINT_TOKEN_FACTORY_ADDRESSES[8453] = "0x36c0BAb2faBE45EfA6d13001143e43A266Af673B"
49
+ // POINT_TOKEN_IMPL_ADDRESSES[8453] = "0x2e6FB1B0C1A51abb83eC974890126a64eC02E995"
524
50
  ```
525
51
 
526
52
  ---
527
53
 
528
- ### Swap Building
529
-
530
- #### Pure functions (from `@pafi-dev/core/swap`)
531
-
532
- ```ts
533
- import {
534
- checkAllowance,
535
- buildErc20ApprovalCalldata,
536
- buildPermit2ApprovalCalldata,
537
- buildUniversalRouterExecuteArgs,
538
- buildV4SwapInput,
539
- } from "@pafi-dev/core/swap";
540
- ```
541
-
542
- ##### `checkAllowance`
543
-
544
- ```ts
545
- async function checkAllowance(
546
- client: PublicClient,
547
- token: Address,
548
- owner: Address,
549
- spender: Address,
550
- ): Promise<bigint>
551
- ```
552
-
553
- Reads the ERC-20 allowance of `spender` on behalf of `owner`.
554
-
555
- ##### `buildErc20ApprovalCalldata`
556
-
557
- ```ts
558
- function buildErc20ApprovalCalldata(spender: Address, amount: bigint): Hex
559
- ```
560
-
561
- Encodes `ERC20.approve(spender, amount)`.
562
-
563
- ##### `buildPermit2ApprovalCalldata`
564
-
565
- ```ts
566
- function buildPermit2ApprovalCalldata(
567
- token: Address,
568
- spender: Address,
569
- amount: bigint,
570
- expiration: number,
571
- ): Hex
572
- ```
573
-
574
- Encodes `Permit2.approve(token, spender, amount, expiration)`.
575
-
576
- ##### `buildUniversalRouterExecuteArgs`
54
+ ## EIP-712 signing (issuer backend)
577
55
 
578
56
  ```ts
579
- function buildUniversalRouterExecuteArgs(
580
- currencyIn: Address,
581
- path: PathKey[],
582
- amountIn: bigint,
583
- minAmountOut: bigint,
584
- outputCurrency: Address,
585
- ): { commands: Hex; inputs: Hex[] }
586
- ```
587
-
588
- Builds the `commands` and `inputs` arguments for `UniversalRouter.execute`. The command sequence is `V4_SWAP` (`0x10`). Actions encoded inside the payload are `SWAP_EXACT_IN → SETTLE_ALL → TAKE_ALL`.
57
+ import { signMintRequest, verifyMintRequest, signBurnRequest } from "@pafi-dev/core";
589
58
 
590
- ##### `buildV4SwapInput`
591
-
592
- ```ts
593
- function buildV4SwapInput(
594
- currencyIn: Address,
595
- path: PathKey[],
596
- amountIn: bigint,
597
- minAmountOut: bigint,
598
- outputCurrency: Address,
599
- ): Hex
600
- ```
601
-
602
- Encodes only the V4_SWAP command payload (`inputs[0]`). Use `buildUniversalRouterExecuteArgs` unless you need the raw payload for custom command composition.
603
-
604
- ##### `buildSwapFromQuote`
605
-
606
- ```ts
607
- function buildSwapFromQuote(params: {
608
- quote: QuoteResult;
609
- currencyIn: Address;
610
- currencyOut: Address;
611
- amountIn: bigint;
612
- minAmountOut: bigint;
613
- }): { commands: Hex; inputs: Hex[] }
614
- ```
615
-
616
- Build UniversalRouter execute args directly from a `QuoteResult` (returned by `findBestQuote` or `quoteBestRoute`). The caller provides `minAmountOut` after applying their own slippage tolerance.
617
-
618
- #### Example
619
-
620
- ```ts
621
- import { findBestQuote } from "@pafi-dev/core/quoting";
622
- import { buildSwapFromQuote } from "@pafi-dev/core/swap";
623
-
624
- // 1. Quote
625
- const { bestRoute } = await findBestQuote(client, 8453, tokenIn, USDC, amountIn, myPools);
59
+ const domain = {
60
+ name: "Test Point", // must match PointToken.name() on-chain
61
+ chainId: 8453,
62
+ verifyingContract: "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e",
63
+ };
626
64
 
627
- // 2. Build swap calldata (apply slippage externally)
628
- const minReceive = bestRoute.amountOut * 99n / 100n; // 1% slippage
629
- const { commands, inputs } = buildSwapFromQuote({
630
- quote: bestRoute,
631
- currencyIn: tokenIn,
632
- currencyOut: USDC,
633
- amountIn,
634
- minAmountOut: minReceive,
65
+ // Sign MintRequest (issuer backend)
66
+ const { signature } = await signMintRequest(walletClient, domain, {
67
+ to: "0xUser",
68
+ amount: 500n * 10n ** 18n,
69
+ nonce: 0n,
70
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 900),
635
71
  });
636
72
 
637
- // 3. Execute via UniversalRouter
73
+ // Verify (optional offline check)
74
+ const { valid } = await verifyMintRequest(domain, request, signature, expectedMinter);
638
75
  ```
639
76
 
640
77
  ---
641
78
 
642
- ### Auth helpers (EIP-4361 Sign-In With Ethereum)
643
-
644
- #### Pure functions (from `@pafi-dev/core/auth`)
645
-
646
- ```ts
647
- import {
648
- createLoginMessage,
649
- parseLoginMessage,
650
- verifyLoginMessage,
651
- } from "@pafi-dev/core/auth";
652
- ```
653
-
654
- These are stateless helpers — they build, parse, and verify EIP-4361
655
- login messages without touching the network. The frontend uses them to
656
- construct the message the wallet signs; the backend uses `verifyLoginMessage`
657
- inside its own `AuthService` (see `@pafi/issuer`).
658
-
659
- ```ts
660
- function createLoginMessage(params: {
661
- domain: string; // e.g. "app.example.com"
662
- address: Address; // user's wallet
663
- chainId: number;
664
- nonce: string; // from issuer backend /auth/nonce
665
- uri: string; // e.g. "https://app.example.com"
666
- statement?: string; // human-readable sign-in message
667
- version?: string; // defaults to "1"
668
- issuedAt?: Date;
669
- expirationTime?: Date;
670
- notBefore?: Date;
671
- requestId?: string;
672
- }): string
673
-
674
- function parseLoginMessage(message: string): LoginMessageParams
675
-
676
- async function verifyLoginMessage(
677
- message: string,
678
- signature: Hex,
679
- ): Promise<{ valid: boolean; address: Address }>
680
- ```
681
-
682
- #### PafiSDK helpers
79
+ ## Swap calldataPT -> USDT
683
80
 
684
81
  ```ts
685
- // Build a login message for the current signer + chain
686
- await sdk.createLoginMessage(params: Omit<LoginMessageParams, "address" | "chainId">): Promise<string>
687
-
688
- // Sign a login message with the current signer (personal_sign)
689
- await sdk.signLoginMessage(message: string): Promise<Hex>
690
- ```
82
+ import { buildSwapWithGasDeduction, findBestQuote, getContractAddresses } from "@pafi-dev/core";
691
83
 
692
- The SDK does not post the signed message for you — the frontend calls
693
- its own `fetch()` against the issuer backend:
84
+ const addrs = getContractAddresses(8453);
694
85
 
695
- ```ts
696
- // 1. Fetch nonce from the issuer backend
697
- const { nonce } = await fetch(`${ISSUER_URL}/auth/nonce`).then(r => r.json());
86
+ // Quote (on-chain V4 Quoter multicall)
87
+ const quote = await findBestQuote(
88
+ publicClient, 8453,
89
+ addrs.pointToken, addrs.usdt,
90
+ 500n * 10n ** 18n,
91
+ pools, // from gg56-backend POST /swap response
92
+ );
698
93
 
699
- // 2. Build + sign the login message
700
- const message = await sdk.createLoginMessage({
701
- domain: "app.example.com",
702
- uri: "https://app.example.com",
703
- nonce,
94
+ // Build BatchExecutor callData
95
+ const userOp = buildSwapWithGasDeduction({
96
+ chainId: 8453,
97
+ sender: "0xUser",
98
+ pointTokenAddress: addrs.pointToken,
99
+ usdtAddress: addrs.usdt,
100
+ universalRouterAddress: "0xUniversalRouter...",
101
+ pafiHookAddress: addrs.pafiHook,
102
+ quote,
103
+ slippageBps: 50,
104
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
105
+ aaNonce: 0n,
106
+ batchExecutorAddress: addrs.batchExecutor,
107
+ gasFeePt: 0n,
704
108
  });
705
- const signature = await sdk.signLoginMessage(message);
706
-
707
- // 3. POST to the issuer backend, receive a JWT
708
- const { token, userAddress } = await fetch(`${ISSUER_URL}/auth/login`, {
709
- method: "POST",
710
- headers: { "Content-Type": "application/json" },
711
- body: JSON.stringify({ message, signature }),
712
- }).then(r => r.json());
713
-
714
- // 4. Store the JWT and attach it to subsequent protected requests
715
- // (handled by the frontend's own state management — @pafi-dev/core does
716
- // not manage tokens)
717
- ```
718
-
719
- > **Why isn't there an HTTP client?** The HTTP contract (routes,
720
- > request/response shapes) is owned by `@pafi/issuer`. Frontends import
721
- > those types `type`-only and build their own `fetch()` calls. This
722
- > keeps the frontend bundle free of server-side dependencies like
723
- > `jose` and `node:crypto`, and ensures the protocol has exactly one
724
- > source of truth.
725
-
726
- ---
727
-
728
- ### ABIs
729
-
730
- All ABIs are available from `@pafi-dev/core/abi` or from the root import.
731
-
732
- ```ts
733
- import {
734
- pointTokenAbi,
735
- relayAbi,
736
- relayV2Abi,
737
- issuerRegistryAbi,
738
- pointTokenFactoryAbi,
739
- mintingOracleAbi,
740
- erc20Abi,
741
- universalRouterAbi,
742
- permit2Abi,
743
- v4QuoterAbi,
744
- } from "@pafi-dev/core/abi";
109
+ // userOp.callData -> BatchExecutor.execute([approve, swap])
745
110
  ```
746
111
 
747
- | Export | Contract |
748
- |---|---|
749
- | `pointTokenAbi` | PointToken ERC-20 with minting and nonce functions |
750
- | `relayAbi` | Relay.sol — `mintAndSwap`, fee and slippage reads |
751
- | `relayV2Abi` | RelayV2.sol — same interface, new deployment |
752
- | `issuerRegistryAbi` | IssuerRegistry — `getIssuer`, `isActiveIssuer` |
753
- | `pointTokenFactoryAbi` | PointTokenFactory |
754
- | `mintingOracleAbi` | MintingOracle — `verifyMintCap`, `pointTokenToIssuer` |
755
- | `erc20Abi` | Standard ERC-20 (`allowance`, `approve`, `transfer`, `balanceOf`) |
756
- | `universalRouterAbi` | Uniswap UniversalRouter — `execute` |
757
- | `permit2Abi` | Uniswap Permit2 — `approve` |
758
- | `v4QuoterAbi` | Uniswap V4 Quoter — `quoteExactInput`, `quoteExactInputSingle` |
759
-
760
112
  ---
761
113
 
762
- ### Constants
114
+ ## Perp deposit calldata — USDC -> Orderly
763
115
 
764
116
  ```ts
765
117
  import {
766
- SUPPORTED_CHAINS,
767
- COMMON_TOKENS,
768
- COMMON_POOLS,
769
- POINT_TOKEN_POOLS,
770
- mintRequestTypes,
771
- receiverConsentTypes,
118
+ buildPerpDepositWithGasDeduction,
119
+ computeAccountId,
120
+ BROKER_HASHES, TOKEN_HASHES, ORDERLY_VAULT_ADDRESSES,
772
121
  } from "@pafi-dev/core";
773
- ```
774
-
775
- | Constant | Type | Description |
776
- |---|---|---|
777
- | `SUPPORTED_CHAINS` | `Record<number, ChainConfig>` | Chain ID to chain metadata map |
778
- | `COMMON_TOKENS` | `Record<number, Record<string, Address>>` | Chain ID to `{ symbol → address }` token map |
779
- | `COMMON_POOLS` | `Record<number, PoolKey[]>` | Chain ID to common pool list (e.g. stablecoin pairs) |
780
- | `POINT_TOKEN_POOLS` | `Record<number, Record<Address, PoolKey[]>>` | Chain ID to per-point-token pool list |
781
- | `mintRequestTypes` | `const` | EIP-712 typed data types for `MintRequest` |
782
- | `receiverConsentTypes` | `const` | EIP-712 typed data types for `ReceiverConsent` |
783
-
784
- `COMMON_POOLS` and `POINT_TOKEN_POOLS` are consumed by `combineRoutes` to assemble candidate routes for quoting.
785
-
786
- ---
787
-
788
- ### Errors
789
-
790
- All errors extend `PafiSDKError`, which extends `Error`.
791
-
792
- ```ts
793
- import { PafiSDKError, ConfigurationError, SigningError, ApiError } from "@pafi-dev/core";
794
- ```
795
-
796
- | Class | Thrown when |
797
- |---|---|
798
- | `PafiSDKError` | Base class — not thrown directly |
799
- | `ConfigurationError` | A required field (`pointTokenAddress`, `signer`, `provider`, etc.) is not set when a method requires it |
800
- | `SigningError` | A signing operation fails |
801
- | `ApiError` | Generic HTTP error helper for callers writing their own `fetch()` against the issuer backend; carries an optional `status` |
802
-
803
- #### Example
804
-
805
- ```ts
806
- import { ConfigurationError, SigningError } from "@pafi-dev/core";
807
-
808
- try {
809
- await sdk.signMintRequest(message);
810
- } catch (err) {
811
- if (err instanceof ConfigurationError) {
812
- console.error(`SDK misconfigured: ${err.message}`);
813
- } else if (err instanceof SigningError) {
814
- console.error(`Signing failed: ${err.message}`);
815
- }
816
- }
817
- ```
818
-
819
- ---
820
-
821
- ## EIP-712 Type Reference
822
-
823
- The PointToken contract is the authoritative EIP-712 domain contract.
824
-
825
- ### Domain
826
-
827
- ```
828
- name: <token name from ERC-20 name()>
829
- version: "1"
830
- chainId: <network chain ID>
831
- verifyingContract: <PointToken address>
832
- ```
833
-
834
- ### MintRequest
835
-
836
- ```
837
- MintRequest(address to, uint256 amount, uint256 nonce, uint256 deadline)
838
- ```
839
-
840
- | Field | Type | Description |
841
- |---|---|---|
842
- | `to` | `address` | Recipient of the minted tokens |
843
- | `amount` | `uint256` | Token amount to mint |
844
- | `nonce` | `uint256` | Anti-replay nonce from `mintRequestNonces(to)` |
845
- | `deadline` | `uint256` | Unix timestamp after which the signature expires |
846
-
847
- The minter (issuer signer) signs this struct. The nonce must match `pointToken.mintRequestNonces(to)` at the time of execution.
848
-
849
- ### ReceiverConsent
850
-
851
- ```
852
- ReceiverConsent(address onBehalfOf, address originalReceiver, uint256 amount, uint256 nonce, uint256 deadline)
853
- ```
854
-
855
- | Field | Type | Description |
856
- |---|---|---|
857
- | `onBehalfOf` | `address` | Address whose points are being redirected |
858
- | `originalReceiver` | `address` | The original recipient who is granting consent |
859
- | `amount` | `uint256` | Token amount involved in the consent |
860
- | `nonce` | `uint256` | Anti-replay nonce from `receiverConsentNonces(originalReceiver)` |
861
- | `deadline` | `uint256` | Unix timestamp after which the signature expires |
862
-
863
- The original receiver signs this struct to authorize minting on behalf of another address.
864
-
865
- ---
866
-
867
- ## PointToken Mint Paths
868
-
869
- The Relay contract supports three mint paths depending on which signatures are provided:
870
-
871
- | Path | Signer | Receiver consent required | Description |
872
- |---|---|---|---|
873
- | 1 | Minter (issuer signer) | No | Issuer mints directly to `receiver`. Only the `minterSig` is validated. |
874
- | 2 | Minter + Receiver | Yes | Issuer mints on behalf of `receiver`, who has pre-signed a `ReceiverConsent`. Both signatures are validated. |
875
- | 3 | Minter + Relayer | No | Issuer authorizes a relayer to execute the mint-and-swap. The relayer submits the transaction. |
876
-
877
- In all paths, the `MintParams.minterSig` is a `MintRequest` EIP-712 signature. Path 2 additionally requires `MintParams.receiverSig`, which is a `ReceiverConsent` EIP-712 signature.
878
-
879
- ---
880
-
881
- ## Type Reference
882
-
883
- ### Core types
884
-
885
- ```ts
886
- interface MintRequest {
887
- to: Address;
888
- amount: bigint;
889
- nonce: bigint;
890
- deadline: bigint;
891
- }
892
-
893
- interface ReceiverConsent {
894
- onBehalfOf: Address;
895
- originalReceiver: Address;
896
- amount: bigint;
897
- nonce: bigint;
898
- deadline: bigint;
899
- }
900
-
901
- interface EIP712Signature {
902
- v: number;
903
- r: Hex;
904
- s: Hex;
905
- serialized: Hex;
906
- }
907
-
908
- interface SignatureVerification {
909
- isValid: boolean;
910
- recoveredAddress: Address;
911
- }
912
-
913
- interface PointTokenDomainConfig {
914
- name: string;
915
- verifyingContract: Address;
916
- chainId: number;
917
- }
918
- ```
919
-
920
- ### Relay types
921
-
922
- ```ts
923
- interface MintParams {
924
- pointToken: Address;
925
- receiver: Address;
926
- amount: bigint;
927
- deadline: bigint;
928
- minterSig: Hex;
929
- receiverSig: Hex;
930
- }
931
-
932
- interface PathKey {
933
- intermediateCurrency: Address;
934
- fee: number;
935
- tickSpacing: number;
936
- hooks: Address;
937
- hookData: Hex;
938
- }
939
-
940
- interface SwapParams {
941
- path: PathKey[];
942
- deadline: bigint;
943
- }
944
- ```
945
-
946
- ### Pool and issuer types
947
-
948
- ```ts
949
- interface PoolKey {
950
- currency0: Address;
951
- currency1: Address;
952
- fee: number;
953
- tickSpacing: number;
954
- hooks: Address;
955
- }
956
-
957
- interface Issuer {
958
- issuerAddress: Address;
959
- signerAddress: Address;
960
- name: string;
961
- symbol: string;
962
- declaredTotalSupply: bigint;
963
- capBasisPoints: number;
964
- active: boolean;
965
- pointToken: Address;
966
- mintingOracle: Address;
967
- }
968
- ```
969
-
970
- ### Quoting types
971
-
972
- ```ts
973
- interface QuoteResult {
974
- amountOut: bigint;
975
- gasEstimate: bigint;
976
- path: PathKey[];
977
- }
978
-
979
- interface BestQuote {
980
- bestRoute: QuoteResult;
981
- allRoutes: QuoteResult[];
982
- }
983
- ```
984
-
985
- ---
986
-
987
- ## Integration Guide
988
-
989
- ### Flow 1: Direct swap via UniversalRouter
990
-
991
- Use this when a user wants to swap tokens directly (not through the PAFI Relay).
992
122
 
993
- ```ts
994
- import { PafiSDK } from "@pafi-dev/core";
995
- import { UNIVERSAL_ROUTER_ADDRESSES } from "@pafi-dev/core";
996
- import { universalRouterAbi } from "@pafi-dev/core/abi";
123
+ const accountId = computeAccountId("0xUser", BROKER_HASHES["woofi_pro"]);
997
124
 
998
- const sdk = new PafiSDK({
125
+ const userOp = buildPerpDepositWithGasDeduction({
999
126
  chainId: 8453,
1000
- rpcUrl: "https://base-mainnet.g.alchemy.com/v2/...",
1001
- signer: walletClient,
1002
- });
1003
-
1004
- // 1. Quote — discovers all routes and picks the best via multicall
1005
- const { bestRoute, allRoutes } = await sdk.findBestQuote(
1006
- tokenIn, // e.g. WETH address
1007
- USDC, // output token
1008
- parseEther("1"), // input amount
1009
- myPools, // optional: your own pools (merged with COMMON_POOLS)
1010
- );
1011
-
1012
- console.log(`Best route: ${formatUnits(bestRoute.amountOut, 6)} USDC`);
1013
- console.log(`Found ${allRoutes.length} valid routes`);
1014
-
1015
- // 2. Build swap calldata — caller decides minAmountOut (slippage)
1016
- const minReceive = bestRoute.amountOut * 99n / 100n; // 1% slippage
1017
- const { commands, inputs } = sdk.buildSwapFromQuote({
1018
- quote: bestRoute,
1019
- currencyIn: tokenIn,
1020
- currencyOut: USDC,
1021
- amountIn: parseEther("1"),
1022
- minAmountOut: minReceive,
1023
- });
1024
-
1025
- // 3. Approve (one-time): token → Permit2 → UniversalRouter
1026
- // ERC20.approve(PERMIT2, MAX)
1027
- // Permit2.approve(token, UNIVERSAL_ROUTER, MAX, MAX_EXPIRY)
1028
-
1029
- // 4. Execute swap
1030
- const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800);
1031
- await walletClient.writeContract({
1032
- address: UNIVERSAL_ROUTER_ADDRESSES[8453],
1033
- abi: universalRouterAbi,
1034
- functionName: "execute",
1035
- args: [commands, inputs, deadline],
127
+ sender: "0xUser",
128
+ usdcAddress: "0xUSDC...",
129
+ vaultAddress: ORDERLY_VAULT_ADDRESSES[8453],
130
+ amount: 100n * 10n ** 6n,
131
+ accountId,
132
+ brokerHash: BROKER_HASHES["woofi_pro"],
133
+ tokenHash: TOKEN_HASHES["USDC"],
134
+ layerZeroFee: layerZeroFeeWei, // from Vault.getDepositFee()
135
+ aaNonce: 0n,
136
+ batchExecutorAddress: addrs.batchExecutor,
1036
137
  });
1037
138
  ```
1038
139
 
1039
- ### Flow 2: Mint-and-swap via Relay (the PAFI cash-out flow)
1040
-
1041
- The frontend signs, the issuer backend submits. This is the primary PAFI flow.
1042
-
1043
- #### Frontend (signs + sends to backend)
1044
-
1045
- The SDK builds the EIP-712 typed data object. You can sign it with any signer:
1046
- - **viem WalletClient** — use `sdk.signReceiverConsent()` directly
1047
- - **Privy / WalletConnect / external** — use `sdk.buildReceiverConsentTypedData()` to get the typed data, then pass it to the external signer's `signTypedData`
1048
-
1049
- ```ts
1050
- import { PafiSDK } from "@pafi-dev/core";
1051
-
1052
- const sdk = new PafiSDK({
1053
- chainId: 8453,
1054
- rpcUrl: "https://base-mainnet.g.alchemy.com/v2/...",
1055
- pointTokenAddress: "0xPointToken...",
1056
- // signer is optional — not needed if using Privy/external signer
1057
- });
1058
-
1059
- // 1. Quote
1060
- const { bestRoute } = await sdk.findBestQuote(
1061
- pointTokenAddress, USDC, amount, pointTokenPools,
1062
- );
1063
-
1064
- // 2. Set swap terms
1065
- const minReceive = bestRoute.amountOut * 99n / 100n;
1066
- const feeInUsdt = 1_000_000n; // 1 USDC relayer fee
1067
- const extData = sdk.encodeExtData(minReceive, feeInUsdt);
1068
-
1069
- // 3. Read nonce + build typed data
1070
- const nonce = await sdk.getReceiverConsentNonce(userAddress);
1071
- const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800);
1072
- const consentMessage = {
1073
- onBehalfOf: relayAddress,
1074
- originalReceiver: userAddress,
1075
- amount,
1076
- nonce,
1077
- deadline,
1078
- extData,
1079
- };
1080
-
1081
- // 4a. Sign with Privy (or any external signer)
1082
- const typedData = await sdk.buildReceiverConsentTypedData(consentMessage);
1083
- const receiverSig = await privy.signTypedData(typedData);
1084
- // receiverSig is a 0x-prefixed hex string (65 bytes)
1085
-
1086
- // 4b. Or sign with viem WalletClient (if signer is set on SDK)
1087
- // const { serialized: receiverSig } = await sdk.signReceiverConsent(consentMessage);
1088
-
1089
- // 5. Send to issuer backend (HTTP — not part of @pafi-dev/core)
1090
- await fetch(`${ISSUER_URL}/claim-and-swap`, {
1091
- method: "POST",
1092
- headers: { "Content-Type": "application/json" },
1093
- body: JSON.stringify({
1094
- receiver: userAddress,
1095
- amount: amount.toString(),
1096
- deadline: deadline.toString(),
1097
- receiverSig,
1098
- path: bestRoute.path,
1099
- minAmountOut: minReceive.toString(),
1100
- feeInUsdt: feeInUsdt.toString(),
1101
- }),
1102
- });
1103
- ```
1104
-
1105
- #### Issuer backend (signs + submits tx)
140
+ ---
1106
141
 
1107
- The backend can sign with a viem WalletClient, or use `buildMintRequestTypedData`
1108
- to get the typed data for an external signer (HSM/KMS).
142
+ ## SIWE login message
1109
143
 
1110
144
  ```ts
1111
- import { PafiSDK } from "@pafi-dev/core";
1112
- import { relayAbi } from "@pafi-dev/core/abi";
145
+ import { createLoginMessage } from "@pafi-dev/core";
1113
146
 
1114
- const sdk = new PafiSDK({
147
+ const message = createLoginMessage({
148
+ address: "0xUser",
149
+ domain: "app.gg56.com",
1115
150
  chainId: 8453,
1116
- rpcUrl: "...",
1117
- pointTokenAddress: "0xPointToken...",
1118
- // signer is optional — not needed if using HSM/KMS via buildMintRequestTypedData
1119
- });
1120
-
1121
- // 1. Issuer signs MintRequest
1122
- const mintNonce = await sdk.getMintRequestNonce(receiver);
1123
- const mintMessage = { to: receiver, amount, nonce: mintNonce, deadline };
1124
-
1125
- // Option A: Sign with viem WalletClient (set signer on SDK)
1126
- // const { serialized: minterSig } = await sdk.signMintRequest(mintMessage);
1127
-
1128
- // Option B: Build typed data for external signer (HSM/KMS/Privy)
1129
- const typedData = await sdk.buildMintRequestTypedData(mintMessage);
1130
- const minterSig = await externalSigner.signTypedData(typedData);
1131
-
1132
- // 2. Build Relay params from the frontend's quote path
1133
- const { swapParams, extData } = sdk.buildRelaySwapParams({
1134
- quote: { path: requestBody.path },
1135
- minAmountOut: BigInt(requestBody.minAmountOut),
1136
- feeInUsdt: BigInt(requestBody.feeInUsdt),
1137
- deadline: BigInt(requestBody.deadline),
1138
- });
1139
-
1140
- // 3. Submit to Relay contract
1141
- await relayerWallet.writeContract({
1142
- address: relayAddress,
1143
- abi: relayAbi,
1144
- functionName: "mintAndSwap",
1145
- args: [
1146
- {
1147
- pointToken: pointTokenAddress,
1148
- receiver,
1149
- amount,
1150
- deadline,
1151
- minterSig,
1152
- receiverSig: requestBody.receiverSig,
1153
- extData,
1154
- },
1155
- swapParams,
1156
- ],
151
+ nonce: "abc123", // from GET /auth/nonce
152
+ issuedAt: new Date().toISOString(),
153
+ uri: "https://app.gg56.com",
1157
154
  });
1158
155
  ```
1159
156
 
1160
157
  ---
1161
158
 
1162
- ## Development
159
+ ## Changelog
1163
160
 
1164
- This package is part of the `pafi-sdk` monorepo. To work on it in isolation:
1165
-
1166
- ```bash
1167
- cd packages/core
1168
- pnpm build # Compile TypeScript with tsup (ESM + CJS dual output)
1169
- pnpm test # Run vitest test suite
1170
- pnpm typecheck # Type-check without emitting (tsc --noEmit)
1171
- ```
1172
-
1173
- From the monorepo root:
1174
-
1175
- ```bash
1176
- pnpm build # Build all packages
1177
- pnpm test # Test all packages
1178
- pnpm typecheck # Type-check all packages
1179
- ```
1180
-
1181
- ### Output
1182
-
1183
- `tsup` produces dual ESM + CJS output under `dist/`. Each sub-path export (`./eip712`, `./relay`, etc.) has its own entry in the bundle with full type declarations.
1184
-
1185
- ---
161
+ ### 0.3.0-beta.10
162
+ - Removed `sponsor-auth/` module (PAFI-signed EIP-712 SponsorAuth — superseded by Pimlico proxy)
163
+ - All Base mainnet contract addresses live and verified against deployed contracts
1186
164
 
1187
- ## License
165
+ ### 0.3.0-beta.8/9
166
+ - Full 4-scenario UserOp builders (mint, burn, swap, perp_deposit)
167
+ - Added `SponsorAuth` signing helpers (removed in beta.10)
1188
168
 
1189
- UNLICENSED
169
+ ### 0.3.0-alpha.0
170
+ - Initial EIP-7702 BatchExecutor support, UserOp primitives