Source Code

;; MSA NFT collection by Megapont.
;; Technical partner: Apollo Labs, Inc.
;; EXCLUSIVE COMMERCIAL RIGHTS WITH NO CREATOR RETENTION ("CBE-EXCLUSIVE")
;; https://zzttkwj3ferbc4svr43g6b6aehbajrfri7tfkha4oug5gbpa4fxq.arweave.net/zmc1WTspIhFyVY82bwfAIcIExLFH5lUcHHUN0wXg4W8/1
(impl-trait .nft-trait.nft-trait)
(use-trait commission-trait .commission-trait.commission)


(define-non-fungible-token Megapont-Space-Agency uint)

;; Storage
(define-map token-count principal uint)
(define-map market uint {price: uint, commission: principal})
(define-map mint-address bool principal)
(define-map airdrop-claimed uint bool)
(define-map suit-mapping uint (string-ascii 10))

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant MAX-SUPPLY u7500)

;; Megapont shared wallet
(define-constant WALLET 'SP2J9XB6CNJX9C36D5SY4J85SA0P1MQX7R5VFKZZX)

;; Errors
(define-constant ERR-SOLD-OUT (err u300))
(define-constant ERR-WRONG-COMMISSION (err u301))
(define-constant ERR-NOT-AUTHORIZED (err u401))
(define-constant ERR-NOT-FOUND (err u404))
(define-constant ERR-ALREADY-SUITED (err u405))
(define-constant ERR-LISTING (err u406))
(define-constant ERR-MINT-ALREADY-SET (err u407))
(define-constant ERR-MINT-FROZEN (err u408))
(define-constant ERR-AIRDROP-CLAIMED (err u409))
(define-constant ERR-SUITING-INACTIVE (err u410))

;; Variables
;; Index last-id at 2500 as 1-2500 are reserved for Megapont NFTs
(define-data-var last-id uint u2500)
(define-data-var contract-uri (string-ascii 80) "ipfs://QmZPv48fV1HfJe1pmmJSneQp6oqFVrp7DTtKLbxT1MR4vK")
(define-data-var base-uri (string-ascii 80) "https://api.megapont.com/metadata/msa/{id}")
(define-data-var mint-override bool false)
(define-data-var suiting-active bool false)

(define-read-only (get-balance (account principal))
    (default-to u0
        (map-get? token-count account)))

;; SIP009: Get the owner of the specified token ID
(define-read-only (get-owner (id uint))
    (ok (nft-get-owner? Megapont-Space-Agency id)))

;; SIP009: Get the last token ID
(define-read-only (get-last-token-id)
    (ok (var-get last-id)))

;; SIP009: Get the token URI
(define-read-only (get-token-uri (id uint))
    (ok
        (if (is-none (get-suit-mapping id))
            (some (var-get base-uri))
            (some
                (concat (var-get base-uri) (concat "/" (default-to "" (get-suit-mapping id))))
            )
            )))

(define-read-only (get-contract-uri)
    (ok  (var-get contract-uri)))

(define-read-only (get-suit-mapping (token-id uint))
    (map-get? suit-mapping token-id))

(define-read-only (get-listing-in-ustx (id uint))
    (map-get? market id))

(define-private (trnsfr (id uint) (sender principal) (recipient principal))
    (match (nft-transfer? Megapont-Space-Agency id sender recipient)
        success
            (let
                ((sender-balance (get-balance sender))
                    (recipient-balance (get-balance recipient)))
                (map-set token-count sender (- sender-balance u1))
                (map-set token-count recipient (+ recipient-balance u1))
                (ok success))
        error
            (err error)))

(define-private (is-sender-owner (id uint))
    (let
        ((owner (unwrap! (nft-get-owner? Megapont-Space-Agency id) false)))
        (or (is-eq tx-sender owner) (is-eq contract-caller owner))))

(define-private (called-from-mint)
    (let ((the-mint
        (unwrap! (map-get? mint-address true) false)))
    (is-eq contract-caller the-mint)))

;; SIP009: Transfer token to a specified principal
(define-public (transfer (id uint) (sender principal) (recipient principal))
    (begin
        ;; Only the owner of the token can transfer it
        (asserts! (is-sender-owner id) ERR-NOT-AUTHORIZED)
        (asserts! (is-none (map-get? market id)) ERR-LISTING)
        (trnsfr id sender recipient)))

(define-public (list-in-ustx (id uint) (price uint) (comm <commission-trait>))
    (let ((listing  {price: price, commission: (contract-of comm)}))
        (asserts! (is-sender-owner id) ERR-NOT-AUTHORIZED)
        (map-set market id listing)
        (print (merge listing {action: "list-in-ustx", id: id}))
        (ok true)))

(define-public (unlist-in-ustx (id uint))
    (begin
        (asserts! (is-sender-owner id) ERR-NOT-AUTHORIZED)
        (map-delete market id)
        (print {action: "unlist-in-ustx", id: id})
        (ok true)))

(define-public (buy-in-ustx (id uint) (comm <commission-trait>))
    (let ((owner (unwrap! (nft-get-owner? Megapont-Space-Agency id) ERR-NOT-FOUND))
        (listing (unwrap! (map-get? market id) ERR-LISTING))
        (price (get price listing)))
    (asserts! (is-eq (contract-of comm) (get commission listing)) ERR-WRONG-COMMISSION)
    (try! (stx-transfer? price tx-sender owner))
    (try! (contract-call? comm pay id price))
    (try! (trnsfr id owner tx-sender))
    (map-delete market id)
    (print {action: "buy-in-ustx", id: id})
    (ok true)))

;; can only be called once
(define-public (set-mint-address)
    (let ((the-mint (map-get? mint-address true)))
        (asserts! (and (is-none the-mint)
            (map-insert mint-address true tx-sender)) ERR-MINT-ALREADY-SET)
        (ok tx-sender)))

(define-public (set-suiting-active)
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (var-set suiting-active true)
        (ok true)))

;; can only be called once
;; will forever freeze minting but allow for airdrops
(define-public (set-mint-override)
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (var-set mint-override true)
        (ok true)))

(define-public (set-base-uri (new-base-uri (string-ascii 80)))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (var-set base-uri new-base-uri)
        (ok true)))

(define-public (set-contract-uri (new-contract-uri (string-ascii 80)))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (var-set contract-uri new-contract-uri)
        (ok true)))

;; Mint new NFT
;; can only be called from the Mint
(define-public (mint-many (new-owner principal) (orders (list 50 principal)))
    (let
        (
            (next-nft-id (+ u1 (var-get last-id)))
            (enabled (asserts! (< (var-get last-id) MAX-SUPPLY) ERR-SOLD-OUT))
            (id-reached (fold mint-many-iter orders next-nft-id))
            (payment (* u50000000 (- id-reached next-nft-id)))
            (current-balance (get-balance new-owner))
        )
        (asserts! (called-from-mint) ERR-NOT-AUTHORIZED)
        (asserts! (is-eq (var-get mint-override) false) ERR-MINT-FROZEN)
        (begin
            (var-set last-id id-reached)
            (map-set token-count new-owner (+ current-balance (- id-reached next-nft-id)))
            (try! (stx-transfer? payment new-owner WALLET))
        )
        (ok id-reached)))

(define-private (mint-many-iter (new-owner principal) (next-id uint))
    (if (or (is-eq MAX-SUPPLY u0) (<= next-id MAX-SUPPLY))
        (begin
            (unwrap! (nft-mint? Megapont-Space-Agency next-id new-owner) next-id)
            (+ next-id u1)
        )
        next-id))

(define-public (airdrop (new-owner principal) (ape-id uint))
    (begin
        (asserts! (called-from-mint) ERR-NOT-AUTHORIZED)
        (asserts! (is-eq (default-to false (map-get? airdrop-claimed ape-id)) false) ERR-AIRDROP-CLAIMED)
        (match (nft-mint? Megapont-Space-Agency ape-id new-owner)
            success
                (let
                    ((current-balance (get-balance new-owner)))
                    (begin
                        (map-set token-count new-owner (+ current-balance u1))
                        (map-set airdrop-claimed ape-id true)
                (ok true)))
            error (err (* error u10000)))))

;; Fuck clarity, bring on 2.1
(define-public (set-suit-mapping (token-id uint) (ape-id uint))
    (begin
        (asserts! (is-sender-owner token-id) ERR-NOT-AUTHORIZED)
        (asserts! (is-eq tx-sender (unwrap-panic (unwrap-panic (contract-call? .megapont-ape-club-nft get-owner ape-id)))) ERR-NOT-AUTHORIZED)
        (asserts! (is-eq (var-get suiting-active) true) ERR-SUITING-INACTIVE)
        ;; can't suit a listed token for safety
        (asserts! (is-none (map-get? market token-id)) ERR-LISTING)
        ;; can't suit a suited token
        (asserts! (is-none (get-suit-mapping token-id)) ERR-ALREADY-SUITED)
        (map-set suit-mapping token-id (uint-to-ascii ape-id))
        (ok true)))

;; Credit goes to lnow for this conversion work
(define-read-only (uint-to-ascii (value uint))
    (if (<= value u9)
        (unwrap-panic (element-at "0123456789" value))
        (get r (fold uint-to-ascii-inner
          0x000000000000000000000000000000000000000000000000000000000000000000000000000000
          {v: value, r: ""}
        ))
    )
)

(define-read-only (uint-to-ascii-inner (i (buff 1)) (d {v: uint, r: (string-ascii 10)}))
    (if (> (get v d) u0)
        {
          v: (/ (get v d) u10),
          r: (unwrap-panic (as-max-len? (concat (unwrap-panic (element-at "0123456789" (mod (get v d) u10))) (get r d)) u10))
        }
        d
    )
)

Functions (25)

FunctionAccessArgs
get-balanceread-onlyaccount: principal
get-ownerread-onlyid: uint
get-last-token-idread-only
get-token-uriread-onlyid: uint
get-contract-uriread-only
get-suit-mappingread-onlytoken-id: uint
get-listing-in-ustxread-onlyid: uint
trnsfrprivateid: uint, sender: principal, recipient: principal
is-sender-ownerprivateid: uint
called-from-mintprivate
transferpublicid: uint, sender: principal, recipient: principal
list-in-ustxpublicid: uint, price: uint, comm: <commission-trait>
unlist-in-ustxpublicid: uint
buy-in-ustxpublicid: uint, comm: <commission-trait>
set-mint-addresspublic
set-suiting-activepublic
set-mint-overridepublic
set-base-uripublicnew-base-uri: (string-ascii 80
set-contract-uripublicnew-contract-uri: (string-ascii 80
mint-manypublicnew-owner: principal, orders: (list 50 principal
mint-many-iterprivatenew-owner: principal, next-id: uint
airdroppublicnew-owner: principal, ape-id: uint
set-suit-mappingpublictoken-id: uint, ape-id: uint
uint-to-asciiread-onlyvalue: uint
uint-to-ascii-innerread-onlyi: (buff 1