@pafi-dev/trading 0.2.0 → 0.3.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 +132 -378
- package/dist/index.cjs +63 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -51
- package/dist/index.d.ts +75 -51
- package/dist/index.js +65 -36
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,462 +3,216 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@pafi-dev/trading)
|
|
4
4
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
5
5
|
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
##
|
|
15
|
+
## Why this exists
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 {
|
|
42
|
-
|
|
45
|
+
import {
|
|
46
|
+
TradingHandlers,
|
|
47
|
+
fetchPafiPools,
|
|
48
|
+
} from "@pafi-dev/trading";
|
|
49
|
+
import { getContractAddresses } from "@pafi-dev/core";
|
|
43
50
|
|
|
44
|
-
const provider = createPublicClient({
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
const
|
|
58
|
+
// Quote — direction-agnostic.
|
|
59
|
+
const q = await trading.handleQuote({
|
|
59
60
|
chainId: 8453,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
//
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
103
|
+
## Operator fee strategy
|
|
166
104
|
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
+
---
|
|
173
115
|
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
// Loop call — fetch 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
|
-
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
const deposit = await trading.handlePerpDeposit({
|
|
128
|
+
const swap = await trading.handleSwap({
|
|
204
129
|
chainId: 8453,
|
|
205
|
-
userAddress
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
##
|
|
146
|
+
## Perp deposit
|
|
235
147
|
|
|
236
148
|
```ts
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
##
|
|
169
|
+
## Standalone primitives
|
|
308
170
|
|
|
309
|
-
|
|
171
|
+
For callers that want to compose flows beyond what `TradingHandlers`
|
|
172
|
+
provides:
|
|
310
173
|
|
|
311
174
|
```ts
|
|
312
|
-
import
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
324
|
-
|
|
185
|
+
Use these to build custom direction-agnostic swap flows or pre-flight
|
|
186
|
+
simulations.
|
|
325
187
|
|
|
326
188
|
---
|
|
327
189
|
|
|
328
|
-
##
|
|
190
|
+
## API reference
|
|
329
191
|
|
|
330
|
-
|
|
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
|
-
|
|
195
|
+
new TradingHandlers({ provider: PublicClient, chainId: number })
|
|
386
196
|
```
|
|
387
197
|
|
|
388
|
-
|
|
198
|
+
Methods:
|
|
389
199
|
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
`
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
|