CenturionDEX

Liquidity position

Last modified:

Consider a CenturionDEX v3 liquidity pool with tokens XX and YY. Throughout this section, the price will always mean the price of token XX expressed in units of token YY. Suppose that a liquidity provider chooses to supply liquidity over the price interval [pa,pb][p_a,p_b]. We adopt the convention that the position is active on the closed interval [pa,pb][p_a,p_b]. The defining feature of CenturionDEX v3 is that the position is active only within that interval: its liquidity is used to facilitate trades only while the market price remains between pap_a and pbp_b. If the price moves outside the chosen range, the position becomes inactive and its balances remain unchanged until the price re-enters the interval [pa,pb][p_a,p_b]. Moreover, if the price falls below pap_a, the position is entirely converted into token XX; if the price rises above pbp_b, it is entirely converted into token YY. In other words, once the price exits the range, the position ends up holding only the token associated with that side of the interval.

Although the CenturionDEX v3 AMM still relies on a constant-product structure, the geometry must be modified so that one of the token balances can become zero exactly when the price reaches one of the interval boundaries. To achieve this, we apply the translation illustrated in the animation below to the virtual constant-product curve

xvyv=L2,x_v \cdot y_v = L^2,

where (xv,yv)(x_v,y_v) denote virtual reserves; we will distinguish these from real balances (x,y)(x,y) below.

Recall that, for a state (P=(xv,yv))(P=(x_v,y_v)) on this virtual curve, the price is given by

p=yvxv,p=\frac{y_v}{x_v},

which is precisely the slope of the line joining the origin to PP. Consequently, in the animation above, the state aa corresponds to the lower boundary price pap_a, while the state bb corresponds to the upper boundary price pbp_b, and therefore pa<pbp_a<p_b.

To determine the translation that converts the virtual constant-product curve into the actual balance curve of a CenturionDEX v3 position, we begin by evaluating the virtual boundary states associated with the chosen price interval [pa,pb][p_a,p_b]. Let

va=(xav,yav),vb=(xbv,ybv)v_a=\left(x_a^{v},y_a^{v}\right),\qquad v_b=\left(x_b^{v},y_b^{v}\right)

denote the virtual states corresponding to the lower and upper boundary prices pap_a and pbp_b, respectively. Since these states lie on the virtual curve

xvyv=L2x_v y_v=L^2

and satisfy

yavxav=pa,ybvxbv=pb,\frac{y_a^{v}}{x_a^{v}}=p_a, \qquad \frac{y_b^{v}}{x_b^{v}}=p_b,

we obtain

xav=Lpa,yav=Lpa,x_a^{v}=\frac{L}{\sqrt{p_a}}, \qquad y_a^{v}=L\sqrt{p_a},

and

xbv=Lpb,ybv=Lpb.x_b^{v}=\frac{L}{\sqrt{p_b}}, \qquad y_b^{v}=L\sqrt{p_b}.

Therefore,

va=(Lpa,Lpa),vb=(Lpb,Lpb).v_a=\left(\frac{L}{\sqrt{p_a}},\,L\sqrt{p_a}\right), \qquad v_b=\left(\frac{L}{\sqrt{p_b}},\,L\sqrt{p_b}\right).

At the lower boundary pap_a, the actual balance of token YY must be zero, so the corresponding real state must lie on the horizontal axis. This requires a downward translation by LpaL\sqrt{p_a}. Likewise, at the upper boundary pbp_b, the actual balance of token XX must be zero, so the corresponding real state must lie on the vertical axis. This requires a leftward translation by L/pbL/\sqrt{p_b}. Hence, the virtual curve is translated by the vector

(Lpb,Lpa).\left(-\frac{L}{\sqrt{p_b}},\,-L\sqrt{p_a}\right).

Applying this translation to the virtual curve xvyv=L2x_v y_v=L^2, we obtain the equation of the real-balance curve:

(x+Lpb)(y+Lpa)=L2.\left(x+\frac{L}{\sqrt{p_b}}\right)\left(y+L\sqrt{p_a}\right)=L^2.

This is the curve of real balances shown in the animation above.

Applying the same translation to the boundary virtual states yields the corresponding real boundary states

ra=(LpaLpb,0),rb=(0,LpbLpa).r_a=\left(\frac{L}{\sqrt{p_a}}-\frac{L}{\sqrt{p_b}},\,0\right), \qquad r_b=\left(0,\,L\sqrt{p_b}-L\sqrt{p_a}\right).

These are precisely the two endpoints of the active range on the real-balance curve.

Virtual reserves. We now introduce the virtual reserves, which are central to the concentrated-liquidity model. Let xx and yy denote the actual balances of tokens XX and YY in a position, and let xvx_v and yvy_v denote the corresponding virtual reserves. By definition, the virtual reserves satisfy the constant-product relation

xvyv=L2,x_v y_v=L^2,

and the spot-price relation

yvxv=p,\frac{y_v}{x_v}=p,

where pp is the current price of token XX in units of token YY. Thus, (xv,yv)(x_v,y_v) represents a virtual pool state lying on the virtual curve in the animation above. When the market price remains inside the interval [pa,pb][p_a,p_b], trades are modeled as taking place along this virtual constant-product curve, even though the actual balances of the position are given by the translated real-balance curve.

The real and virtual reserves are related by the translation derived above:

xv=x+Lpb,yv=y+Lpa.x_v=x+\frac{L}{\sqrt{p_b}}, \qquad y_v=y+L\sqrt{p_a}.

Accordingly, the real-balance equation above may be written equivalently as

(x+Lpb)(y+Lpa)=L2\left(x+\frac{L}{\sqrt{p_b}}\right)\left(y+L\sqrt{p_a}\right)=L^2

or, in virtual-reserve form,

xvyv=L2.x_v y_v=L^2.

Since

yvxv=p,\frac{y_v}{x_v}=p,

it follows that

xv=Lp,yv=Lp.x_v=\frac{L}{\sqrt{p}}, \qquad y_v=L\sqrt{p}.

Real balances at price pp. We can now express the actual balances of a position as functions of the current price. If p[pa,pb]p\in[p_a,p_b], then the position is active and the real-balance equation above applies. Substituting the virtual-reserve identities above into the translation relation, we obtain

x=LpLpb,y=LpLpa.x=\frac{L}{\sqrt{p}}-\frac{L}{\sqrt{p_b}}, \qquad y=L\sqrt{p}-L\sqrt{p_a}.

In the boundary cases, these formulas reduce to the expected values. If p=pap=p_a, then

y=0,x=LpaLpb,y=0, \qquad x=\frac{L}{\sqrt{p_a}}-\frac{L}{\sqrt{p_b}},

so the position is entirely held in token XX. If p=pbp=p_b, then

x=0,y=LpbLpa,x=0, \qquad y=L\sqrt{p_b}-L\sqrt{p_a},

so the position is entirely held in token YY.

If the price moves outside the active interval, the balances remain frozen at the nearest boundary state. Therefore, the actual balances of a CenturionDEX v3 position are given by the following piecewise formulas:

Price rangeReal balance of token XReal balance of token YppaL(1pa1pb)0pappbL(1p1pb)L(ppa)ppb0L(pbpa)\begin{array}{c|c|c} \text{Price range} & \text{Real balance of token } X & \text{Real balance of token } Y \\[4pt] \hline p\le p_a & L\left(\frac{1}{\sqrt{p_a}}-\frac{1}{\sqrt{p_b}}\right) & 0 \\[10pt] p_a\le p\le p_b & L\left(\frac{1}{\sqrt{p}}-\frac{1}{\sqrt{p_b}}\right) & L\left(\sqrt{p}-\sqrt{p_a}\right) \\[10pt] p\ge p_b & 0 & L\left(\sqrt{p_b}-\sqrt{p_a}\right) \end{array}

This is the natural canonical piecewise form.

Opening a position. Suppose now that a liquidity provider wants to create a CenturionDEX v3 position with chosen parameters pap_a, pbp_b, and LL. Since trading fees in CenturionDEX v3 are accounted for separately rather than automatically reinvested into liquidity, the parameter LL remains fixed for a given position unless liquidity is explicitly added or removed. Consequently, once pap_a, pbp_b, and LL are fixed, both the virtual-balance curve and the real-balance curve are fixed as well, and the state of the position always lies on the corresponding real-balance curve.

It follows that, given the current price pp, the liquidity provider must deposit exactly the token amounts prescribed by the piecewise formulas above. Concretely, there are three possible cases.

(i) If the current price satisfies p<pap<p_a, then the position starts entirely in token XX. The required deposit is

x=L(1pa1pb),y=0.x=L\left(\frac{1}{\sqrt{p_a}}-\frac{1}{\sqrt{p_b}}\right), \qquad y=0.

(ii) If the current price satisfies pappbp_a\le p\le p_b, then the position starts with both tokens. The required deposits are

x=L(1p1pb),y=L(ppa).x=L\left(\frac{1}{\sqrt{p}}-\frac{1}{\sqrt{p_b}}\right), \qquad y=L\left(\sqrt{p}-\sqrt{p_a}\right).

(iii) If the current price satisfies p>pbp>p_b, then the position starts entirely in token YY. The required deposit is

x=0,y=L(pbpa).x=0, \qquad y=L\left(\sqrt{p_b}-\sqrt{p_a}\right).

These three situations correspond to the three states represented in animation below.

Example

In practice, a liquidity provider will usually not choose the liquidity parameter LL directly. Instead, the provider selects a price interval [pa,pb][p_a,p_b] and decides how much of one of the two tokens to contribute. Once that information is fixed, the value of LL can be recovered from the position formulas, and the required amount of the other token can then be computed accordingly.

Consider a CenturionDEX v3 pool between CTN and USDC, and suppose that the current price of CTN in units of USDC is

p0=3600.p_0 = 3600.

Assume that a liquidity provider wants to open a position over the interval

[pa,pb]=[2500,4900],[p_a,p_b]=[2500,4900],

and that, at the current price p0p_0, they want the position to contain

x(p0)=5x(p_0)=5

units of CTN.

Since pa<p0<pbp_a < p_0 < p_b, the position is opened inside its active interval, so both tokens must be deposited. From the canonical formula for the real balance of token XX inside the active range, we have

x(p)=L(1p1pb).x(p)=L\left(\frac{1}{\sqrt{p}}-\frac{1}{\sqrt{p_b}}\right).

Evaluating at p=p0p=p_0, we obtain

5=L(1360014900)=L(160170).5 = L\left(\frac{1}{\sqrt{3600}}-\frac{1}{\sqrt{4900}}\right) = L\left(\frac{1}{60}-\frac{1}{70}\right).

Hence,

5=L420,soL=2100.5 = \frac{L}{420}, \qquad\text{so}\qquad L = 2100.

Now that LL is known, the required amount of USDC follows from the canonical formula for the real balance of token YY inside the active range:

y(p)=L(ppa).y(p)=L\left(\sqrt{p}-\sqrt{p_a}\right).

Therefore,

y(p0)=2100(36002500)=2100(6050)=21000.y(p_0) = 2100\left(\sqrt{3600}-\sqrt{2500}\right) = 2100(60-50) = 21000.

Thus, to open the position, the liquidity provider must deposit

5 CTNand21000 USDC.5 \text{ CTN} \qquad\text{and}\qquad 21000 \text{ USDC}.

Value of a Position

We now compute the value of a CenturionDEX v3 position as a function of the current price. Let x(p)x(p) and y(p)y(p) denote the real balances of tokens XX and YY held by the position when the price is pp. The value of the position, expressed in units of token YY, is

V(p)=x(p)p+y(p).V(p)=x(p)\,p+y(p).

Once a position is created, the parameters LL, pap_a, and pbp_b are fixed. Using the canonical balance formulas, we obtain the following piecewise expression for the position value.

If ppap \le p_a, then the position is entirely held in token XX, so

V(p)=L(1pa1pb)p.V(p) = L\left(\frac{1}{\sqrt{p_a}}-\frac{1}{\sqrt{p_b}}\right)p.

If pappbp_a \le p \le p_b, then the position holds both tokens, and therefore

V(p)=[L(1p1pb)]p+L(ppa).V(p) = \left[ L\left(\frac{1}{\sqrt{p}}-\frac{1}{\sqrt{p_b}}\right) \right]p + L\left(\sqrt{p}-\sqrt{p_a}\right).

After simplification, this becomes

V(p)=L(2pppbpa).V(p) = L\left( 2\sqrt{p} - \frac{p}{\sqrt{p_b}} - \sqrt{p_a} \right).

Finally, if ppbp \ge p_b, then the position is entirely held in token YY, and hence

V(p)=L(pbpa).V(p) = L\left(\sqrt{p_b}-\sqrt{p_a}\right).

These formulas describe the value of the position throughout the entire price range and are the basis for the plot shown below.

Worked example

Consider the position constructed in the example above. Its parameters are

L=2100,[pa,pb]=[2500,4900],L=2100, \qquad [p_a,p_b]=[2500,4900],

and it was opened at price

p0=3600p_0=3600

with deposits of 55 CTN and 2100021000 USDC. Therefore, the initial value of the position is

V(3600)=53600+21000=39000 USDC.V(3600)=5\cdot 3600 + 21000 = 39000 \text{ USDC}.

Now suppose that the price rises to

p1=4225.p_1=4225.

Since 2500<4225<49002500 < 4225 < 4900, the position remains inside its active interval, so both balances must be recomputed using the in-range formulas. The CTN balance becomes

x(4225)=2100(1422514900)=2100(165170)=30132.308.x(4225) = 2100\left(\frac{1}{\sqrt{4225}}-\frac{1}{\sqrt{4900}}\right) = 2100\left(\frac{1}{65}-\frac{1}{70}\right) = \frac{30}{13} \approx 2.308.

The USDC balance becomes

y(4225)=2100(42252500)=2100(6550)=31500.y(4225) = 2100\left(\sqrt{4225}-\sqrt{2500}\right) = 2100(65-50) = 31500.

Hence, the value of the position at the new price is

V(4225)=x(4225)4225+y(4225)2.3084225+3150041250 USDC.V(4225) = x(4225)\cdot 4225 + y(4225) \approx 2.308\cdot 4225 + 31500 \approx 41250 \text{ USDC}.

Now compare this with the value the liquidity provider would have had if they had simply held the original assets outside the pool. In that case, the portfolio would still consist of 55 CTN and 2100021000 USDC, whose value at price 42254225 would be

54225+21000=42125 USDC.5\cdot 4225 + 21000 = 42125 \text{ USDC}.

Since

42125>41250,42125 > 41250,

the liquidity provider is worse off than under simple holding. The difference is

4212541250=875 USDC,42125 - 41250 = 875 \text{ USDC},

which is the impermanent loss associated with this price move.

We will analyze impermanent loss in CenturionDEX v3 in more detail in the next section.