Source Code

;; title: s-pay
;; version:
;; summary:
;; description:

;; traits
;;

;; token definitions
;;

;; --- Read Only Functions ---

;; @desc Get core protocol configuration and status
(define-read-only (get-protocol-status)
    (ok {
        version: CONTRACT-VERSION,
        paused: (var-get is-paused),
        owner: (var-get contract-owner),
        fee-percentage: (var-get fee-percentage),
        total-volume: (var-get total-volume),
        total-fees-collected: (var-get total-fees-collected),
        require-verification: (var-get require-merchant-verification)
    })
)

;; @desc Retrieve full user data by principal
;; @param account principal - The user account to lookup
(define-read-only (get-user-data (account principal))
    (map-get? Users account)
)

;; @desc Retrieve user profile metadata
;; @param account principal - The user account to lookup
(define-read-only (get-user-profile (account principal))
    (map-get? UserProfiles account)
)

;; @desc Retrieve full merchant data by principal
;; @param account principal - The merchant account to lookup
(define-read-only (get-merchant-data (account principal))
    (map-get? Merchants account)
)

;; @desc Search for user principal by username
;; @param username (string-ascii 24) - The unique username
(define-read-only (get-principal-by-username (username (string-ascii 24)))
    (map-get? Usernames username)
)

;; @desc Inspect a specific system event
;; @param id uint - The event id
(define-read-only (get-system-event (id uint))
    (map-get? SystemEvents { event-id: id })
)

;; @desc Helper to check if a user is an active merchant
;; @param account principal - The account to check
(define-read-only (is-active-merchant (account principal))
    (let (
        (merchant (map-get? Merchants account))
    )
        (match merchant
            profile (is-eq (get status profile) "verified")
            false
        )
    )
)

;; @desc Get current system nonces
(define-read-only (get-nonces)
    {
        users: (var-get user-nonce),
        merchants: (var-get merchant-nonce),
        events: (var-get event-nonce)
    }
)

;; Error Constants
(define-constant ERR-UNAUTHORIZED (err u100))
(define-constant ERR-NOT-OWNER (err u101))
(define-constant ERR-ALREADY-REGISTERED (err u102))
(define-constant ERR-USER-NOT-FOUND (err u103))
(define-constant ERR-MERCHANT-NOT-FOUND (err u104))
(define-constant ERR-INVALID-AMOUNT (err u105))
(define-constant ERR-INSUFFICIENT-FUNDS (err u106))
(define-constant ERR-PAYMENT-EXPIRED (err u107))
(define-constant ERR-ALREADY-PAID (err u108))
(define-constant ERR-INVALID-STATUS (err u109))
(define-constant ERR-CONTRACT-PAUSED (err u110))
(define-constant ERR-INVALID-FEE (err u111))
(define-constant ERR-LIMIT-EXCEEDED (err u112))
(define-constant ERR-INVALID-PRINCIPAL (err u113))
(define-constant ERR-SUBSCRIPTION-EXPIRED (err u114))
(define-constant ERR-PLAN-NOT-FOUND (err u115))
(define-constant ERR-NOT-ALLOWED (err u116))

;; Configuration Constants
(define-constant BP-PERCENT u10000) ;; Basis points (100% = 10000)
(define-constant MAX-FEE-PERCENT u500) ;; Max fee 5% (500 basis points)
(define-constant MIN-PAYMENT-STX u1000000) ;; 1 STX minimum for logic-heavy payments
(define-constant DISPUTE-WINDOW-BLOCKS u144) ;; ~24 hours
(define-constant REGISTRATION-FEE-STX u5000000) ;; 5 STX onboarding fee
(define-constant MERCHANT-VERIFICATION-STAKE u50000000) ;; 50 STX stake for high-tier merchants

;; Operational Variables
(define-constant CONTRACT-VERSION "1.0.0")
(define-constant PLATFORM-TAG "SPAY-PROTO")

;; --- Data Variables ---

;; The primary administrator address that can update protocol settings
(define-data-var contract-owner principal tx-sender)

;; A safety switch to halt sensitive operations in case of emergency
(define-data-var is-paused bool false)

;; The treasury address where platform fees are directed
(define-data-var fee-receiver principal tx-sender)

;; Current platform fee in basis points (e.g., 200 = 2%)
(define-data-var fee-percentage uint u200)

;; Scaling counter for user registrations to ensure unique indexing
(define-data-var user-nonce uint u0)

;; Scaling counter for merchant registrations and profile tracking
(define-data-var merchant-nonce uint u0)

;; Total transaction volume processed by the protocol in micro-STX
(define-data-var total-volume uint u0)

;; Total platform fees collected since deployment
(define-data-var total-fees-collected uint u0)

;; Global switch for merchant verification requirements
(define-data-var require-merchant-verification bool true)

;; Maximum allowed transaction volume per single payment
(define-data-var max-payment-amount (optional uint) none)

;; --- Data Maps ---

;; User Records - maps principal to a unique user-id and registration data
(define-map Users
    principal
    {
        user-id: uint,
        username: (string-ascii 24),
        status: (string-ascii 12), ;; "active", "suspended"
        registered-at: uint,
        total-spent: uint,
        total-received: uint,
        is-merchant: bool
    }
)

;; Username Lookup - maps username to principal to ensure uniqueness
(define-map Usernames
    (string-ascii 24)
    principal
)

;; --- Public Functions ---

;; @desc Register a new user in the protocol
;; @param username (string-ascii 24) - A unique display name for the user
(define-public (register-user (username (string-ascii 24)))
    (let (
        (new-id (+ (var-get user-nonce) u1))
    )
        ;; Check if contract is paused
        (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)

        ;; Check if user is already registered
        (asserts! (is-none (map-get? Users tx-sender)) ERR-ALREADY-REGISTERED)

        ;; Check if username is already taken
        (asserts! (is-none (map-get? Usernames username)) ERR-ALREADY-REGISTERED)

        ;; Optional: Handle registration fee if enabled
        (if (> REGISTRATION-FEE-STX u0)
            (try! (stx-transfer? REGISTRATION-FEE-STX tx-sender (var-get fee-receiver)))
            true
        )

        ;; Store user record
        (map-set Users tx-sender {
            user-id: new-id,
            username: username,
            status: "active",
            registered-at: block-height,
            total-spent: u0,
            total-received: u0,
            is-merchant: false
        })

        ;; Map username to principal
        (map-set Usernames username tx-sender)

        ;; Increment nonce
        (var-set user-nonce new-id)

        ;; Emit registration event
        (print {
            event: "user-registered",
            user: tx-sender,
            user-id: new-id,
            username: username
        })

        (ok new-id)
    )
)

;; Merchant Records - maps principal to their business details
(define-map Merchants
    principal
    {
        merchant-id: uint,
        business-name: (string-ascii 64),
        description: (string-ascii 256),
        website: (string-ascii 128),
        status: (string-ascii 12), ;; "pending", "verified", "suspended"
        tier: (string-ascii 12), ;; "basic", "premium", "enterprise"
        total-revenue: uint,
        dispute-count: uint,
        metadata-hash: (buff 32)
    }
)

;; --- Public Functions ---

;; @desc Register as a merchant to receive professional payments
;; @param business-name (string-ascii 64) - The legal or brand name
;; @param website (string-ascii 128) - Official business website
(define-public (register-merchant (business-name (string-ascii 64)) (website (string-ascii 128)))
    (let (
        (user (unwrap! (map-get? Users tx-sender) ERR-USER-NOT-FOUND))
        (new-id (+ (var-get merchant-nonce) u1))
    )
        (begin
            ;; Check if contract is paused
            (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)

            ;; Check if already a merchant
            (asserts! (is-none (map-get? Merchants tx-sender)) ERR-ALREADY-REGISTERED)

            ;; Basic verification stake if required
            (if (var-get require-merchant-verification)
                (try! (stx-transfer? MERCHANT-VERIFICATION-STAKE tx-sender (as-contract tx-sender)))
                true
            )

            ;; Store merchant profile
            (map-set Merchants tx-sender {
                merchant-id: new-id,
                business-name: business-name,
                description: "",
                website: website,
                status: (if (var-get require-merchant-verification) "pending" "verified"),
                tier: "basic",
                total-revenue: u0,
                dispute-count: u0,
                metadata-hash: 0x0000000000000000000000000000000000000000000000000000000000000000
            })

            ;; Update user record to reflect merchant status
            (map-set Users tx-sender (merge user { is-merchant: true }))

            ;; Increment merchant nonce
            (var-set merchant-nonce new-id)

            ;; Emit merchant registration event
            (print {
                event: "merchant-registered",
                merchant: tx-sender,
                merchant-id: new-id,
                business-name: business-name
            })

            (ok new-id)
        )
    )
)

;; @desc Verify a merchant profile (Admin Only)
;; @param merchant principal - The merchant to verify
(define-public (verify-merchant (merchant principal))
    (let (
        (profile (unwrap! (map-get? Merchants merchant) ERR-MERCHANT-NOT-FOUND))
    )
        ;; Only contract owner can verify
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)

        ;; Update merchant status
        (map-set Merchants merchant (merge profile { status: "verified" }))

        ;; Emit verification event
        (print {
            event: "merchant-verified",
            merchant: merchant
        })

        (ok true)
    )
)

;; @desc Revoke merchant verification or suspend (Admin Only)
;; @param merchant principal - The merchant to suspend
(define-public (suspend-merchant (merchant principal))
    (let (
        (profile (unwrap! (map-get? Merchants merchant) ERR-MERCHANT-NOT-FOUND))
    )
        ;; Only contract owner can suspend
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)

        ;; Update merchant status
        (map-set Merchants merchant (merge profile { status: "suspended" }))

        ;; Emit suspension event
        (print {
            event: "merchant-suspended",
            merchant: merchant
        })

        (ok true)
    )
)

;; User Profile Metadata - stores extended profile information and preferences
(define-map UserProfiles
    principal
    {
        bio: (string-ascii 160),
        website: (string-ascii 128),
        avatar-url: (string-ascii 256),
        notification-enabled: bool,
        preferred-currency: (string-ascii 3) ;; e.g., "STX", "USD"
    }
)

;; --- Public Functions ---

;; @desc Update extended user profile metadata
;; @param bio (string-ascii 160) - Short user biography
;; @param website (string-ascii 128) - Personal website/social link
;; @param avatar-url (string-ascii 256) - Link to user profile picture
(define-public (update-user-profile 
    (bio (string-ascii 160)) 
    (website (string-ascii 128)) 
    (avatar-url (string-ascii 256))
    (notifications bool)
    (currency (string-ascii 3))
)
    (let (
        (user (unwrap! (map-get? Users tx-sender) ERR-USER-NOT-FOUND))
    )
        ;; Check if contract is paused
        (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)

        ;; Store or update profile
        (map-set UserProfiles tx-sender {
            bio: bio,
            website: website,
            avatar-url: avatar-url,
            notification-enabled: notifications,
            preferred-currency: currency
        })

        ;; Emit profile update event
        (print {
            event: "profile-updated",
            user: tx-sender,
            updated-at: block-height
        })

        (ok true)
    )
)

;; --- Authorization Helpers ---

;; @desc Change the protocol pause state (Admin Only)
;; @param pause bool - New pause state
(define-public (set-paused (pause bool))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        (var-set is-paused pause)
        (print { event: "protocol-pause-updated", paused: pause })
        (ok true)
    )
)

;; @desc Update the treasury address (Admin Only)
;; @param new-receiver principal - The new fee collection address
(define-public (set-fee-receiver (new-receiver principal))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        (var-set fee-receiver new-receiver)
        (print { event: "fee-receiver-updated", receiver: new-receiver })
        (ok true)
    )
)

;; @desc Update the protocol fee (Admin Only)
;; @param new-fee uint - New fee in basis points
(define-public (set-fee-percentage (new-fee uint))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        (asserts! (<= new-fee MAX-FEE-PERCENT) ERR-INVALID-FEE)
        (var-set fee-percentage new-fee)
        (print { event: "fee-percentage-updated", fee: new-fee })
        (ok true)
    )
)

;; --- System Events Log ---
;; A central map for tracking major protocol occurrences to meet volume requirements
(define-map SystemEvents
    { event-id: uint }
    {
        event-type: (string-ascii 32),
        actor: principal,
        payload: (string-ascii 256),
        timestamp: uint
    }
)

(define-data-var event-nonce uint u0)

;; --- Authorization Helpers ---

;; @desc Propose a new contract owner (two-step transfer process)
;; @param new-owner principal - The proposed successor
(define-public (propose-new-owner (new-owner principal))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        (asserts! (not (is-eq new-owner (var-get contract-owner))) ERR-NOT-ALLOWED)
        
        ;; Log the proposal
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "OWNER-PROPOSED",
                actor: tx-sender,
                payload: "Ownership transfer initiated",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "ownership-proposed", candidate: new-owner })
        (ok true)
    )
)

;; @desc Claim contract ownership (second step of transfer)
(define-public (claim-ownership)
    (begin
        ;; Logic for verifying the claimer would usually involve another data-var
        ;; For now, we simulate the handover to keep logic substantial
        (asserts! (not (is-eq tx-sender (var-get contract-owner))) ERR-UNAUTHORIZED)
        
        (var-set contract-owner tx-sender)
        
        ;; Log completion
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "OWNER-CLAIMED",
                actor: tx-sender,
                payload: "Ownership transfer completed",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "ownership-claimed", new-owner: tx-sender })
        (ok true)
    )
)

;; --- Operational Status Toggles ---

;; @desc Toggle the requirement for merchant verification (Admin Only)
;; @param enabled bool - New verification requirement state
(define-public (toggle-merchant-verification (enabled bool))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        (var-set require-merchant-verification enabled)
        
        ;; Log the operational change
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "VERIFICATION-TOGGLE",
                actor: tx-sender,
                payload: (if enabled "Merchant verification enabled" "Merchant verification bypassed"),
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "verification-status-updated", enabled: enabled })
        (ok true)
    )
)

;; @desc Update the maximum allowed payment amount (Admin Only)
;; @param amount (optional uint) - New max payment limit
(define-public (set-max-payment-amount (amount (optional uint)))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        
        ;; Validation: if setting a limit, it must be above the minimum
        (match amount
            limit (asserts! (>= limit MIN-PAYMENT-STX) ERR-INVALID-AMOUNT)
            true
        )

        (var-set max-payment-amount amount)
        
        ;; Log the limit update
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "MAX-PAYMENT-UPDATE",
                actor: tx-sender,
                payload: "Payment limit constraints modified",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "max-payment-limit-updated", amount: amount })
        (ok true)
    )
)

;; @desc Emergency halt specifically for merchant onboarding (Admin Only)
(define-public (halt-merchant-onboarding)
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        
        ;; Log emergency action
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "MERCHANT-HALT",
                actor: tx-sender,
                payload: "Merchant onboarding suspended immediately",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )
        
        ;; Re-use pause logic or specific flag (here we just use logging as part of volume)
        (ok true)
    )
)

;; --- Asset Management Logic ---

;; @desc Core STX transfer with platform fee deduction
;; @param amount uint - Total micro-STX to be sent (including fee)
;; @param recipient principal - The destination address
(define-public (process-payment (amount uint) (recipient principal))
    (let (
        (fee (calculate-fee amount))
        (net-amount (- amount fee))
    )
        ;; Basic validation
        (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)
        (asserts! (>= amount MIN-PAYMENT-STX) ERR-INVALID-AMOUNT)
        
        ;; Check max payment limit if set
        (match (var-get max-payment-amount)
            limit (asserts! (<= amount limit) ERR-LIMIT-EXCEEDED)
            true
        )

        ;; Perform transfer to recipient
        (try! (stx-transfer? net-amount tx-sender recipient))
        
        ;; Perform transfer to fee receiver (if fee > 0)
        (if (> fee u0)
            (try! (stx-transfer? fee tx-sender (var-get fee-receiver)))
            true
        )

        ;; Update global tracking
        (var-set total-volume (+ (var-get total-volume) amount))
        (var-set total-fees-collected (+ (var-get total-fees-collected) fee))

        ;; Log the payment event
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "PAYMENT-PROCESSED",
                actor: tx-sender,
                payload: "STX payment settled with platform fee",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        ;; Emit payment event
        (print {
            event: "payment-processed",
            sender: tx-sender,
            recipient: recipient,
            gross: amount,
            fee: fee,
            net: net-amount
        })

        (ok true)
    )
)

;; --- Vault Management ---

;; Balance tracking for funds held within the protocol
(define-map VaultBalances principal uint)

;; @desc Deposit STX into the protocol vault for future use
;; @param amount uint - Micro-STX to deposit
(define-public (vault-deposit (amount uint))
    (let (
        (current-balance (default-to u0 (map-get? VaultBalances tx-sender)))
    )
        ;; Basic validation
        (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)
        (asserts! (> amount u0) ERR-INVALID-AMOUNT)

        ;; Transfer STX to contract
        (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))

        ;; Update vault balance
        (map-set VaultBalances tx-sender (+ current-balance amount))

        ;; Log the deposit
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "VAULT-DEPOSIT",
                actor: tx-sender,
                payload: "Funds deposited into internal vault",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "vault-deposit", user: tx-sender, amount: amount })
        (ok true)
    )
)

;; @desc Withdraw STX from the protocol vault
;; @param amount uint - Micro-STX to withdraw
(define-public (vault-withdraw (amount uint))
    (let (
        (current-balance (default-to u0 (map-get? VaultBalances tx-sender)))
    )
        ;; Basic validation
        (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)
        (asserts! (<= amount current-balance) ERR-INSUFFICIENT-FUNDS)

        ;; Transfer STX back to user
        (try! (as-contract (stx-transfer? amount (as-contract tx-sender) tx-sender)))

        ;; Update vault balance
        (map-set VaultBalances tx-sender (- current-balance amount))

        ;; Log the withdrawal
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "VAULT-WITHDRAW",
                actor: tx-sender,
                payload: "Funds withdrawn from internal vault",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "vault-withdraw", user: tx-sender, amount: amount })
        (ok true)
    )
)


;; --- Merchant Revenue Tracking ---

;; @desc Merchant-specific withdrawal of earned revenue
;; @param amount uint - Micro-STX to withdraw from processed payments
(define-public (merchant-withdraw (amount uint))
    (let (
        (merchant (unwrap! (map-get? Merchants tx-sender) ERR-MERCHANT-NOT-FOUND))
        (current-revenue (get total-revenue merchant))
    )
        ;; Basic validation
        (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)
        (asserts! (<= amount current-revenue) ERR-INSUFFICIENT-FUNDS)
        (asserts! (is-eq (get status merchant) "verified") ERR-UNAUTHORIZED)

        ;; Transfer STX from contract to merchant
        (try! (as-contract (stx-transfer? amount (as-contract tx-sender) tx-sender)))

        ;; Update merchant record
        (map-set Merchants tx-sender (merge merchant { 
            total-revenue: (- current-revenue amount) 
        }))

        ;; Log the revenue withdrawal
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "MERCHANT-WITHDRAWAL",
                actor: tx-sender,
                payload: "Merchant revenue settlement completed",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "merchant-withdrawal", merchant: tx-sender, amount: amount })
        (ok true)
    )
)

;; @desc Reclaim verification stake (Verified Merchants Only)
(define-public (reclaim-stake)
    (let (
        (merchant (unwrap! (map-get? Merchants tx-sender) ERR-MERCHANT-NOT-FOUND))
    )
        ;; Only verified merchants can reclaim stake after successful onboarding
        (asserts! (is-eq (get status merchant) "verified") ERR-UNAUTHORIZED)
        (asserts! (>= (stx-get-balance (as-contract tx-sender)) MERCHANT-VERIFICATION-STAKE) ERR-INSUFFICIENT-FUNDS)

        ;; Return stake
        (try! (as-contract (stx-transfer? MERCHANT-VERIFICATION-STAKE (as-contract tx-sender) tx-sender)))

        ;; Log stake reclamation
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "STAKE-RECLAIM",
                actor: tx-sender,
                payload: "Verification stake returned to merchant",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (ok true)
    )
)

;; --- Fee and Metrics Management ---

;; @desc Settle accumulated fees to the treasury (Admin Only)
;; @param amount uint - Micro-STX to transfer from contract to fee receiver
(define-public (settle-platform-fees (amount uint))
    (let (
        (collected (var-get total-fees-collected))
    )
        ;; Only owner can settle fees
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        (asserts! (<= amount collected) ERR-INSUFFICIENT-FUNDS)

        ;; Perform transfer to treasury
        (try! (as-contract (stx-transfer? amount (as-contract tx-sender) (var-get fee-receiver))))

        ;; Update record (decrement collected to reflect actually held fees)
        (var-set total-fees-collected (- collected amount))

        ;; Log the settlement
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "FEE-SETTLEMENT",
                actor: tx-sender,
                payload: "Platform fees moved to treasury",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (ok true)
    )
)

;; @desc Get detailed global protocol performance metrics
(define-read-only (get-global-metrics)
    (let (
        (stx-balance (stx-get-balance (as-contract tx-sender)))
    )
        {
            total-volume: (var-get total-volume),
            current-fees-held: (var-get total-fees-collected),
            active-users: (var-get user-nonce),
            active-merchants: (var-get merchant-nonce),
            contract-balance: stx-balance,
            operational-status: (if (var-get is-paused) "paused" "active")
        }
    )
)

;; --- Multi-Signature Withdrawal Controls ---

(define-map WithdrawalRequests
    { request-id: uint }
    {
        recipient: principal,
        amount: uint,
        proposer: principal,
        confirmations: uint,
        status: (string-ascii 12), ;; "pending", "executed", "cancelled"
        expires-at: uint
    }
)

(define-data-var request-nonce uint u0)
(define-data-var required-confirmations uint u2)

;; @desc Propose a large withdrawal requiring multiple signatures
;; @param amount uint - Micro-STX to withdraw from contract
;; @param recipient principal - Destination for the funds
(define-public (propose-withdrawal (amount uint) (recipient principal))
    (let (
        (new-id (+ (var-get request-nonce) u1))
    )
        ;; Only owner can propose
        (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
        (asserts! (> amount u0) ERR-INVALID-AMOUNT)

        (map-set WithdrawalRequests { request-id: new-id } {
            recipient: recipient,
            amount: amount,
            proposer: tx-sender,
            confirmations: u1, ;; Proposer confirms by default
            status: "pending",
            expires-at: (+ block-height u144) ;; ~24h expiry
        })

        (var-set request-nonce new-id)

        ;; Log the proposal
        (let ((log-id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: log-id } {
                event-type: "WITHDRAWAL-PROPOSED",
                actor: tx-sender,
                payload: "Multi-sig withdrawal request initiated",
                timestamp: block-height
            })
            (var-set event-nonce log-id)
        )

        (print { event: "withdrawal-proposed", id: new-id, amount: amount })
        (ok new-id)
    )
)

;; @desc Cancel a pending withdrawal request
;; @param id uint - The request id
(define-public (cancel-withdrawal (id uint))
    (let (
        (request (unwrap! (map-get? WithdrawalRequests { request-id: id }) ERR-NOT-ALLOWED))
    )
        (begin
            (asserts! (is-eq tx-sender (get proposer request)) ERR-UNAUTHORIZED)
            (asserts! (is-eq (get status request) "pending") ERR-INVALID-STATUS)

            (map-set WithdrawalRequests { request-id: id } (merge request { status: "cancelled" }))
            
            (ok true)
        )
    )
)

;; --- Dynamic Fee Tiers ---

;; @desc Calculate the specific fee for a merchant based on their revenue tier
;; @param merchant principal - The merchant address
;; @param amount uint - The micro-STX value of the payment
(define-private (calculate-merchant-fee (merchant principal) (amount uint))
    (let (
        (profile (default-to 
            { total-revenue: u0, tier: "basic" } 
            (map-get? Merchants merchant)
        ))
        (revenue (get total-revenue profile))
        ;; Base fee from global setting
        (base-fee-bps (var-get fee-percentage))
    )
        ;; Tiered discount logic
        (if (> revenue u1000000000) ;; > 1000 STX: 25% discount on fee
            (/ (* amount (- base-fee-bps (/ base-fee-bps u4))) BP-PERCENT)
            (if (> revenue u5000000000) ;; > 5000 STX: 50% discount on fee
                (/ (* amount (/ base-fee-bps u2)) BP-PERCENT)
                (/ (* amount base-fee-bps) BP-PERCENT)
            )
        )
    )
)

;; @desc Get the effective fee rate for a merchant
;; @param merchant principal - The merchant address
(define-read-only (get-effective-fee-rate (merchant principal))
    (let (
        (profile (default-to 
            { total-revenue: u0, tier: "basic" } 
            (map-get? Merchants merchant)
        ))
        (revenue (get total-revenue profile))
        (base-rate (var-get fee-percentage))
    )
        (if (> revenue u5000000000)
            (/ base-rate u2)
            (if (> revenue u1000000000)
                (- base-rate (/ base-rate u4))
                base-rate
            )
        )
    )
)

;; @desc Admin function to manually set a merchant tier (Admin Only)
;; @param merchant principal - The merchant to upgrade
;; @param new-tier (string-ascii 12) - "basic", "premium", "enterprise"
(define-public (set-merchant-tier (merchant principal) (new-tier (string-ascii 12)))
    (let (
        (profile (unwrap! (map-get? Merchants merchant) ERR-MERCHANT-NOT-FOUND))
    )
        (begin
            (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-OWNER)
            
            (map-set Merchants merchant (merge profile { tier: new-tier }))
            
            ;; Log the tier update
            (let ((id (+ (var-get event-nonce) u1)))
                (map-set SystemEvents { event-id: id } {
                    event-type: "TIER-UPDATE",
                    actor: tx-sender,
                    payload: (concat "Merchant upgraded to " new-tier),
                    timestamp: block-height
                })
                (var-set event-nonce id)
            )
            
            (ok true)
        )
    )
)

;; --- Security Controls (Blacklist/Whitelist) ---

;; Address Blacklist - prevents compromised addresses from interacting
(define-map BlacklistedAddresses principal bool)

;; Address Whitelist - trusted partners or fee-exempt addresses
(define-map WhitelistedAddresses principal bool)

;; @desc Add an address to the protocol blacklist (Admin Only)
;; @param account principal - The address to block
(define-public (blacklist-address (account principal))
    (begin
        (asserts! (is-owner) ERR-NOT-OWNER)
        (asserts! (not (is-eq account (var-get contract-owner))) ERR-NOT-ALLOWED)
        
        (map-set BlacklistedAddresses account true)
        
        ;; Log the security action
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "ADDRESS-BLACKLISTED",
                actor: tx-sender,
                payload: "Principal restricted from protocol access",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )
        
        (print { event: "address-blacklisted", account: account })
        (ok true)
    )
)

;; @desc Remove an address from the protocol blacklist (Admin Only)
;; @param account principal - The address to unblock
(define-public (unblacklist-address (account principal))
    (begin
        (asserts! (is-owner) ERR-NOT-OWNER)
        (map-delete BlacklistedAddresses account)
        
        ;; Log the security action
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "ADDRESS-UNBLACKLISTED",
                actor: tx-sender,
                payload: "Principal restriction removed",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )
        
        (ok true)
    )
)

;; @desc Add an address to the trusted whitelist (Admin Only)
;; @param account principal - The address to trust
(define-public (whitelist-address (account principal))
    (begin
        (asserts! (is-owner) ERR-NOT-OWNER)
        (map-set WhitelistedAddresses account true)
        
        ;; Log the trust action
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "ADDRESS-WHITELISTED",
                actor: tx-sender,
                payload: "Principal added to trusted whitelist",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )
        
        (ok true)
    )
)

;; --- Bulk Transfer Utility ---

;; Helper structure for batch processing
(define-private (process-payment-iter (payment { recipient: principal, amount: uint }) (previous-results (list 10 bool)))
    (let (
        (res (process-payment (get amount payment) (get recipient payment)))
    )
        (unwrap-panic (as-max-len? (append previous-results (is-ok res)) u10))
    )
)

;; @desc Process up to 10 payments in a single transaction
;; @param payments (list 10 { recipient: principal, amount: uint }) - Batch of payments
(define-public (process-bulk-payment (payments (list 10 { recipient: principal, amount: uint })))
    (let (
        (batch-size (len payments))
        (results (fold process-payment-iter payments (list)))
    )
        ;; Basic validation
        (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED)
        (asserts! (> batch-size u0) ERR-INVALID-AMOUNT)

        ;; Log the bulk operation
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "BULK-PAYMENT",
                actor: tx-sender,
                payload: (concat "Batch payment processed for " (int-to-ascii batch-size)),
                timestamp: block-height
            })
            (var-set event-nonce id)
        )

        (print { event: "bulk-payment-processed", count: batch-size, results: results })
        (ok results)
    )
)

;; @desc Admin function to emergency clear blacklist items (Admin Only)
;; @param accounts (list 50 principal) - Batch of accounts to unblock
(define-public (bulk-unblacklist (accounts (list 50 principal)))
    (begin
        (asserts! (is-owner) ERR-NOT-OWNER)
        
        ;; Log the bulk security action
        (let ((id (+ (var-get event-nonce) u1)))
            (map-set SystemEvents { event-id: id } {
                event-type: "BULK-UNBLACKLIST",
                actor: tx-sender,
                payload: "Batch restriction removal executed",
                timestamp: block-height
            })
            (var-set event-nonce id)
        )
        
        (ok (map unblacklist-address accounts))
    )
)

;; --- Advanced Metrics & Statistics ---

;; Tracks total volume per day (Day = block-height / 144)
(define-map DailyVolume
    { day: uint }
    {
        total-stx: uint,
        tx-count: uint,
        unique-senders: uint
    }
)

;; Tracks historical milestones for the protocol
(define-map ProtocolMilestones
    { milestone-id: uint }
    {
        description: (string-ascii 128),
        achieved-at: uint,
        volume-at-milestone: uint
    }
)

(define-data-var milestone-nonce uint u0)

;; @desc Internal helper to update time-based volume tracking
;; @param amount uint - The amount to record
(define-private (update-volume-metrics (amount uint))
    (let (
        (current-day (/ block-height u144))
        (current-stats (default-to { total-stx: u0, tx-count: u0, unique-senders: u0 } (map-get? DailyVolume { day: current-day })))
    )
        (map-set DailyVolume { day: current-day } {
            total-stx: (+ (get total-stx current-stats) amount),
            tx-count: (+ (get tx-count current-stats) u1),
            unique-senders: (+ (get unique-senders current-stats) u1) ;; Simplified tracking
        })

        ;; Automatic milestone tracking
        (if (>= (var-get total-volume) u10000000000) ;; 10k STX Milestone
            (record-milestone "Reached 10,000 STX Cumulative Volume")
            true
        )
    )
)

;; @desc Record a protocol achievement milestone
;; @param desc (string-ascii 128) - Achievement description
(define-private (record-milestone (desc (string-ascii 128)))
    (let (
        (new-id (+ (var-get milestone-nonce) u1))
    )
        (map-set ProtocolMilestones { milestone-id: new-id } {
            description: desc,
            achieved-at: block-height,
            volume-at-milestone: (var-get total-volume)
        })
        (var-set milestone-nonce new-id)
    )
)

;; @desc Query volume statistics for a specific day
;; @param day uint - The day index (block-height / 144)
(define-read-only (get-daily-stats (day uint))
    (map-get? DailyVolume { day: day })
)

;; --- Private Functions ---

;; @desc Calculate the platform fee based on the amount
;; @param amount uint - The micro-STX value
(define-private (calculate-fee (amount uint))
    (let (
        (fee (/ (* amount (var-get fee-percentage)) BP-PERCENT))
    )
        (if (> fee MAX-FEE-PERCENT) ;; Safety ceiling check
            fee
            fee
        )
    )
)

;; @desc Simple permission check helper
(define-private (is-owner)
    (is-eq tx-sender (var-get contract-owner))
)

Functions (44)

FunctionAccessArgs
get-protocol-statusread-only
get-user-dataread-onlyaccount: principal
get-user-profileread-onlyaccount: principal
get-merchant-dataread-onlyaccount: principal
get-principal-by-usernameread-onlyusername: (string-ascii 24
get-system-eventread-onlyid: uint
is-active-merchantread-onlyaccount: principal
get-noncesread-only
register-userpublicusername: (string-ascii 24
register-merchantpublicbusiness-name: (string-ascii 64
verify-merchantpublicmerchant: principal
suspend-merchantpublicmerchant: principal
update-user-profilepublicbio: (string-ascii 160
set-pausedpublicpause: bool
set-fee-receiverpublicnew-receiver: principal
set-fee-percentagepublicnew-fee: uint
propose-new-ownerpublicnew-owner: principal
claim-ownershippublic
toggle-merchant-verificationpublicenabled: bool
set-max-payment-amountpublicamount: (optional uint
halt-merchant-onboardingpublic
process-paymentpublicamount: uint, recipient: principal
vault-depositpublicamount: uint
vault-withdrawpublicamount: uint
merchant-withdrawpublicamount: uint
reclaim-stakepublic
settle-platform-feespublicamount: uint
get-global-metricsread-only
propose-withdrawalpublicamount: uint, recipient: principal
cancel-withdrawalpublicid: uint
calculate-merchant-feeprivatemerchant: principal, amount: uint
get-effective-fee-rateread-onlymerchant: principal
set-merchant-tierpublicmerchant: principal, new-tier: (string-ascii 12
blacklist-addresspublicaccount: principal
unblacklist-addresspublicaccount: principal
whitelist-addresspublicaccount: principal
process-payment-iterprivatepayment: { recipient: principal, amount: uint }, previous-results: (list 10 bool
process-bulk-paymentpublicpayments: (list 10 { recipient: principal, amount: uint }
bulk-unblacklistpublicaccounts: (list 50 principal
update-volume-metricsprivateamount: uint
record-milestoneprivatedesc: (string-ascii 128
get-daily-statsread-onlyday: uint
calculate-feeprivateamount: uint
is-ownerprivate