# DAMM

## Abstract

The ALEX AMM v3 contract is an enhanced version of the Automated Market Making (AMM), optimized for concentrated liquidity. It utilizes the invariant function $(Vx + x) \* (Vy + y) = K$, which, with appropriately chosen virtual liquidity values $Vx$ and $Vy$, allows the AMM to focus liquidity within a specific price range $\[P\_{start}, P\_{end}]$.

## Math

The invariant function is:

$$
(Vx + x) \* (Vy + y) = K
$$

Where $Vx$ and $Vy$ ensure the pair is traded in the price range $\[P\_{start}, P\_{end}]$.

$$
Vx = f\_x(P\_{start}, P\_{end}, x, y)
$$

$$
Vy = f\_y(P\_{start}, P\_{end}, x, y)
$$

In order to simply the math, we segregate the price range $(0, \infty)$ by introducing an integer parameter $tick$ and bin size $bs$, where for each $tick$ we have a price range $P\_{start} = (1 + 0.01 \* bs)^{tick}$ and $P\_{end} = (1 + 0.01 \* bs)^{tick + 1}$.

We define $t = \sqrt{1 + 0.01 \* bs}$ and $p = P\_{start}$, then we have:

$$
P\_{end} = t^2 \* P\_{start} = pt^2
$$

The price of the pair is $P\_{start}$ when balance x is swapped out (i.e. $\Delta x = -x$), and is $P\_{end}$ when balance y is swapped out (i.e. $\Delta y = -y$). Hence:

$$
p = \frac{Vx}{Vy + y + \Delta y} = \frac{Vx^2}{Vx \* (Vy + y + \Delta y)} = \frac{Vx^2}{K}
$$

$$
pt^2 = \frac{Vx + x + \Delta x}{Vy} = \frac{Vy \* (Vx + x + \Delta x)}{Vy ^ 2} = \frac{K}{Vy^2}
$$

Hence:

$$
p^2t^2 = \frac{Vx^2}{Vy^2}
$$

$$
Vx = ptVy
$$

So:

$$
pt^2 = \frac{K}{Vy^2} = \frac{(Vx + x)(Vy + y)}{Vy^2} = \frac{(ptVy + x)(Vy + y)}{Vy^2}
$$

$$
p(t^2 - t)Vy^2 - (x + pty)Vy - xy = 0
$$

$$
Vy = \frac{x + pty + \sqrt{(x + pty)^2 + 4p(t^2 - t)xy}}{2p(t^2 - t)}
$$

$$
Vx = \frac{x + pty + \sqrt{(x + pty)^2 + 4p(t^2 - t)xy}}{2(t - 1)}
$$

## Swap x for y util price hit $P\_{max}$

This is similar to Immediate or Cancel (IOC) order in orderbook, given $\Delta x$, swap as much as possible until the price hit $P\_{max}$, where $P\_{max}$ is within the price range $\[P\_{start}, P\_{end}]$.

$$
P\_{max} = \frac{Vx + x + \Delta x\_{max}}{Vy + y - \Delta y}
$$

$$
K \* P\_{max} = (Vx + x + \Delta x\_{max})^2
$$

$$
\Delta x\_{max} = \sqrt{K \* P\_{max}} - (Vx + x)
$$

$$
\Delta x\_{swappable} = min(max(0, \Delta x\_{max}), \Delta x)
$$

$$
\Delta y = (Vy + y) - \frac{K}{Vx + x + \Delta x\_{swappable}}
$$

## Swap y for x util price hit $P\_{min}$

Likewise, given $\Delta y$, swap as much as possible until the price hit $P\_{min}$, where $P\_{min}$ is within the price range $\[P\_{start}, P\_{end}]$.

$$
P\_{min} = \frac{Vx + x - \Delta x}{Vy + y + \Delta y\_{max}}
$$

$$
\frac{K}{P\_{min}} = (Vy + y + \Delta y\_{max})^2
$$

$$
\Delta y\_{max} = \sqrt{\frac{K}{P\_{min}}} - (Vy + y)
$$

$$
\Delta y\_{swappable} = min(max(0, \Delta y\_{max}), \Delta y)
$$

$$
\Delta x = (Vx + x) - \frac{K}{Vy + y + \Delta y\_{swappable}}
$$

## Add liquidity

After adding $\Delta x$ and $\Delta y$ to the pool, the pool size (i.e. liquidity token balance) and token pair price will both be affected.

$$
Price = \frac{V\_x' + x + \Delta x}{V\_y' + y + \Delta y}
$$

The price should be checked to prevent unexpected deviation from other markets.

And the pool size increase proportionally to the virtual balances.

$$
L\_p' = L\_p \* \frac{V\_x'}{V\_x} = L\_p \* \frac{V\_y'}{V\_y}
$$

This is based on the fact that swapping does not change $V\_x$, $V\_y$, and the pool size. So when token y is depleted, i.e. $y = 0$, the balance of token x represents the total value of the pool:

$$
V\_x = \frac{x + pty + \sqrt{(x + pty)^2 + 4p(t^2 - t)xy}}{2(t - 1)} = \frac{x}{t-1}
$$

$$
x = (t-1)\*V\_x
$$

$$
L\_p' = L\_p \* \frac{x'}{x} = L\_p \* \frac{(t-1)\*V\_x'}{(t-1)\*V\_x}= L\_p \* \frac{V\_x'}{V\_x}
$$

The same when token x is depleted, i.e. $x = 0$, the balance of token y represents the total value of the pool:

$$
Vy = \frac{x + pty + \sqrt{(x + pty)^2 + 4p(t^2 - t)xy}}{2p(t^2 - t)} = \frac{y}{t-1}
$$

$$
y = (t-1)\*V\_y
$$

$$
L\_p' = L\_p \* \frac{y'}{y} = L\_p \* \frac{(t-1)\*V\_y'}{(t-1)\*V\_y}= L\_p \* \frac{V\_y'}{V\_y}
$$

## Reduce liquidity

If we change the existing pool balance proportionally, we get the new invariant function:

$$
(\sigma Vx + \sigma x) \* (\sigma Vy + \sigma y) = \sigma ^2 K
$$

$$
Price = \frac{\sigma Vx + \sigma x}{\sigma Vy + \sigma y} = \frac{Vx + x}{Vy + y}
$$

Hence changing the existing pool balance proportionally does not affect the price of the pair. So when adjusting the liquidity, for a given $\Delta x$ we have:

$$
\frac{y - \Delta y}{x - \Delta x} = \frac{y}{x}
$$

$$
\Delta y = \frac{y}{x} \* \Delta x
$$

## Appendix

### Implement pow-fixed in Clarity

The pow-fixed function needed for AMM v3 is defined as:

$$
pow(x, n) = x ^ n
$$

Where x is a fixed point number with precision $10^8$, and n is an integer.

To simplify the calculation in Clarity, we assume only a limited set of bin sizes are supported (e.g. 5%, 10%, and 20%), and the price range falls between 0.00000001 and 100000000. And then we can pre-calculate the result for $(1 + bs) ^ {(2 ^ n)}$.

```clarity
(define-constant PRICES_5 (list u26574222192236 u51550191262 u2270466719 u476494146 u218287458 u147745544 u121550625 u110250000 u105000000))
(define-constant PRICES_10 (list u19873012250342 u44579156845 u2111377674 u459497298 u214358881 u146410000 u121000000 u110000000))
(define-constant PRICES_20 (list u11684220576272 u34182189187 u1848842588 u429981695 u207360000 u144000000 u120000000))
```

So that for a given tick, we have:

$$
tick = \sum\_{i=n}^{0} (abs(tick & (2^i)) \* (2^i))
$$

$$
Price = \prod\_{i=n}^{0} (1 + 0.01 \* bs)^{abs(tick & (2^i)) \* (2^i)}
$$

This mathematical optimization reduce the calculation complexity exponentially.

## Calculate Virtual Balances in Clarity

Since there's no float point numbers in clarity, we use fixed decimal to calculate values, specifically using uint128 to represent a decimal with 8 fixed decimal places. So the calculations should be handled very cautiously in order to prevent precision loss (with error rate less than roughly 1e-8) and arithmetic overflow.

This is how it looks like after we translate the formula of the virtual balances into code using float point numbers:

```javascript
const ts = 1 + 0.01 * bin;
const t = Math.sqrt(ts);
const price = ts ** tick;
// x + pty
const x_pty = balanceX + price * t * balanceY;
const denominator_vx = 2 * (t - 1);
const denominator_vy = 2 * (price * (ts - t));
const numerator =
  x_pty +
  Math.sqrt(x_pty ** 2 + 2 * denominator_vy * balanceX * balanceY);
const vx = numerator / denominator_vx;
const vy = numerator / denominator_vy;
```

Then we translate it into code using fixed decimal with 8 decimal places:

```javascript
const ONE_8 = 10n ** 8n;
const ONE_16 = 10n ** 16n;
// bin: 1n, 5n, 10n or 20n
const ts = ONE_8 + (10n ** 6n) * bin;
const t = Math.sqrt(ts * ONE_8);
const price = pow_fixed(ts, tick);
// x + pty
const x_pty = balanceX + price * t * balanceY / ONE_16;
const denominator_vx = 2n * (t - ONE_8);
const denominator_vy = 2n * (price * (ts - t) / ONE_8);
const numerator =
  x_pty +
  Math.sqrt(x_pty ** 2n + 2n * denominator_vy * balanceX * balanceY / ONE_8);
const vx = numerator * ONE_8 / denominator_vx;
const vy = numerator * ONE_8 / denominator_vy;
```

Now we consider the value ranges of the input:

* balanceX, balanceY cap at 1000T=$10^{15}$: `[0n, 10n ** (8n + 15n)]`
* Allowed price range: `[10n ** 4n, 10n ** (8n + 7n)]`
* U128\_MAX: `2n ** 128n - 1n` = `340282366920938463463374607431768211455n`, approximately `3.4e38`

When the values are small, there might be precision loss, for example:

* When price and balanceY are small, e.g. price=1e4, balanceX=0, balanceY=1e3, `x_pty = 1e4 * 1e8 * 1e3 / 1e16 = 0` which should be `1e-9` in float point number, in this case `numerator = 2 * x_pty = 0` which should be `2e-9` in float point number, resulting `vx = 0` which should be `1e-9 / (1.01 ** 0.5 - 1) = 20.0498756211e-8` when `bin = 1`
* When price is small, e.g. price=1e4, `denominator_vy = 2 * 1e4 * (1.01 - 1.01 ** 0.5) = 100`, which should be `100.2487577582`

And when values are huge, there might be overflow, for example when `balanceX > 2e19` then `x_pty > 2e19`, `x_pty ** 2n > U128_MAX`

To remediate that, we can scale balanceX and balanceY, to make max(balanceX, balanceY) be around `1e16`. This is based on the fact that virtual balances and balances scale proportionally, which is proved before. And do not divide by 1e8 so fast when getting `denominator_vy`.

So we get the first revised version:

```javascript
const ONE_8 = 10n ** 8n;
const ONE_16 = 10n ** 16n;
// bin: 1n, 5n, 10n or 20n
const ts = ONE_8 + (10n ** 6n) * bin;
const t = Math.sqrt(ts * ONE_8);
const price = pow_fixed(ts, tick);
// scale balances
const amplify_factor = max(1n, ONE_16 / (max(balanceX, balanceY) + 1n));
const shrink_factor = max(max(balanceX, balanceY) / ONE_16, 1n);
const bx_scaled = balanceX * amplify_factor / shrink_factor;
const by_scaled = balanceY * amplify_factor / shrink_factor;
// x + pty
const x_pty = bx_scaled + (t * by_scaled / ONE_8) * price / ONE_8;
const denominator_vx = 2n * (t - ONE_8);
const denominator_vy_e8 = 2n * price * (ts - t);
const numerator =
  x_pty +
  Math.sqrt(x_pty ** 2n + 2n * denominator_vy_e8 * bx_scaled * by_scaled / ONE_16);
const vx_scaled = numerator * ONE_8 / denominator_vx;
const vy_scaled = numerator * ONE_16 / denominator_vy_e8;
const vx = vx_scaled * shrink_factor / amplify_factor;
const vy = vy_scaled * shrink_factor / amplify_factor;
```

There are 2 issues with the first revision:

1. The max value of `x_pty` can be roughly `1e16 + 1e8 * 1e16 * 1e15 / 1e16`, which is `1e23`, this will cause `x_pty ** 2` to overflow when calculating the numerator
2. `denominator_vy_e8 * bx_scaled * by_scaled / ONE_16` should be handled cautiously to prevent precision loss

For the first issue we use the same scaling method:

```javascript
const x_pty = bx_scaled + (t * by_scaled / ONE_8) * price / ONE_8;
const x_pty_shrink_factor = max(x_pty / ONE_16, 1n);
const x_pty_scaled = x_pty / x_pty_shrink_factor;
const denominator_vx = 2n * (t - ONE_8);
const denominator_vy_e8 = 2n * price * (ts - t);
// numerator is also scaled accordingly (numerator_scaled = numerator / x_pty_shrink_factor) to avoid overflow in the following calculations, and is approximately between 1e16 and 1e17
const numerator_scaled =
  x_pty_scaled +
  Math.sqrt(
    x_pty_scaled ** 2n +
    2n * denominator_vy_e8 * bx_scaled * by_scaled / ONE_16 / (x_pty_shrink_factor ** 2)
  );
const vx_scaled = numerator_scaled * ONE_8 / denominator_vx;
const vy_scaled = numerator_scaled * ONE_16 / denominator_vy_e8;
const vx = vx_scaled * shrink_factor * x_pty_shrink_factor / amplify_factor;
const vy = vy_scaled * shrink_factor * x_pty_shrink_factor / amplify_factor;
```

Since both `denominator_vy_e8` and `x_pty_shrink_factor` scale proportionally to price, and `x_pty_shrink_factor` is greater than 1 when `price` is greater than approximately `1e8`, hence `denominator_vy_e8 / x_pty_shrink_factor` is greater than `1e12`. And also `x_pty_shrink_factor` cap at approximately `1e7` when `by_scaled` and `price` are with the maximum value.

Base on those, we can rewrite `2n * denominator_vy_e8 * bx_scaled * by_scaled / ONE_16 / (x_pty_shrink_factor ** 2)` into `2n * (denominator_vy_e8 / x_pty_shrink_factor) * (bx_scaled * by_scaled / ONE_16 / x_pty_shrink_factor)` to prevent precision loss.

Now we get final revision:

```javascript
const ONE_8 = 10n ** 8n;
const ONE_16 = 10n ** 16n;
// bin: 1n, 5n, 10n or 20n
const ts = ONE_8 + (10n ** 6n) * bin;
const t = Math.sqrt(ts * ONE_8);
const price = pow_fixed(ts, tick);
// scale balances
const amplify_factor = max(1n, ONE_16 / (max(balanceX, balanceY) + 1n));
const shrink_factor = max(max(balanceX, balanceY) / ONE_16, 1n);
const bx_scaled = balanceX * amplify_factor / shrink_factor;
const by_scaled = balanceY * amplify_factor / shrink_factor;
// x + pty
const x_pty = bx_scaled + (t * by_scaled / ONE_8) * price / ONE_8;
const x_pty_shrink_factor = max(x_pty / ONE_16, 1n);
const x_pty_scaled = x_pty / x_pty_shrink_factor;
const denominator_vx = 2n * (t - ONE_8);
const denominator_vy_e8 = 2n * price * (ts - t);
// numerator is also scaled accordingly (numerator_scaled = numerator / x_pty_shrink_factor) to avoid overflow in the following calculations, and is approximately between 1e16 and 1e17
const numerator_scaled =
  x_pty_scaled +
  Math.sqrt(
    x_pty_scaled ** 2n +
    2n * (denominator_vy_e8 / x_pty_shrink_factor) * (bx_scaled * by_scaled / ONE_16 / x_pty_shrink_factor)
  );
const vx_scaled = numerator_scaled * ONE_8 / denominator_vx;
const vy_scaled = numerator_scaled * ONE_16 / denominator_vy_e8;
const vx = vx_scaled * shrink_factor * x_pty_shrink_factor / amplify_factor;
const vy = vy_scaled * shrink_factor * x_pty_shrink_factor / amplify_factor;
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.alexlab.co/developers/products/amm-v3.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
