@pafi-dev/trading 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +132 -378
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -3,462 +3,216 @@
3
3
  [![npm](https://img.shields.io/npm/v/@pafi-dev/trading)](https://www.npmjs.com/package/@pafi-dev/trading)
4
4
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
5
 
6
- Stateless on-chain trading handlers for PAFI swap PT USDT and deposit USDC into Orderly perp.
6
+ On-chain trading for PAFI: V4 swap quote + UserOp build, Orderly
7
+ perp deposit. **Direction-agnostic** since 0.2.0 — quote and swap any
8
+ ERC-20 → ERC-20 routable through PAFI's V4 pools.
7
9
 
8
- All handlers are purely on-chain (Uniswap V4 Quoter + UniversalRouter + Orderly Vault). No
9
- ledger, no signer, no database. Safe to use in any backend (NestJS, Express, Hono) or
10
- client-side (React, React Native).
10
+ **Browser + Node-safe.** Stateless (no DB, no signer, no auth). Peer-deps:
11
+ `viem ^2`. Plus `@pafi-dev/core` for primitives.
11
12
 
12
13
  ---
13
14
 
14
- ## Requirements
15
+ ## Why this exists
15
16
 
16
- - Node.js >= 18 (server) or any modern bundler (client)
17
- - TypeScript >= 5.0
18
- - `viem` ^2.0.0 and `@pafi-dev/core` ^0.5.17 (peer dependencies)
17
+ Issuer backends (`@pafi-dev/issuer`) cover the issuer-signed flows
18
+ (claim, redeem). Trading is the FE-callable surface for actions that
19
+ don't need an issuer signature:
19
20
 
20
- > **Latest:** `0.1.9``handleSwap` / `handlePerpDeposit` now
21
- > auto-quote the operator fee and auto-resolve the PAFI fee recipient.
22
- > Caller no longer pre-fetches anything from the issuer backend. See
23
- > [Changelog](#changelog).
21
+ - **Quote** PT → USDT, USDT → PT, PT0 → PT1 pure on-chain V4 Quoter
22
+ read, multicall'd.
23
+ - **Build swap UserOp** with operator gas-reimbursement fee in the
24
+ input token (auto-quoted via Chainlink). Sponsored + fallback variants.
25
+ - **Build perp deposit UserOp** via the PAFI Orderly Relay (Relay
26
+ covers LayerZero `msg.value`, user pays USDC fee).
27
+
28
+ Stateless and HTTP-free — issuer backends or FE apps that want a
29
+ server-canonical quote/swap can wrap `TradingHandlers` directly.
24
30
 
25
31
  ---
26
32
 
27
33
  ## Installation
28
34
 
29
35
  ```bash
30
- npm install @pafi-dev/trading @pafi-dev/core viem
31
- # or
32
36
  pnpm add @pafi-dev/trading @pafi-dev/core viem
33
37
  ```
34
38
 
35
39
  ---
36
40
 
37
- ## Quick start
41
+ ## Quick start (FE)
38
42
 
39
43
  ```ts
40
44
  import { createPublicClient, http } from "viem";
41
- import { base } from "viem/chains";
42
- import { TradingHandlers } from "@pafi-dev/trading";
45
+ import {
46
+ TradingHandlers,
47
+ fetchPafiPools,
48
+ } from "@pafi-dev/trading";
49
+ import { getContractAddresses } from "@pafi-dev/core";
43
50
 
44
- const provider = createPublicClient({ chain: base, transport: http(RPC_URL) });
51
+ const provider = createPublicClient({ transport: http("https://mainnet.base.org") });
45
52
  const trading = new TradingHandlers({ provider, chainId: 8453 });
46
- ```
47
-
48
- ---
49
-
50
- ## Handlers
51
-
52
- ### `handleQuote` — GET /quote
53
+ const { usdt } = getContractAddresses(8453);
53
54
 
54
- Quote exact-input PT USDT via Uniswap V4 on-chain Quoter. Uses multicall to batch all
55
- candidate routes into a single RPC call.
55
+ // Pool discovery loop call when both sides are PT (PT0 PT1 multi-hop).
56
+ const pools = await fetchPafiPools(8453, POINT_TOKEN);
56
57
 
57
- ```ts
58
- const quote = await trading.handleQuote({
58
+ // Quote — direction-agnostic.
59
+ const q = await trading.handleQuote({
59
60
  chainId: 8453,
60
- pointTokenAddress: "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e",
61
- amount: 1000n * 10n ** 18n, // 1000 PT
62
- pools: poolsFromSubgraph, // optional combined with COMMON_POOLS
61
+ inputTokenAddress: POINT_TOKEN,
62
+ outputTokenAddress: usdt,
63
+ amount: 100n * 10n ** 18n, // 100 PT
64
+ pools,
63
65
  });
66
+ // q.estimatedOutputAmount — bigint USDT raw (6 dec)
64
67
 
65
- // quote.estimatedUsdtOut raw USDT (6 decimals)
66
- // quote.gasEstimate — estimated gas units
67
- // quote.quoteError — "QUOTE_UNAVAILABLE" | "AMOUNT_TOO_SMALL_FOR_GAS" | undefined
68
- ```
69
-
70
- Returns `quoteError` instead of throwing when no pool/path is found, so the caller can
71
- show a soft "unavailable" UI state without 500-ing.
72
-
73
- #### Frontend "quote review" pattern
74
-
75
- Quotes are pure on-chain reads (Uniswap V4 Quoter via Base RPC) — **no
76
- auth, no issuer-side state, nothing to proxy through the issuer
77
- backend.** Call `handleQuote` directly from the browser / React Native.
78
-
79
- ```tsx
80
- import { TradingHandlers, fetchPafiPools } from "@pafi-dev/trading";
81
- import { createPublicClient, http, formatUnits } from "viem";
82
- import { base } from "viem/chains";
83
-
84
- const trading = new TradingHandlers({
85
- provider: createPublicClient({ chain: base, transport: http(RPC_URL) }),
68
+ // Swap builds UserOp ready to submit via Bundler + EIP-7702.
69
+ const swap = await trading.handleSwap({
86
70
  chainId: 8453,
71
+ userAddress,
72
+ inputTokenAddress: POINT_TOKEN,
73
+ outputTokenAddress: usdt,
74
+ amount: 100n * 10n ** 18n,
75
+ aaNonce,
76
+ pools,
77
+ // gasFeeAmount auto-quotes when input is a PT; pass explicit bigint to override.
78
+ // slippageBps auto-picks 50 bps single-hop / 100 bps multi-hop.
87
79
  });
88
-
89
- useEffect(() => {
90
- if (amount <= 0n) return setQuote(null);
91
- const t = setTimeout(async () => {
92
- const pools = await fetchPafiPools(8453, POINT_TOKEN);
93
- const result = await trading.handleQuote({
94
- chainId: 8453,
95
- pointTokenAddress: POINT_TOKEN,
96
- amount,
97
- pools,
98
- });
99
- setQuote(result);
100
- }, 400);
101
- return () => clearTimeout(t);
102
- }, [amount]);
103
-
104
- // Render:
105
- // In: formatUnits(amount, 18) PT
106
- // You receive: formatUnits(quote.estimatedUsdtOut, 6) USDT
107
- // Rate: estimatedUsdtOut · 1e18 / amount / 1e6 USDT per 1 PT
108
- // On `quote.quoteError === "QUOTE_UNAVAILABLE"` → hide the Swap CTA.
80
+ // swap.userOp + swap.userOpFallback (when fee > 0)
81
+ // swap.hops — number of hops in chosen route
109
82
  ```
110
83
 
111
- Why client-direct (no `GET /quote` HTTP hop):
112
-
113
- - The quoter is stateless and reads from the public RPC — there's no
114
- secret to hide and nothing to validate against an issuer's database.
115
- - One fewer round-trip on every keystroke (debounce hits the Quoter
116
- directly, ~150 ms vs. ~400 ms via a backend proxy).
117
- - The quote stays internally consistent with the swap path that
118
- `handleSwap` will pick — both use the same `findBestQuote()` call
119
- under the hood with the same `pools` array, so what the user sees in
120
- the review card is what they get on submit.
121
-
122
- > **Operator fee in USDT** is **NOT** part of the quote — it's an
123
- > issuer-policy concern decided at swap-build time. If you want a
124
- > "net out after fee" preview, fetch the issuer's gas-fee endpoint
125
- > separately and subtract on the client.
126
-
127
- Reference implementation lives in
128
- [`privy-pimlico-eip7702-example/app/components/WalletPanel.tsx`](https://github.com/pacific-finance/pafi-backend/blob/main/privy-pimlico-eip7702-example/app/components/WalletPanel.tsx)
129
- (search for `Quote review`).
130
-
131
84
  ---
132
85
 
133
- ### `handleSwap` — POST /swap
134
-
135
- Quote the best route, apply slippage, and build an unsigned `PartialUserOperation` that
136
- batches: `PT.approve` → `Permit2.approve` → `UniversalRouter.execute` (→ `PT.transfer` fee).
86
+ ## Direction matrix
137
87
 
138
- ```ts
139
- const swap = await trading.handleSwap({
140
- chainId: 8453,
141
- userAddress: "0xUserEOA",
142
- pointTokenAddress: "0x7d25E7156E51F865D522fd3ef257a6B5DD41b97e",
143
- amount: 1000n * 10n ** 18n,
144
- aaNonce: 0n, // from EntryPoint.getNonce(user, 0)
145
- slippageBps: 50, // 0.5% — default
146
- pools: poolsFromSubgraph,
147
- // optional gas fee deduction in PT:
148
- // gasFeePt: 5n * 10n ** 18n,
149
- // feeRecipient: "0xOperatorAddress",
150
- });
88
+ | Direction | Use case | Hop count | Hook fee | Operator fee token |
89
+ | --- | --- | --- | --- | --- |
90
+ | `PT → USDT` | Cashout | 1 | 10% on this leg | PT |
91
+ | `USDT → PT` | Buy PT | 1 | 0% | USDT |
92
+ | `PT0 → PT1` | Same-issuer multi-token swap | 2 (via USDT) | 10% on PT0 leg only | PT0 |
151
93
 
152
- // swap.userOp unsigned PartialUserOperation
153
- // swap.estimatedUsdtOut — raw USDT before slippage
154
- // swap.minAmountOut — encoded in UserOp (revert if less)
155
- // swap.deadline — unix seconds (now + 5 min)
156
- ```
94
+ **Same-issuer only** for PT0 → PT1 caller is responsible for
95
+ ensuring both PTs belong to the same issuer. SDK doesn't enforce.
157
96
 
158
- After receiving the response, the frontend:
159
- 1. Requests paymaster sponsorship from `POST /pimlico` (sponsor-relayer, Privy auth)
160
- 2. Signs the UserOp hash via Privy
161
- 3. Submits to the Bundler
97
+ The PAFI V4 hook charges 10% on PT → USDT direction at pool level
98
+ (applied inside `UniversalRouter.execute`). Reflected in
99
+ `estimatedOutputAmount` from `findBestQuote`.
162
100
 
163
101
  ---
164
102
 
165
- ### `handlePerpDeposit` POST /perp-deposit
103
+ ## Operator fee strategy
166
104
 
167
- Build an unsigned UserOp that deposits USDC into Orderly perp. The
168
- handler picks one of two paths automatically:
105
+ The operator gas-reimbursement fee is charged in the **input token**
106
+ (0.2.0+). Single approve covers `amountIn + gasFeeAmount`. User only
107
+ needs to hold a single token.
169
108
 
170
- #### Path 1 Relay (default, gas-sponsored)
109
+ | Input | Auto-quote source | Caller needs to override? |
110
+ | --- | --- | --- |
111
+ | PT | `quoteOperatorFeePt` (Chainlink + V4 subgraph) | No |
112
+ | USDT | falls back to 0 — pass `gasFeeAmount` explicitly | Yes (use `quoteOperatorFeeUsdt`) |
171
113
 
172
- `USDC.approve(relay)` → `relay.deposit(req)`
114
+ ---
173
115
 
174
- The PAFI Orderly Relay holds an ETH reserve and pays Orderly's
175
- LayerZero `msg.value` out of it. The user pays a USDC fee instead
176
- (quoted via `Relay.quoteTokenFee` and capped by `maxRelayFee`).
177
- Combined with paymaster sponsorship of ERC-4337 gas, **the user does
178
- not need any native ETH** — they only need USDC.
116
+ ## Multi-hop PT PT1
179
117
 
180
118
  ```ts
181
- const deposit = await trading.handlePerpDeposit({
182
- chainId: 8453,
183
- userAddress: "0xUserEOA",
184
- amount: 100_000_000n, // 100 USDC (6 decimals)
185
- aaNonce: 0n,
186
- brokerId: "woofi_pro", // "woofi_pro" | "orderly" | "logx"
187
- // viaRelay: true, // default
188
- // maxRelayFee: 5_000_000n, // optional, default = 5% of amount
189
- });
190
-
191
- // deposit.path === "relay"
192
- // deposit.userOp — USDC.approve(relay) + relay.deposit(req)
193
- // deposit.relayTokenFee — USDC fee charged by the Relay
194
- // deposit.layerZeroFee — ETH wei the Relay covers on the user's behalf (informational)
195
- // deposit.relayAddress — getContractAddresses(chainId).orderlyRelay
196
- ```
119
+ import { fetchPafiPools, TradingHandlers } from "@pafi-dev/trading";
197
120
 
198
- #### Path 2Vault (fallback, requires native ETH)
121
+ // Loop callfetch pools for both PTs, merge.
122
+ const [poolsA, poolsB] = await Promise.all([
123
+ fetchPafiPools(8453, POINT_TOKEN_0),
124
+ fetchPafiPools(8453, POINT_TOKEN_1),
125
+ ]);
126
+ const pools = [...poolsA, ...poolsB];
199
127
 
200
- `USDC.approve(vault)` `vault.deposit{value: layerZeroFee}(data)`
201
-
202
- ```ts
203
- const deposit = await trading.handlePerpDeposit({
128
+ const swap = await trading.handleSwap({
204
129
  chainId: 8453,
205
- userAddress: "0xUserEOA",
206
- amount: 100_000_000n,
207
- aaNonce: 0n,
208
- brokerId: "woofi_pro",
209
- viaRelay: false, // explicit opt-out of Relay
130
+ userAddress,
131
+ inputTokenAddress: POINT_TOKEN_0,
132
+ outputTokenAddress: POINT_TOKEN_1,
133
+ amount: 100n * 10n ** 18n,
134
+ aaNonce,
135
+ pools,
210
136
  });
211
137
 
212
- // deposit.path === "vault"
213
- // deposit.userOp — unsigned PartialUserOperation
214
- // deposit.layerZeroFee — REQUIRED native ETH balance on the sender
215
- // deposit.relayTokenFee — 0n
138
+ console.log(swap.hops); // 2 went through USDT
216
139
  ```
217
140
 
218
- > **Vault path = native ETH required.** The paymaster sponsors ERC-4337
219
- > gas, but `msg.value` (the LayerZero fee) must come from the user's
220
- > own ETH balance. Always check `walletEth >= layerZeroFee` before
221
- > submitting, OR — far better — leave `viaRelay` at its default to
222
- > stay on the zero-ETH path.
223
-
224
- #### Auto-fallback
225
-
226
- When `viaRelay` is `true` (default) but
227
- `getContractAddresses(chainId).orderlyRelay` is the placeholder
228
- sentinel (Relay not deployed for that chain), the handler silently
229
- falls back to the Vault path. Inspect `deposit.path` if you need to
230
- distinguish.
141
+ V4 router auto-picks the best route across `pools + COMMON_POOLS`
142
+ (default `maxHops=3`). Slippage auto-bumps to 100 bps for multi-hop.
231
143
 
232
144
  ---
233
145
 
234
- ## Wiring into a NestJS controller
146
+ ## Perp deposit
235
147
 
236
148
  ```ts
237
- import { Controller, Get, Post, Body, Query } from "@nestjs/common";
238
- import { TradingHandlers } from "@pafi-dev/trading";
239
-
240
- @Controller()
241
- export class TradingController {
242
- constructor(private readonly trading: TradingHandlers) {}
243
-
244
- @Get("quote")
245
- async quote(@Query() q: QuoteQueryDto) {
246
- return this.trading.handleQuote({
247
- chainId: q.chainId,
248
- pointTokenAddress: q.pointToken,
249
- amount: BigInt(q.amount),
250
- });
251
- }
252
-
253
- @Post("swap")
254
- async swap(@Body() body: SwapBodyDto) {
255
- return this.trading.handleSwap({
256
- chainId: body.chainId,
257
- userAddress: body.userAddress,
258
- pointTokenAddress: body.pointTokenAddress,
259
- amount: BigInt(body.amount),
260
- aaNonce: BigInt(body.aaNonce),
261
- slippageBps: body.slippageBps,
262
- });
263
- }
264
-
265
- @Post("perp-deposit")
266
- async perpDeposit(@Body() body: PerpDepositBodyDto) {
267
- return this.trading.handlePerpDeposit({
268
- chainId: body.chainId,
269
- userAddress: body.userAddress,
270
- amount: BigInt(body.amount),
271
- aaNonce: BigInt(body.aaNonce),
272
- brokerId: body.brokerId,
273
- });
274
- }
275
- }
276
- ```
277
-
278
- Register `TradingHandlers` as a provider:
149
+ const deposit = await trading.handlePerpDeposit({
150
+ chainId: 8453,
151
+ userAddress,
152
+ amount: 1_000_000n, // 1 USDC (6 dec)
153
+ aaNonce,
154
+ brokerId: "orderly", // "orderly" | "woofi_pro" | "logx"
155
+ // viaRelay: true (default) — uses PAFI Orderly Relay (no msg.value needed)
156
+ pointTokenAddress: POINT_TOKEN, // optional — for PT operator fee
157
+ });
279
158
 
280
- ```ts
281
- import { createPublicClient, http } from "viem";
282
- import { base } from "viem/chains";
283
- import { TradingHandlers } from "@pafi-dev/trading";
284
-
285
- @Module({
286
- providers: [
287
- {
288
- provide: TradingHandlers,
289
- useFactory: (config: ConfigService) =>
290
- new TradingHandlers({
291
- provider: createPublicClient({
292
- chain: base,
293
- transport: http(config.get("RPC_URL")),
294
- }),
295
- chainId: config.get<number>("CHAIN_ID"),
296
- }),
297
- inject: [ConfigService],
298
- },
299
- TradingController,
300
- ],
301
- })
302
- export class TradingModule {}
159
+ // deposit.userOp — submit via Bundler + Paymaster
160
+ // deposit.path "relay" (preferred) or "vault" (direct, requires native ETH)
303
161
  ```
304
162
 
163
+ The PAFI Relay covers LayerZero `msg.value` from its ETH reserve and
164
+ charges a USDC fee (`Relay.quoteTokenFee`). User wallet doesn't need
165
+ native ETH.
166
+
305
167
  ---
306
168
 
307
- ## API types
169
+ ## Standalone primitives
308
170
 
309
- All request/response types are exported for use in frontend SDKs or OpenAPI generation:
171
+ For callers that want to compose flows beyond what `TradingHandlers`
172
+ provides:
310
173
 
311
174
  ```ts
312
- import type {
313
- ApiQuoteRequest,
314
- ApiQuoteResponse,
315
- ApiQuoteError,
316
- ApiSwapRequest,
317
- ApiSwapResponse,
318
- ApiPerpDepositRequest,
319
- ApiPerpDepositResponse,
175
+ import {
176
+ buildSwapUserOp,
177
+ findBestQuote,
178
+ simulateSwap,
179
+ buildUniversalRouterExecuteArgs,
180
+ buildPermit2ApprovalCalldata,
181
+ fetchPafiPools,
320
182
  } from "@pafi-dev/trading";
321
183
  ```
322
184
 
323
- Since `bigint` does not serialize to JSON natively, HTTP controllers should convert
324
- `bigint` fields to strings in the response (e.g. `.toString()`).
185
+ Use these to build custom direction-agnostic swap flows or pre-flight
186
+ simulations.
325
187
 
326
188
  ---
327
189
 
328
- ## Relationship to other PAFI packages
190
+ ## API reference
329
191
 
330
- | Package | Scope |
331
- |---|---|
332
- | `@pafi-dev/core` | Chain primitives — EIP-712, UserOp builders, on-chain reads, ABIs |
333
- | `@pafi-dev/issuer` | Issuer backend — mint/redeem auth, ledger, policy, indexer |
334
- | `@pafi-dev/trading` | On-chain trading — swap quote + UserOp, perp deposit UserOp |
335
-
336
- `@pafi-dev/trading` depends on `@pafi-dev/core` for `findBestQuote`,
337
- `buildSwapWithGasDeduction`, `buildPerpDepositWithGasDeduction`, and contract addresses.
338
- It does not depend on `@pafi-dev/issuer`.
339
-
340
- ---
341
-
342
- ## Changelog
343
-
344
- ### 0.1.9
345
-
346
- `handleSwap` and `handlePerpDeposit` now **auto-quote the operator fee
347
- + auto-resolve the PAFI fee recipient**. Caller no longer needs to
348
- pre-fetch anything from the issuer backend.
349
-
350
- **API changes** (additive — passing the old explicit values still works
351
- as overrides):
352
-
353
- - `ApiSwapRequest.gasFeePt` — semantics changed:
354
- - `undefined` (default): handler runs `quoteOperatorFeePt` (Chainlink
355
- + V4 subgraph) to compute the PT fee.
356
- - `0n`: strips the `PT.transfer(...)` from the batch (unsponsored
357
- fallback).
358
- - explicit bigint: override (issuer markup / subsidy).
359
- - `ApiSwapRequest.feeRecipient` — **REMOVED**. Hardcoded to
360
- `getContractAddresses(chainId).pafiFeeRecipient`. Sponsor-relayer's
361
- L1 gate would reject any other recipient anyway, so the option was
362
- removed to keep the API honest.
363
- - `ApiPerpDepositRequest.gasFeePtRecipient` — **REMOVED** (same
364
- reason). `pointTokenAddress` still required when fee > 0 (handler
365
- needs the token to call `transfer` against).
366
- - `ApiSwapResponse.feeAmountUsed` / `.feeRecipient` — **NEW**. Echo
367
- what the handler actually embedded so the FE can display it without
368
- re-quoting.
369
- - `ApiPerpDepositResponse.feeAmountUsed` / `.feeRecipient` — same.
370
-
371
- **Migration**:
372
-
373
- ```diff
374
- - const gasFeePt = await quoteOperatorFeePt({...});
375
- - const result = await trading.handleSwap({
376
- - ..., gasFeePt, feeRecipient: PAFI_FEE_RECIPIENT,
377
- - });
378
- + const result = await trading.handleSwap({...});
379
- + console.log(`Fee charged: ${result.feeAmountUsed} → ${result.feeRecipient}`);
380
- ```
381
-
382
- Force unsponsored / fallback (no fee transfer):
192
+ ### `TradingHandlers`
383
193
 
384
194
  ```ts
385
- const result = await trading.handleSwap({ ..., gasFeePt: 0n });
195
+ new TradingHandlers({ provider: PublicClient, chainId: number })
386
196
  ```
387
197
 
388
- Fee policy override (rare):
198
+ Methods:
389
199
 
390
- ```ts
391
- const result = await trading.handleSwap({ ..., gasFeePt: customAmount });
392
- ```
200
+ | Method | Purpose |
201
+ | --- | --- |
202
+ | `handleQuote({ inputTokenAddress, outputTokenAddress, amount, pools? })` | V4 quote. Returns `{ inputAmount, estimatedOutputAmount, gasEstimate, quoteError? }` |
203
+ | `handleSwap({ userAddress, inputTokenAddress, outputTokenAddress, amount, aaNonce, pools?, gasFeeAmount?, slippageBps? })` | Build UserOp. Returns `{ userOp, userOpFallback?, estimatedOutputAmount, minAmountOut, hops, deadline, feeAmountUsed, feeRecipient }` |
204
+ | `handlePerpDeposit({ userAddress, amount, aaNonce, brokerId, viaRelay?, maxRelayFee?, pointTokenAddress?, gasFeePt? })` | Build UserOp. Returns Orderly deposit response |
205
+
206
+ ### Free functions
393
207
 
394
- ### 0.1.5
395
-
396
- `handlePerpDeposit` now defaults to the **PAFI Orderly Relay** path —
397
- zero native ETH required.
398
-
399
- **Why.** The previous Vault-direct path required the user wallet to
400
- hold `layerZeroFee` as native ETH (paymaster sponsors ERC-4337 gas
401
- but never `msg.value`). For users coming through Privy embedded
402
- wallets / sponsored onboarding flows that's often `0 ETH`, so the
403
- deposit reverted with `BatchExecutor.CallFailed(1, 0x)` (no-data
404
- revert from the Vault when it can't pay LayerZero out of the EOA).
405
-
406
- The Relay (deployed at `getContractAddresses(8453).orderlyRelay
407
- =` `0xDA082DAce1522c185aeB5A713FcA6fa6B6E99e7f` on Base) holds an ETH
408
- reserve and pays LayerZero out of it; the user pays a USDC fee
409
- instead. Combined with paymaster sponsorship, the user only needs
410
- USDC.
411
-
412
- **API additions** on `ApiPerpDepositRequest`:
413
-
414
- - `viaRelay?: boolean` — default `true`. Set `false` to force the
415
- Vault fallback.
416
- - `maxRelayFee?: bigint` — slippage cap on the Relay's USDC fee.
417
- Defaults to 5% of `amount`.
418
- - `pointTokenAddress?` / `gasFeePt?` / `gasFeePtRecipient?` — optional
419
- PT gas-fee transfer prepended to the batch (sponsored issuer flow).
420
-
421
- **API additions** on `ApiPerpDepositResponse`:
422
-
423
- - `path: "relay" | "vault"` — which execution path the handler chose.
424
- Inspect this when `viaRelay` was left at its default to know if the
425
- auto-fallback to Vault triggered (e.g. on a chain without a deployed
426
- Relay).
427
- - `relayTokenFee: bigint` — USDC fee the Relay will charge. `0n` when
428
- `path === "vault"`.
429
- - `relayAddress: Address` — the address the UserOp targets. Equal to
430
- the Vault address when `path === "vault"`.
431
-
432
- When `viaRelay` is `true` but no Relay is deployed for the chain
433
- (`getContractAddresses(chainId).orderlyRelay` is a placeholder), the
434
- handler silently falls back to the Vault path — inspect
435
- `response.path` to detect this.
436
-
437
- ### 0.1.4
438
-
439
- - README rewrite for the **frontend "quote review" pattern** — direct
440
- client-side `trading.handleQuote` call, no HTTP hop through the
441
- issuer backend. Quotes are stateless on-chain reads, so the
442
- pre-existing `GET /quote` proxy was redundant.
443
-
444
- ### 0.1.3
445
-
446
- - Peer dependency `@pafi-dev/core` bumped to `0.5.17` so the swap path
447
- picks up `buildUserOpTypedData()` + the v0.8 EIP-712 `computeUserOpHash`.
448
- Required for the EIP-7702 mobile swap flow on Pimlico's
449
- `Simple7702Account` — the previous v0.7 hash format and EIP-191
450
- signing path both reverted with `AA24 signature error`. See
451
- [`@pafi-dev/core` changelog 0.5.16](../core/README.md#0516).
452
- - README adds a **frontend "quote review" pattern** — debounced
453
- `/quote` polling so the user sees `X PT → ~Y USDT` (after operator
454
- gas fee) before submitting the swap UserOp. Reference implementation
455
- in `privy-pimlico-eip7702-example/app/components/WalletPanel.tsx`.
456
-
457
- ### 0.1.2
458
-
459
- - Initial public release. `handleQuote`, `handleSwap`,
460
- `handlePerpDeposit` stateless handlers + `fetchPafiPools` subgraph
461
- helper.
208
+ - `findBestQuote(client, chainId, tokenIn, tokenOut, amount, pools, quoterAddress?, maxHops?)`
209
+ - `quoteBestRoute`, `quoteExactInput`, `quoteExactInputSingle`
210
+ - `buildAllPaths`, `combineRoutes`
211
+ - `buildSwapUserOp(params)` direction-agnostic UserOp builder
212
+ - `buildUniversalRouterExecuteArgs`, `buildV4SwapInput`, `buildSwapFromQuote`
213
+ - `buildPermit2ApprovalCalldata`, `buildErc20ApprovalCalldata`, `checkAllowance`
214
+ - `simulateSwap` `eth_call` dry-run
215
+ - `fetchPafiPools(chainId, pointTokenAddress, subgraphUrl?)`
462
216
 
463
217
  ---
464
218
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pafi-dev/trading",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Stateless on-chain trading handlers for PAFI — swap, quote, perp deposit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@pafi-dev/core": "0.6.0"
25
+ "@pafi-dev/core": "0.6.2"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "viem": "^2.0.0"