Skip to content

Math

All arithmetic is integer, checked, and rounded in the pool’s favour. The same rules run on chain (programs/pina_amm/src/math.rs) and are covered by property tests that try thousands of random reserves, amounts, and fee rates on every run.

Notation

Symbol Meaning
x Reserve of the token the trader sells
y Reserve of the token the trader buys
t Trade fee rate, parts per million
p Protocol share of the trade fee, parts per million
c Creator fee rate, parts per million
D 1_000_000

Reserves exclude accrued fees: see Architecture.

Rounding rules

Quantity Direction Why
Any fee up The pool never undercharges
Amount a trader receives down The pool never overpays
Amount a trader pays up The pool never undercharges
Protocol and creator shares of a fee down The remainder stays with LPs
Tokens added for a deposit up Depositors cannot mint LP cheaply
Tokens returned for a withdrawal down Withdrawers cannot drain dust

After every swap the program also checks that x * y did not decrease. Rounding already guarantees it; the check turns any future arithmetic mistake into a failed transaction instead of a drained pool.

Exact-input swaps

The trader sells exactly a.

Creator fee taken from the input (mode Input, or the mode names the input token):

fee = ceil(a * (t + c) / D)
creator = floor(fee * c / (t + c))
trade = fee - creator
out = floor((a - fee) * y / (x + a - fee))

Creator fee taken from the output (the mode names the output token):

trade = ceil(a * t / D)
gross = floor((a - trade) * y / (x + a - trade))
creator = ceil(gross * c / D)
out = gross - creator

In both cases protocol = floor(trade * p / D) and the LPs keep trade - protocol.

Worked example

x = 10,000,000, y = 20,000,000, t = 2,500 (0.25%), c = 500 (0.05%), p = 200,000 (20%), selling a = 1,000,000 with the creator fee on the input:

fee = ceil(1,000,000 * 3,000 / 1,000,000) = 3,000
creator = floor(3,000 * 500 / 3,000) = 500
trade = 3,000 - 500 = 2,500
protocol = floor(2,500 * 200,000 / 1,000,000) = 500
out = floor(997,000 * 20,000,000 / 10,997,000) = 1,813,221

After the swap the token-0 vault holds 1,000,000 more tokens, of which 500 are protocol fees and 500 are creator fees awaiting collection; the reserve grows by 999,000.

Exact-output swaps

The trader buys exactly b. Each step inverts the exact-input formula and rounds up:

Creator fee from the input:

net = ceil(x * b / (y - b))
a = ceil(net * D / (D - (t + c)))
fee = a - net
creator = floor(fee * c / (t + c))
trade = fee - creator

Creator fee from the output:

gross = ceil(b * D / (D - c))
creator = gross - b
net = ceil(x * gross / (y - gross))
a = ceil(net * D / (D - t))
trade = a - net

Buying back the output of an exact-input trade never costs less than that trade paid; the property tests check this for random inputs.

Fee splits by creator fee mode

Mode Selling token 0 Selling token 1
0 Input creator fee in token 0 creator fee in token 1
1 Token 0 creator fee in token 0 (from input) creator fee in token 0 (from output)
2 Token 1 creator fee in token 1 (from output) creator fee in token 1 (from input)

The trade fee, and therefore the protocol fee, is always taken from the input token.

Liquidity

First deposit

lp_supply = floor(sqrt(amount_0 * amount_1))
minted = lp_supply - 1,000

lp_supply must be greater than 1,000. The integer square root uses a shift-and-subtract method that is exact for every u128 and avoids a software division per iteration on SBF.

Deposit

Minting lp shares against supply S costs:

amount_0 = ceil(reserve_0 * lp / S)
amount_1 = ceil(reserve_1 * lp / S)

Withdraw

Burning lp shares returns:

amount_0 = floor(reserve_0 * lp / S)
amount_1 = floor(reserve_1 * lp / S)

lp may not exceed S - 1,000, so the locked minimum always stays in the pool, and at least one of the two amounts must be positive.

Limits

Limit Value
Trade fee + creator fee at most 100,000 ppm (10%)
Protocol share at most 1,000,000 ppm (100% of the trade fee)
Locked LP 1,000 units
Amounts u64; intermediate products use u128

Any result that does not fit its type fails with MathOverflow instead of wrapping.