EDIT: Full code available here.

The following idea is motivated by the paper “The Yield Protocol: On-Chain Lending With Interest Rate Discovery” which uses the Ethereum model.

I think the same thing can be implemented in Ergo in a possibly easier way.

The following uses the idea of “zero-coupon bonds”, which are essentially tokens issued by a company at a discounted value (at 0.9 USD per token, for example) and redeemable at 1:1 rate after some future date.

While in the real world, such bonds are backed by the trust of the company that issues them, in Ergo, we can remove the trust and make the bonds backed by collateral in Ergs. The benefit for the issuer is the potential to profit if the price of Ergs goes up, and the benefit of the buyer is the potential to benefit if the price of ergs goes down, along with the trustless nature because of collateral that can be instantly liquidated in a margin call.

While the tokens are themselves exchangeable using only the contract that issued them, we can have another contract that functions as a decentralized exchange (DEX) on top that allows them to be fully fungible. This is a topic for another post.

The following describes a bond for a singler user, Alice.

The bond is represented using two boxes. The first is a “bond box”, which stores the tokens used as the bond, along with the collateral in Ergs. The second is a “liquidated box”, that stores the collateral in Ergs after the bond box is liquidated. A box is liquidated either when the bond expires or in the event of a margin call. Alice can prevent a margin call by topping up the box with Ergs. This is one place where “trust” comes into the picture; if people trust Alice to top up the box, then her tokens can potentially be valued higher. However, since we can instantly liquidate the box and then purchase new tokens (at a possibly discounted rate) this is not a major issue.

Since the bond box references the liquidated box, we describe the latter first.

{ // liquidatedBoxSource val bondOwner = proveDlog(alice) val fixedRate = SELF.R4[Long].get // nanoErgs per usdCent at time of liquidation val maxRedeemTime = SELF.R5[Int].get val tokenID = SELF.tokens(0)._1 // tokenID that maps to bonds val tokenNum = SELF.tokens(0)._2 // how many bond tokens left val newBox = OUTPUTS(0) val newBoxTokenID = newBox.tokens(0)._1 val newBoxTokenNum = newBox.tokens(0)._2 // how many bond tokens left val bondDiff = newBoxTokenNum - tokenNum val ergsDiff = SELF.value - newBox.value val validNewBox = newBox.propositionBytes == SELF.propositionBytes && newBoxTokenID == tokenID && bondDiff >= 10000 && // at least 100 USD difference (prevent micro tx) ergsDiff <= bondDiff * fixedRate && newBox.R4[Long].get == fixedRate && newBox.R5[Int].get == maxRedeemTime (bondOwner && (HEIGHT > maxRedeemTime) ) || validNewBox }

The bond box is defined using the script:

{ // bondBoxSource val numBonds = SELF.R4[Long].get // how many bonds issued (one bond = 1 USD cent) val tokenID = SELF.tokens(0)._1 // tokenID that maps to bonds val tokenNum = SELF.tokens(0)._2 // how many bond tokens left val newBox = OUTPUTS(0) val newBoxTokenID = newBox.tokens(0)._1 val newBoxTokenNum = newBox.tokens(0)._2 // how many bond tokens left val validNewBoxToken = tokenID == newBoxTokenID val rateBox = CONTEXT.dataInputs(0) val rate = rateBox.R4[Long].get // nanoErgs per usdCent val validRateBox = rateBox.tokens(0)._1 == rateTokenID val lockedErgs = SELF.value // nanoErgs val neededErgs = numBonds * rate val insufficientErgs = lockedErgs * 10 >= neededErgs * 11 // at least 10 percent margin if (HEIGHT > endHeight || insufficientErgs) { // bond ended or margin call blake2b256(newBox.propositionBytes) == liquidatedBoxScriptHash && validNewBoxToken && newBoxTokenNum == tokenNum && newBox.R4[Long].get == rate && newBox.R5[Int].get >= HEIGHT + withdrawDeadline } else { // purchase bonds val numTokensReduced = tokenNum - newBoxTokenNum val numNewBonds = newBox.R4[Long].get val numBondsIncreased = numNewBonds - numBonds val ergsIncreased = newBox.value - SELF.value val validErgsIncrease = ergsIncreased >= numBondsIncreased * rate newBox.propositionBytes == SELF.propositionBytes && numBondsIncreased >= minBondsToPurchase && numBondsIncreased == numTokensReduced && validErgsIncrease && numNewBonds <= maxBonds } }

The environment variables used above are: