Source Code

(define-constant err-unauthorized (err u6000))
(define-constant err-paused (err u6001))
(define-constant err-peg-in-address-not-found (err u6002))
(define-constant err-invalid-amount (err u6003))
(define-constant err-invalid-tx (err u6004))
(define-constant err-already-sent (err u6005))
(define-constant err-bitcoin-tx-not-mined (err u6006))
(define-constant err-invalid-input (err u6007))
(define-constant err-withdrawal-does-not-exist (err u6008))
(define-constant err-output-index-out-of-bounds (err u6009))
(define-constant err-order-index-out-of-bounds (err u6010))
(define-constant err-invalid-order-script (err u6011))
(define-constant err-reading-varint (err u6012))

(define-constant success (ok true))

(define-constant one-12 u1000000000000)
(define-constant sats-to-precision u10000)

(define-data-var contract-owner principal tx-sender)

(define-public (deposit
	(tx (buff 4096))
	(block { header: (buff 80), height: uint })
	(proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint })
	(output-idx uint)
	(order-idx uint))
	(let (
		(common-check (try! (verify-mined tx block proof)))
		(parsed-tx (try! (extract-tx-ins-outs tx)))
		(output (unwrap! (element-at (get outs parsed-tx) output-idx) err-output-index-out-of-bounds))
		(amount (get value output))
		(peg-in-address (get scriptPubKey output))
		(order-script (get scriptPubKey (unwrap! (element-at? (get outs parsed-tx) order-idx) err-order-index-out-of-bounds)))
		(fee (mul-sats-with-ratio-to-sats amount (contract-call? .peg-data get-peg-in-fee)))
		(amount-net (- amount fee))
		(recipient (try! (decode-order-0 order-script)))
		(btc-to-btcz-ratio (get-btc-to-btcz-ratio))
		(btcz-to-receive (div-sats-with-ratio amount-net btc-to-btcz-ratio))
	)
		(asserts! (not (contract-call? .peg-data is-peg-in-paused)) err-paused)
		(asserts! (not (contract-call? .btc-registry get-peg-in-sent tx output-idx)) err-already-sent)
		(asserts! (contract-call? .btc-registry is-peg-in-address-approved peg-in-address) err-peg-in-address-not-found)
		(asserts! (> amount-net u0) err-invalid-amount)

		(try! (set-total-btc (+ (get-total-btc) amount-net)))

		(try! (contract-call? .btc-registry set-peg-in-sent tx output-idx true))
		(try! (contract-call? .token-btc mint btcz-to-receive recipient))

		(print { action: "deposit", data: { tx-id: (get-txid tx), tx: tx, btcz-to-receive: btcz-to-receive, fee: fee, amount-net: amount-net, recipient: recipient, peg-in-address: peg-in-address, amount: amount } })
		(ok { order-script: order-script })
	)
)

(define-public (init-withdraw
	(peg-out-address (buff 128))
	(btcz-amount uint)
	)
	(let (
		(sender contract-caller)
		(redeemable-btc (get-redeemable-btc-by-amount btcz-amount))
		(fee (mul-sats-with-ratio-to-sats redeemable-btc (get-peg-out-fee)))
		(gas-fee (get-peg-out-gas-fee))
		(check-amount (asserts! (> redeemable-btc (+ fee gas-fee)) err-invalid-amount))
		(amount-net (- redeemable-btc fee gas-fee))
		(next-nonce (get-next-withdrawal-nonce))
		(withdraw-data {
			btc-amount: amount-net,
			btcz-amount: btcz-amount,
			peg-out-address: peg-out-address,
			requested-by: sender,
			fee: fee,
			gas-fee: gas-fee,
			finalized: false,
			requested-at: block-height,
			requested-at-burn-height: burn-block-height,
		})
	)
		(asserts! (not (contract-call? .peg-data is-peg-out-paused)) err-paused)
		(try! (contract-call? .token-btc burn btcz-amount sender))
		(try! (set-total-btc (- (get-total-btc) redeemable-btc)))

		(try! (set-withdrawal next-nonce withdraw-data))
		(try! (contract-call? .stacking-data set-withdrawal-nonce next-nonce))

		(print { action: "init-withdraw", data: { withdraw-data: withdraw-data, nonce: next-nonce } })
		(ok next-nonce)
	)
)

;; called by protocol
(define-public (finalize-withdraw (withdrawal-id uint))
	(let (
		(withdraw-data (unwrap! (contract-call? .stacking-data get-withdrawal withdrawal-id) err-withdrawal-does-not-exist))
	)
		(try! (is-contract-owner))
		(asserts! (not (get finalized withdraw-data)) err-already-sent)

		(try! (set-withdrawal withdrawal-id (merge withdraw-data { finalized: true })))
		(print { action: "finalize-withdraw", data: { withdraw-data: withdraw-data, withdrawal-id: withdrawal-id, finalize-height: burn-block-height } })
		success
	)
)

(define-public (add-rewards (btc-amount uint))
	(let (
		(new-total-btc (+ (get-total-btc) btc-amount))
	)
		(try! (is-contract-owner))

		(try! (set-total-btc new-total-btc))
		(print { action: "add-rewards", data: { new-total-btc: new-total-btc, btc-amount: btc-amount } })
		success
	)
)

(define-read-only (get-btc-to-btcz-ratio)
	(let (
		(btc-amount (get-total-btc))
		(btcz-supply (unwrap-panic (contract-call? .token-btc get-total-supply)))
	)
		(if (is-eq btcz-supply u0)
			one-12
			(div-sats-with-ratio btc-amount btcz-supply)
		)
	)
)

(define-read-only (get-next-withdrawal-nonce)
	(+ (contract-call? .stacking-data get-withdrawal-nonce) u1))

(define-read-only (get-redeemable-btc-by-amount (btcz-amount uint))
	(mul-btcz-with-ratio-to-sats btcz-amount (get-btc-to-btcz-ratio)))

(define-read-only (get-redeemable-btc-by-amount-after-fees (btcz-amount uint))
	(let (
		(redeemable-btc (get-redeemable-btc-by-amount btcz-amount))
		(fee (mul-sats-with-ratio-to-sats redeemable-btc (get-peg-out-fee)))
		(gas-fee (get-peg-out-gas-fee))
		(check-amount (asserts! (> redeemable-btc (+ fee gas-fee)) err-invalid-amount))
		(amount-net (- redeemable-btc fee gas-fee))
	)
		(ok amount-net)
	)
)

(define-read-only (get-redeemable-btc (user principal))
	(get-redeemable-btc-by-amount (unwrap-panic (contract-call? .token-btc get-balance user)))
)

(define-read-only (get-redeemable-btc-after-fees (user principal))
	(get-redeemable-btc-by-amount-after-fees (unwrap-panic (contract-call? .token-btc get-balance user)))
)

(define-read-only (is-contract-owner)
	(ok (asserts! (is-eq (var-get contract-owner) contract-caller) err-unauthorized)))

(define-public (set-contract-owner (new-contract-owner principal))
	(begin
		(try! (is-contract-owner))
		(print { action: "set-contract-owner", data: { new-contract-owner: new-contract-owner } })
		(ok (var-set contract-owner new-contract-owner))))

(define-read-only (mul-sats-with-ratio (sats uint) (ratio uint))
	(/ (* (* sats sats-to-precision) ratio) one-12))

(define-read-only (mul-sats-with-ratio-to-sats (sats uint) (ratio uint))
	(/ (* sats ratio) one-12))

(define-read-only (mul-btcz-with-ratio-to-sats (btcz uint) (ratio uint))
	(/ (/ (* btcz ratio) one-12) sats-to-precision))

(define-read-only (div-sats-with-ratio (sats uint) (ratio uint))
	(/ (* (* sats sats-to-precision) one-12) ratio))

(define-read-only (div-sats-with-ratio-to-sats (sats uint) (ratio uint))
	(/ (* sats one-12) ratio))

;; stacking data
(define-read-only (get-peg-out-fee)
	(contract-call? .peg-data get-peg-out-fee))

(define-read-only (get-peg-out-gas-fee)
	(contract-call? .peg-data get-peg-out-gas-fee))

;; btc data
(define-read-only (get-total-btc)
	(contract-call? .stacking-data get-total-btc))

(define-private (set-total-btc (total-btc uint))
	(contract-call? .stacking-data set-total-btc total-btc))

(define-private (set-withdrawal
	(withdrawal-id uint)
	(new-withdrawal {
		btc-amount: uint,
		btcz-amount: uint,
		peg-out-address: (buff 128),
		requested-by: principal,
		fee: uint,
		gas-fee: uint,
		finalized: bool,
		requested-at: uint,
		requested-at-burn-height: uint
	}))
	(contract-call? .stacking-data set-withdrawal withdrawal-id new-withdrawal)
)

;; bitcoin parsing functions
(define-read-only (extract-tx-ins-outs (tx (buff 4096)))
	(if (try! (contract-call? .clarity-bitcoin-v1-02 is-segwit-tx tx))
		(let (
			(parsed-tx (unwrap! (contract-call? .clarity-bitcoin-v1-02 parse-wtx tx) err-invalid-tx)))
			(ok { ins: (get ins parsed-tx), outs: (get outs parsed-tx) }))
		(let (
			(parsed-tx (unwrap! (contract-call? .clarity-bitcoin-v1-02 parse-tx tx) err-invalid-tx)))
			(ok { ins: (get ins parsed-tx), outs: (get outs parsed-tx) }))
	))

(define-read-only (get-txid (tx (buff 4096)))
	(if (try! (contract-call? .clarity-bitcoin-v1-02 is-segwit-tx tx))
		(ok (contract-call? .clarity-bitcoin-v1-02 get-segwit-txid tx))
		(ok (contract-call? .clarity-bitcoin-v1-02 get-txid tx))
	))

(define-read-only (verify-mined (tx (buff 4096)) (block { header: (buff 80), height: uint }) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }))
	(if is-in-mainnet
		(let (
			(response (if (try! (contract-call? .clarity-bitcoin-v1-02 is-segwit-tx tx))
				(contract-call? .clarity-bitcoin-v1-02 was-segwit-tx-mined? block tx proof)
				(contract-call? .clarity-bitcoin-v1-02 was-tx-mined? block tx proof))))
			(if (or (is-err response) (not (unwrap-panic response)))
				err-bitcoin-tx-not-mined
				success
			))
		success)) ;; if not mainnet, assume verified

;; data output parse helpers
(define-read-only (decode-order-0 (order-script (buff 128)))
	(let (
		(op-code (unwrap! (slice? order-script u1 u2) err-invalid-order-script)))
		(ok (unwrap! (from-consensus-buff? principal (unwrap! (slice? order-script (if (< op-code 0x4c) u2 u3) (len order-script)) err-reading-varint)) err-invalid-input))))

Functions (25)

FunctionAccessArgs
depositpublictx: (buff 4096
init-withdrawpublicpeg-out-address: (buff 128
finalize-withdrawpublicwithdrawal-id: uint
add-rewardspublicbtc-amount: uint
get-btc-to-btcz-ratioread-only
get-next-withdrawal-nonceread-only
get-redeemable-btc-by-amountread-onlybtcz-amount: uint
get-redeemable-btc-by-amount-after-feesread-onlybtcz-amount: uint
get-redeemable-btcread-onlyuser: principal
get-redeemable-btc-after-feesread-onlyuser: principal
is-contract-ownerread-only
set-contract-ownerpublicnew-contract-owner: principal
mul-sats-with-ratioread-onlysats: uint, ratio: uint
mul-sats-with-ratio-to-satsread-onlysats: uint, ratio: uint
mul-btcz-with-ratio-to-satsread-onlybtcz: uint, ratio: uint
div-sats-with-ratioread-onlysats: uint, ratio: uint
div-sats-with-ratio-to-satsread-onlysats: uint, ratio: uint
get-peg-out-feeread-only
get-peg-out-gas-feeread-only
get-total-btcread-only
set-total-btcprivatetotal-btc: uint
extract-tx-ins-outsread-onlytx: (buff 4096
get-txidread-onlytx: (buff 4096
verify-minedread-onlytx: (buff 4096
decode-order-0read-onlyorder-script: (buff 128