Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 185 additions & 20 deletions contracts/time-auction.clar
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
(define-constant ERR_RESERVE_NOT_MET (err u6008))
(define-constant ERR_INVALID_DURATION (err u6009))

;; NEW: Buy It Now errors
(define-constant ERR_BUY_NOW_UNAVAILABLE (err u6010))
(define-constant ERR_BUY_NOW_EXPIRED (err u6011))
(define-constant ERR_BUY_NOW_ALREADY_SOLD (err u6012))

;; Minimum auction duration: 1 hour
(define-constant MIN_DURATION u3600)
;; Maximum auction duration: 30 days
Expand All @@ -46,6 +51,8 @@
starting-price: uint,
reserve-price: uint,
min-increment: uint,
;; NEW: Buy It Now price (0 if not available)
buy-now-price: uint,
highest-bid: uint,
highest-bidder: (optional principal),
start-time: uint,
Expand All @@ -70,35 +77,84 @@
{
active-bids: uint,
total-bids: uint,
wins: uint
wins: uint,
;; NEW: Track buy now purchases separately
buy-now-purchases: uint
}
)

(define-data-var next-auction-id uint u1)
(define-data-var total-volume uint u0)
;; NEW: Track volume from buy now transactions
(define-data-var buy-now-volume uint u0)

;; ============================================
;; EVENTS (using print for monitoring)
;; ============================================

(define-private (log-auction-created (auction-id uint) (seller principal) (item-type (string-ascii 20)) (starting-price uint) (end-time uint))
(print { event: "auction-created", auction-id: auction-id, seller: seller, item-type: item-type, starting-price: starting-price, end-time: end-time, timestamp: stacks-block-time })
(define-private (log-auction-created (auction-id uint) (seller principal) (item-type (string-ascii 20)) (starting-price uint) (end-time uint) (buy-now-price uint))
(print {
event: "auction-created",
auction-id: auction-id,
seller: seller,
item-type: item-type,
starting-price: starting-price,
buy-now-price: buy-now-price,
end-time: end-time,
timestamp: stacks-block-time
})
)

(define-private (log-bid-placed (auction-id uint) (bidder principal) (bid-amount uint) (bid-number uint))
(print { event: "bid-placed", auction-id: auction-id, bidder: bidder, bid-amount: bid-amount, bid-number: bid-number, timestamp: stacks-block-time })
(print {
event: "bid-placed",
auction-id: auction-id,
bidder: bidder,
bid-amount: bid-amount,
bid-number: bid-number,
timestamp: stacks-block-time
})
)

;; NEW: Buy now event
(define-private (log-buy-now (auction-id uint) (buyer principal) (price uint))
(print {
event: "buy-now-purchase",
auction-id: auction-id,
buyer: buyer,
price: price,
timestamp: stacks-block-time
})
)

(define-private (log-auction-settled (auction-id uint) (winner principal) (winning-bid uint) (reserve-met bool))
(print { event: "auction-settled", auction-id: auction-id, winner: winner, winning-bid: winning-bid, reserve-met: reserve-met, timestamp: stacks-block-time })
(print {
event: "auction-settled",
auction-id: auction-id,
winner: winner,
winning-bid: winning-bid,
reserve-met: reserve-met,
timestamp: stacks-block-time
})
)

(define-private (log-auction-cancelled (auction-id uint) (seller principal))
(print { event: "auction-cancelled", auction-id: auction-id, seller: seller, timestamp: stacks-block-time })
(print {
event: "auction-cancelled",
auction-id: auction-id,
seller: seller,
timestamp: stacks-block-time
})
)

(define-private (log-auction-extended (auction-id uint) (old-end uint) (new-end uint))
(print { event: "auction-extended", auction-id: auction-id, old-end-time: old-end, new-end-time: new-end, timestamp: stacks-block-time })
(print {
event: "auction-extended",
auction-id: auction-id,
old-end-time: old-end,
new-end-time: new-end,
timestamp: stacks-block-time
})
)

;; ============================================
Expand All @@ -113,6 +169,29 @@
stacks-block-time
)

;; NEW: Check if buy now is available
(define-read-only (is-buy-now-available (auction-id uint))
(match (map-get? auctions { auction-id: auction-id })
auction (and
(> (get buy-now-price auction) u0)
(< stacks-block-time (get end-time auction))
(is-eq (get bid-count auction) u0)
(not (get is-settled auction))
)
false
)
)

;; NEW: Get buy now price
(define-read-only (get-buy-now-price (auction-id uint))
(match (map-get? auctions { auction-id: auction-id })
auction (if (> (get buy-now-price auction) u0)
(ok (get buy-now-price auction))
(err ERR_BUY_NOW_UNAVAILABLE))
(err ERR_AUCTION_NOT_FOUND)
)
)

(define-read-only (get-time-remaining (auction-id uint))
(match (map-get? auctions { auction-id: auction-id })
auction (if (> (get end-time auction) stacks-block-time)
Expand Down Expand Up @@ -169,18 +248,42 @@
)
)

;; NEW: Generate buy now receipt
(define-read-only (generate-buy-now-receipt
(auction-id uint)
(price uint)
)
(let
(
(auction-str (unwrap-panic (to-ascii? auction-id)))
(price-str (unwrap-panic (to-ascii? price)))
(time-str (unwrap-panic (to-ascii? stacks-block-time)))
)
(concat "BUY_NOW_RECEIPT|AUCTION:"
(concat auction-str
(concat "|PRICE:"
(concat price-str
(concat "|TIME:" time-str)
)
)
)
)
)
)

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

;; Create a new auction
;; Create a new auction (with optional buy now price)
(define-public (create-auction
(item-type (string-ascii 20))
(item-id uint)
(starting-price uint)
(reserve-price uint)
(min-increment uint)
(duration uint)
(buy-now-price uint) ;; NEW: 0 = disabled
)
(let
(
Expand All @@ -192,6 +295,10 @@
(asserts! (>= duration MIN_DURATION) ERR_INVALID_DURATION)
(asserts! (<= duration MAX_DURATION) ERR_INVALID_DURATION)

;; Validate buy now price if provided
(asserts! (or (is-eq buy-now-price u0) (>= buy-now-price starting-price))
(err u6013)) ;; ERR_INVALID_BUY_NOW

;; Create auction
(map-set auctions
{ auction-id: auction-id }
Expand All @@ -202,6 +309,7 @@
starting-price: starting-price,
reserve-price: reserve-price,
min-increment: min-increment,
buy-now-price: buy-now-price, ;; NEW
highest-bid: u0,
highest-bidder: none,
start-time: start-time,
Expand All @@ -215,17 +323,76 @@
(var-set next-auction-id (+ auction-id u1))

;; Log event
(log-auction-created auction-id tx-sender item-type starting-price end-time)
(log-auction-created auction-id tx-sender item-type starting-price end-time buy-now-price)

(ok {
auction-id: auction-id,
start-time: start-time,
end-time: end-time
end-time: end-time,
buy-now-price: buy-now-price
})
)
)

;; NEW: Buy It Now function
(define-public (buy-it-now (auction-id uint))
(let
(
(auction (unwrap! (map-get? auctions { auction-id: auction-id }) ERR_AUCTION_NOT_FOUND))
(buy-now (get buy-now-price auction))
(seller (get seller auction))
)
;; Validate buy now is available
(asserts! (is-buy-now-available auction-id) ERR_BUY_NOW_UNAVAILABLE)

;; Cannot buy your own auction
(asserts! (not (is-eq tx-sender seller)) ERR_CANNOT_BID_OWN)

;; Transfer payment to seller
(try! (stx-transfer? buy-now tx-sender seller))

;; Mark auction as settled
(map-set auctions
{ auction-id: auction-id }
(merge auction {
is-settled: true,
highest-bid: buy-now,
highest-bidder: (some tx-sender)
})
)

;; Update stats
(var-set total-volume (+ (var-get total-volume) buy-now))
(var-set buy-now-volume (+ (var-get buy-now-volume) buy-now))

;; Update buyer stats
(let
(
(buyer-stat (default-to { active-bids: u0, total-bids: u0, wins: u0, buy-now-purchases: u0 }
(map-get? user-bids { user: tx-sender })))
)
(map-set user-bids
{ user: tx-sender }
(merge buyer-stat {
buy-now-purchases: (+ (get buy-now-purchases buyer-stat) u1),
wins: (+ (get wins buyer-stat) u1)
})
)
)

;; Log event
(log-buy-now auction-id tx-sender buy-now)
(log-auction-settled auction-id tx-sender buy-now true)

(ok {
purchase-complete: true,
price: buy-now,
receipt: (generate-buy-now-receipt auction-id buy-now)
})
)
)

;; Place a bid
;; Place a bid (updated to check buy now availability)
(define-public (place-bid (auction-id uint) (bid-amount uint))
(let
(
Expand All @@ -250,8 +417,7 @@
true
)

;; Accept new bid (simplified for testnet - no escrow)
;; In production, use: (try! (stx-transfer? bid-amount tx-sender (as-contract tx-sender)))
;; Accept new bid
(var-set total-volume (+ (var-get total-volume) bid-amount))

;; Extend auction if bid near end (anti-sniping)
Expand Down Expand Up @@ -287,7 +453,7 @@
;; Update user stats
(let
(
(user-stat (default-to { active-bids: u0, total-bids: u0, wins: u0 }
(user-stat (default-to { active-bids: u0, total-bids: u0, wins: u0, buy-now-purchases: u0 }
(map-get? user-bids { user: tx-sender })))
)
(map-set user-bids
Expand Down Expand Up @@ -316,7 +482,7 @@
)
)

;; Settle auction after it ends
;; Settle auction after it ends (unchanged)
(define-public (settle-auction (auction-id uint))
(let
(
Expand All @@ -335,8 +501,7 @@
(
(winner (unwrap! (get highest-bidder auction) ERR_AUCTION_NOT_FOUND))
)
;; Transfer funds to seller (simplified for testnet)
;; In production, use: (try! (as-contract (stx-transfer? winning-bid tx-sender seller)))
;; Transfer funds to seller
(try! (stx-transfer? winning-bid tx-sender seller))

;; Update stats
Expand All @@ -345,7 +510,7 @@
;; Update winner stats
(let
(
(winner-stat (default-to { active-bids: u0, total-bids: u0, wins: u0 }
(winner-stat (default-to { active-bids: u0, total-bids: u0, wins: u0, buy-now-purchases: u0 }
(map-get? user-bids { user: winner })))
)
(map-set user-bids
Expand Down Expand Up @@ -384,7 +549,7 @@
(log-auction-settled auction-id seller u0 false)

(ok {
winner: seller, ;; Item returns to seller
winner: seller,
winning-bid: u0,
reserve-met: false
})
Expand All @@ -393,7 +558,7 @@
)
)

;; Cancel auction (only if no bids)
;; Cancel auction (only if no bids) - updated to handle buy now
(define-public (cancel-auction (auction-id uint))
(let
(
Expand Down