Source Code

(define-constant ERR-OUT-OF-BOUNDS u4) ;; (err u1) -- sender does not have enough balance to transfer (err u2) -- sender and recipient are the same principal (err u3) -- amount to send is non-positive
(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)) ;; this needs to be used to prevent double claiming?
(define-constant ERR_IN_COOLDOWN (err u10))
(define-constant ERR_ALREADY_RESERVED (err u11))
(define-constant ERR_INVALID_STX_SENDER (err u12))
(define-constant ERR_INVALID_ID (err u13))
(define-constant ERR_FORBIDDEN (err u14))
(define-constant ERR_NOT_PRICED (err u15))
(define-constant ERR_NO_BTC_RECEIVER (err u16)) ;; this
(define-constant ERR_NO_SUCH_OFFER (err u17))
(define-constant ERR_USTX (err u18))
(define-constant ERR_SATS (err u19))
(define-constant ERR_PREMIUM (err u20))
(define-constant ERR_INVALID_STX_RECEIVER (err u22))
(define-constant ERR_OFFER_ALREADY_EXISTS (err u23)) ;; one offer at a time
(define-constant ERR_INVALID_OFFER (err u24))
(define-constant ERR_PROOF_FALSE (err u25))
(define-constant ERR_RESERVATION_EXPIRED (err u26))
(define-constant ERR_NOT_RESERVED (err u27))
(define-constant ERR_SAME_SENDER_RECEIVER (err u28))
(define-constant ERR_NATIVE_FAILURE (err u99)) ;; this is not necessary?
(define-constant nexus (as-contract tx-sender))
(define-constant expiry u100)
(define-constant cooldown u6)

(define-map swaps uint {sats: (optional uint), btc-receiver: (optional (buff 40)), stx-sender: principal, ustx: uint, stx-receiver: (optional principal), when: uint, expired-height: (optional uint), done: bool, premium: (optional uint), ask-priced: bool})
(define-map swap-offers {stx-receiver: principal, swap-id: (optional uint)} ;; allows a stx-receiver to do an offer per swap-id and 1 without swap-id
  {stx-sender: (optional principal), ustx: uint, sats: uint, premium: uint})
;; (define-map submitted-btc-txs (buff 128) uint)  ;; map between accepted btc txs and swap ids

(define-data-var next-id uint u0)

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

(define-public (collateralize-stx (ustx uint) (btc-receiver (optional (buff 40))))
  (let ((id (var-get next-id)))
    (print 
      {
        type: "collateralize-stx",
        id: id,
        ustx: ustx,
        stxSender: tx-sender,
        btcReceiver: btc-receiver,
        done: false,
        askPriced: false,
        sats: none,
        premium: none,
        stxReceiver: none,
        when: burn-block-height,
        fees: "zero",
      }
    )
    (asserts! (map-insert swaps id
      {sats: none, btc-receiver: btc-receiver, ustx: ustx, stx-receiver: none,
        stx-sender: tx-sender, when: burn-block-height, expired-height: none, done: false, premium: none, ask-priced: false}) ERR_INVALID_ID)
    (var-set next-id (+ id u1))
    (match (stx-transfer? ustx tx-sender (as-contract tx-sender)) ;; memo?
      success (ok id)
      error (err (* error u1000)))))

(define-public (make-ask (id uint) (sats uint) (btc-receiver (buff 40)) (stx-receiver (optional principal)) (premium uint))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID)))
    (asserts! (is-eq tx-sender (get stx-sender swap)) ERR_INVALID_STX_SENDER)
    (asserts! (not (get done swap)) ERR_ALREADY_DONE)
    (match (get expired-height swap)
            some-height (asserts! (>= burn-block-height some-height) ERR_ALREADY_RESERVED) 
            true) ;; no cool down so make sure ulterior func cool down
    (print 
      {
        type: "make-ask",
        id: id,
        sats: (some sats),
        btcReceiver: (some btc-receiver),
        stxReceiver: stx-receiver,
        premium: (some premium),
        askPriced: true,
      }
    )
    (ok (map-set swaps id (merge swap {
      sats: (some sats), 
      btc-receiver: (some btc-receiver), 
      stx-receiver: stx-receiver,
      premium: (some premium), 
      ask-priced: true})))))

(define-public (collateralize-and-make-ask
  (ustx uint) 
  (btc-receiver (buff 40)) 
  (sats uint)
  (stx-receiver (optional principal))
  (premium uint))
  (let 
    ((swap-id (try! (collateralize-stx ustx (some btc-receiver)))))
    (try! (make-ask swap-id sats btc-receiver stx-receiver premium))
    (print   
      {
        type: "collateralize-and-make-ask",
        id: swap-id,
        ustx: ustx,
        stxSender: tx-sender,
        done: false,
        when: burn-block-height,
        fees: "zero",
        sats: (some sats),
        btcReceiver: (some btc-receiver),
        stxReceiver: stx-receiver,
        premium: (some premium),
        askPriced: true,
      }
    )
    (ok swap-id)))

(define-public (take-ask (id uint)) ;; BTC sender accepts the initial offer of STX sender
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
    (premium (unwrap! (get premium swap) ERR_PREMIUM))
    (stx-receiver (default-to tx-sender (get stx-receiver swap))))
    (asserts! (is-eq tx-sender stx-receiver) ERR_INVALID_STX_RECEIVER)
    (asserts! (get ask-priced swap) ERR_NOT_PRICED)
    (asserts! (not (is-eq tx-sender (get stx-sender swap))) ERR_SAME_SENDER_RECEIVER) 
    (asserts! (not (get done swap)) ERR_ALREADY_DONE)
    (match (get expired-height swap)
            some-height (asserts! (>= burn-block-height some-height) ERR_ALREADY_RESERVED) 
            true)
    (print 
      {
        type: "take-ask",
        id: id,
        stxReceiver: (some tx-sender),
        expiredHeight: (some (+ burn-block-height expiry)),
        when: burn-block-height,
      }
    )
    (and (> premium u0) (try! (contract-call? .usda-token transfer premium tx-sender (get stx-sender swap) (some 0x707265746D69756D))))
    (ok (map-set swaps id (merge swap {stx-receiver: (some tx-sender), expired-height: (some (+ burn-block-height expiry)), when: burn-block-height}))))) ;; expiration kicks in

(define-public (make-bid
  (id (optional uint))
  (stx-sender (optional principal))
  (ustx (optional uint))
  (sats uint)
  (premium uint)) ;; allowing the BTC sender to initiate swap offers - without a swap-id
  (begin
    (asserts! (is-none (get-bid tx-sender id)) ERR_OFFER_ALREADY_EXISTS)
    (asserts! (> sats u0) ERR_INVALID_OFFER)
    (match id
      some-id 
        (let ((swap (unwrap! (map-get? swaps some-id) ERR_INVALID_ID))
              (swap-ustx  (get ustx swap)))
          (asserts! (not (get done swap)) ERR_ALREADY_DONE) ;; ability to make a bid even when the swap is reserved
          (and (> premium u0) (try! (contract-call? .usda-token transfer premium tx-sender nexus (some 0x707265746D69756D))))
          (print 
            {
              type: "make-bid",
              id: id,
              stxReceiver: tx-sender,
              stxSender: stx-sender,
              ustx: swap-ustx,
              sats: sats,
              premium: premium,
            }
          )
          (ok (map-set swap-offers 
            { stx-receiver: tx-sender, swap-id: (some some-id) }
            { stx-sender: (some (get stx-sender swap)),
              ustx: swap-ustx,
              sats: sats,
              premium: premium })))
      (begin
        (asserts! (and (is-some ustx) (> (unwrap-panic ustx) u0)) ERR_INVALID_OFFER)
        (and (> premium u0) (try! (contract-call? .usda-token transfer premium tx-sender nexus (some 0x707265746D69756D))))
        (print 
          {
            type: "make-bid",
            id: none,
            stxReceiver: tx-sender,
            stxSender: stx-sender,
            ustx: (unwrap-panic ustx),
            sats: sats,
            premium: premium,
          }
        )
        (ok (map-set swap-offers 
          { stx-receiver: tx-sender, swap-id: none }
          { stx-sender: stx-sender,
            ustx: (unwrap-panic ustx),
            sats: sats,
            premium: premium }))))))

(define-public (take-bid (id uint) (offer-swap-id (optional uint)) (sats uint) (premium uint) (stx-receiver principal))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
        (offer (unwrap! (get-bid stx-receiver offer-swap-id) ERR_NO_SUCH_OFFER))
        (premium-offer (get premium offer))
        (sats-offer (get sats offer))
        (offer-stx-sender (default-to tx-sender (get stx-sender offer))))
    (asserts! (is-eq tx-sender (get stx-sender swap)) ERR_INVALID_STX_SENDER)
    (asserts! (is-eq tx-sender offer-stx-sender) ERR_INVALID_STX_SENDER) ;; important (not redundant and by transitivity...)
    (asserts! (not (is-eq tx-sender stx-receiver)) ERR_SAME_SENDER_RECEIVER) ;; Corrected: bid taker cannot be bid creator
    (asserts! (is-eq (get ustx offer) (get ustx swap)) ERR_USTX)
    (asserts! (is-eq sats-offer sats) ERR_SATS) ;; user agrees to sats-offer
    (asserts! (is-eq premium-offer premium) ERR_PREMIUM) ;; user agrees to premium offer (not the swap)
    (asserts! (not (get done swap)) ERR_ALREADY_DONE)
    (match (get expired-height swap)
            some-height (asserts! (>= burn-block-height some-height) ERR_ALREADY_RESERVED) 
            true) ;; taking bid forbidden before expiration
    (and (> premium u0) (try! (as-contract (contract-call? .usda-token transfer premium tx-sender (get stx-sender swap) (some 0x707265746D69756D))))) ;; nexus releases premium
    (map-delete swap-offers {stx-receiver: stx-receiver, swap-id: offer-swap-id })
    (print 
      {
        type: "take-bid",
        id: id,
        stxReceiver: (some stx-receiver),
        expiredHeight: (some (+ burn-block-height expiry)),
        sats: (some sats),
        premium: (some premium),
        when: burn-block-height,
      }
    )
    (ok (map-set swaps id (merge swap {
      stx-receiver: (some stx-receiver),
      expired-height: (some (+ burn-block-height expiry)),
      sats: (some sats),
      premium: (some premium),
      when: burn-block-height 
    }))))) ;; expiration kicks in

(define-public (cancel-bid (offer-swap-id (optional uint)))
  (let ((offer (unwrap! (get-bid tx-sender offer-swap-id) ERR_NO_SUCH_OFFER))
        (premium (get premium offer))
        (offerer tx-sender))
    (and (> premium u0) (as-contract (try! (contract-call? .usda-token transfer premium tx-sender offerer (some 0x707265746D69756D)))))
    (map-delete swap-offers {stx-receiver: tx-sender, swap-id: offer-swap-id })
    (print
      {
        type: "cancel-bid",
        swapId: offer-swap-id,
        stxReceiver: tx-sender,
      }
    )
    (ok true)))

(define-public (cancel-ask (id uint))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID)))
    (asserts! (is-eq tx-sender (get stx-sender swap)) ERR_INVALID_STX_SENDER)
    (match (get expired-height swap)
            some-height (asserts! (>= burn-block-height some-height) ERR_ALREADY_RESERVED) 
            true) 
    (asserts! (not (get done swap)) ERR_ALREADY_DONE)
    (print 
      {
        type: "cancel-ask",
        id: id,
        askPriced: false,
      }
    )
    (ok (map-set swaps id (merge swap {
      ask-priced: false
    })))))

(define-public (claim-collateral (id uint))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
        (stx-sender (get stx-sender swap)))
    (asserts! (is-eq tx-sender stx-sender) ERR_INVALID_STX_SENDER)
    (match (get expired-height swap)
            some-height (asserts! (>= burn-block-height some-height) ERR_ALREADY_RESERVED) ;; expired
            true) ;; or not reserved 
    (asserts! (not (get done swap)) ERR_ALREADY_DONE)   
    (try! (as-contract (stx-transfer? (get ustx swap) tx-sender stx-sender)))
    (print 
      {
        type: "claim-collateral",
        id: id,
        done: true,
      }
    )
    (ok (map-set swaps id (merge swap {done: true}))))
)

;; (define-public (submit-swap 
;;     (id uint)
;;     (height uint)
;;     (blockheader (buff 32))
;;     (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 })
;;     (fees <fees-trait>)) ;; any user can submit a tx that contains the swap
;;   (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))
;;         (btc-receiver (unwrap! (get btc-receiver swap) ERR_NO_BTC_RECEIVER)))
;;       (asserts! (> burn-block-height (+ (get when swap) cooldown)) ERR_IN_COOLDOWN)
;;       (match (get expired-height swap)
;;               some-height (asserts! (< burn-block-height some-height) ERR_RESERVATION_EXPIRED) ;; not expired
;;               (asserts! false ERR_NOT_RESERVED)) ;; needs to be reserved
;;       (asserts! (is-eq tx-sender stx-receiver) ERR_INVALID_STX_RECEIVER)     
;;       (asserts! (is-eq fees .zero) ERR_INVALID_FEE_CONTRACT)
;;       (asserts! (not (get done swap)) ERR_ALREADY_DONE)
;;       (try! (contract-call? fees pay-fees (get ustx swap)))
;;       (match (contract-call? 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.clarity-bitcoin-lib-v5 was-tx-mined-compact
;;                 height tx-buff blockheader proof )
;;         result
;;           (let
;;             (
;;               ;; (result-is-true (asserts! result ERR_PROOF_FALSE))
;;               (sats (unwrap! (get sats swap) ERR_NOT_PRICED))
              
;;               )
;;             (asserts! (is-none (map-get? submitted-btc-txs result)) ERR_BTC_TX_ALREADY_USED)
;;             (match (get out (unwrap! (get-out-value tx btc-receiver) ERR_NATIVE_FAILURE))
;;               out (if (>= (get value out) sats)
;;                 (begin
;;                       (map-set swaps id (merge swap {done: true}))
;;                       (map-set submitted-btc-txs result id)
;;                       (as-contract (stx-transfer? (get ustx 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)))
;;     (fees <fees-trait>))
;;   (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
;;         (stx-receiver (unwrap! (get stx-receiver swap) ERR_NO_STX_RECEIVER))
;;         (btc-receiver (unwrap! (get btc-receiver swap) ERR_NO_BTC_RECEIVER))
;;         (sats (unwrap! (get sats swap) ERR_NOT_PRICED))
;;         (tx-buff (contract-call? .clarity-bitcoin-helper-wtx concat-wtx wtx witness-data)))
;;       (asserts! (> burn-block-height (+ (get when swap) cooldown)) ERR_IN_COOLDOWN) 
;;       (asserts! (is-eq tx-sender stx-receiver) ERR_INVALID_STX_RECEIVER)
;;       (asserts! (not (get done swap)) ERR_ALREADY_DONE)
;;       (match (get expired-height swap)
;;               some-height (asserts! (< burn-block-height some-height) ERR_RESERVATION_EXPIRED) ;; not expired
;;               (asserts! false ERR_NOT_RESERVED)) ;; needs to be reserved
;;       (asserts! (is-eq fees .zero) ERR_INVALID_FEE_CONTRACT)
;;       (try! (contract-call? fees pay-fees (get ustx swap)))
;;       (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! result ERR_PROOF_FALSE)
;;             (asserts! (is-none (map-get? submitted-btc-txs result)) ERR_BTC_TX_ALREADY_USED)
;;             (match (get out (unwrap! (get-out-value wtx btc-receiver) ERR_NATIVE_FAILURE))
;;               out (if (>= (get value out) sats)
;;                 (begin
;;                       (map-set swaps id (merge swap {done: true}))
;;                       (map-set submitted-btc-txs result id)
;;                       (as-contract (stx-transfer? (get ustx 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-read-only (get-swap (id uint)) ;; read-only function to get swap details by id
  (match (map-get? swaps id)
    swap (ok swap)
    (err ERR_INVALID_ID)))

(define-read-only (get-bid (stx-receiver principal) (id (optional uint)))
  (map-get? swap-offers {stx-receiver: stx-receiver, swap-id: id}))

  (define-public (simulate-swap
    (id uint))
  (let ((swap (unwrap! (map-get? swaps id) ERR_INVALID_ID))
        (stx-sender (get stx-sender swap))
        (stx-receiver (unwrap! (get stx-receiver swap) ERR_NO_STX_RECEIVER))
        (btc-receiver (unwrap! (get btc-receiver swap) ERR_NO_BTC_RECEIVER))
        (sats (unwrap! (get sats swap) ERR_NOT_PRICED)))
      (asserts! (> burn-block-height (+ (get when swap) cooldown)) ERR_IN_COOLDOWN) 
      (asserts! (is-eq tx-sender stx-receiver) ERR_INVALID_STX_RECEIVER)
      (asserts! (not (get done swap)) ERR_ALREADY_DONE)
      (match (get expired-height swap)
              some-height (asserts! (< burn-block-height some-height) ERR_RESERVATION_EXPIRED) ;; not expired
              (asserts! false ERR_NOT_RESERVED)) ;; needs to be reserved
      (print 
        {
          type: "simulate-swap",
          id: id,
          done: true,
        }
      )
      (match (contract-call? .sbtc transfer sats tx-sender stx-sender (some 0x707265746D69756D))
        result
                (begin
                      (map-set swaps id (merge swap {done: true}))  
                      (as-contract (stx-transfer? (get ustx swap) tx-sender (unwrap! (get stx-receiver swap) ERR_NO_STX_RECEIVER))))
        error (err (* error u1000)))))

Functions (12)

FunctionAccessArgs
collateralize-stxpublicustx: uint, btc-receiver: (optional (buff 40
make-askpublicid: uint, sats: uint, btc-receiver: (buff 40
collateralize-and-make-askpublicustx: uint, btc-receiver: (buff 40
take-askpublicid: uint
make-bidpublicid: (optional uint
take-bidpublicid: uint, offer-swap-id: (optional uint
cancel-bidpublicoffer-swap-id: (optional uint
cancel-askpublicid: uint
claim-collateralpublicid: uint
get-swapread-onlyid: uint
get-bidread-onlystx-receiver: principal, id: (optional uint
simulate-swappublicid: uint