├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── Swap.MD ├── README.MD ├── Options.MD ├── src ├── main │ └── scala │ │ └── client │ │ ├── ContractClient.scala │ │ └── ScriptGenerator.scala └── test │ └── scala │ └── bonds │ ├── TestHelper.scala │ ├── BondGenerator.scala │ └── BondTestSuite.scala ├── contracts ├── options │ ├── CoveredCallContractTokenToken.ergo │ └── OpenCallTokenToken.ergo ├── BondContractERG.ergo ├── BondContractToken.ergo ├── EXP_BondContractERG.ergo ├── ex │ ├── ExOrderERG.ergo │ └── ExOrderToken.ergo ├── OpenOrderFixedHeightERG.ergo ├── OpenOfferFixedHeightERG.ergo ├── OpenOrderFixedHeightToken.ergo ├── OpenOrderOnCloseERG.ergo ├── OpenOfferFixedHeightToken.ergo └── OpenOrderOnCloseToken.ergo └── SigmaBonds.MD /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.8.0 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.1") 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /project/project/target/ 3 | /project/target/ 4 | /target/ -------------------------------------------------------------------------------- /Swap.MD: -------------------------------------------------------------------------------- 1 | # SigmaX 2 | 3 | SigmaFi offers P2P Exchange options to allow users to trade assets between one another. 4 | Sellers may sell any combination of assets and ERG in exchange for a specific token or ERG value. 5 | 6 | 7 | Idea by HQΣr :) 8 | 9 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Sigma Finance 2 | 3 | Sigma Finance is a set of decentralized financial contracts which leverage the eUTXO model to allow for easy to use 4 | P2P DeFi. All SigmaFi contracts are open-source and free to build upon. Developers are encouraged to view and learn 5 | from the contracts, to help build more advanced DeFi applications in the Ergo ecosystems. SigmaFi implements 6 | many different real-world financial instruments and agreements. 7 | 8 | ## SigmaFi Contracts 9 | - [SigmaBonds](./SigmaBonds.MD): P2P, extendable bond contracts. 10 | -------------------------------------------------------------------------------- /Options.MD: -------------------------------------------------------------------------------- 1 | # Options 2 | 3 | SigmaFi offers option contracts to allow users to buy and sell call and put options between any assets on the Ergo blockchain. 4 | SigmaFi options have few or no restrictions on expiry dates, strike prices, or underlying assets used. 5 | There are two ways for a user to buy an option: 6 | - Close an open option order to receive an option NFT 7 | - Buy an option NFT from an exchange contract 8 | 9 | In contrast, there exists only one way for a user to sell an option contract. Selling option contracts requires 10 | creating an open option order, which must then be closed by a buyer. 11 | 12 | It is suggested that UI's created for SigmaFi options make this distinction indistinguishable for users (which will help liquidity 13 | in such a p2p marketplace) by default. To aid in this matter, finding option information for a given option token 14 | is as simple as searching for the spent box whose id is equal to the option token's id. 15 | 16 | ## Design 17 | The general design of 18 | SigmaFi's option contracts is to reduce options down to their most basic form. Options are simply agreements 19 | which give the *buyer* the right but not the obligation to swap a set amount of assets at a given exchange rate before some expiry date. 20 | The definition of *Calls* and *Puts* is in reality determined by which assets are exchanged, meaning that the distinction between 21 | these two option types is blurred. SigmaFi's option contracts follow this philosophy, so in the end it is the UI or the option buyer 22 | which decides whether a given option is a *Call* or *Put*. 23 | 24 | -------------------------------------------------------------------------------- /src/main/scala/client/ContractClient.scala: -------------------------------------------------------------------------------- 1 | package ksingh.sigmabonds 2 | package client 3 | 4 | import org.ergoplatform.appkit.{Address, BlockchainContext, NetworkType, RestApiErgoClient} 5 | 6 | object ContractClient extends App { 7 | 8 | val networkType: NetworkType = NetworkType.MAINNET 9 | val nodePort: String = if (networkType == NetworkType.MAINNET) ":9053/" else ":9052/" 10 | 11 | val client = RestApiErgoClient.create( 12 | "http://213.239.193.208" + nodePort, 13 | networkType, 14 | "", 15 | RestApiErgoClient.getDefaultExplorerUrl(networkType)) 16 | 17 | val tokenIds: Seq[String] = Seq("03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04") // Add token id strings here 18 | 19 | def printAddresses(ctx: BlockchainContext, optTokenId: Option[String]) = { 20 | val bondContract = ScriptGenerator.mkBondContract(ctx, optTokenId) 21 | val onClose = ScriptGenerator.mkOrderContract(ctx, isFixed = false, optTokenId) 22 | val fixed = ScriptGenerator.mkOrderContract(ctx, isFixed = true, optTokenId) 23 | val fixedOffer = ScriptGenerator.mkOfferContract(ctx, isFixed = true, optTokenId) 24 | 25 | val tokenId = optTokenId.getOrElse("ERG") 26 | 27 | println(s"Printing addresses for contracts using token ${tokenId}") 28 | 29 | println(s"Bond Contract: ${bondContract.toAddress}") 30 | println(s"On-Close Order: ${onClose.toAddress}") 31 | println(s"Fixed Height Order: ${fixed.toAddress}") 32 | println(s"Fixed Height Offer: ${fixedOffer.toAddress}") 33 | println() 34 | } 35 | 36 | client.execute { 37 | ctx => 38 | println(s"Using network type: ${networkType}") 39 | 40 | printAddresses(ctx, None) 41 | 42 | tokenIds.foreach { 43 | id => 44 | printAddresses(ctx, Some(id)) 45 | } 46 | 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/options/CoveredCallContractTokenToken.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Covered Call Contract with underlying asset being a token, and priced in a token 3 | 4 | val sellerPK = SELF.R5[SigmaProp].get 5 | val strikePrice = SELF.R6[Long].get 6 | val expiryHeight = SELF.R7[Int].get 7 | val buyerPK = SELF.R8[SigmaProp].get 8 | val underlyingAssets = SELF.tokens 9 | val paymentBox = OUTPUTS(0) 10 | 11 | // Constants 12 | // _tokenId: Token id of payment currency 13 | 14 | 15 | val expired = { 16 | allOf( 17 | Coll( 18 | HEIGHT >= expiryHeight, 19 | paymentBox.propositionBytes == sellerPK.propBytes, 20 | paymentBox.tokens == underlyingAssets, 21 | paymentBox.R4[Coll[Byte]].get == SELF.id 22 | paymentBox.value == 1000000L, 23 | ) 24 | ) 25 | } 26 | // Option contract is executed by buyer 27 | val returnBox = OUTPUTS(1) 28 | val executed = { 29 | allOf( 30 | Coll( 31 | // RepaymentBox Conditions 32 | HEIGHT < expiryHeight, 33 | paymentBox.propositionBytes == sellerPK.propBytes, 34 | paymentBox.value == 1000000L, 35 | paymentBox.tokens(0)._1 == _tokenId, 36 | paymentBox.tokens(0)._2 == strikePrice, 37 | paymentBox.tokens.size == 1, 38 | paymentBox.R4[Coll[Byte]].get == SELF.id, 39 | // ReturnBox Conditions 40 | returnBox.propositionBytes == buyerPK.propBytes, 41 | returnBox.tokens == underlyingAssets, 42 | returnBox.value == 1000000L, 43 | ) 44 | ) 45 | } 46 | 47 | (sigmaProp(expired)) || (sigmaProp(executed)) 48 | 49 | } -------------------------------------------------------------------------------- /contracts/BondContractERG.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Bond Contract ERG 3 | // Borrowers's collateral is stored in a utxo under this contract. If borrower spends the box before the 4 | // maturity height, then they are repaying the loan with interest in ERG. If the lender spends the 5 | // box after the maturity height, then they may liquidate the bond and gain the collateral behind it. 6 | // R4: Box id of originating order: Coll[Byte] 7 | // R5: borrowerPK: SigmaProp 8 | // R6: Total Repayment (In nanoERGs): Long 9 | // R7: Bond Maturity Height: Int 10 | // R8: lenderPK: SigmaProp 11 | // Definitions in OpenOrder contract 12 | 13 | val borrowerPK = SELF.R5[SigmaProp].get 14 | val repayment = SELF.R6[Long].get 15 | val maturityHeight = SELF.R7[Int].get 16 | val lenderPK = SELF.R8[SigmaProp].get 17 | val collateralAssets = SELF.tokens 18 | val collateralERG = SELF.value 19 | val repaymentBox = OUTPUTS(0) 20 | 21 | // Constants 22 | // None 23 | 24 | if(HEIGHT >= maturityHeight){ 25 | val liquidated = { 26 | allOf( 27 | Coll( 28 | 29 | repaymentBox.propositionBytes == lenderPK.propBytes, 30 | repaymentBox.tokens == collateralAssets, 31 | repaymentBox.value == collateralERG, 32 | repaymentBox.R4[Coll[Byte]].get == SELF.id 33 | ) 34 | ) 35 | } 36 | sigmaProp(liquidated) 37 | }else{ 38 | // Collateral box returned to borrower 39 | val returnBox = OUTPUTS(1) 40 | val repaid = { 41 | allOf( 42 | Coll( 43 | // RepaymentBox Conditions 44 | repaymentBox.propositionBytes == lenderPK.propBytes, 45 | repaymentBox.value == repayment, 46 | repaymentBox.R4[Coll[Byte]].get == SELF.id, 47 | // ReturnBox Conditions 48 | returnBox.propositionBytes == borrowerPK.propBytes, 49 | returnBox.tokens == collateralAssets, 50 | returnBox.value == collateralERG 51 | ) 52 | ) 53 | } 54 | 55 | sigmaProp(repaid) && borrowerPK 56 | } 57 | } -------------------------------------------------------------------------------- /contracts/BondContractToken.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Bond Contract SIGUSD 3 | // Borrowers's collateral is stored in a utxo under this contract. If borrower spends the box before the 4 | // maturity height, then they are repaying the loan with interest in SIGUSD. If the lender spends the 5 | // box after the maturity height, then they may liquidate the bond and gain the collateral behind it. 6 | // R4: Box id of originating order: Coll[Byte] 7 | // R5: borrowerPK: SigmaProp 8 | // R6: Total Repayment (In number of raw / non-decimaled SIGUSD tokens): Long 9 | // R7: Bond Maturity Height: Int 10 | // R8: lenderPK: SigmaProp 11 | // Definitions in OpenOrder contract 12 | 13 | val borrowerPK = SELF.R5[SigmaProp].get 14 | val repayment = SELF.R6[Long].get 15 | val maturityHeight = SELF.R7[Int].get 16 | val lenderPK = SELF.R8[SigmaProp].get 17 | val collateralAssets = SELF.tokens 18 | val collateralERG = SELF.value 19 | val repaymentBox = OUTPUTS(0) 20 | 21 | // Constants 22 | // _tokenId: Token id of bond currency 23 | 24 | if(HEIGHT >= maturityHeight){ 25 | 26 | val liquidated = { 27 | allOf( 28 | Coll( 29 | repaymentBox.propositionBytes == lenderPK.propBytes, 30 | repaymentBox.tokens == collateralAssets, 31 | repaymentBox.value == collateralERG, 32 | repaymentBox.R4[Coll[Byte]].get == SELF.id 33 | ) 34 | ) 35 | } 36 | 37 | sigmaProp(liquidated) 38 | }else{ 39 | // Collateral box returned to borrower 40 | val returnBox = OUTPUTS(1) 41 | val repaid = { 42 | allOf( 43 | Coll( 44 | // RepaymentBox Conditions 45 | repaymentBox.propositionBytes == lenderPK.propBytes, 46 | repaymentBox.value == 1000000L, 47 | repaymentBox.tokens(0)._1 == _tokenId, 48 | repaymentBox.tokens(0)._2 == repayment, 49 | repaymentBox.tokens.size == 1, 50 | repaymentBox.R4[Coll[Byte]].get == SELF.id, 51 | // ReturnBox Conditions 52 | returnBox.propositionBytes == borrowerPK.propBytes, 53 | returnBox.tokens == collateralAssets, 54 | returnBox.value == collateralERG 55 | ) 56 | ) 57 | } 58 | 59 | (sigmaProp(repaid) && borrowerPK) 60 | } 61 | 62 | 63 | } -------------------------------------------------------------------------------- /src/test/scala/bonds/TestHelper.scala: -------------------------------------------------------------------------------- 1 | package ksingh.sigmabonds 2 | package bonds 3 | 4 | import org.ergoplatform.ErgoAddress 5 | import org.ergoplatform.appkit._ 6 | 7 | object TestHelper { 8 | val networkType: NetworkType = NetworkType.TESTNET 9 | val nodePort: String = if (networkType == NetworkType.MAINNET) ":9053/" else ":9052/" 10 | 11 | val client = RestApiErgoClient.create( 12 | "http://213.239.193.208" + nodePort, 13 | networkType, 14 | "", 15 | RestApiErgoClient.getDefaultExplorerUrl(networkType)) 16 | 17 | case class Party(address: Address, prover: ErgoProver, ergoAddress: ErgoAddress) 18 | 19 | def token(id: ErgoId, amnt: Long) = new ErgoToken(id, amnt) 20 | val buyer: Party = { 21 | client.execute { 22 | ctx => 23 | val prover = ctx.newProverBuilder().withDLogSecret(BigInt(0).bigInteger).build() 24 | Party(prover.getAddress, prover, prover.getAddress.getErgoAddress) 25 | } 26 | } 27 | 28 | val seller: Party = { 29 | client.execute { 30 | ctx => 31 | val prover = ctx.newProverBuilder().withDLogSecret(BigInt(1).bigInteger).build() 32 | Party(prover.getAddress, prover, prover.getAddress.getErgoAddress) 33 | } 34 | } 35 | 36 | val dev: Party = { 37 | client.execute{ 38 | ctx => 39 | val prover = ctx.newProverBuilder().withDLogSecret(BigInt(2).bigInteger).build() 40 | Party(prover.getAddress, prover, prover.getAddress.getErgoAddress) 41 | } 42 | } 43 | 44 | def walletBox(ctx: BlockchainContext, owner: Party, amount: Long, tokens: Seq[ErgoToken] = Seq.empty[ErgoToken]): OutBox = { 45 | val box = ctx.newTxBuilder().outBoxBuilder() 46 | .value(amount) 47 | .contract(owner.address.toErgoContract) 48 | 49 | if(tokens.nonEmpty) 50 | box.tokens(tokens:_*) 51 | 52 | box.build() 53 | } 54 | 55 | def mintToken(ctx: BlockchainContext, owner: Party, amnt: Long, amntTok: Long, box: InputBox): SignedTransaction = { 56 | val out = ctx.newTxBuilder().outBoxBuilder() 57 | .value(amnt) 58 | .mintToken(new Eip4Token(box.getId.toString, amntTok, "tSigUSD", "test SigUSD", 2)) 59 | .contract(owner.address.toErgoContract) 60 | .build() 61 | 62 | val tx = ctx.newTxBuilder().addInputs(box).addOutputs(out).fee(Parameters.MinFee).sendChangeTo(owner.address).build() 63 | owner.prover.sign(tx) 64 | } 65 | 66 | val fakeCurrency: ErgoId = ErgoId.create("0cd8c9f416e5b1ca9f986a7f10a84191dfb85941619e49e53c0dc30ebf83324b") 67 | 68 | val fakeAsset: ErgoId = ErgoId.create("50ee4923fb56e0d215ad49424d76c216be6f807908be3951045f9e0020fbee09") 69 | 70 | val fakeTxId: String = "a1086e447695dc8dcb79c0bf3b06ed715ccfa2b28ef44889ebfbda16c00ff34b" 71 | } 72 | -------------------------------------------------------------------------------- /contracts/EXP_BondContractERG.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // ======================== EXPERIMENTAL ======================== 3 | // ERG bond contract which also holds hashed prop bytes of special liquidation script in R9, allowing 4 | // for more complex liquidation logic outside of height condition 5 | // R4: Box id of originating order: Coll[Byte] 6 | // R5: borrowerPK: SigmaProp 7 | // R6: Total Repayment (In nanoERGs): Long 8 | // R7: Bond Maturity Height: Int 9 | // R8: lenderPK: SigmaProp 10 | // R9: hashedLiqScriptPropBytes: Coll[Byte] 11 | // We use hashed prop bytes because I imagine real liquidation scripts may be complicated (and therefore large) 12 | // Definitions in OpenOrder contract 13 | 14 | val borrowerPK = SELF.R5[SigmaProp].get 15 | val repayment = SELF.R6[Long].get 16 | val maturityHeight = SELF.R7[Int].get 17 | val lenderPK = SELF.R8[SigmaProp].get 18 | val hashedLiqScript = SELF.R9[Coll[Byte]].get 19 | val collateralAssets = SELF.tokens 20 | val collateralERG = SELF.value 21 | val repaymentBox = OUTPUTS(0) 22 | 23 | val liquidationScript = getVar[SigmaProp](0) 24 | // Constants 25 | // None 26 | 27 | val liquidationConditions = { 28 | if(liquidationScript.isDefined){ 29 | val matchedHash = blake2b256( liquidationScript.get.propBytes ) == hashedLiqScript 30 | 31 | sigmaProp(matchedHash) && liquidationScript.get 32 | }else{ 33 | sigmaProp(HEIGHT >= maturityHeight) 34 | } 35 | } 36 | val liquidated = { 37 | allOf( 38 | Coll( 39 | repaymentBox.propositionBytes == lenderPK.propBytes, 40 | repaymentBox.tokens == collateralAssets, 41 | repaymentBox.value == collateralERG, 42 | repaymentBox.R4[Coll[Byte]].get == SELF.id 43 | ) 44 | ) 45 | } 46 | // Collateral box returned to borrower 47 | val returnBox = OUTPUTS(1) 48 | val repaid = { 49 | allOf( 50 | Coll( 51 | // RepaymentBox Conditions 52 | HEIGHT < maturityHeight, 53 | repaymentBox.propositionBytes == lenderPK.propBytes, 54 | repaymentBox.value == repayment, 55 | repaymentBox.R4[Coll[Byte]].get == SELF.id, 56 | // ReturnBox Conditions 57 | returnBox.propositionBytes == borrowerPK.propBytes, 58 | returnBox.tokens == collateralAssets, 59 | returnBox.value == collateralERG 60 | ) 61 | ) 62 | } 63 | 64 | (sigmaProp(liquidated) && liquidationConditions) || (sigmaProp(repaid) && borrowerPK) 65 | 66 | } -------------------------------------------------------------------------------- /src/test/scala/bonds/BondGenerator.scala: -------------------------------------------------------------------------------- 1 | package ksingh.sigmabonds 2 | package bonds 3 | 4 | import client.ScriptGenerator 5 | 6 | import ksingh.sigmabonds.bonds.TestHelper.{Party, dev} 7 | import org.ergoplatform.appkit.scalaapi.ErgoValueBuilder 8 | import org.ergoplatform.appkit.{BlockchainContext, ErgoId, ErgoToken, ErgoValue, InputBox, OutBox} 9 | import scalan.RType 10 | import sigmastate.eval.CostingSigmaDslBuilder.Colls 11 | 12 | object BondGenerator { 13 | 14 | def makeOrderBox(ctx: BlockchainContext, value: Long, tokens: Seq[ErgoToken], 15 | optTokenId: Option[ErgoId] = None, isFixed: Boolean = true, 16 | borrower: Party, principal: Long, repayment: Long, maturity: Int): OutBox = { 17 | val box = ctx.newTxBuilder().outBoxBuilder() 18 | .value(value) 19 | 20 | .contract(ScriptGenerator.mkOrderContract(ctx, isFixed, optTokenId.map(_.toString), dev.address.getPublicKey)) 21 | .registers( 22 | ErgoValue.of(borrower.address.getPublicKey), 23 | ErgoValue.of(principal), 24 | ErgoValue.of(repayment), 25 | ErgoValue.of(maturity), 26 | ) 27 | 28 | if(tokens.nonEmpty) 29 | box.tokens(tokens:_*) 30 | 31 | box.build() 32 | } 33 | 34 | def makeOfferBox(ctx: BlockchainContext, principal: Long, tokens: Seq[ErgoToken], 35 | optTokenId: Option[ErgoId] = None, isFixed: Boolean = true, 36 | lender: Party, repayment: Long, collateralErg: Long, collateralAssets: Seq[ErgoToken], maturity: Int): OutBox = { 37 | val box = ctx.newTxBuilder().outBoxBuilder() 38 | .value(principal) 39 | 40 | .contract(ScriptGenerator.mkOfferContract(ctx, isFixed, optTokenId.map(_.toString), dev.address.getPublicKey)) 41 | .registers( 42 | ErgoValue.of(lender.address.getPublicKey), 43 | ErgoValue.of(collateralErg), 44 | ErgoValueBuilder.buildFor(Colls.fromArray(collateralAssets.map(c => Colls.fromArray(c.getId.getBytes) -> c.getValue).toArray)), 45 | ErgoValue.of(repayment), 46 | ErgoValue.of(maturity), 47 | ) 48 | 49 | if(tokens.nonEmpty) 50 | box.tokens(tokens:_*) 51 | 52 | box.build() 53 | } 54 | 55 | def makeBondBox(ctx: BlockchainContext, value: Long, tokens: Seq[ErgoToken], 56 | optTokenId: Option[ErgoId] = None, isFixed: Boolean = true, 57 | lastBoxId: ErgoId, borrower: Party, lender: Party, repayment: Long, maturityHeight: Int): OutBox = { 58 | val box = ctx.newTxBuilder().outBoxBuilder() 59 | .value(value) 60 | 61 | .contract(ScriptGenerator.mkBondContract(ctx, optTokenId.map(_.toString))) 62 | .registers( 63 | ErgoValueBuilder.buildFor(Colls.fromArray(lastBoxId.getBytes)), 64 | ErgoValue.of(borrower.address.getPublicKey), 65 | ErgoValueBuilder.buildFor(repayment), 66 | ErgoValueBuilder.buildFor(maturityHeight), 67 | ErgoValue.of(lender.address.getPublicKey) 68 | ) 69 | if(tokens.nonEmpty) 70 | box.tokens(tokens:_*) 71 | 72 | box.build() 73 | 74 | } 75 | 76 | def toInput(outBox: OutBox) = outBox.convertToInputWith(TestHelper.fakeTxId, 0.toShort) 77 | } 78 | -------------------------------------------------------------------------------- /contracts/ex/ExOrderERG.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Ex Order SIGUSD/tokens Contract 3 | // Sellers send the assets they wish to sell to a box under this contract 4 | // R4: Seller's PK: SigmaProp 5 | // R5: Sale Price (In nanoERG): Long 6 | 7 | // Sale Price is simply number of nanoERG to exchange for contents of the box 8 | 9 | val sellerPK = SELF.R4[SigmaProp].get 10 | val salePrice = SELF.R5[Long].get 11 | val totalAssets = SELF.tokens 12 | val totalERG = SELF.value 13 | 14 | // Constants 15 | // _devPK: PK of dev :^) 16 | 17 | // Output box if open order is closed. 18 | val exBox = OUTPUTS(0) 19 | 20 | val orderIsClosed = exBox.propositionBytes != sellerPK.propBytes 21 | // Optional UI Fee Address, which may be inserted into context vars 22 | val optUIFee = getVar[SigmaProp](0) 23 | 24 | // We use BigInts to help deal with long overflow on large value bonds 25 | val fees: Coll[(SigmaProp, BigInt)] = { 26 | 27 | val feeDenom = 100000L 28 | val devFee = 500L 29 | // If ui fee is defined, then we add an additional 0.4% fee 30 | if(optUIFee.isDefined){ 31 | val uiFee = 400L 32 | 33 | Coll( 34 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 35 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 36 | ) 37 | }else{ 38 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 39 | } 40 | } 41 | 42 | if(orderIsClosed){ 43 | // Order Matched / Closed path 44 | val sellerBox = OUTPUTS(1) 45 | 46 | val orderMade = { 47 | allOf( 48 | Coll( 49 | // Exchange Box Conditions 50 | exBox.R4[Coll[Byte]].get == SELF.id, 51 | exBox.tokens == totalAssets, 52 | exBox.value == totalERG, 53 | 54 | // Loan Conditions 55 | sellerBox.propositionBytes == sellerPK.propBytes, 56 | sellerBox.value == salePrice 57 | ) 58 | ) 59 | } 60 | 61 | 62 | 63 | // Ensure that correct fee output boxes exist 64 | val feesPaid = { 65 | 66 | val devFeesPaid = { 67 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 68 | val devOutput = OUTPUTS(2) 69 | allOf( 70 | Coll( 71 | devOutput.propositionBytes == fees(0)._1.propBytes, 72 | devOutput.value == 1000000L, 73 | devOutput.tokens(0)._1 == _tokenId, 74 | devOutput.tokens(0)._2.toBigInt == fees(0)._2, 75 | devOutput.tokens.size == 1 76 | ) 77 | ) 78 | }else{ 79 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 80 | } 81 | } 82 | 83 | val uiFeesPaid = { 84 | if(optUIFee.isDefined){ 85 | if(fees(1)._2 > 0){ // UI fee is greater than 0 86 | val uiOutput = OUTPUTS(3) 87 | allOf( 88 | Coll( 89 | uiOutput.propositionBytes == fees(1)._1.propBytes, 90 | uiOutput.value == 1000000L, 91 | uiOutput.tokens(0)._1 == _tokenId, 92 | uiOutput.tokens(0)._2.toBigInt == fees(1)._2, 93 | uiOutput.tokens.size == 1 94 | ) 95 | ) 96 | }else{ 97 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 98 | } 99 | }else{ 100 | true // if ui fee isn't defined, then default to true. 101 | } 102 | } 103 | devFeesPaid && uiFeesPaid 104 | } 105 | 106 | sigmaProp(orderMade && feesPaid) 107 | }else{ 108 | // Refund Path 109 | sellerPK 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /src/main/scala/client/ScriptGenerator.scala: -------------------------------------------------------------------------------- 1 | package ksingh.sigmabonds 2 | package client 3 | 4 | import org.ergoplatform.appkit._ 5 | import scorex.crypto.hash.Blake2b256 6 | import sigmastate.basics.DLogProtocol.ProveDlog 7 | import sigmastate.eval.CostingSigmaDslBuilder.Colls 8 | import special.collection.Coll 9 | 10 | import scala.io.Source 11 | 12 | object ScriptGenerator { 13 | private final val BASE_PATH = "contracts/" 14 | private final val EXT = ".ergo" 15 | 16 | private final val BOND_PREFIX = "BondContract" 17 | private final val ORD_PREFIX = "OpenOrder" 18 | private final val OFF_PREFIX = "OpenOffer" 19 | 20 | 21 | private final val FIX_HEIGHT = "FixedHeight" 22 | private final val ON_CLOSE = "OnClose" 23 | 24 | private final val ERG = "ERG" 25 | private final val TOKEN = "Token" 26 | 27 | def mkScript(name: String): String = { 28 | val src = Source.fromFile(BASE_PATH + name + EXT) 29 | val script = src.mkString 30 | src.close() 31 | script 32 | } 33 | 34 | def idFromStr(idStr: String): Coll[Byte] = { 35 | Colls.fromArray( 36 | ErgoId.create( 37 | idStr 38 | ).getBytes 39 | ) 40 | } 41 | 42 | def mkBondContract(ctx: BlockchainContext, optTokenId: Option[String]): ErgoContract = { 43 | optTokenId match { 44 | case Some(id) => 45 | val script = mkScript(BOND_PREFIX + TOKEN) 46 | val constants = ConstantsBuilder 47 | .create() 48 | .item("_tokenId", idFromStr(id)) 49 | .build() 50 | 51 | ctx.compileContract(constants, script) 52 | case None => 53 | val script = mkScript(BOND_PREFIX + ERG) 54 | val constants = ConstantsBuilder.empty() 55 | 56 | ctx.compileContract(constants, script) 57 | } 58 | } 59 | 60 | def mkOrderContract(ctx: BlockchainContext, isFixed: Boolean, optTokenId: Option[String], 61 | devPK: ProveDlog = Address.create("9hgm8enrcPL3UUVHFpmLXxpSNCxWs5LxGBV6YS8Xy8KSoTtPPhE").getPublicKey): ErgoContract = { 62 | val bondContractHash = { 63 | val bondContract = mkBondContract(ctx, optTokenId) 64 | Blake2b256.hash( 65 | bondContract.getErgoTree.bytes 66 | ) 67 | } 68 | 69 | val orderType = { 70 | if (isFixed) 71 | FIX_HEIGHT 72 | else 73 | ON_CLOSE 74 | } 75 | 76 | optTokenId match { 77 | case Some(id) => 78 | val script = mkScript(ORD_PREFIX + orderType + TOKEN) 79 | val constants = ConstantsBuilder 80 | .create() 81 | .item("_tokenId", idFromStr(id)) 82 | .item("_devPK", devPK) 83 | .item("_bondContractHash", Colls.fromArray(bondContractHash)) 84 | .build() 85 | 86 | ctx.compileContract(constants, script) 87 | case None => 88 | val script = mkScript(ORD_PREFIX + orderType + ERG) 89 | val constants = ConstantsBuilder 90 | .create() 91 | .item("_devPK", devPK) 92 | .item("_bondContractHash", Colls.fromArray(bondContractHash)) 93 | .build() 94 | 95 | ctx.compileContract(constants, script) 96 | } 97 | } 98 | 99 | def mkOfferContract(ctx: BlockchainContext, isFixed: Boolean, optTokenId: Option[String], 100 | devPK: ProveDlog = Address.create("9hgm8enrcPL3UUVHFpmLXxpSNCxWs5LxGBV6YS8Xy8KSoTtPPhE").getPublicKey): ErgoContract = { 101 | val bondContractHash = { 102 | val bondContract = mkBondContract(ctx, optTokenId) 103 | Blake2b256.hash( 104 | bondContract.getErgoTree.bytes 105 | ) 106 | } 107 | 108 | val orderType = { 109 | if (isFixed) 110 | FIX_HEIGHT 111 | else 112 | ON_CLOSE 113 | } 114 | 115 | optTokenId match { 116 | case Some(id) => 117 | val script = mkScript(OFF_PREFIX + orderType + TOKEN) 118 | val constants = ConstantsBuilder 119 | .create() 120 | .item("_tokenId", idFromStr(id)) 121 | .item("_devPK", devPK) 122 | .item("_bondContractHash", Colls.fromArray(bondContractHash)) 123 | .build() 124 | 125 | ctx.compileContract(constants, script) 126 | case None => 127 | val script = mkScript(OFF_PREFIX + orderType + ERG) 128 | val constants = ConstantsBuilder 129 | .create() 130 | .item("_devPK", devPK) 131 | .item("_bondContractHash", Colls.fromArray(bondContractHash)) 132 | .build() 133 | 134 | ctx.compileContract(constants, script) 135 | } 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /contracts/ex/ExOrderToken.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Ex Order SIGUSD/tokens Contract 3 | // Sellers send the assets they wish to sell to a box under this contract 4 | // R4: Seller's PK: SigmaProp 5 | // R5: Sale Price (In number of raw / non-decimaled SIGUSD tokens): Long 6 | 7 | // Sale Price is simply number of tokens for given tokenId to exchange for contents of the box 8 | 9 | val sellerPK = SELF.R4[SigmaProp].get 10 | val salePrice = SELF.R5[Long].get 11 | val totalAssets = SELF.tokens 12 | val totalERG = SELF.value 13 | 14 | // Constants 15 | // _tokenId: Token id of bond currency 16 | // _devPK: PK of dev :^) 17 | 18 | // Output box if open order is closed. 19 | val exBox = OUTPUTS(0) 20 | 21 | val orderIsClosed = exBox.propositionBytes != sellerPK.propBytes 22 | // Optional UI Fee Address, which may be inserted into context vars 23 | val optUIFee = getVar[SigmaProp](0) 24 | 25 | // We use BigInts to help deal with long overflow on large value bonds 26 | val fees: Coll[(SigmaProp, BigInt)] = { 27 | 28 | val feeDenom = 100000L 29 | val devFee = 500L 30 | // If ui fee is defined, then we add an additional 0.4% fee 31 | if(optUIFee.isDefined){ 32 | val uiFee = 400L 33 | 34 | Coll( 35 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 36 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 37 | ) 38 | }else{ 39 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 40 | } 41 | } 42 | 43 | if(orderIsClosed){ 44 | // Order Matched / Closed path 45 | val sellerBox = OUTPUTS(1) 46 | 47 | val orderMade = { 48 | allOf( 49 | Coll( 50 | // Exchange Box Conditions 51 | exBox.R4[Coll[Byte]].get == SELF.id, 52 | exBox.tokens == totalAssets, 53 | exBox.value == totalERG, 54 | 55 | // Loan Conditions 56 | sellerBox.propositionBytes == sellerPK.propBytes, 57 | sellerBox.value == 1000000L, 58 | sellerBox.tokens(0)._1 == _tokenId, 59 | sellerBox.tokens(0)._2 == salePrice, 60 | sellerBox.tokens.size == 1 61 | ) 62 | ) 63 | } 64 | 65 | 66 | 67 | // Ensure that correct fee output boxes exist 68 | val feesPaid = { 69 | 70 | val devFeesPaid = { 71 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 72 | val devOutput = OUTPUTS(2) 73 | allOf( 74 | Coll( 75 | devOutput.propositionBytes == fees(0)._1.propBytes, 76 | devOutput.value == 1000000L, 77 | devOutput.tokens(0)._1 == _tokenId, 78 | devOutput.tokens(0)._2.toBigInt == fees(0)._2, 79 | devOutput.tokens.size == 1 80 | ) 81 | ) 82 | }else{ 83 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 84 | } 85 | } 86 | 87 | val uiFeesPaid = { 88 | if(optUIFee.isDefined){ 89 | if(fees(1)._2 > 0){ // UI fee is greater than 0 90 | val uiOutput = OUTPUTS(3) 91 | allOf( 92 | Coll( 93 | uiOutput.propositionBytes == fees(1)._1.propBytes, 94 | uiOutput.value == 1000000L, 95 | uiOutput.tokens(0)._1 == _tokenId, 96 | uiOutput.tokens(0)._2.toBigInt == fees(1)._2, 97 | uiOutput.tokens.size == 1 98 | ) 99 | ) 100 | }else{ 101 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 102 | } 103 | }else{ 104 | true // if ui fee isn't defined, then default to true. 105 | } 106 | } 107 | devFeesPaid && uiFeesPaid 108 | } 109 | 110 | sigmaProp(orderMade && feesPaid) 111 | }else{ 112 | // Refund Path 113 | sellerPK 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /contracts/options/OpenCallTokenToken.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Call Order Contract 3 | 4 | val sellerPK = SELF.R4[SigmaProp].get 5 | val strikePrice = SELF.R5[Long].get 6 | val credit = SELF.R6[Long].get 7 | val expiryHeight = SELF.R7[Int].get 8 | 9 | 10 | // Constants 11 | // _tokenId: Token id of payment currency 12 | // _optContractHash: Hash of bond contract 13 | // _devPK: PK of dev :^) 14 | 15 | // Output box if open order is closed. 16 | val optBox = OUTPUTS(0) 17 | val orderIsClosed = _optContractHash == blake2b256( optBox.propositionBytes ) 18 | 19 | // Optional UI Fee Address, which may be inserted into context vars 20 | val optUIFee = getVar[SigmaProp](0) 21 | 22 | // We use BigInts to help deal with long overflow on large value bonds 23 | val fees: Coll[(SigmaProp, BigInt)] = { 24 | 25 | val feeDenom = 100000L 26 | val devFee = 450L 27 | // If ui fee is defined, then we add an additional 0.45% fee 28 | if(optUIFee.isDefined){ 29 | val uiFee = 450L 30 | 31 | Coll( 32 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 33 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 34 | ) 35 | }else{ 36 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 37 | } 38 | } 39 | 40 | if(orderIsClosed){ 41 | // Order Matched / Closed path 42 | val creditBox = OUTPUTS(1) 43 | 44 | val orderMade = { 45 | allOf( 46 | Coll( 47 | // Opt Box Conditions 48 | optBox.R4[Coll[Byte]].get == SELF.id, 49 | optBox.R5[SigmaProp].get == sellerPK, 50 | optBox.R6[Long].get == strikePrice, 51 | optBox.R7[Int].get == expiryHeight, 52 | optBox.R8[SigmaProp].isDefined, 53 | optBox.tokens.size == 1, // We don't specify what token or amount of said token 54 | // Allows flexibility for abnormal option contracts, 55 | // along with options on NFTs 56 | optBox.value == 1000000L, 57 | 58 | 59 | // Credit Conditions 60 | creditBox.propositionBytes == sellerPK.propBytes, 61 | creditBox.value == 1000000L, 62 | creditBox.tokens(0)._1 == _tokenId, 63 | creditBox.tokens(0)._2 == credit, 64 | creditBox.tokens.size == 1 65 | ) 66 | ) 67 | } 68 | 69 | 70 | 71 | // Ensure that correct fee output boxes exist 72 | val feesPaid = { 73 | 74 | val devFeesPaid = { 75 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 76 | val devOutput = OUTPUTS(2) 77 | allOf( 78 | Coll( 79 | devOutput.propositionBytes == fees(0)._1.propBytes, 80 | devOutput.value == 1000000L, 81 | devOutput.tokens(0)._1 == _tokenId, 82 | devOutput.tokens(0)._2.toBigInt == fees(0)._2, 83 | devOutput.tokens.size == 1 84 | ) 85 | ) 86 | }else{ 87 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 88 | } 89 | } 90 | 91 | val uiFeesPaid = { 92 | if(optUIFee.isDefined){ 93 | if(fees(1)._2 > 0){ // UI fee is greater than 0 94 | val uiOutput = OUTPUTS(3) 95 | allOf( 96 | Coll( 97 | uiOutput.propositionBytes == fees(1)._1.propBytes, 98 | uiOutput.value == 1000000L, 99 | uiOutput.tokens(0)._1 == _tokenId, 100 | uiOutput.tokens(0)._2.toBigInt == fees(1)._2, 101 | uiOutput.tokens.size == 1 102 | ) 103 | ) 104 | }else{ 105 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 106 | } 107 | }else{ 108 | true // if ui fee isn't defined, then default to true. 109 | } 110 | } 111 | devFeesPaid && uiFeesPaid 112 | } 113 | 114 | sigmaProp(orderMade && feesPaid) 115 | }else{ 116 | // Refund Path 117 | sellerPK 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /contracts/OpenOrderFixedHeightERG.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Order ERG Contract 3 | // Borrowers send their collateralized ERG and/or tokens, along with setting the following registers 4 | // to specify details about the SigmaBond they wish to make 5 | // R4: Borrower's PK: SigmaProp 6 | // R5: Bond Principal (In number of nanoERG): Long 7 | // R6: Total Repayment (In number of nanoERG): Long 8 | // R7: Bond Maturity Height: Int 9 | 10 | // Principal represents the amount of tokens the borrower gets immediately upon a Bond order being taken by a lender. 11 | // Total Repayment is the amount of tokens the lender must receive before the maturity height, in order for 12 | // the borrower to not be liquidated 13 | // Bond Maturity Height is the height at which the borrower may be liquidated if the Bond has not been repaid yet. 14 | // The collateral for the Bond is the total contents of all assets + ERG within this box 15 | 16 | val borrowerPK = SELF.R4[SigmaProp].get 17 | val principal = SELF.R5[Long].get 18 | val repayment = SELF.R6[Long].get 19 | val maturityHeight = SELF.R7[Int].get 20 | val totalAssets = SELF.tokens 21 | val totalERG = SELF.value 22 | 23 | // Constants 24 | // _bondContractHash: Hash of bond contract 25 | // _devPK: PK of dev :^) 26 | 27 | // Output box if open order is closed. 28 | val bondBox = OUTPUTS(0) 29 | val orderIsClosed = _bondContractHash == blake2b256( bondBox.propositionBytes ) 30 | 31 | // Optional UI Fee Address, which may be inserted into context vars 32 | val optUIFee = getVar[SigmaProp](0) 33 | 34 | // We use BigInts to prevent long overflow during multiplication of feeNumerator and principal value 35 | // This is only really necessary when dealing with large values 36 | val fees: Coll[(SigmaProp, BigInt)] = { 37 | 38 | val feeDenom = 100000L 39 | val devFee = 500L // 0.5% 40 | // If ui fee is defined, then we add an additional 0.4% fee 41 | if(optUIFee.isDefined){ 42 | val uiFee = 400L // 0.4% 43 | 44 | Coll( 45 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 46 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 47 | ) 48 | }else{ 49 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 50 | } 51 | } 52 | 53 | if(orderIsClosed){ 54 | // Order Matched / Closed path 55 | val loanBox = OUTPUTS(1) 56 | val totalFees = fees.fold(0.toBigInt, { 57 | (z: BigInt, b: (SigmaProp, BigInt)) => 58 | z + b._2 59 | }) 60 | val orderMade = { 61 | allOf( 62 | Coll( 63 | 64 | // Bond Box Conditions 65 | bondBox.R4[Coll[Byte]].get == SELF.id, 66 | bondBox.R5[SigmaProp].get == borrowerPK, 67 | bondBox.R6[Long].get == repayment, 68 | 69 | bondBox.R8[SigmaProp].isDefined, 70 | bondBox.tokens == totalAssets, 71 | bondBox.value == totalERG, 72 | 73 | // Maturity Conditions 74 | bondBox.R7[Int].get == maturityHeight, 75 | 76 | // Loan Conditions 77 | loanBox.propositionBytes == borrowerPK.propBytes, 78 | loanBox.value == principal - totalFees 79 | ) 80 | ) 81 | } 82 | 83 | 84 | // Ensure that correct fee output boxes exist 85 | val feesPaid = { 86 | 87 | val devFeesPaid = { 88 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 89 | val devOutput = OUTPUTS(2) 90 | allOf( 91 | Coll( 92 | devOutput.propositionBytes == fees(0)._1.propBytes, 93 | devOutput.value.toBigInt == fees(0)._2 94 | ) 95 | ) 96 | }else{ 97 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 98 | } 99 | } 100 | 101 | val uiFeesPaid = { 102 | if(optUIFee.isDefined){ 103 | if(fees(1)._2 > 0){ // UI fee is greater than 0 104 | val uiOutput = OUTPUTS(3) 105 | allOf( 106 | Coll( 107 | uiOutput.propositionBytes == fees(1)._1.propBytes, 108 | uiOutput.value.toBigInt == fees(1)._2 109 | ) 110 | ) 111 | }else{ 112 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 113 | } 114 | }else{ 115 | true // if ui fee isn't defined, then default to true. 116 | } 117 | } 118 | devFeesPaid && uiFeesPaid 119 | } 120 | 121 | sigmaProp(orderMade && feesPaid) 122 | }else{ 123 | // Refund Path 124 | borrowerPK 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /contracts/OpenOfferFixedHeightERG.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Offer ERG Contract 3 | // Lender sends principal amount of tokens, and specifies desired collateral and repayment 4 | // to specify details about the SigmaBond they wish to make 5 | // R4: Lenders's PK: SigmaProp 6 | // R5: Bond Collateral (In number of nanoERG): Long 7 | // R6: Bond Collateral Assets: Coll(Coll[Byte], Long) 8 | // R7: Total Repayment (In number of tokens): Long 9 | // R7: Bond Maturity Height: Int 10 | 11 | // Principal represents the amount of tokens the borrower gets immediately upon a Bond order being taken by a lender. 12 | // Total Repayment is the amount of tokens the lender must receive before the maturity height, in order for 13 | // the borrower to not be liquidated 14 | // Bond Maturity Height is the height at which the borrower may be liquidated if the Bond has not been repaid yet. 15 | // The collateral for the Bond is the total contents of all assets + ERG within this box 16 | 17 | val lenderPK = SELF.R4[SigmaProp].get 18 | val collateral = SELF.R5[Long].get 19 | val collateralAssets = SELF.R6[Coll[(Coll[Byte], Long)]].get 20 | val repayment = SELF.R7[Long].get 21 | val maturityHeight = SELF.R8[Int].get 22 | val totalAssets = SELF.tokens 23 | val totalERG = SELF.value 24 | 25 | // Constants 26 | // _bondContractHash: Hash of bond contract 27 | // _devPK: PK of dev :^) 28 | 29 | // Output box if open order is closed. 30 | val bondBox = OUTPUTS(0) 31 | val orderIsClosed = _bondContractHash == blake2b256( bondBox.propositionBytes ) 32 | 33 | // Optional UI Fee Address, which may be inserted into context vars 34 | val optUIFee = getVar[SigmaProp](0) 35 | val principal = SELF.value 36 | // We use BigInts to prevent long overflow during multiplication of feeNumerator and principal value 37 | // This is only really necessary when dealing with large values 38 | val fees: Coll[(SigmaProp, BigInt)] = { 39 | 40 | val feeDenom = 100000L 41 | val devFee = 500L // 0.5% 42 | // If ui fee is defined, then we add an additional 0.5% fee 43 | if(optUIFee.isDefined){ 44 | val uiFee = 500L // 0.5% 45 | 46 | Coll( 47 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 48 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 49 | ) 50 | }else{ 51 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 52 | } 53 | } 54 | 55 | if(orderIsClosed){ 56 | // Order Matched / Closed path 57 | val loanBox = OUTPUTS(1) 58 | 59 | val orderMade = { 60 | allOf( 61 | Coll( 62 | 63 | // Bond Box Conditions 64 | bondBox.R4[Coll[Byte]].get == SELF.id, 65 | bondBox.R5[SigmaProp].isDefined, 66 | bondBox.R6[Long].get == repayment, 67 | 68 | bondBox.R8[SigmaProp].get == lenderPK, 69 | bondBox.tokens == collateralAssets, 70 | bondBox.value == collateral, 71 | 72 | // Maturity Conditions 73 | bondBox.R7[Int].get == maturityHeight, 74 | 75 | // Loan Conditions 76 | loanBox.propositionBytes == bondBox.R5[SigmaProp].get.propBytes, 77 | loanBox.value == principal 78 | ) 79 | ) 80 | } 81 | 82 | 83 | // Ensure that correct fee output boxes exist 84 | val feesPaid = { 85 | 86 | val devFeesPaid = { 87 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 88 | val devOutput = OUTPUTS(2) 89 | allOf( 90 | Coll( 91 | devOutput.propositionBytes == fees(0)._1.propBytes, 92 | devOutput.value.toBigInt == fees(0)._2 93 | ) 94 | ) 95 | }else{ 96 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 97 | } 98 | } 99 | 100 | val uiFeesPaid = { 101 | if(optUIFee.isDefined){ 102 | if(fees(1)._2 > 0){ // UI fee is greater than 0 103 | val uiOutput = OUTPUTS(3) 104 | allOf( 105 | Coll( 106 | uiOutput.propositionBytes == fees(1)._1.propBytes, 107 | uiOutput.value.toBigInt == fees(1)._2 108 | ) 109 | ) 110 | }else{ 111 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 112 | } 113 | }else{ 114 | true // if ui fee isn't defined, then default to true. 115 | } 116 | } 117 | devFeesPaid && uiFeesPaid 118 | } 119 | 120 | sigmaProp(orderMade && feesPaid) 121 | }else{ 122 | // Refund Path 123 | lenderPK 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /contracts/OpenOrderFixedHeightToken.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Order SIGUSD/tokens Contract 3 | // Borrowers send their collateralized ERG and/or tokens, along with setting the following registers 4 | // to specify details about the SigmaBond they wish to make 5 | // R4: Borrower's PK: SigmaProp 6 | // R5: Bond Principal (In number of raw / non-decimaled SIGUSD tokens): Long 7 | // R6: Total Repayment (In number of raw / non-decimaled SIGUSD tokens): Long 8 | // R7: Bond Maturity Height: Int 9 | 10 | // Principal represents the amount of tokens the borrower gets immediately upon a Bond order being taken by a lender. 11 | // Total Repayment is the amount of tokens the lender must receive before the maturity height, in order for 12 | // the borrower to not be liquidated 13 | // Bond Maturity Height is the height at which the borrower may be liquidated if the Bond has not been repaid yet. 14 | 15 | val borrowerPK = SELF.R4[SigmaProp].get 16 | val principal = SELF.R5[Long].get 17 | val repayment = SELF.R6[Long].get 18 | val maturityHeight = SELF.R7[Int].get 19 | val totalAssets = SELF.tokens 20 | val totalERG = SELF.value 21 | 22 | // Constants 23 | // _tokenId: Token id of bond currency 24 | // _bondContractHash: Hash of bond contract 25 | // _devPK: PK of dev :^) 26 | 27 | // Output box if open order is closed. 28 | val bondBox = OUTPUTS(0) 29 | val orderIsClosed = _bondContractHash == blake2b256( bondBox.propositionBytes ) 30 | 31 | // Optional UI Fee Address, which may be inserted into context vars 32 | val optUIFee = getVar[SigmaProp](0) 33 | 34 | // We use BigInts to help deal with long overflow on large value bonds 35 | val fees: Coll[(SigmaProp, BigInt)] = { 36 | 37 | val feeDenom = 100000L 38 | val devFee = 500L 39 | // If ui fee is defined, then we add an additional 0.4% fee 40 | if(optUIFee.isDefined){ 41 | val uiFee = 400L 42 | 43 | Coll( 44 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 45 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 46 | ) 47 | }else{ 48 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 49 | } 50 | } 51 | 52 | if(orderIsClosed){ 53 | // Order Matched / Closed path 54 | val loanBox = OUTPUTS(1) 55 | 56 | val orderMade = { 57 | allOf( 58 | Coll( 59 | // Bond Box Conditions 60 | bondBox.R4[Coll[Byte]].get == SELF.id, 61 | bondBox.R5[SigmaProp].get == borrowerPK, 62 | bondBox.R6[Long].get == repayment, 63 | 64 | bondBox.R8[SigmaProp].isDefined, 65 | bondBox.tokens == totalAssets, 66 | bondBox.value == totalERG, 67 | 68 | // Maturity Conditions 69 | bondBox.R7[Int].get == maturityHeight, 70 | 71 | // Loan Conditions 72 | loanBox.propositionBytes == borrowerPK.propBytes, 73 | loanBox.value == 1000000L, 74 | loanBox.tokens(0)._1 == _tokenId, 75 | loanBox.tokens(0)._2 == principal, 76 | loanBox.tokens.size == 1 77 | ) 78 | ) 79 | } 80 | 81 | 82 | 83 | // Ensure that correct fee output boxes exist 84 | val feesPaid = { 85 | 86 | val devFeesPaid = { 87 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 88 | val devOutput = OUTPUTS(2) 89 | allOf( 90 | Coll( 91 | devOutput.propositionBytes == fees(0)._1.propBytes, 92 | devOutput.value == 1000000L, 93 | devOutput.tokens(0)._1 == _tokenId, 94 | devOutput.tokens(0)._2.toBigInt == fees(0)._2, 95 | devOutput.tokens.size == 1 96 | ) 97 | ) 98 | }else{ 99 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 100 | } 101 | } 102 | 103 | val uiFeesPaid = { 104 | if(optUIFee.isDefined){ 105 | if(fees(1)._2 > 0){ // UI fee is greater than 0 106 | val uiOutput = OUTPUTS(3) 107 | allOf( 108 | Coll( 109 | uiOutput.propositionBytes == fees(1)._1.propBytes, 110 | uiOutput.value == 1000000L, 111 | uiOutput.tokens(0)._1 == _tokenId, 112 | uiOutput.tokens(0)._2.toBigInt == fees(1)._2, 113 | uiOutput.tokens.size == 1 114 | ) 115 | ) 116 | }else{ 117 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 118 | } 119 | }else{ 120 | true // if ui fee isn't defined, then default to true. 121 | } 122 | } 123 | devFeesPaid && uiFeesPaid 124 | } 125 | 126 | sigmaProp(orderMade && feesPaid) 127 | }else{ 128 | // Refund Path 129 | borrowerPK 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /contracts/OpenOrderOnCloseERG.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Order ERG Contract 3 | // Borrowers send their collateralized ERG and/or tokens, along with setting the following registers 4 | // to specify details about the SigmaBond they wish to make 5 | // R4: Borrower's PK: SigmaProp 6 | // R5: Bond Principal (In number of nanoERG): Long 7 | // R6: Total Repayment (In number of nanoERG): Long 8 | // R7: Bond Maturity Height: Int 9 | 10 | // Principal represents the amount of tokens the borrower gets immediately upon a Bond order being taken by a lender. 11 | // Total Repayment is the amount of tokens the lender must receive before the maturity height, in order for 12 | // the borrower to not be liquidated 13 | // Bond Maturity Length is duration of Bond, which is starting at open order closing. 14 | // If using length, value must be less than 30 (So 30 block length of bond). 15 | // The collateral for the Bond is the total contents of all assets + ERG within this box 16 | 17 | val borrowerPK = SELF.R4[SigmaProp].get 18 | val principal = SELF.R5[Long].get 19 | val repayment = SELF.R6[Long].get 20 | val maturityLength = SELF.R7[Int].get 21 | val totalAssets = SELF.tokens 22 | val totalERG = SELF.value 23 | 24 | // Constants 25 | // _bondContractHash: Hash of bond contract 26 | // _devPK: PK of dev :^) 27 | 28 | // Output box if open order is closed. 29 | val bondBox = OUTPUTS(0) 30 | val orderIsClosed = _bondContractHash == blake2b256( bondBox.propositionBytes ) 31 | 32 | // Optional UI Fee Address, which may be inserted into context vars 33 | val optUIFee = getVar[SigmaProp](0) 34 | 35 | // We use BigInts to prevent long overflow during multiplication of feeNumerator and principal value 36 | // This is only really necessary when dealing with large values 37 | val fees: Coll[(SigmaProp, BigInt)] = { 38 | 39 | val feeDenom = 100000L 40 | val devFee = 500L // 0.5% 41 | // If ui fee is defined, then we add an additional 0.4% fee 42 | if(optUIFee.isDefined){ 43 | val uiFee = 400L // 0.4% 44 | 45 | Coll( 46 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 47 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 48 | ) 49 | }else{ 50 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 51 | } 52 | } 53 | 54 | if(orderIsClosed){ 55 | // Order Matched / Closed path 56 | val loanBox = OUTPUTS(1) 57 | 58 | val orderMade = { 59 | allOf( 60 | Coll( 61 | 62 | // Bond Box Conditions 63 | bondBox.R4[Coll[Byte]].get == SELF.id, 64 | bondBox.R5[SigmaProp].get == borrowerPK, 65 | bondBox.R6[Long].get == repayment, 66 | 67 | bondBox.R8[SigmaProp].isDefined, 68 | bondBox.tokens == totalAssets, 69 | bondBox.value == totalERG, 70 | 71 | // Maturity Conditions 72 | maturityLength >= 30, // All bond orders with duration must at least have duration of 30 blocks 73 | 74 | (HEIGHT + maturityLength) - bondBox.R7[Int].get <= 8, // We allow 8 blocks for a tx spending this box 75 | (HEIGHT + maturityLength) - bondBox.R7[Int].get >= 0, // to be confirmed, and assume maturity height 76 | // of bond box is HEIGHT + maturityLength 77 | // at time of transaction send. 78 | // Should prevent tx's disappearing from 79 | // mempool 80 | 81 | // Loan Conditions 82 | loanBox.propositionBytes == borrowerPK.propBytes, 83 | loanBox.value == principal 84 | ) 85 | ) 86 | } 87 | 88 | 89 | // Ensure that correct fee output boxes exist 90 | val feesPaid = { 91 | 92 | val devFeesPaid = { 93 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 94 | val devOutput = OUTPUTS(2) 95 | allOf( 96 | Coll( 97 | devOutput.propositionBytes == fees(0)._1.propBytes, 98 | devOutput.value.toBigInt == fees(0)._2 99 | ) 100 | ) 101 | }else{ 102 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 103 | } 104 | } 105 | 106 | val uiFeesPaid = { 107 | if(optUIFee.isDefined){ 108 | if(fees(1)._2 > 0){ // UI fee is greater than 0 109 | val uiOutput = OUTPUTS(3) 110 | allOf( 111 | Coll( 112 | uiOutput.propositionBytes == fees(1)._1.propBytes, 113 | uiOutput.value.toBigInt == fees(1)._2 114 | ) 115 | ) 116 | }else{ 117 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 118 | } 119 | }else{ 120 | true // if ui fee isn't defined, then default to true. 121 | } 122 | } 123 | devFeesPaid && uiFeesPaid 124 | } 125 | 126 | sigmaProp(orderMade && feesPaid) 127 | }else{ 128 | // Refund Path 129 | borrowerPK 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /contracts/OpenOfferFixedHeightToken.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Offer ERG Contract 3 | // Lender sends principal amount of tokens, and specifies desired collateral and repayment 4 | // to specify details about the SigmaBond they wish to make 5 | // R4: Lenders's PK: SigmaProp 6 | // R5: Bond Collateral (In number of nanoERG): Long 7 | // R6: Bond Collateral Assets: Coll(Coll[Byte], Long) 8 | // R7: Total Repayment (In number of tokens): Long 9 | // R8: Bond Maturity Height: Int 10 | 11 | // Principal represents the amount of tokens the borrower gets immediately upon a Bond order being taken by a lender. 12 | // Total Repayment is the amount of tokens the lender must receive before the maturity height, in order for 13 | // the borrower to not be liquidated 14 | // Bond Maturity Height is the height at which the borrower may be liquidated if the Bond has not been repaid yet. 15 | // The collateral for the Bond is the total contents of all assets + ERG within this box 16 | 17 | val lenderPK = SELF.R4[SigmaProp].get 18 | val collateral = SELF.R5[Long].get 19 | val collateralAssets = SELF.R6[Coll[(Coll[Byte], Long)]].get 20 | val repayment = SELF.R7[Long].get 21 | val maturityHeight = SELF.R8[Int].get 22 | 23 | 24 | // Constants 25 | // _tokenId: Token id of bond currency 26 | // _bondContractHash: Hash of bond contract 27 | // _devPK: PK of dev :^) 28 | 29 | // Output box if open order is closed. 30 | val bondBox = OUTPUTS(0) 31 | val orderIsClosed = _bondContractHash == blake2b256( bondBox.propositionBytes ) 32 | 33 | // Optional UI Fee Address, which may be inserted into context vars 34 | val optUIFee = getVar[SigmaProp](0) 35 | val principal = SELF.tokens(0)._2 36 | // We use BigInts to prevent long overflow during multiplication of feeNumerator and principal value 37 | // This is only really necessary when dealing with large values 38 | val fees: Coll[(SigmaProp, BigInt)] = { 39 | 40 | val feeDenom = 100000L 41 | val devFee = 500L // 0.5% 42 | // If ui fee is defined, then we add an additional 0.5% fee 43 | if(optUIFee.isDefined){ 44 | val uiFee = 500L // 0.5% 45 | 46 | Coll( 47 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 48 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 49 | ) 50 | }else{ 51 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 52 | } 53 | } 54 | 55 | if(orderIsClosed){ 56 | // Order Matched / Closed path 57 | val loanBox = OUTPUTS(1) 58 | val totalFees = fees.fold(0.toBigInt, { 59 | (z: BigInt, b: (SigmaProp, BigInt)) => 60 | z + b._2 61 | }) 62 | val orderMade = { 63 | allOf( 64 | Coll( 65 | 66 | // Bond Box Conditions 67 | bondBox.R4[Coll[Byte]].get == SELF.id, 68 | bondBox.R5[SigmaProp].isDefined, 69 | bondBox.R6[Long].get == repayment, 70 | 71 | bondBox.R8[SigmaProp].get == lenderPK, 72 | bondBox.tokens == collateralAssets, 73 | bondBox.value == collateral, 74 | 75 | // Maturity Conditions 76 | bondBox.R7[Int].get == maturityHeight, 77 | 78 | // Loan Conditions 79 | loanBox.propositionBytes == bondBox.R5[SigmaProp].get.propBytes, 80 | loanBox.value == 1000000L, 81 | loanBox.tokens(0)._1 == _tokenId, 82 | loanBox.tokens(0)._2 == principal - totalFees, 83 | loanBox.tokens.size == 1, 84 | SELF.tokens.size == 1, // Ensure only one token used for lending 85 | SELF.tokens(0)._1 == _tokenId, 86 | SELF.value == 1000000L 87 | ) 88 | ) 89 | } 90 | 91 | 92 | // Ensure that correct fee output boxes exist 93 | val feesPaid = { 94 | 95 | val devFeesPaid = { 96 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 97 | val devOutput = OUTPUTS(2) 98 | allOf( 99 | Coll( 100 | devOutput.propositionBytes == fees(0)._1.propBytes, 101 | devOutput.value == 1000000L, 102 | devOutput.tokens(0)._1 == _tokenId, 103 | devOutput.tokens(0)._2.toBigInt == fees(0)._2, 104 | devOutput.tokens.size == 1 105 | ) 106 | ) 107 | }else{ 108 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 109 | } 110 | } 111 | 112 | val uiFeesPaid = { 113 | if(optUIFee.isDefined){ 114 | if(fees(1)._2 > 0){ // UI fee is greater than 0 115 | val uiOutput = OUTPUTS(3) 116 | allOf( 117 | Coll( 118 | uiOutput.propositionBytes == fees(1)._1.propBytes, 119 | uiOutput.value == 1000000L, 120 | uiOutput.tokens(0)._1 == _tokenId, 121 | uiOutput.tokens(0)._2.toBigInt == fees(1)._2, 122 | uiOutput.tokens.size == 1 123 | ) 124 | ) 125 | }else{ 126 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 127 | } 128 | }else{ 129 | true // if ui fee isn't defined, then default to true. 130 | } 131 | } 132 | devFeesPaid && uiFeesPaid 133 | } 134 | 135 | sigmaProp(orderMade && feesPaid) 136 | }else{ 137 | // Refund Path 138 | lenderPK 139 | } 140 | 141 | } -------------------------------------------------------------------------------- /contracts/OpenOrderOnCloseToken.ergo: -------------------------------------------------------------------------------- 1 | { 2 | // Open Order SIGUSD/tokens Contract 3 | // Borrowers send their collateralized ERG and/or tokens, along with setting the following registers 4 | // to specify details about the SigmaBond they wish to make 5 | // R4: Borrower's PK: SigmaProp 6 | // R5: Bond Principal (In number of raw / non-decimaled SIGUSD tokens): Long 7 | // R6: Total Repayment (In number of raw / non-decimaled SIGUSD tokens): Long 8 | // R7: Bond Maturity Height: Int 9 | 10 | // Principal represents the amount of tokens the borrower gets immediately upon a Bond order being taken by a lender. 11 | // Total Repayment is the amount of tokens the lender must receive before the maturity height, in order for 12 | // the borrower to not be liquidated 13 | // Bond Maturity Length is duration of Bond, which is starting at open order closing. 14 | // If using length, value must be less than 30 (So 30 block length of bond). 15 | // The collateral for the Bond is the total contents of all assets + ERG within this box 16 | val borrowerPK = SELF.R4[SigmaProp].get 17 | val principal = SELF.R5[Long].get 18 | val repayment = SELF.R6[Long].get 19 | val maturityLength = SELF.R7[Int].get 20 | val totalAssets = SELF.tokens 21 | val totalERG = SELF.value 22 | 23 | // Constants 24 | // _tokenId: Token id of bond currency 25 | // _bondContractHash: Hash of bond contract 26 | // _devPK: PK of dev :^) 27 | 28 | // Output box if open order is closed. 29 | val bondBox = OUTPUTS(0) 30 | val orderIsClosed = _bondContractHash == blake2b256( bondBox.propositionBytes ) 31 | 32 | // Optional UI Fee Address, which may be inserted into context vars 33 | val optUIFee = getVar[SigmaProp](0) 34 | 35 | // We use BigInts to help deal with long overflow on large value bonds 36 | val fees: Coll[(SigmaProp, BigInt)] = { 37 | 38 | val feeDenom = 100000L 39 | val devFee = 500L 40 | // If ui fee is defined, then we add an additional 0.4% fee 41 | if(optUIFee.isDefined){ 42 | val uiFee = 400L 43 | 44 | Coll( 45 | (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt), 46 | (optUIFee.get, (uiFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) 47 | ) 48 | }else{ 49 | Coll( (_devPK, (devFee.toBigInt * principal.toBigInt) / feeDenom.toBigInt) ) 50 | } 51 | } 52 | 53 | if(orderIsClosed){ 54 | // Order Matched / Closed path 55 | val loanBox = OUTPUTS(1) 56 | 57 | val orderMade = { 58 | allOf( 59 | Coll( 60 | // Bond Box Conditions 61 | bondBox.R4[Coll[Byte]].get == SELF.id, 62 | bondBox.R5[SigmaProp].get == borrowerPK, 63 | bondBox.R6[Long].get == repayment, 64 | 65 | bondBox.R8[SigmaProp].isDefined, 66 | bondBox.tokens == totalAssets, 67 | bondBox.value == totalERG, 68 | 69 | // Maturity Conditions 70 | maturityLength >= 30, // All bond orders with duration must at least have duration of 30 blocks 71 | 72 | (HEIGHT + maturityLength) - bondBox.R7[Int].get <= 8, // We allow 8 blocks for a tx spending this box 73 | (HEIGHT + maturityLength) - bondBox.R7[Int].get >= 0, // to be confirmed, and assume maturity height 74 | // of bond box is HEIGHT + maturityLength 75 | // at time of transaction send. 76 | // Should prevent tx's disappearing from 77 | // mempool 78 | 79 | // Loan Conditions 80 | loanBox.propositionBytes == borrowerPK.propBytes, 81 | loanBox.value == 1000000L, 82 | loanBox.tokens(0)._1 == _tokenId, 83 | loanBox.tokens(0)._2 == principal, 84 | loanBox.tokens.size == 1 85 | ) 86 | ) 87 | } 88 | 89 | 90 | 91 | // Ensure that correct fee output boxes exist 92 | val feesPaid = { 93 | 94 | val devFeesPaid = { 95 | if(fees(0)._2 > 0){ // Dev fee is greater than 0 96 | val devOutput = OUTPUTS(2) 97 | allOf( 98 | Coll( 99 | devOutput.propositionBytes == fees(0)._1.propBytes, 100 | devOutput.value == 1000000L, 101 | devOutput.tokens(0)._1 == _tokenId, 102 | devOutput.tokens(0)._2.toBigInt == fees(0)._2, 103 | devOutput.tokens.size == 1 104 | ) 105 | ) 106 | }else{ 107 | true // do nothing if dev fee doesn't add up greater than 0, prevents errors on low value bonds 108 | } 109 | } 110 | 111 | val uiFeesPaid = { 112 | if(optUIFee.isDefined){ 113 | if(fees(1)._2 > 0){ // UI fee is greater than 0 114 | val uiOutput = OUTPUTS(3) 115 | allOf( 116 | Coll( 117 | uiOutput.propositionBytes == fees(1)._1.propBytes, 118 | uiOutput.value == 1000000L, 119 | uiOutput.tokens(0)._1 == _tokenId, 120 | uiOutput.tokens(0)._2.toBigInt == fees(1)._2, 121 | uiOutput.tokens.size == 1 122 | ) 123 | ) 124 | }else{ 125 | true // do nothing if ui fee doesn't end up greater than 0, prevents errors on low value bonds 126 | } 127 | }else{ 128 | true // if ui fee isn't defined, then default to true. 129 | } 130 | } 131 | devFeesPaid && uiFeesPaid 132 | } 133 | 134 | sigmaProp(orderMade && feesPaid) 135 | }else{ 136 | // Refund Path 137 | borrowerPK 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /src/test/scala/bonds/BondTestSuite.scala: -------------------------------------------------------------------------------- 1 | package ksingh.sigmabonds 2 | package bonds 3 | 4 | import ksingh.sigmabonds.bonds.BondGenerator.toInput 5 | import org.scalatest.funsuite.AnyFunSuite 6 | import ksingh.sigmabonds.bonds.TestHelper._ 7 | import org.ergoplatform.appkit.{ErgoId, ErgoToken, Parameters} 8 | import org.ergoplatform.appkit.scalaapi.ErgoValueBuilder 9 | import sigmastate.eval.CostingSigmaDslBuilder.Colls 10 | 11 | 12 | class BondTestSuite extends AnyFunSuite{ 13 | test("Make Bond Order"){ 14 | 15 | client.execute{ 16 | ctx => 17 | val out = BondGenerator.makeOrderBox(ctx, Parameters.OneErg * 5, Seq(), Some(fakeAsset), 18 | isFixed = true, seller, 5000L, 10000L, 137178) 19 | 20 | val in = toInput(walletBox(ctx, seller, Parameters.OneErg*10)) 21 | 22 | val tx = ctx.newTxBuilder() 23 | .addInputs(in) 24 | .addOutputs(out) 25 | .fee(Parameters.MinFee) 26 | .sendChangeTo(seller.address) 27 | .build() 28 | 29 | val sTx = seller.prover.sign(tx) 30 | 31 | println(sTx.toJson(true)) 32 | //ctx.sendTransaction(sTx) 33 | 34 | } 35 | 36 | } 37 | 38 | test("Make Bond Offer"){ 39 | client.execute{ 40 | ctx => 41 | val out = BondGenerator.makeOfferBox(ctx, Parameters.OneErg * 5, Seq(), None, true, buyer, Parameters.OneErg * 6, 42 | Parameters.OneErg, Seq(new ErgoToken(fakeAsset, 1L)),137178) 43 | 44 | val in = toInput(walletBox(ctx, buyer, Parameters.OneErg*10)) 45 | 46 | val tx = ctx.newTxBuilder() 47 | .addInputs(in) 48 | .addOutputs(out) 49 | .fee(Parameters.MinFee) 50 | .sendChangeTo(buyer.address) 51 | .build() 52 | 53 | val sTx = buyer.prover.sign(tx) 54 | 55 | println(sTx.toJson(true)) 56 | //ctx.sendTransaction(sTx) 57 | 58 | } 59 | } 60 | 61 | test("Close Bond Offer"){ 62 | client.execute{ 63 | ctx => 64 | 65 | val offerBox = toInput(BondGenerator.makeOfferBox(ctx, Parameters.MinFee, Seq(new ErgoToken(fakeCurrency, 1)), Some(fakeCurrency), true, buyer, Parameters.OneErg * 6, 66 | Parameters.OneErg, Seq(new ErgoToken(fakeAsset, 1L)),137178)) 67 | //val orderBox = ctx.getBoxesById("a360ed9afc76ee0cecaa628aa5639bfb8946e114ca11d5982e3a7b2e8143093e").head 68 | 69 | val borrowerInput = toInput(walletBox(ctx, seller, 2 * Parameters.OneErg, Seq(new ErgoToken(fakeAsset, 1L)))) 70 | 71 | val outBondBox = BondGenerator.makeBondBox(ctx, Parameters.OneErg, Seq(new ErgoToken(fakeAsset, 1L)), Some(fakeCurrency), isFixed = true, 72 | offerBox.getId, seller, buyer, Parameters.OneErg * 6, 137178) 73 | 74 | val outLoan = walletBox(ctx, seller, Parameters.MinFee, Seq(new ErgoToken(fakeCurrency, 1))) 75 | 76 | // val outFee = walletBox(ctx, dev, (0.005 * Parameters.OneErg * 5).toLong, Seq()) 77 | 78 | val tx = ctx.newTxBuilder() 79 | .addInputs(offerBox, borrowerInput) 80 | .addOutputs(outBondBox, outLoan) 81 | .fee(Parameters.MinFee) 82 | .sendChangeTo(seller.address) 83 | .build() 84 | 85 | val signTx = seller.prover.sign(tx) 86 | 87 | println(signTx.toJson(true, true)) 88 | 89 | //ctx.sendTransaction(signTx) 90 | } 91 | } 92 | 93 | 94 | 95 | // Tx Id: 086c43ff415cd735617247bb8e237567c994662e5399cb615bda5d27bf2e24cc - Testnet 96 | test("Closing Bond Order"){ 97 | client.execute{ 98 | ctx => 99 | 100 | val orderBox = toInput(BondGenerator.makeOrderBox(ctx, Parameters.OneErg * 10, Seq(), Some(fakeAsset), 101 | isFixed = true, seller, 5000L, 10000L, 137178)) 102 | //val orderBox = ctx.getBoxesById("a360ed9afc76ee0cecaa628aa5639bfb8946e114ca11d5982e3a7b2e8143093e").head 103 | 104 | val lenderInput = toInput(walletBox(ctx, buyer, 5 * Parameters.OneErg, Seq(new ErgoToken(fakeAsset, 5050L)))) 105 | 106 | val outBondBox = BondGenerator.makeBondBox(ctx, Parameters.OneErg / 4, Seq(), Some(fakeAsset), isFixed = true, 107 | ErgoId.create("a360ed9afc76ee0cecaa628aa5639bfb8946e114ca11d5982e3a7b2e8143093e"), seller, buyer, 10000L, 137178) 108 | 109 | val outLoan = walletBox(ctx, seller, Parameters.MinFee, Seq(token(fakeAsset, 5000L))) 110 | 111 | val outFee = walletBox(ctx, dev, Parameters.MinFee, Seq(token(fakeAsset, (5000L * 500) / 100000L))) 112 | 113 | val tx = ctx.newTxBuilder() 114 | .addInputs(lenderInput) 115 | .addOutputs(outBondBox, outLoan, outFee) 116 | .fee(Parameters.MinFee) 117 | .sendChangeTo(buyer.address) 118 | .build() 119 | 120 | val signTx = buyer.prover.sign(tx) 121 | 122 | println(signTx.toJson(true, true)) 123 | 124 | //ctx.sendTransaction(signTx) 125 | } 126 | 127 | } 128 | 129 | test("Liquidating Bond"){ 130 | client.execute{ 131 | ctx => 132 | val bondBox = toInput(BondGenerator.makeBondBox(ctx, Parameters.OneErg / 4, Seq(), Some(fakeAsset), isFixed = true, 133 | ErgoId.create("a360ed9afc76ee0cecaa628aa5639bfb8946e114ca11d5982e3a7b2e8143093e"), seller, buyer, 10000L, 137178)) 134 | val changeBox = toInput(walletBox(ctx, buyer, 5*Parameters.OneErg, Seq(new ErgoToken(fakeAsset, 10000L)))) 135 | val outLiquidated = ctx.newTxBuilder().outBoxBuilder() 136 | .value(Parameters.OneErg / 4) 137 | .contract(buyer.address.toErgoContract) 138 | .registers( 139 | ErgoValueBuilder.buildFor(Colls.fromArray(bondBox.getId.getBytes)) 140 | ) 141 | .build() 142 | 143 | val tx = ctx.newTxBuilder() 144 | .addInputs(bondBox, changeBox) 145 | .addOutputs(outLiquidated) 146 | .fee(Parameters.MinFee) 147 | .sendChangeTo(buyer.address) 148 | .build() 149 | 150 | val sTx = buyer.prover.sign(tx) 151 | 152 | println(sTx.toJson(true, true)) 153 | 154 | // ctx.sendTransaction(sTx) 155 | 156 | } 157 | 158 | } 159 | 160 | test("Closing Bond"){ 161 | client.execute{ 162 | ctx => 163 | val bondBox = toInput(BondGenerator.makeBondBox(ctx, Parameters.OneErg / 4, Seq(), Some(fakeAsset), isFixed = true, 164 | ErgoId.create("a360ed9afc76ee0cecaa628aa5639bfb8946e114ca11d5982e3a7b2e8143093e"), seller, buyer, 10000L, 10000000)) 165 | val changeBox = toInput(walletBox(ctx, seller, 5*Parameters.OneErg, Seq(new ErgoToken(fakeAsset, 10000L)))) 166 | val outClosed = ctx.newTxBuilder().outBoxBuilder() 167 | .value(Parameters.MinFee) 168 | .contract(buyer.address.toErgoContract) 169 | .tokens(new ErgoToken(fakeAsset, 10000L)) 170 | .registers( 171 | ErgoValueBuilder.buildFor(Colls.fromArray(bondBox.getId.getBytes)) 172 | ) 173 | .build() 174 | 175 | val outCollat = walletBox(ctx, seller, Parameters.OneErg / 4) 176 | 177 | val tx = ctx.newTxBuilder() 178 | .addInputs(bondBox, changeBox) 179 | .addOutputs(outClosed, outCollat) 180 | .fee(Parameters.MinFee) 181 | .sendChangeTo(buyer.address) 182 | .build() 183 | 184 | val sTx = seller.prover.sign(tx) 185 | 186 | println(sTx.toJson(true, true)) 187 | 188 | // ctx.sendTransaction(sTx) 189 | 190 | } 191 | 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /SigmaBonds.MD: -------------------------------------------------------------------------------- 1 | # SigmaBonds 2 | 3 | SigmaBonds is a decentralized, P2P bond protocol built on the Ergo blockchain. 4 | SigmaBonds allows borrowers to create bond requests in which they set the 5 | maturity date, APY, principal amount, and collateral. Lenders may then view 6 | all the available bond requests on the blockchain in order to decide where they 7 | wish to place their money. SigmaBonds may be collateralized by *any* asset available 8 | on the Ergo blockchain. That includes ERG, tokens (like SIGUSD, Ergopad, Neta, etc.), and 9 | NFTs. Moreover, borrowers may use any combination of the above in a single bond, allowing 10 | for more flexible collateralization methods. 11 | 12 | 13 | ## Utility 14 | SigmaBonds can be used for a variety of different purposes, including the raising of funds, 15 | access to quick liquid assets, improvements to mining rigs/farms, 16 | arbitrage opportunities, and many other unique DeFi usecases. For lenders, SigmaBonds 17 | can be used to slowly gain yield on ERG or a native asset of their choice. Flexibility in collateralization also allows 18 | for lenders to have more options when evaluating loans (e.g. A lender may be willing to take less collateral 19 | from entities they deem to be "trusted". Borrower identities can be verified via Public Key or ErgoNames). 20 | 21 | ### Extensions: Lender Based Bond Market 22 | We can create extensions of the protocol which allow for lender-based bond markets. During order closing, 23 | lender can mint an NFT, and set the lenderPK on the bond UTXO to be of some contract which allows spending 24 | of the collateral or repayment by spending a box which contains the NFT. In this way, lenders can trade 25 | the NFT to speculate on the value of the bond. 26 | 27 | ## UI 28 | There is no official UI associated with SigmaBonds. Please be sure that any UI 29 | interacting with the SigmaBonds protocol uses known contract addresses and is trusted. Frontend 30 | developers who create a UI for SigmaBonds can get a 0.4% fee for their implementation. 31 | To get the fee, add your address' `SigmaProp` as Context Variable 0 on the Open Order UTXO whenever 32 | an open bond request is taken/spent by a lender. Additionally, add an output box under your address, 33 | whose ERG / token value will contain exactly `(400 * principal) / 100000)` tokens / nanoERGs. 34 | The UI fee output will be `OUTPUTS(3)`. `OUTPUTS(2)` will be the dev fee, with a value of `(500 * principal) / 100000)` 35 | 36 | ## Token 37 | SigmaBonds has no plans to make a token, and likely never will. 38 | 39 | ## Fees 40 | ### Orders 41 | There is a 0.5% developer fee while using SigmaBond Orders, which is taken from the lender when an order is first closed. 42 | An additional 0.4% may be taken by frontend developers who implement a UI for 43 | SigmaBond Orders. 44 | 45 | ### Offers 46 | There is a 0.5% developer fee while using SigmaBond Orders, which is taken from the borrower's loan when an offer is first closed. 47 | An additional 0.5% may be taken by frontend developers who implement a UI for 48 | SigmaBond Offers. 49 | 50 | # Protocol 51 | There are 4 main transactions which must be implemented from a frontend interface 52 | in order to interact with the SigmaBonds protocol. There are also a number of known contract 53 | addresses to interact with. 54 | 55 | ## Contract Addresses 56 | 57 | 58 | ### ERG-based bonds 59 | 60 | Mainnet Bond Contract: 61 | `2f7L4F3Q9eCjdWRmxSENw18Bw5SPAf3vBaimRqgpWB5JayiqSWG2tvnc6kF8ae8mpYwtZasmVDzmgjbfa8EBTdA1u55yB8ypRZDDFhs6DmhQekuGvzBoViApMyKdAXCPriXMaJWgHxAdjtR7QhXSjdnyozxZ7ApXrQY6hDSX6H2Fg9siuGUQpTQ3oJDa8nScMGdLNK2T5A7oHs` 62 | 63 | Mainnet Order Contract (On-Close): 64 | `2jMoa21VMLebD9C3j1jXHBCSZitYqMdcqJt7jSKAVwAsVaKifkspA8jfSeRp2dnnQVgjdsXfRDzo4h1hmroTnPsMAB8qiBnyGjStmMxxFirroejmZFgH25zouApdAZtjTERNrvn67QFPGhGRpxopGEZYrQbpj3PmNR4UPyvKkCHzWbkM7cMA2o45RadU4gY6LAoDwvop35QmPsbP2CuETfJPWUKHgDw87wzwPfpBHYxNht3btUuaNB7ifL1To8KotbFKQyRQ3s84vsbed5abchfWhgSkU6HDvJJM9vmt2axcC5P72jEtPsdK19oGw3FsYZp5S9DpKmQRJasDStTRtgBEbD6vnP6orTrje9oJEpvarFdRE9gG1gQKoQdUn5PtssaVhWG3yX67BSyy1mQVkdPq17CrD4di6mhuakBKi9Wn6YTFCvdeLqfSTAaFJPA3uitsqpSkLxNGyqMMtHZ5oahuZ3jVtx3As5N2ZTt835XUYsFE31n3CzfZPvyHCDtahYTRqeCBvPMZw8vdh3a1XtfqNX15AmQy7Jf8W6Y7McsKQVwG5gogXu6XSSCrGn9r2j9m3DBPitUPpEdNqavvxX5Jqrrp2UNLNNfsZZRT26ifnWWa1W5LnjVQVeRFdki5Haf` 65 | 66 | Mainnet Order Contract (Fixed-Height): 67 | `9drQ9RPn3p6hMfLBBffMoNSXzpa8eFpxz922UjQVKXWL8nyHNkRVhLWoKzkE8xWL9AHHHnh3HgrqjC9XjVu7KR4gL1fnrVWcQcrHiUSAEacsUNvkwFCy9xoG52UTAbj3jJyvkhNQg2h2RV86CniFXEsS6bRDcAdTAN8ytLLMAuE3DcMjgntsXeyVwrGRWYrktmtm6xCVAYnFRJhopAUTmYqGEmE2FnpC9uuqvG2bVR9xvngZJ1SxZzti7XnhsW6pSvMBLbVNcbGCJp3STHkrNU2QGV6WsgcHcif6aSMpnjQ26E1v3LMQ75RkWSU2RYnkKS8bbwQmAPrLTcnsC87Gj4gMTf1Agz62Smh7emvXUVqMYmfCF8yah3WthGsWUJb5jGa2VdAFoB7UyB7ksg4MYBPKX6QhCWsEuNjS2WJY6ZAVPtiU3cJbAmMqpqhY1FExJD4tXA1A7B3yTwFrq2epWdoHuSLewCpubNmJSXn5EVRiW6JN8vz5U1P4Jk4R4sRZBKtz2PFu53HEEgvc1TjGZ8sQA96mh74h26zEA3kWUEv6tP8pyFQYByr9xLARgngVNNXk6Hz8UFyyBt3xNr9kxNtA5hh` 68 | 69 | Mainnet Offer Contract (Fixed-Height): 70 | `JLnFvL2xubxtxwnfkHV8368NCaUvKUoxsmzq1hfo4h2y1J9pbJL5CUhrGVqLFVn4h7GuCs7hVfHayB5P3h2BXS2Xqk3gpX2C2rDjaKQHXgiGB3d9ShGdzn7Nes7h6Fnkiiz3RcvxMb1VEuCdaWyFSBzLBvenbnhfWW7iX2mtnZQGwgH2t3dZvEjnbamyf6RguxbKXKXTxsphZgrVmxnBBwj5QaRuLrSuDxwhBDvKkFw3zRtTSQfzEKxsy52D17yYmifP1qczST5mH5oSNRuKh4FnFTpBpRQvWC1zSPkJTLyA4kvWADLkF4ftWgmAYkPpuU8JqisenhsnRrqg4eoS4mSBMADccJ1NvaksYBCaKY7q8NUMFQwhXYp6e1Z5TvvhKnBVhhmAgDagGeF4jwQWmZsCGS37h1vZ62fWMc2sdprt8Dg2pcbr2ocUjZcAiWLh8aXoNc7h72Q7x1LMWoPgMEpPphsciM62WJqc9YbLthZ7798iZ94uvBoP5mG8JDDCqza5ssZoAb2pyLqXNrmKwCPGDdPEGX3h9oVoK7uWhKH2LEY6cxZA29TUGjWqCYvVQphtTQBU17vVQPLcbUDm` 71 | 72 | Testnet Bond Contract: 73 | `4TwNASK7pbxYLmVWa2Xo7BSpfq2aiuLtYX7XWtA3Y1iwSZU8a4NpViuVQakJmtUqqi7YX5T6B58C9TFgjhx1FNHVR4UxwXzVR22haFAhMT16eg1HyZkJpRHbhaFpaZ6Kbz3BLYtqR6oVhKvrmBkzv69St6vGjG9BekxfevL8AXVwTSGFRTnP7DNMYZLESKSLJuwGThLoWqU4ouC6J` 74 | 75 | Testnet Order Contract (On-Close): 76 | `BmdoLSSvWwhomZRj9YeShBGzjLfTvaojnCtxciMHS4KGFCChagixkFRfc4XFtA8C2dBsQuqe6UUBgCHN8HNYSPVgB7PHRhfXzhCD1WoB9jHzyECzPzN1GN1PBep9BNvN8QbAUsZFXEPi5uQuxHJvPnyYBrZocSZUrQ9s8eVeerW3vgWcvaTZHEY4faHewxPcwcxvsiPFa2rPCp1xCQu63z5Acjm9eeJSpDNxyBmjNdr2VwThCJKrWTr3H4t4qM71DzoCewKeibfRN1gNK6oPAk9EN63t1ova2efbexNz4PHzDfUUtjWZ1scbeHKrNnb6kqSwjurFKamXyGbUHhTnzqQHnks3KL87f6Z5Gwk5RCagHmeiNeJQfn15yCbCwRgtAK2RQxKmW2sbmuZSVsmSZztwj8KCFuRRn8VwMRP7cPbqi8HikpX3NU57rGApMFsjKpafhaWkjgCGfhiVnvrVo4ys1DsTsakLHP4NGyd6zUJG2zk42PaTTm7s6wd4mxrZu3hLNzLy9G22GZk29A2f4BRhFze4WKsBADNaRao8i4kAZjbY6avEVkUbvvbiG5KWnaPTSu6GTx3XZZj7K3ae6N6Zvegu1DXhienFNPKaX3dj5YsE6PoWK85LtYSwioLYzJU` 77 | 78 | Testnet Order Contract (Fixed-Height): 79 | `vkMLPhPPSJhewd4uLSmBLvxuBWgFn2sAChDEX7TmfbHeuv42oViHvAwvzsADbRDUM6ywXF8DUp3Wjk8robmwNBctVSX9cucuV1WskPmpYDCkP9bbLkeqKN1SRs4HutheGNjda5TWfXEdwZqiMs6LXQVe168kBX8bn3LiNyifuMsJst4jQJKFVu3aCJRG4ueLr6eUKCKKNv5JhTJPAtRXzgauwps4SLtUyyALoe8brQWPCPuVoSFVaSPop5fzxt4vaLfL1pGUmmRQnrxpiiLNEC7u1LD5nYkMcZjGmRsM89fbW3wxwwAQw71MEmdMpudLkoRz1uN8eU4ABzqAJEqtFS1fDvM82tuhQSnFF8ZPa3ux8q21MRZhBp6hhP3XHD1cdQXjZ5tf6VdKoo4GKZZPZk7oAD715nPFFqoAhbtcbXbc6pfvS4RJdUMk74TbkxQEAzFmsjDtxwNUTrHM62SnRELjMqoAtgfQWSVWqKiLug49gpELkEBQw7Ehr576oXQjZXHLmmprxuk3W3sU1tUBzbCNzAMJUVkoFa5v7aMQxkuqyYgywFBJV7CJb4nLpyECrxwP8aAMMnQSvGoxXFsrFHa9oRv` 80 | 81 | ### SIGUSD-based bonds: 82 | Mainnet Bond Contract: 83 | `47r8CNpYJhLaJy9vQAyyhVX7SLu73dg8EDmi9zzei7YWomvTAbNaZMAHdM38TsFoiZfAcKuyrgngD6ZS2uPQktLfFpvypxkRiRi9LswRYd5tk6B5HHDsFNMfLcdqeWT9RDR2SRq1zm2HF9F913aY1gc9gVyeh8PGED2ThKJ2NCG19XhyPqCbgTFY5uTC6RaqpGCdH9p58fD4DWDd46D3EfUXz3XLzqGQvDGXTghkh9UtZ1LB7nFFoPDFc2QVDt6BCtTQwq4Jh9vFfTfBG9q6ReVF5cVX7nA6vXhWjUuHKMd7Zw5anM2u95e` 84 | 85 | Mainnet Order Contract (On-Close): 86 | `3FdyuY8fqeTtYcsuvTsmsw4ZBGde2cad6hyntGxzSY3rd4JtTwTvK64xp3XJZvYNTijrTMZMQxkFiULuHpq46VFbAiSSHXLe1qy5WXNRELj4tcx6wEPbSDqzDyVbY6wKFQhmvYQT2XjqLJAiBGV9F6f2uXrjXFPzPnXpVeJnWc8ob1wMqbfgBQKdfu24qx3pejkAfAz2hmQQ57xgzGwHCcGyJdad7UxwmDetAUER8iwE32rygGCmqXaVeKGcJ33epYNGpDLPnYUg3Qz7eYuHHb7mAqwkEp6XGE1jWbf8nS7TA1taNpakssbWYN2YmB9NBG8nWNzLhc9QBdqyAn9WQ63Jh1znt2up6sQJR7xnon9ZsYoeo7bErzg4uoJbTLzwjhZBJ9En9bMX72xUb5zhvSSua6CseMMA6s86g2mwrieRwqFM7CGDypy78MzWS1nagUBKLet8LAFFpb6DKBK1h2HpAvLnMbWB7Kp3rJ4be6ed5crNdZTqhbSww96P2KNSZemhwfgAkUFjMJzB2dEG9VNAW1dMk5nBrS8JsRHhYpPqFcJiFtQvcar1ijs2PqsR9k4P41Y99iomfYXkSoA6z612v1U1C19ASVurzuxGP1vNwNyKLceFVTYXbgkgUFX8Z2nwMz6d2TCa4XTpt9daV5sBV88YJgCiYbL5QhxnoTdYRZbxu725jnV7gbnX9FWjLNaXYJubi5u8kPs3MYzSALeUvtfx6wJV1XyBn5DMbvy8XgFFMKyxtUrfU3dAVaZmUZJnYy8Dz6qrf8tyKx3uU7RfosjV7rcAFRDpozznWRfsxZUir5EzcVFQc9NbjB4HdgJxxvUJ9Nr9F8XCQLL89wCTtaNZBFF284qLtrQ` 87 | 88 | Mainnet Order Contract (Fixed-Height): 89 | `CExp7TDXwCNoFe5Ad3Mbiwkwd5Y9y9JmKPftycdMuwfUUy8WbaAfAr6kjSQWTK53LPrvEemmdnwMXWrKYpfhhnPMfFEA1c8nSdxdZ93V2HTMtKLFPscG4cwUCiw7Xg4g31UbLT2PkTUtkRBqsaiEUn8mU1hf5jNUoB9DwzbcBVaoHn7xWS3wrTFYJFU4bdD3Qstm88Yj3YZ86iC8Sq31ScQbCP7xrvyZPk7tAWhJehT7Hq5LWwHHeeGifjK4AEhXQe4qgzdGphPntYpySZcd94eCLbdB91YzfgirtkVBQaxA24WyX2Hxq5iwUha3JM5WCxkvoxEFZdujNVncsiafRWsvinAgAJAgk4SK55CgqzETo4smphqUVKzZYgYxGTcWfhqRh31Ly8BXwCc3EywRwPbSe56PjoJmXidcoY57CbG2LUutnDnQf2Q9jMCfGnmbgXDrz4mJNuw8wDaZXtkcHFKkTMLpB2xfJQgS2NtVsRxHL5qFrrrP9f18meiiE4rE1cSWzxSqJfxriyfho91j8xDJiLg1moaZuwnN9V5LQ4uLJ8u3wyxSfV8xRzufudRHbJ8L936G9rZ3RybJeFL35SBwnkZ3jfWnmKb9xRoyYicHHVmLRB8Z4bja9GeNk1EnerTasv7gTHrGDKgsdkLYN4Atg9NqTbbnyM8gTyn6wbWetxwMHNoeMHnA63kCceuS4xM9wrD1U6dXQ9CJCS9myxWyD4sewYYB3n8ZR9LiEpu3iNAExngi3xaGT4aKhACjbfTr76d1nK8fvPrbCGn6nJL7fLN6ApPg53WznkQ6jM1crZk5vv4MNac5YwfK6ra` 90 | 91 | Mainnet Offer Contract (Fixed-Height): 92 | `zXwrGkN9w6ErQEsXFBc4WhatY2PH2dBECTjBJ7qxEy2FNW4hy6UHRg4sGVYoxTZPNPTPhrT36RAF7JJR9K3bTQkZK5837iqMFr9ca4eeG1UpWmUkDaQSfu531bpvV9wuYk1mLuWqStgdXo87D9UvF7pk6RqwHYsY3efvbWYVUDBMM6um2dEaxBqXLVVzvuBSdseEXwy3d3UaBJDHamHPaoJbvLTzWNzzvXpL4YRR1XGDkeiWeWyByq8s152ZP1WzVMbzeJUQwfGdhaeAscr3F2ktJxFV4ED77BYu7X4mntmhUirdZwjH4juez2S5BfVv6ZkTvfuXkAV5pL5wVqHHVbuZXcwJZRLPWWNPvHL6BCSHAxnkSs51DCWhuoc92ikmeWTJx5Fzeqm4Wd826Lfqdk95MKF6ogiqiqwTD7AA5y4zhKnBkAGYFDuSmrMdLPN3th26U2JEH77Cqe8M2z9pqVoj7gmkMuCTzVuBX8v4Qd8TyX7deR6qBdZeeQZSz1CSaiutcJvUzUFbMtLyanqvu3USFKszWHsWkGXjCJqdEEFSgkm2P21cq87orseH17HuXrmGNEehNvoaCYsYoLUTjhS6GB4kXWY7EEvqsFbc1s4eMuVJ2x1fz4BKaZmUVVyhDPYRWFmxJPmk8psXbRvjvCJCWQSFK7YQSa4GrTciAALsTj6AVV2aH25dSdFXPg6ZCGsDGDfK38UUniD43pCFkwaAKYTorXEZeLbB6vLKNEe6LMoJnag64JcB5V2EJ4jhC1Zww2AZmugifT3k1ZFFYvgBbNtbSbpEnSWkKtZSkLmvqA1WTGQDSeRoBCoxFfyo3j3L9Z3Wct8xfEJyZhgDBN5seUjKt3iQToJ2ELrzP7egehUVYn3EaRCDG5ThfLDpiC9jtrQwnkR9B2yUdGkKkV` 93 | 94 | ## Tx 1A: Open Bond Order 95 | In this transaction, a borrower wishes to create a new bond request / order on the 96 | blockchain. The borrower will send all the collateral (assets + ERG) they wish 97 | to use for their bond to a utxo under the bond order contract. On this outputted UTXO, they 98 | will set the following registers to record information about their potential bond. 99 | - R4: Borrower's PK: `SigmaProp` 100 | - R5: Bond Principal (In tokens / nanoERGs): `Long` 101 | - R6: Total Repayment (In tokens / nanoERGs): `Long` 102 | - R7: Maturity: `Int` 103 | 104 | Bond Maturity comes in two different forms: 105 | If order is of "OnClose" type, then it represents bond which expires a number of blocks after 106 | closing of the order. In this case, value must be greater than or equal to 30 (meaning minimum duration of 30 blocks). 107 | 108 | ## Tx 1B: Open Bond Offer 109 | In this transaction, a lender wishes to create a new bond offer on the 110 | blockchain. The lender will send the total amount of ERG or quantity of a specific (singular) token to a utxo under the 111 | `OpenOffer` contract. This will be used as the loan to the borrower upon offer closure. In the case of a token offer, the utxo MUST have a value of `0.001 ERG`. On this outputted UTXO, they 112 | will set the following registers to record information about their potential bond. 113 | - R4: Lender's PK: `SigmaProp` 114 | - R5: Bond Collateral ERG (In nanoERGs): `Long` 115 | - R6: Bond Collateral Assets (tokens): `Coll[(Coll[Byte], Long)]` 116 | - R7: Total Repayment (In tokens / nanoERGs): `Long` 117 | - R8: Maturity: `Int` 118 | 119 | Currently, only Fixed Height bonds are available for Bond Offers. 120 | 121 | 122 | ## Tx 2A: Close Bond Order 123 | In this transaction, a lender has found a bond order they wish to fulfill by sending 124 | the principal amount back to the borrower. The lender will first create a 125 | bond contract utxo in `OUTPUTS(0)`. They will set the following registers on it: 126 | - R4: Box id of originating order: `Coll[Byte]` 127 | - R5: borrowerPK: `SigmaProp` 128 | - R6: Total Repayment (In number of nanoERGs / non-decimaled tokens): `Long` 129 | - R7: Bond Maturity Height (Height at which bond may be liquidated): `Int` 130 | - R8: lenderPK: `SigmaProp` 131 | 132 | There are two ways to derive Bond Maturity Height, depending on the `maturity` stored in R7 of the Open 133 | Order UTXO. 134 | 135 | If `maturity` is on an "On-Close" order: 136 | The bond maturity height is derived by taking the current height of the blockchain + `maturity` 137 | on R7 of the Open Order input box. If a close bond tx is not confirmed within 8 blocks after being 138 | sent to the mempool, the transaction may disappear. 139 | 140 | If `maturity` is on a "Fixed Height" order: 141 | Simply copy the value of `maturity` into the bond maturity height (So copying R7 of open order into R7 of 142 | bond utxo). 143 | 144 | At the same time, `OUTPUTS(1)` will have a utxo going to the borrower's PK, which 145 | is holding the total principal amount specified by the borrower in R5 of the order contract. 146 | If the principal is in tokens, the only value of ERG that can be accepted is `0.001` 147 | 148 | ## Tx 2B: Close Bond Offer 149 | In this transaction, a borrower has found a bond offer they wish to fulfill by claiming the loan present in the offer utxo, 150 | and then sending their required collateral into an outputted bond UTXO. The borrower will create a 151 | bond contract UTXO in `OUTPUTS(0)`. They will set the following registers on it: 152 | - R4: Box id of originating order: `Coll[Byte]` 153 | - R5: borrowerPK: `SigmaProp` 154 | - R6: Total Repayment (In number of nanoERGs / non-decimaled tokens): `Long` 155 | - R7: Bond Maturity Height (Height at which bond may be liquidated): `Int` 156 | - R8: lenderPK: `SigmaProp` 157 | 158 | To set `maturity`: 159 | Simply copy the value of `maturity` into the bond maturity height (So copying R7 of open order into R7 of 160 | bond utxo). 161 | 162 | At the same time, `OUTPUTS(1)` will have a utxo going to the borrower's PK, which 163 | is holding the total principal amount (with UI & Dev Fees subtracted) as specified by the contents of the offer UTXO. 164 | If the principal is in tokens, the only value of ERG that can be accepted is `0.001` 165 | ## Tx 3: RePay Bond Order 166 | Once a bond order has been fulfilled / closed, a bond utxo with specific information then exists on the blockchain. If the 167 | borrower is able to get the repayment value in time before the maturity height is reached, they may perform 168 | a repayment transaction. In this transaction, the repayment value is sent to the lender (must have `0.001` ERG in it if it 169 | is a token-based bond) in `OUTPUTS(0)`. The collateral (Assets + ERG) held by the bond utxo is then returned to the borrower in 170 | `OUTPUTS(1)`. 171 | 172 | On `OUTPUTS(0)`, the following registers exist: 173 | - R4: Box id of originating bond UTXO 174 | 175 | 176 | Only the borrower may sign and initiate this transaction. 177 | ## Tx 4: Liquidate Bond Collateral 178 | In the case that a borrower does not repay their bond order by the maturity height, the lender may 179 | sign and initiate a liquidation transaction. In this transaction, the total amount of collateral held in 180 | the bond utxo is all sent to the lender's PK in `OUTPUTS(0)`. 181 | On `OUTPUTS(0)`, the following registers exist: 182 | - R4: Box id of originating bond UTXO 183 | 184 | 185 | ## KYA / Note To Users 186 | Lenders should always be wary to ensure that the bond orders they accept match 187 | their own personal risk/reward ratio. Lenders of NFT-backed loans should also 188 | be sure that the NFTs used as collateral are real and minted by their original artists. 189 | The creator of SigmaBonds is not responsible for the gain/loss of any digital 190 | assets and/or perceived value incurred through the use of SigmaBonds. --------------------------------------------------------------------------------