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:

  1. If \(\tau_{now} > \tau_{timeout} \):
    • send funds to return address
  2. If strike_price btc are added:
    • send funds + strike_price to strike_into contract

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 {
    /// return the funds on expiry
    fn expires(self, ctx: Context) {
            // set the timeout for this path -- because it is using
            // then! we do not require a guard.
                // ctx.funds() knows how much money has been sent to this contract
                // this bootstraps an address into a contract object
                &Compiled::from_address(self.return_address.clone(), None),
    /// continue the contract
    fn strikes(self, ctx: Context) {
        let tmpl = ctx.template().add_amount(self.strike_price);
        let amt = (tmpl.ctx().funds() + self.strike_price).into();
                // use the inner context of tmpl because it has added funds

impl Contract for UnderFundedExpiringOption {
    declare!(then, Self::expires, Self::strikes);
    declare!(non updatable);


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.

  1. Clear out your helloworld plugin and put this code in.
  2. Write a contract designed to be put into the strike_into field which sends funds to one party or the other based on a third-party revealing a hash preimage A or B.
  3. Modify the contract so that there is a expire_A and a expire_B path that go to different addresses, and expire_A requires a signature or hash reveal to be taken.
  4. Modify the contract so that if expire_A is taken, a small payout early_exit_fee: bitcoin::Address is made to a early_exit : bitcoin::Address.
  5. Modify the contract so that expire_A is only present the fields required by it are Option::is_some (hint: use compile_if!).
  6. Add logic to deduct fees.
  7. Add a cooperative_close guard! clause that allows both parties to exit gracefully