Turbine is a private orderbook built for large and long-running orders to settle peer-to-peer.
Orders and LP on Turbine follow the market and settle only when the spread is low. Turbine can settle small and large trades at low, zero, and even negative spreads.
Read the docs at https://docs.turbine.exchange.
This SDK helps you interact with Turbine.
Make sure you have Node.js and Yarn installed.
Clone the repo and install the dependencies with:
git clone https://github.com/propeller-heads/turbine-sdk.git
cd turbine-sdk
yarnThe main entry point is the TurbineClient class.
Here's how to instantiate it. This example requires a private key and an RPC URL.
import { TurbineClient } from "turbine-sdk";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
// Create Viem clients
const account = privateKeyToAccount(PRIVATE_KEY);
const walletClient = createWalletClient({
account: account,
chain: mainnet,
transport: http(RPC_URL),
});
const publicClient = createPublicClient({
chain: mainnet,
transport: http(RPC_URL),
});
// Create Turbine client.
const turbineClient = await TurbineClient.create(walletClient, publicClient);Important
You need to allow Permit2 contract to spend your sell token! You can do this by running the yarn approve-token <tokenAddress> script.
Note
When placing orders, SDK automatically adds necessary Permit2 approvals. If you're using a walletClient with a private key, these approvals are signed automatically without asking for confirmation.
Note
For the Beta version, Turbine has a limit of 80 active orders per owner.
Tip
You can also submit orders using our frontend: https://app.turbine.exchange/
Orders in Turbine are represented by the OrderIntent interface. They contain the following fields:
owner: The address of the user who is selling the tokenssellToken: Address of the token being soldbuyToken: Address of the token being boughtsellAmount: Amount of sell token (in atomic units)minBuyAmount: Minimum amount of buy token to receive (defines the limit price)spreadCurve: Piecewise-linear mid-price-delta curve over the order window. Required on every order.startDeltaBps,endDeltaBps: signed delta in basis points at the order's start and end. Range[-10_000, 10_000)— negative values mean the order accepts only fills better than the mid-price.points: interior knots{windowBps, deltaBps}, strictly increasingwindowBpswindowBpsis integer in(0, 10_000)(open interval) —5000means halfway through[startTime, endTime]- Use the
spreads.constant(deltaBps)helper for the common flat case
startTime: Unix timestamp when the order becomes valid- Note: only immediately valid orders are supported for now.
endTime: Unix timestamp when the order expirespartialFill: Boolean flag allowing partial fills of the order.- Note: only partially fillable orders are supported for now. This must be
true.
- Note: only partially fillable orders are supported for now. This must be
callData: Optional call data for smart orders, allowing custom routing (see #smart-orders below)callDataTarget: Target address for the call datasalt: Random value to ensure order uniqueness
Warning
Smart orders will be deprecated soon. Market makers are encouraged to submit their quotes as regular orders.
Smart orders allow using a third party smart contract to route the order.
Smart orders are executed by calling a function encoded in callData on the callDataTarget contract.
Smart orders are intented to be used by market makers. Submitting them to Turbine is permissioned. If you are a market maker and want to use Turbine with your own router contract, please reach out to Propeller Heads.
import { NULL_ADDRESS, USDC, WETH } from "turbine-sdk/constants";
import { OrderIntent, spreads, getRandomSalt} from "turbine-sdk";
// Create an order to sell USDC for WETH
const order: OrderIntent = {
owner: account.address,
sellToken: USDC.address,
buyToken: WETH.address,
sellAmount: USDC.toOnchainAmount("100"), // Sell 100 USDC
minBuyAmount: WETH.toOnchainAmount("0.05"), // Buy 0.05 WETH
spreadCurve: spreads.constant(500), // 5% flat spread (at most 5% worse than mid-price)
startTime: Math.floor(Date.now() / 1000), // Start now
endTime: Math.floor(Date.now() / 1000) + 3600, // End in 1 hour
partialFill: true,
callData: "0x",
callDataTarget: NULL_ADDRESS,
salt: getRandomSalt(),
};const orderHash = await turbineClient.addOrder(order);
console.log(`Order submitted with ID: ${orderHash}`);Expect Turbine to execute your order after some time (if it can be executed at current market conditions).
const orderHashes = await turbineClient.addOrders([order1, order2, order3]);const orderStates = await turbineClient.getOrders({statuses: ["Active"]});Check the documentation of getOrders method for the details.
await turbineClient.cancelOrder(orderHash);Please note that order cancellation is subject to speedbump (see Turbine docs). An order will not be cancelled immediately, but after a short delay.
Tip
You can also submit liquidity intents using our frontend: https://app.turbine.exchange/
import { AddLiquidityIntent, getRandomSalt } from "turbine-sdk";
import { USDC, WETH } from "turbine-sdk/constants";
// Create an intent to provide 3000 USDC and 1 WETH
// to the USDC/WETH Turbine pool at 0.3% fee tier
const intent: AddLiquidityIntent = {
owner: account.address,
token0: USDC.address,
token1: WETH.address,
fee: 3000, // 0.3%
token0Amount: USDC.toOnchainAmount("3000"),
token1Amount: WETH.toOnchainAmount("1"),
exact: true,
salt: getRandomSalt(),
};const intentHash = await turbineClient.addLiquidity(intent);
console.log(`Liquidity intent submitted with ID: ${intentHash}`);Expect Turbine to take your tokens, add them to the pool, and mint you LP tokens.
You need to know the LP token address of the pool you want to remove liquidity from. You have received this token when you provided liquidity to the pool.
You can get LP token's address by calling getPools method and inspecting the result:
const pools = await turbineClient.getPools();
console.log(pools);...or you can use the list-pools script, which will print the list of pools with their LP token addresses:
yarn list-poolsNow you can create an intent to remove liquidity:
import { RemoveLiquidityIntent } from "turbine-sdk/models";
import { getRandomSalt } from "turbine-sdk/turbineClient";
import { USDC, WETH } from "turbine-sdk/constants";
// Create an intent to burn 10_000 LP tokens from the USDC/WETH Turbine pool
const intent: RemoveLiquidityIntent = {
owner: account.address,
token0: USDC.address,
token1: WETH.address,
fee: 3000, // 0.3%
lpToken: "0x24746c26c7b83ddabbaf384e02c3eb0e7b8cd307",
lpTokenAmount: 10_000,
salt: getRandomSalt(),
};const intentHash = await turbineClient.removeLiquidity(intent);
console.log(`Liquidity intent submitted with ID: ${intentHash}`);Expect Turbine to burn your LP tokens, and send you a share of the pool's liquidity.
In case Turbine goes offline, you can submit an intent to remove liquidity directly on-chain.
const intentHash = await turbineClient.submitRemoveLiquidityIntentOnchain(intent);
console.log(`Liquidity intent submitted with ID: ${intentHash}`);This will submit a transaction to the TurbineLiquidityRouter contract. Now your intent needs to wait a bit to pass a speedbump, and then you can execute it.
const intentHash = await turbineClient.executePendingRemoveLiquidityIntentsOnchain([
intentHash,
]);This will submit a transaction to the TurbineLiquidityRouter contract to execute your intent. Expect your LP tokens to be burned and your share of the pool's liquidity to be sent to you.
const intentStates = await turbineClient.getLiquidityIntents([
intentHash1,
intentHash2,
intentHash3,
]);
console.log(intentStates);This will return the state of the liquidity intents submitted via API. It won't list the intents submitted on-chain.
This repository contains example scripts that demonstrate common actions on Turbine.
The scripts are located in the scripts directory.
See the scripts README for more details.