(use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
(use-trait ft-trait-ext 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.trait-sip-010.sip-010-trait)
(use-trait ft-plus-trait 'SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1.ft-plus-trait.ft-plus-trait)
;; ERRS
(define-constant ERR-INVALID-POOL (err u4001))
(define-constant ERR-INSUFFICIENT-AMOUNT (err u5001))
(define-constant ERR-ZERO-AMOUNT (err u5002))
(define-constant ERR-INVALID-AMOUNT (err u5003))
(define-constant ERR-INVALID-ID (err u5004))
(define-constant ERR-INVALID-ADDRESS (err u5005))
(define-constant ERR-NOT-QUALIFIED (err u5006))
(define-constant ERR-NOT-AUTHORIZED (err u5009))
(define-constant ERR-BELOW-MIN-PERIOD (err u6000))
(define-constant ERR-LAUNCHPAD-INACTIVE (err u7000))
(define-constant ERR-INVALID-TOKEN (err u7001))
(define-constant ERR-INVALID-LP-TOKEN (err u7002))
(define-constant ERR-TOKEN-LAUNCH-NOT-ENDED (err u7003))
(define-constant ERR-NOT-PARTICIPANT (err u7004))
(define-constant ERR-ALREADY-CLAIMED (err u7005))
(define-constant ERR-MAX-DEPOSIT-EXCEEDED (err u8001))
(define-constant ERR-HARDCAP-EXCEEDED (err u8002))
(define-constant ERR-MIN-TARGET-NOT-REACHED (err u8003))
(define-constant ERR-TOKEN-IS-VESTED (err u8004))
(define-constant ERR-BELOW-MINIMUM-POOL-ALLOCATION (err u9000))
(define-constant ERR-BELOW-MINIMUM-LISTING-ALLOCATION (err u9001))
(define-constant ERR-BELOW-MINIMUM-CAMPAIGN-ALLOCATION (err u9002))
(define-constant ERR-NO-CAMPAIGN-ALLOCATION (err u9003))
(define-constant ERR-REWARDS-NOT-SET (err u9004))
;; DATA MAPS AND VARS
(define-data-var contract-owner principal tx-sender)
(define-data-var launchpad-nonce uint u0)
(define-data-var min-goat-balance uint u0)
(define-data-var goat-token principal .memegoatstx)
(define-data-var paused bool false)
(define-data-var launchpad-fee uint u2)
;; @desc map to stop token launches
(define-map launchpad-map
{token-launch-id: uint}
{
token: principal,
pool-amount: uint,
hardcap: uint,
softcap: uint,
total-stx-deposited: uint,
no-of-participants: uint,
min-stx-deposit: uint,
max-stx-deposit: uint,
duration: uint,
start-block: uint,
end-block: uint,
owner: principal,
is-vested: bool,
is-listed: bool,
listing-allocation: uint,
campaign-allocation: (optional uint),
campaign-rewards-set: bool,
burn-lp: bool
}
)
;; @desc map to store token-addr
(define-map launchpad-map-by-token-addr
{token-addr: principal}
uint
)
;; @desc map to store user deposits
(define-map users-deposits
{ user-addr: principal, token-launch-id: uint }
uint
)
;; @desc map to store claim history
(define-map user-claimed
{ user-addr : principal, token-launch-id: uint }
bool
)
;; @desc map to store rewards
(define-map campaign-rewards
{ user-addr: principal, token-launch-id: uint }
uint
)
;; READ-ONLY CALLS
;; @desc is-paused: contract status
;; @returns (boolean)
(define-read-only (is-paused)
(var-get paused)
)
;; @desc get-token-launch-by-id: gets the token launch by id
;; @params token-launch-id
;; @returns (response launchpad-record)
(define-read-only (get-token-launch-by-id (token-launch-id uint))
(ok (unwrap! (map-get? launchpad-map {token-launch-id: token-launch-id}) ERR-INVALID-ID))
)
;; @desc get-token-id-launch-by-addr : gets the token id of a token-launch using the token address
;; @params token-addr
;; @returns (response uint)
(define-read-only (get-token-id-launch-by-addr (token-addr <ft-trait>))
(ok (unwrap! (map-get? launchpad-map-by-token-addr {token-addr: (contract-of token-addr)}) ERR-INVALID-ADDRESS))
)
;; @desc get-user-deposits-exists: checks if user has deposited stx
;; @params user-addr
;; @params token-launch-id
;; @returns (response boolean)
(define-read-only (get-user-deposits-exists (user-addr principal) (token-launch-id uint))
(map-get? users-deposits {user-addr: user-addr, token-launch-id: token-launch-id})
)
;; @desc get-user-deposits-exists: checks if user has deposited stx
;; @params user-addr
;; @params token-launch-id
;; @returns (response boolean)
(define-read-only (get-user-rewards (user-addr principal) (token-launch-id uint))
(default-to u0 (map-get? campaign-rewards {user-addr: user-addr, token-launch-id: token-launch-id}))
)
;; @desc get-user-deposits: gets amount of stx deposited by user
;; @params user-addr
;; @params token-launch-id
;; @returns (response uint)
(define-read-only (get-user-deposits (user-addr principal) (token-launch-id uint))
(default-to u0 (get-user-deposits-exists user-addr token-launch-id))
)
;; @desc calculate-allocation: gets the calculated amount of launch tokens allocated to the user
;; @params user-addr
;; @params token-launch-id
;; @returns (response uint)
(define-read-only (calculate-allocation (user-addr principal) (token-launch-id uint))
(let
((user-deposit (get-user-deposits user-addr token-launch-id)))
(if (> user-deposit u0)
(* (unwrap-panic (get-stx-quote token-launch-id)) user-deposit)
u0
)
)
)
;; @desc check-if-claimed: checks if user has claimed tokens
;; @params user-addr
;; @params token-launch-id
;; @returns (response boolean)
(define-read-only (check-if-claimed (user-addr principal) (token-launch-id uint))
(default-to false (map-get? user-claimed { user-addr: user-addr, token-launch-id: token-launch-id}))
)
;; @desc get-contract-owner: gets owner address
;; @returns (response principal)
(define-read-only (get-contract-owner)
(ok (var-get contract-owner))
)
;; @desc get-stx-quote: gets the current exchange rate of token
;; @params user-addr
;; @params token-launch-id
;; @returns (response uint)
(define-read-only (get-stx-quote (token-launch-id uint))
(let
(
(token-launch (try! (get-token-launch-by-id token-launch-id)))
(token-pool (get pool-amount token-launch))
(stx-pool (get total-stx-deposited token-launch))
)
(if (> stx-pool u0)
(ok (/ token-pool stx-pool))
(ok u0)
)
)
)
;; @desc get-launchpad-fee: gets launchpad fee
;; @returns (response uint)
(define-read-only (get-launchpad-fee)
(var-get launchpad-fee)
)
;; MANAGEMENT CALLS
;; @desc set-contract-owner: sets owner
;; @requirement only callable by current owner
;; @params owner
;; @returns (response boolean)
(define-public (set-contract-owner (owner principal))
(begin
(try! (check-is-owner))
(ok (var-set contract-owner owner))
)
)
;; @desc set-launchpad-fee: updates the launchpad-fee
;; @requirement only callable by current owner
;; @params new-fee
;; @returns (response boolean)
(define-public (set-launchpad-fee (new-fee uint))
(begin
(try! (check-is-owner))
(ok (var-set launchpad-fee new-fee))
)
)
;; @desc set-min-goat-balance: updates the minimum balance of memegoat tokens required to participate
;; @requirement only callable by current owner
;; @params amount
;; @returns (response boolean)
(define-public (set-min-goat-balance (amount uint))
(begin
(try! (check-is-owner))
(asserts! (> amount u0) ERR-ZERO-AMOUNT)
(var-set min-goat-balance amount)
(ok true)
)
)
;; @desc set-campaign-rewards: set campaign rewards for participants
;; @requirement only callable by current owner
;; @params token-launch-id
;; @params addresses
;; @returns (response boolean)
(define-public (set-rewards (token-launch-id uint) (addresses (list 200 {addr: principal, points: uint})))
(begin
(try! (check-is-owner))
(let
(
(token-launch (try! (get-token-launch-by-id token-launch-id)))
(token-launch-updated (merge token-launch {
campaign-rewards-set: true,
}))
)
(fold add-reward addresses token-launch-id)
(map-set launchpad-map {token-launch-id: token-launch-id} token-launch-updated)
)
(ok true)
)
)
;; @desc add-exchange-liquidity: transfers launch liquidity to velar
;; @requirement only callable by current owner or campaign creator
;; @params token-launch-id
;; @params token-trait
;; @params token-0
;; @param lp-token
;; @returns (response boolean)
(define-public (add-exchange-liquidity
(token-launch-id uint)
(token-trait <ft-trait>)
(token-0 <ft-trait>)
(lp-token <ft-plus-trait>)
)
(begin
(asserts! (not (var-get paused)) ERR-LAUNCHPAD-INACTIVE)
(let
(
(sender tx-sender)
(token-launch (try! (get-token-launch-by-id token-launch-id)))
(pool-id (unwrap! (contract-call? 'SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1.univ2-core get-pool-id (contract-of token-0) (contract-of token-trait)) ERR-INVALID-POOL))
(pool (contract-call? 'SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1.univ2-core do-get-pool pool-id))
(total-stx-deposited (get total-stx-deposited token-launch))
(softcap (get softcap token-launch))
(hardcap (get hardcap token-launch))
(token (get token token-launch))
(end-block (get end-block token-launch))
(owner (get owner token-launch))
(burn-lp (get burn-lp token-launch))
(listing-allocation (get listing-allocation token-launch))
(fee (get-fee total-stx-deposited))
(list-amount-stx (- total-stx-deposited fee))
(token-launch-updated (merge token-launch {
is-listed: true,
}))
)
(asserts! (or (is-eq owner sender) (try! (check-is-owner))) ERR-NOT-AUTHORIZED)
(asserts! (>= total-stx-deposited softcap) ERR-MIN-TARGET-NOT-REACHED)
(asserts! (or (< end-block block-height) (is-eq total-stx-deposited hardcap)) ERR-TOKEN-LAUNCH-NOT-ENDED)
(asserts! (is-eq token (contract-of token-trait)) ERR-INVALID-TOKEN)
(asserts! (is-eq (get lp-token pool) (contract-of lp-token)) ERR-INVALID-LP-TOKEN)
;; transfer stx to vault
(try!
(as-contract (contract-call? .memegoat-launchpad-vault-v2 transfer-to-exchange
pool-id
token-0
token-trait
lp-token
list-amount-stx
listing-allocation
(calc-4-percent list-amount-stx)
(calc-4-percent listing-allocation)
)
)
)
(if burn-lp
(try! (as-contract (contract-call? .memegoat-launchpad-vault-v2 burn-lp-token lp-token)))
(let
(
(lp-balance (try! (contract-call? .memegoat-launchpad-vault-v2 get-balance lp-token)))
)
(try! (as-contract (contract-call? .memegoat-launchpad-vault-v2 transfer-ft lp-token lp-balance owner)))
)
)
;; transfer fee to treasury
(try! (as-contract (contract-call? .memegoat-launchpad-vault-v2 transfer-stx fee .memegoat-treasury)))
(map-set launchpad-map {token-launch-id: token-launch-id} token-launch-updated)
)
(ok true)
)
)
;; @desc pause: updates contracts paused state
;; @requirement only callable by current owner
;; @params new-paused
;; @returns (response boolean)
(define-public (pause (new-paused bool))
(begin
(try! (check-is-owner))
(ok (var-set paused new-paused))
)
)
;; PUBLIC CALLS
;; @desc register-token-launch: creates a new token launch
;; @params token
;; @params pool-amount
;; @params hardcap
;; @params softcap
;; @params start-block
;; @params end-block
;; @params min-stx-deposit
;; @params max-stx-deposit
;; @params is-vested
;; @params listing-allocation
;; @params campaign-allocation
;; @returns (response boolean)
(define-public
(register-token-launch
(token <ft-trait>)
(pool-amount uint)
(hardcap uint)
(softcap uint)
(start-block uint)
(end-block uint)
(min-stx-deposit uint)
(max-stx-deposit uint)
(is-vested bool)
(listing-allocation uint)
(campaign-allocation (optional uint))
(burn-lp bool)
)
(begin
(asserts! (not (var-get paused)) ERR-LAUNCHPAD-INACTIVE)
(asserts! (> hardcap softcap) ERR-INVALID-AMOUNT)
(asserts! (> pool-amount u0) ERR-ZERO-AMOUNT)
(asserts! (and (> min-stx-deposit u0) (> max-stx-deposit u0)) ERR-ZERO-AMOUNT)
(let
(
(total-supply (try! (contract-call? token get-total-supply)))
(min-pool-amount (/ (* total-supply u40) u100))
(min-listing-allocation (/ (* total-supply u25) u100))
(min-campaign-allocation (/ (* total-supply u5) u100))
(campaign-amount (if (is-some campaign-allocation) (unwrap-panic campaign-allocation) u0 ))
(total-to-send (+ pool-amount listing-allocation campaign-amount))
(duration (- end-block start-block))
(next-launchpad-id (+ (var-get launchpad-nonce) u1))
)
(asserts! (>= pool-amount min-pool-amount ) ERR-BELOW-MINIMUM-POOL-ALLOCATION)
(asserts! (>= listing-allocation min-listing-allocation ) ERR-BELOW-MINIMUM-LISTING-ALLOCATION)
(asserts! (>= duration u144) ERR-BELOW-MIN-PERIOD) ;; rough estimate of one day
(if (is-some campaign-allocation)
(asserts! (>= campaign-amount min-campaign-allocation ) ERR-BELOW-MINIMUM-CAMPAIGN-ALLOCATION)
(asserts! (is-eq campaign-amount u0 ) ERR-INVALID-AMOUNT)
)
(map-set launchpad-map {token-launch-id: next-launchpad-id} {
token: (contract-of token),
pool-amount: pool-amount,
hardcap: hardcap,
softcap: softcap,
total-stx-deposited: u0,
no-of-participants: u0,
min-stx-deposit: min-stx-deposit,
max-stx-deposit: max-stx-deposit,
duration: duration,
start-block: start-block,
end-block: end-block,
owner: tx-sender,
is-vested: is-vested,
is-listed: false,
listing-allocation: listing-allocation,
campaign-allocation: campaign-allocation,
campaign-rewards-set: false,
burn-lp: burn-lp
})
(map-set launchpad-map-by-token-addr {token-addr: (contract-of token)} next-launchpad-id)
(try! (contract-call? token transfer total-to-send tx-sender .memegoat-launchpad-vault-v2 none))
(var-set launchpad-nonce next-launchpad-id)
)
(ok true)
)
)
;; @desc deposit-stx: sends stx to get launch token
;; @requirement user has active stake in contract or holds a min amount of goat token.
;; @params amount
;; @params token-launch-id
;; @params goat-token-trait
;; @returns (response boolean)
(define-public (deposit-stx (amount uint) (token-launch-id uint) (goat-token-trait <ft-trait-ext>))
(begin
(asserts! (not (var-get paused)) ERR-LAUNCHPAD-INACTIVE)
;; check that token passed is goat token
(asserts! (is-eq (var-get goat-token) (contract-of goat-token-trait)) ERR-INVALID-TOKEN)
(let
(
(sender tx-sender)
(has-stake (contract-call? .memegoat-staking-v1 get-user-stake-has-staked sender))
(user-goat-balance (try! (contract-call? goat-token-trait get-balance sender)))
(token-launch (try! (get-token-launch-by-id token-launch-id)))
(total-stx-deposited (get total-stx-deposited token-launch))
(participants (get no-of-participants token-launch))
(min-stx-deposit (get min-stx-deposit token-launch))
(max-stx-deposit (get max-stx-deposit token-launch))
(exists (is-some (get-user-deposits-exists sender token-launch-id)))
(user-deposit (get-user-deposits sender token-launch-id))
(end-block (get end-block token-launch))
(hardcap (get hardcap token-launch))
(token-launch-updated (merge token-launch {
total-stx-deposited: (+ total-stx-deposited amount),
no-of-participants: (if exists participants (+ participants u1))
}
))
)
;; check that user has access
(asserts! (or has-stake (>= user-goat-balance (var-get min-goat-balance))) ERR-NOT-QUALIFIED)
(asserts! (>= amount min-stx-deposit) ERR-INSUFFICIENT-AMOUNT)
;; check that hardcap has not been reached
(asserts! (<= (+ amount total-stx-deposited) hardcap) ERR-HARDCAP-EXCEEDED)
;; check that user has not exceeded max deposit
(asserts! (<= (+ user-deposit amount) max-stx-deposit) ERR-MAX-DEPOSIT-EXCEEDED)
;; transfer stx to vault
(try! (stx-transfer? amount tx-sender .memegoat-launchpad-vault-v2))
;; updated user-deposits
(map-set users-deposits {user-addr: sender, token-launch-id: token-launch-id} (+ user-deposit amount))
;; updated token-launch
(map-set launchpad-map {token-launch-id: token-launch-id} token-launch-updated)
)
(ok true)
)
)
;; @desc claim-token: allows users to claim the allocated tokens
;; @params token-launch-id
;; @params token-trait
;; @returns (response boolean)
(define-public (claim-token (token-launch-id uint) (token-trait <ft-trait>))
(begin
(asserts! (not (var-get paused)) ERR-LAUNCHPAD-INACTIVE)
(let
(
(sender tx-sender)
(token-launch (try! (get-token-launch-by-id token-launch-id)))
(total-stx-deposited (get total-stx-deposited token-launch))
(softcap (get softcap token-launch))
(hardcap (get hardcap token-launch))
(token (get token token-launch))
(is-vested (get is-vested token-launch))
(end-block (get end-block token-launch))
(exists (is-some (get-user-deposits-exists sender token-launch-id)))
(user-allocation (calculate-allocation sender token-launch-id))
(claimed (check-if-claimed sender token-launch-id))
)
(asserts! (>= total-stx-deposited softcap) ERR-MIN-TARGET-NOT-REACHED)
(asserts! (or (< end-block block-height) (is-eq total-stx-deposited hardcap)) ERR-TOKEN-LAUNCH-NOT-ENDED)
(asserts! exists ERR-NOT-PARTICIPANT)
(asserts! (not claimed) ERR-ALREADY-CLAIMED)
(asserts! (is-eq token (contract-of token-trait)) ERR-INVALID-TOKEN)
(asserts! (not is-vested) ERR-TOKEN-IS-VESTED)
;; transfer token from vault
(as-contract (try! (contract-call? .memegoat-launchpad-vault-v2 transfer-ft token-trait user-allocation sender)))
;; set user status to claimed
(map-set user-claimed { user-addr: sender, token-launch-id: token-launch-id } true)
)
(ok true)
)
)
;; @desc claim-reward: allows users to claim campaign reward
;; @params token-launch-id
;; @params token-trait
;; @returns (response boolean)
(define-public (claim-rewards (token-launch-id uint) (token-trait <ft-trait>))
(begin
(asserts! (not (var-get paused)) ERR-LAUNCHPAD-INACTIVE)
(let
(
(sender tx-sender)
(token-launch (try! (get-token-launch-by-id token-launch-id)))
(campaign-rewards-set (get campaign-rewards-set token-launch))
(token (get token token-launch))
(reward (get-user-rewards sender token-launch-id))
)
(asserts! campaign-rewards-set ERR-REWARDS-NOT-SET)
(asserts! (> reward u0) ERR-ZERO-AMOUNT)
(asserts! (is-eq token (contract-of token-trait)) ERR-INVALID-TOKEN)
;; transfer token from vault
(as-contract (try! (contract-call? .memegoat-launchpad-vault-v2 transfer-ft token-trait reward sender)))
;; remove records
(map-delete campaign-rewards {user-addr: sender, token-launch-id: token-launch-id})
)
(ok true)
)
)
;; PRIVATE CALLS
(define-private (check-is-owner)
(ok (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED))
)
(define-private (calc-4-percent (amount uint))
(let
((percent (/ (* amount u4) u100)))
(- amount percent)
)
)
(define-private (get-fee (amount uint))
(/ (* amount (var-get launchpad-fee)) u100)
)
(define-private (add-reward (recipient {addr: principal, points: uint}) (token-launch-id uint))
(let
(
(addr (get addr recipient))
(points (get points recipient))
)
(map-set campaign-rewards {user-addr: addr, token-launch-id: token-launch-id} points)
token-launch-id
)
)