Source Code


;; wannabe rNFT contract
(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)
;;
;; =========
;; CONSTANTS
;; =========
;;

;; Contract owner
(define-constant contract-owner tx-sender)

;; ERROR DEFINITIONS
;;


;; referral code was already used to mint rNFT
(define-constant err-refcode-used (err u3001))

;; generic invalid argument error
(define-constant err-invalid-argument (err u3002))

;; invalid token id
(define-constant err-invalid-token-id (err u3003))

;; already exists
(define-constant err-already-exists (err u3004))

;; referral code too length error
(define-constant err-invalid-length (err u3005))

;; recipient invalid i.e. attempted to transfer to self
(define-constant err-invalid-recipient (err u3006))

;; numeric argument is out of allowed range
(define-constant err-argument-out-of-range (err u3007))

;; user not allowed to perform transfer
(define-constant err-forbidden (err u403))


;; asset not found
(define-constant err-not-found (err u404))
;;
;; ==================
;; DATA MAPS AND VARS
;; ==================
;;
(define-non-fungible-token rnft uint)
(define-data-var last-token-id uint u0)

;; define token to token invitation relationship
(define-map token-invitation-meta uint { first-invited-id: uint,
            last-invited-id: uint })
(define-map bt-allocation uint {
            parent: uint,
            alloc-left: (optional bool)
            })
(define-map child-map uint {
            left-child: (optional uint),
            right-child: (optional uint)
            })

;; first owner of minted token
(define-map first-owner uint principal)


;; maps token id to referral code
(define-map token-ids uint (string-utf8 150))
(define-map ref-codes (string-utf8 150) uint)

;; uri mapping
(define-map token-uri uint (string-ascii 256))




;;
;; =================
;; PRIVATE FUNCTIONS
;; =================
;;

(define-private (is-valid-token-id (token-id uint))
  (and (> token-id u0) (<= token-id (var-get last-token-id))))


;;
;; ================
;; PUBLIC FUNCTIONS
;; ================
;;

;; Registering invites into holding tank
(define-public (invite-token (inviting-token-id uint)
                             (invited-token-id uint))
    (let (
          (inviting-meta (map-get? token-invitation-meta inviting-token-id))
          (prev-alloc (map-get? bt-allocation invited-token-id))
          )
     (try! (contract-call? .sys-admin assert-invoked-by-operator))
     (asserts! (not (is-eq inviting-token-id invited-token-id))
               err-invalid-argument)
     (asserts! (is-valid-token-id inviting-token-id) err-invalid-token-id)
     (asserts! (is-valid-token-id invited-token-id) err-invalid-token-id)
     (if (is-some prev-alloc) err-already-exists
         (let (
               (first (match inviting-meta meta 
                             (if (> invited-token-id (get first-invited-id meta))
                                 (get first-invited-id meta) invited-token-id)
                             invited-token-id))
               (last (match inviting-meta meta
                            (if (> invited-token-id (get last-invited-id meta))
                                invited-token-id (get last-invited-id meta))
                            invited-token-id))
               )
           (map-set token-invitation-meta inviting-token-id 
                    {  first-invited-id: first,
                       last-invited-id: last })
           (ok (map-insert bt-allocation invited-token-id 
                           { parent: inviting-token-id,
                           alloc-left: none }))
           )
         )
     )
  )

;; Allocating token from holding tank to right or left node of a token
(define-public (move-from-ht-to-node (ht-node uint) (ht-owner uint)
                                     (dest-node uint)
                                     (left-branch bool))
    (let (
          (ht-alloc (unwrap! (map-get? bt-allocation ht-node) err-not-found))
          (dest-map (default-to { left-child: none, right-child: none }
                                (map-get? child-map dest-node)
                                ))
          )
      (try! (contract-call? .sys-admin assert-invoked-by-operator))
      (asserts! (is-valid-token-id ht-node) err-invalid-token-id)
      (asserts! (is-valid-token-id ht-owner) err-invalid-token-id)
      (asserts! (is-valid-token-id dest-node) err-invalid-token-id)
      (asserts! (is-in-holding-tank ht-node ht-owner) err-invalid-argument)
      (asserts! (not (is-eq ht-node dest-node)) err-invalid-argument)
      (match 
       (if left-branch
           (if (is-some (get left-child dest-map)) err-already-exists
               (ok (map-set child-map dest-node { left-child: (some ht-node),
                            right-child: (get right-child dest-map) })))
           (if (is-some (get right-child dest-map)) err-already-exists
               (ok (map-set child-map dest-node { left-child: (get left-child dest-map),
                            right-child: (some ht-node) })))
           )
       succ (ok (map-set bt-allocation ht-node { parent: dest-node,
                         alloc-left: (some left-branch)} ))
       err (err err))
      )
  )

;;
;; Functions required by nft-trait
;;

(define-read-only (get-last-token-id)
  (ok (var-get last-token-id))
  )

(define-read-only (get-token-uri (token-id uint))
  (ok (map-get? token-uri token-id))
  )

(define-read-only (get-owner (token-id uint))
  (ok (nft-get-owner? rnft token-id))
  )

(define-public (transfer (token-id uint) (sender principal)
                         (recipient principal))
  (let (
        (token-owner (unwrap! (unwrap! (get-owner token-id) (err u3)) (err u3)))
        (operator (unwrap-panic
                   (unwrap-panic (contract-call? .sys-admin get-operator))))
        (sender-is-internal-wallet (contract-call? .sys-investor
                                                   is-active
                                                   sender))
        )
    (asserts! (not (is-eq sender recipient)) (err u2))
    (asserts! (is-eq token-owner sender) (err u1))
    (if (or
         (and (is-eq tx-sender operator)
              sender-is-internal-wallet)
         (is-eq tx-sender sender))
        (nft-transfer? rnft
                       ;; #[allow(unchecked_data)]
                       token-id
                       sender
                       recipient)
        err-forbidden
        )
    )
  )
;;
;; rNFT public interface
;;
(define-public (mint (refcode (string-utf8 150))
                     (uri (string-ascii 256))
                     (recipient principal)
                     )
    (let (
          (next-token-id (+ (var-get last-token-id) u1))
          )
      (try! (contract-call? .sys-admin assert-invoked-by-operator))
      (if (is-none (map-get? ref-codes refcode))
          (begin
           (map-insert first-owner next-token-id recipient)
           (map-insert ref-codes refcode next-token-id)
           (map-insert token-ids next-token-id refcode)
           (try! (nft-mint? rnft next-token-id recipient))
           (map-set token-uri next-token-id uri)
           (var-set last-token-id next-token-id)
           (ok next-token-id)
           )
          err-refcode-used)
      )
  )

(define-read-only (get-first-owner (token-id uint))
    (match (map-get? first-owner token-id)
           val (ok val)
           err-not-found)
  )

(define-read-only (get-bt-children (token-id uint))
    (map-get? child-map token-id))

(define-read-only (get-parent (token-id uint))
    (match (map-get? bt-allocation token-id)
           all (some { parent: (get parent all),
                       left: (is-eq (get alloc-left all) (some true)),
                       right: (is-eq (get alloc-left all) (some false)),
                       holding-tank: (is-none (get alloc-left all)) })
           none))

(define-read-only (get-token-by-refcode (refcode (string-utf8 150)))
    (map-get? ref-codes refcode))

(define-read-only (get-refcode-of-token (token-id uint))
    (map-get? token-ids token-id))


(define-public (set-uri (token-id uint)
                        (uri (string-ascii 256)))
    (begin
     (try! (contract-call? .sys-admin
                           assert-invoked-by-operator))
     (map-set token-uri token-id uri)
     (ok true)
     )
  )

(define-read-only (is-in-holding-tank
                   (token-id uint) (tank-owner-token-id uint))
    (let (
          (meta-maybe (map-get? bt-allocation  token-id))
          )
      (match meta-maybe meta
             (let (
                   (parent (get parent meta))
                   (alloc-left (get alloc-left meta))
                   )
               (and (is-eq parent tank-owner-token-id)
                    (is-none alloc-left))
               )
             false)
      )
  )

Functions (15)

FunctionAccessArgs
is-valid-token-idprivatetoken-id: uint
invite-tokenpublicinviting-token-id: uint, invited-token-id: uint
move-from-ht-to-nodepublicht-node: uint, ht-owner: uint, dest-node: uint, left-branch: bool
get-last-token-idread-only
get-token-uriread-onlytoken-id: uint
get-ownerread-onlytoken-id: uint
transferpublictoken-id: uint, sender: principal, recipient: principal
mintpublicrefcode: (string-utf8 150
get-first-ownerread-onlytoken-id: uint
get-bt-childrenread-onlytoken-id: uint
get-parentread-onlytoken-id: uint
get-token-by-refcoderead-onlyrefcode: (string-utf8 150
get-refcode-of-tokenread-onlytoken-id: uint
set-uripublictoken-id: uint, uri: (string-ascii 256
is-in-holding-tankread-onlytoken-id: uint, tank-owner-token-id: uint