;; pulse-seed - Token Launchpad Protocol
;; Decentralized token launch and fair distribution
(define-constant CONTRACT-OWNER tx-sender)
(define-constant MIN-RAISE u1000000000)
(define-constant MAX-RAISE u100000000000)
(define-constant MIN-CONTRIBUTION u10000000)
(define-constant MAX-CONTRIBUTION u1000000000)
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-NOT-ACTIVE (err u101))
(define-constant ERR-ALREADY-CLAIMED (err u102))
(define-constant ERR-BELOW-MIN (err u103))
(define-constant ERR-ABOVE-MAX (err u104))
(define-constant ERR-NOT-ENDED (err u105))
(define-constant ERR-GOAL-NOT-MET (err u106))
(define-map launches uint
{ creator: principal, token-address: (optional principal),
soft-cap: uint, hard-cap: uint,
start-block: uint, end-block: uint,
total-raised: uint, finalized: bool, refunded: bool,
title: (string-ascii 64), description: (string-utf8 512) })
(define-map contributions { launch-id: uint, contributor: principal } uint)
(define-map claimed { launch-id: uint, contributor: principal } bool)
(define-data-var launch-count uint u0)
(define-data-var platform-fee uint u200)
(define-public (create-launch
(soft-cap uint) (hard-cap uint)
(start-block uint) (end-block uint)
(title (string-ascii 64)) (description (string-utf8 512)))
(let ((lid (+ (var-get launch-count) u1)))
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
(asserts! (>= soft-cap MIN-RAISE) ERR-BELOW-MIN)
(asserts! (<= hard-cap MAX-RAISE) ERR-ABOVE-MAX)
(map-set launches lid
{ creator: tx-sender, token-address: none,
soft-cap: soft-cap, hard-cap: hard-cap,
start-block: start-block, end-block: end-block,
total-raised: u0, finalized: false, refunded: false,
title: title, description: description })
(var-set launch-count lid)
(ok lid)))
(define-public (contribute (launch-id uint) (amount uint))
(let ((launch (unwrap! (map-get? launches launch-id) ERR-NOT-ACTIVE)))
(asserts! (>= block-height (get start-block launch)) ERR-NOT-ACTIVE)
(asserts! (<= block-height (get end-block launch)) ERR-NOT-ACTIVE)
(asserts! (>= amount MIN-CONTRIBUTION) ERR-BELOW-MIN)
(asserts! (<= (+ amount (default-to u0 (map-get? contributions { launch-id: launch-id, contributor: tx-sender })))
MAX-CONTRIBUTION) ERR-ABOVE-MAX)
(asserts! (< (get total-raised launch) (get hard-cap launch)) ERR-ABOVE-MAX)
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
(map-set contributions { launch-id: launch-id, contributor: tx-sender }
(+ amount (default-to u0 (map-get? contributions { launch-id: launch-id, contributor: tx-sender }))))
(map-set launches launch-id (merge launch { total-raised: (+ (get total-raised launch) amount) }))
(ok true)))
(define-public (finalize (launch-id uint))
(let ((launch (unwrap! (map-get? launches launch-id) ERR-NOT-ACTIVE)))
(asserts! (is-eq tx-sender (get creator launch)) ERR-OWNER-ONLY)
(asserts! (> block-height (get end-block launch)) ERR-NOT-ENDED)
(asserts! (>= (get total-raised launch) (get soft-cap launch)) ERR-GOAL-NOT-MET)
(let ((fee (/ (* (get total-raised launch) (var-get platform-fee)) u10000))
(net (- (get total-raised launch) fee)))
(try! (as-contract (stx-transfer? net tx-sender (get creator launch))))
(try! (as-contract (stx-transfer? fee tx-sender CONTRACT-OWNER)))
(ok (map-set launches launch-id (merge launch { finalized: true }))))))
(define-public (refund (launch-id uint))
(let ((launch (unwrap! (map-get? launches launch-id) ERR-NOT-ACTIVE))
(contribution (default-to u0 (map-get? contributions { launch-id: launch-id, contributor: tx-sender }))))
(asserts! (> block-height (get end-block launch)) ERR-NOT-ENDED)
(asserts! (or (< (get total-raised launch) (get soft-cap launch)) (get refunded launch)) ERR-ALREADY-CLAIMED)
(asserts! (> contribution u0) ERR-BELOW-MIN)
(asserts! (is-none (map-get? claimed { launch-id: launch-id, contributor: tx-sender })) ERR-ALREADY-CLAIMED)
(map-set claimed { launch-id: launch-id, contributor: tx-sender } true)
(as-contract (stx-transfer? contribution tx-sender contract-caller))))
(define-read-only (get-launch (launch-id uint)) (ok (map-get? launches launch-id)))
(define-read-only (get-contribution (launch-id uint) (contributor principal))
(ok (map-get? contributions { launch-id: launch-id, contributor: contributor })))
(define-read-only (get-launch-count) (ok (var-get launch-count)))