Source Code

;; Stacks Name Service (SNS)
;; Decentralized naming system for Stacks - register .stx domains

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-name-taken (err u101))
(define-constant err-name-not-found (err u102))
(define-constant err-not-name-owner (err u103))
(define-constant err-name-expired (err u104))
(define-constant err-invalid-name (err u105))
(define-constant err-insufficient-payment (err u106))
(define-constant err-name-too-short (err u107))
(define-constant err-name-too-long (err u108))

;; Pricing (in microSTX)
(define-constant price-1-char u100000000000) ;; 100000 STX for 1 char
(define-constant price-2-char u10000000000)  ;; 10000 STX for 2 chars
(define-constant price-3-char u1000000000)   ;; 1000 STX for 3 chars
(define-constant price-4-char u100000000)    ;; 100 STX for 4 chars
(define-constant price-5-plus u10000000)     ;; 10 STX for 5+ chars

;; Registration period: ~1 year in blocks (assuming 10 min blocks)
(define-constant registration-period u52560)

;; Treasury
(define-constant treasury 'SP2PEBKJ2W1ZDDF2QQ6Y4FXKZEDPT9J9R2NKD9WJB)

;; Data Variables
(define-data-var total-registrations uint u0)
(define-data-var total-revenue uint u0)

;; Name Registry
(define-map names (string-ascii 64)
  {
    owner: principal,
    resolver: principal,
    registered-at: uint,
    expires-at: uint,
    records: (optional (string-utf8 256))
  }
)

;; Reverse lookup: principal -> primary name
(define-map primary-names principal (string-ascii 64))

;; Name ownership count
(define-map name-counts principal uint)

;; Read-only functions
(define-read-only (get-name-info (name (string-ascii 64)))
  (map-get? names name)
)

(define-read-only (get-owner (name (string-ascii 64)))
  (match (map-get? names name)
    info (some (get owner info))
    none
  )
)

(define-read-only (get-resolver (name (string-ascii 64)))
  (match (map-get? names name)
    info (some (get resolver info))
    none
  )
)

(define-read-only (is-available (name (string-ascii 64)))
  (match (map-get? names name)
    info (> stacks-block-height (get expires-at info))
    true
  )
)

(define-read-only (get-primary-name (owner principal))
  (map-get? primary-names owner)
)

(define-read-only (get-name-count (owner principal))
  (default-to u0 (map-get? name-counts owner))
)

(define-read-only (get-price (name (string-ascii 64)))
  (let ((len (len name)))
    (if (is-eq len u1) price-1-char
      (if (is-eq len u2) price-2-char
        (if (is-eq len u3) price-3-char
          (if (is-eq len u4) price-4-char
            price-5-plus
          )
        )
      )
    )
  )
)

(define-read-only (get-expiry (name (string-ascii 64)))
  (match (map-get? names name)
    info (some (get expires-at info))
    none
  )
)

(define-read-only (is-expired (name (string-ascii 64)))
  (match (map-get? names name)
    info (> stacks-block-height (get expires-at info))
    true
  )
)

(define-read-only (get-stats)
  {
    total-registrations: (var-get total-registrations),
    total-revenue: (var-get total-revenue),
    current-block: stacks-block-height
  }
)

;; Public functions

;; Register a new name
(define-public (register (name (string-ascii 64)))
  (let (
    (price (get-price name))
    (name-len (len name))
  )
    ;; Validate name
    (asserts! (>= name-len u1) err-name-too-short)
    (asserts! (<= name-len u64) err-name-too-long)
    (asserts! (is-available name) err-name-taken)
    (asserts! (>= (stx-get-balance tx-sender) price) err-insufficient-payment)
    
    ;; Transfer payment
    (try! (stx-transfer? price tx-sender treasury))
    
    ;; Register name
    (map-set names name {
      owner: tx-sender,
      resolver: tx-sender,
      registered-at: stacks-block-height,
      expires-at: (+ stacks-block-height registration-period),
      records: none
    })
    
    ;; Update primary name if first name
    (if (is-eq (get-name-count tx-sender) u0)
      (map-set primary-names tx-sender name)
      true
    )
    
    ;; Update counts
    (map-set name-counts tx-sender (+ (get-name-count tx-sender) u1))
    (var-set total-registrations (+ (var-get total-registrations) u1))
    (var-set total-revenue (+ (var-get total-revenue) price))
    
    (ok { name: name, expires-at: (+ stacks-block-height registration-period), price: price })
  )
)

;; Renew a name
(define-public (renew (name (string-ascii 64)))
  (match (map-get? names name)
    info
    (let ((price (get-price name)))
      (asserts! (is-eq (get owner info) tx-sender) err-not-name-owner)
      (asserts! (>= (stx-get-balance tx-sender) price) err-insufficient-payment)
      
      ;; Transfer payment
      (try! (stx-transfer? price tx-sender treasury))
      
      ;; Extend expiry
      (map-set names name 
        (merge info { expires-at: (+ (get expires-at info) registration-period) })
      )
      
      (var-set total-revenue (+ (var-get total-revenue) price))
      
      (ok { name: name, new-expiry: (+ (get expires-at info) registration-period) })
    )
    err-name-not-found
  )
)

;; Transfer name to new owner
(define-public (transfer (name (string-ascii 64)) (new-owner principal))
  (match (map-get? names name)
    info
    (begin
      (asserts! (is-eq (get owner info) tx-sender) err-not-name-owner)
      (asserts! (not (is-expired name)) err-name-expired)
      
      ;; Update ownership
      (map-set names name (merge info { owner: new-owner }))
      
      ;; Update name counts
      (map-set name-counts tx-sender (- (get-name-count tx-sender) u1))
      (map-set name-counts new-owner (+ (get-name-count new-owner) u1))
      
      ;; Update primary name for new owner if they don't have one
      (if (is-eq (get-name-count new-owner) u1)
        (map-set primary-names new-owner name)
        true
      )
      
      ;; Clear primary name if it was transferred
      (match (map-get? primary-names tx-sender)
        primary (if (is-eq primary name) (map-delete primary-names tx-sender) false)
        false
      )
      
      (ok { name: name, new-owner: new-owner })
    )
    err-name-not-found
  )
)

;; Set resolver address
(define-public (set-resolver (name (string-ascii 64)) (resolver principal))
  (match (map-get? names name)
    info
    (begin
      (asserts! (is-eq (get owner info) tx-sender) err-not-name-owner)
      (asserts! (not (is-expired name)) err-name-expired)
      
      (map-set names name (merge info { resolver: resolver }))
      (ok true)
    )
    err-name-not-found
  )
)

;; Set records (IPFS hash, website, etc.)
(define-public (set-records (name (string-ascii 64)) (records (string-utf8 256)))
  (match (map-get? names name)
    info
    (begin
      (asserts! (is-eq (get owner info) tx-sender) err-not-name-owner)
      (asserts! (not (is-expired name)) err-name-expired)
      
      (map-set names name (merge info { records: (some records) }))
      (ok true)
    )
    err-name-not-found
  )
)

;; Set primary name
(define-public (set-primary-name (name (string-ascii 64)))
  (match (map-get? names name)
    info
    (begin
      (asserts! (is-eq (get owner info) tx-sender) err-not-name-owner)
      (asserts! (not (is-expired name)) err-name-expired)
      
      (map-set primary-names tx-sender name)
      (ok true)
    )
    err-name-not-found
  )
)

Functions (16)

FunctionAccessArgs
get-name-inforead-onlyname: (string-ascii 64
get-ownerread-onlyname: (string-ascii 64
get-resolverread-onlyname: (string-ascii 64
is-availableread-onlyname: (string-ascii 64
get-primary-nameread-onlyowner: principal
get-name-countread-onlyowner: principal
get-priceread-onlyname: (string-ascii 64
get-expiryread-onlyname: (string-ascii 64
is-expiredread-onlyname: (string-ascii 64
get-statsread-only
registerpublicname: (string-ascii 64
renewpublicname: (string-ascii 64
transferpublicname: (string-ascii 64
set-resolverpublicname: (string-ascii 64
set-recordspublicname: (string-ascii 64
set-primary-namepublicname: (string-ascii 64