Minting and Redeeming Junior Tranche tokens (ERC-20)

Please refer to the SmartYield.sol repo for code references made on this page.


SMART Yield relies on minting and burning ERC-20 tokens to keep track of users' claims on the junior tranche of a given originator.

Upon depositing, the user receives a commensurate number of fungible tokens that reflects the exchange value between one such token and the underlying stablecoin.

event BuyTokens(address indexed buyer, uint256 underlyingIn, uint256 tokensOut, uint256 fee);

SMART Yield junior tranche tokens all have a common format indicating that they are a BarnBridge product built on top of an existing platform (e.g., bb_cUSDC for USDC deposits into Compound Finance).

Upon redemption, the user burns the ERC-20 tokens in exchange for a proportional subset of the junior tranche, minus a possible redemption fee (i.e., the "forfeits" variable below) depending on what the weighted average maturity date of the senior tranche is.

event SellTokens(address indexed seller, uint256 tokensIn, uint256 underlyingOut, uint256 forfeits);

How to Mint jTokens

Deposits into the junior tranches of all SMART Yield junior tranches assess a nominal fee on the principal.

uint256 fee = MathUtils.fractionOf(underlyingAmount_, IController(controller).FEE_BUY_JUNIOR_TOKEN());

The junior tranche deposit fee is currently set to 0.5%, or 50 basis points, of the principal. This fee is then directed to the address set as "feesOwner" in IController.sol. In practice, this is the DAO's treasury address, which also allows for governance votes to adjust the fee size.

With the fee assessed, the appropriate number of jTokens for their deposit sum can be determined.

uint256 getsTokens = (underlyingAmount_.sub(fee)).mul(EXP_SCALE).div(price());

The primary price of a given jToken is determined by the price() function.

function price()
    public override
returns (uint256)
  uint256 ts = totalSupply();
  // (ts == 0) ? EXP_SCALE : (underlyingJuniors() * EXP_SCALE) / ts
  return (ts == 0) ? EXP_SCALE : underlyingJuniors().mul(EXP_SCALE).div(ts);

Assuming that checks are passed (i.e., the contract is not paused and the getsTokens variable is within range), the user is then able to exchange their stablecoins for jTokens. (See: IProvider.sol)

address buyer = msg.sender;

IProvider(pool)._takeUnderlying(buyer, underlyingAmount_);
IProvider(pool)._depositProvider(underlyingAmount_, fee);
_mint(buyer, getsTokens);

emit BuyTokens(buyer, underlyingAmount_, getsTokens, fee);

The forfeits Variable

The junior tranche of any given SMART Yield pool is earning yield in part to cover the fixed yields of the senior tranche. In order to ensure that there remains enough capital in the system to cover what is owed to senior tranche tokens, the forfeits variable keeps track of the obligation for a given subset of the junior tranche.

forfeits is calculated within the sellTokens function upon a user's attempt to redeem their jTokens.

First, the debtShare variable is calculated to determine what percentage of the outstanding junior tranche token supply a potential sale amount represents.

uint256 debtShare = tokenAmount_.mul(EXP_SCALE).div(totalSupply());

With the user's debt share determined, SMART Yield is then able to assess the proportionally correct value for forfeits which then gets subtracted from the value of the user's jTokens within the toPay variable.

uint256 forfeits = abondDebt().mul(debtShare).div(EXP_SCALE);
uint256 toPay = tokenAmount_.mul(price()).div(EXP_SCALE).sub(forfeits);

The abond variable contains the necessary rate of payment by the junior tranche to the senior tranche at that given point in time; abondDebt() calls the difference between what yield is owed to outstanding senior tranche bonds and how much yield has been generated thus far.

Redeeming jTokens: Instant vs. Two-Step

Users are able to redeem their jTokens in one of two ways: either immediately, and be obligated to pay their respective forfeits sum, or via a slower two-step redemption process.

Instant Redemption

Single-step redemption allows the user to claim their principal and earned yield immediately, but potentially at the expense of forfeiting a portion necessary for covering senior tranche obligations. (See: IProvider.sol)

address seller = msg.sender;

_burn(seller, tokenAmount_);
IProvider(pool)._withdrawProvider(toPay, 0);
IProvider(pool)._sendUnderlying(seller, toPay);

emit SellTokens(seller, tokenAmount_, toPay, forfeits);

There is no redemption fee charged on the junior tranche.

Two-Step Redemption

Two-step redemption allows the user to avoid forfeiting capital, but requires them to wait until the weighted average maturity date for the senior tranche at the time of initiating the process. This process involves the exchange of jTokens for a junior tranche ERC-721 bond, akin to the senior tranche token process.

When the user elects to redeem via this method, their maturity date is determined as the day following the aforementioned senior tranche, or ABOND, date.

uint256 maturesAt = abond.maturesAt.div(EXP_SCALE).add(1);

Upon maturity of the jToken bond, the user is then able to redeem the original sum of jTokens they had by burning the associated ERC-721.

address payTo = IBond(juniorBond).ownerOf(jBondId_);

uint256 payAmnt = jBondsAt.price.mul(jb.tokens).div(EXP_SCALE);

IProvider(pool)._withdrawProvider(payAmnt, 0);
IProvider(pool)._sendUnderlying(payTo, payAmnt);
underlyingLiquidatedJuniors = underlyingLiquidatedJuniors.sub(payAmnt);

emit RedeemJuniorBond(payTo, jBondId_, payAmnt);

Last updated