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.4.0

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: V4 swap quote + UserOp build, Orderly perp deposit. Direction-agnostic since 0.2.0 — quote and swap any ERC-20 → ERC-20 routable through PAFI's V4 pools.

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


Why this exists

Issuer backends (@pafi-dev/issuer) cover the 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 V4 Quoter read, multicall'd.
  • Build swap UserOp with operator gas-reimbursement fee in the input token (auto-quoted via Chainlink). 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

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 } = getContractAddresses(8453);

// 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({
  chainId: 8453,
  userAddress,
  inputTokenAddress: POINT_TOKEN,
  outputTokenAddress: usdt,
  amount: 100n * 10n ** 18n,
  aaNonce,
  pools,
  // gasFeeAmount auto-quotes when input is a PT; 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 — number of hops in chosen route

Direction matrix

| Direction | Use case | Hop count | Hook fee | Operator fee token | | --- | --- | --- | --- | --- | | PT → USDT | Cashout | 1 | 10% on this leg | PT | | USDT → PT | Buy PT | 1 | 0% | USDT | | PT0 → PT1 | Same-issuer multi-token swap | 2 (via USDT) | 10% on PT0 leg only | PT0 |

Same-issuer only for PT0 → PT1 — caller is responsible for ensuring both PTs belong to the same issuer. SDK doesn't enforce.

The PAFI V4 hook charges 10% on PT → USDT direction at pool level (applied inside UniversalRouter.execute). Reflected in estimatedOutputAmount from findBestQuote.


Operator fee strategy

The operator gas-reimbursement fee is charged in the input token (0.2.0+). Single approve covers amountIn + gasFeeAmount. User only needs to hold a single token.

| Input | Auto-quote source | Caller needs to override? | | --- | --- | --- | | PT | quoteOperatorFeePt (Chainlink + V4 subgraph) | No | | USDT | falls back to 0 — pass gasFeeAmount explicitly | Yes (use quoteOperatorFeeUsdt) |


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({
  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

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


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 what TradingHandlers provides:

import {
  buildSwapUserOp,
  findBestQuote,
  simulateSwap,
  buildUniversalRouterExecuteArgs,
  buildPermit2ApprovalCalldata,
  fetchPafiPools,
} from "@pafi-dev/trading";

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


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

v0.4.0+ 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() });
// `walletClient` from Privy embedded wallet, MetaMask, etc.
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
});

console.log("Swap tx:", result.txHash);
// {
//   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; that's separate from PAFI's operator fee (which is skipped by default on the 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.


API reference

TradingHandlers

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

Methods:

| Method | Purpose | | --- | --- | | handleQuote({ inputTokenAddress, outputTokenAddress, amount, pools? }) | V4 quote. Returns { inputAmount, estimatedOutputAmount, gasEstimate, quoteError? } | | handleSwap({ userAddress, inputTokenAddress, outputTokenAddress, amount, aaNonce, pools?, gasFeeAmount?, slippageBps? }) | Build UserOp. Returns { userOp, userOpFallback?, estimatedOutputAmount, minAmountOut, hops, deadline, feeAmountUsed, feeRecipient } | | handlePerpDeposit({ userAddress, amount, aaNonce, brokerId, viaRelay?, maxRelayFee?, pointTokenAddress?, gasFeePt? }) | Build UserOp. Returns Orderly deposit response |

Free functions

  • findBestQuote(client, chainId, tokenIn, tokenOut, amount, pools, quoterAddress?, maxHops?)
  • quoteBestRoute, quoteExactInput, quoteExactInputSingle
  • buildAllPaths, combineRoutes
  • buildSwapUserOp(params) — direction-agnostic UserOp builder
  • buildUniversalRouterExecuteArgs, buildV4SwapInput, buildSwapFromQuote
  • buildPermit2ApprovalCalldata, buildErc20ApprovalCalldata, checkAllowance
  • simulateSwapeth_call dry-run
  • fetchPafiPools(chainId, pointTokenAddress, subgraphUrl?)
  • swapDirect(params) — FE-direct swap (no AA, user pays gas) — see "Direct path" section
  • perpDepositDirect(params) — FE-direct perp deposit (no AA, user pays gas)

License

Apache-2.0