Skip to main content
You can use the Kuru SDK to deploy a market. We have two methods of doing this:
  1. Use the Kuru Deployer to deploy a new token alongwith a market for it in one transaction.
  2. Use the Kuru Router directly to deploy a market with your chosen parameters for an existing token.

Deploy a market with the Kuru Deployer

We have deployed a deployer contract which launches a token for you, creates a market for it and bootstraps the market with liquidity. All markets made through the deployer are paired with MON. You can use the KuruSDK.MonadDeployer class to deploy a market with the Kuru Deployer. You can find the Kuru Deployer address here Here’s how you can do it:
deployMarket.ts
import { ethers } from "ethers";
import { MonadDeployer } from "@kuru-labs/kuru-sdk";

//The deployer doesn’t derive the initial price; it only seeds the vault with the amounts you supply. 
//If you want the pool to open around 5 quote per base, choose _supplyToVault (the base tokens sent to the vault) and nativeTokenAmount so that _supplyToVault ÷ nativeTokenAmount ≈ 5, after accounting for token decimals. 

// Example script to deploy a token and its market using MonadDeployer
// Docs template: fill placeholders before running.
const IMAGE_URL = ""; // e.g., https://your-cdn/path/logo.svg
const rpcUrl = ""; // e.g., http://localhost:8545
const monadDeployerAddress = ""; // e.g., 0x6A803B8F038554AF34AC73F1C099bd340dcC7026

async function main() {
  const provider = new ethers.providers.JsonRpcProvider(rpcUrl); // set in config.json
  const privateKey = process.env.PRIVATE_KEY; // export PRIVATE_KEY in your environment
  if (!privateKey) throw new Error("PRIVATE_KEY environment variable not set");
  const signer = new ethers.Wallet(privateKey, provider);

  const monadDeployer = new MonadDeployer();

  // Token configuration (docs placeholders)
  const tokenParams = {
    name: "",                               // token name (e.g., "Clobby Token")
    symbol: "",                             // token symbol (e.g., "CLOB")
    tokenURI: IMAGE_URL,                     // token metadata/logo URL
    initialSupply: ethers.utils.parseUnits("", 18), // total supply (string), 18 decimals (e.g., "1000000")
    dev: await signer.getAddress(),          // developer/owner address
    supplyToDev: ethers.BigNumber.from(""),  // bps to dev (0-10000), e.g., "1000" for 10%
  };

  // Market configuration (docs placeholders)
  const marketParams = {
    nativeTokenAmount: ethers.utils.parseEther(""),   // initial native liquidity (e.g., "10")
    sizePrecision: ethers.BigNumber.from(""),         // e.g., "10000000000" (1e10)
    pricePrecision: ethers.BigNumber.from(""),        // e.g., "1000000000" (1e9)
    tickSize: ethers.BigNumber.from(""),              // min price tick (in pricePrecision units), e.g., "100"
    minSize: ethers.BigNumber.from(""),               // e.g., "100000000" (0.01 * 1e10)
    maxSize: ethers.BigNumber.from(""),               // e.g., "10000000000000000" (1e6 * 1e10)
    takerFeeBps: 0,                                    // taker fee in bps (e.g., 30)
    makerFeeBps: 0,                                    // maker fee in bps (e.g., 10)
  };

  try {
    const result = await monadDeployer.deployTokenAndMarket(
      signer,
      monadDeployerAddress,
      tokenParams,
      marketParams
    );
    console.log("Deployment successful!");
    console.log("Token deployed at:", result.tokenAddress);
    console.log("Market deployed at:", result.marketAddress);
  } catch (error) {
    console.error("Error deploying token and market:", error);
  }
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
We recommend using the calculatePrecisions function to get the suggested parameters for the market. Using inappropriate parameters for the market can lead to users not being able to place limit orders.

Deploy a market with the Kuru Router

You can use the KuruSDK.ParamCreator class to calculate suggested parameters for a market and for deploying a market with the Kuru Router. Use the calculatePrecisions function to get suggested parameters for a market.

Double-check the output from Calculate Precision before you deploy

calculatePrecisions.ts
import {ParamCreator} from "@kuru-labs/kuru-sdk";
// Example: 1 ABC = 10 USDC (make math simple)
// Implied price = QUOTE_TOKENS / BASE_TOKENS = 10
const QUOTE_TOKENS = 10; // USDC amount
const BASE_TOKENS = 1;   // ABC amount
const MAX_PRICE = 10000; // Max price which should be supported by the market (avoid too high prices)
const MIN_SIZE = 1; //Min size of a limit order, in terms of base asset 
const TICK_IN_BPS = 100; //Min price difference between ticks in BPS 
(async() => {    
	const paramCreator = new ParamCreator();    
	const params = await paramCreator.calculatePrecisions(QUOTE_TOKENS,BASE_TOKENS,MAX_PRICE,MIN_SIZE,TICK_IN_BPS);
    console.log(params.pricePrecision.toString());    
	console.log(params.sizePrecision.toString());    
	console.log(params.tickSize.toString());    
	console.log(params.minSize.toString());    
	console.log(params.maxSize.toString());
	})();
Here, TICK_IN_BPS is the tick size in basis points. We recommend a bigger tick for memecoins and a smaller tick for tokens which are more likely to move in a short range. Use the deployMarket function to deploy a market with the Kuru Router.
deployMarket.ts
// This example shows how to deploy a new market using the Kuru SDK ParamCreator.
// Fill in the placeholders (rpcUrl, routerAddress, baseAssetAddress, quoteAssetAddress)
// and export PRIVATE_KEY in your environment before running.
import { ethers } from 'ethers';
import {ParamCreator} from "@kuru-labs/kuru-sdk";

async function main() {
    // JSON-RPC endpoint of the chain where you want to deploy the market
    const rpcUrl = ""; // e.g., "http://localhost:8545"
    // Kuru Router contract address
    const routerAddress = ""; // e.g., 0x...
    // Base (ABC) ERC-20 token address
    const baseAssetAddress = ""; // e.g., 0x...
    // Quote (USDC) ERC-20 token address
    const quoteAssetAddress = ""; // e.g., 0x...
    
    // Market type (OrderBookType):
    // 0 = NO_NATIVE        → Neither token is the chain native asset
    // 1 = NATIVE_IN_BASE   → Base token is the native asset (e.g., MON)
    // 2 = NATIVE_IN_QUOTE  → Quote token is the native asset (e.g., MON)
    const type = 0

    // Calculate precisions for target price. Example: 1 ABC = 10 USDC
    // currentQuote/currentBase = implied price in quote per base
    const currentQuote = 10; // quote amount (USDC)
    const currentBase = 1; // base amount (ABC)
    const maxPrice = 20; // Maximum expected price
    const minSize = 0.01; // Minimum order size

    // Build market parameters from the target price / constraints
    const paramCreator = new ParamCreator();
    const precisions = paramCreator.calculatePrecisions(currentQuote, currentBase, maxPrice, minSize, 10);
    console.log('Price precision', precisions.pricePrecision.toString());
    console.log('Size precision', precisions.sizePrecision.toString());
    console.log('Tick size', precisions.tickSize.toString());
    console.log('Min size', precisions.minSize.toString());
    console.log('Max size', precisions.maxSize.toString());
    const takerFeeBps = 30; // 0.3%
    const makerFeeBps = 10; // -0.1% (rebate)
    const kuruAmmSpread = ethers.BigNumber.from(100); // 1% Kuru AMM spread

    // Create signer from env key and connect to the network
    const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
    const privateKey = process.env.PRIVATE_KEY; // must be set in environment
    if (!privateKey) throw new Error('PRIVATE_KEY not set');
    const signer = new ethers.Wallet(privateKey as string, provider);
    
    try {
        // Deploy the market via the router
        const marketAddress = await paramCreator.deployMarket(
            signer,
            routerAddress,
            type,
            baseAssetAddress,
            quoteAssetAddress,
            precisions.sizePrecision,
            precisions.pricePrecision,
            precisions.tickSize,
            precisions.minSize,
            precisions.maxSize,
            takerFeeBps,
            makerFeeBps,
            kuruAmmSpread,
        );

        console.log('Market deployed at:', marketAddress);
    } catch (error) {
        console.error('Error deploying market:', error);
    }
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });
Here, TYPE is an integer from 0-2 according to the type of market you want to deploy:
TypeDescription
0Market with two ERC20 tokens
1Market with the base token as MON (ex. MON-USDC)
2Market with the quote token as MON (ex. USDC-MON)
KURU_AMM_SPREAD is the spread for the Kuru AMM. We recommend a large spread of 100 for volatile markets and 30 for more stable markets. The minimum spread is 10 BPS and the max is 500 BPS. The spread must be a multiple of 10.

Bootstrapping with liquidity after deploying using Kuru-Router

After the router deploys your market, seed its vault with the initial base/quote inventory so liquidity is available from block one. The script below walks through a first-time deposit that respects a target price, checks allowances, and shows how to broadcast the transaction.
FirstVaultDeposit.ts

import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";
import erc20Abi from "@kuru-labs/kuru-sdk/abi/IERC20.json";

// Example: first-time vault deposit with a target price (e.g., 1 ABC = 5 USDC)
// Fill placeholders and export PRIVATE_KEY before running.
const rpcUrl = "";                 // RPC endpoint
const vaultAddress = "";           // Vault contract address
const marketAddress = "";          // Orderbook/market address
const baseTokenAddress = "";       // Base token (0x00..00 for native)
const quoteTokenAddress = "";      // Quote token
const marginAccountAddress = "";   // Margin account used by vault
const quoteAmount = "";            // Quote liquidity to deposit (string)
const targetPrice = "";            // Quote per base (string)
const approveTokens = false;        // Set true to auto-approve ERC-20s

async function main() {
  const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
  const privateKey = process.env.PRIVATE_KEY; // must be set in env
  if (!privateKey) throw new Error("PRIVATE_KEY environment variable not set");
  const signer = new ethers.Wallet(privateKey, provider);
  const signerAddress = await signer.getAddress();

  console.log("Fetching market parameters...");
  const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, marketAddress);
  const quoteDecimals = marketParams.quoteAssetDecimals;
  const baseDecimals = marketParams.baseAssetDecimals;

  console.log("Market parameters:", { quoteDecimals, baseDecimals });

  const quoteAmountWei = ethers.utils.parseUnits(quoteAmount, quoteDecimals);
  const priceWei = ethers.utils.parseUnits(targetPrice, 18); // price scaled to 1e18 (quote per base)
  if (priceWei.isZero()) throw new Error("targetPrice must be greater than zero");

  // amount1 = (amount2 * 10^baseDecimals * 1e18) / (price * 10^quoteDecimals)
  const baseAmountWei = quoteAmountWei
    .mul(ethers.utils.parseUnits("1", baseDecimals))
    .mul(ethers.utils.parseUnits("1", 18))
    .div(priceWei)
    .div(ethers.utils.parseUnits("1", quoteDecimals));

  console.log("Planned deposit ratio:", {
    quoteAmount: quoteAmountWei.toString(),
    baseAmount: baseAmountWei.toString(),
    targetPrice,
  });

  console.log("Fetching existing vault liquidity (optional sanity check)...");
  const { token1, token2 } = await KuruSdk.Vault.getVaultLiquidity(
    vaultAddress,
    baseTokenAddress,
    quoteTokenAddress,
    marginAccountAddress,
    provider
  );
  console.log("Vault token balances:", {
    token1: token1.balance.toString(),
    token2: token2.balance.toString(),
  });

  console.log("Checking wallet balances...");
  // Show balances to confirm you have enough base/quote before depositing.
  const token1Balance = baseTokenAddress === ethers.constants.AddressZero as string
    ? await signer.getBalance()
    : await new ethers.Contract(baseTokenAddress, erc20Abi.abi, provider).balanceOf(signerAddress);
  const token2Balance = quoteTokenAddress === ethers.constants.AddressZero as string
    ? await signer.getBalance()
    : await new ethers.Contract(quoteTokenAddress, erc20Abi.abi, provider).balanceOf(signerAddress);
  console.log("Wallet balances:", {
    token1: token1Balance.toString(),
    token2: token2Balance.toString(),
  });

  console.log("Sending first vault deposit transaction...");
  const receipt = await KuruSdk.Vault.depositWithAmounts(
    baseAmountWei,
    quoteAmountWei,
    baseTokenAddress,
    quoteTokenAddress,
    vaultAddress,
    signer,
    approveTokens,
  );
  console.log("First deposit confirmed. Transaction hash:", receipt.transactionHash);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error("First vault deposit failed:", error);
    process.exit(1);
  });

Adding Liquidity to Existing Vaults

Whenever you need to deepen an established pool, add base/quote liquidity in the same proportion the vault already holds. This helper script pulls the live vault balances, computes the matching base amount from your quote input, takes care of ERC-20 approvals, and submits the proportional top-up.
VaultDeposit.ts

import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";
import erc20Abi from "@kuru-labs/kuru-sdk/abi/IERC20.json";

// Example: top up an existing Kuru AMM vault without changing price.
// Fill every placeholder (RPC, addresses, amounts) before running.
const RPC_URL = "";                 // e.g., "https://rpc.fullnode.kuru.io"
const VAULT_ADDRESS = "";           // Vault contract address
const MARKET_ADDRESS = "";          // Orderbook / market address
const BASE_TOKEN_ADDRESS = "";      // Base token (0x00..00 if native)
const QUOTE_TOKEN_ADDRESS = "";     // Quote token (0x00..00 if native)
const MARGIN_ACCOUNT_ADDRESS = "";  // Margin account that holds vault balances
const QUOTE_AMOUNT = "";            // Human quote amount, e.g., "500"

async function main() {
  if (!RPC_URL || !VAULT_ADDRESS || !MARKET_ADDRESS || !BASE_TOKEN_ADDRESS || !QUOTE_TOKEN_ADDRESS || !MARGIN_ACCOUNT_ADDRESS) {
    throw new Error("Fill in all placeholders (RPC_URL, VAULT_ADDRESS, MARKET_ADDRESS, token addresses, MARGIN_ACCOUNT_ADDRESS)");
  }

  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    throw new Error("Set PRIVATE_KEY in your environment before running the script");
  }

  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(privateKey, provider);
  const signerAddress = await signer.getAddress();

  console.log("Fetching current vault liquidity...");
  const { token1, token2 } = await KuruSdk.Vault.getVaultLiquidity(
    VAULT_ADDRESS,
    BASE_TOKEN_ADDRESS,
    QUOTE_TOKEN_ADDRESS,
    MARGIN_ACCOUNT_ADDRESS,
    provider
  );

  console.log("Vault balances:", {
    token1: token1.balance.toString(),
    token2: token2.balance.toString(),
  });

  console.log("Fetching market params...");
  const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, MARKET_ADDRESS);

  const quoteAmountStr = QUOTE_AMOUNT || "0";
  const quoteAmountWei = ethers.utils.parseUnits(quoteAmountStr, marketParams.quoteAssetDecimals);

  const baseAmountWei = await KuruSdk.Vault.calculateAmount1ForAmount2(
    quoteAmountWei,
    MARKET_ADDRESS,
    marketParams,
    provider
  );

  console.log("Calculated base amount needed:", baseAmountWei.toString());

  const token1Balance = token1.address === ethers.constants.AddressZero
    ? await signer.getBalance()
    : await new ethers.Contract(token1.address, erc20Abi.abi, provider).balanceOf(signerAddress);
  const token2Balance = token2.address === ethers.constants.AddressZero
    ? await signer.getBalance()
    : await new ethers.Contract(token2.address, erc20Abi.abi, provider).balanceOf(signerAddress);

  console.log("User balances before deposit:", {
    token1: token1Balance.toString(),
    token2: token2Balance.toString(),
  });

  if (BASE_TOKEN_ADDRESS !== ethers.constants.AddressZero) {
    const baseToken = new ethers.Contract(BASE_TOKEN_ADDRESS, erc20Abi.abi, signer);
    const baseAllowance = await baseToken.allowance(signerAddress, VAULT_ADDRESS);
    if (baseAllowance.lt(baseAmountWei)) {
      console.log("Approving base token allowance", {
        required: baseAmountWei.toString(),
        current: baseAllowance.toString(),
      });
      const approveBaseTx = await baseToken.approve(VAULT_ADDRESS, baseAmountWei);
      await approveBaseTx.wait();
      console.log("Base token approved");
    }
  }

  if (QUOTE_TOKEN_ADDRESS !== ethers.constants.AddressZero) {
    const quoteToken = new ethers.Contract(QUOTE_TOKEN_ADDRESS, erc20Abi.abi, signer);
    const quoteAllowance = await quoteToken.allowance(signerAddress, VAULT_ADDRESS);
    if (quoteAllowance.lt(quoteAmountWei)) {
      console.log("Approving quote token allowance", {
        required: quoteAmountWei.toString(),
        current: quoteAllowance.toString(),
      });
      const approveQuoteTx = await quoteToken.approve(VAULT_ADDRESS, quoteAmountWei);
      await approveQuoteTx.wait();
      console.log("Quote token approved");
    }
  }

  console.log("Constructing deposit transaction...");
  console.log("Base amount wei:", baseAmountWei.toString());
  console.log("Quote amount wei:", quoteAmountWei.toString());

  const depositTx = await KuruSdk.Vault.constructDepositTransaction(
    baseAmountWei,
    quoteAmountWei,
    BASE_TOKEN_ADDRESS,
    QUOTE_TOKEN_ADDRESS,
    VAULT_ADDRESS,
    signer
  );

  console.log("Sending deposit transaction...");
  const sentTx = await signer.sendTransaction(depositTx);
  console.log("Deposit submitted. Hash:", sentTx.hash);

  const receipt = await sentTx.wait();
  console.log("Deposit confirmed in block:", receipt.blockNumber);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error("Vault deposit failed:", error);
    process.exit(1);
  });