
;; Interface definitions
;; test/mocknet
;; (impl-trait 'SP3N4AJFZZYC4BK99H53XP8KDGXFGQ2PRSQP2HGT6.nft-approvable-trait.nft-approvable-trait)
;; (impl-trait 'SP3N4AJFZZYC4BK99H53XP8KDGXFGQ2PRSQP2HGT6.nft-trait.nft-trait)
;; mainnet
;; (impl-trait SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.nft-approvable-trait.nft-approvable-trait)
;; (impl-trait SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)

;; contract variables
(define-data-var administrator principal 'SP3N4AJFZZYC4BK99H53XP8KDGXFGQ2PRSQP2HGT6)
(define-data-var mint-price uint u1000000)
(define-data-var base-token-uri (string-ascii 256) "https://loopbomb.io/nfts/")
(define-data-var mint-counter uint u0)
(define-data-var platform-fee uint u5)
(define-data-var project-broker principal 'SP3N4AJFZZYC4BK99H53XP8KDGXFGQ2PRSQP2HGT6)
(define-data-var centralised-broker1 principal 'SP3N4AJFZZYC4BK99H53XP8KDGXFGQ2PRSQP2HGT6)
(define-data-var centralised-broker2 principal 'SP3N4AJFZZYC4BK99H53XP8KDGXFGQ2PRSQP2HGT6)
(define-data-var centralised-broker3 principal 'SP3N4AJFZZYC4BK99H53XP8KDGXFGQ2PRSQP2HGT6)
(define-data-var transfer-status uint u1)

;; constants
(define-constant token-name "loopbomb")
(define-constant token-symbol "LPB")

;; Non Fungible Token, modeled after ERC-721 via nft-trait
;; Note this is a basic implementation - no support yet for setting approvals for assets
;; NFT are identified by nft-index (uint) which is tied via a reverse lookup to a real world
;; asset hash - SHA 256 32 byte value. The Asset Hash is used to tie arbitrary real world
;; data to the NFT
(define-non-fungible-token loopbomb uint)

;; data structures
(define-map nft-approvals {nft-index: uint} {approval: principal})
(define-map nft-lookup {asset-hash: (buff 32), edition: uint} {nft-index: uint})
(define-map nft-data {nft-index: uint} {asset-hash: (buff 32), meta-data-url: (buff 200), max-editions: uint, edition: uint, edition-cost: uint, mint-block-height: uint, series-original: uint})
(define-map nft-sale-data {nft-index: uint} {sale-type: uint, increment-stx: uint, reserve-stx: uint, amount-stx: uint, bidding-end-time: uint, sale-cycle-index: uint})
(define-map nft-beneficiaries {nft-index: uint} { addresses: (list 10 principal), shares: (list 10 uint), secondaries: (list 10 uint) })
(define-map nft-bid-history {nft-index: uint, bid-index: uint} {sale-cycle: uint, bidder: principal, amount: uint, app-timestamp: uint})
(define-map nft-offer-history {nft-index: uint, offer-index: uint} {sale-cycle: uint, offerer: principal, app-timestamp: uint, amount: uint, accepted: uint})

;; counters keep track per NFT of the...
;;       a) number of editions minted (1 based index)
;;       b) number of offers made (0 based index)
;;       c) number of bids made (0 based index)
(define-map nft-offer-counter {nft-index: uint} {offer-counter: uint, sale-cycle: uint})
(define-map nft-edition-counter {nft-index: uint} {edition-counter: uint})
(define-map nft-high-bid-counter {nft-index: uint} {high-bid-counter: uint, sale-cycle: uint})

(define-constant percentage-with-twodp u10000000000)

(define-constant not-allowed (err u10))
(define-constant not-found (err u11))
(define-constant amount-not-set (err u12))
(define-constant seller-not-found (err u13))
(define-constant asset-not-registered (err u14))
(define-constant transfer-error (err u15))
(define-constant not-approved-to-sell (err u16))
(define-constant same-spender-err (err u17))
(define-constant failed-to-mint-err (err u18))
(define-constant edition-counter-error (err u19))
(define-constant edition-limit-reached (err u20))
(define-constant user-amount-different (err u21))
(define-constant failed-to-stx-transfer (err u22))
(define-constant failed-to-close-1 (err u23))
(define-constant failed-refund (err u24))
(define-constant failed-to-close-3 (err u24))
(define-constant cant-pay-mint-price (err u25))
(define-constant editions-error (err u26))
(define-constant payment-error (err u28))
(define-constant payment-address-error (err u33))
(define-constant payment-share-error (err u34))
(define-constant bidding-error (err u35))
(define-constant prevbid-bidding-error (err u36))
(define-constant not-originale (err u37))
(define-constant bidding-opening-error (err u38))
(define-constant bidding-amount-error (err u39))
(define-constant bidding-endtime-error (err u40))
(define-constant bad-broker-err (err u41)) ;; unauthorized

(define-constant nft-not-owned-err (err u401)) ;; unauthorized
(define-constant sender-equals-recipient-err (err u405)) ;; method not allowed
(define-constant nft-not-found-err (err u404)) ;; not found

;; interface methods
;; from nft-trait: Last token ID, limited to uint range
;; note decrement as mint counter is the id of the next nft
(define-read-only (get-last-token-id)
  (ok (- (var-get mint-counter) u1))
)

;; from nft-trait: URI for metadata associated with the token
(define-read-only (get-token-uri (nftIndex uint))
  (ok (some (var-get base-token-uri)))
)

;; from nft-trait: Gets the owner of the 'SPecified token ID.
(define-read-only (get-owner (nftIndex uint))
  (ok (nft-get-owner? loopbomb nftIndex))
)

;; from nft-trait: Gets the owner of the 'SPecified token ID.
(define-read-only (get-approval (nftIndex uint))
  (ok (unwrap! (get approval (map-get? nft-approvals {nft-index: nftIndex})) not-found))
)

;; sets an approval principal - allowed to call transfer on owner behalf.
(define-public (set-approval-for (nftIndex uint) (approval principal))
    (if (is-owner nftIndex tx-sender)
        (begin
            (map-set nft-approvals {nft-index: nftIndex} {approval: approval})
            (ok true)
        )
        nft-not-owned-err
    )
)

;; Transfers tokens to a 'SPecified principal.
(define-public (transfer (nftIndex uint) (owner principal) (recipient principal))
    (if (is-eq (var-get transfer-status) u2)
        (if (is-eq (var-get project-broker) tx-sender)
            (transfer-internal nftIndex owner recipient)
            not-allowed
        )
        (if (is-eq (var-get transfer-status) u1)
            (if (and (is-owner-or-approval nftIndex owner) (is-owner-or-approval nftIndex tx-sender))
                (if (not (is-centralised-broker))
                    (transfer-internal nftIndex owner recipient)
                    bad-broker-err
                )
                nft-not-owned-err
            )
            ;; check for centralised broker addresses
            (if (is-eq (var-get transfer-status) u3)
                (if (or (is-eq (var-get project-broker) tx-sender) (and (is-owner-or-approval nftIndex owner) (is-owner-or-approval nftIndex tx-sender)))
                    (if (not (is-centralised-broker))
                        (transfer-internal nftIndex owner recipient)
                        bad-broker-err
                    )
                    nft-not-owned-err
                )
                nft-not-owned-err
            )
        )
    )
)

(define-private (is-centralised-broker)
    (if (is-eq tx-sender (var-get centralised-broker1))
        true
        (if (is-eq tx-sender (var-get centralised-broker2))
            true
            (if (is-eq tx-sender (var-get centralised-broker3))
                true
                false
            )
        )
    )
)

(define-private (transfer-internal (nftIndex uint) (owner principal) (recipient principal))
     (match (nft-transfer? loopbomb nftIndex owner recipient)
         success (ok true)
         error (nft-transfer-err error))
)

(define-public (set-broker-info (tstatus uint) (nft-admin principal) (bb1 principal) (bb2 principal) (bb3 principal))
    (begin
        (asserts! (is-eq (var-get administrator) tx-sender) not-allowed)
        (var-set project-broker nft-admin)
        (var-set centralised-broker1 bb1)
        (var-set centralised-broker2 bb2)
        (var-set centralised-broker3 bb3)
        (var-set transfer-status tstatus)
        (ok true)
    )
)

;; Burns tokens
(define-public (burn (nftIndex uint) (owner principal))
  (if (and (is-owner-or-approval nftIndex owner) (is-owner-or-approval nftIndex tx-sender))
    (match (nft-burn? loopbomb nftIndex owner)
        success (ok true)
        error (nft-transfer-err error))
    nft-not-owned-err)
)

(define-private (nft-transfer-err (code uint))
  (if (is-eq u1 code)
    nft-not-owned-err
    (if (is-eq u2 code)
      sender-equals-recipient-err
      (if (is-eq u3 code)
        nft-not-found-err
        (err code)))))

(define-private (is-owner (nftIndex uint) (user principal))
  (is-eq user (unwrap! (nft-get-owner? loopbomb nftIndex) false))
)
(define-private (is-approval (nftIndex uint) (user principal))
  (is-eq user (unwrap! (get approval (map-get? nft-approvals {nft-index: nftIndex})) false))
)
(define-private (is-owner-or-approval (nftIndex uint) (user principal))
    (if (is-owner nftIndex user) true
        (if (is-approval nftIndex user) true false)
    )
)

;; public methods
;; --------------
;; the contract administrator can change the contract administrator
(define-public (transfer-administrator (new-administrator principal))
    (begin
        (asserts! (is-eq (var-get administrator) tx-sender) not-allowed)
        (var-set administrator new-administrator)
        (ok true)
    )
)

;; the contract administrator can change the transfer fee charged by the contract on sale of tokens
(define-public (change-fee (new-fee uint))
    (begin
        (asserts! (is-eq (var-get administrator) tx-sender) not-allowed)
        (var-set platform-fee new-fee)
        (ok true)
    )
)

;; the contract administrator can change the base uri - where meta data for tokens in this contract
;; are located
(define-public (update-base-token-uri (new-base-token-uri (string-ascii 256)))
    (begin
        (asserts! (is-eq (var-get administrator) tx-sender) not-allowed)
        (var-set base-token-uri new-base-token-uri)
        (ok true)
    )
)

;; the contract administrator can change the mint price
(define-public (update-mint-price (new-mint-price uint))
    (begin
        (asserts! (is-eq (var-get administrator) tx-sender) not-allowed)
        (var-set mint-price new-mint-price)
        (ok true)
    )
)

;; The administrator can transfer the balance in the contract to another address
(define-public (transfer-balance (recipient principal))
    (let
        (
            (balance (stx-get-balance (as-contract tx-sender)))
        )
        (asserts! (is-eq (var-get administrator) tx-sender) not-allowed)
        (unwrap! (stx-transfer? balance (as-contract tx-sender) recipient) failed-to-stx-transfer)
        (print {evt: "transfer-balance", recipient: recipient, balance: balance})
        (ok balance)
    )
)

;; adds an offer to the list of offers on an NFT
(define-public (make-offer (nft-index uint) (amount uint) (app-timestamp uint))
    (let
        (
            (saleType (unwrap! (get sale-type (map-get? nft-sale-data {nft-index: nft-index})) not-allowed))
            (offerCounter (default-to u0 (get offer-counter (map-get? nft-offer-counter {nft-index: nft-index}))))
            (saleCycleIndex (unwrap! (get sale-cycle-index (map-get? nft-sale-data {nft-index: nft-index})) amount-not-set))
        )
        (map-insert nft-offer-history {nft-index: nft-index, offer-index: offerCounter} {sale-cycle: saleCycleIndex, offerer: tx-sender, app-timestamp: app-timestamp, amount: amount, accepted: u0})
        (map-set nft-offer-counter {nft-index: nft-index} {sale-cycle: saleCycleIndex, offer-counter: (+ offerCounter u1)})
        (ok (+ offerCounter u1))
    )
)

;; accept-offer
;; marks offer as accepted and transfers ownership to the recipient
(define-public (accept-offer (nft-index uint) (offer-index uint) (owner principal) (recipient principal))
    (let
        (
            (saleType (unwrap! (get sale-type (map-get? nft-sale-data {nft-index: nft-index})) not-allowed))
            (offerer (unwrap! (get offerer (map-get? nft-offer-history {nft-index: nft-index, offer-index: offer-index})) not-allowed))
            (app-timestamp (unwrap! (get app-timestamp (map-get? nft-offer-history {nft-index: nft-index, offer-index: offer-index})) not-allowed))
            (sale-cycle (unwrap! (get sale-cycle (map-get? nft-offer-history {nft-index: nft-index, offer-index: offer-index})) not-allowed))
            (amount (unwrap! (get amount (map-get? nft-offer-history {nft-index: nft-index, offer-index: offer-index})) not-allowed))
        )
        (asserts! (is-eq saleType u3) not-allowed)
        (map-set nft-offer-history {nft-index: nft-index, offer-index: offer-index} {sale-cycle: sale-cycle, offerer: offerer, app-timestamp: app-timestamp, amount: amount, accepted: u1})
        (ok (transfer nft-index owner recipient))
    )
)

;; mint ten tokens
(define-public (mint-token-twenty (hashes (list 20 (buff 32))) (meta-urls (list 20 (buff 200))) (maxEditions uint) (editionCost uint) (clientMintPrice uint) (buyNowPrice uint) (addresses (list 10 principal)) (shares (list 10 uint)) (secondaries (list 10 uint)))
    (begin
        (unwrap! (mint-token (unwrap! (element-at hashes u0) not-allowed) (unwrap! (element-at meta-urls u0) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u1) not-allowed) (unwrap! (element-at meta-urls u1) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u2) not-allowed) (unwrap! (element-at meta-urls u2) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u3) not-allowed) (unwrap! (element-at meta-urls u3) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u4) not-allowed) (unwrap! (element-at meta-urls u4) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u5) not-allowed) (unwrap! (element-at meta-urls u5) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u6) not-allowed) (unwrap! (element-at meta-urls u6) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u7) not-allowed) (unwrap! (element-at meta-urls u7) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u8) not-allowed) (unwrap! (element-at meta-urls u8) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u9) not-allowed) (unwrap! (element-at meta-urls u9) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u10) not-allowed) (unwrap! (element-at meta-urls u10) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u11) not-allowed) (unwrap! (element-at meta-urls u11) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u12) not-allowed) (unwrap! (element-at meta-urls u12) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u13) not-allowed) (unwrap! (element-at meta-urls u13) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u14) not-allowed) (unwrap! (element-at meta-urls u14) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u15) not-allowed) (unwrap! (element-at meta-urls u15) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u16) not-allowed) (unwrap! (element-at meta-urls u16) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u17) not-allowed) (unwrap! (element-at meta-urls u17) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u18) not-allowed) (unwrap! (element-at meta-urls u18) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (unwrap! (mint-token (unwrap! (element-at hashes u19) not-allowed) (unwrap! (element-at meta-urls u19) not-allowed) maxEditions editionCost clientMintPrice buyNowPrice addresses shares secondaries) not-allowed)
        (print {evt: "mint-token-twenty", txSender: tx-sender})
        (ok true)
    )
)

;; mint a new token
;; asset-hash: sha256 hash of asset file
;; max-editions: maximum number of editions allowed for this asset
;; royalties: a list of priciple/percentages to be be paid from sale price
;;
;; 1. transfer mint price to the administrator
;; 2. mint the token using built in mint function
;; 3. update the two maps - first contains the data indexed by the nft index, second
;; provides a reverse lookup based on the asset hash - this allows tokens to be located
;; from just a knowledge of the original asset.
;; Note series-original in the case of the original in series is just
;; mintCounter - for editions this provides a safety hook back to the original in cases
;; where the asset hash is unknown (ie cant be found from nft-lookup).
(define-public (mint-token (asset-hash (buff 32)) (metaDataUrl (buff 200)) (maxEditions uint) (editionCost uint) (clientMintPrice uint) (buyNowPrice uint) (addresses (list 10 principal)) (shares (list 10 uint)) (secondaries (list 10 uint)))
    (if (< (len metaDataUrl) u10) (ok (var-get mint-counter))
        (let
            (
                ;; if client bypasses UI clientMintPrice then charge mint-price
                (myMintPrice (max-of (var-get mint-price) clientMintPrice))
                (mintCounter (var-get mint-counter))
                (ahash (get asset-hash (map-get? nft-data {nft-index: (var-get mint-counter)})))
                (block-time (unwrap! (get-block-info? time u0) amount-not-set))
            )
            (asserts! (> maxEditions u0) editions-error)
            (asserts! (> (stx-get-balance tx-sender) (var-get mint-price)) cant-pay-mint-price)
            (asserts! (is-none ahash) asset-not-registered)

            ;; Note: series original is really for later editions to refer back to this one - this one IS the series original
            (map-insert nft-data {nft-index: mintCounter} {asset-hash: asset-hash, meta-data-url: metaDataUrl, max-editions: maxEditions, edition: u1, edition-cost: editionCost, mint-block-height: block-height, series-original: mintCounter})

            ;; Note editions are 1 based and <= maxEditions - the one minted here is #1
            (map-insert nft-edition-counter {nft-index: mintCounter} {edition-counter: u2})

            ;; By default we accept offers - sale type can be changed via the UI.
            (if (> buyNowPrice u0)
                (map-insert nft-sale-data { nft-index: mintCounter } { sale-cycle-index: u1, sale-type: u1, increment-stx: u0, reserve-stx: u0, amount-stx: buyNowPrice, bidding-end-time: (+ block-time u1814400)})
                (map-insert nft-sale-data { nft-index: mintCounter } { sale-cycle-index: u1, sale-type: u0, increment-stx: u0, reserve-stx: u0, amount-stx: u0, bidding-end-time: (+ block-time u1814400)})
            )

            (map-insert nft-lookup {asset-hash: asset-hash, edition: u1} {nft-index: mintCounter})

            ;; The payment is split between the nft-beneficiaries with share > 0 they are set per edition
            (map-insert nft-beneficiaries {nft-index: mintCounter} {addresses: addresses, shares: shares, secondaries: secondaries})

            ;; finally - mint the NFT and step the counter
            (if (is-eq tx-sender (var-get administrator))
                (print "mint-token : tx-sender is contract - skipping mint price")
                (begin
                    (unwrap! (stx-transfer? myMintPrice tx-sender (var-get administrator)) failed-to-stx-transfer)
                    (print "mint-token : tx-sender paid mint price")
                )
            )
            (unwrap! (nft-mint? loopbomb mintCounter tx-sender) failed-to-mint-err)
            (print {evt: "mint-token", nftIndex: mintCounter, owner: tx-sender, amount: (var-get mint-price)})
            (var-set mint-counter (+ mintCounter u1))
            (ok mintCounter)
        )
    )
)

(define-private (max-of (i1 uint) (i2 uint))
  (if (> i1 i2)
      i1
      i2))

(define-public (mint-edition (nftIndex uint))
    (let
        (
            ;; before we start... check the hash corresponds to a minted asset
            (ahash          (unwrap! (get asset-hash   (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (metaDataUrl    (unwrap! (get meta-data-url   (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (maxEditions    (unwrap! (get max-editions (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (editionCost    (unwrap! (get edition-cost (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (editionCounter (unwrap! (get edition-counter (map-get? nft-edition-counter {nft-index: nftIndex})) edition-counter-error))
            (thisEdition    (default-to u0 (get nft-index (map-get? nft-lookup {asset-hash: ahash, edition: editionCounter}))))
            (block-time     (unwrap!  (get-block-info? time u0) amount-not-set))
            (mintCounter    (var-get mint-counter))
        )
        ;; can only mint an edition via buy now or bidding - not offers
        (asserts! (is-eq thisEdition u0) edition-counter-error)
        ;; Note - the edition index is 1 based and incremented before insertion in this method - therefore the test is '<=' here!
        (asserts! (<= editionCounter maxEditions) edition-limit-reached)
        ;; This asserts the first one has been minted already - see mint-token.
        (asserts! (> editionCounter u1) edition-counter-error)
        ;; check the buyer has enough funds..
        (asserts! (> (stx-get-balance tx-sender) editionCost) cant-pay-mint-price)
        ;; set max editions so we know where we are in the series
        (map-insert nft-data {nft-index: mintCounter} {asset-hash: ahash, meta-data-url: metaDataUrl, max-editions: maxEditions, edition: editionCounter, edition-cost: editionCost, mint-block-height: block-height, series-original: nftIndex})
        ;; put the nft index into the list of editions in the look up map
        (map-insert nft-lookup {asset-hash: ahash, edition: editionCounter} {nft-index: mintCounter})
        ;; mint the NFT and update the counter for the next..
        (unwrap! (nft-mint? loopbomb mintCounter tx-sender) failed-to-mint-err)
        ;; saleType = 1 (buy now) - split out the payments according to royalties - or roll everything back.
        (if (> editionCost u0)
            (begin (unwrap! (payment-split nftIndex editionCost tx-sender nftIndex) failed-to-mint-err) (print "mint-edition : payment split made"))
                (print "mint-edition : payment not required")
        )
        ;; (print "mint-edition : payment managed")

        ;; initialise the sale data - not for sale until the owner sets it.
        (map-insert nft-sale-data { nft-index: mintCounter } { sale-cycle-index: u1, sale-type: u0, increment-stx: u0, reserve-stx: u0, amount-stx: u0, bidding-end-time: (+ block-time u1814400)})

        (print {evt: "mint-edition", nftIndex: nftIndex, owner: tx-sender, edition: editionCounter, amount: editionCost})

        ;; inncrement the mint counter and edition counter ready for the next edition
        (map-set nft-edition-counter {nft-index: nftIndex} {edition-counter: (+ u1 editionCounter)})
        (var-set mint-counter (+ mintCounter u1))

        (ok mintCounter)
    )
)

;; allow the owner of the series original to set the cost of minting editions
;; the cost for each edition is taken from the series original and so we need to
;; operate on the the original here - ie nftIndex is the index of thee original
;; and NOT the edition andd only the creator of the series original can change this.
(define-public (set-edition-cost (nftIndex uint) (maxEditions uint) (editionCost uint))
    (let
        (
            (ahash           (unwrap! (get asset-hash   (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (metaDataUrl     (unwrap! (get meta-data-url   (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (edition         (unwrap! (get edition (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (mintBlockHeight (unwrap! (get mint-block-height (map-get? nft-data {nft-index: nftIndex})) not-allowed))
            (seriesOriginal  (unwrap! (get series-original (map-get? nft-data {nft-index: nftIndex})) not-allowed))
        )
        (asserts! (is-owner nftIndex tx-sender) nft-not-owned-err)
        (asserts! (is-eq nftIndex seriesOriginal) not-originale)
        (ok (map-set nft-data {nft-index: nftIndex} {asset-hash: ahash, meta-data-url: metaDataUrl, max-editions: maxEditions, edition: edition, edition-cost: editionCost, mint-block-height: mintBlockHeight, series-original: seriesOriginal}))
    )
)


;; set-sale-data updates the sale type and purchase info for a given NFT. Only the owner can call this method
;; and doing so make the asset transferable by the recipient - on condition of meeting the conditions of sale
;; This is equivalent to the setApprovalForAll method in ERC 721 contracts.
;; Assumption being made here is that all editions have the same sale data associated
(define-public (set-sale-data (nftIndex uint) (sale-type uint) (increment-stx uint) (reserve-stx uint) (amount-stx uint) (bidding-end-time uint))
    (let
        (
            ;; before we start... check the hash corresponds to a minted asset
            (saleCycleIndex (unwrap! (get sale-cycle-index (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleType (unwrap! (get sale-type (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (currentBidIndex (default-to u0 (get high-bid-counter (map-get? nft-high-bid-counter {nft-index: nftIndex}))))
            (currentAmount (unwrap! (get-current-bid-amount nftIndex currentBidIndex) bidding-error))
        )
        (asserts! (not (and (> currentAmount u0) (is-eq saleType u2))) bidding-error)
        (print {evt: "set-sale-data", nftIndex: nftIndex, saleType: sale-type, increment: increment-stx, reserve: reserve-stx, amount: amount-stx, biddingEndTime: bidding-end-time})
        (if (is-owner nftIndex tx-sender)
            ;; Note - don't override the sale cyle index here as this is a public method and can be called ad hoc. Sale cycle is update at end of sale!
            (if (map-set nft-sale-data {nft-index: nftIndex} {sale-cycle-index: saleCycleIndex, sale-type: sale-type, increment-stx: increment-stx, reserve-stx: reserve-stx, amount-stx: amount-stx, bidding-end-time: bidding-end-time})
                (ok nftIndex) not-allowed
            )
            not-allowed
        )
    )
)

;; buy-now
;; pay royalties and transfer asset ownership to tx-sender.
;; Checks that:
;;             a) asset is registered
;;             b) on sale via buy now
;;             c) amount is set
;;
(define-public (buy-now (nftIndex uint) (owner principal) (recipient principal))
    (let
        (
            (saleType (unwrap! (get sale-type (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleCycleIndex (unwrap! (get sale-cycle-index (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (amount (unwrap! (get amount-stx (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (ahash (get asset-hash (map-get? nft-data {nft-index: nftIndex})))
            (seriesOriginal  (unwrap! (get series-original (map-get? nft-data {nft-index: nftIndex})) not-allowed))
        )
        (asserts! (is-some ahash) asset-not-registered)
        (asserts! (is-eq saleType u1) not-approved-to-sell)
        (asserts! (> amount u0) amount-not-set)

        ;; Make the royalty payments - then zero out the sale data and register the transfer
        ;; (print "buy-now : Make the royalty payments")
        (print (unwrap! (payment-split nftIndex amount tx-sender seriesOriginal) payment-error))
        (map-set nft-sale-data { nft-index: nftIndex } { sale-cycle-index: (+ saleCycleIndex u1), sale-type: u0, increment-stx: u0, reserve-stx: u0, amount-stx: u0, bidding-end-time: u0})
        ;; (print "buy-now : Added internal transfer - transfering nft...")
        ;; finally transfer ownership to the buyer (note: via the buyers transaction!)
        (print {evt: "buy-now", nftIndex: nftIndex, owner: owner, recipient: recipient, amount: amount})
        (nft-transfer? loopbomb nftIndex owner recipient)
    )
)

;; opening-bid
;; nft-index: unique index for NFT
;; The opening bid in the given sale cycle a given item.
(define-public (opening-bid (nftIndex uint) (bidAmount uint) (appTimestamp uint))
    (let
        (
            (amount (unwrap! (get amount-stx (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleType (unwrap! (get sale-type (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleCycle (unwrap! (get sale-cycle-index (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (biddingEndTime (unwrap! (get bidding-end-time (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (bidCounter (default-to u0 (get high-bid-counter (map-get? nft-high-bid-counter {nft-index: nftIndex}))))
        )

        ;; Check the user bid amount is the opening price OR the current bid plus increment
        (asserts! (is-eq bidAmount amount) bidding-amount-error)
        (asserts! (> biddingEndTime appTimestamp) bidding-endtime-error)

        (print "place-bid : sending this much to; ")
        (print bidAmount)
        (print (as-contract tx-sender))
        (print "place-bid : when")
        (print appTimestamp)
        (unwrap! (stx-transfer? bidAmount tx-sender (as-contract tx-sender)) failed-to-stx-transfer)
        (map-insert nft-bid-history {nft-index: nftIndex, bid-index: bidCounter} {bidder: tx-sender, amount: bidAmount, app-timestamp: appTimestamp, sale-cycle: saleCycle})
        (map-set nft-high-bid-counter {nft-index: nftIndex} {high-bid-counter: (+ bidCounter u1), sale-cycle: saleCycle})
        (print {evt: "opening-bid", nftIndex: nftIndex, txSender: tx-sender, appTimestamp: appTimestamp, amount: bidAmount})
        (ok bidAmount)
    )
)

(define-private (get-current-bidder (nftIndex uint) (currentBidIndex uint))
  (let
      (
        (currentBidder (unwrap! (get bidder (map-get? nft-bid-history {nft-index: nftIndex, bid-index: (- currentBidIndex u1)})) bidding-error))
      )
      (ok currentBidder)
  )
)

(define-private (get-current-bid-amount (nftIndex uint) (currentBidIndex uint))
  (if (is-eq currentBidIndex u0)
    (ok u0)
    (let
        (
            (currentAmount (default-to u0 (get amount (map-get? nft-bid-history {nft-index: nftIndex, bid-index: (- currentBidIndex u1)}))))
        )
        (ok currentAmount)
    )
  )
)

;; place-bid
;; nft-index: unique index for NFT
;; nextBidAmount: amount the user is bidding - i.e the amount display on th place bid button.
(define-public (place-bid (nftIndex uint) (nextBidAmount uint) (appTimestamp uint))
    (let
        (
            (bidding-end-time (unwrap! (get bidding-end-time (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleType (unwrap! (get sale-type (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleCycle (unwrap! (get sale-cycle-index (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (amountStart (unwrap! (get amount-stx (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (increment (unwrap! (get increment-stx (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (reserve (unwrap! (get reserve-stx (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (currentBidIndex (default-to u0 (get high-bid-counter (map-get? nft-high-bid-counter {nft-index: nftIndex}))))
            (currentBidder (unwrap! (get-current-bidder nftIndex currentBidIndex) bidding-error))
            (currentAmount (unwrap! (get-current-bid-amount nftIndex currentBidIndex) bidding-error))
            (owner (unwrap! (nft-get-owner? loopbomb nftIndex) nft-not-owned-err))
            (seriesOriginal  (unwrap! (get series-original (map-get? nft-data {nft-index: nftIndex})) not-allowed))
        )

        ;; Check the user bid amount is the opening price OR the current bid plus increment
        (print "place-bid : assert there is an opening bid - otherwise client calls opening-bid")
        (print currentAmount)
        (asserts! (> currentAmount u0) user-amount-different)
        (asserts! (is-eq nextBidAmount (+ currentAmount increment)) user-amount-different)

        ;; if (appTimestamp > bidding-end-time) then this is either the winning or a too late bid on the NFT
        ;; a too late bid will have been rejected as the last bid resets the sale/bidding data on the item.
        ;; if its the last bid...
        ;;               1. Refund the currentBid to the bidder
        ;;               2. move currentBid to bid history
        ;;               3. Set the bid in nft-high-bid-counter - note 'set' so we overwrite the previous bid
        ;; (next-bid) we
        ;;               1. Refund the currentBid to the bidder
        ;;               2. Insert currentBid to bid history
        ;;               3. Set the bid in nft-high-bid-counter - note 'set' so we overwrite the previous bid

        (if (> appTimestamp bidding-end-time)
            (begin
                (print {evt: "place-bid-closure", nftIndex: nftIndex, appTimestamp: appTimestamp, biddingEndTime: bidding-end-time, amount: nextBidAmount, reserve: reserve})
                (unwrap! (refund-bid nftIndex currentBidder currentAmount) failed-to-stx-transfer)
                (if (< nextBidAmount reserve)
                    ;; if this bid is less than reserve & its the last bid then just refund previous bid
                    (unwrap! (ok true) failed-to-stx-transfer)
                    (begin
                        ;; WINNING BID - is the FIRST bid after bidding close.
                        (print "place-bid : Make the royalty payments")
                        (unwrap! (payment-split nftIndex nextBidAmount tx-sender seriesOriginal) payment-error)
                        (unwrap! (record-bid nftIndex nextBidAmount currentBidIndex appTimestamp saleCycle) failed-to-stx-transfer)
                        (map-set nft-sale-data { nft-index: nftIndex } { sale-cycle-index: (+ saleCycle u1), sale-type: u0, increment-stx: u0, reserve-stx: u0, amount-stx: u0, bidding-end-time: u0})
                        (print "place-bid : Added internal transfer - transfering nft...")
                        ;; finally transfer ownership to the buyer (note: via the buyers transaction!)
                        (unwrap! (nft-transfer? loopbomb nftIndex owner tx-sender) failed-to-stx-transfer)
                    )
                )
            )
            (begin
                (print {evt: "place-bid-refund", nftIndex: nftIndex, appTimestamp: appTimestamp, biddingEndTime: bidding-end-time, amount: nextBidAmount})
                (unwrap! (refund-bid nftIndex currentBidder currentAmount) failed-refund)
                (unwrap! (next-bid nftIndex nextBidAmount currentBidIndex appTimestamp saleCycle) failed-to-stx-transfer)
            )
        )
        ;;
        ;; NOTE: Above code will only reconcile IF a bid comes in after 'block-time'
        ;; We may need a manual trigger to end bidding when this doesn't happen - unless there is a
        ;; to repond to future events / timeouts that I dont know about.
        ;;
        (print {evt: "place-bid", nftIndex: nftIndex, txSender: tx-sender, appTimestamp: appTimestamp, amount: nextBidAmount})
        (ok true)
    )
)

;; Mint subsequent editions of the NFT
;; nft-index: the index of the original NFT in this series of editions.
;; The sale data must have been set on the asset before calling this.
;; The amount is split according to the royalties.
;; The nextBidAmount is passed to avoid concurrency issues - amount on the buy/bid button must
;; equal the amount expected by the contract.

;; close-bidding
;; nft-index: index of the NFT
;; closeType: type of closure, values are;
;;             1 = buy now closure - uses the last bid (thats held in escrow) to transfer the item to the bidder and to pay royalties
;;             2 = refund closure - the last bid gets refunded and sale is closed. The item ownership does not change.
;; Note bidding can also be closed automatically - if a bid is received after the bidding end time.
;; In the context of a 'live auction' items have no end time and are closed by the 'auctioneer'.
(define-public (close-bidding (nftIndex uint) (closeType uint))
    (let
        (
            (bidding-end-time (unwrap! (get bidding-end-time (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleType (unwrap! (get sale-type (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (saleCycleIndex (unwrap! (get sale-cycle-index (map-get? nft-sale-data {nft-index: nftIndex})) failed-to-close-1))
            (block-time (unwrap! (get-block-info? time u0) not-allowed))
            (currentBidIndex (default-to u0 (get high-bid-counter (map-get? nft-high-bid-counter {nft-index: nftIndex}))))
            (currentBidder (unwrap! (get-current-bidder nftIndex currentBidIndex) bidding-error))
            (currentAmount (unwrap! (get-current-bid-amount nftIndex currentBidIndex) bidding-error))
            (seriesOriginal  (unwrap! (get series-original (map-get? nft-data {nft-index: nftIndex})) not-allowed))
        )
        (asserts! (or (is-eq closeType u1) (is-eq closeType u2)) failed-to-close-1)
        ;; only the owner or administrator can call close
        (asserts! (or (is-owner nftIndex tx-sender) (unwrap! (is-administrator) not-allowed)) not-allowed)
        ;; only the administrator can call close BEFORE the end time - note we use the less accurate
        ;; but fool proof block time here to prevent owner/client code jerry mandering the close function
        (asserts! (or (> block-time bidding-end-time) (unwrap! (is-administrator) failed-to-close-3)) failed-to-close-3)

        ;; Check for a current bid - if none then just reset the sale data to not selling
        (if (is-eq currentAmount u0)
            (map-set nft-sale-data { nft-index: nftIndex } { sale-cycle-index: (+ saleCycleIndex u1), sale-type: u0, increment-stx: u0, reserve-stx: u0, amount-stx: u0, bidding-end-time: u0})
            (if (is-eq closeType u1)
                (begin
                    ;; buy now closure - pay and transfer ownership
                    ;; note that the money to pay with is in the contract!
                    (print {evt: "close-bidding", nftIndex: nftIndex, payType: "from-contract", txSender: tx-sender, currentBidder: currentBidder, currentAmount: currentAmount, currentBidIndex: currentBidIndex})
                    (unwrap! (payment-split nftIndex currentAmount (as-contract tx-sender) seriesOriginal) payment-error)
                    (unwrap! (nft-transfer? loopbomb nftIndex (unwrap! (nft-get-owner? loopbomb nftIndex) nft-not-owned-err) tx-sender) transfer-error)
                )
                (begin
                    ;; refund closure - refund the bid and reset sale data
                    (print {evt: "close-bidding", nftIndex: nftIndex, payType: "refund", txSender: tx-sender, currentBidder: currentBidder, currentAmount: currentAmount})
                    (unwrap! (refund-bid nftIndex currentBidder currentAmount) failed-refund)
                    (map-set nft-sale-data { nft-index: nftIndex } { sale-cycle-index: (+ saleCycleIndex u1), sale-type: u0, increment-stx: u0, reserve-stx: u0, amount-stx: u0, bidding-end-time: u0})
                )
            )
        )
        (print {evt: "close-bidding", nftIndex: nftIndex, closeType: closeType, txSender: tx-sender, currentBidder: currentBidder, currentAmount: currentAmount})
        (ok nftIndex)
    )
)

;; read only methods
;; ---------------
(define-read-only (get-administrator)
    (var-get administrator))

(define-read-only (is-administrator)
    (ok (is-eq (var-get administrator) tx-sender)))

(define-read-only (get-base-token-uri)
    (var-get base-token-uri))

(define-read-only (get-mint-counter)
  (ok (var-get mint-counter))
)

(define-read-only (get-mint-price)
    (var-get mint-price))

(define-read-only (get-token-by-index (nftIndex uint))
    (ok (get-all-data nftIndex))
)

(define-read-only (get-beneficiaries (nftIndex uint))
    (let
        (
            (beneficiaries (map-get? nft-beneficiaries {nft-index: nftIndex}))
        )
        (ok beneficiaries)
    )
)

(define-read-only (get-offer-at-index (nftIndex uint) (offerIndex uint))
    (let
        (
            (the-offer (map-get? nft-offer-history {nft-index: nftIndex, offer-index: offerIndex}))
        )
        (ok the-offer)
    )
)

(define-read-only (get-bid-at-index (nftIndex uint) (bidIndex uint))
    (let
        (
            (the-bid (map-get? nft-bid-history {nft-index: nftIndex, bid-index: bidIndex}))
        )
        (ok the-bid)
    )
)

;; Get the edition from a knowledge of the #1 edition and the specific edition number
(define-read-only (get-edition-by-hash (asset-hash (buff 32)) (edition uint))
    (let
        (
            (nftIndex (unwrap! (get nft-index (map-get? nft-lookup {asset-hash: asset-hash, edition: edition})) amount-not-set))
        )
        (ok (get-all-data nftIndex))
    )
)


(define-read-only (get-token-by-hash (asset-hash (buff 32)))
    (let
        (
            (nftIndex (unwrap! (get nft-index (map-get? nft-lookup {asset-hash: asset-hash, edition: u1})) amount-not-set))
        )
        (ok (get-all-data nftIndex))
    )
)

(define-read-only (get-contract-data)
    (let
        (
            (the-administrator  (var-get administrator))
            (the-mint-price  (var-get mint-price))
            (the-base-token-uri  (var-get base-token-uri))
            (the-mint-counter  (var-get mint-counter))
            (the-platform-fee  (var-get platform-fee))
            (the-token-name  token-name)
            (the-token-symbol  token-symbol)
        )
        (ok (tuple  (administrator the-administrator)
                    (mintPrice the-mint-price)
                    (baseTokenUri the-base-token-uri)
                    (mintCounter the-mint-counter)
                    (platformFee the-platform-fee)
                    (tokenName the-token-name)
                    (tokenSymbol the-token-symbol)))
    )
)

(define-private (get-all-data (nftIndex uint))
    (let
        (
            (the-owner                  (unwrap-panic (nft-get-owner? loopbomb nftIndex)))
            (the-token-info             (map-get? nft-data {nft-index: nftIndex}))
            (the-sale-data              (map-get? nft-sale-data {nft-index: nftIndex}))
            (the-beneficiary-data       (map-get? nft-beneficiaries {nft-index: nftIndex}))
            (the-edition-counter        (default-to u0 (get edition-counter (map-get? nft-edition-counter {nft-index: nftIndex}))))
            (the-offer-counter          (default-to u0 (get offer-counter (map-get? nft-offer-counter {nft-index: nftIndex}))))
            (the-high-bid-counter       (default-to u0 (get high-bid-counter (map-get? nft-high-bid-counter {nft-index: nftIndex}))))
        )
        (ok (tuple  (offerCounter the-offer-counter)
                    (bidCounter the-high-bid-counter)
                    (editionCounter the-edition-counter)
                    (nftIndex nftIndex)
                    (tokenInfo the-token-info)
                    (saleData the-sale-data)
                    (beneficiaryData the-beneficiary-data)
                    (owner the-owner)
            )
        )
    )
)

(define-read-only (get-sale-data (nftIndex uint))
    (match (map-get? nft-sale-data {nft-index: nftIndex})
        mySaleData
        (ok mySaleData)
        not-found
    )
)

(define-read-only (get-token-name)
    (ok token-name)
)

(define-read-only (get-token-symbol)
    (ok token-symbol)
)

(define-read-only (get-balance)
    (begin
        (asserts! (is-eq (var-get administrator) tx-sender) not-allowed)
        (ok (stx-get-balance (as-contract tx-sender)))
    )
)

;; private methods
;; ---------------
(define-private (refund-bid (nftIndex uint) (currentBidder principal) (currentAmount uint))
    (begin
        (unwrap! (as-contract (stx-transfer? currentAmount tx-sender currentBidder)) failed-to-stx-transfer)
        (print {evt: "refund-bid", nftIndex: nftIndex, txSender: tx-sender, currentBidder: currentBidder, currentAmount: currentAmount})
        (ok true)
    )
)

;; need to account for reserve-stx
(define-private (record-bid (nftIndex uint) (bidAmount uint) (bidCounter uint) (appTimestamp uint) (saleCycle uint))
    (begin
        ;; see place-bid for the payment - no need for this (unwrap! (stx-transfer? bidAmount tx-sender (as-contract tx-sender)) failed-to-stx-transfer)
        (map-insert nft-bid-history {nft-index: nftIndex, bid-index: bidCounter} {bidder: tx-sender, amount: bidAmount, app-timestamp: appTimestamp, sale-cycle: saleCycle})
        (map-set nft-high-bid-counter {nft-index: nftIndex} {high-bid-counter: (+ bidCounter u1), sale-cycle: saleCycle})
        (print {evt: "record-bid", nftIndex: nftIndex, txSender: tx-sender, bidAmount: bidAmount, bidCounter: bidCounter, appTimestamp: appTimestamp, saleCycle: saleCycle})
        (ok true)
    )
)

(define-private (next-bid (nftIndex uint) (bidAmount uint) (bidCounter uint) (appTimestamp uint) (saleCycle uint))
    (begin
        (unwrap! (stx-transfer? bidAmount tx-sender (as-contract tx-sender)) failed-to-stx-transfer)
        (map-insert nft-bid-history {nft-index: nftIndex, bid-index: bidCounter} {bidder: tx-sender, amount: bidAmount, app-timestamp: appTimestamp, sale-cycle: saleCycle})
        (map-set nft-high-bid-counter {nft-index: nftIndex} {high-bid-counter: (+ bidCounter u1), sale-cycle: saleCycle})
        (print {appTimestamp: appTimestamp, bidAmount: bidAmount, bidCounter: bidCounter, evt: "next-bid", nftIndex: nftIndex, saleCycle: saleCycle, txSender: tx-sender})
        (ok true)
    )
)

;; sends payments to each recipient listed in the royalties
;; Note this is called by mint-edition where thee nftIndex actuallt referes to the series orginal and is where the royalties are stored.
(define-private (payment-split (nftIndex uint) (saleAmount uint) (payer principal) (seriesOriginal uint))
    (let
        (
            (addresses (unwrap! (get addresses (map-get? nft-beneficiaries {nft-index: seriesOriginal})) failed-to-mint-err))
            (shares (unwrap! (get shares (map-get? nft-beneficiaries {nft-index: seriesOriginal})) failed-to-mint-err))
            (secondaries (unwrap! (get secondaries (map-get? nft-beneficiaries {nft-index: seriesOriginal})) failed-to-mint-err))
            (saleCycle (unwrap! (get sale-cycle-index (map-get? nft-sale-data {nft-index: nftIndex})) amount-not-set))
            (split u0)
        )
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (nft-get-owner? loopbomb nftIndex) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u0) (element-at secondaries u0)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u1) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u1) (element-at secondaries u1)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u2) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u2) (element-at secondaries u2)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u3) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u3) (element-at secondaries u3)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u4) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u4) (element-at secondaries u4)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u5) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u5) (element-at secondaries u5)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u6) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u6) (element-at secondaries u6)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u7) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u7) (element-at secondaries u7)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u8) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u8) (element-at secondaries u8)) payment-share-error)) payment-share-error))
        (+ split (unwrap! (pay-royalty payer saleAmount (unwrap! (element-at addresses u9) payment-address-error) (unwrap! (if (is-eq saleCycle u1) (element-at shares u9) (element-at secondaries u9)) payment-share-error)) payment-share-error))
        (print {evt: "payment-split", nftIndex: nftIndex, saleCycle: saleCycle, payer: payer, saleAmount: saleAmount, txSender: tx-sender})
        (ok split)
    )
)

;; unit of saleAmount is in Satoshi and the share variable is a percentage (ex for 5% it will be equal to 5)
;; also the scalor is 1 on first purchase - direct from artist and 2 for secondary sales - so the seller gets half the
;; sale value and each royalty address gets half their original amount.
(define-private (pay-royalty (payer principal) (saleAmount uint) (payee principal) (share uint))
    (begin
        (if (> share u0)
            (let
                (
                    (split (/ (* saleAmount share) percentage-with-twodp))
                )
                ;; ignore royalty payment if its to the buyer / tx-sender.
                (if (not (is-eq tx-sender payee))
                    (unwrap! (stx-transfer? split payer payee) transfer-error)
                    (unwrap! (ok true) transfer-error)
                )
                (print {evt: "pay-royalty-primary", payee: payee, payer: payer, saleAmount: saleAmount, share: share, split: split, txSender: tx-sender})
                (ok split)
            )
            (ok u0)
        )
    )
)
