Source Code

(define-constant ERR-OUT-OF-BOUNDS u1)
(define-constant ERR_VERIFICATION_FAILED (err u1))
(define-constant ERR_FAILED_TO_PARSE_TX (err u2))
(define-constant ERR_INVALID_ID (err u3))
(define-constant ERR_FORBIDDEN (err u4))
(define-constant ERR_TX_VALUE_TOO_SMALL (err u5))
(define-constant ERR_TX_NOT_FOR_RECEIVER (err u6))
(define-constant ERR_ALREADY_DONE (err u7))
(define-constant ERR_NO_STX_RECEIVER (err u8))
(define-constant ERR_BTC_TX_ALREADY_USED (err u9))
(define-constant ERR_NATIVE_FAILURE (err u99))

(define-constant expiry u100)
(define-map swaps uint {sats: uint, btc-receiver: (buff 40), amount: uint, stx-receiver: (optional principal), sbtc-sender: principal, when: uint, done: bool, premium: uint})
(define-data-var next-id uint u0)
;; map between accepted btc txs and swap id
(define-map submitted-btc-txs (buff 128) uint)

(define-private (sbtc-transfer (amount uint) (sender principal) (recipient principal))
  (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token transfer amount sender recipient none))


(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)}})))

(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))


(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})))

;; create a swap between btc and stx
(define-public (create-swap (sats uint) (btc-receiver (buff 40)) (amount uint) (stx-receiver (optional principal)) (premium uint))
  (let ((id (var-get next-id)))
    (asserts! (map-insert swaps id
      {sats: sats, btc-receiver: btc-receiver, amount: amount, stx-receiver: stx-receiver,
        sbtc-sender: tx-sender, when: burn-block-height, done: false, premium: premium}) ERR_INVALID_ID)
    (var-set next-id (+ id u1))
    (match (sbtc-transfer amount tx-sender (as-contract tx-sender))
      success (ok id)
      error (err (* error u1000)))))

(define-public (set-stx-receiver (id uint))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
    (premium (get premium swap)))
    (asserts! (is-none (get stx-receiver swap)) ERR_ALREADY_DONE)
    (and (> premium u0))
      (try! (sbtc-transfer premium tx-sender (get sbtc-sender swap)))
    (ok (map-set swaps id (merge swap {stx-receiver: (some tx-sender), when: burn-block-height})))))

;; any user can cancle the swap after the expiry period
;; sbtc-sender can cancle it before if the stx-receiver was not yet set
(define-public (cancel (id uint))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID)))
    (asserts!
      (or
        (and (is-none (get stx-receiver swap)) (is-eq tx-sender (get sbtc-sender swap)))
        (< (+ (get when swap) expiry) burn-block-height)) ERR_FORBIDDEN)
    (asserts! (not (get done swap)) ERR_ALREADY_DONE)
    (map-set swaps id (merge swap {done: true}))
    (as-contract (sbtc-transfer (get amount swap) tx-sender (get sbtc-sender swap)))))

;; any user can submit a tx that contains the swap
(define-public (submit-swap
    (id uint)
    (height uint)
    (blockheader (buff 80))
    (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)})
    (proof { tx-index: uint, hashes: (list 12 (buff 32)), tree-depth: uint }))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
        (tx-buff (contract-call? 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.clarity-bitcoin-helper concat-tx tx))
        (stx-receiver (unwrap! (get stx-receiver swap) ERR_NO_STX_RECEIVER)))
      (asserts! (is-eq tx-sender stx-receiver) ERR_FORBIDDEN)
      (match (contract-call? 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.clarity-bitcoin-lib-v5 was-tx-mined-compact
                height tx-buff blockheader proof )
        result
          (begin
            (asserts! (is-none (map-get? submitted-btc-txs result)) ERR_BTC_TX_ALREADY_USED)
            (asserts! (not (get done swap)) ERR_ALREADY_DONE)
            (match (get out (unwrap! (get-out-value tx (get btc-receiver swap)) ERR_NATIVE_FAILURE))
              out (if (>= (get value out) (get sats swap))
                (begin
                      (map-set swaps id (merge swap {done: true}))
                      (map-set submitted-btc-txs result id)
                      (as-contract (sbtc-transfer (get amount swap) tx-sender (unwrap! (get stx-receiver swap) ERR_NO_STX_RECEIVER))))
                ERR_TX_VALUE_TOO_SMALL)
            ERR_TX_NOT_FOR_RECEIVER))
        error (err (* error u1000)))))


(define-public (submit-swap-segwit
    (id uint)
    (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 ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
        (tx-buff (contract-call? .bitcoin-helper-wtx-v1 concat-wtx wtx witness-data)))
      (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
            (asserts! (is-none (map-get? submitted-btc-txs result)) ERR_BTC_TX_ALREADY_USED)
            (asserts! (not (get done swap)) ERR_ALREADY_DONE)
            (match (get out (unwrap! (get-out-value wtx (get btc-receiver swap)) ERR_NATIVE_FAILURE))
              out (if (>= (get value out) (get sats swap))
                (begin
                      (map-set swaps id (merge swap {done: true}))
                      (map-set submitted-btc-txs result id)
                      (as-contract (sbtc-transfer (get amount swap) tx-sender (unwrap! (get stx-receiver swap) ERR_NO_STX_RECEIVER))))
                ERR_TX_VALUE_TOO_SMALL)
            ERR_TX_NOT_FOR_RECEIVER))
        error (err (* error u1000)))))

Functions (5)

FunctionAccessArgs
sbtc-transferprivateamount: uint, sender: principal, recipient: principal
create-swappublicsats: uint, btc-receiver: (buff 40
set-stx-receiverpublicid: uint
cancelpublicid: uint
submit-swappublicid: uint, height: uint, blockheader: (buff 80