Skip to main content

Deploy Kandel strategy

This tutorial covers how to deploy a Kandel strategy from a developer standpoint. For more information about Kandel, see the Kandel documentation.

Prerequisites​

  • The tutorial assumes knowledge of JavaScript
  • Follow preparation to create a new tutorial folder.
  • Make sure to use a chain where Mangrove is live - you can find all live addresses for Mangrove here
  • For a more simple tutorial to get acquainted with Mangrove, we recommend Deploy a simple offer

Start local node​

Before proceeding, let's import the environment variables made as part of the preparation.

source .env

Start Foundry's local node anvil to test things locally, with $RPC_URL coming from .env and pointing, for instance, to the Polygon Mumbai testnet.

anvil --fork-url $RPC_URL

Import and connect​

Start up node in a new terminal and issue the following code which performs the initial setup of loading the .env you added in preparation, importing the Mangrove SDK, and connecting the SDK to the local node for a specific market.

examples/tutorials/deploy-kandel.js
// Load environment variables (RPC_URL and PRIVATE_KEY) from the .env file
require("dotenv").config();

// Import Mangrove and KandelStrategies APIs
const {
Mangrove,
KandelStrategies,
ethers,
} = require("@mangrovedao/mangrove.js");

// Create a wallet with a provider to interact with the chain
const provider = new ethers.providers.WebSocketProvider(process.env.LOCAL_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Connect the API to Mangrove
const mgv = await Mangrove.connect({ signer: wallet });

// Choose a market
const market = await mgv.market({
base: "WETH",
quote: "USDT",
tickSpacing: 1,
});

Generate a minimum distribution​

Next, create an instance to manage Kandel strategies (kandelStrategies), and load the recommended configuration for the market.

examples/tutorials/deploy-kandel.js
// Initialize KandelStrategies for strategy management
const kandelStrategies = new KandelStrategies(mgv);

// Retrieve default configuration for the selected market
const config = kandelStrategies.configuration.getConfig(market);

With this, you can generate a distribution with the minimum recommended amount of liquidity to avoid density issues by:

  • Creating a generator
  • Calculating minimums per offer
  • Calculating the distribution for the given price parameters of minPrice: 900, maxPrice: 1100, and priceRatio: 1.01.

See the API documentation for calculateMinimumDistribution for more details on other distributionParams. In our example here, midPrice: 1100 is used to set the current price, and decide which offers become bids and which become asks.

examples/tutorials/deploy-kandel.js
// Create a distribution generator for the selected market
const distributionGenerator = kandelStrategies.generator(market);

// Get the minimum required base and quote amounts per offer for the market
const minBasePerOffer = await kandelStrategies.seeder.getMinimumVolume({
market,
offerType: "asks",
onAave: false,
});
const minQuotePerOffer = await kandelStrategies.seeder.getMinimumVolume({
market,
offerType: "bids",
onAave: false,
});

// Calculate a candidate distribution with the recommended minimum volumes given the price range and a price ratio of roughly 1% (it gets converted to an offset in ticks between prices, and some precision is lost)
// The price points are generated outwards from the mid price, and the default step size for the market is used.
const minDistribution =
await distributionGenerator.calculateMinimumDistribution({
distributionParams: {
minPrice: 900,
maxPrice: 1100,
priceRatio: 1.01,
midPrice: 1000,
generateFromMid: true,
stepSize: config.stepSize,
},
minimumBasePerOffer: minBasePerOffer,
minimumQuotePerOffer: minQuotePerOffer,
});

// Output information about the minimum distribution
const minVolumes = minDistribution.getOfferedVolumeForDistribution();
console.log("Number of price points:", minDistribution.pricePoints);
console.log("Minimum base volume:", minVolumes.requiredBase.toString());
console.log("Minimum quote volume:", minVolumes.requiredQuote.toString());

The last three lines should output something similar to the following (actual volumes may differ due to different configuration for the market):

Number of price points: 21
Minimum base volume: 0.0021
Minimum quote volume: 1.9063

πŸ’‘ The minimums depend on the price; if the price range is changed, then the minimums should be re-checked.

Generate desired distribution​

Based on the minimum volumes we calculated, we can select a desired distribution with volumes above these values. Here we use 3 for base (WETH) and 3000 for quote (USDC).

examples/tutorials/deploy-kandel.js
// Recalculate the distribution based on desired base and quote amounts, which should be at least the recommended.
const finalDistribution =
await distributionGenerator.recalculateDistributionFromAvailable({
distribution: minDistribution,
availableBase: 3,
availableQuote: 3000,
});
const offeredVolumes = finalDistribution.getOfferedVolumeForDistribution();

// Inspect the final distribution's offers - their raw ticks are shown along with their price - and initially dead offers can be seen with 0 gives.
console.log(finalDistribution.getOffersWithPrices());

Note the final log which shows all the bids and asks that will be created, including those that will initially have no liquidity (0 gives) but serve as dual offers of the other side of the book.

Deploy Kandel instance​

Now, you can use the seeder to sow a Kandel instance for a given seed, and retrieve a kandelInstance to manage the deployed instance.

examples/tutorials/deploy-kandel.js
// Prepare seed data for deploying a Kandel instance
const seed = {
onAave: false,
market,
liquiditySharing: false,
};

// Deploy a Kandel instance with the specified seed data (the offers are later populated based on the above distribution)
const { result: kandelPromise } = await kandelStrategies.seeder.sow(seed);
const kandelInstance = await kandelPromise;

A brief explanation on the above seed parameters:

  • onAave indicates whether or not the liquidity to be used by Kandel is sitting on AAVE - here, it is not the case (it will be fetched from a wallet)
  • market: this is the WETH/USDC pair that we previously chose
  • liquiditySharing indicates whether you are using shared liquidity or not (SDK only, not available via the UI). This refers to the concept amplified liquidity.

Approve transfers​

The kandelInstance has functions for approving transfers for the base and quote tokens. This is required for the Kandel strategy to be able to transfer tokens from the wallet when depositing funds.

examples/tutorials/deploy-kandel.js
// Approve Kandel instance to use our funds
const approvalTxs = await kandelInstance.approveIfHigher();

// Wait for approval transactions (one for base, one for quote) to be mined
const approvalReceipts = await Promise.all(approvalTxs.map((x) => x?.wait()));

Mint test tokens​

If you are running on a testnet, then you can mint test tokens to send to the Kandel instance.

examples/tutorials/deploy-kandel.js
// To mint test tokens the following can be used
await (
await market.base.contract.mint(
market.base.toUnits(offeredVolumes.requiredBase),
)
).wait();
await (
await market.quote.contract.mint(
market.quote.toUnits(offeredVolumes.requiredQuote),
)
).wait();

Populate offers for the distribution​

Now that our Kandel instance is deployed, we can populate the offers for the distribution. This will create offers for the base and quote tokens, and deposit the required amounts of tokens into the Kandel instance.

The offers also need a provision, hence here the default that we are using can be inspected.

πŸ’‘ The population can span multiple transactions due to gas limits. After this step, the Kandel offers are deployed and are ready to be taken!

examples/tutorials/deploy-kandel.js
// Populate the Kandel instance according to our desired distribution (can be multiple transactions if there are many price points)
const populateTxs = await kandelInstance.populateGeometricDistribution({
distribution: finalDistribution,
depositBaseAmount: offeredVolumes.requiredBase,
depositQuoteAmount: offeredVolumes.requiredQuote,
});

// The populate uses the recommended provision of native tokens for the offers which can be inspected with:
ethers.utils.formatUnits(populateTxs[0].value, "ether");
// The recommended provision was calculated using this:
await kandelStrategies.seeder.getRequiredProvision(seed, finalDistribution);

// Wait for the populate transactions to be mined
const populateReceipts = await Promise.all(populateTxs.map((x) => x.wait()));

// If the transactions went through, then the kandel is now populated and has the funds transferred to it
console.log(
"Kandel balance of base =",
await kandelInstance.getBalance("asks"),
"and quote =",
await kandelInstance.getBalance("bids"),
);

Manage existing Kandel instance​

Later on, you can also manage an existing Kandel instance. For example, you might want to inspect the status of your offers. For this, the farm can be used to retrieve Kandel instances you own based on events from the seeder.

examples/tutorials/deploy-kandel.js
// Retrieve deployed Kandels owned by the wallet via the farm which detects Kandels by inspecting events from the seeder.
const ownedKandels = await kandelStrategies.farm.getKandels({
owner: wallet.address,
});

// Get an instance to interact with one of the deployed Kandels
const deployedKandel = await kandelStrategies.instance({
address: ownedKandels[0].kandelAddress,
});

// We can get the status of the offers using the following which retrieves data for all the Kandel offers and correlates it with the given mid price
const offerStatuses = await deployedKandel.getOfferStatuses(1000);
// Inspect the lowest priced bid
console.log(offerStatuses.statuses[0].bids);

Close Kandel strategy and withdraw funds​

At some point, you might want to close your Kandel strategy (for instance due to price movements). This can be easily done with the retractAndWithdraw function. It will withdraw all funds (both tokens and provision) from the Kandel instance and retract all offers.

examples/tutorials/deploy-kandel.js
// Finally, we may at some point want to withdraw the entire instance and all funds
const withdrawTxs = await deployedKandel.retractAndWithdraw();
const withdrawReceipts = await Promise.all(withdrawTxs.map((x) => x.wait()));

// Check the Kandel instance balances after withdrawal are now 0
console.log(
"Kandel balance of base =",
await kandelInstance.getBalance("asks"),
"and quote =",
await kandelInstance.getBalance("bids"),
);