Custom Market-Making Algorithm

The AMM Blueprint’s modular design allows developers to define and extend their own market-making algorithms by implementing core Pool instance methods. This design defines a universal interface, enabling different algorithms to seamlessly integrate into the AMM system.

Interface

Custom AMM algorithms must implement the following interfaces:

  • Pool.new: Initializes the Pool instance.

  • Pool:balances: Returns the asset balances in the pool.

  • Pool:Info: Returns metadata about the pool.

  • Pool:updateLiquidity: Updates the liquidity of the pool.

  • Pool:updateAfterSwap: Updates the pool’s input or output asset balances after a swap.

  • Pool:getAmountOut: Calculates the output asset quantity based on a given input asset amount.

Developers must ensure that these interfaces are implemented to meet the AMM Blueprint’s call conventions.

Below, we will analyze the BigOrder market-making algorithm to help developers better understand how to create custom market-making algorithms.

BigOrder Market-Making Algorithm Analysis

BigOrder is a simple AMM market-making algorithm that facilitates exchange based on a fixed ratio between the input and output assets.

Use Case Example:

For instance, a user wants to sell 10,000 AR at a price of 20 USDC per AR. If directly swapped on a DEX, there might be significant slippage due to insufficient liquidity. To avoid this, the user can deploy an AMM Agent based on the BigOrder algorithm, setting tokenIn as 10,000 AR and tokenOut as 200,000 USDC. The agent will then offer this liquidity to the FFP system, ensuring that matching orders for AR purchases are automatically filled. The agent will continue matching orders in FFP until all 10,000 AR are exchanged.

Code Breakdown:

1. Pool.new

code
function Pool.new(x, y, params)
    if not validateInitParams(params) then
        return nil
    end
    local self = setmetatable({}, Pool)
    self.algo = 'BigOrder'
    self.x = x
    self.y = y
    self.tokenIn = params.tokenIn
    self.tokenOut = params.tokenOut
    self.amountOut = params.amountOut
    self.amountIn = params.amountIn
    self.balanceTokenIn = '0'
    self.balanceTokenOut = self.amountOut
    return self
end

This method initializes a new BigOrder pool instance.

Parameters:

  • tokenIn and tokenOut are the input and output asset types.

  • amountIn and amountOut specify the exchange ratio.

  • balancesTokenIn and balancesTokenOut track the asset balances.

2. Pool:balances

code
function Pool:balances()
    local balance = {
        [self.tokenOut] = self.balanceTokenOut,
        [self.tokenIn] = self.balanceTokenIn
    }
    return balance
end

This method returns the current balances of the two assets in the pool.

3. Pool:Info

code
function Pool:info()
    local info = {
        x = self.x,
        y = self.y,
        algo = self.algo,
        tokenIn = self.tokenIn,
        tokenOut = self.tokenOut,
        amountOut = self.amountOut,
        amountIn = self.amountIn,
        balances = self:balances()
    }
    return info
end

This method returns metadata about the pool.

4. Pool:updateLiquidity

function Pool:updateLiquidity(params)
    return false
end

This algorithm does not require liquidity pool information updates.

5. Pool:updateAfterSwap

function Pool:updateAfterSwap(tokenIn, amountIn, tokenOut, amountOut)
   self.balanceTokenIn = utils.add(self.balanceTokenIn, amountIn)
   self.balanceTokenOut = utils.subtract(self.balanceTokenOut, amountOut)
end

After a trade is executed, this method updates the asset balances in the pool.

6. Pool:getAmountOut

function Pool:getAmountOut(tokenIn, amountIn)
    if tokenIn == self.tokenIn then
        local amountOut = utils.div(utils.mul(amountIn, self.amountOut), self.amountIn)
        if utils.lt(amountOut, self.balanceTokenOut) then
            return self.tokenOut, tostring(amountOut)
        end
    end

    return nil, nil
end

This method calculates the output amount of assets based on the input asset type and amount.

The formula for the fixed ratio is:

Complete Code

bigorder.lua
local bint = require('.bint')(1024)

local Pool = {}
Pool.__index = Pool 

local utils = {
    add = function (a,b) 
      return tostring(bint(a) + bint(b))
    end,
    subtract = function (a,b)
      return tostring(bint(a) - bint(b))
    end,
    mul = function(a, b)
        return tostring(bint.__mul(bint(a), bint(b)))
    end,
    div = function(a, b)
        return tostring(bint.udiv(bint(a), bint(b)))
    end,
    lt = function (a, b)
        return bint.__lt(bint(a), bint(b))
    end
}

local function validateAmount(amount)
    if not amount then
        return false, 'err_no_amount'
    end
    local ok, qty = pcall(bint, amount)
    if not ok then
        return false, 'err_invalid_amount'
    end
    if not utils.lt(0, qty) then
        return false, 'err_negative_amount'
    end
    return true, nil
end

local function validateInitParams(params)
    if not params then
        return false
    end
    return params.tokenOut and params.tokenIn and validateAmount(params.amountOut) and validateAmount(params.amountIn)
end

function Pool.new(x, y, params)
    if not validateInitParams(params) then
        return nil
    end
    local self = setmetatable({}, Pool)
    self.algo = 'BigOrder'
    self.x = x
    self.y = y
    self.tokenIn = params.tokenIn
    self.tokenOut = params.tokenOut
    self.amountOut = params.amountOut
    self.amountIn = params.amountIn
    self.balanceTokenIn = '0'
    self.balanceTokenOut = self.amountOut
    return self
end

function Pool:balances()
    local balance = {
        [self.tokenOut] = self.balanceTokenOut,
        [self.tokenIn] = self.balanceTokenIn
    }
    return balance
end

function Pool:info()
    local info = {
        x = self.x,
        y = self.y,
        algo = self.algo,
        tokenIn = self.tokenIn,
        tokenOut = self.tokenOut,
        amountOut = self.amountOut,
        amountIn = self.amountIn,
        balances = self:balances()
    }
    return info
end

function Pool:updateLiquidity(params)
    return false
end

function Pool:updateAfterSwap(tokenIn, amountIn, tokenOut, amountOut)
   self.balanceTokenIn = utils.add(self.balanceTokenIn, amountIn)
   self.balanceTokenOut = utils.subtract(self.balanceTokenOut, amountOut)
end

function Pool:getAmountOut(tokenIn, amountIn)
    if tokenIn == self.tokenIn then
        local amountOut = utils.div(utils.mul(amountIn, self.amountOut), self.amountIn)
        if utils.lt(amountOut, self.balanceTokenOut) then
            return self.tokenOut, tostring(amountOut)
        end
    end

    return nil, nil
end

return Pool

Summary

BigOrder is a simple and easy-to-use market-making algorithm, with a clear and straightforward implementation logic, making it an ideal example for custom AMM market-making algorithms in the AMM Blueprint. Developers can use the BigOrder implementation as a reference to build more complex algorithms, such as Uniswap V2 or V3 market-making curve algorithms.

Last updated

Was this helpful?