diff --git a/Clarinet.toml b/Clarinet.toml index 440cab9..0a19eb9 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -1,21 +1,19 @@ [project] -name = "sbtc-options" -description = "" +name = 'sbtc-options' +description = '' authors = [] telemetry = true -cache_dir = "./.cache" - -# [contracts.counter] -# path = "contracts/counter.clar" - +cache_dir = './.cache' +requirements = [] +[contracts.sbtc-options] +path = 'contracts/sbtc-options.clar' +clarity_version = 3 +epoch = 3.1 [repl.analysis] -passes = ["check_checker"] -check_checker = { trusted_sender = false, trusted_caller = false, callee_filter = false } +passes = ['check_checker'] -# Check-checker settings: -# trusted_sender: if true, inputs are trusted after tx_sender has been checked. -# trusted_caller: if true, inputs are trusted after contract-caller has been checked. -# callee_filter: if true, untrusted data may be passed into a private function without a -# warning, if it gets checked inside. This check will also propagate up to the -# caller. -# More informations: https://www.hiro.so/blog/new-safety-checks-in-clarinet +[repl.analysis.check_checker] +strict = false +trusted_sender = false +trusted_caller = false +callee_filter = false diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b386b3 --- /dev/null +++ b/README.md @@ -0,0 +1,188 @@ +# sBTC Options Protocol + +A secure, decentralized protocol for Bitcoin options trading on Stacks, leveraging sBTC for seamless Bitcoin integration. This protocol enables trustless options trading with automated settlement, robust price oracle integration, and comprehensive risk management. + +## Overview + +The sBTC Options Protocol is a decentralized financial instrument that allows users to trade Bitcoin options in a trustless manner on the Stacks blockchain. It utilizes sBTC (a Bitcoin wrapper on Stacks) for collateral and settlement, providing a secure and efficient way to participate in options trading. + +## Features + +- **Decentralized Options Trading**: Create and trade Bitcoin CALL and PUT options without intermediaries +- **sBTC Integration**: Seamless integration with Bitcoin through sBTC for collateral and settlement +- **Automated Settlement**: Smart contract-based settlement process +- **Price Oracle Integration**: Reliable price feeds with validity window checks +- **Comprehensive Risk Management**: Collateral ratio enforcement and balance tracking +- **Flexible Parameters**: Configurable fees, collateral ratios, and validity windows + +## Contract Functions + +### Core Trading Functions + +#### `deposit-sbtc` + +- Deposit sBTC as collateral or trading balance +- Parameters: + - `amount`: Amount of sBTC to deposit (must be between MIN_DEPOSIT_AMOUNT and MAX_DEPOSIT_AMOUNT) + +#### `create-option` + +- Create a new options contract +- Parameters: + - `option-type`: "CALL" or "PUT" + - `strike-price`: Target price for the option + - `expiry`: Block height at which the option expires + - `amount`: Size of the option contract + +#### `exercise-option` + +- Exercise an active option contract +- Parameters: + - `option-id`: Unique identifier of the option + +#### `expire-option` + +- Expire an option and return collateral +- Parameters: + - `option-id`: Unique identifier of the option + +### Oracle Functions + +#### `update-btc-price` + +- Update the current BTC price (oracle only) +- Parameters: + - `new-price`: Updated BTC price + +#### `get-current-btc-price` + +- Retrieve the current valid BTC price +- Returns the current price if within validity window + +### Administrative Functions + +#### `set-platform-fee` + +- Update the platform fee (owner only) +- Parameters: + - `new-fee`: New fee in basis points (0-10000) + +#### `set-min-collateral-ratio` + +- Update minimum collateral ratio (owner only) +- Parameters: + - `new-ratio`: New ratio (100-1000) + +#### `set-oracle-address` + +- Update the price oracle address (owner only) +- Parameters: + - `new-oracle`: Principal of the new oracle + +#### `set-price-validity-window` + +- Update price validity window (owner only) +- Parameters: + - `new-window`: New window in blocks (10-1440) + +## System Parameters + +### Limits and Constraints + +- **Maximum Fee**: 100% (10000 basis points) +- **Collateral Ratio Range**: 100% to 1000% +- **Minimum Deposit**: 1000 sBTC units +- **Maximum Deposit**: 100,000,000,000 sBTC units +- **Price Validity**: 10 to 1440 blocks +- **Default Collateral Ratio**: 150% +- **Default Platform Fee**: 0.1% (10 basis points) + +### Error Codes + +| Code | Description | +| ---- | ----------------------- | +| 100 | Not authorized | +| 101 | Invalid amount | +| 102 | Insufficient balance | +| 103 | Option not found | +| 104 | Option expired | +| 105 | Invalid strike price | +| 106 | Invalid expiry | +| 107 | Insufficient collateral | +| 108 | Option not exercisable | +| 109 | Stale price | +| 110 | Invalid price | +| 111 | Option not expired | +| 112 | Invalid parameter | + +## Data Structures + +### Option Contract + +```clarity +{ + creator: principal, + holder: principal, + option-type: (string-ascii 4), + strike-price: uint, + expiry: uint, + amount: uint, + collateral: uint, + status: (string-ascii 10) +} +``` + +### User Balance + +```clarity +{ + sbtc-balance: uint, + locked-collateral: uint +} +``` + +## Security Features + +1. **Access Control** + + - Contract owner privileges for administrative functions + - Oracle authorization for price updates + - User authorization for option operations + +2. **Balance Protection** + + - Strict balance checking + - Collateral locking mechanism + - Safe arithmetic operations + +3. **Price Security** + + - Price validity window + - Stale price protection + - Zero/null price prevention + +4. **Option Safety** + - Expiry validation + - Status state machine + - Collateral ratio enforcement + +## Integration Guide + +### Implementing an Oracle + +1. Deploy an oracle contract with the following function: + +```clarity +(define-public (update-price (new-price uint)) + (contract-call? .bitcoin-options update-btc-price new-price)) +``` + +2. Set the oracle address using `set-oracle-address` +3. Ensure regular price updates within the validity window + +### Creating Options + +1. Deposit sBTC using `deposit-sbtc` +2. Calculate required collateral (amount \* strike-price / 100) +3. Create option with `create-option` +4. Monitor option status through `get-option` diff --git a/contracts/sbtc-options.clar b/contracts/sbtc-options.clar new file mode 100644 index 0000000..3e4dc8d --- /dev/null +++ b/contracts/sbtc-options.clar @@ -0,0 +1,288 @@ +;; Title: sBTC Options Protocol +;; A secure, decentralized protocol for Bitcoin options trading on Stacks, +;; leveraging sBTC for seamless Bitcoin integration. This protocol enables +;; trustless options trading with automated settlement, robust price +;; oracle integration, and comprehensive risk management. + +;; Constants and Error Codes + +;; Contract Owner +(define-constant CONTRACT_OWNER tx-sender) + +;; Parameter Limits +(define-constant MAX_FEE_BASIS_POINTS u10000) ;; 100% +(define-constant MAX_COLLATERAL_RATIO u1000) ;; 1000% +(define-constant MIN_DEPOSIT_AMOUNT u1000) ;; Minimum deposit +(define-constant MAX_DEPOSIT_AMOUNT u100000000000) ;; Maximum deposit +(define-constant MIN_VALIDITY_WINDOW u10) ;; Minimum blocks for price validity +(define-constant MAX_VALIDITY_WINDOW u1440) ;; Maximum blocks (~24 hours) + +;; Error Codes +(define-constant ERR_NOT_AUTHORIZED (err u100)) +(define-constant ERR_INVALID_AMOUNT (err u101)) +(define-constant ERR_INSUFFICIENT_BALANCE (err u102)) +(define-constant ERR_OPTION_NOT_FOUND (err u103)) +(define-constant ERR_OPTION_EXPIRED (err u104)) +(define-constant ERR_INVALID_STRIKE_PRICE (err u105)) +(define-constant ERR_INVALID_EXPIRY (err u106)) +(define-constant ERR_INSUFFICIENT_COLLATERAL (err u107)) +(define-constant ERR_OPTION_NOT_EXERCISABLE (err u108)) +(define-constant ERR_STALE_PRICE (err u109)) +(define-constant ERR_INVALID_PRICE (err u110)) +(define-constant ERR_OPTION_NOT_EXPIRED (err u111)) +(define-constant ERR_INVALID_PARAMETER (err u112)) + +;; Data Variables + +(define-data-var min-collateral-ratio uint u150) ;; 150% collateral ratio +(define-data-var platform-fee uint u10) ;; 0.1% fee (basis points) +(define-data-var next-option-id uint u0) + +;; Oracle Variables +(define-data-var oracle-address principal CONTRACT_OWNER) +(define-data-var btc-price uint u0) +(define-data-var price-last-updated uint u0) +(define-data-var price-validity-window uint u150) ;; ~25 minutes in blocks + +;; Data Maps + +;; Options Storage +(define-map options + uint ;; option-id + { + creator: principal, + holder: principal, + option-type: (string-ascii 4), ;; "CALL" or "PUT" + strike-price: uint, + expiry: uint, + amount: uint, + collateral: uint, + status: (string-ascii 10) ;; "ACTIVE", "EXERCISED", "EXPIRED" + } +) + +;; User Balances +(define-map user-balances + principal + { + sbtc-balance: uint, + locked-collateral: uint + } +) + +;; Oracle Functions + +;; Update BTC Price +(define-public (update-btc-price (new-price uint)) + (begin + (asserts! (is-eq tx-sender (var-get oracle-address)) ERR_NOT_AUTHORIZED) + (asserts! (> new-price u0) ERR_INVALID_PRICE) + (var-set btc-price new-price) + (var-set price-last-updated stacks-block-height) + (ok true)) +) + +;; Get Current BTC Price +(define-read-only (get-current-btc-price) + (let ( + (price (var-get btc-price)) + (last-updated (var-get price-last-updated)) + (validity-window (var-get price-validity-window)) + ) + (asserts! (> price u0) ERR_INVALID_PRICE) + (asserts! (< (- stacks-block-height last-updated) validity-window) ERR_STALE_PRICE) + (ok price)) +) + +;; Set Oracle Address +(define-public (set-oracle-address (new-oracle principal)) + (begin + (asserts! (is-contract-owner) ERR_NOT_AUTHORIZED) + (asserts! (not (is-eq new-oracle 'SP000000000000000000002Q6VF78)) ERR_INVALID_PARAMETER) + (var-set oracle-address new-oracle) + (ok true)) +) + +;; Set Price Validity Window +(define-public (set-price-validity-window (new-window uint)) + (begin + (asserts! (is-contract-owner) ERR_NOT_AUTHORIZED) + (asserts! (and (>= new-window MIN_VALIDITY_WINDOW) + (<= new-window MAX_VALIDITY_WINDOW)) ERR_INVALID_PARAMETER) + (var-set price-validity-window new-window) + (ok true)) +) + +;; Private Functions + +;; Authorization Check +(define-private (is-contract-owner) + (is-eq tx-sender CONTRACT_OWNER) +) + +;; Option Expiry Check +(define-private (check-expiry (option-id uint)) + (let ( + (option (unwrap! (map-get? options option-id) ERR_OPTION_NOT_FOUND)) + (current-height stacks-block-height) + ) + (if (> current-height (get expiry option)) + ERR_OPTION_EXPIRED + (ok true) + )) +) + +;; Balance Management +(define-private (update-user-balance (user principal) (delta uint) (is-subtract bool)) + (let ( + (current-balance (default-to {sbtc-balance: u0, locked-collateral: u0} + (map-get? user-balances user))) + (current-sbtc (get sbtc-balance current-balance)) + (new-balance (if is-subtract + (begin + (asserts! (>= current-sbtc delta) ERR_INSUFFICIENT_BALANCE) + (- current-sbtc delta)) + (+ current-sbtc delta))) + ) + (ok (map-set user-balances + user + (merge current-balance {sbtc-balance: new-balance}))) + ) +) + +;; Public Functions + +;; Deposit sBTC +(define-public (deposit-sbtc (amount uint)) + (begin + (asserts! (and (>= amount MIN_DEPOSIT_AMOUNT) + (<= amount MAX_DEPOSIT_AMOUNT)) ERR_INVALID_AMOUNT) + (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) + (try! (update-user-balance tx-sender amount false)) + (ok true) + ) +) + +;; Create Option +(define-public (create-option (option-type (string-ascii 4)) + (strike-price uint) + (expiry uint) + (amount uint)) + (let ( + (option-id (var-get next-option-id)) + (required-collateral (/ (* amount strike-price) u100)) + (user-balance (default-to {sbtc-balance: u0, locked-collateral: u0} + (map-get? user-balances tx-sender))) + ) + (asserts! (or (is-eq option-type "CALL") (is-eq option-type "PUT")) + ERR_NOT_AUTHORIZED) + (asserts! (>= strike-price u0) ERR_INVALID_STRIKE_PRICE) + (asserts! (and (> expiry stacks-block-height) + (<= (- expiry stacks-block-height) u5200)) ERR_INVALID_EXPIRY) + (asserts! (and (>= amount MIN_DEPOSIT_AMOUNT) + (<= amount MAX_DEPOSIT_AMOUNT)) ERR_INVALID_AMOUNT) + (asserts! (>= (get sbtc-balance user-balance) required-collateral) + ERR_INSUFFICIENT_COLLATERAL) + + (try! (update-user-balance tx-sender required-collateral true)) + + (map-set options option-id { + creator: tx-sender, + holder: tx-sender, + option-type: option-type, + strike-price: strike-price, + expiry: expiry, + amount: amount, + collateral: required-collateral, + status: "ACTIVE" + }) + + (map-set user-balances tx-sender + (merge user-balance { + locked-collateral: (+ (get locked-collateral user-balance) required-collateral) + })) + + (var-set next-option-id (+ option-id u1)) + (ok option-id)) +) + +;; Exercise Option +(define-public (exercise-option (option-id uint)) + (let ( + (option (unwrap! (map-get? options option-id) ERR_OPTION_NOT_FOUND)) + (current-price (unwrap! (get-current-btc-price) ERR_INVALID_PRICE)) + ) + (asserts! (is-eq (get holder option) tx-sender) ERR_NOT_AUTHORIZED) + (try! (check-expiry option-id)) + (asserts! (is-eq (get status option) "ACTIVE") ERR_OPTION_NOT_EXERCISABLE) + + (if (is-eq (get option-type option) "CALL") + (if (> current-price (get strike-price option)) + (let ( + (profit (- current-price (get strike-price option))) + ) + (try! (update-user-balance tx-sender profit false)) + (map-set options option-id + (merge option {status: "EXERCISED"})) + (ok true)) + ERR_OPTION_NOT_EXERCISABLE) + (if (< current-price (get strike-price option)) + (let ( + (profit (- (get strike-price option) current-price)) + ) + (try! (update-user-balance tx-sender profit false)) + (map-set options option-id + (merge option {status: "EXERCISED"})) + (ok true)) + ERR_OPTION_NOT_EXERCISABLE)) + ) +) + +;; Expire Option +(define-public (expire-option (option-id uint)) + (let ( + (option (unwrap! (map-get? options option-id) ERR_OPTION_NOT_FOUND)) + ) + (asserts! (> stacks-block-height (get expiry option)) ERR_OPTION_NOT_EXPIRED) + (asserts! (is-eq (get status option) "ACTIVE") ERR_OPTION_NOT_EXERCISABLE) + + (try! (update-user-balance (get creator option) (get collateral option) false)) + + (map-set options option-id + (merge option {status: "EXPIRED"})) + (ok true)) +) + +;; Read-Only Functions + +(define-read-only (get-option (option-id uint)) + (map-get? options option-id) +) + +(define-read-only (get-user-balance (user principal)) + (default-to {sbtc-balance: u0, locked-collateral: u0} + (map-get? user-balances user)) +) + +(define-read-only (get-platform-fee) + (var-get platform-fee) +) + +;; Administrative Functions + +(define-public (set-platform-fee (new-fee uint)) + (begin + (asserts! (is-contract-owner) ERR_NOT_AUTHORIZED) + (asserts! (<= new-fee MAX_FEE_BASIS_POINTS) ERR_INVALID_PARAMETER) + (var-set platform-fee new-fee) + (ok true)) +) + +(define-public (set-min-collateral-ratio (new-ratio uint)) + (begin + (asserts! (is-contract-owner) ERR_NOT_AUTHORIZED) + (asserts! (and (>= new-ratio u100) + (<= new-ratio MAX_COLLATERAL_RATIO)) ERR_INVALID_PARAMETER) + (var-set min-collateral-ratio new-ratio) + (ok true)) +) \ No newline at end of file diff --git a/tests/sbtc-options.test.ts b/tests/sbtc-options.test.ts new file mode 100644 index 0000000..4bb9cf3 --- /dev/null +++ b/tests/sbtc-options.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const address1 = accounts.get("wallet_1")!; + +/* + The test below is an example. To learn more, read the testing documentation here: + https://docs.hiro.so/stacks/clarinet-js-sdk +*/ + +describe("example tests", () => { + it("ensures simnet is well initalised", () => { + expect(simnet.blockHeight).toBeDefined(); + }); + + // it("shows an example", () => { + // const { result } = simnet.callReadOnlyFn("counter", "get-counter", [], address1); + // expect(result).toBeUint(0); + // }); +});