Source Code

(define-constant ERR-NOT-AUTHORIZED (err u1000))
(define-constant ERR-UNKNOWN-VALIDATOR (err u1001))
(define-constant ERR-PAUSED (err u1002))
(define-constant ERR-UKNOWN-RELAYER (err u1003))
(define-constant ERR-REQUIRED-VALIDATORS (err u1004))
(define-constant ERR-VALIDATOR-ALREADY-REGISTERED (err u1005))
(define-constant ERR-DUPLICATE-SIGNATURE (err u1006))
(define-constant ERR-ORDER-HASH-MISMATCH (err u1007))
(define-constant ERR-INVALID-SIGNATURE (err u1008))
(define-constant ERR-BITCOIN-TX-NOT-MINED (err u1009))
(define-constant MAX_UINT u340282366920938463463374607431768211455)
(define-constant ONE_8 u100000000)
(define-constant MAX_REQUIRED_VALIDATORS u10)
(define-constant structured-data-prefix 0x534950303138)
(define-constant message-domain-mainnet 0x6d11cd301d11961e7cfeabd61e3f4da17f42f3d627362c8878aa9cbb5c532be2) ;;mainnet
(define-constant message-domain-testnet 0x84deb9a3b41b870d85819000deefa999f43b1bf2c3d80c3ea19d4b83b7b10fbc) ;; testnet
(define-data-var contract-owner principal tx-sender)
(define-map approved-relayers principal bool)
(define-data-var is-paused bool true)
(define-map validators principal (buff 33))
(define-data-var validator-count uint u0)
(define-data-var required-validators uint MAX_UINT)
(define-map tx-validated-by { tx-hash: (buff 32), validator: principal } bool)
(define-data-var tx-hash-to-iter (buff 32) 0x)
(define-public (set-paused (paused bool))
	(begin
		(try! (check-is-owner))
		(ok (var-set is-paused paused))))
(define-public (add-validator (validator-pubkey (buff 33)) (validator principal))
	(begin
		(try! (check-is-owner))
		(asserts! (is-none (map-get? validators validator)) ERR-VALIDATOR-ALREADY-REGISTERED)
		(map-set validators validator validator-pubkey)
		(var-set validator-count (+ u1 (var-get validator-count)))
		(ok (var-get validator-count))))
(define-public (remove-validator (validator principal))
	(begin
		(try! (check-is-owner))
		(asserts! (is-some (map-get? validators validator)) ERR-UNKNOWN-VALIDATOR)
		(map-delete validators validator)
		(var-set validator-count (- (var-get validator-count) u1))
		(ok (var-get validator-count))))
(define-public (approve-relayer (relayer principal) (approved bool))
	(begin
		(try! (check-is-owner))
		(ok (map-set approved-relayers relayer approved))))
(define-public (set-required-validators (new-required-validators uint))
	(begin
		(try! (check-is-owner))
		(asserts! (< new-required-validators MAX_REQUIRED_VALIDATORS) ERR-REQUIRED-VALIDATORS)
		(ok (var-set required-validators new-required-validators))))
(define-public (set-contract-owner (owner principal))
	(begin
		(try! (check-is-owner))
		(ok (var-set contract-owner owner))))
(define-read-only (message-domain)
	(if (is-eq chain-id u1) message-domain-mainnet message-domain-testnet))
(define-read-only (get-contract-owner)
	(var-get contract-owner))
(define-read-only (get-validator-or-fail (validator principal))
	(ok (unwrap! (map-get? validators validator) ERR-UNKNOWN-VALIDATOR)))
(define-read-only (get-required-validators)
	(var-get required-validators))
(define-read-only (hash-tx (tx { bitcoin-tx: (buff 8192), output: uint, offset: uint, tick: (string-utf8 256), amt: uint, from: (buff 128), to: (buff 128), from-bal: uint, to-bal: uint, decimals: uint } ))
	(sha256 (unwrap-panic (to-consensus-buff? tx))))
(define-read-only (get-paused)
	(var-get is-paused))
(define-read-only (get-user-balance-or-default (user (buff 128)) (tick (string-utf8 256)))
	(contract-call? .oracle-registry-v2 get-user-balance-or-default user tick))
(define-read-only (get-tick-decimals-or-default (tick (string-utf8 256)))
	(contract-call? .oracle-registry-v2 get-tick-decimals-or-default tick))
(define-read-only (get-bitcoin-tx-mined-or-fail (tx (buff 8192)))
	(contract-call? .oracle-registry-v2 get-bitcoin-tx-mined-or-fail tx))
(define-read-only (get-bitcoin-tx-indexed-or-fail (bitcoin-tx (buff 8192)) (output uint) (offset uint))
	(contract-call? .oracle-registry-v2 get-bitcoin-tx-indexed-or-fail bitcoin-tx output offset))
(define-read-only (validate-tx (tx-hash (buff 32)) (signature-pack { signer: principal, tx-hash: (buff 32), signature: (buff 65)}))
	(let (
			(validator-pubkey (try! (get-validator-or-fail (get signer signature-pack)))))
		(asserts! (is-none (map-get? tx-validated-by { tx-hash: tx-hash, validator: (get signer signature-pack) })) ERR-DUPLICATE-SIGNATURE)
		(asserts! (is-eq tx-hash (get tx-hash signature-pack)) ERR-ORDER-HASH-MISMATCH)
		(ok (asserts! (is-eq (secp256k1-recover? (sha256 (concat structured-data-prefix (concat (message-domain) tx-hash))) (get signature signature-pack)) (ok validator-pubkey)) ERR-INVALID-SIGNATURE))))
(define-read-only (verify-mined (tx (buff 8192)) (block { header: (buff 80), height: uint }) (proof { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint }))
	(if (is-eq chain-id u1)		
		(let
			(
				(response (if (try! (contract-call? .clarity-bitcoin-v1-07 is-segwit-tx tx)) 
					(contract-call? .clarity-bitcoin-v1-07 was-segwit-tx-mined? block tx proof)
					(contract-call? .clarity-bitcoin-v1-07 was-tx-mined? block tx proof))))
			(if (or (is-err response) (not (unwrap-panic response)))
				ERR-BITCOIN-TX-NOT-MINED
				(ok true)))
		;; if not mainnet, assume verified
		(ok true)))
(define-public (index-tx-many
		(tx-many (list 25 {
			tx: { bitcoin-tx: (buff 8192), output: uint, offset: uint, tick: (string-utf8 256), amt: uint, from: (buff 128), to: (buff 128), from-bal: uint, to-bal: uint, decimals: uint },
			block: { header: (buff 80), height: uint },
			proof: { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint },
			signature-packs: (list 10 { signer: principal, tx-hash: (buff 32), signature: (buff 65) })})))
	(begin
		(asserts! (not (var-get is-paused)) ERR-PAUSED)
		(asserts! (is-some (map-get? approved-relayers tx-sender)) ERR-UKNOWN-RELAYER)
		(fold index-tx-iter tx-many (ok true))))
(define-private (validate-signature-iter
		(signature-pack { signer: principal, tx-hash: (buff 32), signature: (buff 65)})
		(previous-response (response bool uint)))
	(match previous-response
		prev-ok
		(begin
			(try! (validate-tx (var-get tx-hash-to-iter) signature-pack))
			(ok (map-set tx-validated-by { tx-hash: (var-get tx-hash-to-iter), validator: (get signer signature-pack) } true)))
		prev-err
		previous-response))
(define-private (index-tx-iter
		(signed-tx {
			tx: { bitcoin-tx: (buff 8192), output: uint, offset: uint, tick: (string-utf8 256), amt: uint, from: (buff 128), to: (buff 128), from-bal: uint, to-bal: uint, decimals: uint },
			block: { header: (buff 80), height: uint },
			proof: { tx-index: uint, hashes: (list 14 (buff 32)), tree-depth: uint },
			signature-packs: (list 10 { signer: principal, tx-hash: (buff 32), signature: (buff 65)}) })
		(previous-response (response bool uint)))
	(match previous-response
		prev-ok
		(let (
				(tx (get tx signed-tx))
				(signature-packs (get signature-packs signed-tx))
				(tx-hash (hash-tx tx))
				(from-bal (get-user-balance-or-default (get from tx) (get tick tx)))
				(to-bal (get-user-balance-or-default (get to tx) (get tick tx)))
				(height (get height (get block signed-tx)))
			)
			(asserts! (>= (len signature-packs) (var-get required-validators)) ERR-REQUIRED-VALIDATORS)
			(and (is-err (get-bitcoin-tx-mined-or-fail (get bitcoin-tx tx))) 
				(begin 
					(try! (verify-mined (get bitcoin-tx tx) (get block signed-tx) (get proof signed-tx)))
					(as-contract (try! (contract-call? .oracle-registry-v2 set-tx-mined (get bitcoin-tx tx) (get height (get block signed-tx)))))
				)
			)
			(var-set tx-hash-to-iter tx-hash)
			(try! (fold validate-signature-iter signature-packs (ok true)))
			(as-contract (try! (contract-call? .oracle-registry-v2 set-tx-indexed { tx-hash: (get bitcoin-tx tx), output: (get output tx), offset: (get offset tx) } { tick: (get tick tx), amt: (get amt tx), from: (get from tx), to: (get to tx) })))
			(and (> height (get up-to-block from-bal)) (as-contract (try! (contract-call? .oracle-registry-v2 set-user-balance { user: (get from tx), tick: (get tick tx) } { balance: (get from-bal tx), up-to-block: height }))))
			(and (> height (get up-to-block to-bal)) (as-contract (try! (contract-call? .oracle-registry-v2 set-user-balance { user: (get to tx), tick: (get tick tx) } { balance: (get to-bal tx), up-to-block: height }))))
			(as-contract (try! (contract-call? .oracle-registry-v2 set-tick-decimals (get tick tx) (get decimals tx))))
			(ok true))
		prev-err
		previous-response))
(define-private (check-is-owner)
	(ok (asserts! (is-eq (var-get contract-owner) tx-sender) ERR-NOT-AUTHORIZED)))

Functions (18)

FunctionAccessArgs
set-pausedpublicpaused: bool
add-validatorpublicvalidator-pubkey: (buff 33
remove-validatorpublicvalidator: principal
approve-relayerpublicrelayer: principal, approved: bool
set-required-validatorspublicnew-required-validators: uint
set-contract-ownerpublicowner: principal
message-domainread-only
get-contract-ownerread-only
get-validator-or-failread-onlyvalidator: principal
get-required-validatorsread-only
get-pausedread-only
get-user-balance-or-defaultread-onlyuser: (buff 128
get-tick-decimals-or-defaultread-onlytick: (string-utf8 256
get-bitcoin-tx-mined-or-failread-onlytx: (buff 8192
get-bitcoin-tx-indexed-or-failread-onlybitcoin-tx: (buff 8192
validate-txread-onlytx-hash: (buff 32
verify-minedread-onlytx: (buff 8192
check-is-ownerprivate