@pafi-dev/core 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.
Files changed (49) hide show
  1. package/README.md +165 -1084
  2. package/dist/auth/index.cjs +16 -2
  3. package/dist/auth/index.cjs.map +1 -1
  4. package/dist/auth/index.d.cts +93 -2
  5. package/dist/auth/index.d.ts +93 -2
  6. package/dist/auth/index.js +17 -3
  7. package/dist/{chunk-KJKDLD7N.cjs → chunk-2PY5RNVS.cjs} +11 -11
  8. package/dist/{chunk-KJKDLD7N.cjs.map → chunk-2PY5RNVS.cjs.map} +1 -1
  9. package/dist/{chunk-DMW67WBU.cjs → chunk-52SZJDFT.cjs} +6 -2
  10. package/dist/chunk-52SZJDFT.cjs.map +1 -0
  11. package/dist/{chunk-Z2V525IS.cjs → chunk-ARZSGP5Y.cjs} +6 -6
  12. package/dist/{chunk-Z2V525IS.cjs.map → chunk-ARZSGP5Y.cjs.map} +1 -1
  13. package/dist/{chunk-GWLEEXM4.cjs → chunk-FNJZUNK3.cjs} +90 -2
  14. package/dist/chunk-FNJZUNK3.cjs.map +1 -0
  15. package/dist/{chunk-I2P3TIP7.js → chunk-N5M3BLOV.js} +2 -2
  16. package/dist/{chunk-73CQTNLB.js → chunk-S5CCEO4O.js} +6 -2
  17. package/dist/{chunk-73CQTNLB.js.map → chunk-S5CCEO4O.js.map} +1 -1
  18. package/dist/{chunk-W23EJNYG.js → chunk-T6Y32SNK.js} +12 -2
  19. package/dist/chunk-T6Y32SNK.js.map +1 -0
  20. package/dist/{chunk-O4SMTUOY.js → chunk-W6VULMCO.js} +90 -2
  21. package/dist/chunk-W6VULMCO.js.map +1 -0
  22. package/dist/{chunk-DUY7Z2AI.js → chunk-WTWG6QXP.js} +2 -2
  23. package/dist/{chunk-EDR5SFJN.cjs → chunk-YU7MFR6Y.cjs} +12 -2
  24. package/dist/chunk-YU7MFR6Y.cjs.map +1 -0
  25. package/dist/eip712/index.cjs +3 -3
  26. package/dist/eip712/index.js +2 -2
  27. package/dist/{index-B7pGBych.d.cts → index-BEx-Q1bW.d.cts} +23 -40
  28. package/dist/{index-B06IJlHe.d.ts → index-r4xCdQO7.d.ts} +23 -40
  29. package/dist/index.cjs +178 -37
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +211 -26
  32. package/dist/index.d.ts +211 -26
  33. package/dist/index.js +176 -35
  34. package/dist/index.js.map +1 -1
  35. package/dist/quoting/index.cjs +3 -3
  36. package/dist/quoting/index.js +2 -2
  37. package/dist/swap/index.cjs +3 -2
  38. package/dist/swap/index.cjs.map +1 -1
  39. package/dist/swap/index.d.cts +1 -1
  40. package/dist/swap/index.d.ts +1 -1
  41. package/dist/swap/index.js +2 -1
  42. package/package.json +1 -1
  43. package/dist/chunk-DMW67WBU.cjs.map +0 -1
  44. package/dist/chunk-EDR5SFJN.cjs.map +0 -1
  45. package/dist/chunk-GWLEEXM4.cjs.map +0 -1
  46. package/dist/chunk-O4SMTUOY.js.map +0 -1
  47. package/dist/chunk-W23EJNYG.js.map +0 -1
  48. /package/dist/{chunk-I2P3TIP7.js.map → chunk-N5M3BLOV.js.map} +0 -0
  49. /package/dist/{chunk-DUY7Z2AI.js.map → chunk-WTWG6QXP.js.map} +0 -0
package/README.md CHANGED
@@ -1,1189 +1,270 @@
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.
7
-
8
- ## What's new in 0.3.0-alpha.0 — UserOp 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.
3
+ [![npm](https://img.shields.io/npm/v/@pafi-dev/core)](https://www.npmjs.com/package/@pafi-dev/core)
4
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
33
5
 
34
- ```bash
35
- pnpm add @pafi-dev/core@0.3.0-alpha.0
36
- ```
6
+ Core TypeScript SDK for the PAFI point token system. Covers EIP-712 signing, contract addresses + ABIs, ERC-4337 UserOp building, swap/perp calldata encoding, and on-chain quoting.
7
+
8
+ **No HTTP client.** Safe to use in both browser (frontend) and Node.js (issuer backend).
37
9
 
38
10
  ---
39
11
 
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`.
12
+ ## Requirements
46
13
 
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 |
14
+ - Node.js >= 18
15
+ - TypeScript >= 5.0
16
+ - `viem` ^2.0.0 (peer dependency)
17
+
18
+ ---
51
19
 
52
20
  ## Installation
53
21
 
54
22
  ```bash
23
+ npm install @pafi-dev/core viem
24
+ # or
55
25
  pnpm add @pafi-dev/core viem
56
26
  ```
57
27
 
58
- `viem ^2.0.0` is a peer dependency and must be installed alongside this package.
59
-
60
- ---
61
-
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
28
  ---
96
29
 
97
- ## Sub-path Imports
30
+ ## Modules
98
31
 
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 |
32
+ | Import path | What it provides |
102
33
  |---|---|
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 |
34
+ | `@pafi-dev/core` | All exports below |
35
+ | `@pafi-dev/core/eip712` | `signMintRequest`, `verifyMintRequest`, `signBurnRequest`, `signReceiverConsent` |
36
+ | `@pafi-dev/core/contract` | `getContractAddresses()`, ABIs, address constants |
37
+ | `@pafi-dev/core/swap` | `buildSwapWithGasDeduction()` PT→USDT via UniversalRouter |
38
+ | `@pafi-dev/core/quoting` | `findBestQuote()` on-chain V4 Quoter multicall |
39
+ | `@pafi-dev/core/auth` | `createLoginMessage()`, `signSponsorAuth()`, `verifySponsorAuth()` |
40
+ | `@pafi-dev/core/relay` | `buildDelegationUserOp()`, `getAaNonce()` |
111
41
 
112
42
  ---
113
43
 
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";
44
+ ## Contract addresses
124
45
 
125
- const sdk = new PafiSDK(config: PafiSDKConfig);
126
- ```
46
+ All deployed PAFI contract addresses live in one place, keyed by `chainId`:
127
47
 
128
48
  ```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
- ```
49
+ import { getContractAddresses, POINT_TOKEN_FACTORY_ADDRESSES } from "@pafi-dev/core";
138
50
 
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.
51
+ const addrs = getContractAddresses(8453); // Base mainnet
52
+ // addrs.pointToken "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e"
53
+ // addrs.issuerRegistry "0xda2D3338CF70F462Ac175F5f2edfa45660CA4f31"
54
+ // addrs.mintingOracle "0xD85165939C700E51c8a45099316C6482634C2Ab9"
55
+ // addrs.pafiHook "0x870cAF9882d3160602AaC1769C2B264A2d8EC044"
56
+ // addrs.usdt "0x5d313485Ba59C3bb91e1A9C0C11782F0b83d5dcd"
57
+ // addrs.batchExecutor "0x7702cb554e6bFb442cb743A7dF23154544a7176C"
140
58
 
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
59
+ POINT_TOKEN_FACTORY_ADDRESSES[8453] // "0x36c0BAb2faBE45EfA6d13001143e43A266Af673B"
148
60
  ```
149
61
 
150
- #### Domain
62
+ Protocol constants (same on all EVM chains):
151
63
 
152
64
  ```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>
65
+ import { ENTRY_POINT_V07, PERMIT2_ADDRESS } from "@pafi-dev/core";
66
+ // ENTRY_POINT_V07 "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
67
+ // PERMIT2_ADDRESS "0x000000000022D473030F116dDEE9F6B43aC78BA3"
156
68
  ```
157
69
 
158
70
  ---
159
71
 
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"`.
72
+ ## EIP-712 signing
186
73
 
187
- ##### `signMintRequest`
74
+ Used by the **issuer backend** to sign mint/burn authorization. The frontend
75
+ sends the signature alongside the UserOp to the Relay contract.
188
76
 
189
77
  ```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
- ```
262
-
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
- ```
78
+ import { signMintRequest, verifyMintRequest } from "@pafi-dev/core";
282
79
 
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
- ```
80
+ const domain = {
81
+ name: "GG56 Points", // must match PointToken.name() on-chain
82
+ chainId: 8453,
83
+ verifyingContract: addrs.pointToken,
84
+ };
311
85
 
312
- #### PafiSDK methods
86
+ const { signature } = await signMintRequest(issuerWalletClient, domain, {
87
+ to: userAddress,
88
+ amount: 500n * 10n ** 18n,
89
+ nonce: await getMintRequestNonce(publicClient, addrs.pointToken, userAddress),
90
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 900), // 15 min
91
+ });
313
92
 
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 }
93
+ // Offline verification (optional — Relay contract verifies on-chain)
94
+ const { valid } = await verifyMintRequest(domain, request, signature, expectedSigner);
319
95
  ```
320
96
 
321
97
  ---
322
98
 
323
- ### Contract Reads
99
+ ## SponsorAuth — paymaster authorization
324
100
 
325
- #### PointToken (from `@pafi-dev/core/contract`)
101
+ The issuer backend signs a `SponsorAuth` payload so the PAFI paymaster proxy can
102
+ verify the request is authorized before spending gas on behalf of the user.
326
103
 
327
104
  ```ts
328
105
  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>
106
+ signSponsorAuth,
107
+ verifySponsorAuth,
108
+ computeCallDataHash,
109
+ type SponsorAuthPayload,
110
+ } from "@pafi-dev/core";
391
111
 
392
- // Build UniversalRouter execute args from a quote
393
- sdk.buildSwapFromQuote({ quote, currencyIn, currencyOut, amountIn, minAmountOut }): { commands, inputs }
112
+ // Issuer backend: build + sign
113
+ const payload: SponsorAuthPayload = {
114
+ chainId: 8453,
115
+ sender: userAddress,
116
+ callDataHash: computeCallDataHash(userOp.callData),
117
+ nonce: BigInt(Date.now()) * 1_000_000n + BigInt(Math.floor(Math.random() * 1_000_000)),
118
+ expiresAt: Math.floor(Date.now() / 1000) + 600, // 10 min
119
+ scenario: "mint", // "mint" | "burn" | "swap"
120
+ issuerId: "gg56",
121
+ };
394
122
 
395
- // Build Relay SwapParams + extData from a quote
396
- sdk.buildRelaySwapParams({ quote, minAmountOut, feeInUsdt, deadline }): { swapParams, extData }
123
+ const sig = await signSponsorAuth(issuerWalletClient, payload);
397
124
 
398
- // Encode extData for ReceiverConsent signing
399
- sdk.encodeExtData(minAmountOut, feeInUsdt): Hex
125
+ // Paymaster proxy: verify before forwarding to Pimlico
126
+ const result = await verifySponsorAuth(payload, sig, expectedIssuerAddress);
127
+ // result.ok true/false
128
+ // result.reason "EXPIRED" | "INVALID_SIGNER" | "INVALID_SIGNATURE_FORMAT"
400
129
  ```
401
130
 
402
131
  ---
403
132
 
404
- ### Quoting
133
+ ## Swap calldata — PT → USDT
405
134
 
406
- #### Pure functions (from `@pafi-dev/core/quoting`)
135
+ Builds the `callData` for a `BatchExecutor.execute()` call that:
136
+ 1. Approves Permit2
137
+ 2. Sets a Permit2 allowance for UniversalRouter
138
+ 3. Swaps PT → USDT via UniversalRouter
139
+ 4. Optionally deducts a gas fee in PT
407
140
 
408
141
  ```ts
409
142
  import {
410
- quoteExactInput,
411
- quoteExactInputSingle,
412
- quoteBestRoute,
143
+ buildSwapWithGasDeduction,
413
144
  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`
434
-
435
- ```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.
447
-
448
- ##### `quoteBestRoute`
449
-
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.
145
+ getContractAddresses,
146
+ UNIVERSAL_ROUTER_ADDRESSES,
147
+ } from "@pafi-dev/core";
502
148
 
503
- #### Example
149
+ const addrs = getContractAddresses(8453);
504
150
 
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]
151
+ // 1. Quote (on-chain V4 Quoter multicall — read-only)
152
+ const quote = await findBestQuote(
153
+ publicClient,
154
+ 8453,
155
+ addrs.pointToken,
156
+ addrs.usdt,
157
+ 500n * 10n ** 18n,
158
+ pools, // PoolKey[] from issuer backend GET /pools
520
159
  );
521
160
 
522
- console.log(bestRoute.amountOut, bestRoute.gasEstimate);
523
- console.log(`Found ${allRoutes.length} valid routes`);
161
+ // 2. Build unsigned UserOp
162
+ const userOp = buildSwapWithGasDeduction({
163
+ chainId: 8453,
164
+ sender: userAddress,
165
+ pointTokenAddress: addrs.pointToken,
166
+ usdtAddress: addrs.usdt,
167
+ universalRouterAddress: UNIVERSAL_ROUTER_ADDRESSES[8453],
168
+ pafiHookAddress: addrs.pafiHook,
169
+ quote,
170
+ slippageBps: 50,
171
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
172
+ aaNonce: await getAaNonce(publicClient, userAddress),
173
+ batchExecutorAddress: addrs.batchExecutor,
174
+ gasFeePt: 0n, // set > 0 to deduct operator fee in PT
175
+ });
524
176
  ```
525
177
 
526
178
  ---
527
179
 
528
- ### Swap Building
180
+ ## EIP-7702 delegation
529
181
 
530
- #### Pure functions (from `@pafi-dev/core/swap`)
182
+ One-time setup: sponsors the EIP-7702 delegation so the user's EOA gains
183
+ `BatchExecutor` capabilities without spending native ETH.
531
184
 
532
185
  ```ts
533
186
  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`
577
-
578
- ```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`.
589
-
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.
187
+ buildDelegationUserOp,
188
+ getAaNonce,
189
+ getContractAddresses,
190
+ BATCH_EXECUTOR_ADDRESS_BASE_MAINNET,
191
+ } from "@pafi-dev/core";
617
192
 
618
- #### Example
193
+ const aaNonce = await getAaNonce(publicClient, userAddress);
619
194
 
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);
626
-
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,
195
+ const userOp = buildDelegationUserOp({
196
+ userAddress,
197
+ aaNonce,
635
198
  });
636
199
 
637
- // 3. Execute via UniversalRouter
200
+ // Then: sign the EIP-7702 authorization off-chain via Privy
201
+ // const authorization = await signAuthorization({ contractAddress: BATCH_EXECUTOR_ADDRESS_BASE_MAINNET, ... });
202
+ // await smartClient.sendTransaction({ to: userAddress, data: "0x", authorization });
638
203
  ```
639
204
 
640
205
  ---
641
206
 
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
207
+ ## SIWE login message
683
208
 
684
209
  ```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
- ```
210
+ import { createLoginMessage } from "@pafi-dev/core";
691
211
 
692
- The SDK does not post the signed message for you — the frontend calls
693
- its own `fetch()` against the issuer backend:
694
-
695
- ```ts
696
- // 1. Fetch nonce from the issuer backend
697
- const { nonce } = await fetch(`${ISSUER_URL}/auth/nonce`).then(r => r.json());
698
-
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,
212
+ const message = createLoginMessage({
213
+ address: userAddress,
214
+ domain: "app.gg56.com",
215
+ chainId: 8453,
216
+ nonce: nonceFromServer, // GET /auth/nonce
217
+ issuedAt: new Date().toISOString(),
218
+ uri: "https://app.gg56.com",
704
219
  });
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
220
 
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";
221
+ // User signs with wallet, then POST /auth/login { message, signature }
745
222
  ```
746
223
 
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
224
  ---
761
225
 
762
- ### Constants
226
+ ## Perp deposit calldata — USDC → Orderly
763
227
 
764
228
  ```ts
765
229
  import {
766
- SUPPORTED_CHAINS,
767
- COMMON_TOKENS,
768
- COMMON_POOLS,
769
- POINT_TOKEN_POOLS,
770
- mintRequestTypes,
771
- receiverConsentTypes,
230
+ buildPerpDepositWithGasDeduction,
231
+ computeAccountId,
232
+ BROKER_HASHES,
233
+ TOKEN_HASHES,
234
+ ORDERLY_VAULT_ADDRESSES,
772
235
  } 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
236
 
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 |
237
+ const accountId = computeAccountId(userAddress, BROKER_HASHES["woofi_pro"]);
862
238
 
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
-
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";
997
-
998
- const sdk = new PafiSDK({
999
- 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],
1036
- });
1037
- ```
1038
-
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)
1106
-
1107
- The backend can sign with a viem WalletClient, or use `buildMintRequestTypedData`
1108
- to get the typed data for an external signer (HSM/KMS).
1109
-
1110
- ```ts
1111
- import { PafiSDK } from "@pafi-dev/core";
1112
- import { relayAbi } from "@pafi-dev/core/abi";
1113
-
1114
- const sdk = new PafiSDK({
239
+ const userOp = buildPerpDepositWithGasDeduction({
1115
240
  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
- ],
241
+ sender: userAddress,
242
+ usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
243
+ vaultAddress: ORDERLY_VAULT_ADDRESSES[8453],
244
+ amount: 100n * 10n ** 6n,
245
+ accountId,
246
+ brokerHash: BROKER_HASHES["woofi_pro"],
247
+ tokenHash: TOKEN_HASHES["USDC"],
248
+ layerZeroFee: await vault.getDepositFee(),
249
+ aaNonce: await getAaNonce(publicClient, userAddress),
250
+ batchExecutorAddress: addrs.batchExecutor,
1157
251
  });
1158
252
  ```
1159
253
 
1160
254
  ---
1161
255
 
1162
- ## Development
256
+ ## Changelog
1163
257
 
1164
- This package is part of the `pafi-sdk` monorepo. To work on it in isolation:
258
+ ### 0.4.0
259
+ - `sponsorAuth` module added — `signSponsorAuth`, `verifySponsorAuth`, `computeCallDataHash`
260
+ - `PERMIT2_ADDRESS` and `ENTRY_POINT_V07` centralised in `constants.ts`
261
+ - `BATCH_EXECUTOR_ADDRESS_BASE_*` derived from `addresses.ts` — no duplicate hardcoding
1165
262
 
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
- ---
263
+ ### 0.3.0-beta.10
264
+ - All Base mainnet contract addresses live and verified against deployed contracts
1186
265
 
1187
- ## License
266
+ ### 0.3.0-beta.8/9
267
+ - Full 4-scenario UserOp builders (mint, burn, swap, perp_deposit)
1188
268
 
1189
- UNLICENSED
269
+ ### 0.3.0-alpha.0
270
+ - Initial EIP-7702 BatchExecutor support, UserOp primitives