;; title: sBTC-BitDeriv
;; version:
;; summary:
;; description:
;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-EXPIRED (err u1))
(define-constant ERR-INVALID-AMOUNT (err u2))
(define-constant ERR-UNAUTHORIZED (err u3))
(define-constant ERR-NOT-FOUND (err u4))
(define-constant ERR-INSUFFICIENT-BALANCE (err u5))
(define-constant ERR-ALREADY-EXECUTED (err u6))
(define-constant ERR-INVALID-PRICE (err u7))
(define-constant ERR-OPTION-NOT-ACTIVE (err u8))
(define-constant ERR-INVALID-SIGNATURE (err u9))
;; Option Types
(define-constant CALL u1)
(define-constant PUT u2)
;; Data Maps
(define-map BTCBalances
{ holder: principal }
{ balance: uint }
)
;; Escrow map to track BTC locked in options
(define-map EscrowedBTC
{ option-id: uint }
{ amount: uint }
)
(define-map Options
{ option-id: uint }
{
creator: principal,
buyer: (optional principal),
option-type: uint,
strike-price: uint,
premium: uint,
expiry: uint,
btc-amount: uint,
is-active: bool,
is-executed: bool,
creation-time: uint,
}
)
(define-map UserOptions
{ user: principal }
{
created: (list 20 uint),
purchased: (list 20 uint),
}
)
;; Data Variables
(define-data-var next-option-id uint u0)
(define-data-var oracle-price uint u0)
(define-data-var oracle-public-key (buff 33) 0x000000000000000000000000000000000000000000000000000000000000000000)
;; Authorization
(define-private (is-contract-owner)
(is-eq tx-sender CONTRACT-OWNER)
)
;; BTC Balance Management
(define-public (deposit-btc (amount uint))
(let (
(sender tx-sender)
(current-balance (default-to { balance: u0 } (map-get? BTCBalances { holder: sender })))
)
(map-set BTCBalances { holder: sender } { balance: (+ amount (get balance current-balance)) })
(ok true)
)
)
(define-public (withdraw-btc (amount uint))
(let (
(sender tx-sender)
(current-balance (default-to { balance: u0 } (map-get? BTCBalances { holder: sender })))
)
(asserts! (>= (get balance current-balance) amount) ERR-INSUFFICIENT-BALANCE)
(map-set BTCBalances { holder: sender } { balance: (- (get balance current-balance) amount) })
(ok true)
)
)
(define-private (transfer-btc
(from principal)
(to principal)
(amount uint)
)
(let (
(from-balance (default-to { balance: u0 } (map-get? BTCBalances { holder: from })))
(to-balance (default-to { balance: u0 } (map-get? BTCBalances { holder: to })))
)
(asserts! (>= (get balance from-balance) amount) ERR-INSUFFICIENT-BALANCE)
(map-set BTCBalances { holder: from } { balance: (- (get balance from-balance) amount) })
(map-set BTCBalances { holder: to } { balance: (+ amount (get balance to-balance)) })
(ok true)
)
)
(define-private (update-user-options
(user principal)
(option-id uint)
(is-creator bool)
)
(let ((user-options (default-to {
created: (list),
purchased: (list),
}
(map-get? UserOptions { user: user })
)))
(if is-creator
(ok (map-set UserOptions { user: user } {
created: (unwrap! (as-max-len? (append (get created user-options) option-id) u20)
ERR-UNAUTHORIZED
),
purchased: (get purchased user-options),
}))
(ok (map-set UserOptions { user: user } {
created: (get created user-options),
purchased: (unwrap!
(as-max-len? (append (get purchased user-options) option-id) u20)
ERR-UNAUTHORIZED
),
}))
)
)
)
;; Option Management
(define-public (create-option
(option-type uint)
(strike-price uint)
(premium uint)
(expiry uint)
(btc-amount uint)
)
(let (
(option-id (+ (var-get next-option-id) u1))
(sender tx-sender)
(current-balance (default-to { balance: u0 } (map-get? BTCBalances { holder: sender })))
)
;; Validate inputs
(asserts! (or (is-eq option-type CALL) (is-eq option-type PUT))
ERR-INVALID-AMOUNT
)
(asserts! (> strike-price u0) ERR-INVALID-PRICE)
(asserts! (> premium u0) ERR-INVALID-PRICE)
(asserts! (> expiry stacks-block-time) ERR-EXPIRED)
(asserts! (> btc-amount u0) ERR-INVALID-AMOUNT)
;; For CALL options, ensure creator has enough BTC
(asserts!
(if (is-eq option-type CALL)
(>= (get balance current-balance) btc-amount)
true
)
ERR-INSUFFICIENT-BALANCE
)
;; Create option
(map-set Options { option-id: option-id } {
creator: sender,
buyer: none,
option-type: option-type,
strike-price: strike-price,
premium: premium,
expiry: expiry,
btc-amount: btc-amount,
is-active: true,
is-executed: false,
creation-time: stacks-block-time,
})
;; Lock BTC for CALL options by reducing creator's balance and tracking in escrow
(if (is-eq option-type CALL)
(begin
;; Deduct BTC from creator's balance
(map-set BTCBalances { holder: sender } { balance: (- (get balance current-balance) btc-amount) })
;; Add to escrow
(map-set EscrowedBTC { option-id: option-id } { amount: btc-amount })
)
true
)
;; Update state
(var-set next-option-id option-id)
(try! (update-user-options sender option-id true))
(ok option-id)
)
)
;; Private helper functions
(define-private (exercise-call
(option-id uint)
(option {
creator: principal,
buyer: (optional principal),
option-type: uint,
strike-price: uint,
premium: uint,
expiry: uint,
btc-amount: uint,
is-active: bool,
is-executed: bool,
creation-time: uint,
})
(holder principal)
)
(let (
(strike-amount (* (get strike-price option) (get btc-amount option)))
(holder-balance (default-to { balance: u0 } (map-get? BTCBalances { holder: holder })))
)
;; Transfer strike price from holder to creator
(try! (stx-transfer? strike-amount holder (get creator option)))
;; Transfer escrowed BTC to holder
(map-set BTCBalances { holder: holder } { balance: (+ (get balance holder-balance) (get btc-amount option)) })
;; Remove from escrow
(map-delete EscrowedBTC { option-id: option-id })
(ok true)
)
)
(define-public (exercise-option (option-id uint))
(let (
(option (unwrap! (map-get? Options { option-id: option-id }) ERR-NOT-FOUND))
(holder tx-sender)
)
;; Validate option state
(asserts! (is-eq (some holder) (get buyer option)) ERR-UNAUTHORIZED)
(asserts! (not (get is-executed option)) ERR-ALREADY-EXECUTED)
(asserts! (< stacks-block-time (get expiry option)) ERR-EXPIRED)
;; Exercise based on option type
(if (is-eq (get option-type option) CALL)
(try! (exercise-call option-id option holder))
(try! (exercise-put option-id option holder))
)
;; Update option state
(map-set Options { option-id: option-id }
(merge option { is-executed: true })
)
(ok true)
)
)
(define-private (exercise-put
(option-id uint)
(option {
creator: principal,
buyer: (optional principal),
option-type: uint,
strike-price: uint,
premium: uint,
expiry: uint,
btc-amount: uint,
is-active: bool,
is-executed: bool,
creation-time: uint,
})
(holder principal)
)
(let (
(strike-amount (* (get strike-price option) (get btc-amount option)))
(creator-balance (default-to { balance: u0 }
(map-get? BTCBalances { holder: (get creator option) })
))
)
;; Transfer BTC from holder to creator
(try! (transfer-btc holder (get creator option) (get btc-amount option)))
;; Transfer strike price from creator to holder
(try! (stx-transfer? strike-amount (get creator option) holder))
(ok true)
)
)
;; Read-only functions
(define-read-only (get-option (option-id uint))
(map-get? Options { option-id: option-id })
)
(define-read-only (get-btc-balance (holder principal))
(default-to { balance: u0 } (map-get? BTCBalances { holder: holder }))
)
(define-read-only (get-user-options (user principal))
(default-to {
created: (list),
purchased: (list),
}
(map-get? UserOptions { user: user })
)
)
;; Metadata functions using to-ascii? (Clarity 4 feature)
;; ACTUAL to-ascii? USAGE: Convert utf8 string to ascii
(define-read-only (convert-utf8-to-ascii (text (string-utf8 100)))
(to-ascii? text)
)
;; Example: Get option label with to-ascii? conversion
(define-read-only (get-option-label (option-id uint))
(match (map-get? Options { option-id: option-id })
option (let ((label-utf8 (if (is-eq (get option-type option) CALL)
u"BTC-CALL-Option"
u"BTC-PUT-Option"
)))
(to-ascii? label-utf8)
)
(err u999)
)
)
;; Get human-readable option type name
(define-read-only (get-option-type-name (option-type uint))
(if (is-eq option-type CALL)
(some "CALL")
(if (is-eq option-type PUT)
(some "PUT")
none
)
)
)
;; Get option status description
(define-read-only (get-option-status (option-id uint))
(match (map-get? Options { option-id: option-id })
option (if (get is-executed option)
(some "EXECUTED")
(if (>= stacks-block-time (get expiry option))
(some "EXPIRED")
(if (is-some (get buyer option))
(some "ACTIVE-PURCHASED")
(some "ACTIVE-AVAILABLE")
)
)
)
none
)
)
;; Get full option description with all details
(define-read-only (get-option-details (option-id uint))
(match (map-get? Options { option-id: option-id })
option (some {
type: (unwrap-panic (get-option-type-name (get option-type option))),
status: (unwrap-panic (get-option-status option-id)),
strike-price: (get strike-price option),
premium: (get premium option),
btc-amount: (get btc-amount option),
expiry: (get expiry option),
creator: (get creator option),
buyer: (get buyer option),
})
none
)
)
;; Oracle functions
(define-public (set-btc-price (price uint))
(begin
(asserts! (is-contract-owner) ERR-UNAUTHORIZED)
(var-set oracle-price price)
(ok true)
)
)
;; Set oracle public key (only contract owner)
(define-public (set-oracle-public-key (pubkey (buff 33)))
(begin
(asserts! (is-contract-owner) ERR-UNAUTHORIZED)
(var-set oracle-public-key pubkey)
(ok true)
)
)
;; Verified price update using secp256r1-verify (Clarity 4 feature)
;; This ensures the price comes from a trusted oracle with cryptographic proof
(define-public (set-btc-price-verified
(price uint)
(timestamp uint)
(signature (buff 64))
)
(let (
(message-hash (sha256 (concat (concat (uint-to-buff price) (uint-to-buff timestamp)) 0x42544300))) ;; "BTC" suffix
(pubkey (var-get oracle-public-key))
)
;; Verify the signature using secp256r1
(asserts! (secp256r1-verify message-hash signature pubkey)
ERR-INVALID-SIGNATURE
)
;; Optional: Add timestamp validation to prevent replay attacks
(asserts! (>= timestamp stacks-block-time) ERR-EXPIRED)
;; Update price
(var-set oracle-price price)
(ok true)
)
)
;; Helper to convert uint to buffer for hashing
(define-private (uint-to-buff (value uint))
(unwrap-panic (to-consensus-buff? value))
)
(define-read-only (get-btc-price)
(var-get oracle-price)
)
(define-read-only (get-oracle-public-key)
(var-get oracle-public-key)
)