@pafi-dev/trading 0.1.10 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,85 +1,82 @@
1
- import { Address, PublicClient } from 'viem';
2
- import { PartialUserOperation, PoolKey } from '@pafi-dev/core';
1
+ import { Address, PublicClient, Hex } from 'viem';
2
+ import { PartialUserOperation, PoolKey, QuoteResult, PathKey, BestQuote } from '@pafi-dev/core';
3
3
  export { PAFI_SUBGRAPH_URL, fetchPafiPools } from '@pafi-dev/core';
4
4
 
5
5
  interface ApiQuoteRequest {
6
6
  chainId: number;
7
- pointTokenAddress: Address;
8
- /** PT amount (18-decimal raw units). */
7
+ /** Token user is spending. */
8
+ inputTokenAddress: Address;
9
+ /** Token user wants to receive. */
10
+ outputTokenAddress: Address;
11
+ /** Input token amount (raw decimals — input token's native scale). */
9
12
  amount: bigint;
10
13
  /**
11
- * PT/USDT pools to include in routing. Combined with COMMON_POOLS
12
- * from `@pafi-dev/core`. Pass empty array to use only COMMON_POOLS.
14
+ * Pools to include in routing. Combined with COMMON_POOLS. When
15
+ * either side is a PT, the caller should fetch pools for ALL PTs
16
+ * involved (single PT → USDT → PT' multi-hop) and merge before
17
+ * passing here.
13
18
  */
14
19
  pools?: PoolKey[];
15
20
  }
16
21
  type ApiQuoteError = "QUOTE_UNAVAILABLE" | "AMOUNT_TOO_SMALL_FOR_GAS";
17
22
  interface ApiQuoteResponse {
18
- pointAmount: bigint;
19
- estimatedUsdtOut: bigint;
23
+ /** Echoes `request.amount` for FE bookkeeping. */
24
+ inputAmount: bigint;
25
+ /** Quoted output amount before slippage. 0n when `quoteError` set. */
26
+ estimatedOutputAmount: bigint;
27
+ /** V4 Quoter's gas estimate for the route. */
20
28
  gasEstimate: bigint;
21
29
  quoteError?: ApiQuoteError;
22
30
  }
23
31
  interface ApiSwapRequest {
24
32
  chainId: number;
25
33
  userAddress: Address;
26
- pointTokenAddress: Address;
27
- /** PT amount to swap (18-decimal raw units). */
34
+ inputTokenAddress: Address;
35
+ outputTokenAddress: Address;
36
+ /** Input token amount to swap (raw token decimals). */
28
37
  amount: bigint;
29
38
  /** ERC-4337 account nonce for the user's EOA (from EntryPoint). */
30
39
  aaNonce: bigint;
31
- /** Slippage tolerance in basis points (1 bps = 0.01%). Default: 50 (0.5%). */
40
+ /**
41
+ * Slippage tolerance in basis points (1 bps = 0.01%). Default: 50
42
+ * for single-hop, 100 for multi-hop (handler picks based on
43
+ * `bestRoute.path.length`).
44
+ */
32
45
  slippageBps?: number;
33
- /** PT/USDT pools. Combined with COMMON_POOLS. Pass empty to use only COMMON_POOLS. */
46
+ /** Pools to consider. Caller pre-fetches per-PT pools and merges. */
34
47
  pools?: PoolKey[];
35
48
  /**
36
- * PT operator fee (paid to PAFI fee recipient as reimbursement for
37
- * sponsoring ERC-4337 gas).
49
+ * Operator fee in INPUT token units, paid to PAFI fee recipient as
50
+ * reimbursement for sponsoring ERC-4337 gas.
38
51
  *
39
- * - `undefined` (default): handler auto-quotes via Chainlink + V4
40
- * subgraph using `quoteOperatorFeePt` from `@pafi-dev/core`.
41
- * Recommended for normal usage caller doesn't need to know the
42
- * fee policy or pre-fetch oracle data.
43
- * - `0n`: strip the `PT.transfer(pafiFeeRecipient, ...)` call (used
44
- * for the unsponsored fallback path — user pays ETH gas
45
- * themselves so there's nothing to reimburse).
46
- * - explicit `bigint`: override for issuer markup / subsidy
47
- * scenarios. Sponsor-relayer's `FeeValidator` enforces a 5%
48
- * tolerance vs its own quote — too low → `INSUFFICIENT_FEE`.
52
+ * - `undefined` (default): handler auto-quotes via
53
+ * `quoteOperatorFeeInputToken` (Chainlink USD/ETH + token oracle).
54
+ * - `0n`: strip the fee transfer (unsponsored fallback path).
55
+ * - explicit `bigint`: override for issuer markup/subsidy scenarios.
49
56
  */
50
- gasFeePt?: bigint;
57
+ gasFeeAmount?: bigint;
51
58
  }
52
59
  interface ApiSwapResponse {
53
- /** Unsigned UserOp — attach paymaster data + user signature, then submit to Bundler. */
60
+ /** Unsigned UserOp — attach paymaster + user signature, submit to Bundler. */
54
61
  userOp: PartialUserOperation;
55
62
  /**
56
- * Fee-stripped fallback variant. Emitted only when `gasFeePt > 0n` —
57
- * otherwise it would equal `userOp` exactly and we omit it to avoid
58
- * encouraging callers to redundantly resubmit.
59
- *
60
- * Submit this when the paymaster refuses sponsorship (the user pays
61
- * ERC-4337 gas in ETH directly): there's no point charging the
62
- * operator fee in PT for sponsorship that didn't happen. Pair with
63
- * `sendWithPaymasterFallback({ txParams, txParamsFallback })`.
63
+ * Fee-stripped fallback variant. Emitted only when `gasFeeAmount > 0`.
64
+ * Submit when the paymaster refuses sponsorship (user pays ETH gas).
64
65
  */
65
66
  userOpFallback?: PartialUserOperation;
66
- /** Raw USDT out before slippage (6 decimals). For display. */
67
- estimatedUsdtOut: bigint;
68
- /** Minimum USDT accepted — encoded in the UserOp calldata. */
67
+ /** Raw output amount before slippage. For display. */
68
+ estimatedOutputAmount: bigint;
69
+ /** Minimum output accepted — encoded in the UserOp calldata. */
69
70
  minAmountOut: bigint;
71
+ /** Number of hops in the chosen route. Used by FE to display "PT → USDT → PT1" etc. */
72
+ hops: number;
70
73
  /** Swap deadline (unix seconds). Re-request if user doesn't submit in time. */
71
74
  deadline: bigint;
72
75
  /**
73
- * Actual PT fee amount the handler embedded in the sponsored
74
- * UserOp. Echoes the auto-quote result (or the explicit override).
75
- * `0n` when the caller forced no-fee mode.
76
+ * Operator fee amount embedded echoes auto-quote result or override.
77
+ * Denominated in the INPUT token. `0n` when caller forced no-fee mode.
76
78
  */
77
79
  feeAmountUsed: bigint;
78
- /**
79
- * Recipient address used for the PT fee transfer (always
80
- * `getContractAddresses(chainId).pafiFeeRecipient`). Echoed for
81
- * client-side verification before signing.
82
- */
83
80
  feeRecipient: Address;
84
81
  }
85
82
  interface ApiPerpDepositRequest {
@@ -256,4 +253,230 @@ declare class TradingHandlers {
256
253
  handlePerpDeposit(request: ApiPerpDepositRequest): Promise<ApiPerpDepositResponse>;
257
254
  }
258
255
 
259
- export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, TradingHandlers, type TradingHandlersConfig };
256
+ declare function checkAllowance(client: PublicClient, token: Address, owner: Address, spender: Address): Promise<bigint>;
257
+ /**
258
+ * Encode an ERC-20 approve(spender, amount) call.
259
+ */
260
+ declare function buildErc20ApprovalCalldata(spender: Address, amount: bigint): Hex;
261
+ /**
262
+ * Encode a Permit2 approve(token, spender, amount, expiration) call.
263
+ */
264
+ declare function buildPermit2ApprovalCalldata(token: Address, spender: Address, amount: bigint, expiration: number): Hex;
265
+
266
+ /**
267
+ * Build the calldata inputs[0] (the V4_SWAP command payload) for
268
+ * UniversalRouter.execute.
269
+ *
270
+ * Actions encoded: SWAP_EXACT_IN → SETTLE_ALL → TAKE_ALL
271
+ *
272
+ * Encoding matches the Uniswap V4 SDK's V4Planner — each action's params
273
+ * are individually ABI-encoded, then wrapped together with the action bytes.
274
+ */
275
+ declare function buildV4SwapInput(currencyIn: Address, path: PathKey[], amountIn: bigint, minAmountOut: bigint, outputCurrency: Address): Hex;
276
+ /**
277
+ * Build the full commands + inputs args for UniversalRouter.execute.
278
+ */
279
+ declare function buildUniversalRouterExecuteArgs(currencyIn: Address, path: PathKey[], amountIn: bigint, minAmountOut: bigint, outputCurrency: Address): {
280
+ commands: Hex;
281
+ inputs: Hex[];
282
+ };
283
+ /**
284
+ * Build UniversalRouter execute args from a quote result.
285
+ *
286
+ * Takes the output of `findBestQuote` / `quoteBestRoute` and produces
287
+ * ready-to-use `{ commands, inputs }` for `UniversalRouter.execute`.
288
+ *
289
+ * @param quote - Quote result containing the path
290
+ * @param currencyIn - Input token address
291
+ * @param currencyOut - Output token address
292
+ * @param amountIn - Exact input amount (same value passed to the quoter)
293
+ * @param minAmountOut - Minimum acceptable output (caller applies slippage)
294
+ *
295
+ * @deprecated Since v1.4 — the Issuer App no longer handles swaps.
296
+ * For the new PT→USDT batch call on PAFI Web (Scenario 4 with
297
+ * EIP-7702 gas deduction), use `buildSwapWithGasDeduction()` (v1.5).
298
+ * This helper is kept for legacy v0.2.x consumers; will be removed in v2.0.
299
+ */
300
+ declare function buildSwapFromQuote(params: {
301
+ quote: QuoteResult;
302
+ currencyIn: Address;
303
+ currencyOut: Address;
304
+ amountIn: bigint;
305
+ minAmountOut: bigint;
306
+ }): {
307
+ commands: Hex;
308
+ inputs: Hex[];
309
+ };
310
+
311
+ interface SwapSimulationResult {
312
+ success: boolean;
313
+ gasEstimate: bigint;
314
+ }
315
+ /**
316
+ * Simulate a UniversalRouter.execute swap call via eth_call (no gas spent).
317
+ *
318
+ * Runs the full V4 swap flow — token transfer via Permit2, swap via
319
+ * PoolManager, output settlement — without submitting a transaction.
320
+ * If the simulation reverts, throws a `SimulationError` with the reason.
321
+ *
322
+ * @param client - viem PublicClient
323
+ * @param routerAddress - UniversalRouter contract address
324
+ * @param commands - Packed command bytes (from buildSwapFromQuote)
325
+ * @param inputs - Encoded inputs per command (from buildSwapFromQuote)
326
+ * @param deadline - Unix timestamp after which the tx expires
327
+ * @param from - Address that will execute the swap
328
+ */
329
+ declare function simulateSwap(client: PublicClient, routerAddress: Address, commands: Hex, inputs: Hex[], deadline: bigint, from: Address): Promise<SwapSimulationResult>;
330
+
331
+ /**
332
+ * v1.6 — Generalized swap UserOp builder. Direction-agnostic: works
333
+ * for **any** ERC-20 → ERC-20 pair routable through PAFI's V4 pools:
334
+ *
335
+ * - PT → USDT (cashout)
336
+ * - USDT → PT (buy PT with USDT)
337
+ * - PT0 → PT1 (same-issuer multi-token swap; routes via USDT)
338
+ *
339
+ * UserOp shape (atomic batch via EIP-7702 BatchExecutor):
340
+ *
341
+ * 1. `inputToken.approve(Permit2, amountIn + gasFeeAmount)` — single
342
+ * approve covers both swap input + operator gas reimbursement.
343
+ * 2. `Permit2.approve(inputToken, router, amountIn, deadline)` — Permit2
344
+ * authorization to UniversalRouter.
345
+ * 3. `UniversalRouter.execute(commands, inputs, deadline)` — V4 swap
346
+ * `inputToken → outputToken`; user receives ≥ `minAmountOut`.
347
+ * 4. `inputToken.transfer(feeRecipient, gasFeeAmount)` — operator
348
+ * gas reimbursement, paid in INPUT token (omitted when 0).
349
+ *
350
+ * ## Fee model — input-token strategy
351
+ *
352
+ * Operator gas fee is charged in the **input token** (option (a) chosen
353
+ * 2026-04-27). Generic across directions; user only needs to hold a
354
+ * single token in sufficient quantity.
355
+ *
356
+ * User must hold `amountIn + gasFeeAmount` of `inputTokenAddress`.
357
+ *
358
+ * ## PAFI Hook fee
359
+ *
360
+ * The V4 PAFIHook charges 10% on PT → USDT direction (one-way). The
361
+ * 10% is taken at pool level inside `UniversalRouter.execute`, so this
362
+ * builder doesn't model it explicitly — it shows up as a smaller
363
+ * `amountOut` from `findBestQuote`.
364
+ *
365
+ * - PT → USDT: pool charges 10% (output reduced)
366
+ * - USDT → PT: no hook fee
367
+ * - PT0 → PT1: 10% on PT0 → USDT leg, 0% on USDT → PT1 leg
368
+ *
369
+ * ## Order of operations
370
+ *
371
+ * Fee transfer is LAST so a reverting swap also refunds the fee
372
+ * (atomic batch revert semantics).
373
+ */
374
+ interface BuildSwapUserOpParams {
375
+ /** User's EOA (with EIP-7702 delegation to BatchExecutor). */
376
+ userAddress: Address;
377
+ /** ERC-4337 account nonce — fetched from EntryPoint by the caller. */
378
+ aaNonce: bigint;
379
+ /** Token being spent — approved to Permit2 + UniversalRouter. */
380
+ inputTokenAddress: Address;
381
+ /** Token user receives. */
382
+ outputTokenAddress: Address;
383
+ /** UniversalRouter contract address (chain-specific). */
384
+ universalRouterAddress: Address;
385
+ /** Input token units to swap. User's balance must be ≥ `amountIn + gasFeeAmount`. */
386
+ amountIn: bigint;
387
+ /**
388
+ * Minimum output to accept. Caller applies slippage (typically
389
+ * 50-100 bps; multi-hop routes should bias higher) against a fresh
390
+ * quote. Sub-minimum swap reverts.
391
+ */
392
+ minAmountOut: bigint;
393
+ /**
394
+ * V4 pool path for the swap. Single-hop = 1 PathKey, multi-hop = N.
395
+ * Get this from `findBestQuote().bestRoute.path`.
396
+ */
397
+ swapPath: PathKey[];
398
+ /** Unix seconds. After this, the router rejects the swap. */
399
+ deadline: bigint;
400
+ /**
401
+ * Operator gas-reimbursement fee — paid in INPUT token. Omitted from
402
+ * the batch when 0n. Caller is responsible for quoting this in input
403
+ * token units (typically USDT-denominated then converted via
404
+ * `quoteOperatorFee`).
405
+ */
406
+ gasFeeAmount: bigint;
407
+ /**
408
+ * Where the gas fee lands — typically the canonical PAFI fee
409
+ * recipient from `getContractAddresses(chainId).pafiFeeRecipient`.
410
+ */
411
+ feeRecipient: Address;
412
+ /** Override ERC-4337 gas estimates. Defaults are conservative. */
413
+ gasLimits?: {
414
+ callGasLimit?: bigint;
415
+ verificationGasLimit?: bigint;
416
+ preVerificationGas?: bigint;
417
+ };
418
+ }
419
+ /**
420
+ * Build an unsigned UserOp for the generalized swap flow.
421
+ *
422
+ * @throws when `amountIn` is non-positive, `gasFeeAmount` is negative,
423
+ * or `swapPath` is empty.
424
+ */
425
+ declare function buildSwapUserOp(params: BuildSwapUserOpParams): PartialUserOperation;
426
+
427
+ /**
428
+ * Combine point-token-specific pools and common pools for a given chain.
429
+ * Point token pools are listed first so callers can prioritise them.
430
+ */
431
+ declare function combineRoutes(chainId: number, pointTokenAddress: Address): PoolKey[];
432
+ /**
433
+ * Build all possible swap paths from `tokenIn` to `tokenOut` using the given
434
+ * pools. Returns an array of PathKey[] routes (each up to `maxHops` hops).
435
+ *
436
+ * Supports both direct single-hop routes and multi-hop routes through
437
+ * intermediate tokens. Each pool is used at most once per path.
438
+ *
439
+ * @param pools - Available pools to route through
440
+ * @param tokenIn - Input token address
441
+ * @param tokenOut - Desired output token address
442
+ * @param maxHops - Maximum number of hops (default 3)
443
+ */
444
+ declare function buildAllPaths(pools: PoolKey[], tokenIn: Address, tokenOut: Address, maxHops?: number): PathKey[][];
445
+
446
+ /**
447
+ * Quote exact-input for a multi-hop path.
448
+ */
449
+ declare function quoteExactInput(client: PublicClient, quoterAddress: Address, exactCurrency: Address, path: PathKey[], exactAmount: bigint): Promise<QuoteResult>;
450
+ /**
451
+ * Quote exact-input for a single-hop swap, given an explicit PoolKey and direction.
452
+ */
453
+ declare function quoteExactInputSingle(client: PublicClient, quoterAddress: Address, poolKey: PoolKey, zeroForOne: boolean, exactAmount: bigint, hookData: `0x${string}`): Promise<{
454
+ amountOut: bigint;
455
+ gasEstimate: bigint;
456
+ }>;
457
+ /**
458
+ * Try multiple PathKey[] routes and return the best quote plus all results.
459
+ * Routes that fail (e.g. pool does not exist) are silently skipped.
460
+ *
461
+ * Uses viem multicall to batch all quotes into a single RPC call for speed.
462
+ */
463
+ declare function quoteBestRoute(client: PublicClient, quoterAddress: Address, exactCurrency: Address, routes: PathKey[][], exactAmount: bigint): Promise<BestQuote>;
464
+ /**
465
+ * Find and quote the best swap route from `tokenIn` to `tokenOut`.
466
+ *
467
+ * Combines the caller's `pools` with `COMMON_POOLS[chainId]`, builds all
468
+ * possible paths (up to `maxHops`), then quotes them all via a single
469
+ * multicall and returns the best result.
470
+ *
471
+ * @param client - viem PublicClient
472
+ * @param chainId - Chain ID (used to look up COMMON_POOLS and V4_QUOTER_ADDRESSES)
473
+ * @param tokenIn - Input token address
474
+ * @param tokenOut - Desired output token address
475
+ * @param exactAmount - Exact input amount
476
+ * @param pools - Additional pools to consider (e.g. point-token-specific)
477
+ * @param quoterAddress - Override the default V4 Quoter address for this chain
478
+ * @param maxHops - Maximum number of hops per path (default 3)
479
+ */
480
+ declare function findBestQuote(client: PublicClient, chainId: number, tokenIn: Address, tokenOut: Address, exactAmount: bigint, pools?: PoolKey[], quoterAddress?: Address, maxHops?: number): Promise<BestQuote>;
481
+
482
+ export { type ApiPerpDepositRequest, type ApiPerpDepositResponse, type ApiQuoteError, type ApiQuoteRequest, type ApiQuoteResponse, type ApiSwapRequest, type ApiSwapResponse, type BuildSwapUserOpParams, type SwapSimulationResult, TradingHandlers, type TradingHandlersConfig, buildAllPaths, buildErc20ApprovalCalldata, buildPermit2ApprovalCalldata, buildSwapFromQuote, buildSwapUserOp, buildUniversalRouterExecuteArgs, buildV4SwapInput, checkAllowance, combineRoutes, findBestQuote, quoteBestRoute, quoteExactInput, quoteExactInputSingle, simulateSwap };