Notional Whitepaper

Abstract

Fixed rate, fixed term lending is by far the most common type of lending in traditional financial markets. In 2018, there was $15.3 trillion dollars of debt outstanding in U.S. corporate debt and mortgage debt markets[1]. 88% of that debt was in terms of fixed rates; fixed rates are simply more desireable for consumers of the financial system (i.e. corporations and households) who do not want exposure to interest rate volatility. In this paper we describe Notional, an on-chain Ethereum protocol, that enables users to lend and borrow at fixed rates at predefined maturities. It is inspired by other successful Ethereum protocols such as Uniswap, Compound, and MakerDAO.

Introduction

DeFi (Decentralized Finance) is an exciting and rapidly growing ecosystem of new financial products that live on the Ethereum blockchain. In 2019, the amount of funds locked up in DeFi products grew from $274M USD to $674M USD[4] and was continuing to grow into 2020.

The benefits of DeFi are clear: users are able to seamlessly lend, borrow and exchange tokens within a financial system that is secure, private, transparent, and globally accessible. Those without access to the traditional financial system are able to participate in the DeFi ecosystem with nothing but an internet connection and tokens; no financial intermediaries exist to create barriers to entry. Since DeFi products are nothing more than autonomous computer programs, they enable an ecosystem of programmable money where different protocols integrate in order to create entirely new categories of products and businesses.

A core, missing component of this ecosystem is fixed rate, fixed term financing. Notional proposes to fill this gap. Notional is heavily inspired by the constant-product market maker used by Uniswap as well as Compound’s collaterlization mechanism. Our contributions include: the concept of fCash, periodic maturities, the portfolio, and cash settlement which we describe in the following sections.

Finally, Notional is a subset of a larger, more ambitious protocol called Notional V2. Notional V2 will enable users to hedge their exposure to any rate in DeFi, including hash rates, gas prices, token exchange rates as well as interest rates. It does all of this in a capital efficient manner in order to maximize liquidity which generates network effects. We discuss Notional V2 briefly here but will leave a full description for a separate paper.

The Notional Protocol

fCash

fCash is a tokenized representation of a fCash flow. It represents the amount of tokens (in this case Dai) that an account is either entitled to receive (CASH_RECEIVER) or obligated to pay (CASH_PAYER) at its designated maturity. For example, if an account holds +100 fCash tokens for a maturity at block 100, it is entitled to 100 Dai at any block greater than or equal to block 100. Similarly, -100 fCash tokens for the same maturity means that the account is obligated to pay 100 Dai at block 100. A detailed description of lending and borrowing mechanics will follow.

fCash in one maturity (i.e. due to mature at block 100) is fungible with other fCash tokens with the same maturity. However it is not directly fungible with fCash with different maturities. Also note that the entitlement to receive fCash (CASH_RECEIVER) is freely transferrable but the obligation to pay (CASH_PAYER) is not.

Finally, fCash tokens are not strictly ERC20 tokens because fCash tokens at different maturities are not fungible with each other. fCash tokens can be represented under the ERC1155 token standard which allows for interoperability.

Current Cash

Notional uses an internal accounting concept called current cash to represent matured cash flows. Since cash flows may be positive (CASH_RECEIVER) or negative (CASH_PAYER), current cash is represented as a signed integer (i.e. positive or negative). Current cash is different from collateral balances in this way (collateral balances can only be positive).

When a fCash token matures, its positive or negative value is added to the account’s total current cash balance. The current cash balance is used when we settle balances to collateral. This is discussed in the settlement section.

Maturities

Notional introduces the notion of periodic, rolling maturities to simplify trading and pool liquidity. These maturities are defined by two governance parameters: G_NUM_PERIODS and G_PERIOD_SIZE. G_PERIOD_SIZE defines how long each periodic maturity will last in blocks. G_NUM_PERIODS defines how many of these maturities will be traded in the future. For example, if G_PERIOD_SIZE = 1000 and G_NUM_PERIODS = 4 and we are starting at block 0, there will be maturities at blocks 1000, 2000, 3000, 4000. Each one of these maturities will have a pool of fCash tokens that can be bought and sold.

We have chosen to parameterize maturities with blocks instead of timestamps because this enables blockchain-native behaviors that timestamps do not. The reasoning is not immediately apparent here but this is an important choice for the full Notional V2 system. Block times for the Ethereum blockchain are non-deterministic but are designed to have a consistent average of around 13 seconds. This allows us to translate a number of blocks to an approximate time.

Markets

Each maturity defined above creates a market that functions very similarly to a Uniswap exchange. Each market is defined by the following variables and functions:

Variables

Trading

Trading fCash tokens for collateral or vice versa is done via the constant-product market maker formula used in Uniswap. Each trade is subject to a fee that goes to reward liquidity providers.

Lending (Collateral for fCash)

The takefCash function allows users to deposit collateral (i.e. Dai) to the market in exchange for the right to receive a cash flow at maturity. This is the equivalent of lending; the user is depositing collateral for the right to receive a (hopefully larger) cash flow at maturity. From an economic standpoint, the user would simply not deposit collateral unless they were entitled to a greater amount of fCash at maturity; no one would trade 100 Dai today for 95 Dai in the future. If the exchange rate were to fall to that level, arbitrageurs could easily obtain risk-free profits by moving it back into line with the market’s expectation for interest rates over the given period.

def takefCash(maturity, daiToDeposit):
    # 0 < G_LIQUIDITY_FEE < 1
    fee = daiToDeposit * G_LIQUIDITY_FEE
    fCashToReceive = maturity.totalfCash - (
        (maturity.totalfCash * maturity.totalCollateral) / 
            (maturity.totalCollateral - fee + daiToDeposit)
    )

    # Market variables are updated.
    maturity.totalfCash = maturity.totalfCash - fCashToReceive
    maturity.totalCollateral = maturity.totalCollateral + daiToDeposit

Borrowing (fCash for Collateral)

The takeDai function allows users to deposit a fCash obligation in order to receive collateral. This is the equivalent of borrowing; the user is committing to a future obligation in exchange for an amount of Dai that they can withdraw from the contract and spend as they wish. This obligation will be collateralized by ETH, CASH_RECEIVER or LIQUIDITY_TOKEN. See the Portfolio section for more details on this.

def takeDai(maturity, fCashObligation):
    # 0 < G_LIQUIDITY_FEE < 1
    fee = fCashObligation * G_LIQUIDITY_FEE
    daiToReceive = totalCollateral - (
        (totalfCash * totalCollateral) /
            (totalfCash - fee + fCashObligation)
    )

    # Market variables are updated.
    totalCollateral = totalCollateral - daiToReceive
    totalfCash = totalfCash + fCashObligation

Fixed Rates

Note that the amount of Dai and fCash exchanged in the two trades described above (takeDai and takefCash) are calculated when the trade is made. This means, in effect, the interest rate for the user is fixed from that point on. A lender will not have to deposit any more Dai for the cash flow that they are promised; the borrower will not have to pay more fCash for the Dai they received.

For example, a lender deposits 100 Dai in exchange for 105 fCash tokens that mature in 1 year. Once this trade is made, the lender has entered into a fixed rate loan at a 5% annualized rate for a term of 1 year. From this point on, none of these terms will change.

This does not mean that the next lender to trade will also receive the same 5% annualized rate. Since the amount of totalCollateral and totalfCash have changed after the previous trade, the next lender will receive a different fixed rate.

This is how Notional provides fixed rates at fixed maturities.

Providing Liquidity

Borrowers and lenders change the exchange rate by taking from one side of the market and depositing on the other. Without some amount of liquidity on both sides of the market these interactions would not be possible. This is where the concept of liquidity tokens plays a role. Again, this is heavily inspired by the Uniswap liquidity token model.

Liquidity providers can provide liquidity to a market at a specfied maturity by calling the addLiquidity and removeLiquidity functions. When a liquidity provider adds liquidity, they deposit Dai and fCash at the prevailing exchange rate to both sides of the market. The provider submits the amount of fCash to add and the corresponding amount of Dai is calculated using the current exchange rate. Liquidity tokens are minted to account for the provider’s contribution. Since the liquidity provider is depositing fCash, we create a CASH_PAYER token to represent the obligation a liquidity provider has to provide that fCash when the market matures. The net present value of the LIQUIDITY_TOKEN will partially offset this obligation.

# Implied Market Interest Rate = 5%
maturity.totalfCash = 1050
maturity.totalCollateral = 1000
maturity.totalLiquidity = 1050

def addLiquidity(maturity, fCashToAdd, maxDaiToAdd):
    # Both of these are in proportion to the fCash market.
    tokensToMint = maturity.totalLiquidity * fCashToAdd / maturity.totalfCash
    daiRequired = maturity.totalCollateral * fCashToAdd / maturity.totalfCash

    # Ensures that the provider can bail out if the rate has moved against them
    assert(daiRequired <= maxDaiToAdd)

    # Update the markets
    maturity.totalfCash += fCashToAdd
    maturity.totalCollateral += daiRequired
    maturity.totalLiquidity += tokensToMint

    # Remove the Dai balance as well as account for a fCash obligation
    daiBalances[msg.sender] -= daiRequired
    accountPortfolio[msg.sender].push(Asset(CASH_PAYER, maturity, fCashToAdd))
    accountPortfolio[msg.sender].push(Asset(LIQUIDITY_TOKEN, maturity, tokensToMint))

When liquidity providers want to stop providing liquidity, they are able to remove their tokens and receive Dai and fCash in proportion to the market. Note that the CASH_RECEIVER here will offset the CASH_PAYER that was added to the liquidity provider’s portfolio when they deposited the tokens. The pseudocode is below:

def removeLiquidity(maturity, tokensToRemove):
    # Both of these are in proportion to the ownership stake in the liquidity pool
    daiOwed = maturity.totalCollateral * tokensToRemove / maturity.totalLiquidity
    fCashOwed = maturity.totalfCash * tokensToRemove / maturity.totalLiquidity

    # Update the markets
    maturity.totalfCash -= fCashOwed
    maturity.totalCollateral -= daiOwed
    maturity.totalLiquidity -= tokensToRemove

    # Credit the balances back to the liquidity provider
    daiBalances[msg.sender] += daiOwed
    accountPortfolio[msg.sender].push(Asset(CASH_RECEIVER, maturity, fCashOwed))
    accountPortfolio[msg.sender].push(Asset(LIQUIDITY_TOKEN, maturity, -tokensToRemove))

Liquidity providers are incentivized by a transaction fee (parameterized in G_LIQUIDITY_FEE) on every transaction. The advantage of this design is that liquidity providers can passively provide liquidity and earn transaction fees in the market.

A key consideration for liquidity providers is the understanding that they are providing liquidity in a single maturity. Once that maturity passes, their liquidity tokens will be converted to a Dai balance and a current cash balance. In order to provide liquidity in a new market, they will have to make a smart contract call to add liquidity to the new maturity.

User Accounts

Each user in Notional has an account represented by their Ethereum address.

Collateral Balances

The Notional protocol allows for two types of collateral to be deposited: Dai and ETH. Notional V2 will be designed to support multiple collateral types but for simplicity Notional is designed only to support the most common lending and borrowing collateral. There is nothing in the design of Notional that prevents additional collateral types from being supported; it was simply a decision to omit this feature from the initial release.

ETH is used to collateralize Dai loans and is subject to a collateralization ratio. We use the DAI/ETH Uniswap exchange to get the current exchange rate for ETH. The reasoning for this is that we will need to exchange ETH for Dai on Uniswap during liquidation and therefore the Uniswap ETH/DAI rate is an accurate reflection of this price.

Haircuts (Collateralization Ratio)

When calculating the value of an account’s holdings for the purposes of collateralization checks, Notional will apply a discount to every collateral asset (i.e. ETH and fCash) that reflects the riskiness of that asset. An asset’s riskiness is defined as the magnitude by which its value relative to Dai is likely to change. This discount is known as a haircut. Haircuts ensure that there is ample time for an account to be liquidated before a market move pushes it into insolvency.

Portfolio

In addition to collateral balances, each account has a portfolio which is an array of its CASH_PAYER, CASH_RECEIVER and LIQUIDITY_TOKEN balances. The reason for storing this in an array as opposed to mappings is so that the contract can iterate over a portfolio and calculate the net present value of all the assets that the account holds. We do this in order to calculate free collateral. It is important to note that each asset in the portfolio contains some amount of risk and therefore we apply a haircut to the value of each asset.

An additional benefit of the portfolio construction is that it allows for Notional to net out opposing CASH_PAYER and CASH_RECEIVER positions. If an account has an obligation of 100 fCash in 1 year (CASH_PAYER), but then buys the right to receive 50 fCash (CASH_RECEIVER) at the same maturity, its net position is now a CASH_PAYER of 50 fCash in 1 year. The portfolio construction ensures that there is always only one entry per maturity that represents the net position of the account no matter how many trades they have made in that maturity.

It may be odd to see in the following sections that we are valuing fixed rate fCash tokens against a market rate and then applying a haircut. It’s important to realize that when you lend and borrow in Notional and you do not trade again your rate will stay fixed. However, if you need to exit that position before maturity you will have to trade with the market at the prevailing rate. Therefore we value portfolios as if they had to be liquidated immediately; this accounts for the worst case value required to maintain the solvency of the protocol.

The formulas that define the present value for each one of these assets are as follows:

Cash Payer

The value of a fCash obligation is the cost to purchase an equal number of CASH_RECEIVER tokens at the same maturity such that the position is net zero. The formula is as follows:

G_HAIRCUT = 0.01

def getPayerValue(maturity, fCashAmount):
    daiCost = (
        (maturity.totalCollateral * market.totalfCash) /
            (maturity.totalfCash - fCashAmount))
        - maturity.totalCollateral
    ) / (1 - G_LIQUIDITY_FEE)

    # We add a buffer here to account for changes in the market rate
    daiCost = daiCost * (1 + G_HAIRCUT)

    if daiCost > fCashAmount:
        # However we cap this value at the total obligation
        return fCashAmount
    else:
        return daiCost

In the case of negative interest rates (when there is more totalCollateral than totalfCash in the market), the cost to purchase CASH_RECEIVER tokens will be greater than the actual obligation itself and therefore we cap the value of CASH_PAYER tokens at their face value (i.e. the value of 100 CASH_PAYER tokens will never be more than 100).

Cash Receiver

The value of a fCash entitlement is the discounted present value of the token amount at the current market rate. Note that this market rate is not the same as the fixed rate that the account is entitled to receive. The formula for this is:

G_HAIRCUT = 0.01

def getReceiverValue(maturity, fCashAmount):
    fee = fCashAmount * G_LIQUIDITY_FEE

    return market.totalCollateral - (
        (maturity.totalCollateral * market.totalfCash) /
            (maturity.totalfCash + fCashAmount - fee)
    ) * (1 - G_HAIRCUT)

Liquidity Token

Liquidity tokens are a claim to some amount of Dai as well as a positive fCash flow (CASH_RECEIVER). The value of a liquidity token is the present value of those claims.

def getTokenValue(maturity, tokenAmount):
    daiOwed = maturity.totalCollateral * tokenAmount / market.totalLiquidity
    fCashOwed = maturity.totalfCash * tokenAmount / market.totalLiquidity

    return daiOwed + getReceiverValue(maturity, fCashOwed)

Free Collateral

Free collateral represents the amount of excess collateral an account holds beyond what it needs to collateralize its obligations. It represents the buffer that the account has to withstand market volatility, the amount of collateral an account is allowed to withdraw from Notional, as well as the approximate maximum amount that the account is allowed to borrow. There are multiple sources of positive collateral: Dai, ETH, CASH_RECEIVER tokens and LIQUIDITY_TOKEN. CASH_PAYER tokens represent obligations and are the only source of negative collateral. Cash balances are discussed in settlement and they may be positive or negative.

The formula to calculate free collateral is as follows:

G_ETH_HAIRCUT = 0.30

def freeCollateral():
    portfolioValue = sum(getReceiverValue(...)) 
        + sum(getTokenValue(...))
        - sum(getPayerValue(...))

    return daiBalance
        + cashBalance
        + (ethBalance * daiEthExchangeRate * (1 - G_ETH_HAIRCUT))
        + portfolioValue

Cross Margin

Since LIQUIDITY_TOKEN and CASH_RECEIVER tokens have positive value, they can be used as collateral for borrowing in other maturities. The following portfolio below is completely valid and holds no current collateral balances. This is called cross margin.

Maturity Asset Value
Block 100 CASH_RECEIVER +1000
Block 200 LIQUIDITY_TOKEN +1000
Block 300 CASH_PAYER -1500
Free Collateral +500

Settlement

Computing resources are heavily constrained in the EVM so settling matured tokens requires a two stage process. First we settle matured assets to cash balances, then we give accounts the option of converting their cash balances to collateral if they want to withdraw Dai from Notional. Note that cashBalance is treated as one to one equivalent to Dai balances (as seen in free collateral) so it can be used to collateralize positions as if it were Dai. We can do this because the design of Notional assures that for every positive cash balance there is an offsetting negative cash balance somewhere in the system.

The reasoning for this added complexity is because O(n) operations must happen when a maturity block is reached (i.e. all accounts that participated in the maturity need to be updated). In other protocols like Uniswap or Compound, each transaction has O(1) operations - only the user’s balances and the liquidity pools need to be updated. Because O(n) operations within a single block are not possible, we delay the evaluation of these operations until the account wants to trade again or withdraw collateral.

The actions described in this section are unauthenticated, meaning any account can settle the portfolio of any other account. This is required so that unavailable accounts cannot prevent the rest of the system from settling their balances. All the actions described simply reconfigure the way a portfolio is represented in Notional, they do not change the net position of the portfolio.

Settling Matured Assets

First we settle all matured fCash and liquidity tokens to an account’s current cash balance. The formula for this is simple:

def settlePortfolio(portfolio):
    for asset in portfolio if maturity < blockHeight:
        if asset is CASH_PAYER:
            cashBalances -= asset.amount
        else if asset is CASH_RECEIVER:
            cashBalances += asset.amount
        else if asset is LIQUIDITY_TOKEN:
            cashBalances += asset.fCashOwed
            # Note that liquidity tokens will take all the remaining Dai
            # left in the market
            daiBalances += asset.daiOwed

Withdrawing Cash Balances

In a simplified scenario, there are only two accounts, a lender and a borrower. Their cash balances are below and the free collateral condition requires Barbara to have sufficient collateral to cover their obligations. Let’s assume the DAI/ETH exchange rate is 100 Dai/ETH.

Account Maturity Asset Value ETH Balance Dai Balance
Leonard Block 100 CASH_RECEIVER +1000 0 0
Barbara Block 100 CASH_PAYER -1000 15 0

At maturity, both portfolios are settled to cash balances. At this point, Barbara may elect to deposit 1000 Dai into her Dai balances in order to cover her obligation. However, in this scenario she does not.

Account Cash Balance ETH Balance Dai Balance
Leonard +1000 0 0
Barbara -1000 15 0

Leonard would like to withdraw his 1000 Dai. However, there is no Dai in the system - only a negative cash balance. Leonard can observe all of this off-chain. He sees that Barbara has a negative cash balance and enough ETH to cover it. He submits a transaction to Notional and specifies that he would like to settle his 1000 cash balance against Barbara. Notional sells 10 ETH on Uniswap to raise 1000 Dai and transfers it to Leonard’s account. It also erases the negative cash balance that Barabara has accrued. The system is now:

Account Cash Balance ETH Balance Dai Balance
Leanard 0 0 1000
Barbara 0 5 0

Leonard can now withdraw his 1000 Dai from Notional.

Liquidation

When an account’s free collateral drops below zero it can be liquidated. This can occur if the price of ETH/DAI drops or if market conditions change and an account is cross margining.

Raising Cash From Portfolio

The first step in liquidation is to sell off assets in the portfolio in order to generate Dai to de-risk the portfolio. Notional looks at sources of positive collateral and converts them to Dai starting with ETH and then CASH_RECEIVER and LIQUIDITY_TOKEN. It sells them on Uniswap or Notional markets until the free collateral shortfall is covered. Take the following portfolio:

Maturity Asset Market Value Post Haircut
Block 100 1200 CASH_RECEIVER +1000 +990
Block 200 1000 LIQUIDITY_TOKEN +1000 +995
Block 300 3500 CASH_PAYER -3000 -3035
ETH Balance ETH = 5; ETH/DAI = 220 +1100 +880
Dai Balance 0 0
Free Collateral -170

Let’s say this portfolio was collateralized when ETH/DAI was priced at 275, but it has rapidly dropped to 220. The portfolio is at risk of becoming insolvent, we must liquidate it. Notional proceeds as follows:

Liquidation Procedure Dai Raised Free Collateral
Sell 5 ETH 1100 -30
Trade 36 units of CASH_RECEIVER at Block 100 30 +55

Now the portfolio looks as follows:

Maturity Asset Market Value Post Haircut
Block 100 1164 CASH_RECEIVER +970 +965
Block 200 1000 LIQUIDITY_TOKEN +1000 +995
Block 300 3500 CASH_PAYER -3000 -3035
ETH Balance ETH = 0; ETH/DAI = 100 0 0
Dai Balance +1130 +1130
Free Collateral +55

De-Risking the Portfolio

This, however, does not completely solve the problem. If the market for CASH_PAYER tokens at block 300 moves against the portfolio, it will have to be liquidated again. In the second step of liquidation, Notional takes the Dai Balance raised and purchases offsetting CASH_RECEIVER tokens at Block 300 in order to offset the obligation the account has at that maturity. Let’s say that 1130 Dai purchases 700 CASH_RECEIVER tokens at Block 300. Now the portfolio looks as follows:

Maturity Asset Market Value Post Haircut
Block 100 1164 CASH_RECEIVER +970 +965
Block 200 1000 LIQUIDITY_TOKEN +1000 +995
Block 300 2800 CASH_PAYER -1870 -1900
ETH Balance ETH = 0; ETH/DAI = 100 0 0
Dai Balance 0 0
Free Collateral +60

Future Directions

We have described Notional, a decentralized, automated market making protocol for fixed rate, fixed term lending and borrowing on Ethereum. While we are very excited about this protocol and the benefits it will provide to the greater DeFi ecosystem, this is only the beginning for the Notional V2 protocol. Notional represents a subset of the greater functionality that we plan to deliver with the full Notional V2 protocol.

The full Notional V2 protocol will include many of the concepts we’ve introduced in this paper and extend them to enable users to receive fixed rates over fixed maturities against any data that is available on the Ethereum blockchain, including token exchange rates, hash rates, gas prices, as well as variable interest rates including Compound, dydx, and Dai Savings Rate.

The full vision of Notional V2 is a system that allows lenders and borrows to get fixed rate exposure, miners to fix their future mining revenues, crypto startups to hedge their future gas costs, and many new use cases that we have yet to discover.