npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pafi-dev/trading

v0.11.2

Published

Stateless on-chain trading handlers for PAFI — swap, quote, perp deposit

Readme

@pafi-dev/trading

npm License: Apache-2.0

On-chain trading for PAFI: V3 swap quote + UserOp build, Orderly perp deposit. Direction-agnostic — quote and swap any ERC-20 → ERC-20 routable through PAFI's Uniswap V3 pools.

Browser + Node-safe. Stateless (no DB, no signer, no auth). Peer-deps: viem ^2. Plus @pafi-dev/core for primitives.

PAFI's PT pools are standard Uniswap V3 (no hooks). USDT ↔ PT swaps incur only the standard LP fee on top of the SDK's operator fee.


Why this exists

Issuer backends (@pafi-dev/issuer) cover issuer-signed flows (claim, redeem). Trading is the FE-callable surface for actions that don't need an issuer signature:

  • Quote PT → USDT, USDT → PT, PT0 → PT1 — pure on-chain V3 QuoterV2 read, multicall'd.
  • Build swap UserOp with operator gas-reimbursement fee in the input token (auto-quoted via Chainlink + V3 subgraph). Sponsored + fallback variants.
  • Build perp deposit UserOp via the PAFI Orderly Relay (Relay covers LayerZero msg.value, user pays USDC fee).

Stateless and HTTP-free — issuer backends or FE apps that want a server-canonical quote/swap can wrap TradingHandlers directly.


Installation

pnpm add @pafi-dev/trading @pafi-dev/core viem

Make sure to install @pafi-dev/core explicitly at top-level to avoid pnpm deduping into a nested copy with stale URLs/addresses.


Quick start (FE)

import { createPublicClient, http } from "viem";
import {
  TradingHandlers,
  fetchPafiPools,
} from "@pafi-dev/trading";
import { getContractAddresses } from "@pafi-dev/core";

const provider = createPublicClient({ transport: http("https://mainnet.base.org") });
const trading = new TradingHandlers({ provider, chainId: 8453 });
const { usdt, usdc } = getContractAddresses(8453);  // usdc is optional, used by perp-deposit fee path

// Pool discovery — loop call when both sides are PT (PT0 → PT1 multi-hop).
const pools = await fetchPafiPools(8453, POINT_TOKEN);

// Quote — direction-agnostic.
const q = await trading.handleQuote({
  chainId: 8453,
  inputTokenAddress: POINT_TOKEN,
  outputTokenAddress: usdt,
  amount: 100n * 10n ** 18n,    // 100 PT
  pools,
});
// q.estimatedOutputAmount — bigint USDT raw (6 dec)

// Swap — builds UserOp ready to submit via Bundler + EIP-7702.
const swap = await trading.handleSwap(userAddress, {
  chainId: 8453,
  userAddress,
  inputTokenAddress: POINT_TOKEN,
  outputTokenAddress: usdt,
  amount: 100n * 10n ** 18n,
  aaNonce,
  pools,
  // gasFeeAmount auto-quotes; pass explicit bigint to override.
  // slippageBps auto-picks 50 bps single-hop / 100 bps multi-hop.
});
// swap.userOp + swap.userOpFallback (when fee > 0)
// swap.hops, swap.estimatedOutputAmount, swap.minAmountOut

Direction matrix

| Direction | Use case | Hops | Operator fee token | | --- | --- | --- | --- | | PT → USDT | Cashout | 1 | USDT (output) | | USDT → PT | Buy PT | 1 | PT (output) | | PT0 → PT1 | Multi-token swap | 2 (via USDT) | output PT |

Pool fee depends on which V3 tier the pool was created at (typically 0.05% or 0.3% — COMMON_POOLS carries both). findBestQuote picks the route with the best amountOut across all tiers.

Cross-issuer OK for PT0 → PT1 (e.g. TPT ↔ LTR if both issuers ship V3 pools with USDT). The router auto-routes through USDT.


Operator fee strategy

The operator gas-reimbursement fee is charged in the output token. User receives (net - operatorFee) instead of net after the swap.

| Output | Auto-quote source | Caller override | | --- | --- | --- | | PT | quoteOperatorFeePt (Chainlink + V3 subgraph) | gasFeeAmountOutput: bigint | | USDT | quoteOperatorFeeUsdt (Chainlink only) | gasFeeAmountOutput: bigint |

// USDT-output swap (e.g. PT → USDT cashout)
const swap = await trading.handleSwap(user, {
  // ...
  inputTokenAddress: PT,
  outputTokenAddress: USDT,
  // gasFeeAmountOutput auto-quotes ~$0.008 USDT
});

// Or override:
const swap = await trading.handleSwap(user, {
  // ...
  gasFeeAmountOutput: 10_000n,  // 0.01 USDT explicit
});

Multi-hop PT → PT1

import { fetchPafiPools, TradingHandlers } from "@pafi-dev/trading";

// Loop call — fetch pools for both PTs, merge.
const [poolsA, poolsB] = await Promise.all([
  fetchPafiPools(8453, POINT_TOKEN_0),
  fetchPafiPools(8453, POINT_TOKEN_1),
]);
const pools = [...poolsA, ...poolsB];

const swap = await trading.handleSwap(user, {
  chainId: 8453,
  userAddress,
  inputTokenAddress: POINT_TOKEN_0,
  outputTokenAddress: POINT_TOKEN_1,
  amount: 100n * 10n ** 18n,
  aaNonce,
  pools,
});

console.log(swap.hops);  // 2 — went through USDT

V3 router auto-picks the best route across pools + COMMON_POOLS (default maxHops=3). Slippage auto-bumps to 100 bps for multi-hop.

Verify via subgraph that both PTs have indexed pools with USDT:

{ pafiTokens { id pool { token0 { id symbol } token1 { id symbol } } } }

Perp deposit

const deposit = await trading.handlePerpDeposit({
  chainId: 8453,
  userAddress,
  amount: 1_000_000n,           // 1 USDC (6 dec)
  aaNonce,
  brokerId: "orderly",          // "orderly" | "woofi_pro" | "logx"
  // viaRelay: true (default) — uses PAFI Orderly Relay (no msg.value needed)
  pointTokenAddress: POINT_TOKEN,  // optional — for PT operator fee
});

// deposit.userOp — submit via Bundler + Paymaster
// deposit.path — "relay" (preferred) or "vault" (direct, requires native ETH)

The PAFI Relay covers LayerZero msg.value from its ETH reserve and charges a USDC fee (Relay.quoteTokenFee). User wallet doesn't need native ETH.


Standalone primitives

For callers that want to compose flows beyond TradingHandlers:

import {
  buildSwapUserOp,
  buildSwapUserOpExactOut,
  findBestQuote,
  findBestQuoteExactOut,
  simulateSwap,
  buildUniversalRouterExecuteArgs,
  buildUniversalRouterExecuteArgsExactOut,
  buildV3SwapInputExactIn,
  buildV3SwapInputExactOut,
  buildPermit2ApprovalCalldata,
  buildErc20ApprovalCalldata,
  checkAllowance,
  fetchPafiPools,
  V3_SWAP_EXACT_IN,
  V3_SWAP_EXACT_OUT,
} from "@pafi-dev/trading";

Use these to build custom direction-agnostic swap flows or pre-flight simulations.

buildSwapFromQuote is also re-exported but @deprecated — pass bestRoute.path from findBestQuote(...) directly into buildUniversalRouterExecuteArgs(...) instead. Will be removed in a future minor.


Direct path — no AA, no bundler, no sponsor-relayer

Trading ships swapDirect() + perpDepositDirect() for the FE-only flow where the user pays gas in ETH and broadcasts a single type-2 tx calling its own EIP-7702 delegated bytecode. No Pimlico API key, no sponsor-relayer, no PAFI infra.

Precondition: user EOA must already be EIP-7702 delegated (run delegateDirect() from @pafi-dev/core first; both helpers throw a clear error otherwise).

swapDirect

import { swapDirect, fetchPafiPools } from "@pafi-dev/trading";
import { createPublicClient, createWalletClient, custom, http, parseUnits } from "viem";
import { base } from "viem/chains";

const publicClient = createPublicClient({ chain: base, transport: http() });
const walletClient = createWalletClient({
  account: wallet.address,
  chain: base,
  transport: custom(await wallet.getEthereumProvider()),
});

const pools = await fetchPafiPools(8453, POINT_TOKEN);

const result = await swapDirect({
  userAddress: wallet.address,
  chainId: 8453,
  inputTokenAddress: POINT_TOKEN,
  outputTokenAddress: USDT,
  amount: parseUnits("100", 18),
  pools,
  publicClient,
  walletClient,
  // optional: slippageBps, deadline, gasFeeAmountOutput, waitForReceipt
});

// {
//   txHash, receipt?, estimatedOutputAmount, minAmountOut, hops,
//   deadline, feeAmountUsed
// }

Operator fee: default 0n (skip — PAFI not sponsoring this swap). Pass an explicit gasFeeAmountOutput: bigint only if you want to mirror the sponsored flow's fee transfer.

perpDepositDirect

import { perpDepositDirect } from "@pafi-dev/trading";

const result = await perpDepositDirect({
  userAddress: wallet.address,
  chainId: 8453,
  amount: parseUnits("10", 6), // 10 USDC
  brokerId: "orderly",         // | "woofi_pro" | "logx"
  publicClient,
  walletClient,
  // optional: maxRelayFee, gasFeeUsdc, waitForReceipt
});

// {
//   txHash, receipt?, relayTokenFee, maxFee, netDeposit,
//   feeAmountUsed, accountId, brokerHash, usdcAddress, relayAddress
// }

The Relay still charges its own USDC fee (Relay.quoteTokenFee) to cover LayerZero msg.value; separate from PAFI's operator fee (skipped by default on direct path).

When to use direct vs. AA path

| Aspect | swapDirect / perpDepositDirect | TradingHandlers.handleSwap / handlePerpDeposit | | --- | --- | --- | | User pays gas | ETH (native) | Free (sponsor) or ETH (fallback) | | Bundler required | No | Yes (Pimlico) | | Sponsor-relayer required | No | Yes (sponsored path) | | Operator fee charged | Default skip | Default included | | Round trips | 1 (broadcast tx) | 2+ (paymaster + bundler) | | Precondition | EIP-7702 delegated | Same |

Use direct for FE dev/test, integrations without a Pimlico key, or production paths where users explicitly opt to pay gas. Use AA path for the production gas-free UX via sponsor-relayer.


Plain Swap (Permit2 sig — FE only, no AA)

For external wallets (MetaMask) that can't EIP-7702 delegate. Standard Uniswap pattern: ERC-20 approve to PERMIT2, off-chain PermitSingle sig, UR.execute([PERMIT2_PERMIT, V3_SWAP_EXACT_IN], ...).

// Step 1: one-time ERC-20 approve to Permit2 (skip if allowance set)
// Step 2: sign Permit2 PermitSingle EIP-712 (skip if sub-allowance valid)
// Step 3: send UR.execute tx — user pays gas in ETH

// See privy-pimlico-eip7702-example/app/components/WalletPanel.tsx
// handleSwapPlain for the full flow.

Cold start: 1 tx + 1 sig + 1 tx (3 popups). Warm (sub-allowance fresh): 1 tx.

⚠️ Common bug: permitExpiration = (1 << 48) - 1 overflows to 65535 in JS (int32 shift). Use 2 ** 48 - 1 or BigInt for uint48 timestamps.


API reference

TradingHandlers

new TradingHandlers({ provider: PublicClient, chainId: number })

Methods:

| Method | Purpose | | --- | --- | | handleQuote({ inputTokenAddress, outputTokenAddress, amount, pools? }) | V3 exact-input quote. Returns { inputAmount, estimatedOutputAmount, gasEstimate, quoteError? } | | handleQuoteExactOut({ ... }) | V3 exact-output quote | | handleSwap(authedAddr, { userAddress, inputTokenAddress, outputTokenAddress, amount, aaNonce, pools?, gasFeeAmountOutput?, slippageBps? }) | Build UserOp. Returns { userOp, userOpFallback?, estimatedOutputAmount, minAmountOut, hops, deadline, feeAmountUsed, feeRecipient } | | handleSwapExactOut(authedAddr, { ... }) | Build exact-output swap UserOp | | handlePerpDeposit({ userAddress, amount, aaNonce, brokerId, viaRelay?, maxRelayFee?, pointTokenAddress?, gasFeePt? }) | Build perp deposit UserOp |

Free functions

  • findBestQuote(client, chainId, tokenIn, tokenOut, amount, pools, quoterAddress?, maxHops?)
  • findBestQuoteExactOut(...)
  • quoteBestRoute, quoteBestRouteExactOut, quoteExactInput, quoteExactInputSingle, quoteExactOutput, quoteExactOutputSingle
  • buildSwapUserOp(params) / buildSwapUserOpExactOut(params) — direction-agnostic UserOp builder
  • buildUniversalRouterExecuteArgs, buildUniversalRouterExecuteArgsExactOut, buildV3SwapInputExactIn, buildV3SwapInputExactOut (and the @deprecated buildSwapFromQuote alias)
  • buildPermit2ApprovalCalldata, buildErc20ApprovalCalldata, checkAllowance
  • simulateSwapeth_call dry-run
  • fetchPafiPools(chainId, pointTokenAddress, subgraphUrl?) — re-exported from core
  • swapDirect(params), perpDepositDirect(params) — FE-direct flows
  • V3_SWAP_EXACT_IN, V3_SWAP_EXACT_OUT — Universal Router V3 command constants

References

License

Apache-2.0