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/core

v0.39.10

Published

EIP-712 signing, contract interaction, Uniswap V3 path helpers, and ERC-4337 UserOp building for the PAFI point token system

Downloads

2,502

Readme

@pafi-dev/core

npm License: Apache-2.0

Pure primitives for the PAFI point-token system. EIP-712 signing, contract ABIs + addresses, ERC-4337 UserOp building, EIP-7702 delegation, sponsor-auth signing, perp deposit calldata, fee quoting, MintFeeWrapper helpers.

Browser + Node-safe. Zero HTTP client. Peer-dep: viem ^2.


Requirements

  • Node.js ≥ 18 (or modern browser with native fetch)
  • TypeScript ≥ 5.0
  • viem ^2.0.0 (peer)

Installation

pnpm add @pafi-dev/core viem

Package layout

Core ships data + primitives only. Higher-level orchestration in siblings:

| Concern | Package | | --- | --- | | Pure primitives (this package) | @pafi-dev/core | | Issuer backend (claim / redeem / mobile flows) | @pafi-dev/issuer | | Issuer DB ledger (TypeORM + Postgres) | @pafi-dev/issuer-postgres | | Trading (swap + quote, FE-callable) | @pafi-dev/trading |


Exports cheatsheet

| Symbol / area | Provides | | --- | --- | | getContractAddresses(chainId) | All deployed PAFI addresses keyed by chain | | signMintRequest, verifyMintRequest, buildMintRequestTypedData | EIP-712 sign/recover for MintForRequest (5 fields: user, receiver, amount, nonce, deadline) | | signBurnRequest, verifyBurnRequest, buildBurnRequestTypedData | EIP-712 for BurnRequest | | signSponsorAuth, verifySponsorAuth, buildAndSignSponsorAuth | EIP-712 for sponsor-relayer auth | | buildPartialUserOperation, assembleUserOperation, computeUserOpHash | ERC-4337 v0.7+ UserOp builder + hash | | encodeBatchExecute, decodeBatchExecuteCalls | BatchExecutor (Pimlico Simple7702Account) calldata | | buildDelegationUserOp, computeAuthorizationHash, parseEip7702DelegatedAddress, splitAuthorizationSig, buildEip7702Authorization, delegateDirect | EIP-7702 helpers (build + introspect) | | buildPerpDepositViaRelay, buildPerpDepositWithGasDeduction, ORDERLY_RELAY_ABI, ORDERLY_VAULT_ABI, BROKER_HASHES, TOKEN_HASHES, computeAccountId | Orderly perp deposit calldata + types | | quoteOperatorFeePt, quoteOperatorFeeUsdt | Gas-reimbursement fee quoter (Chainlink + V3 subgraph) | | getMintFeeBps, getMintFeeRecipients | Read wrapper fee config per pointToken | | getTokenCap | Read MintingOracle.tokenCaps(pointToken) | | verifyMintCap, getPointTokenIssuer | MintingOracle read helpers | | getIssuer, isActiveIssuer, issuerRegistryGetIssuerFlatAbi | IssuerRegistry helpers (7-field record) | | getMintRequestNonce, getBurnRequestNonce, getPointTokenBalance, isMinter, getTokenName, getPointTokenIssuerAddress | PointToken read helpers | | fetchPafiPools, PAFI_SUBGRAPH_URL | Pool discovery — endpoint is …/pafi-subgraph-v4 (PAFI V3-fork DEX + extensions); parser tolerates both V3 and legacy V4 subgraph responses | | getPafiServiceUrls(chainId) | Hardcoded PAFI service endpoints (sponsorRelayer, issuerApi) | | PoolKey ({ token0, token1, fee }), V3Path ({ tokens, fees }), QuoteResult | Uniswap V3 routing types used by @pafi-dev/trading | | encodeV3Path, encodeV3PathReversed, computeV3PoolAddress | V3 packed-bytes path encoding + CREATE2 pool-address derivation | | V3_FACTORY_ADDRESSES, V3_SWAP_ROUTER_ADDRESSES, QUOTER_V2_ADDRESSES, V3_POOL_INIT_CODE_HASH | PAFI's V3-fork deployment (Base mainnet) | | pointTokenAbi, issuerRegistryAbi, mintingOracleAbi, mintFeeWrapperAbi, pointTokenFactoryAbi, erc20Abi, permit2Abi, universalRouterAbi, v3QuoterV2Abi | Contract ABIs (regenerated from Foundry artifacts) | | createLoginMessage | EIP-4361 SIWE login builder |


Contract addresses

import { getContractAddresses } from "@pafi-dev/core";

const addr = getContractAddresses(8453);  // Base mainnet
// addr.issuerRegistry, addr.mintingOracle, addr.mintFeeWrapper,
// addr.usdt, addr.usdc (optional, perp-deposit + sponsor-relayer stable fee),
// addr.batchExecutor, addr.pafiFeeRecipient,
// addr.chainlinkEthUsd, addr.orderlyRelay, addr.universalRouter

Per-issuer pointToken clones are NOT in the chain-level address bag. Read from your issuer env (e.g. POINT_TOKEN_ADDRESS) or PointTokenFactory.createToken() at runtime.


EIP-712 — MintForRequest

import { signMintRequest, verifyMintRequest } from "@pafi-dev/core";
import { privateKeyToAccount } from "viem/accounts";
import { createWalletClient, http } from "viem";

const wallet = createWalletClient({
  account: privateKeyToAccount(MINTER_PK),
  transport: http(),
});

const sig = await signMintRequest(
  wallet,
  { name: "POINT", chainId: 8453, verifyingContract: pointTokenAddress },
  {
    user,                      // off-chain spender (drives nonces)
    receiver: wrapperAddress,  // on-chain caller of PointToken.mint
                               // (= wrapper for wrapper-mediated; = user for direct)
    amount: 1000n * 10n ** 18n,
    nonce: currentMintRequestNonce,   // from PointToken.mintRequestNonces(user)
    deadline: BigInt(Math.floor(Date.now() / 1000) + 900),
  },
);

// sig.serialized — bytes hex passed to PointToken.mint(...) or wrapper.mintWithFee(...)

Wrapper-mediated mint puts receiver = wrapperAddress; direct mint puts receiver = user. The contract requires msg.sender == receiver.


MintFeeWrapper helpers

import { getMintFeeBps, getMintFeeRecipients, getContractAddresses } from "@pafi-dev/core";

const { mintFeeWrapper } = getContractAddresses(8453);
const POINT_TOKEN = "0x855c2046...";

const bps = await getMintFeeBps(provider, mintFeeWrapper, POINT_TOKEN);
// bps = 50 (= 0.50%)

const recipients = await getMintFeeRecipients(provider, mintFeeWrapper, POINT_TOKEN);
// [{ account: 0x..., basisPoints: 20 }, { account: 0x..., basisPoints: 30 }]

Wrapper deducts gross × bps / 10000 PT on every mint and distributes to recipients. User receives gross × (1 - bps/10000) net.


Operator fee quoter

Two execution paths depending on whether your caller has access to a bundler-driven estimator (recommended) or needs a self-contained quote.

Recommended: bundler-driven via FeeManager (issuer SDK)

For issuer backends (@pafi-dev/issuer) and trading helpers (@pafi-dev/trading), the FeeManager class is wired with a BundlerEstimatorClient (typically createPafiEstimatorClient(...)) that hits PAFI sponsor-relayer's POST /v1/estimate-gas-fee. Server-side that endpoint calls Pimlico's eth_estimateUserOperationGas, caches by (scenario, contract codehash, paymaster) in Redis (5 min TTL), applies PAFI's 110% premium, and returns gas units. SDK FeeManager then converts to PT/USDT via the helpers below with premiumBps: 10_000 (no double-pad). This is the path used by every /claim/prepare and /redeem/prepare flow.

Same Redis key is read by sponsor-relayer's FeeValidator when verifying the submitted UserOp — so the issuer's quote and PAFI's expected fee match exactly (no formula drift; only ±5% slack for ETH-price movement between quote-time and submit-time).

Self-contained: direct helpers (FE / fallback path)

When no backend round-trip is possible (FE direct swapDirect, or a caller without BundlerEstimatorClient wired), use these helpers directly. They run the same math but with hardcoded SCENARIO_GAS_UNITS (per-scenario table) and default premiumBps: 12_000 (legacy 120% self-contained margin — higher than bundler-driven because there's no PAFI-side premium to layer on).

import { quoteOperatorFeeUsdt, quoteOperatorFeePt } from "@pafi-dev/core";

// USDT-denominated (replaces issuer's GET /gas-fee). Chainlink-only.
const gasFeeUsdt = await quoteOperatorFeeUsdt({
  provider, chainId: 8453, scenario: "mint",
});

// PT-denominated. Adds V3 subgraph call for ptPerUsdt.
const gasFeePt = await quoteOperatorFeePt({
  provider, chainId: 8453, scenario: "mint",
  pointTokenAddress: POINT_TOKEN,
  allowStaleFallback: true,   // recommended: don't block on subgraph outage
});

Both return bigint raw units. Fallback prices (0.1 USDT/PT, 3000 USD/ETH) used when oracles unreachable + allowStaleFallback: true.

Scenario table

| Scenario | gasUnits | Notes | |---|---|---| | mint | 500_000n | Standard mint with wrapper | | burn | 500_000n | Standard burn | | swap | 700_000n | Single hop; multi-hop adds headroom inside the premium | | perp-deposit | 800_000n | Heavier — Orderly Vault interaction | | delegate | 200_000n | EIP-7702 no-op (gratis-sponsored as one-time onboarding) | | erc20-transfer | 200_000n | Tiny 2-call batch; bypasses bundler estimator (round-trip > savings) |

Fee math (raw bigint, no scaling shortcuts)

withPremium     = gasUnits × gasPrice × premiumBps / 10000     (wei)
fee_USDT_6dec   = withPremium × ethPrice_8dec / 10^(18+8-6)
fee_PT_18dec    = withPremium × ethPrice_8dec × ptPerUsdt_18dec / 10^26

ptPerUsdt_18dec comes from the V3 subgraph (token0Price / token1Price inverted to PT-per-USDT, scaled to 18 decimals).


ERC-4337 UserOp building

import { encodeBatchExecute, buildPartialUserOperation } from "@pafi-dev/core";

const callData = encodeBatchExecute([
  { target: mintFeeWrapper, value: 0n, data: mintWithFeeCallData },
  { target: pointToken,     value: 0n, data: feeTransferCallData },
]);

const partial = buildPartialUserOperation({
  sender: userAddress,
  nonce: aaNonce,
  callData,
  gasLimits: {
    callGasLimit: 300_000n,
    verificationGasLimit: 150_000n,
    preVerificationGas: 50_000n,
  },
});

computeUserOpHash(userOp, chainId) — EntryPoint v0.8 EIP-712 digest. Mobile signs this hash via eth_signTypedData_v4 (NOT personal_sign — Pimlico Simple7702Account does raw ecrecover without EIP-191 prefix).


EIP-7702 — delegateDirect

FE one-shot delegation, no AA / no sponsor:

import { delegateDirect } from "@pafi-dev/core";
import { useSign7702Authorization, useWallets } from "@privy-io/react-auth";

const { signAuthorization } = useSign7702Authorization();
const wallet = useWallets().find((w) => w.walletClientType === "privy");

const result = await delegateDirect({
  userAddress: wallet.address,
  chainId: 8453,
  publicClient,
  walletClient,
  signAuthorization,
  // optional: skipIfAlreadyDelegated (default true), waitForReceipt (default true)
});

// result.status: "already-delegated" | "broadcasted"
// result.txHash, result.delegatedTo, result.receipt?

Privy embedded wallet only — external wallets (MetaMask) can't sign EIP-7702 (no raw secp256k1_sign exposed). User pays ~$0.01–0.10 ETH gas.


EIP-7702 — attachDelegationIfNeeded (atomic activation, v0.21+)

Merge the 7702 SetCode authorization into the user's FIRST action UserOp instead of running a separate delegate transaction. Pimlico bundles both into ONE bundler tx — no "setup" step for the user.

import { attachDelegationIfNeeded } from "@pafi-dev/core";
import { useSign7702Authorization } from "@privy-io/react-auth";

const { signAuthorization } = useSign7702Authorization();

// Before EVERY sponsored UserOp (mint / swap / transfer / perp-deposit):
const authPart = await attachDelegationIfNeeded({
  rpc: publicClient,                                       // viem PublicClient
  account: userAddress,                                    // user's EOA
  expectedDelegate: "0xe6Cae83BdE06E4c305530e199D7217f42808555B", // BatchExecutor
  chainId: 8453,                                           // Base mainnet
  signAuthorization,                                       // Privy hook result
});

// Spread into permissionless smartClient.sendTransaction — the field is
// stripped + forwarded to Pimlico as `eip7702Auth` automatically.
await smartClient.sendTransaction({
  to: pointToken,
  data: encodeMintCalldata(amount),
  ...(authPart ?? {}),  // { authorization } when needed, nothing otherwise
});

Returns undefined when the EOA already delegates to expectedDelegate — caller passes no authorization field on subsequent UserOps.

Compared to delegateDirect:

| Trait | delegateDirect | attachDelegationIfNeeded | |---|---|---| | Number of on-chain txs | 2 (delegate + action) | 1 (atomic) | | User signs signAuthorization | once per session | once per session | | Submits UserOp itself | no — only delegates | no — caller does it | | Gas mode | user-paid ETH | works with paymaster + EOA-paid fallback | | Best for | one-shot pre-onboarding | every action call |

Concurrency-safe: parallel calls for the same (chainId, account) join the same in-flight promise — exactly ONE Privy popup is shown even when React re-fires the callback or the user double-clicks. The cache auto-evicts when settled so retries always re-check on-chain state.

Wrong-impl handling: if the EOA already delegates to a different contract (e.g. an older Coinbase 7702 impl), the helper signs a new authorization to switch the delegate — atomically, with the next action.


Sponsor-auth signing

import { buildAndSignSponsorAuth } from "@pafi-dev/core";

const sponsorAuth = await buildAndSignSponsorAuth({
  userAddress,
  callData: userOp.callData,
  chainId: 8453,
  scenario: "mint",          // "mint" | "burn" | "swap" | "perp-deposit" | "delegate"
  issuerId: ISSUER_ID,
  issuerSignerWallet,        // KMS-backed in prod
});
// Pass to sponsor-relayer's POST /paymaster/sponsor as `sponsorAuth` field

Issuer signer must be registered in IssuerRegistry (signerAddress field) and whitelisted via PointToken.addMinter(signer).


Verify on-chain state (debug)

import {
  getIssuer, getTokenCap, getMintFeeBps,
  getMintRequestNonce, isMinter,
} from "@pafi-dev/core";

// Issuer registry record (7-field flat record)
const issuer = await getIssuer(provider, registry, issuerAddress);
// { issuerAddress, signerAddress, name, symbol, active, pointToken, mintingOracle }

// Per-token cap (read from oracle, not issuer struct)
const cap = await getTokenCap(provider, mintingOracle, pointToken);
// { declaredTotalSupply, capBasisPoints }

// Fee config
const bps = await getMintFeeBps(provider, wrapper, pointToken);

// Mint state
const nonce = await getMintRequestNonce(provider, pointToken, user);
const authorized = await isMinter(provider, pointToken, signerAddress);

References

License

Apache-2.0