Skip to main content

Market buy order

To perform a market buy. We use KuruSdk.IOC.placeMarket() Set isBuy to false to place a Market sell order.
marketBuy
// Example: place a market buy (IOC) using the Kuru SDK
// Fill placeholders (rpcUrl, marketAddress, size) and export PRIVATE_KEY before running.
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";

// Network RPC endpoint
const rpcUrl = "";          // TODO: e.g., http://localhost:8545
// Address of the market you want to trade on
const marketAddress = "";    // TODO: e.g., 0x...

// Order params (strings OK; SDK handles parsing):
// size → amount in quote asset (e.g., USDC) to spend
const size = "";             // TODO: e.g., "5.5" (USDC)
// Slippage tolerance expressed in basis points (100 = 1%). This is only used to
// derive the on-chain minAmountOut threshold; the contract enforces minAmountOut,
// not the slippage percentage itself.
const slippageToleranceBps = 100;

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);

  try {
    // Pull market parameters (precisions, fees, etc.)
    const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, marketAddress);

    const quoteSize = parseFloat(size);
    if (Number.isNaN(quoteSize)) {
      throw new Error("size must be a numeric string (e.g., \"5.5\")");
    }

    // Estimate base asset output and gas using CostEstimator
    const { output: estimatedBaseOut, estimatedGas } = await KuruSdk.CostEstimator.estimateMarketBuy(
      provider,
      marketAddress,
      marketParams,
      quoteSize
    );
    console.log("Estimated gas:", estimatedGas.toString());
    console.log("Estimated base out:", estimatedBaseOut);

    const baseDecimals = marketParams.baseAssetDecimals.toNumber();
    const slippageMultiplier = (10000 - slippageToleranceBps) / 10000;
    const minAmountOut = (estimatedBaseOut * slippageMultiplier).toFixed(baseDecimals);
    console.log("Min amount out (post slippage):", minAmountOut);

    // Immediate-Or-Cancel market buy
    const receipt = await KuruSdk.IOC.placeMarket(signer, marketAddress, marketParams, {
      approveTokens: true,
      size,
      isBuy: true,
      minAmountOut,
      isMargin: false,
      fillOrKill: true,
    });
    console.log("Transaction hash:", receipt.transactionHash);
  } catch (error) {
    console.error("Error placing market buy order:", error);
  }
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Estimate buy

To estimate the baseAsset received for a buy order with X amount of quoteAsset. We use KuruSdk.CostEstimator.estimateMarketBuy()
EstimateBuy
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";

// Example: estimate how much base you receive for a market buy (no tx sent)
// Fill placeholders (rpcUrl, marketAddress, amount) before running.
const rpcUrl = "";          // TODO: RPC endpoint (e.g., http://localhost:8545)
const marketAddress = "";    // TODO: Market address (e.g., 0x...)
const amount = "";           // TODO: Quote amount to spend (e.g., "100" USDC)

async function main() {
    const provider = new ethers.providers.JsonRpcProvider(rpcUrl);

    // Fetch market params (precisions, fees, etc.) required for estimation
    const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, marketAddress);

    try {
        // Validate and convert input amount (quote asset) to number
        const amountNum = Number(amount);
        if (Number.isNaN(amountNum)) {
            throw new Error("Provide a numeric 'amount' (quote asset), e.g., \"100\"");
        }

        const estimate = await KuruSdk.CostEstimator.estimateMarketBuy(
            provider,
            marketAddress,
            marketParams,
            amountNum
        );
        console.log(estimate);
    } catch (error) {
        console.error("Error estimating market buy:", error);
    }
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

Estimate base for sell

To estimate the required baseAsset for a sell order to get X amount of quoteAsset. We use KuruSdk.CostEstimator.estimateRequiredBaseForSell()
EstimateSell
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";
import orderbookAbi from "@kuru-labs/kuru-sdk/abi/OrderBook.json";

// Example: estimate required BASE to sell a given QUOTE amount (no tx sent)
// Fill placeholders (rpcUrl, marketAddress, amount) before running.
const rpcUrl = "";           // TODO: RPC endpoint (e.g., http://localhost:8545)
const marketAddress = "";     // TODO: Market address (e.g., 0x...)
const amount = "";            // TODO: desired quote amount to receive (e.g., "100" USDC)

(async () => {
    const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
    // Pull market params (precisions, fees, etc.)
    const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, marketAddress);
    // Read orderbook state used by estimator
    const orderbook = new ethers.Contract(marketAddress, orderbookAbi.abi, provider);
    const l2Book = await orderbook.getL2Book();
    const vaultParams = await orderbook.getVaultParams();
    try {
        // Validate numeric amount (quote)
        const amountNum = Number(amount);
        if (Number.isNaN(amountNum)) throw new Error("Provide numeric 'amount' (quote asset)");
        const estimate = await KuruSdk.CostEstimator.estimateRequiredBaseForSell(
            provider,
            marketAddress,
            marketParams,
            amountNum,
            l2Book,
            vaultParams
        );
        console.log(estimate);
    } catch (error) {
        console.error("Error estimating required base for sell:", error);
    }
})();
Before placing any limit orders, deposit the required tokens into your margin account and ensure sufficient allowance; otherwise orders will revert due to insufficient balance.

Deposit into margin account

MarginDeposit
// Example: deposit tokens into a margin account via Kuru SDK
// Fill placeholders (rpcUrl, marginAccountAddress, tokenAddress, amount, decimals)
// and export PRIVATE_KEY before running.
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";
import * as KuruConfig from "../config.json";

const {rpcUrl, marginAccountAddress} = KuruConfig;
const tokenAddress = "";            // TODO: ERC-20 token to deposit (use 0x000...000 for native if supported)
const amount = "";                  // TODO: human amount as string (e.g., "1000")
const decimals = 18;                // TODO: token decimals (e.g., 18 for ABC, 6 for USDC)

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);

  try {
    // Deposits on behalf of the signer address. Set a different receiver if desired
    const receipt = await KuruSdk.MarginDeposit.deposit(
      signer,
      marginAccountAddress,
      await signer.getAddress(),
      tokenAddress,
      amount,
      decimals,
      true // set to true to auto-approve ERC-20 if not already approved
    );
    console.log("Transaction hash:", receipt.transactionHash);
  } catch (error) {
    console.error("Error depositing:", error);
  }
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Place limit buy

To place a limit buy order. We use KuruSdk.GTC.placeLimit() After sending, call parseEvents(receipt) to confirm the order was created and to read new order IDs. Set isBuy to false to place a limit sell order.
parseEvents.ts
import { ethers, BigNumber } from 'ethers';

interface TradeInfo {
    orderId: BigNumber;
    filledSize: BigNumber;
    price: BigNumber;
    isBuy: boolean;
}

export async function parseEvents(receipt: ethers.ContractReceipt): Promise<{
    newOrderIds: BigNumber[];
    trades: TradeInfo[];
}> {
    const newOrderIds: BigNumber[] = [];
    const trades: TradeInfo[] = [];

    receipt.logs.forEach((log) => {
        if (log.topics[0] === ethers.utils.id("OrderCreated(uint40,address,uint96,uint32,bool)")) {
            try {
                const decodedLog = ethers.utils.defaultAbiCoder.decode(
                    ['uint40', 'address', 'uint96', 'uint32', 'bool'],
                    log.data
                );
                const orderId = BigNumber.from(decodedLog[0]);
                newOrderIds.push(orderId);
            } catch (error) {
                console.error("Error decoding OrderCreated event:", error);
            }
        } else if (log.topics[0] === ethers.utils.id("Trade(uint40,address,bool,uint256,uint96,address,address,uint96)")) {
            try {
                const decodedLog = ethers.utils.defaultAbiCoder.decode(
                    ['uint40', 'address', 'bool', 'uint256', 'uint96', 'address', 'address', 'uint96'],
                    log.data
                );
                trades.push({
                    orderId: BigNumber.from(decodedLog[0]),
                    price: BigNumber.from(decodedLog[3]),
                    filledSize: BigNumber.from(decodedLog[7]),
                    isBuy: decodedLog[2]
                });
            } catch (error) {
                console.error("Error decoding trade event:", error);
            }
        }
    });

    return { newOrderIds, trades };
}
placeLimitBuy.ts
// Example: place a limit buy order using the Kuru SDK
// Fill the placeholders (rpcUrl, marketAddress) and export PRIVATE_KEY before running.
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";
import { parseEvents } from "./ParseEvents";

// Address of the orderbook/market you want to trade on
const marketAddress = ""; // TODO: market address (e.g., 0x...)
// Network RPC endpoint to connect the signer/provider
const rpcUrl = "";        // TODO: RPC endpoint (e.g., http://localhost:8545)
 
// Order parameters (strings OK; SDK handles parsing/decimals):
// price → quoted in quote per base (e.g., USDC per ABC); must respect tick size
// size  → amount of base to buy; must be ≥ min size
const price = ""; // TODO: e.g., "5" for 5 USDC per ABC
const size = "";  // TODO: e.g., "10" for 10 ABC
 
async function main() {
    const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
    // PRIVATE_KEY should be exported in your shell before running
    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);
 
    // Fetch market parameters (precisions, tick size, fees)
    const marketParams = await KuruSdk.ParamFetcher.getMarketParams(
        provider,
        marketAddress
    );

    const order = {
        price,
        size,
        isBuy: true,
        postOnly: true,
    };

    try {
        const gasEstimate = await KuruSdk.GTC.estimateGas(
            signer,
            marketAddress,
            marketParams,
            order
        );
        const bufferedGasLimit = gasEstimate.mul(120).div(100);
        console.log("Estimated gas:", gasEstimate.toString());
        console.log("Buffered gas limit (120%):", bufferedGasLimit.toString());

        // Place a Good-Till-Cancel limit buy with buffered gas limit
        const receipt = await KuruSdk.GTC.placeLimit(
            signer,
            marketAddress,
            marketParams,
            {
                ...order,
                txOptions: {
                    gasLimit: bufferedGasLimit,
                },
            }
        );

        console.log("Transaction hash:", receipt.transactionHash);
        // Extract created order ids from events
        const { newOrderIds } = await parseEvents(receipt);
        console.log("OrderIds:", newOrderIds);
    } catch (error) {
        console.error("Error placing limit buy order:", error);
    }
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Cancel order

To cancel orders using the Kuru SDK. We use KuruSdk.OrderCanceler.cancelOrders()
cancelOrder
// Example: cancel one or more orders on an orderbook
// Fill placeholders (rpcUrl, marketAddress, orderIds) and export PRIVATE_KEY before running.
import { ethers, BigNumber } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";

// Network RPC endpoint
const rpcUrl = ""; // TODO: e.g., http://localhost:8545
// Address of the market where the orders exist
const marketAddress = ""; // TODO: market address (e.g., 0x...)

// Order IDs to cancel (as strings). These will be converted to BigNumber.
const orderIds = [""]; // TODO: e.g., ["123", "124"]

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 ids = orderIds.map((orderId) => BigNumber.from(orderId));

  try {
    const gasEstimate = await KuruSdk.OrderCanceler.estimateGas(
      signer,
      marketAddress,
      ids
    );

    const bufferedGasLimit = gasEstimate.mul(120).div(100);
    console.log("Estimated gas:", gasEstimate.toString());
    console.log("Buffered gas limit (120%):", bufferedGasLimit.toString());

    // Convert string IDs to BigNumber and send cancel transaction
    const txReceipt = await KuruSdk.OrderCanceler.cancelOrders(
      signer,
      marketAddress,
      ids,
      { gasLimit: bufferedGasLimit }
    );

    console.log("Transaction hash:", txReceipt.transactionHash);
  } catch (err: any) {
    console.error("Error:", err);
  }
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

FlipOrders( Liquidity )

Flip orders place an order that, when filled, automatically creates an opposite-side order at a specified “flipped” price. They let you enter as a maker and immediately re-quote on the other side to maintain liquidity across both sides of the book. Using FlipOrders we could provide Liquidity in following ways.

Spot

Even spread across a price band around best ask.
placeSpotConcentratedLiquidity
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";

// Docs Template: Place concentrated liquidity around the current best ask
// 1) Fill all placeholders below
// 2) export PRIVATE_KEY=<your_key>
// 3) Run with: npx ts-node scripts/docs_scripts/placeConcentratedLiquidity.ts

const RPC_URL = "";                 // TODO: RPC endpoint (e.g., https://rpc.your-node)
const MARKET_ADDRESS = "";          // TODO: Orderbook/market address
const MIN_FEES_BPS = 30n;            // Minimum fees in bps (e.g., 30 for 0.3%)
const RANGE_PCT = 1n;                // +/- range in percent around best ask (e.g., 1)
const TOTAL_QUOTE = "";             // TODO: Total quote liquidity to allocate (human string, e.g., "1000")

async function main() {
  // Require configuration and PRIVATE_KEY in env
  if (!RPC_URL || !MARKET_ADDRESS || !TOTAL_QUOTE) {
    throw new Error("Fill RPC_URL, MARKET_ADDRESS, TOTAL_QUOTE before running");
  }
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) throw new Error("Set PRIVATE_KEY in your environment before running");

  // Provider/signer setup
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(privateKey, provider);

  // Fetch market params and current orderbook
  const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, MARKET_ADDRESS);
  const orderbook = await KuruSdk.OrderBook.getL2OrderBook(provider, MARKET_ADDRESS, marketParams);
  if (!orderbook.asks || orderbook.asks.length === 0) {
    throw new Error("No asks available to anchor price range");
  }

  // Derive best ask price in precision units (BigInt)
  const pricePrecisionPow = BigInt(10) ** BigInt(KuruSdk.log10BigNumber(marketParams.pricePrecision));
  const bestAskFloat = orderbook.asks[0][0]; // number
  const bestAskPrice = BigInt(Math.floor(bestAskFloat * Number(pricePrecisionPow)));

  // Compute start/end bounds around best ask by +/- RANGE_PCT
  const startPrice = bestAskPrice - (bestAskPrice * RANGE_PCT) / 100n;
  const endPrice = bestAskPrice + (bestAskPrice * RANGE_PCT) / 100n;

  // Convert total quote to raw units using quote decimals
  const quoteDecimals = Number(marketParams.quoteAssetDecimals.toString());
  const totalQuoteWei = ethers.utils.parseUnits(TOTAL_QUOTE, quoteDecimals);

  // Build batch LP details using PositionViewer (quote-liquidity driven)
  const batchLPDetails = await KuruSdk.PositionViewer.getSpotBatchLPDetails(
    MIN_FEES_BPS,
    startPrice,
    endPrice,
    bestAskPrice,
    BigInt(marketParams.pricePrecision.toString()),
    BigInt(marketParams.sizePrecision.toString()),
    BigInt(marketParams.quoteAssetDecimals.toString()),
    BigInt(marketParams.baseAssetDecimals.toString()),
    BigInt(marketParams.tickSize.toString()),
    BigInt(marketParams.minSize.toString()),
    BigInt(totalQuoteWei.toString()), // provide total quote liquidity
    undefined                        // or provide base liquidity instead
  );

  console.log("Placing concentrated liquidity:", {
    bids: batchLPDetails.bids.length,
    asks: batchLPDetails.asks.length,
  });

  // Submit the provision transaction (calls batchProvisionLiquidity)
  const receipt = await KuruSdk.PositionProvider.provisionLiquidity(
    signer,
    MARKET_ADDRESS,
    batchLPDetails
  );

  console.log("Concentrated liquidity provisioned. Tx:", receipt.transactionHash);
}

main()
  .then(() => process.exit(0))
  .catch((err) => {
    console.error("Error placing concentrated liquidity:", err);
    process.exit(1);
  });

Bid-Ask

asymmetric distribution (separate shaping on bids vs asks)
placeBidAskConcentratedLiquidity
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";

// Docs Template: Place bid-ask distributed concentrated liquidity
// 1) Fill placeholders below
// 2) export PRIVATE_KEY=<your_key>
// 3) Run: npx ts-node scripts/docs_scripts/placeBidAskConcentratedLiquidity.ts

const RPC_URL = "";                 // TODO: RPC endpoint
const MARKET_ADDRESS = "";          // TODO: Orderbook/market address
const MIN_FEES_BPS = 30n;            // e.g., 30 for 0.3%
const RANGE_PCT = 1n;                // +/- percent around best ask
const TOTAL_QUOTE = "";             // TODO: human string (e.g., "1000")

async function main() {
  if (!RPC_URL || !MARKET_ADDRESS || !TOTAL_QUOTE) throw new Error("Fill RPC_URL, MARKET_ADDRESS, TOTAL_QUOTE");
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) throw new Error("Set PRIVATE_KEY in env");

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

  const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, MARKET_ADDRESS);
  const orderbook = await KuruSdk.OrderBook.getL2OrderBook(provider, MARKET_ADDRESS, marketParams);
  if (!orderbook.asks?.length) throw new Error("No asks available to anchor price range");

  const pricePow = BigInt(10) ** BigInt(KuruSdk.log10BigNumber(marketParams.pricePrecision));
  const bestAskFloat = orderbook.asks[0][0];
  const bestAskPrice = BigInt(Math.floor(bestAskFloat * Number(pricePow)));

  const startPrice = bestAskPrice - (bestAskPrice * RANGE_PCT) / 100n;
  const endPrice = bestAskPrice + (bestAskPrice * RANGE_PCT) / 100n;

  const totalQuoteWei = ethers.utils.parseUnits(TOTAL_QUOTE, Number(marketParams.quoteAssetDecimals));

  const batch = await KuruSdk.PositionViewer.getBidAskBatchLPDetails(
    MIN_FEES_BPS,
    startPrice,
    endPrice,
    bestAskPrice,
    BigInt(marketParams.pricePrecision.toString()),
    BigInt(marketParams.sizePrecision.toString()),
    BigInt(marketParams.quoteAssetDecimals.toString()),
    BigInt(marketParams.baseAssetDecimals.toString()),
    BigInt(marketParams.tickSize.toString()),
    BigInt(marketParams.minSize.toString()),
    BigInt(totalQuoteWei.toString()),
  );

  console.log("Provisioning bid-ask concentrated liquidity:", { bids: batch.bids.length, asks: batch.asks.length });
  const receipt = await KuruSdk.PositionProvider.provisionLiquidity(signer, MARKET_ADDRESS, batch);
  console.log("Tx:", receipt.transactionHash);
}

main().catch((e) => { console.error("Error:", e); process.exit(1); });

Curve

curved distribution (e.g., more weight near best price, tapering out).
placeCurveConcentratedLiquidity
import { ethers } from "ethers";
import * as KuruSdk from "@kuru-labs/kuru-sdk";

// Docs Template: Place curve-distributed concentrated liquidity
// 1) Fill placeholders below
// 2) export PRIVATE_KEY=<your_key>
// 3) Run: npx ts-node scripts/docs_scripts/placeCurveConcentratedLiquidity.ts

const RPC_URL = "";                 // TODO: RPC endpoint
const MARKET_ADDRESS = "";          // TODO: Orderbook/market address
const MIN_FEES_BPS = 30n;            // e.g., 30 for 0.3%
const RANGE_PCT = 1n;                // +/- percent around best ask
const TOTAL_QUOTE = "";             // TODO: human string (e.g., "1000")

async function main() {
  if (!RPC_URL || !MARKET_ADDRESS || !TOTAL_QUOTE) throw new Error("Fill RPC_URL, MARKET_ADDRESS, TOTAL_QUOTE");
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) throw new Error("Set PRIVATE_KEY in env");

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

  const marketParams = await KuruSdk.ParamFetcher.getMarketParams(provider, MARKET_ADDRESS);
  const orderbook = await KuruSdk.OrderBook.getL2OrderBook(provider, MARKET_ADDRESS, marketParams);
  if (!orderbook.asks?.length) throw new Error("No asks available to anchor price range");

  const pricePow = BigInt(10) ** BigInt(KuruSdk.log10BigNumber(marketParams.pricePrecision));
  const bestAskFloat = orderbook.asks[0][0];
  const bestAskPrice = BigInt(Math.floor(bestAskFloat * Number(pricePow)));

  const startPrice = bestAskPrice - (bestAskPrice * RANGE_PCT) / 100n;
  const endPrice = bestAskPrice + (bestAskPrice * RANGE_PCT) / 100n;

  const totalQuoteWei = ethers.utils.parseUnits(TOTAL_QUOTE, Number(marketParams.quoteAssetDecimals));

  const batch = await KuruSdk.PositionViewer.getCurveBatchLPDetails(
    MIN_FEES_BPS,
    startPrice,
    endPrice,
    bestAskPrice,
    BigInt(marketParams.pricePrecision.toString()),
    BigInt(marketParams.sizePrecision.toString()),
    BigInt(marketParams.quoteAssetDecimals.toString()),
    BigInt(marketParams.baseAssetDecimals.toString()),
    BigInt(marketParams.tickSize.toString()),
    BigInt(marketParams.minSize.toString()),
    BigInt(totalQuoteWei.toString()),
  );

  console.log("Provisioning curve concentrated liquidity:", { bids: batch.bids.length, asks: batch.asks.length });
  const receipt = await KuruSdk.PositionProvider.provisionLiquidity(signer, MARKET_ADDRESS, batch);
  console.log("Tx:", receipt.transactionHash);
}

main().catch((e) => { console.error("Error:", e); process.exit(1); });