OTC Trading (ERC1155)

Native ERC1155 fCash transfers are a powerful tool that can be used to trade fCash OTC (over the counter) without impacting fCash markets.

The vast majority of fCash lending and borrowing will be done via Notional's fCash markets (on-chain AMM) but Notional also exposes ERC1155 transfers for fCash as a powerful tool to trade fCash OTC (over the counter). OTC trading can be used when fCash is idiosyncratic (i.e. temporarily illiquid due to no matching market maturity).

ERC1155 IDs

The ERC1155 standard is a "multi-token" standard that allows a single interface to transfer multiple tokens that are fungible inside a single contract. fCash is natively implemented as ERC1155 tokens inside the main Notional v2 Proxy contract. Each fCash token is uniquely identified by an ERC1155 id that is constructed as follows:

/**
 * 256 bits encoded as:
 * | 0 (192 bits) | 16 bits (currency id) | 40 bits (maturity) | 8 bits (asset type) | 
 *
 * assetType is as follows:
 *  1 = fCash
 *  2 to 8 = Liquidity Token for Market Index 1-7 
 */
function encodeAssetId(
    uint256 currencyId,
    uint256 maturity,
    uint256 assetType
) internal pure returns (uint256) {
    require(currencyId <= Constants.MAX_CURRENCIES);
    require(maturity <= type(uint40).max);
    require(assetType <= Constants.MAX_LIQUIDITY_TOKEN_INDEX);

    return
        uint256(
            (bytes32(uint256(uint16(currencyId))) << 48) |
                (bytes32(uint256(uint40(maturity))) << 8) |
                bytes32(uint256(uint8(assetType)))
        );
}

function decodeAssetId(uint256 id) internal pure returns (
    uint256 currencyId,
    uint256 maturity,
    uint256 assetType
) {
    assetType = uint8(id);
    maturity = uint40(id >> 8);
    currencyId = uint16(id >> 48);
}

balanceOf vs signedBalanceOf

The native ERC1155 standard returns a uint256 value in balanceOf which is insufficient to express negative fCash balances. Notional's ERC1155 implementation will return 0 if an account has a negative fCash balance using balanceOf and will return the proper balance as an int256 when the non-ERC1155 standard signedBalanceOf is called.

Transfers

fCash can be transferred between two accounts using corresponding ids just like any other token. However, since fCash can also be negative there are some interesting properties that can be unlocked via transfers. If the from account does not have sufficient fCash to transfer to to they will incur a debt that must be collateralized on Notional. This unique property can be used to create potential OTC trades.

Example: Lending and Borrowing OTC at Idiosyncratic Maturity

In this example, the from account would like to sell 9 month (idiosyncratic) fCash to a market maker (to). The market maker will receive 9 month fCash and borrow at the liquid 12 month fCash AMM market, charge a spread, and transfer the remaining borrowed amount to the from account. The market maker's 12 month borrow is now collateralized by the 9 month fCash.

BatchBalanceWithTrades[] memory action = new BatchLend(1);

action = new BalanceActionWithTrades[](1);
action[0].actionType = DepositActionType.None;
action[0].currencyId = currencyId;
action[0].withdrawEntireCashBalance = true;
action[0].redeemToUnderlying = toUnderlying;
action[0].trades = new bytes32[](1);
action[0].trades[0] = encodeBorrowTrade(
  3, // 1 year market
  100e8, // borrow 100 fCash
  0.05e9 // revert if borrowed rate is above 5% annualized
);

// Encode the batch action as calldata, Notional will execute this trade
// after exectuing a transfer between accounts.
bytes memory callData = abi.encodeWithSelector(
  NotionalProxy.batchBalanceAndTradeAction.selector,
  marketMakerAddress,
  action
);

// Some third party operator contract must execute this transaction and have
// approval from both parties because te marketMakerAddress is executing
// a borrow transaction. This third party operator must also implement
// the ERC20 token transfer of borrowed assets from marketMakerAddress to 
// fromAddress.
Notional.setApprovalForAll(fromAddress, operator);
Notional.setApprovalForAll(marketMakerAddress, operator);

// In this call, Notional will do the following:
// - transfer fCash fromAddress to marketMakerAddress
// - execute the borrowing on behalf of market maker encoded in callData
Notional.safeTransferFrom(
  fromAddress, // Transfer fCash from this address
  marketMakerAddress, // Market maker will borrow
  encodeAssetId(currencyId, maturity, 1), // fCash ID
  100e8, // Send the 100e8 fDAI being purchased above
  callData // Execute the encoded trade on behalf of marketMakerAddress
);

Example: Bi-Directional fCash Swap

Two accounts may also agree to trade fCash between each other using a single safeBatchTransferFrom. In this example, the from account will send 1 year fCash and receive 9 month fCash from the to account. This requires using a negative fCash balance in an element of the amounts array.

// Some third party operator must have approval to execute ERC1155 exchanges
// in this case because the `to` account will be losing fCash in one of the
// ids.
Notional.setApprovalForAll(fromAddress, operator);
Notional.setApprovalForAll(toAddress, operator);

uint256[] memory amounts = new uint256[](2);
amounts[0] = 100e8 // Send 9 month fCash amount
amounts[1] = uint256(int256(-97.5e8)) // Receive 12 month fCash amount   

uint256[] memory ids = new uint256[](2);
ids[0] = encodeAssetId(currencyId, nineMonthMaturity, 1);
ids[1] = encodeAssetId(currencyId, twelveMonthMaturity, 1);

Notional.safeBatchTransferFrom(
  fromAddress, // Send 9 month fCash, receive 12 month fCash
  toAddress, // Receive 9 month fCash, send 12 month fCash
  ids, 
  amounts,
  ""
);

Last updated