Source Code

;; ---- Constants ----
(define-constant ERR-OUT-OF-BOUNDS u104)
(define-constant ERR_TX_VALUE_TOO_SMALL (err u105))
(define-constant ERR_TX_NOT_FOR_RECEIVER (err u106))
(define-constant ERR_NOT_PROCESSED (err u107))
(define-constant ERR_BTC_TX_ALREADY_USED (err u109)) 
(define-constant ERR_IN_COOLDOWN (err u110))
(define-constant ERR_FORBIDDEN (err u114))
(define-constant ERR_AMOUNT_NULL (err u115))
(define-constant ERR_INSUFFICIENT_POOL_BALANCE (err u132))
(define-constant ERR_NATIVE_FAILURE (err u99)) 
(define-constant ERR-TRANSACTION-SEGWIT (err u130))
(define-constant ERR-TRANSACTION (err u131))
(define-constant ERR-ELEMENT-EXPECTED (err u129))

(define-constant FEE-RECEIVER 'SMHAVPYZ8BVD0BHBBQGY5AQVVGNQY4TNHAKGPYP)
(define-constant OPERATOR_STYX 'SMH8FRN30ERW1SX26NJTJCKTDR3H27NRJ6W75WQE) 
(define-constant COOLDOWN u6)
(define-constant MIN_SATS u10000) ;; min 10k satoshis 0.0001
(define-constant FIXED_FEE u2000)

;; ---- Data structures ----

;; Counter for processed transactions
(define-data-var processed-tx-count uint u0)

;; Pool structure to track sBTC liquidity (single pool per contract)
(define-data-var pool 
  {
    total-sbtc: uint, ;; Total sBTC amount originally deposited
    available-sbtc: uint,
    btc-receiver: (buff 40), ;; BTC address of the operator
    last-updated: uint
  }
  {
    total-sbtc: u0,
    available-sbtc: u0,
    btc-receiver: 0x0000000000000000000000000000000000000000,
    last-updated: u0
  }
)

;; Track processed BTC transactions
(define-map processed-btc-txs 
  (buff 128)  ;; BTC transaction ID
  {
    btc-amount: uint,
    sbtc-amount: uint,
    stx-receiver: principal,
    processed-at: uint,
    tx-number: uint
  }
)

;; ---- Helper functions ----

;; Read 32-bit unsigned integer in little-endian format
(define-read-only (read-uint32 (ctx { txbuff: (buff 4096), index: uint}))
		(let ((data (get txbuff ctx))
					(base (get index ctx)))
				(ok {uint32: (buff-to-uint-le (unwrap-panic (as-max-len? (unwrap! (slice? data base (+ base u4)) (err ERR-OUT-OF-BOUNDS)) u4))),
						 ctx: { txbuff: data, index: (+ u4 base)}})))

;; Find output in BTC transaction for a given address
(define-private (find-out (entry {scriptPubKey: (buff 128), value: (buff 8)}) (result {pubscriptkey: (buff 40), out: (optional {scriptPubKey: (buff 128), value: uint})}))
  (if (is-eq (get scriptPubKey entry) (get pubscriptkey result))
    (merge result {out: (some {scriptPubKey: (get scriptPubKey entry), value: (get uint32 (unwrap-panic (read-uint32 {txbuff: (get value entry), index: u0})))})})
    result))

;; Get BTC output value for a given address
(define-public (get-out-value (tx {
    version: (buff 4),
    ins: (list 8
      {outpoint: {hash: (buff 32), index: (buff 4)}, scriptSig: (buff 256), sequence: (buff 4)}),
    outs: (list 8
      {value: (buff 8), scriptPubKey: (buff 128)}),
    locktime: (buff 4)}) (pubscriptkey (buff 40)))
    (ok (fold find-out (get outs tx) {pubscriptkey: pubscriptkey, out: none})))

;; ---- Pool initialization ----

;; Initialize or add liquidity to pool
(define-public (add-liquidity-to-pool (sbtc-amount uint) (btc-receiver (optional (buff 40))))
  (let (
        (current-pool (var-get pool))
        (this-bitcoin-receiver (default-to (get btc-receiver current-pool) btc-receiver))
        (new-total (+ (get total-sbtc current-pool) sbtc-amount))
        (new-available (+ (get available-sbtc current-pool) sbtc-amount))
      )
    ;; Verify caller is the operator
    (asserts! (is-eq tx-sender OPERATOR_STYX) ERR_FORBIDDEN)
    
    ;; Verify parameters
    (asserts! (> sbtc-amount u0) ERR_AMOUNT_NULL)
    
    ;; Transfer sBTC to contract
    (match (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token 
                transfer sbtc-amount tx-sender (as-contract tx-sender) none)
      success 
      (begin
        ;; Update pool - set or update BTC receiver if provided
        (var-set pool (merge current-pool 
                  {
                    total-sbtc: new-total,
                    available-sbtc: new-available,
                    btc-receiver: this-bitcoin-receiver,
                    last-updated: burn-block-height
                  }))
        
        (print {
          type: "add-liquidity",
          operator: tx-sender,
          sbtc: sbtc-amount,
          btc-receiver: this-bitcoin-receiver,
          total-sbtc: new-total,
          available-sbtc: new-available,
          last-updated: burn-block-height
        })
        (ok true)
      )
      error (err (* error u1000))
    )
  )
)

;; Withdraw remaining sBTC from pool (operator only)
(define-public (withdraw-from-pool)
  (let (
        (current-pool (var-get pool))
        (available-sbtc (get available-sbtc current-pool))
      )
    ;; Verify caller is the operator
    (asserts! (is-eq tx-sender OPERATOR_STYX) ERR_FORBIDDEN)
    
    ;; Verify there is available balance
    (asserts! (> available-sbtc u0) ERR_INSUFFICIENT_POOL_BALANCE)
    
    ;; Update pool - set available to 0
    (var-set pool (merge current-pool { available-sbtc: u0 }))
    
    ;; Transfer sBTC to operator
    (try! (as-contract (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token 
            transfer available-sbtc tx-sender OPERATOR_STYX none)))
    
    (print {
      type: "withdraw-from-pool",
      sbtc-amount: available-sbtc
    })
    
    (ok available-sbtc)
  )
)

;; ---- BTC processing functions ----

;; Parse the payload from a segwit transaction
(define-read-only (parse-payload-segwit (tx (buff 4096)))
  (match (get-output-segwit tx u0)
    result
    (let
      (
        (script (get scriptPubKey result))
        (script-len (len script))
        ;; lenght is dynamic one or two bytes!
        (offset (if (is-eq (unwrap! (element-at? script u1) ERR-ELEMENT-EXPECTED) 0x4C) u3 u2)) 
        (payload (unwrap! (slice? script offset script-len) ERR-ELEMENT-EXPECTED))
      )
      (ok (from-consensus-buff? { p: principal } payload))
    )
    not-found ERR-ELEMENT-EXPECTED
  )
)

;; Get output from a segwit transaction
(define-read-only (get-output-segwit (tx (buff 4096)) (index uint))
  (let
    (
      (parsed-tx (contract-call? 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.clarity-bitcoin-lib-v5 parse-wtx tx false))
    )
    (match parsed-tx
      result
      (let
        (
          (tx-data (unwrap-panic parsed-tx)) 
          (outs (get outs tx-data)) 
          (out (unwrap! (element-at? outs index) ERR-TRANSACTION-SEGWIT))
          (scriptPubKey (get scriptPubKey out))
          (value (get value out)) 
        )
        (ok { scriptPubKey: scriptPubKey, value: value })
      )
      missing ERR-TRANSACTION
    )
  )
)

;; Process a BTC deposit and release sBTC - can be called by anyone
(define-public (process-btc-deposit
    (height uint) 
    (wtx {version: (buff 4),
      ins: (list 8
        {outpoint: {hash: (buff 32), index: (buff 4)}, scriptSig: (buff 256), sequence: (buff 4)}),
      outs: (list 8
        {value: (buff 8), scriptPubKey: (buff 128)}),
      locktime: (buff 4)})
    (witness-data (buff 1650))
    (header (buff 80)) 
    (tx-index uint) 
    (tree-depth uint) 
    (wproof (list 14 (buff 32))) 
    (witness-merkle-root (buff 32)) 
    (witness-reserved-value (buff 32)) 
    (ctx (buff 1024)) 
    (cproof (list 14 (buff 32)))) 
  (let (
        (current-pool (var-get pool))
        (tx-buff (contract-call? 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.bitcoin-helper-wtx-v1 concat-wtx wtx witness-data))
      )
    
    (asserts! (> burn-block-height (+ (get last-updated current-pool) COOLDOWN)) ERR_IN_COOLDOWN)

    ;; Verify Bitcoin transaction proof
    (match (contract-call? 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.clarity-bitcoin-lib-v5 was-segwit-tx-mined-compact
                    height 
                    tx-buff 
                    header 
                    tx-index 
                    tree-depth 
                    wproof 
                    witness-merkle-root 
                    witness-reserved-value 
                    ctx 
                    cproof)
      result
        (begin
          ;; Verify transaction is not already used
          (asserts! (is-none (map-get? processed-btc-txs result)) ERR_BTC_TX_ALREADY_USED)
          
          ;; Verify BTC was sent to correct address
          (match (get out (unwrap! (get-out-value wtx (get btc-receiver current-pool)) ERR_NATIVE_FAILURE))
            out (if (>= (get value out) MIN_SATS)
              (let (
                    (btc-amount (get value out))
                    (payload (unwrap! (parse-payload-segwit tx-buff) ERR-ELEMENT-EXPECTED))
                    (stx-receiver (unwrap! (get p payload) ERR-ELEMENT-EXPECTED))
                    (sbtc-amount-out (- btc-amount FIXED_FEE))
                    (available-sbtc (get available-sbtc current-pool))
                    (current-count (var-get processed-tx-count))
                   ) 
                   
                   ;; Verify sufficient balance in pool
                   (asserts! (<= btc-amount available-sbtc) ERR_INSUFFICIENT_POOL_BALANCE)
                   
                   ;; Record processed transaction
                   (map-set processed-btc-txs result 
                     {
                       btc-amount: btc-amount,
                       sbtc-amount: sbtc-amount-out,
                       stx-receiver: stx-receiver,
                       processed-at: burn-block-height,
                       tx-number: current-count
                     })
                    
                    (var-set processed-tx-count (+ current-count u1))

                   ;; Update pool's available balance
                   (var-set pool 
                     (merge current-pool 
                       { available-sbtc: (- available-sbtc btc-amount) }
                     ))
                   
                   ;; Transfer fee to fee receiver
                   (try! (as-contract (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token 
                             transfer FIXED_FEE tx-sender FEE-RECEIVER none)))
                   
                   ;; Transfer sBTC to user
                   (try! (as-contract (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token 
                             transfer sbtc-amount-out tx-sender stx-receiver none)))
                   
                   (print {
                     type: "process-btc-deposit",
                     btc-tx-id: result,
                     btc-amount: btc-amount,
                     sbtc-amount-out: sbtc-amount-out,
                     stx-receiver: stx-receiver,
                     processor: tx-sender
                   })
                   
                   (ok true))
            ERR_TX_VALUE_TOO_SMALL)
            ERR_TX_NOT_FOR_RECEIVER))
      error (err (* error u1000))))
)

;; ---- Read-only functions ----

;; Get pool information
(define-read-only (get-pool)
  (ok (var-get pool)))

;; Check if a transaction has been processed
(define-read-only (is-tx-processed (tx-id (buff 128)))
  (is-some (map-get? processed-btc-txs tx-id)))

;; Get transaction details
(define-read-only (get-processed-tx (tx-id (buff 128)))
  (match (map-get? processed-btc-txs tx-id)
    tx-info (ok tx-info)
    (err ERR_NOT_PROCESSED)))

Functions (7)

FunctionAccessArgs
add-liquidity-to-poolpublicsbtc-amount: uint, btc-receiver: (optional (buff 40
withdraw-from-poolpublic
parse-payload-segwitread-onlytx: (buff 4096
get-output-segwitread-onlytx: (buff 4096
get-poolread-only
is-tx-processedread-onlytx-id: (buff 128
get-processed-txread-onlytx-id: (buff 128