Sapio for Fun (and Profit)
In this section, we’re going to build a simple option contract. This sort of contract could be used, for example, to make an on-chain asynchronous offer to someone to enter a bet with you.
Then, you’ll have some challenges to modify the contract to extend it’s functionality meaningfully.
The logic for the basic contract is as follows:
- If \(\tau_{now} > \tau_{timeout} \):
- send funds to return address
- If
strike_pricebtc are added:- send funds +
strike_priceto strike_into contract
- send funds +
#![allow(unused)]
fn main() {
/// The Data Fields required to create a on-chain bet
pub struct UnderFundedExpiringOption {
/// How much money has to be paid to strike the contract
strike_price: Amount,
/// if the contract expires, where to return the money
return_address: bitcoin::Address,
/// if the contract strikes, where to send the money
strike_into: Box<dyn Compilable>,
/// the timeout (as an absolute time) when the contract should end.
timeout: AnyAbsTimeLock,
}
impl UnderFundedExpiringOption {
#[then]
/// return the funds on expiry
fn expires(self, ctx: Context) {
ctx.template()
// set the timeout for this path -- because it is using
// a committed action needs no separate timelock guard.
.set_lock_time(self.timeout)?
.add_output(
// ctx.funds() knows how much money has been sent to this contract
ctx.funds(),
// this bootstraps an address into a contract object
&Compiled::from_address(self.return_address.clone(), None),
None,
)?
.into()
}
/// continue the contract
#[then]
fn strikes(self, ctx: Context) {
let tmpl = ctx.template().add_amount(self.strike_price);
let amt = (tmpl.ctx().funds() + self.strike_price).into();
tmpl.add_sequence()
.add_output(
// use the inner context of tmpl because it has added funds
amt,
self.strike_into.as_ref(),
None,
)?
.into()
}
}
impl Contract for UnderFundedExpiringOption {
declare!(actions, Self::expires, Self::strikes);
}
}
Challenges
There’s no right answer to the following challenges, and the resulting contract may not be too useful, but it should be a good exercise to learn more about writing Sapio contracts.
- Clear out your helloworld plugin and put this code in.
- Write a contract designed to be put into the
strike_intofield which sends funds to one party or the other based on a third-party revealing a hash preimageAorB. - Modify the contract so that there is a
expire_Aand aexpire_Bpath that go to different addresses, andexpire_Arequires a signature or hash reveal to be taken. - Modify the contract so that if
expire_Ais taken, a small payoutearly_exit_fee: bitcoin::Addressis made to aearly_exit : bitcoin::Address. - Modify the contract so that
expire_Ais only present the fields required by it areOption::is_some(hint: usecompile_if!). - Add logic to deduct fees.
- Add a
cooperative_closeguard!clause that allows both parties to exit gracefully