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

@apex_labs/sdk

v0.7.4

Published

TypeScript SDK for Apex CL pools, Classic pools, routing, farms, ApexVault, and contract ABIs on Robinhood Chain.

Readme

Apex SDK

Production TypeScript SDK for Apex CL and Classic pools on Robinhood Chain. It provides canonical chain definitions, explicit deployment-state handling, Apex fee tiers, deterministic pool address helpers, Classic AMM quoting, mixed CL/Classic path encoding, transaction builders, and provenance-tracked contract ABIs.

Supported execution environments are Node.js 22 or newer and modern bundlers. Both ESM and CommonJS package entry points are verified in CI.

Setup

npm ci
npm run verify

Install the published package:

npm install @apex_labs/sdk

Chains and deployments

The SDK exports chain definitions for Robinhood Chain mainnet (4663), Robinhood Chain testnet (46630), and local Anvil (31337):

import {
  ROBINHOOD_TESTNET_CHAIN_ID,
  getApexDeployment,
  robinhoodTestnet,
} from "@apex_labs/sdk";

const state = getApexDeployment(ROBINHOOD_TESTNET_CHAIN_ID);

if (!state || state.status === "unavailable") {
  // Render a deliberate unavailable state. Never substitute stale addresses.
  console.info(state?.message ?? "unsupported chain");
} else {
  const apex = state.config;
  console.info(robinhoodTestnet.name, apex.clFactory);
}

No Apex contract deployment is bundled for Robinhood Chain yet. Address-bearing entries will only be added from a verified deployment manifest. For an ephemeral local deployment, load the generated manifest into an ApexDeploymentConfig instead of committing local addresses:

import type { ApexDeploymentConfig } from "@apex_labs/sdk";

export const apex: ApexDeploymentConfig = {
  chainId: 31337,
  clFactory: "0x...",
  clPoolDeployer: "0x...",
  clSwapRouter: "0x...",
  clQuoter: "0x...",
  clNonfungiblePositionManager: "0x...",
  clMasterChef: "0x...",
  smartRouter: "0x...",
  mixedQuoter: "0x...",
  classicFactory: "0x...",
  classicPairInitCodeHash: "0x...",
  classicRouter: "0x...",
  classicChef: "0x...",
  weth: "0x...",
};

Core exports

import {
  CLFeeAmount,
  APEX_CL_POOL_INIT_CODE_HASH,
  APEX_CLASSIC_PAIR_INIT_CODE_HASH,
  apexDeployments,
  robinhoodMainnet,
  robinhoodTestnet,
  getApexDeployment,
  requireApexDeployment,
  ClassicChef,
  ClassicRouter,
  CLMasterChef,
  SmartRouter,
  ApexVault,
  getUserStakedApexVaultNFTs,
  getCLTickSpacing,
  createCLPool,
  computeCLPoolAddress,
  getCLPoolStatus,
  getClassicPoolStatus,
  computeClassicPoolAddress,
  quoteClassicExactInput,
  encodeMixedRouteToPath,
  encodeMixedRouteToQuoteParams,
  decodeMixedRoutePath,
  parseProtocolFeePacked,
  clPoolAbi,
  clFactoryAbi,
  clMasterChefAbi,
  nonfungiblePositionManagerAbi,
  swapRouterAbi,
  smartRouterAbi,
  mixedQuoterAbi,
  quoterV2Abi,
  classicFactoryAbi,
  classicPoolAbi,
  classicRouterAbi,
  Pool,
  Route,
  Trade,
  SwapRouter,
  NonfungiblePositionManager,
  SwapQuoter,
  TickMath,
  nearestUsableTick,
  encodeSqrtRatioX96,
} from "@apex_labs/sdk";

CL pool address

const pool = computeCLPoolAddress({
  config: apex,
  tokenA,
  tokenB,
  fee: CLFeeAmount.FEE_0_30,
});

CL pool status

Use getCLPoolStatus before deciding whether the UI should show fee unsupported, create pool, initialize pool, or add liquidity. Pass wrapped-native addresses for native pairs; for example, use WETH instead of an ETH placeholder.

const result = await getCLPoolStatus({
  client: publicClient,
  config: apex,
  tokenA: apex.weth,
  tokenB: usdt0.address,
  fee: CLFeeAmount.FEE_0_30,
});

if (result.status === "UNSUPPORTED_FEE") showUnsupportedFee();
if (result.status === "NOT_CREATED") showCreatePool();
if (result.status === "NOT_INITIALIZED") showInitializePool(result.pool);
if (result.status === "READY") showAddLiquidity(result.pool);

CL pool object

const pool = createCLPool({
  tokenA,
  tokenB,
  fee: CLFeeAmount.FEE_0_30,
  sqrtRatioX96,
  liquidity,
  tickCurrent,
  ticks,
});

createCLPool returns an Apex CL Pool model with Apex fee tiers installed. Position, trade, route, quoter, swap-router, and NFT-position-manager logic should use the classes exported by @apex_labs/sdk.

Periphery ABIs

Use the exact ABIs exported from compiled Apex contracts:

import {
  nonfungiblePositionManagerAbi,
  swapRouterAbi,
  quoterAbi,
  quoterV2Abi,
  smartRouterAbi,
  mixedQuoterAbi,
  tickLensAbi,
  interfaceMulticallAbi,
  clFactoryAbi,
  clMasterChefAbi,
  clPoolAbi,
  classicFactoryAbi,
  classicPoolAbi,
  classicRouterAbi,
} from "@apex_labs/sdk";

Classic pair address

const stablePair = computeClassicPoolAddress({
  config: apex,
  tokenA: tokenA.address,
  tokenB: tokenB.address,
  stable: true,
});

Classic quote

const amountOut = quoteClassicExactInput({
  amountIn,
  tokenIn: tokenA.address,
  token0,
  token1,
  reserve0,
  reserve1,
  token0Decimals: 18,
  token1Decimals: 18,
  stable: false,
  fee,
});

Classic pool status

Use getClassicPoolStatus before deciding whether the UI should show create, first-liquidity, or normal add-liquidity states. Pass wrapped-native addresses for native pairs; for example, use WETH instead of an ETH placeholder.

const result = await getClassicPoolStatus({
  client: publicClient,
  config: apex,
  tokenA: apex.weth,
  tokenB: usdt0.address,
  stable: false,
});

if (result.status === "NOT_CREATED") showCreateOrAddFirstLiquidity();
if (result.status === "NEEDS_LIQUIDITY") showAddFirstLiquidity(result.pool);
if (result.status === "READY") showAddLiquidity(result.pool);

Wallet position discovery

Use these helpers to list wallet-held liquidity positions. They do not include Classic LP staked in ClassicChef or CL NFTs staked in CLMasterChef.

const positions = await getUserWalletLiquidityPositions({
  client: publicClient,
  config: apex,
  account,
});

positions.classic; // wallet-held Classic pair LP positions
positions.cl; // wallet-held CL NFT positions

If the UI wants separate reads, call:

await getUserClassicWalletLiquidityPositions({
  client: publicClient,
  config: apex,
  account,
});

await getUserCLWalletLiquidityPositions({
  client: publicClient,
  config: apex,
  account,
});

Classic liquidity

Use ClassicRouter for all Classic add/remove liquidity UX. Do not raw-transfer tokens to Classic pairs from the frontend. addLiquidity and addLiquidityETH create the pair if it does not exist, pull both assets atomically, and mint LP in one transaction.

const add = ClassicRouter.addLiquidityCallParameters({
  tokenA: apex.weth,
  tokenB: usdt0.address,
  stable: false,
  amountADesired,
  amountBDesired,
  amountAMin,
  amountBMin,
  to: account,
  deadline,
});

await walletClient.sendTransaction({
  to: apex.classicRouter,
  data: add.calldata,
  value: BigInt(add.value),
});

For native ETH deposits, use the ETH helper. The router wraps only the used ETH amount and refunds excess ETH.

const addETH = ClassicRouter.addLiquidityETHCallParameters({
  token: usdt0.address,
  stable: false,
  amountTokenDesired,
  amountETHDesired,
  amountTokenMin,
  amountETHMin,
  to: account,
  deadline,
});

Deployments whose ClassicRouter is wired to ClassicChef also support atomic add-and-stake helpers. These mint LP to the router and immediately call ClassicChef.depositFor(pid, liquidity, account), so the user receives a farm position instead of wallet LP. The pair must already be registered in ClassicChef; otherwise the transaction reverts.

These selectors require the next ClassicRouter deployment that exposes classicChef(). Do not call them against an older router address.

const addAndStake = ClassicRouter.addLiquidityAndStakeCallParameters({
  tokenA: apex.weth,
  tokenB: usdt0.address,
  stable: false,
  amountADesired,
  amountBDesired,
  amountAMin,
  amountBMin,
  to: account,
  deadline,
});

const addETHAndStake = ClassicRouter.addLiquidityETHAndStakeCallParameters({
  token: usdt0.address,
  stable: false,
  amountTokenDesired,
  amountETHDesired,
  amountTokenMin,
  amountETHMin,
  to: account,
  deadline,
});

Remove paths are also router-only:

const remove = ClassicRouter.removeLiquidityCallParameters({
  tokenA: apex.weth,
  tokenB: usdt0.address,
  stable: false,
  liquidity,
  amountAMin,
  amountBMin,
  to: account,
  deadline,
});

const removeETH = ClassicRouter.removeLiquidityETHCallParameters({
  token: usdt0.address,
  stable: false,
  liquidity,
  amountTokenMin,
  amountETHMin,
  to: account,
  deadline,
});

For LP already staked in ClassicChef, use the Chef helpers. The transaction target is apex.classicChef, not apex.classicRouter: the Chef must see the user as msg.sender, withdraw the staked LP to the router, then the router burns it during the trusted callback. These selectors require the next ClassicChef/ClassicRouter deployment that exposes withdrawToAndCall and onClassicChefWithdraw.

const unstakeAndRemove = ClassicChef.withdrawAndRemoveLiquidityCallParameters({
  pid,
  classicRouter: apex.classicRouter,
  tokenA: apex.weth,
  tokenB: usdt0.address,
  stable: false,
  liquidity,
  amountAMin,
  amountBMin,
  to: account,
  deadline,
});

await walletClient.sendTransaction({
  to: apex.classicChef,
  data: unstakeAndRemove.calldata,
  value: BigInt(unstakeAndRemove.value),
});

const unstakeAndRemoveETH = ClassicChef.withdrawAndRemoveLiquidityETHCallParameters({
  pid,
  classicRouter: apex.classicRouter,
  token: usdt0.address,
  stable: false,
  liquidity,
  amountTokenMin,
  amountETHMin,
  to: account,
  deadline,
});

Classic farm UX

Use ClassicChef for Classic LP staking. Pools are pid-based; read ClassicChef.pidOf(pair) from the exported ABI or an indexed farm list, then encode normal stake/claim/exit calls:

const stakeClassic = ClassicChef.depositCallParameters({
  pid,
  amount,
});

const stakeClassicFor = ClassicChef.depositForCallParameters({
  pid,
  amount,
  account,
});

const claimClassic = ClassicChef.harvestCallParameters({
  pid,
});

const exitClassic = ClassicChef.withdrawCallParameters({
  pid,
  amount,
});

const exitClassicTo = ClassicChef.withdrawToCallParameters({
  pid,
  amount,
  to: recipient,
});

Mixed CL/Classic path

const path = encodeMixedRouteToPath([
  {
    kind: "CL",
    tokenIn: tokenA.address,
    tokenOut: tokenB.address,
    fee: CLFeeAmount.FEE_0_30,
  },
  { kind: "CLASSIC_STABLE", tokenIn: tokenB.address, tokenOut: tokenC.address },
]);

Quote that path through MixedQuoter.quoteExactInput(path, amountIn), then execute it with SmartRouter. The high-level helpers encode the swap, then wrap it in SmartRouter.multicall(deadlineOrPreviousBlockhash, data).

const swap = SmartRouter.mixedExactInputCallParameters(
  { hops, recipient: account, amountIn, amountOutMinimum },
  { deadlineOrPreviousBlockhash: deadline },
);

For MixedQuoter.quoteExactInput, encode the route path and Classic-hop flags:

const { path, flags } = encodeMixedRouteToQuoteParams(hops);
const quote = await publicClient.readContract({
  address: apex.mixedQuoter,
  abi: mixedQuoterAbi,
  functionName: "quoteExactInput",
  args: [path, flags, amountIn],
});

For routes split by protocol:

// Classic volatile section
SmartRouter.classicExactInputCallParameters(
  { amountIn, amountOutMin, path, recipient },
  { deadlineOrPreviousBlockhash: deadline },
);

// Classic stable section
SmartRouter.stableExactInputCallParameters(
  { amountIn, amountOutMin, path, flags, recipient },
  { deadlineOrPreviousBlockhash: deadline },
);

// CL section
SmartRouter.exactInputCallParameters(
  { path, recipient, amountIn, amountOutMinimum },
  { deadlineOrPreviousBlockhash: deadline },
);

Use the raw encode* helpers only when deliberately assembling a custom multicall. User-facing swaps should use *CallParameters, or SmartRouter.swapCallParameters(rawCalls, options) for custom batches. These require a deadline or previous blockhash.

When value is nonzero, SmartRouter call-parameter helpers append refundETH() automatically. Token sweeps and WETH unwraps require explicit cleanup entries because the SDK cannot infer the intended token, recipient, or minimum amount for every route.

The mixed periphery is exact-input only. Use the CL-only SwapRouter/QuoterV2 for CL exact-output routes.

CL farm UX

Use CLMasterChef for staked CL positions. It encodes the supported paths without requiring applications to assemble Chef calls directly.

const { calldata, value } = CLMasterChef.mintAndStakeCallParameters(
  {
    token0: tokenA.address,
    token1: tokenB.address,
    fee: CLFeeAmount.FEE_0_30,
    tickLower,
    tickUpper,
    amount0Desired,
    amount1Desired,
    amount0Min,
    amount1Min,
    account,
    deadline,
  },
  {
    stakingReceiver: apex.clMasterChef,
    nonfungiblePositionManager: apex.clNonfungiblePositionManager,
  },
);

mintAndStakeCallParameters takes account and encodes it as the staked NFT owner. It rejects the Chef and NFP manager as owners so callers cannot strand positions in protocol contracts.

When minting with native ETH, pass value in the options object. The SDK appends refundETH() in the same NFP multicall only when value is nonzero.

For fee collection on staked NFTs, prefer collectCallParameters or decreaseAndCollectCallParameters:

const collect = CLMasterChef.collectCallParameters({
  tokenId,
  recipient: account,
});

const exitPart = CLMasterChef.decreaseAndCollectCallParameters(
  { tokenId, liquidity, amount0Min, amount1Min, deadline },
  { tokenId, recipient: account },
);

These helpers call CLMasterChef.multicall(...) and use collectTo with the inner collect recipient set to address(0), so Chef receives the tokens first and immediately sweeps/unwraps them to the final recipient.

For a full exit, use closeAndBurnCallParameters:

const close = CLMasterChef.closeAndBurnCallParameters(
  { tokenId, liquidity, amount0Min, amount1Min, deadline },
  { tokenId, recipient: account },
);

This encodes harvest -> decreaseLiquidity -> collectTo -> burn. The harvest happens before liquidity is removed, so it is safe even when rewards are zero and keeps burn compatible with strict empty-position semantics.

ApexVault UX

Use ApexVault for user-facing VeApexToken actions. Users choose reward-token percentages and a compound percentage. Compound is not user-claimable APEX; the user config selects the compound bucket, and the protocol operator later calls the controller and ApexVault compound functions.

const approveVault = ApexVault.encodeVeApexTokenApprove({
  tokenId,
  spender: apexVault,
});

const stake = ApexVault.stakeCallParameters(tokenId, {
  rewardConfigs: [
    { rewardToken: weth, bps: 5_000 },
    { rewardToken: usdc, bps: 2_000 },
  ],
  compoundBps: 3_000,
});

The helper rejects configs that do not sum to exactly 10_000 bps, repeat a reward token, use zero bps for a reward token, or use the zero address as a reward token.

To list a user's current staked veNFTs, call the SDK helper:

const tokenIds = await getUserStakedApexVaultNFTs({
  client: publicClient,
  vault: apex.apexVault,
  owner: account,
});

Pass activeOnly: true if the UI should hide vault positions that are still custodied but no longer active for rewards, such as positions flushed after becoming ineligible.

This is a current-state read and does not parse logs. Because ApexVault currently exposes positionOwner(tokenId) but not a direct tokensOfOwner(owner) view, the helper scans VeApexToken.tokenOfOwnerByIndex(vault, index) across the vault's custodied veNFTs and filters each token ID with ApexVault.

For an existing staked veNFT:

const updateConfig = ApexVault.setUserConfigCallParameters(tokenId, {
  rewardConfigs: [{ rewardToken: weth, bps: 7_000 }],
  compoundBps: 3_000,
});

const claim = ApexVault.claimCallParameters({
  tokenId,
  rewardTokens: [weth, usdc],
});

const extend = ApexVault.extendLockCallParameters({
  tokenId,
  lockDuration: 26n * 7n * 86_400n,
});

const increase = ApexVault.increaseAmountCallParameters({
  tokenId,
  amount,
});

ApexVault is custodial while a veNFT is staked. For staked veNFTs, route amount increases through ApexVault.increaseAmountCallParameters(...), not raw VeApexToken.depositFor(...), so ApexVault reward weights are refreshed. A direct VeApexToken transfer outside ApexVault is a normal NFT transfer and will not create vault position accounting; treat ApexVault stake and unstake as the canonical earning path.

ABI refresh

After Solidity changes, build the canonical contracts repository and pass its explicit Forge artifact directory and Git commit to the SDK. The sync command never traverses to a parent repository or guesses an artifact source:

forge build
npm run sync:abis -- \
  --artifacts-dir /absolute/path/to/apex-contracts/out \
  --source-commit "$(git -C /absolute/path/to/apex-contracts rev-parse HEAD)" \
  --repository giga-dex/apex-contracts
npm run verify

Use the same command with --check in an artifact-integration job to detect ABI drift without writing. src/abi/provenance.json records the exact ABI hashes and contract source used for each SDK release.

Notes

  • Apex periphery exports include NonfungiblePositionManager, SwapRouter, SmartRouter, Quoter, QuoterV2, MixedQuoter, TickLens, and InterfaceMulticall.
  • MixedQuoter supports quoteExactInput(bytes,uint256) and quoteExactInput(bytes,uint256[],uint256) for Classic+CL route discovery.
  • Apex CL pool addresses use the Apex CLPoolDeployer, not the factory, matching the contract salt keccak256(abi.encode(token0, token1, fee)).
  • Apex Classic pair addresses use the Classic factory salt keccak256(abi.encodePacked(token0, token1, stable)).
  • APEX_CL_POOL_INIT_CODE_HASH and APEX_CLASSIC_PAIR_INIT_CODE_HASH are exported and used by default; override them only if the compiled contract bytecode changes.
  • Apex Classic fees are denominated by 1_000_000.
  • Apex CL protocol fees are denominated by 10_000, so 10_000 means 100%.
  • Classic protocol fees are first realized by collectClassicProtocolFees(pair), which burns FeeCenter-held fee LP into raw pair tokens. pushAndSplitFees(tokens) only splits raw token balances that are already in FeeCenter.