Source Code

;; Crash Punk NFT(s) -> $SNOW Staking Contract
;; This contract is in charge of handling all staking within the Crash Punks ecosystem.
;; Written by StrataLabs


;; $SNOW FT Unique Properties
;; 1. Minting is only allowed by the staking contract

(use-trait nft-trait .nft-trait.nft-trait)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Cons, Vars, & Maps ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;
;; Vars/Cons ;;
;;;;;;;;;;;;;;;

;; @desc - Uint that represents the *max* possible stake reward per block
(define-constant max-payout-per-block u69444)

;; (temporary) Helper Variable to stake and unstake custodial
(define-data-var function-caller-helper-to-unstake principal tx-sender)

;; @desc - List of principals that represents all whitelisted, actively-staking collections
(define-data-var whitelist-collections (list 100 principal) 
  (list 
    'SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.crashpunks-v2
    'SPAGKDWK07GB9T2X5PZ12N004PDW94MJGRR2JSHS.crash-punks-animated-series
    'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crash-punks-animated-punks
    'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crash-punks-animated-series-ep-1
    'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.crash-punks-boxes
    'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.crash-punks-vip-pass
    'SP3K22XKPT9WJFCE957J94J6XXVZHP7747YNPDTFD.crash-punks-termination-shock
    'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crashpunks-punkettes
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.brandx
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.the-smiley-collection
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.xlove
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.cx-tokens
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.narcotix
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.hback-whales-nft
    'SP6P4EJF0VG8V0RB3TQQKJBHDQKEF6NVRD1KZE3C.monster-satoshibles
    'SP6P4EJF0VG8V0RB3TQQKJBHDQKEF6NVRD1KZE3C.satoshibles
    'SP3RBMGTRD92F0S8DTDJ4FVP3D76SM4A27EV93106.giraffe-mafia
    'SP3M05ETW09E98NNFMFHT1WND3ZRX9DV31TFC6DFW.hooch-haus---booze-brains
    'SP3M05ETW09E98NNFMFHT1WND3ZRX9DV31TFC6DFW.hooch-haus-creature-feature
    'SP3252T1HMQHZTA9S22WZ2HZMKC4CVH965SHSERTH.the-guests-woymuls
    'SP3252T1HMQHZTA9S22WZ2HZMKC4CVH965SHSERTH.misfit-chimp-society
    'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.the-guests
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.stacks-parrots-3d
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.byzantion-stacks-parrots
    'SPNWZ5V2TPWGQGVDR6T7B6RQ4XMGZ4PXTEE0VQ0S.stacculents
    'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.steady-lads-msa
    'SP176ZMV706NZGDDX8VSQRGMB7QN33BBDVZ6BMNHD.project-indigo-act1
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.wasteland-apes-nft
    'SP2W12MNM4SPV37VZHN4GCDG35G9ACG3FDKK7WF04.MetaBoy
    'SPVVASJ83H223TCEP8Z8SHZDFDBFXSM4EGSWCVR2.sol-townsfolk-nft
    'SP27F9EJH20K3GT6GHZG0RD08REZKY2TDMD6D9M2Z.btc-badgers-v2
    'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.the-explorer-guild
    'SP2ESPYE74G94D2HD9X470426W1R6C2P22B4Z1Q5.skullcoin-souls-nft
    'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.megapont-ape-club-nft
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.bitcoin-monkeys
  )
)
(define-data-var custodial-whitelist-collections (list 100 principal)
  (list 
    'SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.crashpunks-v2
    'SPAGKDWK07GB9T2X5PZ12N004PDW94MJGRR2JSHS.crash-punks-animated-series
    'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crash-punks-animated-punks
    'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crash-punks-animated-series-ep-1
    'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.crash-punks-boxes
    'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.crash-punks-vip-pass
    'SP3K22XKPT9WJFCE957J94J6XXVZHP7747YNPDTFD.crash-punks-termination-shock
    'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crashpunks-punkettes
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.brandx
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.the-smiley-collection
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.xlove
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.cx-tokens
    'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.narcotix
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.hback-whales-nft
    'SP6P4EJF0VG8V0RB3TQQKJBHDQKEF6NVRD1KZE3C.monster-satoshibles
    'SP6P4EJF0VG8V0RB3TQQKJBHDQKEF6NVRD1KZE3C.satoshibles
    'SP3RBMGTRD92F0S8DTDJ4FVP3D76SM4A27EV93106.giraffe-mafia
    'SP3M05ETW09E98NNFMFHT1WND3ZRX9DV31TFC6DFW.hooch-haus---booze-brains
    'SP3M05ETW09E98NNFMFHT1WND3ZRX9DV31TFC6DFW.hooch-haus-creature-feature
    'SP3252T1HMQHZTA9S22WZ2HZMKC4CVH965SHSERTH.the-guests-woymuls
    'SP3252T1HMQHZTA9S22WZ2HZMKC4CVH965SHSERTH.misfit-chimp-society
    'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.the-guests
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.stacks-parrots-3d
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.byzantion-stacks-parrots
    'SPNWZ5V2TPWGQGVDR6T7B6RQ4XMGZ4PXTEE0VQ0S.stacculents
    'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.steady-lads-msa
    'SP176ZMV706NZGDDX8VSQRGMB7QN33BBDVZ6BMNHD.project-indigo-act1
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.wasteland-apes-nft
    'SP2W12MNM4SPV37VZHN4GCDG35G9ACG3FDKK7WF04.MetaBoy
    'SPVVASJ83H223TCEP8Z8SHZDFDBFXSM4EGSWCVR2.sol-townsfolk-nft
    'SP27F9EJH20K3GT6GHZG0RD08REZKY2TDMD6D9M2Z.btc-badgers-v2
    'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.the-explorer-guild
    'SP2ESPYE74G94D2HD9X470426W1R6C2P22B4Z1Q5.skullcoin-souls-nft
    'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.megapont-ape-club-nft
    'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.bitcoin-monkeys
  )
)
(define-data-var non-custodial-whitelist-collections (list 100 principal) (list))

;; @desc - (temporary) Uint that's used to aggregate when calling "get-unclaimed-balance"
(define-data-var helper-total-unclaimed-balance uint u0)

;; @desc - (temporary) Principal that's used to temporarily hold a collection principal
(define-data-var helper-collection-principal principal tx-sender)

;; @desc - (temporary) List of uints that's used to temporarily hold the output of a map resulting in a list of height differences (aka blocks staked)
(define-data-var helper-height-difference-list (list 10000 uint) (list))

;; @desc - (temporary) Uint that needs to be removed when unstaking
(define-data-var id-being-removed uint u0)

;; @desc - (temporary) Uint and Collection that needs to be removed when unstaking
(define-data-var id-and-collection-being-removed {collection: principal, nft: uint} {collection: tx-sender, nft: u0})

;; @desc - variable to set staking true or false
(define-data-var active-staking bool true)

;; @desc - Map that keeps track of whitelisted principal (key) & corresponding multiplier (value)
(define-map collection-multiplier principal uint)
;; CrashPunks Collections
(map-set collection-multiplier 'SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.crashpunks-v2 u10)
(map-set collection-multiplier 'SPAGKDWK07GB9T2X5PZ12N004PDW94MJGRR2JSHS.crash-punks-animated-series u8)
(map-set collection-multiplier 'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crash-punks-animated-punks u6)
(map-set collection-multiplier 'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crash-punks-animated-series-ep-1 u5)
(map-set collection-multiplier 'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.crash-punks-boxes u5)
(map-set collection-multiplier 'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.crash-punks-vip-pass u3)
(map-set collection-multiplier 'SP3K22XKPT9WJFCE957J94J6XXVZHP7747YNPDTFD.crash-punks-termination-shock u2)
(map-set collection-multiplier 'SPQE8N8BHMT462W2XPK028GDM4RMQBSHAAY8D37G.crashpunks-punkettes u2)
;; Collaborations
(map-set collection-multiplier 'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.brandx u1)
(map-set collection-multiplier 'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.the-smiley-collection u1)
(map-set collection-multiplier 'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.xlove u1)
(map-set collection-multiplier 'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.cx-tokens u1)
(map-set collection-multiplier 'SP8HMQP4Q63V3E6SXXPXZ4WJXA263HBD95QY2AM3.narcotix u1)
(map-set collection-multiplier 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.hback-whales-nft u1)
(map-set collection-multiplier 'SP6P4EJF0VG8V0RB3TQQKJBHDQKEF6NVRD1KZE3C.monster-satoshibles u1)
(map-set collection-multiplier 'SP6P4EJF0VG8V0RB3TQQKJBHDQKEF6NVRD1KZE3C.satoshibles u1)
(map-set collection-multiplier 'SP3RBMGTRD92F0S8DTDJ4FVP3D76SM4A27EV93106.giraffe-mafia u1)
(map-set collection-multiplier 'SP3M05ETW09E98NNFMFHT1WND3ZRX9DV31TFC6DFW.hooch-haus---booze-brains u1)
(map-set collection-multiplier 'SP3M05ETW09E98NNFMFHT1WND3ZRX9DV31TFC6DFW.hooch-haus-creature-feature u1)
(map-set collection-multiplier 'SP3252T1HMQHZTA9S22WZ2HZMKC4CVH965SHSERTH.the-guests-woymuls u1)
(map-set collection-multiplier 'SP3252T1HMQHZTA9S22WZ2HZMKC4CVH965SHSERTH.misfit-chimp-society u1)
(map-set collection-multiplier 'SP1CSHTKVHMMQJ7PRQRFYW6SB4QAW6SR3XY2F81PA.the-guests u1)
(map-set collection-multiplier 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.stacks-parrots-3d u1)
(map-set collection-multiplier 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.byzantion-stacks-parrots u1)
(map-set collection-multiplier 'SPNWZ5V2TPWGQGVDR6T7B6RQ4XMGZ4PXTEE0VQ0S.stacculents u1)
(map-set collection-multiplier 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.steady-lads-msa u1)
(map-set collection-multiplier 'SP176ZMV706NZGDDX8VSQRGMB7QN33BBDVZ6BMNHD.project-indigo-act1 u1)
(map-set collection-multiplier 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.wasteland-apes-nft u1)
(map-set collection-multiplier 'SP2W12MNM4SPV37VZHN4GCDG35G9ACG3FDKK7WF04.MetaBoy u1)
(map-set collection-multiplier 'SPVVASJ83H223TCEP8Z8SHZDFDBFXSM4EGSWCVR2.sol-townsfolk-nft u1)
(map-set collection-multiplier 'SP27F9EJH20K3GT6GHZG0RD08REZKY2TDMD6D9M2Z.btc-badgers-v2 u1)
(map-set collection-multiplier 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.the-explorer-guild u1)
(map-set collection-multiplier 'SP2ESPYE74G94D2HD9X470426W1R6C2P22B4Z1Q5.skullcoin-souls-nft u1)
(map-set collection-multiplier 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.megapont-ape-club-nft u1)
(map-set collection-multiplier 'SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.bitcoin-monkeys u1)

;; @desc - List of principals that are whitelisted/have admin privileges
(define-data-var whitelist-admins (list 100 principal) (list tx-sender))

;; @desc - Map that tracks a staked item details (value) by collection & ID (key)
(define-map staked-item {collection: principal, id: uint}
  {
    staker: principal,
    status: bool,
    last-staked-or-claimed: uint
  }
)

;; @desc - Map that tracks all staked IDs (value) by collection principal (key)
(define-map all-stakes-in-collection principal (list 10000 uint))

;; @desc - Map that tracks all staked IDs in a collection (value) by user & collection & ID (key)
(define-map user-stakes-by-collection {user: principal, collection: principal}
  (list 100 uint)
)

;; @desc - Map that tracks all staked IDs with its collection (value) by user (key)
(define-map all-user-stakes principal (list 100 { collection: principal, nft: (list 100 uint)}))



;;;;;;;;;;;;;;;;
;; Error Cons ;;
;;;;;;;;;;;;;;;;

(define-constant ERR-NOT-AUTH (err u101))
(define-constant ERR-NOT-STAKED (err u102))
(define-constant ERR-STAKED-OR-NONE (err u103))
(define-constant ERR-NOT-WHITELISTED (err u104))
(define-constant ERR-UNWRAP (err u105))
(define-constant ERR-NOT-OWNER (err u106))
(define-constant ERR-MIN-STAKE-HEIGHT (err u107))
(define-constant ERR-ALREADY-WHITELISTED (err u108))
(define-constant ERR-MULTIPLIER (err u109))
(define-constant ERR-STAKING-NOT-ACTIVE (err u110))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;; Read Functions ;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-read-only (get-all-user-stakes (user principal))
  (map-get? all-user-stakes user)
)

(define-read-only (is-staking-active)
  (var-get active-staking)
)

(define-read-only (active-admins)
  (var-get whitelist-admins)
)

(define-read-only (active-collections)
  (var-get whitelist-collections)
)

(define-read-only (custodial-active-collections)
  (var-get custodial-whitelist-collections)
)

(define-read-only (non-custodial-active-collections)
  (var-get non-custodial-whitelist-collections)
)

;; @desc - Read only to get how many snow per day a collection is generating
(define-read-only (get-generation-rate-of-a-collection (collection principal)) 
  (map-get? collection-multiplier collection)
)

;; @desc - Read function that returns the current generation rate for tx-sender across all actively staked collective assets
(define-read-only (get-total-generation-rate-through-all-collections)
  (let
    (
      (list-of-collections-with-active-user-stakes (filter filter-out-collections-with-no-stakes (var-get whitelist-collections)))
      (list-of-generation-per-collection (map map-from-list-staked-to-generation-per-collection list-of-collections-with-active-user-stakes))
    )
    (print list-of-collections-with-active-user-stakes)
    (ok (fold + list-of-generation-per-collection u0))
  )
)

;; @desc - Filter function used which takes in all (list principal) stakeable/whitelist principals & outputs a (list principal) of actively-staked (by tx-sender) principals
(define-private (filter-out-collections-with-no-stakes (collection principal))
  (let
    (
      (collection-staked-by-user-list (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: collection})))
      (collection-staked-by-user-count (len collection-staked-by-user-list))
    )
    (if (> collection-staked-by-user-count u0)
      true
      false
    )
  )
)

;; @desc - Map function which takes in a list of actively-staked principals & returns a list of current generation rate per collection
(define-private (map-from-list-staked-to-generation-per-collection (collection principal))
  (let
    (
      (this-collection-multiplier (default-to u0 (map-get? collection-multiplier collection)))
      (collection-staked-by-user-list (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: collection})))
      (collection-staked-by-user-count (len collection-staked-by-user-list))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
    )
    (* this-collection-multiplier-normalized collection-staked-by-user-count)
  )
)

;; @desc - function that gets the unclaimed balance by item and collection
(define-read-only (get-unclaimed-balance-by-collection-and-item (collection <nft-trait>) (item uint))
  (let
    (
      (this-collection-multiplier (default-to u0 (map-get? collection-multiplier (contract-of collection))))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
      (item-info (get-item-stake-details (contract-of collection) item))
      (get-time-from-staking-or-claiming (unwrap! (get last-staked-or-claimed item-info) ERR-UNWRAP))
      (time-passed (- block-height get-time-from-staking-or-claiming))
    ) 
    ;; check collection is existing whitelist collection
    (asserts! (> this-collection-multiplier u0) ERR-NOT-WHITELISTED)
    ;; check if item is staked
    (asserts! (is-eq true (unwrap! (get status item-info) ERR-UNWRAP)) ERR-NOT-STAKED)
    (ok (* this-collection-multiplier-normalized time-passed))
  )
)

;; @desc - function that gets the unclaimed balance by a list of items and a specific collection
(define-read-only (get-unclaimed-balance-by-collection-and-items (collection <nft-trait>) (items (list 10 (optional uint))))
  (let
    (
      (this-collection-multiplier (default-to u0 (map-get? collection-multiplier (contract-of collection))))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
      (item-info-1 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u0) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-1 (unwrap! (get last-staked-or-claimed item-info-1) ERR-UNWRAP))
      (time-passed-1 (- block-height get-time-from-staking-or-claiming-1))
      (item-info-2 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u1) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-2 (unwrap! (get last-staked-or-claimed item-info-2) ERR-UNWRAP))
      (time-passed-2 (- block-height get-time-from-staking-or-claiming-2))
      (item-info-3 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u2) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-3 (unwrap! (get last-staked-or-claimed item-info-3) ERR-UNWRAP))
      (time-passed-3 (- block-height get-time-from-staking-or-claiming-3))
      (item-info-4 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u3) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-4 (unwrap! (get last-staked-or-claimed item-info-4) ERR-UNWRAP))
      (time-passed-4 (- block-height get-time-from-staking-or-claiming-4))
      (item-info-5 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u4) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-5 (unwrap! (get last-staked-or-claimed item-info-5) ERR-UNWRAP))
      (time-passed-5 (- block-height get-time-from-staking-or-claiming-5))
      (item-info-6 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u5) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-6 (unwrap! (get last-staked-or-claimed item-info-6) ERR-UNWRAP))
      (time-passed-6 (- block-height get-time-from-staking-or-claiming-6))
      (item-info-7 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u6) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-7 (unwrap! (get last-staked-or-claimed item-info-7) ERR-UNWRAP))
      (time-passed-7 (- block-height get-time-from-staking-or-claiming-7))
      (item-info-8 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u7) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-8 (unwrap! (get last-staked-or-claimed item-info-8) ERR-UNWRAP))
      (time-passed-8 (- block-height get-time-from-staking-or-claiming-8))
      (item-info-9 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u8) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-9 (unwrap! (get last-staked-or-claimed item-info-9) ERR-UNWRAP))
      (time-passed-9 (- block-height get-time-from-staking-or-claiming-9))
      (item-info-10 (get-item-stake-details (contract-of collection) (unwrap! (unwrap! (element-at items u9) ERR-UNWRAP) ERR-UNWRAP)))
      (get-time-from-staking-or-claiming-10 (unwrap! (get last-staked-or-claimed item-info-10) ERR-UNWRAP))
      (time-passed-10 (- block-height get-time-from-staking-or-claiming-10))
    )
    ;; check collection is existing whitelist collection
    (asserts! (> this-collection-multiplier u0) ERR-NOT-WHITELISTED)
    ;; check if item is staked
    (asserts! (is-eq true (unwrap! (get status item-info-1) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-2) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-3) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-4) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-5) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-6) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-7) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-8) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-9) ERR-UNWRAP)) ERR-NOT-STAKED)
    (asserts! (is-eq true (unwrap! (get status item-info-10) ERR-UNWRAP)) ERR-NOT-STAKED)
    ;; Return a list of balance per each nft on the list
    (ok
      (list
        (* this-collection-multiplier-normalized time-passed-1)
        (* this-collection-multiplier-normalized time-passed-2)
        (* this-collection-multiplier-normalized time-passed-3)
        (* this-collection-multiplier-normalized time-passed-4)
        (* this-collection-multiplier-normalized time-passed-5)
        (* this-collection-multiplier-normalized time-passed-6)
        (* this-collection-multiplier-normalized time-passed-7)
        (* this-collection-multiplier-normalized time-passed-8)
        (* this-collection-multiplier-normalized time-passed-9)
        (* this-collection-multiplier-normalized time-passed-10)
      )
    )
  )
)

;; @desc - Read function that returns a (list uint) of all actively-staked IDs in a collection by user
(define-read-only (get-items-staked-by-collection-and-user (collection principal) (user principal))
  (ok (default-to (list) (map-get? user-stakes-by-collection {user: user, collection: collection})))
)


;; @desc - Read function that returns stake details (staker, status, last-staked-or-claimed) in a specific collection & id
(define-read-only (get-item-stake-details (collection principal) (item-id uint))
      (map-get? staked-item {collection: collection, id: item-id})
)

;; @desc - Read function that returns the tx-sender's total unclaimed balance across all whitelisted collections
(define-public (get-unclaimed-balance)
  (let
    (
      ;; Filter from (list principal) of all whitelist principals/NFTs to (list principal) of all whitelist principals/NFTs where user has > 0 stakes
      (this-collection-stakes-by-user (filter filter-out-collections-with-no-stakes (var-get whitelist-collections)))
      (list-of-height-differences (list))
    )
    ;; 1. Filter from whitelisted to active staked
    ;; 2. Map from a list of principals to a list of uints
    ;; clear temporary unclaimed balance uint
    (var-set helper-total-unclaimed-balance u0)
    ;; map through this-collection-stakes-by-user, don't care about output list, care about appending to list-of-height-differences
    (map map-to-append-to-list-of-height-differences this-collection-stakes-by-user)
    ;; return unclaimed balance from tx-sender
    (ok (var-get helper-total-unclaimed-balance))
  )
)

;; @desc - looping through all the collections that a user *does* have active stakes, goal of this function is to append the unclaimed balance from each collection to a new list (helper-height-difference)
(define-private (map-to-append-to-list-of-height-differences (collection principal))
  (let
    (
      (this-collection-multiplier (default-to u0 (map-get? collection-multiplier collection)))
      (this-collection-stakes-by-user (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: collection})))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
    )
    ;; set helper list to empty
    (var-set helper-height-difference-list (list))
    ;; Set collection helper var for folding through height differences
    (var-set helper-collection-principal collection)
    ;; Use map as a loop to append helper list with get-unclaimed-balance-by-collection
    (map append-helper-list-from-id-staked-to-height-difference this-collection-stakes-by-user)
    ;; Total unclaimed balance in collection
    (var-set helper-total-unclaimed-balance
      (+
        (var-get helper-total-unclaimed-balance)
        (* this-collection-multiplier-normalized (fold + (var-get helper-height-difference-list) u0))
      )
    )
    tx-sender
  )
)

;; @desc - function to append the height-difference
(define-private (append-helper-list-from-id-staked-to-height-difference (staked-id uint))
  (let
    (
      (staked-or-claimed-height (get last-staked-or-claimed (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (var-get helper-collection-principal), id: staked-id}))))
      (height-difference (- block-height staked-or-claimed-height))
    )
    (var-set helper-height-difference-list
      (unwrap! (as-max-len? (append (var-get helper-height-difference-list) height-difference) u1000) u0)
    )
    u1
  )
)

;; @desc - Read function that outputs a tx-sender total unclaimed balance from a specific collection
(define-public (get-unclaimed-balance-by-collection (collection <nft-trait>))
  (let
    (
      (this-collection-multiplier (unwrap! (map-get? collection-multiplier (contract-of collection)) ERR-UNWRAP))
      (this-collection-stakes-by-user (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)})))
      (list-of-staked-height-differences (map map-from-id-staked-to-height-difference this-collection-stakes-by-user))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
    )
      ;; Assert at least one stake exists
      (asserts! (and (> (len this-collection-stakes-by-user) u0) (> (len list-of-staked-height-differences) u0)) ERR-NOT-STAKED)
      ;; Var-set helper-collection-principal for use in map-from-id-staked-to-height-difference
      (var-set helper-collection-principal (contract-of collection))
      ;; Unclaimed $SNOW balance by user in this collection
      ;; Fold to aggregate total blocks staked across all IDs, then multiply collection multiplier
      (ok (* this-collection-multiplier-normalized (fold + list-of-staked-height-differences u0)))
  )
)

;; @desc - Helper function used to map from a list of uint of staked ids to a list of uint of height-differences
(define-private (map-from-id-staked-to-height-difference (staked-id uint))
  (let
    (
      (staked-or-claimed-height (get last-staked-or-claimed (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (var-get helper-collection-principal), id: staked-id}))))
    )
    (print (- block-height staked-or-claimed-height))
    (- block-height staked-or-claimed-height)
  )
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;; Stake Functions ;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-data-var combo-to-remove {collection: principal, nft: (list 100 uint)} {collection: tx-sender, nft: (list)})

(define-public (stake (collection <nft-trait>) (id uint))
  (let
    (
      (current-all-staked-in-collection-list (default-to (list) (map-get? all-stakes-in-collection (contract-of collection))))
      (is-unstaked-in-all-staked-ids-list (index-of current-all-staked-in-collection-list id))
      (is-unstaked-in-staked-by-user-list (index-of (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)})) id))
      (is-unstaked-in-stake-details-map (get status (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (var-get helper-collection-principal), id: id}))))
      (current-nft-owner (unwrap! (contract-call? collection get-owner id) ERR-UNWRAP))
      (custodial-list (var-get custodial-whitelist-collections))
      (transaction-sender tx-sender)
      (current-stakes-in-collection (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)})))
      (current-user-stakes (default-to (list) (map-get? all-user-stakes tx-sender)))
      (current-stakes-collection-and-nft (var-set combo-to-remove {collection: (contract-of collection), nft: current-stakes-in-collection}))
      (reset-map (remove-a-stake (contract-of collection) tx-sender))

    )
    ;; Assert collection is whitelisted
    (asserts! (is-some (index-of (var-get whitelist-collections) (contract-of collection))) ERR-NOT-WHITELISTED)
    ;; Asserts item is unstaked across all necessary storage
    (asserts! (and (is-none is-unstaked-in-all-staked-ids-list) (is-none is-unstaked-in-staked-by-user-list) (not is-unstaked-in-stake-details-map)) ERR-STAKED-OR-NONE)
    ;; Assert caller is current owner of NFT
    (asserts! (is-eq (some tx-sender) current-nft-owner) ERR-NOT-OWNER)
    ;; Asserts that Staking is active
    (asserts! (var-get active-staking) ERR-STAKING-NOT-ACTIVE)
    ;; manual staking for custodial
    (if
      (is-some (index-of custodial-list (contract-of collection))) 
        (unwrap! (contract-call? collection transfer id tx-sender .testing-stake-contract) (err u401))
    false
    )
    ;; Map set all staked ids list
    (map-set all-stakes-in-collection (contract-of collection)
      (unwrap! (as-max-len? (append (default-to (list) (map-get? all-stakes-in-collection (contract-of collection))) id) u10000) ERR-UNWRAP)
    )    

    (map-set all-user-stakes tx-sender (unwrap! (as-max-len? (append (default-to (list) (map-get? all-user-stakes tx-sender)) {collection: (contract-of collection), nft: (unwrap! (as-max-len? (append current-stakes-in-collection id) u100) ERR-UNWRAP)}) u100) ERR-UNWRAP))

    ;; Map set user staked in collection list
    (map-set user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)}
        (unwrap! (as-max-len? (append (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)})) id) u100) ERR-UNWRAP)
    )
    (ok 
      ;; Map set staked-item details
      (map-set staked-item {collection: (contract-of collection), id: id}
        {
          staker: tx-sender,
          status: true,
          last-staked-or-claimed: block-height
        }
      )
    )
  )
)

(define-private (remove-a-stake (collection principal) (user principal)) 
  (let 
    (
      (current-stakes-in-collection (default-to (list) (map-get? user-stakes-by-collection {user: user, collection: collection})))
      (current-user-stakes (default-to (list) (map-get? all-user-stakes user)))
      (current-stakes-collection-and-nft {collection: collection, nft: current-stakes-in-collection})
    )
    (var-set combo-to-remove current-stakes-collection-and-nft)
    (ok
      (map-set all-user-stakes tx-sender (filter is-not-removeable-collection-and-id-list current-user-stakes))
    )
  )
)

(define-private (remove-a-specific-stake (collection principal) (user principal) (nft uint)) 
  (let 
    (
      (current-user-staked-by-collection-list (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: collection})))
      (current-user-stakes (default-to (list) (map-get? all-user-stakes user)))
      (set-var (var-set id-being-removed nft))
      (substract-nft-in-stakes (filter is-not-id current-user-staked-by-collection-list))
      (current-stakes-collection-and-nft {collection: collection, nft: current-user-staked-by-collection-list})
    )
    (var-set combo-to-remove current-stakes-collection-and-nft)
    (ok
      (begin
        (map-set all-user-stakes user (filter is-not-removeable-collection-and-id-list current-user-stakes))
        (map-set all-user-stakes user (unwrap! (as-max-len? (append (default-to (list) (map-get? all-user-stakes user)) {collection: collection, nft: substract-nft-in-stakes}) u100) ERR-UNWRAP))
      )
    )
  )
)

(define-public (stake-many (collection <nft-trait>) (nfts (list 10 uint)))
  (let 
    (
      (nft-1 (element-at nfts u0))
      (nft-2 (element-at nfts u1))
      (nft-3 (element-at nfts u2))
      (nft-4 (element-at nfts u3))
      (nft-5 (element-at nfts u4))
      (nft-6 (element-at nfts u5))
      (nft-7 (element-at nfts u6))
      (nft-8 (element-at nfts u7))
      (nft-9 (element-at nfts u8))
      (nft-10 (element-at nfts u9))
      (staking-list (list nft-1 nft-2 nft-3 nft-4 nft-5 nft-6 nft-7 nft-8 nft-9 nft-10))
      (stake-1 (if (is-some nft-1) (some (stake collection (unwrap! nft-1 ERR-UNWRAP))) none))
      (stake-2 (if (is-some nft-2) (some (stake collection (unwrap! nft-2 ERR-UNWRAP))) none))
      (stake-3 (if (is-some nft-3) (some (stake collection (unwrap! nft-3 ERR-UNWRAP))) none))
      (stake-4 (if (is-some nft-4) (some (stake collection (unwrap! nft-4 ERR-UNWRAP))) none))
      (stake-5 (if (is-some nft-5) (some (stake collection (unwrap! nft-5 ERR-UNWRAP))) none))
      (stake-6 (if (is-some nft-6) (some (stake collection (unwrap! nft-6 ERR-UNWRAP))) none))
      (stake-7 (if (is-some nft-7) (some (stake collection (unwrap! nft-7 ERR-UNWRAP))) none))
      (stake-8 (if (is-some nft-8) (some (stake collection (unwrap! nft-8 ERR-UNWRAP))) none))
      (stake-9 (if (is-some nft-9) (some (stake collection (unwrap! nft-9 ERR-UNWRAP))) none))
      (stake-10 (if (is-some nft-10) (some (stake collection (unwrap! nft-10 ERR-UNWRAP))) none))
      (final-list (list stake-1 stake-2 stake-3 stake-4 stake-5 stake-6 stake-7 stake-8 stake-9 stake-10))
    )
    (ok final-list)
  )
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;; Claim Functions ;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; @desc - Function that a user calls to claim any generated stake rewards for a specific collection & specific id
(define-public (claim-item-stake (collection-collective <nft-trait>) (staked-id uint))
  (let
    (
      (this-collection-multiplier (default-to u0 (map-get? collection-multiplier (contract-of collection-collective))))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
      (current-staker (get staker (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection-collective), id: staked-id}))))
      (stake-status (get status (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection-collective), id: staked-id}))))
      (last-claimed-or-staked-height (get last-staked-or-claimed (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection-collective), id: staked-id}))))
      (current-nft-owner (unwrap! (contract-call? collection-collective get-owner staked-id) ERR-NOT-AUTH))
      (blocks-staked (- block-height last-claimed-or-staked-height))
    )
    ;; assert collection-collective is active/whitelisted
    (asserts! (is-some (index-of (var-get whitelist-collections) (contract-of collection-collective))) ERR-NOT-WHITELISTED)                                     
    ;; asserts is staked
    (asserts! stake-status ERR-NOT-STAKED)
    ;; asserts tx-sender is owner && asserts tx-sender is staker
    (asserts! (is-eq tx-sender current-staker) ERR-NOT-OWNER)
    ;; asserts height-difference > 0
    (asserts! (> blocks-staked u0) ERR-MIN-STAKE-HEIGHT)
    ;; contract call to mint for X amount
    (unwrap! (contract-call? .testing-stake-contract-snow mint (* this-collection-multiplier-normalized blocks-staked) tx-sender) ERR-UNWRAP)
    ;; update last-staked-or-claimed height
    (ok (map-set staked-item {collection: (contract-of collection-collective), id: staked-id}
      {
        status: true,
        last-staked-or-claimed: block-height,
        staker: tx-sender
      }
    ))
  )
)

;; @desc -Function that a user calls to stake any current or future SGC asset for $SNOW
;; @param - Collection (principal or collection?), ID (uint) -> bool?
(define-public (claim-all-stake)
  (let
    (
      (list-of-collections-with-active-user-stakes (filter filter-out-collections-with-no-stakes (var-get whitelist-collections)))
      (unclaimed-balance-total (unwrap! (get-unclaimed-balance) ERR-UNWRAP))
    )
    ;; contract call to mint for X amount
    (unwrap! (contract-call? .testing-stake-contract-snow mint unclaimed-balance-total tx-sender) ERR-UNWRAP)
    ;; loop through collections, then through IDs, reset last-staked-or-claimed value for each staked ID in each collection by user
    (map map-to-loop-through-active-collection list-of-collections-with-active-user-stakes)

    (ok unclaimed-balance-total)
  )
)

(define-private (map-to-loop-through-active-collection (collection principal))
  (let
    (
      (collection-staked-by-user-list (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: collection})))
    )
    (var-set helper-collection-principal collection)
    (map map-to-set-reset-last-claimed-or-staked-height collection-staked-by-user-list)
    tx-sender
  )
)

(define-private (map-to-set-reset-last-claimed-or-staked-height (staked-id uint))
  (begin
    (map-set staked-item {collection: (var-get helper-collection-principal), id: staked-id}
      (merge
        (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (var-get helper-collection-principal), id: staked-id}))
        {last-staked-or-claimed: block-height}
      )
    )
    u0
  )
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;; Unstake Functions ;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-public (unstake-item (collection <nft-trait>) (staked-id uint))
  (let
    (
      (this-collection-multiplier (default-to u0 (map-get? collection-multiplier (contract-of collection))))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
      (current-staker (get staker (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection), id: staked-id}))))
      (stake-status (get status (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection), id: staked-id}))))
      (last-claimed-or-staked-height (get last-staked-or-claimed (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection), id: staked-id}))))
      (current-nft-owner (unwrap! (contract-call? collection get-owner staked-id) ERR-NOT-AUTH))
      (blocks-staked (- block-height last-claimed-or-staked-height))
      (current-all-staked-in-collection-list (default-to (list) (map-get? all-stakes-in-collection (contract-of collection))))
      (current-user-staked-by-collection-list (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)})))
      (custodial-list (var-get custodial-whitelist-collections))
      (set-var (var-set id-being-removed staked-id))
      (remove-stake (remove-a-specific-stake (contract-of collection) tx-sender staked-id))
    )
    ;; asserts is staked
    (asserts! stake-status ERR-NOT-STAKED)
    ;; asserts tx-sender is owner staker
    (asserts! (is-eq tx-sender current-staker) ERR-NOT-OWNER)
    ;; temporary var set to help remove param
    (var-set id-and-collection-being-removed {collection: (contract-of collection), nft: staked-id})
    ;; check if blocks-staked > 0 to see if there's any unclaimed $SNOW to claim
    (if (> blocks-staked u0)
      ;; if there is, need to claim snow balance
      (unwrap! (contract-call? .testing-stake-contract-snow mint (* this-collection-multiplier-normalized blocks-staked) tx-sender) ERR-UNWRAP)
      ;; if not, proceed
      true
    )
    ;; set function caller to tx-sender to send from contract
    (var-set function-caller-helper-to-unstake tx-sender)
    ;;manual unstake of custodial
    (if
        (is-some (index-of custodial-list (contract-of collection)))
        
        (as-contract (unwrap! (contract-call? collection transfer staked-id .testing-stake-contract (var-get function-caller-helper-to-unstake)) (err u401)))

        true
    )
    ;; filter/remove staked-id from all-stakes-in-collection
    (map-set all-stakes-in-collection (contract-of collection) (filter is-not-id current-all-staked-in-collection-list))

     (map-set staked-item {collection: (contract-of collection), id: staked-id}
      {
        status: false,
        last-staked-or-claimed: block-height,
        staker: tx-sender
      }
    )

    ;; update last-staked-or-claimed height
    (ok
    (map-set user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)} (filter is-not-id current-user-staked-by-collection-list))
    )
  )
)

(define-private (is-not-id (list-id uint))
  (not (is-eq list-id (var-get id-being-removed)))
)

;; @desc - Helper function for removing a specific combo of collection and nft from a user
(define-private (is-not-removeable-collection-and-id (combo {collection: principal, nft: uint}))
  (not (is-eq combo (var-get id-and-collection-being-removed)))
)

(define-private (is-not-removeable-collection-and-id-list (combo {collection: principal, nft: (list 100 uint)}))
  (not (is-eq combo (var-get combo-to-remove)))
)

(define-public (unstake-many (collection <nft-trait>) (nfts (list 10 uint)))
  (let 
    (
      (nft-1 (element-at nfts u0))
      (nft-2 (element-at nfts u1))
      (nft-3 (element-at nfts u2))
      (nft-4 (element-at nfts u3))
      (nft-5 (element-at nfts u4))
      (nft-6 (element-at nfts u5))
      (nft-7 (element-at nfts u6))
      (nft-8 (element-at nfts u7))
      (nft-9 (element-at nfts u8))
      (nft-10 (element-at nfts u9))
      (staking-list (list nft-1 nft-2 nft-3 nft-4 nft-5 nft-6 nft-7 nft-8 nft-9 nft-10))
      (stake-1 (if (is-some nft-1) (some (unstake-item collection (unwrap! nft-1 ERR-UNWRAP))) none))
      (stake-2 (if (is-some nft-2) (some (unstake-item collection (unwrap! nft-2 ERR-UNWRAP))) none))
      (stake-3 (if (is-some nft-3) (some (unstake-item collection (unwrap! nft-3 ERR-UNWRAP))) none))
      (stake-4 (if (is-some nft-4) (some (unstake-item collection (unwrap! nft-4 ERR-UNWRAP))) none))
      (stake-5 (if (is-some nft-5) (some (unstake-item collection (unwrap! nft-5 ERR-UNWRAP))) none))
      (stake-6 (if (is-some nft-6) (some (unstake-item collection (unwrap! nft-6 ERR-UNWRAP))) none))
      (stake-7 (if (is-some nft-7) (some (unstake-item collection (unwrap! nft-7 ERR-UNWRAP))) none))
      (stake-8 (if (is-some nft-8) (some (unstake-item collection (unwrap! nft-8 ERR-UNWRAP))) none))
      (stake-9 (if (is-some nft-9) (some (unstake-item collection (unwrap! nft-9 ERR-UNWRAP))) none))
      (stake-10 (if (is-some nft-10) (some (unstake-item collection (unwrap! nft-10 ERR-UNWRAP))) none))
      (final-list (list stake-1 stake-2 stake-3 stake-4 stake-5 stake-6 stake-7 stake-8 stake-9 stake-10))
    )
    (ok final-list)
  )
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;; Admin Functions ;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; @desc - Function that only an admin user can call to add a new SGC collection for staking
;; @param - Collection (principal or collection?), Collection-Multiple (uint)
(define-public (admin-add-new-custodial-collection (collection <nft-trait>) (collection-multiple uint))
  (let
    (
      (active-whitelist (var-get custodial-whitelist-collections))
      (all-whitelist (var-get whitelist-collections))
    )
    ;; assert the tx-sender is admin
    (asserts! (is-some (index-of (var-get whitelist-admins) tx-sender)) ERR-NOT-AUTH)
    ;; assert collection not already added
    (asserts! (is-none (index-of all-whitelist (contract-of collection))) ERR-ALREADY-WHITELISTED)
    ;; assert multiple < 10
    (asserts! (and (< collection-multiple u11) (> collection-multiple u0)) ERR-MULTIPLIER)
    ;; update collection-multiplier map
    (map-set collection-multiplier (contract-of collection) collection-multiple)
    ;; add new principle to whitelist
   (ok 
      (begin
        (var-set custodial-whitelist-collections (unwrap! (as-max-len? (append active-whitelist (contract-of collection)) u100) ERR-UNWRAP))
        (var-set whitelist-collections (unwrap! (as-max-len? (append all-whitelist (contract-of collection)) u100) ERR-UNWRAP))
      )
    )
  )
)

(define-public (admin-remove-custodial-collection (collection <nft-trait>))
  (let
    (
      (active-whitelist (var-get custodial-whitelist-collections))
      (all-whitelist (var-get whitelist-collections))
      (removeable-principal-position-in-custodial-whitelist (index-of active-whitelist (contract-of collection)))
      (removeable-principal-position-in-all-whitelist (index-of all-whitelist (contract-of collection)))
    )
    ;; assert the tx-sender is admin
    (asserts! (is-some (index-of (var-get whitelist-admins) tx-sender)) ERR-NOT-AUTH)
    ;; assert collection is already added in custodial
    (asserts! (is-some (index-of active-whitelist (contract-of collection))) ERR-NOT-WHITELISTED)
    ;; assert collection is already added
    (asserts! (is-some (index-of all-whitelist (contract-of collection))) ERR-NOT-WHITELISTED)
    ;; update collection-multiplier map
    (map-set collection-multiplier (contract-of collection) u0)
    ;; temporary var set to help remove param principal
    (var-set helper-collection-principal (contract-of collection))
    ;; add new principal to whitelist
    (ok 
      (begin
        (var-set whitelist-collections (filter is-not-removeable-collection active-whitelist))
        (var-set custodial-whitelist-collections (filter is-not-removeable-collection all-whitelist))
      )
    )
  )
)

(define-public (admin-add-new-non-custodial-collection (collection <nft-trait>) (collection-multiple uint))
  (let
    (
      (active-whitelist (var-get non-custodial-whitelist-collections))
      (all-whitelist (var-get whitelist-collections))
    )
    ;; assert the tx-sender is admin
    (asserts! (is-some (index-of (var-get whitelist-admins) tx-sender)) ERR-NOT-AUTH)
    ;; assert collection not already added
    (asserts! (is-none (index-of all-whitelist (contract-of collection))) ERR-ALREADY-WHITELISTED)
    ;; assert multiple < 10
    (asserts! (and (< collection-multiple u11) (> collection-multiple u0)) ERR-MULTIPLIER)
    ;; update collection-multiplier map
    (map-set collection-multiplier (contract-of collection) collection-multiple)
    ;; add new principal to whitelist
    (ok 
      (begin
        (var-set non-custodial-whitelist-collections (unwrap! (as-max-len? (append active-whitelist (contract-of collection)) u100) ERR-UNWRAP))
        (var-set whitelist-collections (unwrap! (as-max-len? (append all-whitelist (contract-of collection)) u100) ERR-UNWRAP))
      )
    )
  )
)

(define-public (admin-remove-non-custodial-collection (collection <nft-trait>))
  (let
    (
      (active-whitelist (var-get non-custodial-whitelist-collections))
      (all-whitelist (var-get whitelist-collections))
      (removeable-principal-position-in-non-custodial-whitelist (index-of active-whitelist (contract-of collection)))
      (removeable-principal-position-in-all-whitelist (index-of all-whitelist (contract-of collection)))
    )
    ;; assert the tx-sender is admin
    (asserts! (is-some (index-of (var-get whitelist-admins) tx-sender)) ERR-NOT-AUTH)
    ;; assert collection is already added in non custodial
    (asserts! (is-some (index-of active-whitelist (contract-of collection))) ERR-NOT-WHITELISTED)
    ;; assert collection is already added
    (asserts! (is-some (index-of all-whitelist (contract-of collection))) ERR-NOT-WHITELISTED)
    ;; update collection-multiplier map
    (map-set collection-multiplier (contract-of collection) u0)
    ;; temporary var set to help remove param principal
    (var-set helper-collection-principal (contract-of collection))
    ;; add new principal to whitelist
    (ok 
      (begin
        (var-set whitelist-collections (filter is-not-removeable-collection active-whitelist))
        (var-set non-custodial-whitelist-collections (filter is-not-removeable-collection all-whitelist))
      )
    )
  )
)

;; @desc - Helper function for removing a specific collection from the whitelist
(define-private (is-not-removeable-collection (whitelist-collection principal))
  (not (is-eq whitelist-collection (var-get helper-collection-principal)))
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Add Admin Address For Whitelisting ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; @desc - Function for add principals that have explicit permission to add current or future stakeable collections
;; @param - Principal that we're adding as whitelist
(define-public (add-admin-address-for-whitelisting (new-whitelist principal))
  (let
    (
      (current-admin-list (var-get whitelist-admins))
      (caller-principal-position-in-list (index-of current-admin-list tx-sender))
      (param-principal-position-in-list (index-of current-admin-list new-whitelist))
    )
    ;; asserts tx-sender is an existing whitelist address
    (asserts! (is-some caller-principal-position-in-list) ERR-NOT-AUTH)
    ;; asserts param principal (new whitelist) doesn't already exist
    (asserts! (is-none param-principal-position-in-list) ERR-ALREADY-WHITELISTED)
    ;; append new whitelist address
    (ok (var-set whitelist-admins (unwrap! (as-max-len? (append (var-get whitelist-admins) new-whitelist) u100) ERR-UNWRAP)))
  )
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Remove Admin Address For Whitelisting ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; @desc - Function for removing principals that have explicit permission to add current or future stakeable collections
;; @param - Principal that we're adding removing as white
(define-public (remove-admin-address-for-whitelisting (remove-whitelist principal))
  (let
    (
      (current-admin-list (var-get whitelist-admins))
      (caller-principal-position-in-list (index-of current-admin-list tx-sender))
      (removeable-principal-position-in-list (index-of current-admin-list remove-whitelist))
    )
    ;; assert the tx-sender is admin
    (asserts! (is-some (index-of (var-get whitelist-admins) tx-sender)) ERR-NOT-AUTH)
    ;; asserts param principal (removeable whitelist) already exist
    (asserts! (is-eq removeable-principal-position-in-list) ERR-NOT-WHITELISTED)
    ;; temporary var set to help remove param principal
    (var-set helper-collection-principal remove-whitelist)
    ;; filter existing whitelist address
    (ok 
      (var-set whitelist-admins (filter is-not-removeable current-admin-list))
    )
  )
)

;; @desc - Helper function for removing a specific admin from the admin whitelist
(define-private (is-not-removeable (admin-principal principal))
  (not (is-eq admin-principal (var-get helper-collection-principal)))
)

;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Admin Manual Unstake ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;

;; @desc - Function for emergency un-staking all manually custodied assets (Stacculents or Spookies)
;; @param - Principal of collection we're removing, ID of item we're manually unstaking & returning to user

(define-public (admin-emergency-unstake (collection <nft-trait>) (staked-id uint) (original-owner principal))
  (let
    (
      (this-collection-multiplier (default-to u0 (map-get? collection-multiplier (contract-of collection))))
      (this-collection-multiplier-normalized (/ (* this-collection-multiplier max-payout-per-block) u10))
      (current-staker (get staker (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection), id: staked-id}))))
      (stake-status (get status (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection), id: staked-id}))))
      (last-claimed-or-staked-height (get last-staked-or-claimed (default-to {status: false, last-staked-or-claimed: block-height, staker: tx-sender} (map-get? staked-item {collection: (contract-of collection), id: staked-id}))))
      (current-nft-owner (unwrap! (contract-call? collection get-owner staked-id) ERR-NOT-AUTH))
      (blocks-staked (- block-height last-claimed-or-staked-height))
      (current-all-staked-in-collection-list (default-to (list) (map-get? all-stakes-in-collection (contract-of collection))))
      (current-user-staked-by-collection-list (default-to (list) (map-get? user-stakes-by-collection {user: tx-sender, collection: (contract-of collection)})))
      (custodial-list (var-get custodial-whitelist-collections))
      (admins (var-get whitelist-admins))
      (set-var (var-set id-being-removed staked-id))
      (remove-stake (remove-a-specific-stake (contract-of collection) original-owner staked-id))
    )
    ;; asserts is staked
    (asserts! stake-status ERR-NOT-STAKED)
    ;; asserts original-owner is staker
    (asserts! (is-eq original-owner current-staker) ERR-NOT-OWNER)
    ;; asserts that tx-sender is admin
    (asserts! (is-some (index-of admins tx-sender)) ERR-NOT-AUTH)

    ;; temporary var set to help remove param
    (var-set id-and-collection-being-removed {collection: (contract-of collection), nft: staked-id})
    ;; check if blocks-staked > 0 to see if there's any unclaimed $SNOW to claim
    (if (> blocks-staked u0)
      ;; if there is, need to claim unstaked
      (unwrap! (contract-call? .testing-stake-contract-snow mint (* this-collection-multiplier-normalized blocks-staked) original-owner) ERR-UNWRAP)
      ;; if not, proceed
      true
    )
    ;;manual unstake of custodial
    (if
        (is-some (index-of custodial-list (contract-of collection)))   
        (as-contract (unwrap! (contract-call? collection transfer staked-id .testing-stake-contract original-owner) (err u401))) 
        true
    )
    ;; Set helper id for removal in filters below
    (var-set id-being-removed staked-id)
    ;; filter/remove staked-id from all-stakes-in-collection
    (map-set all-stakes-in-collection (contract-of collection) (filter is-not-id current-all-staked-in-collection-list))
    ;; filter/remove staked-id from user-stakes-by-collection
    (map-set user-stakes-by-collection {user: original-owner, collection: (contract-of collection)} (filter is-not-id current-user-staked-by-collection-list))
    ;; update last-staked-or-claimed height
    (ok (map-set staked-item {collection: (contract-of collection), id: staked-id}
      {
        status: false,
        last-staked-or-claimed: block-height,
        staker: original-owner
      }
    ))
  )
)

(define-public (admin-emergency-unstake-many (collection <nft-trait>) (nfts (list 10 uint)) (original-owner principal))
  (let 
    (
      (admins (var-get whitelist-admins))
      (nft-1 (element-at nfts u0))
      (nft-2 (element-at nfts u1))
      (nft-3 (element-at nfts u2))
      (nft-4 (element-at nfts u3))
      (nft-5 (element-at nfts u4))
      (nft-6 (element-at nfts u5))
      (nft-7 (element-at nfts u6))
      (nft-8 (element-at nfts u7))
      (nft-9 (element-at nfts u8))
      (nft-10 (element-at nfts u9))
      (staking-list (list nft-1 nft-2 nft-3 nft-4 nft-5 nft-6 nft-7 nft-8 nft-9 nft-10))
      (stake-1 (if (is-some nft-1) (some (admin-emergency-unstake collection (unwrap! nft-1 ERR-UNWRAP) original-owner)) none))
      (stake-2 (if (is-some nft-2) (some (admin-emergency-unstake collection (unwrap! nft-2 ERR-UNWRAP) original-owner)) none))
      (stake-3 (if (is-some nft-3) (some (admin-emergency-unstake collection (unwrap! nft-3 ERR-UNWRAP) original-owner)) none))
      (stake-4 (if (is-some nft-4) (some (admin-emergency-unstake collection (unwrap! nft-4 ERR-UNWRAP) original-owner)) none))
      (stake-5 (if (is-some nft-5) (some (admin-emergency-unstake collection (unwrap! nft-5 ERR-UNWRAP) original-owner)) none))
      (stake-6 (if (is-some nft-6) (some (admin-emergency-unstake collection (unwrap! nft-6 ERR-UNWRAP) original-owner)) none))
      (stake-7 (if (is-some nft-7) (some (admin-emergency-unstake collection (unwrap! nft-7 ERR-UNWRAP) original-owner)) none))
      (stake-8 (if (is-some nft-8) (some (admin-emergency-unstake collection (unwrap! nft-8 ERR-UNWRAP) original-owner)) none))
      (stake-9 (if (is-some nft-9) (some (admin-emergency-unstake collection (unwrap! nft-9 ERR-UNWRAP) original-owner)) none))
      (stake-10 (if (is-some nft-10) (some (admin-emergency-unstake collection (unwrap! nft-10 ERR-UNWRAP) original-owner)) none))
      (final-list (list stake-1 stake-2 stake-3 stake-4 stake-5 stake-6 stake-7 stake-8 stake-9 stake-10))
    )
    (asserts! (is-some (index-of admins tx-sender)) ERR-NOT-AUTH)
    (ok final-list)
  )
)

;; Function to change a collection multiplier (amount of SNOW generated per day)
(define-public (change-collection-multiplier (collection principal) (new-multiplier uint))
  (let
    (
      (admins (var-get whitelist-admins))
    )
    (asserts! (is-some (index-of admins tx-sender)) ERR-NOT-AUTH)
    (ok (map-set collection-multiplier collection new-multiplier))
  )
)

(define-public (switch-staking)
  (let
    (
      (admins (var-get whitelist-admins))
    )
    (asserts! (is-some (index-of admins tx-sender)) ERR-NOT-AUTH)

    (ok (if (var-get active-staking)
      (var-set active-staking false)
      (var-set active-staking true)
    ) )
  )
)

Functions (43)

FunctionAccessArgs
claim-all-stakepublic
get-all-user-stakesread-onlyuser: principal
is-staking-activeread-only
active-adminsread-only
active-collectionsread-only
custodial-active-collectionsread-only
non-custodial-active-collectionsread-only
get-generation-rate-of-a-collectionread-onlycollection: principal
get-total-generation-rate-through-all-collectionsread-only
filter-out-collections-with-no-stakesprivatecollection: principal
map-from-list-staked-to-generation-per-collectionprivatecollection: principal
get-unclaimed-balance-by-collection-and-itemread-onlycollection: <nft-trait>, item: uint
get-unclaimed-balance-by-collection-and-itemsread-onlycollection: <nft-trait>, items: (list 10 (optional uint
get-items-staked-by-collection-and-userread-onlycollection: principal, user: principal
get-item-stake-detailsread-onlycollection: principal, item-id: uint
get-unclaimed-balancepublic
map-to-append-to-list-of-height-differencesprivatecollection: principal
append-helper-list-from-id-staked-to-height-differenceprivatestaked-id: uint
get-unclaimed-balance-by-collectionpubliccollection: <nft-trait>
map-from-id-staked-to-height-differenceprivatestaked-id: uint
stakepubliccollection: <nft-trait>, id: uint
remove-a-stakeprivatecollection: principal, user: principal
remove-a-specific-stakeprivatecollection: principal, user: principal, nft: uint
stake-manypubliccollection: <nft-trait>, nfts: (list 10 uint
claim-item-stakepubliccollection-collective: <nft-trait>, staked-id: uint
map-to-loop-through-active-collectionprivatecollection: principal
map-to-set-reset-last-claimed-or-staked-heightprivatestaked-id: uint
unstake-itempubliccollection: <nft-trait>, staked-id: uint
is-not-idprivatelist-id: uint
is-not-removeable-collection-and-idprivatecombo: {collection: principal, nft: uint}
unstake-manypubliccollection: <nft-trait>, nfts: (list 10 uint
admin-add-new-custodial-collectionpubliccollection: <nft-trait>, collection-multiple: uint
admin-remove-custodial-collectionpubliccollection: <nft-trait>
admin-add-new-non-custodial-collectionpubliccollection: <nft-trait>, collection-multiple: uint
admin-remove-non-custodial-collectionpubliccollection: <nft-trait>
is-not-removeable-collectionprivatewhitelist-collection: principal
add-admin-address-for-whitelistingpublicnew-whitelist: principal
remove-admin-address-for-whitelistingpublicremove-whitelist: principal
is-not-removeableprivateadmin-principal: principal
admin-emergency-unstakepubliccollection: <nft-trait>, staked-id: uint, original-owner: principal
admin-emergency-unstake-manypubliccollection: <nft-trait>, nfts: (list 10 uint
change-collection-multiplierpubliccollection: principal, new-multiplier: uint
switch-stakingpublic