Nako - How it works

Inside Nako we have a few mechanisms that we need to use to sync with the initial system. We need those because of the different states and restrictions Kona Protocol has.

Two Phase Rollover

We split the Rollover into two phases to then be able to split the rebuying phase into multiple steps.

So we implemented a 2 phase rollover that updates all the queues and buy/sells and loss/profit.

The second phase can be split in two to avoid the protocol to get stuck to set manual slippage in case of low liquidity. Meaning we can partially finish the second phase. And continue at a later point in time.

In the first phase we withdraw all the pool shares Nako had in each pool.

In first phase of the two phase rollover, we calculate the excess, which is the amount of losses or wins that we had, which is then saved in a mapping for the excess per cycle per pool, which we then later use in this second phase.

// Excess balance per pool by cycle
mapping(uint => mapping(address => int)) public excessPerPool; 

Additionally, with the excess as well as the amount of actual converted USDT, we can calculate the amount of USDT that a user can get per BRZ. (Important for the calculation of the money that is claimable by the borrower)

In the second phase, we then rebuy by or sell the needed amounts to account for wins or losses. And also finalize the queues. Meaning we deposit or withdraw the needed amount.

The second phase functions are only usable by a balancer role to avoid frontrunning or sandwich attacks. (Which would drain money out of the protocol)

We also have two different types of the second phase.

One that buys or sells the whole amount at once and the other one that basically only buys or sells the amount that we want. (It still needs to sell everything)

Like this we can avoid having liquidity issues and having high slippage or the price not having the opportunity to recover after it depegs from the original BRL (because of our marketorder).

Example

If for example we have 1 Million tokens that we need to sell but we only have 500 thousand in liquidity in a 5% pricerange, we would need to split our buys for the price to recover to the original pricerange to not have the protocol and therefore the users, overpay.
In this case we would need to call the function multiple times and wait for the price on the pool to be close to the fiat price.

Liquidity deposits

The liquidity deposits and queues for liquidity are also based on the rollover.

We can't direct deposit eventho we are in JoinLeave, if nako isn't fully rolled over yet. At this stage, we actually still can queue for the next cycle. Meaning virtually for nako, the Join Leave period didn't yet start and at this point, the deposits should be queued for the next cycle.

  • Direct deposits are only possible if the Rollover has been done for the last cycle and we are still in the JoinLeave Period.
  • If we are in joinLeave and want to queue, we need the last cycle to not be rolled over.
  • If are in any other state we need the last cycle to be rolled over.
  • The liquidity queueing mechanism is the same as of the base protocol (click here)

Each Borrower has his positions isolated per pool, so potential loss or win is also isolated.

General accounting

Nako as a whole has a record of each pools ratio (brz and shares) which will then, in the first phase of the rollover, be used to determine wins and losses.

For each pool we have a mapping that shows us how many shares and how many brz we have in this pool.

We use this for accounting, so we know what the original ratio was and then we can account for the wins and losses that occurred in that cycle in that specific pool.

// Total shares and BRZ balance in pools by cycle and pool
mapping(uint => mapping(address => uint)) public totalSharesPool;
mapping(uint => mapping(address => uint)) public totalBRZPool;

Once the user borrows BRZ using their USDT as a collateral they create a pool-postion which accounts for the original USDT deposited the amount borrowed and the shares that it holds.

// Struct to represent a user's position in a pool
struct PoolPosition {
    uint usdcDeposited;     // Amount of USDC deposited
    uint brzBorrowed;       // Amount of BRZ borrowed
    uint shares;            // Number of shares held
}

// Mapping to store borrower positions in pools by cycle, user, and pool
//cycle => user => pool => position
mapping(uint => mapping(address => mapping(address => PoolPosition))) public borrowerPoolPosition;

getAmountClaimableInPool

This function is essential as it calculates the actual amount in USDT claimable by the borrower.

// Get the ratio of USDC per BRZ and the total USDC that have been won/lost
uint usdcRatio =(excessPerPool[cycle][pool] < 0 ? negativeToUint(excessPerPool[cycle][pool]) : uint(excessPerPool[cycle][pool]))* usdtConverted[cycle] * ACCURACY_BASE / brzConverted[cycle];