Skip to content

Commit

Permalink
Add regular sat bonds.
Browse files Browse the repository at this point in the history
  • Loading branch information
msinkec committed Dec 4, 2023
1 parent 0afae6c commit e8349a8
Show file tree
Hide file tree
Showing 2 changed files with 336 additions and 0 deletions.
179 changes: 179 additions & 0 deletions src/contracts/couponBond.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
assert,
hash256,
method,
prop,
PubKey,
pubKey2Addr,
Sig,
SmartContract,
toByteString,
Utils,
} from 'scrypt-ts'

export class CouponBond extends SmartContract {
@prop()
issuer: PubKey

@prop(true)
investor: PubKey

@prop(true)
forSale: boolean

// Price of the bond in satoshis.
@prop(true)
price: bigint

@prop()
faceValue: bigint

@prop()
interestRate: bigint // 1n == 1%

@prop()
matureTime: bigint

constructor(
issuer: PubKey,
faceValue: bigint,
price: bigint,
interestRate: bigint,
matureTime: bigint
) {
super(...arguments)
this.issuer = issuer
this.investor = PubKey(
toByteString(
'0000000000000000000000000000000000000000000000000000000000000000'
)
)
this.faceValue = faceValue
this.interestRate = interestRate
this.matureTime = matureTime
this.forSale = true
this.price = price
}

@method()
public buy(newInvestor: PubKey) {
const prevInvestor = this.investor

// Set new investor.
this.investor = newInvestor

// Toggle for sale flag.
this.forSale = false

let outputs = toByteString('')
const alreadyOwned =
this.investor ==
PubKey(
toByteString(
'0000000000000000000000000000000000000000000000000000000000000000'
)
)
if (alreadyOwned) {
// Pay previous investor.
outputs += this.buildStateOutput(this.ctx.utxo.value)
outputs += Utils.buildAddressOutput(
pubKey2Addr(prevInvestor),
this.price
)
} else {
// Deposit to contract.
outputs += this.buildStateOutput(this.ctx.utxo.value + this.price)
}

// Enforce outputs.
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public makePayment(issuerSig: Sig) {
// Check issuer signature.
assert(this.checkSig(issuerSig, this.issuer), 'invalid sig issuer')

let outputs = this.buildStateOutput(this.ctx.utxo.value)

// Ensure investor gets payed interest.
const interest = (this.faceValue * this.interestRate) / 100n
outputs += Utils.buildAddressOutput(
pubKey2Addr(this.investor),
this.faceValue
)
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public mature(issuerSig: Sig) {
// Check issuer signature.
assert(this.checkSig(issuerSig, this.issuer), 'invalid sig issuer')

// Check mature time passed.
assert(this.timeLock(this.matureTime), 'bond not matured')

// Ensure investor gets payed face value of the bond.
let outputs = Utils.buildAddressOutput(
pubKey2Addr(this.investor),
this.faceValue
)
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public listForSale(price: bigint, investorSig: Sig) {
// Check investor signature.
assert(
this.checkSig(investorSig, this.investor),
'invalid sig investor'
)

// Set price and toggle for sale flag.
this.price = price
this.forSale = true

// Propagate contract.
let outputs = this.buildStateOutput(this.ctx.utxo.value)
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public cancelSale(investorSig: Sig) {
// Check investor signature.
assert(
this.checkSig(investorSig, this.investor),
'invalid sig investor'
)

// Toggle for sale flag.
this.forSale = false

// Propagate contract.
let outputs = this.buildStateOutput(this.ctx.utxo.value)
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public default(investorSig: Sig) {
// After default deadline is reached the investor can
// take everything locked within the smart contract...

// Check investor signature.
assert(
this.checkSig(investorSig, this.investor),
'invalid sig investor'
)

// Check mature time + ~14 days.
assert(
this.timeLock(this.matureTime + 20160n),
'deadline for default not reached'
)
}
}
157 changes: 157 additions & 0 deletions src/contracts/zeroCouponBond.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
assert,
hash256,
method,
prop,
PubKey,
pubKey2Addr,
Sig,
SmartContract,
toByteString,
Utils,
} from 'scrypt-ts'

export class ZeroCouponBond extends SmartContract {
@prop()
issuer: PubKey

@prop(true)
investor: PubKey

@prop(true)
forSale: boolean

// Price of the bond in satoshis.
@prop(true)
price: bigint

@prop()
faceValue: bigint

@prop()
matureTime: bigint

constructor(
issuer: PubKey,
faceValue: bigint,
price: bigint,
matureTime: bigint
) {
super(...arguments)
this.issuer = issuer
this.investor = PubKey(
toByteString(
'0000000000000000000000000000000000000000000000000000000000000000'
)
)
this.faceValue = faceValue
this.matureTime = matureTime
this.forSale = true
this.price = price
}

@method()
public buy(newInvestor: PubKey) {
const prevInvestor = this.investor

// Set new investor.
this.investor = newInvestor

// Toggle for sale flag.
this.forSale = false

let outputs = toByteString('')
const alreadyOwned =
this.investor ==
PubKey(
toByteString(
'0000000000000000000000000000000000000000000000000000000000000000'
)
)
if (alreadyOwned) {
// Pay previous investor.
outputs += this.buildStateOutput(this.ctx.utxo.value)
outputs += Utils.buildAddressOutput(
pubKey2Addr(prevInvestor),
this.price
)
} else {
// Deposit to contract.
outputs += this.buildStateOutput(this.ctx.utxo.value + this.price)
}

// Enforce outputs.
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public mature(issuerSig: Sig) {
// Check issuer signature.
assert(this.checkSig(issuerSig, this.issuer), 'invalid sig issuer')

// Check mature time passed.
assert(this.timeLock(this.matureTime), 'bond not matured')

// Ensure investor gets payed face value of the bond.
let outputs = Utils.buildAddressOutput(
pubKey2Addr(this.investor),
this.faceValue
)
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public listForSale(price: bigint, investorSig: Sig) {
// Check investor signature.
assert(
this.checkSig(investorSig, this.investor),
'invalid sig investor'
)

// Set price and toggle for sale flag.
this.price = price
this.forSale = true

// Propagate contract.
let outputs = this.buildStateOutput(this.ctx.utxo.value)
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public cancelSale(investorSig: Sig) {
// Check investor signature.
assert(
this.checkSig(investorSig, this.investor),
'invalid sig investor'
)

// Toggle for sale flag.
this.forSale = false

// Propagate contract.
let outputs = this.buildStateOutput(this.ctx.utxo.value)
outputs += this.buildChangeOutput()
assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

@method()
public default(investorSig: Sig) {
// After default deadline is reached the investor can
// take everything locked within the smart contract...

// Check investor signature.
assert(
this.checkSig(investorSig, this.investor),
'invalid sig investor'
)

// Check mature time + ~14 days.
assert(
this.timeLock(this.matureTime + 20160n),
'deadline for default not reached'
)
}
}

0 comments on commit e8349a8

Please sign in to comment.