Source Code

;; This contract exposes functions for interacting with the
;; BNS L1<->L2 bridge.

(define-data-var signer-var principal tx-sender)
(define-data-var signer-pubkey-hash-var (buff 20)
  (get-pubkey-hash tx-sender))

;; Errors

(define-constant ERR_INVALID_BLOCK (err u1200))
(define-constant ERR_RECOVER (err u1201))
(define-constant ERR_INVALID_SIGNER (err u1202))
(define-constant ERR_NAME_NOT_MIGRATED (err u1203))
(define-constant ERR_TRANSFER (err u1204))
(define-constant ERR_INVALID_BURN_ADDRESS (err u1205))

;; Public functions

(define-public (bridge-to-l1
    (name (buff 48))
    (namespace (buff 20))
    (inscription-id (buff 35))
    (signature (buff 65))
  )
  (let
    (
      ;; #[filter(name, namespace, inscription-id, signature)]
      (name-id (unwrap! (contract-call? .bnsx-registry get-id-for-name { name: name, namespace: namespace }) ERR_NAME_NOT_MIGRATED))
    )
    (match (contract-call? .bnsx-registry transfer name-id tx-sender .l1-registry)
      res (handle-bridge-to-v1
        name-id
        name
        namespace
        inscription-id
        signature
      )
      e (begin
        (print {
          topic: "transfer-error",
          error: e,
        })
        ERR_TRANSFER
      )
    )
  )
)

(define-public (migrate-and-bridge
    (name (buff 48))
    (namespace (buff 20))
    (inscription-id (buff 35))
    (bridge-signature (buff 65))
    (wrapper principal)
    (migrate-signature (buff 65))
  )
  (let
    (
      (name-details (try! (contract-call? .wrapper-migrator-v2 migrate wrapper migrate-signature .l1-registry)))
      (name-id (get id name-details))
    )
    ;; #[allow(unchecked_data)]
    (handle-bridge-to-v1 name-id name namespace inscription-id bridge-signature)
  )
)

(define-private (handle-bridge-to-v1
    (name-id uint)
    (name (buff 48))
    (namespace (buff 20))
    (inscription-id (buff 35))
    (signature (buff 65))
  )
  (begin
    (try! (validate-wrap-signature name namespace inscription-id signature))
    (try! (contract-call? .l1-registry wrap name-id tx-sender inscription-id))
    (ok true)
  )
)

(define-public (bridge-to-l2
    (inscription-id (buff 35))
    (recipient principal)
    (signature (buff 65))
  )
  (let
    (
      (expected-output (generate-burn-output recipient))
    )
    (try! (validate-unwrap-signature inscription-id recipient expected-output signature))
    (try! (contract-call? .l1-registry unwrap inscription-id recipient))
    (ok true)
  )
)

;; Signature validation

;; #[filter(signature)]
(define-read-only (validate-wrap-signature
    (name (buff 48))
    (namespace (buff 20))
    (inscription-id (buff 35))
    (signature (buff 65))
  )
  (let
    (
      (hash (hash-wrap-data name namespace inscription-id))
      (pubkey (unwrap! (secp256k1-recover? hash signature) ERR_RECOVER))
      (pubkey-hash (hash160 pubkey))
    )
    (asserts! (is-eq (var-get signer-pubkey-hash-var) pubkey-hash) ERR_INVALID_SIGNER)
    (ok true)
  )
)

;; (define-read-only (validate-block-hash (height uint) (header-hash (buff 32)))
;;   (let
;;     (
;;       (block-hash (unwrap! (get-block-info? header-hash height) ERR_INVALID_BLOCK))
;;     )
;;     (asserts! (is-eq block-hash header-hash) ERR_INVALID_BLOCK)
;;     (ok true)
;;   )
;; )

(define-read-only (hash-for-height (height uint))
  (unwrap-panic (get-block-info? header-hash height))
)

(define-read-only (hash-wrap-data
    (name (buff 48))
    (namespace (buff 20))
    (inscription-id (buff 35))
  )
  (sha256 (unwrap-panic (to-consensus-buff? {
    name: name,
    namespace: namespace,
    inscription-id: inscription-id,
  })))
)

(define-read-only (hash-unwrap-data (inscription-id (buff 35)) (owner (buff 34)))
  (sha256 (unwrap-panic (to-consensus-buff? {
    inscription-id: inscription-id,
    owner: owner
  })))
)

(define-read-only (validate-unwrap-signature
    (inscription-id (buff 35))
    (recipient principal)
    (owner (buff 34))
    (signature (buff 65))
  )
  (let
    (
      (hash (hash-unwrap-data inscription-id owner))
      (pubkey (unwrap! (secp256k1-recover? hash signature) ERR_RECOVER))
      (pubkey-hash (hash160 pubkey))
      (expected-output (generate-burn-output recipient))
    )
    (asserts! (is-eq pubkey-hash (var-get signer-pubkey-hash-var)) ERR_INVALID_SIGNER)
    (asserts! (is-eq expected-output owner) ERR_INVALID_BURN_ADDRESS)
    (ok true)
  )
)

;; Signer management functions

;; #[allow(unchecked_data)]
(define-private (set-signer-inner (signer principal))
  (let
    (
      (pubkey (get-pubkey-hash signer))
    )
    (var-set signer-var signer)
    (var-set signer-pubkey-hash-var pubkey)
    true
  )
)

;; #[allow(unchecked_data)]
(define-private (get-pubkey-hash (addr principal))
  (get hash-bytes (unwrap-panic (principal-destruct? addr)))
)

(define-public (update-signer (signer principal))
  (begin
    (asserts! (is-eq (var-get signer-var) tx-sender) ERR_INVALID_SIGNER)
    (set-signer-inner signer)
    (ok true)
  )
)

(define-read-only (get-signer) (var-get signer-var))

(define-public (update-registry-extension (new-extension principal))
  (begin
    (asserts! (is-eq (var-get signer-var) tx-sender) ERR_INVALID_SIGNER)
    (try! (contract-call? .l1-registry update-extension new-extension))
    (ok new-extension)
  )
)

;; Burn script helpers

(define-read-only (burn-script-data (recipient principal))
  (unwrap-panic (to-consensus-buff? {
    recipient: recipient,
    topic: "burn",
    bridge: (as-contract tx-sender),
  }))
)

(define-read-only (hash-burn-script-data (recipient principal))
  (hash160 (burn-script-data recipient))
)

(define-read-only (generate-burn-script (recipient principal))
  (let
    (
      (data-hash (hash-burn-script-data recipient))
      (pushdata (concat 0x14 data-hash))
    )
    (concat pushdata 0x7500) ;; OP_DROP, OP_FALSE
  )
)

(define-read-only (generate-burn-output (recipient principal))
  (let
    (
      (script (generate-burn-script recipient))
    )
    (concat 0x0020 (sha256 script))
  )
)

Functions (19)

FunctionAccessArgs
bridge-to-l1publicname: (buff 48
migrate-and-bridgepublicname: (buff 48
handle-bridge-to-v1privatename-id: uint, name: (buff 48
bridge-to-l2publicinscription-id: (buff 35
validate-wrap-signatureread-onlyname: (buff 48
validate-block-hashread-onlyheight: uint, header-hash: (buff 32
hash-for-heightread-onlyheight: uint
hash-wrap-dataread-onlyname: (buff 48
hash-unwrap-dataread-onlyinscription-id: (buff 35
validate-unwrap-signatureread-onlyinscription-id: (buff 35
set-signer-innerprivatesigner: principal
get-pubkey-hashprivateaddr: principal
update-signerpublicsigner: principal
get-signerread-only
update-registry-extensionpublicnew-extension: principal
burn-script-dataread-onlyrecipient: principal
hash-burn-script-dataread-onlyrecipient: principal
generate-burn-scriptread-onlyrecipient: principal
generate-burn-outputread-onlyrecipient: principal