Source Code

;; FakFun T-Shirt Minimal Pre-Order Contract
(use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-constant ERR_UNAUTHORIZED (err u100))
(define-constant ERR_ALREADY_ORDERED (err u101))
(define-constant ERR_CAMPAIGN_FULL (err u102))
(define-constant ERR_NOT_SHIPPED (err u103))
(define-constant ERR_ALREADY_RATED (err u104))
(define-constant ERR_INVALID_SIZE (err u105))
(define-constant ERR_NO_ORDER (err u106))
(define-constant ERR_ALREADY_SHIPPED (err u107))
(define-constant ERR_INVALID_RATING (err u108))
(define-constant ERR_NOT_RATED (err u109))
(define-constant ERR_NOT_A_RATING (err u110))
(define-constant ERR_DEADLINE (err u111)) 
(define-constant ERR_SHIPPING_TOO_SLOW (err u112)) 
(define-constant ERR_ALREADY_CLAIMED (err u113)) 
(define-constant ERR_CAMPAIGN_ONGOING (err u114))
(define-constant ERR_CAMPAIGN_CLOSED (err u115)) 

(define-constant PRICE u50000000)
(define-constant TARGET_ORDERS u21)
(define-constant DEADLINE u2016) 
(define-constant FEES u5000000) 
(define-constant CAMPAIGN_DEADLINE u3024) 
(define-constant CAMPAIGN_START burn-block-height)
(define-constant ORACLE tx-sender)

(define-data-var artist principal tx-sender)
(define-data-var total-orders uint u0)
(define-data-var block-completion uint u0)
(define-data-var campaign-status uint u1)


(define-map orders principal 
  { 
    size: (string-ascii 3),
    ordered-block: uint,
    shipped-block: (optional uint),
    delivery-days: (optional uint),
    rating: (optional uint),
    rated-block: (optional uint),
    artist-response: (optional bool),
    claimed: bool
  }
)

(define-data-var buyer-list (list 21 principal) (list))

(define-map valid-sizes (string-ascii 3) bool)
(map-set valid-sizes "XS" true)
(map-set valid-sizes "S" true)
(map-set valid-sizes "M" true) 
(map-set valid-sizes "L" true)
(map-set valid-sizes "XL" true)
(map-set valid-sizes "XXL" true)

(define-read-only (get-order (buyer principal))
  (map-get? orders buyer)
)

(define-read-only (get-campaign-status)
  { orders: (var-get total-orders), target: TARGET_ORDERS }
)

(define-public (place-order (size (string-ascii 3)))
  (begin
    (asserts! (default-to false (map-get? valid-sizes size)) ERR_INVALID_SIZE)
    (asserts! (is-none (map-get? orders tx-sender)) ERR_ALREADY_ORDERED)
    (asserts! (< (var-get total-orders) TARGET_ORDERS) ERR_CAMPAIGN_FULL)
    (asserts! (> (var-get campaign-status) u0) ERR_CAMPAIGN_CLOSED)
  
    (try! (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer PRICE tx-sender (as-contract tx-sender) none))
    
    ;; Record order
    (map-set orders tx-sender { 
      size: size,
      ordered-block: burn-block-height, 
      shipped-block: none, 
      delivery-days: none,
      rating: none,
      rated-block: none,
      artist-response: none,
      claimed: false
    })
    (var-set buyer-list (unwrap! (as-max-len? (append (var-get buyer-list) tx-sender) u21) ERR_CAMPAIGN_FULL))
    (var-set total-orders (+ (var-get total-orders) u1))
    (if (is-eq (var-get total-orders) TARGET_ORDERS)
        (var-set block-completion burn-block-height)
        true)
    (ok true)
  )
)

(define-public (mark-shipped (buyer principal) (delivery-days uint))
  (let ((order (unwrap! (map-get? orders buyer) ERR_NO_ORDER)))
    (asserts! (is-eq tx-sender (var-get artist)) ERR_UNAUTHORIZED)
    (asserts! (is-none (get shipped-block order)) ERR_ALREADY_SHIPPED)
    (asserts! (< delivery-days u24) ERR_SHIPPING_TOO_SLOW)
    (asserts! (< burn-block-height (+ (var-get block-completion) DEADLINE)) ERR_DEADLINE)
    (asserts! (not (get claimed order)) ERR_ALREADY_CLAIMED)

    (map-set orders buyer (merge order { 
      shipped-block: (some burn-block-height),
      delivery-days: (some delivery-days)
    }))
    (ok true)
  )
)

(define-public (buyer-rates-delivery (rating uint))
  (let ((order (unwrap! (map-get? orders tx-sender) ERR_NO_ORDER)))
    (asserts! (is-some (get shipped-block order)) ERR_NOT_SHIPPED)
    (asserts! (is-none (get rating order)) ERR_ALREADY_RATED)
    (asserts! (or (is-eq rating u0) (is-eq rating u50) (is-eq rating u100)) ERR_INVALID_RATING)
    (asserts! (not (get claimed order)) ERR_ALREADY_CLAIMED)

    (map-set orders tx-sender (merge order { rating: (some rating), rated-block: (some burn-block-height) }))
    
    (if (is-eq rating u100)
      (execute-rating tx-sender)
      (ok true)
    )
  )
)

(define-public (artist-respond (buyer principal) (agrees bool))
  (let ((order (unwrap! (map-get? orders buyer) ERR_NO_ORDER)))
    (asserts! (is-eq tx-sender (var-get artist)) ERR_UNAUTHORIZED)
    (asserts! (is-some (get rating order)) ERR_NOT_RATED)
    (asserts! (not (get claimed order)) ERR_ALREADY_CLAIMED)

    (map-set orders buyer (merge order { 
      artist-response: (some agrees)
    }))
    
    (if agrees
      (execute-rating buyer)
      (ok true)
    )
  )
)

(define-public (oracle-decide (buyer principal) (final-rating uint))
  (let ((order (unwrap! (map-get? orders buyer) ERR_NO_ORDER))
        (rated-block (unwrap! (get rated-block order) ERR_NOT_SHIPPED))
        (delivery-days (unwrap! (get delivery-days order) ERR_NOT_SHIPPED))
        (artist-response-deadline (+ rated-block (* delivery-days u72)))
        (artist-resp (get artist-response order)))
    
    (asserts! (not (get claimed order)) ERR_ALREADY_CLAIMED)
    (asserts! (is-eq tx-sender ORACLE) ERR_UNAUTHORIZED)
    (asserts! (<= final-rating u100) ERR_INVALID_RATING) 
    
    (if (is-none artist-resp) 
    (asserts! (> burn-block-height artist-response-deadline) ERR_DEADLINE) 
    true)
        
    ;; Update rating and execute    
    (map-set orders buyer (merge order { rating: (some final-rating) }))
    (execute-rating buyer)
  )
)

(define-public (claim-never-rated (buyer principal))
  (let ((order (unwrap! (map-get? orders buyer) ERR_NO_ORDER)))
    (asserts! (is-eq tx-sender (var-get artist)) ERR_UNAUTHORIZED)
    (asserts! (is-some (get shipped-block order)) ERR_NOT_SHIPPED)
    (asserts! (is-none (get rating order)) ERR_NOT_RATED)
    (asserts! (not (get claimed order)) ERR_ALREADY_CLAIMED)

    ;; Check if 2x delivery time has passed
    (let ((shipped-block (unwrap! (get shipped-block order) ERR_NOT_SHIPPED))
          (delivery-days (unwrap! (get delivery-days order) ERR_NOT_SHIPPED))
          (deadline (+ shipped-block (* delivery-days u288)))) 
      (asserts! (> burn-block-height deadline) ERR_DEADLINE)
      
      ;; Mark as 100% and pay artist
      (map-set orders buyer (merge order { rating: (some u100) }))
      (execute-rating buyer)
    )
  )
)

(define-public (claim-never-shipped (buyer principal))
  (let ((order (unwrap! (map-get? orders buyer) ERR_NO_ORDER)))
    (asserts! (is-eq tx-sender buyer) ERR_UNAUTHORIZED)
    (asserts! (is-none (get shipped-block order)) ERR_ALREADY_SHIPPED)
    (asserts! (is-none (get rating order)) ERR_ALREADY_RATED)
    (asserts! (>= burn-block-height (+ (var-get block-completion) DEADLINE)) ERR_DEADLINE)
    (asserts! (not (get claimed order)) ERR_ALREADY_CLAIMED)

    (map-set orders buyer (merge order { rating: (some u0) }))
    (execute-rating buyer)
  )
)

(define-private (execute-rating (buyer principal))
  (let ((order (unwrap! (map-get? orders buyer) ERR_NO_ORDER))
        (rating (unwrap! (get rating order) ERR_NOT_RATED))
        (artista (var-get artist)))
    (try! (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer FEES tx-sender ORACLE none)))
    (map-set orders buyer (merge order { claimed: true}))
    (if (is-eq rating u100)
      (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer (- PRICE FEES) tx-sender artista none))
      (if (is-eq rating u50)
        (begin
          (try! (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer (/ (- PRICE FEES) u2) tx-sender artista none)))
          (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer (- (- PRICE FEES) (/ (- PRICE FEES) u2)) tx-sender buyer none))
        )
        (if (is-eq rating u0)
          (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer (- PRICE FEES) tx-sender buyer none))
          (let ((artist-money (/ (* (- PRICE FEES) rating) u100)))
          (try! (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer artist-money tx-sender artista none)))
          (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer (- (- PRICE FEES) artist-money) tx-sender buyer none))
          )
        )
      )
    )
  )
)

;; Admin functions
(define-public (set-artist (new-artist principal))
  (begin
    (asserts! (is-eq tx-sender ORACLE) ERR_UNAUTHORIZED)
    (var-set artist new-artist)
    (ok true)
  )
)

(define-public (oracle-refund-incomplete-campaign)
  (begin
    (asserts! (> burn-block-height (+ CAMPAIGN_START CAMPAIGN_DEADLINE)) ERR_CAMPAIGN_ONGOING)
    
    (asserts! (is-eq (var-get block-completion) u0) ERR_CAMPAIGN_FULL)
    (asserts! (< (var-get total-orders) TARGET_ORDERS) ERR_CAMPAIGN_FULL)

    (asserts! (is-eq tx-sender ORACLE) ERR_UNAUTHORIZED)
    (var-set campaign-status u0)
    (fold refund-buyer (var-get buyer-list) (ok true))
  )
)

(define-private (refund-buyer (buyer principal) (previous-result (response bool uint)))
  (begin
    (try! previous-result) 
    (as-contract (contract-call? 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token transfer PRICE tx-sender buyer none))
  )
)

Functions (13)

FunctionAccessArgs
get-orderread-onlybuyer: principal
get-campaign-statusread-only
place-orderpublicsize: (string-ascii 3
mark-shippedpublicbuyer: principal, delivery-days: uint
buyer-rates-deliverypublicrating: uint
artist-respondpublicbuyer: principal, agrees: bool
oracle-decidepublicbuyer: principal, final-rating: uint
claim-never-ratedpublicbuyer: principal
claim-never-shippedpublicbuyer: principal
execute-ratingprivatebuyer: principal
set-artistpublicnew-artist: principal
oracle-refund-incomplete-campaignpublic
refund-buyerprivatebuyer: principal, previous-result: (response bool uint