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

propamm

v1.2.1

Published

TypeScript SDK for interacting with the PropAMM contracts

Readme

PropAMM TypeScript SDK

SDK for interacting with the PropAMMRouter contract over JSON-RPC.

Setup

pnpm install
pnpm build      # compile to dist/
pnpm typecheck

Getting started

Quote and swap 1 ETH for USDC through the best venue:

import { ContractClient } from "propamm/client";
import { PropAmmRouter } from "propamm/router";
import { ETH_SENTINEL, USDC } from "propamm/common/tokens";
import { applySlippage, deadlineIn, formatUnits, parseEther } from "propamm/common/helpers";
import { mainnet } from "propamm/common/chains";
import { privateKeyToAccount } from "propamm/common/accounts";

const account = privateKeyToAccount("0x...");
const client = ContractClient.fromRpc({ rpcUrl: "https://...", chain: mainnet, account });
const router = new PropAmmRouter(client); // defaults to the mainnet router proxy

const amountIn = parseEther("1");
const { amountOut } = await router.quote(ETH_SENTINEL, USDC, amountIn);

const result = await router.swapAndWait({
  tokenIn: ETH_SENTINEL,
  tokenOut: USDC,
  amountIn,
  amountOutMin: applySlippage(amountOut, 50), // quote - 0.5%
  recipient: account.address,
  deadline: deadlineIn(300), // now + 5 min
});
console.log(`received ${formatUnits(result.amountOut, 6)} USDC via ${result.executedVenue}`);

A runnable version of this example lives in examples/getting-started.ts:

pnpm build
node examples/getting-started.ts   # Node >= 22.18 runs TypeScript directly

It defaults to a local anvil mainnet fork with anvil's funded account and the mainnet router deployment; override with RPC_URL / PRIVATE_KEY / ROUTER_ADDRESS / SLIPPAGE_BPS.

Usage

import { ContractClient } from "propamm/client";
import { PropAmmRouter } from "propamm/router";
import { ETH_SENTINEL, USDC, WETH } from "propamm/common/tokens";
import { PAMMS } from "propamm/common/pamms";
import { applySlippage, deadlineIn, parseEther, parseUnits } from "propamm/common/helpers";
import { mainnet } from "propamm/common/chains";
import { privateKeyToAccount } from "propamm/common/accounts";

const client = ContractClient.fromRpc({
  rpcUrl: "http://localhost:8545",
  chain: mainnet,
  account: privateKeyToAccount("0x..."), // omit for read-only (quotes/views still work)
});
const router = new PropAmmRouter(client, "0x..."); // pass an explicit proxy; omit for the mainnet default

// Quote, approve, swap, then wait
const amountIn = parseUnits("100", 6); // 100 USDC
const { amountOut, venue } = await router.quote(USDC, WETH, amountIn);

await router.approve(USDC, amountIn); // ERC-20 input requires router allowance

const hash = await router.swap({
  tokenIn: USDC,
  tokenOut: WETH,
  amountIn,
  amountOutMin: applySlippage(amountOut, 50), // quote - 0.5%
  recipient: me,
  deadline: deadlineIn(300), // now + 5 min
});
const result = await router.waitForSwap(hash);
// { hash, receipt, amountIn, amountOut, executedVenue, recipient, fee? }

// One-shot: swap + wait combined, pinned to a single venue
const res = await router.swapAndWait(
  {
    tokenIn: USDC,
    tokenOut: WETH,
    amountIn,
    amountOutMin: minOut,
    recipient: me,
    deadline: deadlineIn(300),
  },
  { venues: [PAMMS.kipseli] },
);

// Native ETH input: sentinel token, msg.value attached automatically
await router.swapAndWait({
  tokenIn: ETH_SENTINEL,
  tokenOut: USDC,
  amountIn: parseEther("1"),
  amountOutMin: minOut,
  recipient: me,
  deadline: deadlineIn(300),
});

// Frontend fee: optional, bps validated in [1, MAX_FEE_BPS]
await router.swap(params, { frontendFee: { bps: 25, recipient: feeRecipient } });

// Pinned / selected-venue quotes
const pinned = await router.quote(USDC, WETH, amountIn, { venues: [PAMMS.fermi] });
const subset = await router.quote(USDC, WETH, amountIn, { venues: [PAMMS.fermi, PAMMS.bebop] });

// Views
await router.getWhitelistedVenues();
await router.paused();
await router.fallbackSwapRouter(); // Uniswap fallback "venue" address (dynamic, not in PAMMS)

Browser wallets (wagmi / MetaMask)

In a browser app the viem clients come from the connected wallet, not an rpcUrl. Build the client with ContractClient.fromClients and use it exactly like the router above. Writes are then signed by the wallet (MetaMask, WalletConnect, ...) over its own transport.

import { ContractClient } from "propamm/client";
import { usePublicClient, useWalletClient } from "wagmi";

// viem public client for reads and quote simulations
const publicClient = usePublicClient();

// viem wallet client for writes, signing through the connected wallet (MetaMask)
const { data: walletClient } = useWalletClient();

// SDK client backed by the wallet's viem clients
const client = ContractClient.fromClients({ publicClient, walletClient });

walletClient is undefined until a wallet connects; omit it (pass only publicClient) for a read-only client that can still quote and read views.

State overrides

The pAMM venues price off-chain liquidity that on-chain state does not reflect, so a plain eth_call quote sees stale prices. Titan publishes fresh state overrides, and quotes apply them automatically: the simulation carries the overrides plus their block number/timestamp so venues price their pushed state instead of the chain's.

Two sources are available; both need no authentication:

  • OverridesWsSource — streams wss://rpc.titanbuilder.xyz/ws/pamm_quote_stream, caching per-pAMM entries across frames and reconnecting with backoff. This is the default: a router constructed without options creates one (connecting lazily on the first quote). The socket auto-closes after an idle window without quotes (idleTimeoutMs, default 30s; 0 closes after each quote, Infinity never) and reconnects transparently, so no teardown is needed — close() exists for immediate, permanent shutdown.
  • OverridesRpcSource — calls titan_getPammStateOverrides over HTTP on each quote. No connection to manage.
import { OverridesRpcSource, OverridesWsSource } from "propamm/overrides";

// default: streaming WS source created automatically
const router = new PropAmmRouter(client, ROUTER);

// or attach a source explicitly
const rpcRouter = new PropAmmRouter(client, ROUTER, {
  overrides: new OverridesRpcSource(),
});

// per-call control
await router.quote(WETH, USDC, amountIn, { overrides: null }); // skip overrides
await router.quote(WETH, USDC, amountIn, { overrides: rpcSource }); // one-off source

Admin functions (addVenue, pause, setPairFee, ...) have no typed methods, but they are in the exported ABI — call them through the generic client:

import { propAmmRouterAbi } from "propamm/router/abi";

await client.write({
  address: router.address,
  abi: propAmmRouterAbi,
  functionName: "addVenue",
  args: [newVenue],
});

Price levels

Alongside the raw state overrides, Titan publishes prices it has already quoted, grouped per pAMM: for each pair, an orderBook of rungs mapping an input amount to the output it would receive. This lets a taker read prices across a range of trade sizes without an eth_call per size. Rungs are either Simulated (from an EVM simulation) or Interpolated (a linear spline between simulated rungs, for finer granularity).

The PriceLevels client wraps it, mirroring PropAmmRouter: a single class with a default snapshot source you can override in the constructor.

import { PriceLevels } from "propamm/prices";
import { USDC, WETH } from "propamm/common/tokens";
import { parseUnits } from "propamm/common/helpers";

const prices = new PriceLevels(); // default: one-shot HTTP snapshot source

const snapshot = await prices.getPriceLevels();
// snapshot.pamms[i].pairs[j].orderBook -> [{ amountIn, amountOut, variant }, ...]

// Quote helpers are served from Titan's latest snapshot over HTTP, skipping the
// on-chain eth_call that router.quote runs.
const best = await prices.getQuote(USDC, WETH, parseUnits("1000", 6));
// { tokenIn, tokenOut, amountIn, amountOut, pamm, router, blockNumber, slot }
const pinned = await prices.getQuoteVenue(best.pamm, USDC, WETH, parseUnits("1000", 6));

The snapshot source defaults to a PriceLevelsRpcSource (one titan_getPammPriceLevels call per getPriceLevels). For a live feed, pass a PriceLevelsWsSource instead — it streams complete snapshots, reconnects with backoff, and idle auto-closes, like OverridesWsSource. The stream is served from regional hosts (eu., ap., us.); pick the nearest:

import { PriceLevels, PriceLevelsWsSource } from "propamm/prices";

const prices = new PriceLevels({
  source: new PriceLevelsWsSource({ url: "wss://eu.rpc.titanbuilder.xyz/ws/pamm_price_levels" }),
});
const snapshot = await prices.getPriceLevels(); // served from the live stream
prices.close(); // close the stream socket when done (no-op for the HTTP default)

The quote helpers (getQuote / getQuoteVenue) are HTTP-only and default to https://rpc.titanbuilder.xyz. When pairing a PriceLevelsWsSource with a private or regional deployment, pass rpcUrl to route quotes to the same host:

const prices = new PriceLevels({
  source: new PriceLevelsWsSource({ url: "wss://us.rpc.titanbuilder.xyz/ws/pamm_price_levels" }),
  rpcUrl: "https://us.rpc.titanbuilder.xyz",
});

Passing a PriceLevelsRpcSource with a custom url instead points both snapshots and quotes at that endpoint (and rpcUrl is ignored).

A runnable version lives in examples/price-levels.ts (node examples/price-levels.ts; override the HTTP endpoint with PRICE_LEVELS_URL).

Layout

Source modules map 1:1 to import paths (src/<path>.tspropamm/<path>):

  • src/client.ts — generic viem-based contract client (read/call/write/waitForTransaction); call accepts state and block overrides; ContractClient.fromClients builds one from prebuilt viem clients (browser wallets).
  • src/router/index.tsPropAmmRouter bindings (quote, swap, swapAndWait, waitForSwap, approve/allowance, views) plus MAX_FEE_BPS.
  • src/router/abi.ts — human-readable PropAMMRouter ABI (functions, events, errors).
  • src/overrides/index.ts — pAMM state-override sources (OverridesWsSource, OverridesRpcSource), payload parsing, and toStateOverride.
  • src/prices/index.tsPriceLevels client plus its swappable snapshot sources (PriceLevelsWsSource, PriceLevelsRpcSource), snapshot parsing, and the Titan quote helpers (getQuote, getQuoteVenue).
  • src/common/tokens.tsETH_SENTINEL and mainnet token addresses.
  • src/common/pamms.tsPAMMS name → venue address mapping.
  • src/common/helpers.tsapplySlippage, deadlineIn, and unit conversion (parseEther, parseUnits, formatEther, formatUnits).
  • src/common/chains.ts, src/common/accounts.ts — viem re-exports.

The on-chain quote functions are nonpayable (not view), so the bindings call them through eth_call simulation (ContractClient.call) rather than a plain read.