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 🙏

© 2025 – Pkg Stats / Ryan Hefner

doppler-v4-sdk

v0.2.21

Published

A TypeScript SDK for interacting with the Doppler V4 protocol - a liquidity bootstrapping system built on Uniswap V4.

Readme

Doppler V4 SDK

A TypeScript SDK for interacting with the Doppler V4 protocol - a liquidity bootstrapping system built on Uniswap V4.

Overview

The Doppler V4 SDK provides a comprehensive interface for:

  • Token Creation & Management: Deploy ERC-20 tokens with vesting schedules
  • Pool Creation: Create Uniswap V4 pools with custom hooks for price discovery
  • Price Discovery: Automated gradual dutch auctions with customizable parameters
  • Governance: Deploy and manage governance contracts for token communities (optional)
  • Liquidity Migration: Move liquidity between discovery and trading pools

Installation

npm install doppler-v4-sdk

Quick Start

import { ReadWriteFactory, DOPPLER_V4_ADDRESSES } from 'doppler-v4-sdk';
import { createPublicClient, createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import { createDrift } from '@delvtech/drift';

// Setup clients
const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

const account = privateKeyToAccount('0x...');
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
});

// Create drift instance
const drift = createDrift({ publicClient, walletClient });

// Get contract addresses for Base
const addresses = DOPPLER_V4_ADDRESSES[8453];

// Initialize factory
const factory = new ReadWriteFactory(addresses.airlock, drift);

Core Concepts

Price Discovery Mechanism

Doppler V4 implements a gradual dutch auction where:

  • Token prices move along a predefined curve over time
  • Price movements occur in discrete epochs
  • The gamma parameter controls price movement per epoch
  • Liquidity is concentrated around the current price

Pool Configuration

Each Doppler pool requires:

  • Asset Token: The token being sold
  • Quote Token: The token being received (e.g., ETH, USDC)
  • Price Range: Starting and ending ticks defining the price curve
  • Time Parameters: Start time, duration, and epoch length
  • Proceeds Thresholds: Minimum and maximum proceeds targets

API Reference

ReadWriteFactory

The main class for creating and managing Doppler pools.

Creating a Pool with Governance

const config: DopplerPreDeploymentConfig = {
  name: tokenName,
  symbol: tokenSymbol,
  totalSupply: parseEther('1000000000'),
  numTokensToSell: parseEther('600000000'),
  tokenURI,
  blockTimestamp: Math.floor(Date.now() / 1000),
  startTimeOffset: 1,
  duration: 1 / 4,
  epochLength: 200,
  gamma: 800,
  tickRange: {
    startTick: 174_312,
    endTick: 186_840,
  },
  tickSpacing: 2,
  fee: 20_000, // 2%
  minProceeds: parseEther('2'),
  maxProceeds: parseEther('4'),
  yearlyMintRate: 0n,
  vestingDuration: BigInt(24 * 60 * 60 * 365),
  recipients: [wallet.account.address],
  amounts: [parseEther('50000000')],
  numPdSlugs: 15,
  integrator,
};

// Build configuration (uses governance by default)
const { createParams, hook, token } = factory.buildConfig(config, addresses);

// Simulate transaction
const simulation = await factory.simulateCreate(createParams);
console.log(`Estimated gas: ${simulation.request.gas}`);

// Execute creation
const txHash = await factory.create(createParams);
console.log(`Pool created: ${txHash}`);

Creating a Pool without Governance

To deploy tokens without governance (using NoOpGovernanceFactory), pass the optional useGovernance: false parameter:

// Build configuration without governance
const { createParams, hook, token } = factory.buildConfig(
  config,
  addresses,
  { useGovernance: false }
);

// Deploy the token (no governance contracts will be created)
const txHash = await factory.create(createParams);

When using useGovernance: false:

  • The NoOpGovernanceFactory will be used instead of the regular governance factory
  • No actual governance or timelock contracts will be deployed
  • The governance and timelock addresses will be set to 0xdead
  • This saves gas and simplifies deployment for tokens that don't need governance

Key Methods

  • buildConfig(params, addresses, options?) - Build complete pool configuration
  • create(createParams, options?) - Deploy the pool
  • simulateCreate(createParams) - Simulate deployment
  • migrate(asset, options?) - Migrate liquidity after price discovery

ReadDoppler

Read-only interface for querying pool state.

import { ReadDoppler } from 'doppler-v4-sdk';

const doppler = new ReadDoppler(
  dopplerAddress,
  addresses.stateView,
  drift,
  poolId
);

// Get current price
const price = await doppler.getCurrentPrice();

// Get pool configuration
const poolKey = await doppler.getPoolKey();

// Get strategy parameters
const startTime = await doppler.getStartingTime();
const endTime = await doppler.getEndingTime();
const gamma = await doppler.getGamma();

// Get token instances
const assetToken = await doppler.getAssetToken();
const quoteToken = await doppler.getQuoteToken();

Token Interfaces

ReadDerc20 / ReadWriteDerc20

const token = await doppler.getAssetToken();

// Read operations
const name = await token.getName();
const symbol = await token.getSymbol();
const decimals = await token.getDecimals();
const totalSupply = await token.getTotalSupply();
const balance = await token.getBalanceOf(userAddress);

// Write operations (ReadWriteDerc20 only)
await token.transfer(recipient, amount);
await token.approve(spender, amount);

Quoter

Get price quotes for swaps:

import { ReadQuoter } from 'doppler-v4-sdk';

const quoter = new ReadQuoter(addresses.v4Quoter, drift);

const quote = await quoter.quoteExactInputSingle({
  tokenIn: assetTokenAddress,
  tokenOut: quoteTokenAddress,
  fee: 3000,
  amountIn: parseEther('100'),
  sqrtPriceLimitX96: BigInt(0),
});

console.log(`Input: ${quote.amountIn}`);
console.log(`Output: ${quote.amountOut}`);

Supported Networks

| Network | Chain ID | Status | | ------------ | -------- | ------------- | | Base | 8453 | ✅ Production | | Base Sepolia | 84532 | 🧪 Testnet | | Unichain | 130 | ✅ Production | | Ink | 57073 | ✅ Production |

Get network addresses:

import { DOPPLER_V4_ADDRESSES } from 'doppler-v4-sdk';

const baseAddresses = DOPPLER_V4_ADDRESSES[8453];
const sepoliaAddresses = DOPPLER_V4_ADDRESSES[84532];

Advanced Usage

Multicurve Initializer (V4)

Create a pool with the multicurve initializer by encoding curve ranges and optional lockable fee beneficiaries. Below are two examples:

  1. Compact 2-curve example
import { ReadWriteFactory } from 'doppler-v4-sdk'
import { parseEther } from 'viem'

const factory = new ReadWriteFactory(addresses.airlock, drift)

const multicurve = {
  name: 'MyToken',
  symbol: 'MTK',
  totalSupply: parseEther('1000000'),
  numTokensToSell: parseEther('600000'),
  tokenURI: 'ipfs://.../metadata.json',
  numeraire: quoteTokenAddress,
  pool: {
    fee: 3000,
    tickSpacing: 60,
    curves: [
      { tickLower: 170_000, tickUpper: 175_000, numPositions: 16, shares: parseEther('1') },
      { tickLower: 175_000, tickUpper: 180_000, numPositions: 16, shares: parseEther('2') },
    ],
    // Optional lockable fee beneficiaries (shares in WAD)
    lockableBeneficiaries: [
      { beneficiary: someAddress, shares: parseEther('1.0') },
    ],
  },
  integrator: integratorAddress,
}

const { createParams } = factory.buildMulticurveCreateParams(multicurve, addresses, { useGovernance: true })
const sim = await factory.simulateCreate(createParams)
const tx = await factory.create(createParams)
  1. 10-curve config (matches our Base Sepolia fork test)
import { ReadWriteFactory } from 'doppler-v4-sdk'
import { parseEther } from 'viem'

const factory = new ReadWriteFactory(addresses.airlock, drift)

const multicurve10 = {
  name: 'ForkTest Multicurve',
  symbol: 'FTMC',
  totalSupply: parseEther('1000000'),
  numTokensToSell: parseEther('1000000'),
  tokenURI: 'ipfs://example/token.json',
  numeraire: quoteTokenAddress,
  pool: {
    // fee = 0, tickSpacing = 8, 10 curves stepping by 16_000 ticks
    fee: 0,
    tickSpacing: 8,
    curves: Array.from({ length: 10 }, (_, i) => ({
      tickLower: i * 16_000,
      tickUpper: 240_000,
      numPositions: 10,
      shares: parseEther('0.1'),
    })),
    lockableBeneficiaries: [],
  },
  integrator: integratorAddress,
}

const { createParams } = factory.buildMulticurveCreateParams(multicurve10, addresses, { useGovernance: true })
const sim = await factory.simulateCreate(createParams)
const tx = await factory.create(createParams)

Notes:

  • Ensure addresses.v4MulticurveInitializer is set for your target chain (Base Sepolia is prefilled in this SDK).
  • Use useGovernance: false to target noOpGovernanceFactory when desired.

Custom Liquidity Migration

// Configure custom LP migration
const migrationData = factory.encodeCustomLPLiquidityMigratorData({
  customLPWad: parseEther('1000'), // LP tokens to mint
  customLPRecipient: lpRecipientAddress,
  lockupPeriod: 86400, // 1 day lockup
});

const config = {
  // ... other parameters
  liquidityMigratorData: migrationData,
};

Gamma Calculation

The SDK automatically calculates optimal gamma (price movement per epoch):

// Manual gamma calculation
const totalEpochs = (durationDays * 86400) / epochLength;
const tickDelta = Math.abs(endTick - startTick);
const gamma = Math.ceil(tickDelta / totalEpochs) * tickSpacing;

Error Handling

The SDK provides detailed error messages for common issues:

  • Missing or invalid configuration parameters
  • NoOpGovernanceFactory not deployed when useGovernance: false
  • Invalid price ranges or tick ranges
  • Insufficient permissions

Building

bun run build

Examples

See the examples/ directory for complete implementation examples:

  • Basic pool creation
  • Multi-chain deployment
  • Custom governance setup
  • Liquidity migration strategies // (For a simple usage example, see examples/multicurve-create.ts.)

Important Notes

  • The useGovernance parameter defaults to true to maintain backward compatibility
  • NoOpGovernanceFactory must be deployed on the target chain before using useGovernance: false
  • If NoOpGovernanceFactory is not deployed, the SDK will throw an error when attempting to use it

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

License

MIT License - see LICENSE file for details.

Support