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

@nradko/metricamm-sdk

v0.0.1

Published

TypeScript SDK for interacting with MetricAMM contracts using viem

Readme

@nradko/metricamm-sdk

TypeScript SDK for interacting with MetricAMM contracts using viem.

THIS IS A NON PRODUCTION READY RELEASE

This Readme file was AI generated

Features

  • ✅ Pool state reading and management
  • MetricAmmPoolStateView integration for efficient off-chain reads
  • ✅ Direct liquidity position management
  • ✅ Swap Router with slippage protection
  • ✅ Bin data packing utilities
  • ✅ Price conversion helpers (Q64 format)
  • ✅ TypeScript types for all contract interfaces
  • ✅ Pre-bundled contract ABIs with full TypeScript inference
  • Liquidity removal by percentage

Installation

npm install @nradko/metricamm-sdk viem

or

yarn add @nradko/metricamm-sdk viem

or

pnpm add @nradko/metricamm-sdk viem

Quick Start

1. Import the SDK

import * as sdk from "@nradko/metricamm-sdk";
import { createPublicClient, createWalletClient, http } from "viem";

2. Setup Clients

const publicClient = createPublicClient({
  transport: http("https://rpc.example.com"),
});

const walletClient = createWalletClient({
  account: yourAccount,
  transport: http("https://rpc.example.com"),
});

3. Read Pool State

// Option 1: Direct pool reads
const immutables = await sdk.getPoolImmutables(publicClient, poolAddress);
const state = await sdk.getPoolState(publicClient, poolAddress);
const binState = await sdk.getBinState(publicClient, poolAddress, 0);

// Option 2: Using StateView (recommended for efficiency)
const stateViewAddress = "0x..."; // MetricAmmPoolStateView address

// Get all slot0 data in one call
const slot0 = await sdk.getSlot0(publicClient, stateViewAddress, poolAddress);
console.log(`Current Bin: ${slot0.curBinIdx}`);
console.log(`Position: ${slot0.curPosInBin}`);
console.log(`Drift: ${slot0.driftE8 / 1e6}%`);

// Get performance fees (in external token decimals)
const slot1 = await sdk.getSlot1(publicClient, stateViewAddress, poolAddress);
console.log(
  `Collected fees: ${slot1.performanceFeeAmount0}, ${slot1.performanceFeeAmount1}`,
);

// Batch read multiple bins efficiently
const binIndices = [-2, -1, 0, 1, 2];
const binStates = await sdk.getBinStatesExternal(
  publicClient,
  stateViewAddress,
  poolAddress,
  binIndices,
);
console.log(`Read ${binStates.token0Balances.length} bins in one call`);

// Get single bin with balances in external units
const binExternal = await sdk.getBinStateExternal(
  publicClient,
  stateViewAddress,
  poolAddress,
  0,
);
console.log(`Token0: ${binExternal.token0Balance} (native decimals)`);

4. Execute a Swap

// Setup swap parameters
const swapParams: sdk.SwapExactInputParams = {
  pool: poolAddress,
  recipient: userAddress,
  zeroForOne: true, // token0 -> token1
  amountIn: parseEther("1"),
  priceLimitX64: sdk.MAX_UINT128, // No limit
  minAmountOut: parseEther("0.99"), // 1% slippage tolerance
  deadline: BigInt(Date.now() / 1000 + 300), // 5 minutes
};

// Execute swap
const hash = await sdk.swapExactInput(
  publicClient,
  walletClient,
  routerAddress,
  swapParams,
  userAddress,
);

// Wait for confirmation
await publicClient.waitForTransactionReceipt({ hash });

5. Add Liquidity

// Define liquidity deltas (positive to add, negative to remove)
const deltas: sdk.LiquidityDelta[] = [
  { bin: -1, deltaShares: 1000n },
  { bin: 0, deltaShares: 2000n },
  { bin: 1, deltaShares: 1000n },
];

// Add liquidity to a position
// Note: You need to approve token transfers before calling this
const { hash, amount0Delta, amount1Delta } = await sdk.modifyLiquidity(
  publicClient,
  walletClient,
  poolAddress,
  0n, // salt - unique identifier for your position
  deltas,
  userAddress,
  sdk.NO_SLIPPAGE_LIMIT, // or specify max token0 to spend
  sdk.NO_SLIPPAGE_LIMIT, // or specify max token1 to spend
);

console.log(`Transaction: ${hash}`);
console.log(`Token0 spent: ${amount0Delta}`);
console.log(`Token1 spent: ${amount1Delta}`);

6. Query Position Shares

// Get all shares for a position across bins
const positionShares = await sdk.getPositionShares(
  publicClient,
  stateViewAddress,
  poolAddress,
  ownerAddress, // Position owner
  0n, // salt
  -10, // lowerBin
  10, // upperBin
);

for (const { binIdx, shares } of positionShares) {
  console.log(`Bin ${binIdx}: ${shares} shares`);
}

7. Remove Liquidity by Percentage

// Remove 50% of liquidity from a position
const { hash } = await sdk.removeLiquidity(
  publicClient,
  walletClient,
  stateViewAddress,
  poolAddress,
  ownerAddress,
  0n, // salt
  userAddress, // account executing tx
  { percentageToRemove: 50 },
);

// Remove 100% (all liquidity) from specific bin range
const { hash: hash2 } = await sdk.removeLiquidity(
  publicClient,
  walletClient,
  stateViewAddress,
  poolAddress,
  ownerAddress,
  0n, // salt
  userAddress,
  {
    percentageToRemove: 100,
    lowerBin: -5,
    upperBin: 5,
  },
);

// Or prepare the deltas manually for more control
const deltas = await sdk.prepareRemoveLiquidityDeltas(
  publicClient,
  stateViewAddress,
  poolAddress,
  ownerAddress,
  0n, // salt
  { percentageToRemove: 25 },
);

// Then execute with modifyLiquidity
const { hash: hash3 } = await sdk.modifyLiquidity(
  publicClient,
  walletClient,
  poolAddress,
  0n, // salt
  deltas,
  userAddress,
);

Utility Functions

Bin Data Packing

// Create uniform bins for pool deployment
const bins = sdk.createUniformBins(
  520, // Total bins
  10_000, // Length: 1% per bin (10000 / 1e6)
  0, // No additional buy fee
  0, // No additional sell fee
);

// Pack into uint256 array (5 bins per uint256)
const packed = sdk.packBinDataArray(bins);

// Unpack a single bin
const binData = sdk.unpackBinData(packed[0]);
console.log(`Length: ${binData.lengthE6}`);
console.log(`Buy Fee: ${binData.addFeeBuyE6}`);
console.log(`Sell Fee: ${binData.addFeeSellE6}`);

Price Conversion

// Convert regular price to Q64 format
const priceQ64 = sdk.priceToQ64(1.5); // 1.5 as Q64

// Convert Q64 back to regular price
const price = sdk.q64ToPrice(priceQ64); // 1.5

Position Queries

// Get user's shares in a specific bin
const shares = await sdk.getPositionBinShares(
  publicClient,
  poolAddress,
  userAddress,
  salt, // Position salt (uint72)
  binIndex, // Bin index (int24)
);

console.log(`User has ${shares} shares in bin ${binIndex}`);

Complete Examples

See scripts/examples/sdk-usage.ts for comprehensive examples covering:

  • Reading pool state
  • Adding/removing liquidity
  • Executing swaps (all types)
  • Creating bin data for deployment
  • Querying positions

Type Reference

LiquidityDelta

interface LiquidityDelta {
  bin: number; // int24: Bin index
  deltaShares: bigint; // int104: Positive = add, negative = remove
}

RemoveLiquidityParams

interface RemoveLiquidityParams {
  percentageToRemove: number; // 0-100: Percentage of position to remove
  lowerBin?: number; // Lower bin index (default: -10)
  upperBin?: number; // Upper bin index (default: 10)
}

BinData

interface BinData {
  lengthE6: number; // uint16: Bin width (1e6 = 100%)
  addFeeBuyE6: number; // uint16: Additional buy fee
  addFeeSellE6: number; // uint16: Additional sell fee
}

BinState

interface BinState {
  token0BalanceScaled: bigint; // uint104: Scaled token0 balance
  token1BalanceScaled: bigint; // uint104: Scaled token1 balance
  lengthE6: number; // uint16: Bin width
  addFeeBuyE6: number; // uint16: Buy fee adjustment
  addFeeSellE6: number; // uint16: Sell fee adjustment
}

// External units (from StateView)
interface BinStateExternal {
  token0Balance: bigint; // Unscaled, in native token decimals
  token1Balance: bigint; // Unscaled, in native token decimals
  lengthE6: number;
  addFeeBuyE6: number;
  addFeeSellE6: number;
}

Slot0 and Slot1

// All slot0 values in one call
interface Slot0 {
  curBinIdx: number; // Current bin index
  curPosInBin: bigint; // Position within bin (0 to MAX_UINT104)
  curBinDistFromProvidedPriceE6: number; // Distance in units
  driftE8: number; // Current drift (1e8 = 100%)
  performanceFeeE6: number; // Performance fee (1e6 = 100%)
}

// Performance fee amounts
interface Slot1 {
  performanceFeeAmount0: bigint; // Collected token0 fees (external units)
  performanceFeeAmount1: bigint; // Collected token1 fees (external units)
}

FeeConfig

interface FeeConfig {
  adminAddr: Address; // Admin address
  protocolFee: number; // Protocol fee (1e6 = 100%)
  adminFee: number; // Admin fee (1e6 = 100%)
  adminFeeDest: Address; // Admin fee destination address
}

SwapExactInputParams

interface SwapExactInputParams {
  pool: Address;
  recipient: Address;
  zeroForOne: boolean; // true = token0->token1, false = token1->token0
  amountIn: bigint; // Input amount
  priceLimitX64: bigint; // Price limit (Q64 format)
  minAmountOut: bigint; // Minimum output (slippage protection)
  deadline: bigint; // Unix timestamp
}

SwapExactOutputParams

interface SwapExactOutputParams {
  pool: Address;
  recipient: Address;
  zeroForOne: boolean;
  amountOut: bigint; // Desired output amount
  priceLimitX64: bigint;
  maxAmountIn: bigint; // Maximum input willing to pay
  deadline: bigint;
}

Constants

sdk.Q64; // 2^64 - Q64 format multiplier
sdk.MAX_UINT128; // 2^128-1 - Max uint128 (no price limit)
sdk.MAX_UINT104; // 2^104-1 - Max bin position
sdk.MAX_INT104; // 2^103-1 - Max signed int104 (for deltaShares)
sdk.MAX_INT128; // 2^127-1 - Max signed int128
sdk.NO_SLIPPAGE_LIMIT; // Max int128 - Use for specAmount to disable slippage checks

Usage with Scripts

The SDK is designed to work seamlessly with Hardhat scripts:

// In a Hardhat script
import { network } from "hardhat";
import * as sdk from "../sdk/index.js";

const { viem } = await network.connect();
const publicClient = await viem.getPublicClient();
const [walletClient] = await viem.getWalletClients();

// Use SDK functions
const state = await sdk.getPoolState(publicClient, poolAddress);

See updated scripts in scripts/actions/:

  • swap-with-sdk.ts - Swap using SDK
  • add-liquidity-with-sdk.ts - Liquidity management using SDK

Advanced Usage

Using MetricAmmPoolStateView for Efficient Reads

The StateView contract provides optimized read functions that are especially useful for off-chain operations:

const stateViewAddress = "0x..."; // Deploy MetricAmmPoolStateView once

// Get complete state efficiently
const slot0 = await sdk.getSlot0(publicClient, stateViewAddress, poolAddress);
const slot1 = await sdk.getSlot1(publicClient, stateViewAddress, poolAddress);

// Read fee configuration
const feeConfig = await sdk.getFeeConfig(
  publicClient,
  stateViewAddress,
  poolAddress,
);
console.log(`Admin: ${feeConfig.adminAddr}`);
console.log(`Protocol fee: ${feeConfig.protocolFee / 10000}%`);
console.log(`Admin fee: ${feeConfig.adminFee / 10000}%`);
console.log(`Admin fee dest: ${feeConfig.adminFeeDest}`);

// Get price provider
const priceProvider = await sdk.getPriceProvider(
  publicClient,
  stateViewAddress,
  poolAddress,
);

// Get last trade timestamp
const lastTrade = await sdk.getLastTradeTimestamp(
  publicClient,
  stateViewAddress,
  poolAddress,
);

// Get bin total shares
const totalShares = await sdk.getBinTotalShares(
  publicClient,
  stateViewAddress,
  poolAddress,
  0, // bin index
);

// Query user position via StateView
const userShares = await sdk.getPositionBinSharesFromStateView(
  publicClient,
  stateViewAddress,
  poolAddress,
  userAddress,
  salt,
  binIndex,
);

// Batch read bins for liquidity distribution
const binIndices = Array.from({ length: 21 }, (_, i) => i - 10); // -10 to 10
const distribution = await sdk.getBinStatesExternal(
  publicClient,
  stateViewAddress,
  poolAddress,
  binIndices,
);

// Analyze liquidity distribution
for (let i = 0; i < binIndices.length; i++) {
  console.log(`Bin ${binIndices[i]}: ${distribution.totalShares[i]} shares`);
}

Benefits of StateView:

  • ✅ Returns balances in native token decimals (external units)
  • ✅ Batch reads multiple bins in single call
  • ✅ No gas cost when called off-chain
  • ✅ Efficient EXTSLOAD implementation
  • ✅ Convenient single-call functions (slot0, slot1, feeConfig)

Custom Bin Configurations

// Create custom bin configurations
const customBins: sdk.BinData[] = [
  { lengthE6: 5000, addFeeBuyE6: 0, addFeeSellE6: 0 }, // 0.5% wide, no fees
  { lengthE6: 10000, addFeeBuyE6: 1000, addFeeSellE6: 0 }, // 1% wide, 0.1% extra buy fee
  { lengthE6: 15000, addFeeBuyE6: 0, addFeeSellE6: 500 }, // 1.5% wide, 0.05% extra sell fee
];

const packed = sdk.packBinDataArray(customBins);

Multi-Bin Liquidity

// Add liquidity across multiple bins with custom amounts
const deltas: sdk.LiquidityDelta[] = [];

for (let i = -5; i <= 5; i++) {
  // More liquidity near the center
  const shares = 1000n - BigInt(Math.abs(i)) * 100n;
  deltas.push({ bin: i, deltaShares: shares > 100n ? shares : 100n });
}

// Approve tokens first (example)
// await token0.write.approve([poolAddress, maxAmount0]);
// await token1.write.approve([poolAddress, maxAmount1]);

const { hash, amount0Delta, amount1Delta } = await sdk.modifyLiquidity(
  publicClient,
  walletClient,
  poolAddress,
  0n, // salt - unique identifier for this position
  deltas,
  userAddress,
  sdk.NO_SLIPPAGE_LIMIT, // or specify max token0 to spend
  sdk.NO_SLIPPAGE_LIMIT, // or specify max token1 to spend
);

console.log(
  `Added liquidity - Token0: ${amount0Delta}, Token1: ${amount1Delta}`,
);

Price Impact Estimation

// Estimate output for a given input (simplified version)
async function estimateSwapOutput(
  publicClient: PublicClient,
  poolAddress: Address,
  amountIn: bigint,
  zeroForOne: boolean,
): Promise<bigint> {
  const state = await sdk.getPoolState(publicClient, poolAddress);
  const binState = await sdk.getBinState(
    publicClient,
    poolAddress,
    state.curBinIdx,
  );

  // Simplified: assumes swap stays in one bin
  // Production should iterate through bins
  const availableOutput = zeroForOne
    ? binState.token1BalanceScaled
    : binState.token0BalanceScaled;

  // Apply fee (simplified)
  const outputBeforeFee = amountIn; // Assumes 1:1 price
  const fee = (outputBeforeFee * 3000n) / 1_000_000n; // 0.3%
  const output = outputBeforeFee - fee;

  return output < availableOutput ? output : availableOutput;
}

Error Handling

The SDK functions will throw errors that can be caught and handled:

try {
  const hash = await sdk.swapExactInput(
    walletClient,
    routerAddress,
    swapParams,
    userAddress,
  );
  console.log("Swap successful:", hash);
} catch (error: any) {
  if (error.message.includes("InsufficientOutput")) {
    console.error("Slippage too high, try increasing tolerance");
  } else if (error.message.includes("TransactionExpired")) {
    console.error("Transaction deadline passed");
  } else {
    console.error("Swap failed:", error.message);
  }
}

Best Practices

  1. Always Set Deadlines: Prevent transactions from being executed far in the future

    const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); // 5 minutes
  2. Use Appropriate Slippage: Balance protection with execution certainty

    const slippage = 0.5; // 0.5% for stable pairs
    const slippage = 1.0; // 1% for volatile pairs
  3. Approve Tokens: Ensure sufficient allowances before operations

    // Approve max for convenience (user should be aware)
    await token.write.approve([spender, sdk.MAX_UINT128]);
  4. Check Balances: Verify user has sufficient tokens

    const balance = await token.read.balanceOf([userAddress]);
    if (balance < requiredAmount) {
      throw new Error("Insufficient balance");
    }
  5. Wait for Confirmations: Always wait for transaction receipts

    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    if (receipt.status !== "success") {
      throw new Error("Transaction failed");
    }

Contributing

When adding new SDK functions:

  1. Add TypeScript types
  2. Add JSDoc comments
  3. Handle errors appropriately
  4. Add example usage to this README
  5. Update the specification document

Resources

License

See main project license.