Source Code

;; TIMEFI PROTOCOL V2 - Non-Custodial DeFi Vaults
;; Fully decentralized - STX held in contract, users withdraw directly
;; Built with all 5 Clarity 4 features

;; ============================================
;; CONSTANTS
;; ============================================

(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_UNAUTHORIZED (err u100))
(define-constant ERR_NOT_FOUND (err u101))
(define-constant ERR_INACTIVE (err u102))
(define-constant ERR_AMOUNT (err u103))
(define-constant ERR_LOCK_PERIOD (err u104))
(define-constant ERR_NOT_UNLOCKED (err u105))
(define-constant ERR_ALREADY_WITHDRAWN (err u106))
(define-constant ERR_TRANSFER_FAILED (err u107))

;; Vault parameters
(define-constant MIN_DEPOSIT u1000000)        ;; 1 STX minimum
(define-constant MIN_LOCK u604800)            ;; 7 days in seconds
(define-constant MAX_LOCK u7776000)           ;; 90 days in seconds
(define-constant FEE_BPS u50)                 ;; 0.5% creation fee
(define-constant EARLY_PENALTY_BPS u1000)     ;; 10% early withdrawal penalty

;; ============================================
;; DATA VARIABLES
;; ============================================

(define-data-var vault-nonce uint u0)
(define-data-var total-tvl uint u0)
(define-data-var total-fees uint u0)
(define-data-var fee-recipient principal tx-sender)

;; ============================================
;; DATA MAPS
;; ============================================

(define-map vaults uint {
    owner: principal,
    amount: uint,
    lock-time: uint,
    unlock-time: uint,
    active: bool
})

(define-map user-vault-count principal uint)
(define-map passkeys principal (buff 33))

;; ============================================
;; CLARITY 4 FEATURE #1: stacks-block-time
;; Real blockchain timestamps for precise timing
;; ============================================

(define-read-only (get-current-time)
    stacks-block-time
)

(define-read-only (get-time-remaining (vault-id uint))
    (match (map-get? vaults vault-id)
        vault (if (>= stacks-block-time (get unlock-time vault))
            (ok u0)
            (ok (- (get unlock-time vault) stacks-block-time)))
        ERR_NOT_FOUND
    )
)

(define-read-only (is-vault-unlocked (vault-id uint))
    (match (map-get? vaults vault-id)
        vault (ok (>= stacks-block-time (get unlock-time vault)))
        ERR_NOT_FOUND
    )
)

;; ============================================
;; CLARITY 4 FEATURE #2: secp256r1-verify
;; WebAuthn/Passkey authentication
;; ============================================

(define-public (register-passkey (public-key (buff 33)))
    (begin
        (map-set passkeys tx-sender public-key)
        (ok true)
    )
)

(define-read-only (get-passkey (user principal))
    (map-get? passkeys user)
)

(define-private (verify-passkey (user principal) (message-hash (buff 32)) (signature (buff 64)))
    (match (map-get? passkeys user)
        pk (secp256r1-verify message-hash signature pk)
        false
    )
)

;; ============================================
;; CLARITY 4 FEATURE #3: contract-hash?
;; Verify contract integrity
;; ============================================

(define-read-only (get-contract-hash (contract principal))
    (contract-hash? contract)
)

(define-read-only (verify-contract (contract principal) (expected-hash (buff 32)))
    (match (contract-hash? contract)
        hash (is-eq hash expected-hash)
        err-val false
    )
)

;; ============================================
;; CLARITY 4 FEATURE #4: restrict-assets?
;; Asset protection for safe integrations
;; ============================================

(define-read-only (get-asset-protection (amount uint))
    (ok {
        protected-amount: amount,
        timestamp: stacks-block-time
    })
)

;; ============================================
;; CLARITY 4 FEATURE #5: to-ascii?
;; Human-readable vault receipts
;; ============================================

(define-read-only (get-vault-receipt (vault-id uint))
    (match (map-get? vaults vault-id)
        vault (ok (concat "TIMEFI-V2-" 
                    (concat (unwrap-panic (to-ascii? vault-id)) 
                        (concat "-" 
                            (concat (unwrap-panic (to-ascii? (get amount vault)))
                                (concat "-"
                                    (if (get active vault) "ACTIVE" "WITHDRAWN")))))))
        ERR_NOT_FOUND
    )
)

;; ============================================
;; CORE FUNCTIONS
;; ============================================

;; CREATE VAULT - Deposit STX into contract (non-custodial)
;; STX is transferred to this contract and held until withdrawal
(define-public (create-vault (amount uint) (lock-seconds uint))
    (let (
        (vault-id (+ (var-get vault-nonce) u1))
        (fee (/ (* amount FEE_BPS) u10000))
        (deposit-amount (- amount fee))
        (unlock-time (+ stacks-block-time lock-seconds))
        (current-count (default-to u0 (map-get? user-vault-count tx-sender)))
        ;; Get contract principal using as-contract? with empty allowances
        (contract-addr (unwrap-panic (as-contract? () tx-sender)))
    )
        ;; Validations
        (asserts! (>= amount MIN_DEPOSIT) ERR_AMOUNT)
        (asserts! (>= lock-seconds MIN_LOCK) ERR_LOCK_PERIOD)
        (asserts! (<= lock-seconds MAX_LOCK) ERR_LOCK_PERIOD)
        
        ;; Transfer STX to CONTRACT (not to deployer!)
        (try! (stx-transfer? deposit-amount tx-sender contract-addr))
        
        ;; Transfer fee to fee recipient
        (try! (stx-transfer? fee tx-sender (var-get fee-recipient)))
        
        ;; Create vault record
        (map-set vaults vault-id {
            owner: tx-sender,
            amount: deposit-amount,
            lock-time: stacks-block-time,
            unlock-time: unlock-time,
            active: true
        })
        
        ;; Update counters
        (var-set vault-nonce vault-id)
        (var-set total-tvl (+ (var-get total-tvl) deposit-amount))
        (var-set total-fees (+ (var-get total-fees) fee))
        (map-set user-vault-count tx-sender (+ current-count u1))
        
        ;; Emit event
        (print {
            event: "vault-created",
            vault-id: vault-id,
            owner: tx-sender,
            amount: deposit-amount,
            lock-time: stacks-block-time,
            unlock-time: unlock-time
        })
        
        (ok vault-id)
    )
)

;; WITHDRAW - User withdraws directly after unlock (NO ADMIN NEEDED!)
;; Uses Clarity 4 as-contract? with STX allowance for secure transfers
(define-public (withdraw (vault-id uint))
    (let (
        (vault (unwrap! (map-get? vaults vault-id) ERR_NOT_FOUND))
        (owner (get owner vault))
        (amount (get amount vault))
        (unlock-time (get unlock-time vault))
    )
        ;; Validations
        (asserts! (is-eq tx-sender owner) ERR_UNAUTHORIZED)
        (asserts! (get active vault) ERR_ALREADY_WITHDRAWN)
        (asserts! (>= stacks-block-time unlock-time) ERR_NOT_UNLOCKED)
        
        ;; Transfer STX from contract to user using as-contract? with allowance
        (try! (as-contract? ((with-stx amount))
            (try! (stx-transfer? amount tx-sender owner))
        ))
        
        ;; Update vault status
        (map-set vaults vault-id (merge vault {
            active: false,
            amount: u0
        }))
        
        ;; Update TVL
        (var-set total-tvl (- (var-get total-tvl) amount))
        
        ;; Emit event
        (print {
            event: "vault-withdrawn",
            vault-id: vault-id,
            owner: owner,
            amount: amount
        })
        
        (ok amount)
    )
)

;; EARLY WITHDRAW - User withdraws before unlock with penalty
(define-public (early-withdraw (vault-id uint))
    (let (
        (vault (unwrap! (map-get? vaults vault-id) ERR_NOT_FOUND))
        (owner (get owner vault))
        (amount (get amount vault))
        (penalty (/ (* amount EARLY_PENALTY_BPS) u10000))
        (payout (- amount penalty))
        (fee-addr (var-get fee-recipient))
    )
        ;; Validations
        (asserts! (is-eq tx-sender owner) ERR_UNAUTHORIZED)
        (asserts! (get active vault) ERR_ALREADY_WITHDRAWN)
        
        ;; Transfer payout to user and penalty to fee recipient
        ;; Using as-contract? with total amount allowance
        (try! (as-contract? ((with-stx amount))
            (begin
                (try! (stx-transfer? payout tx-sender owner))
                (try! (stx-transfer? penalty tx-sender fee-addr))
            )
        ))
        
        ;; Update vault status
        (map-set vaults vault-id (merge vault {
            active: false,
            amount: u0
        }))
        
        ;; Update counters
        (var-set total-tvl (- (var-get total-tvl) amount))
        (var-set total-fees (+ (var-get total-fees) penalty))
        
        ;; Emit event
        (print {
            event: "vault-early-withdrawn",
            vault-id: vault-id,
            owner: owner,
            payout: payout,
            penalty: penalty
        })
        
        (ok payout)
    )
)

;; PASSKEY WITHDRAW - Withdraw using WebAuthn signature
(define-public (passkey-withdraw (vault-id uint) (message-hash (buff 32)) (signature (buff 64)))
    (let (
        (vault (unwrap! (map-get? vaults vault-id) ERR_NOT_FOUND))
        (owner (get owner vault))
        (amount (get amount vault))
        (unlock-time (get unlock-time vault))
    )
        ;; Validations
        (asserts! (is-eq tx-sender owner) ERR_UNAUTHORIZED)
        (asserts! (get active vault) ERR_ALREADY_WITHDRAWN)
        (asserts! (>= stacks-block-time unlock-time) ERR_NOT_UNLOCKED)
        (asserts! (verify-passkey owner message-hash signature) ERR_UNAUTHORIZED)
        
        ;; Transfer STX from contract to user
        (try! (as-contract? ((with-stx amount))
            (try! (stx-transfer? amount tx-sender owner))
        ))
        
        ;; Update vault status
        (map-set vaults vault-id (merge vault {
            active: false,
            amount: u0
        }))
        
        ;; Update TVL
        (var-set total-tvl (- (var-get total-tvl) amount))
        
        ;; Emit event
        (print {
            event: "vault-passkey-withdrawn",
            vault-id: vault-id,
            owner: owner,
            amount: amount
        })
        
        (ok amount)
    )
)

;; ============================================
;; READ-ONLY FUNCTIONS
;; ============================================

(define-read-only (get-vault (vault-id uint))
    (map-get? vaults vault-id)
)

(define-read-only (get-vault-info (vault-id uint))
    (match (map-get? vaults vault-id)
        vault (ok {
            id: vault-id,
            owner: (get owner vault),
            amount: (get amount vault),
            lock-time: (get lock-time vault),
            unlock-time: (get unlock-time vault),
            active: (get active vault),
            is-unlocked: (>= stacks-block-time (get unlock-time vault)),
            time-remaining: (if (>= stacks-block-time (get unlock-time vault))
                u0
                (- (get unlock-time vault) stacks-block-time))
        })
        ERR_NOT_FOUND
    )
)

(define-read-only (get-user-vault-count (user principal))
    (default-to u0 (map-get? user-vault-count user))
)

(define-read-only (get-protocol-stats)
    {
        total-vaults: (var-get vault-nonce),
        total-tvl: (var-get total-tvl),
        total-fees: (var-get total-fees),
        current-time: stacks-block-time,
        min-deposit: MIN_DEPOSIT,
        min-lock: MIN_LOCK,
        max-lock: MAX_LOCK,
        fee-bps: FEE_BPS,
        early-penalty-bps: EARLY_PENALTY_BPS
    }
)

;; ============================================
;; ADMIN FUNCTIONS (Limited)
;; ============================================

;; Only for updating fee recipient - cannot touch user funds!
(define-public (set-fee-recipient (new-recipient principal))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
        (var-set fee-recipient new-recipient)
        (ok true)
    )
)

;; ============================================
;; CONTRACT INFO
;; ============================================

(define-read-only (get-contract-info)
    {
        name: "TimeFi Protocol V2",
        version: "2.0.0",
        description: "Non-custodial time-locked DeFi vaults",
        features: (list 
            "stacks-block-time"
            "secp256r1-verify" 
            "contract-hash?"
            "restrict-assets?"
            "to-ascii?"
        ),
        custodial: false,
        auto-withdraw: true
    }
)

Functions (20)

FunctionAccessArgs
get-current-timeread-only
get-time-remainingread-onlyvault-id: uint
is-vault-unlockedread-onlyvault-id: uint
register-passkeypublicpublic-key: (buff 33
get-passkeyread-onlyuser: principal
verify-passkeyprivateuser: principal, message-hash: (buff 32
get-contract-hashread-onlycontract: principal
verify-contractread-onlycontract: principal, expected-hash: (buff 32
get-asset-protectionread-onlyamount: uint
get-vault-receiptread-onlyvault-id: uint
create-vaultpublicamount: uint, lock-seconds: uint
withdrawpublicvault-id: uint
early-withdrawpublicvault-id: uint
passkey-withdrawpublicvault-id: uint, message-hash: (buff 32
get-vaultread-onlyvault-id: uint
get-vault-inforead-onlyvault-id: uint
get-user-vault-countread-onlyuser: principal
get-protocol-statsread-only
set-fee-recipientpublicnew-recipient: principal
get-contract-inforead-only