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_price
btc are added:- send funds +
strike_price
to 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 // then! we do not require a 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!(then, Self::expires, Self::strikes); declare!(non updatable); } }
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_into
field which sends funds to one party or the other based on a third-party revealing a hash preimageA
orB
. - Modify the contract so that there is a
expire_A
and aexpire_B
path that go to different addresses, andexpire_A
requires a signature or hash reveal to be taken. - Modify the contract so that if
expire_A
is taken, a small payoutearly_exit_fee: bitcoin::Address
is made to aearly_exit : bitcoin::Address
. - Modify the contract so that
expire_A
is only present the fields required by it areOption::is_some
(hint: usecompile_if!
). - Add logic to deduct fees.
- Add a
cooperative_close
guard!
clause that allows both parties to exit gracefully