Yield generating commitment contracts tied to Strava activity. Ride your bike, earn yield!
Ride or Die is a commitment contract system built with Ethereum, Strava, DAI, and StakeDAO. It's designed to use both positive and negative incentives to help you set and meet fitness goals.
First, link an ETH address to your Strava account (a popular fitness tracker for cyclists).
Then you can set a goal: a distance in kilometers, a deadline date/time, and a stake. Ride or Die deposits your stake in a StakeDAO vault, where it will earn yield until you complete your goal. If you complete a goal before the deadline, as reported by your Strava activity, you can withdraw your stake plus yield. But if the deadline expires before you've completed your goal, your stake will be liquidated and distributed to other Ride or Die users who complete their goals.
Liquidated stakes go into a vault we call the "dead pool." When a successful staker completes their goal, they earn shares in the dead pool equal to the number of kilometers set in their goal. This is designed to prevent gaming the pool by setting easy goals and harvesting failed stakes—in order to earn your share of liquidated stakes, you have to prove that you put in the miles.
While your goal is active, it's represented by an NFT. In theory, this enables some interesting use cases, like selling your goal token to someone who's confident you'll complete it, or pooling tokens from multiple users at once.
We made extensive use of the Hardhat toolchain for our smart contracts. Mainnet forking is a really fantastic feature. Smart contracts are in Solidity, with a lot of help from OpenZeppelin. We have a few underlying contracts:
The GoalManager handles creating goals, redeeming goals, and liquidating failed goals. It's an ERC721 token, so when you have an active goal, it's represented by an NFT.
The Vault handles locking up stakes in StakeDAO. It's a nontransferable ERC20 that enables users to track their balance and yield until goals are completed.
The Deadpool handles distributing failed stakes back to successful goal setters. It uses an ERC20 token to represent shares.
A simple Oracle writes goal progress data to the chain. We'd like to avoid this by using a more sophisticated oracle system like Chainlink, but we didn't have time for this demo.
Our backend API is a simple Node + Typescript + Express app, but we had to learn about message signing as an auth mechanism in order to link up Strava accounts. It proxies requests to the Strava API and keeps track of links between Strava identity and ETH accounts.
The frontend is React + Typescript (and, uh, written pretty quickly). We also wrote super simple Oracle and Keeper bots in Typescript that handle publishing data to the chain and finding and liquidating failed stakes.
We tried using a Chainlink oracle, but weren't able to get too far with our Hardhat setup. We got tantalizingly close running a local Chainlink node pointed at Hardhat, but seems there are still some tooling challenges to work out. The Chainlink code is still in our repo, though, and we'd love to use something more sophisticated in the future.
We used StakeDAO vaults as the underlying yield generation protocol. Locked stakes go into a StakeDAO vault, and DIE-sd3Crv tokens are convertible to sd3Crv tokens when claiming rewards from the dead pool. In order to zap DAI deposits into sd3Crv, we also integrate with Curve.
As part of our test tooling, we wrote a simple Fountain contract that uses Uniswap to distribute test tokens on forked mainnet. It's super cool to be able to use real protocols as dev tools!