Source Code

;; Decentralized Salary Streamer & Payroll
;; Enable real-time salary payments and automated payroll on-chain
;; Built for Stacks Builder Challenge Week 3

(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_NOT_AUTHORIZED (err u401))
(define-constant ERR_STREAM_NOT_FOUND (err u404))
(define-constant ERR_INSUFFICIENT_FUNDS (err u403))

(define-map streams
    uint
    {
        sender: principal,
        recipient: principal,
        amount-total: uint,
        amount-withdrawn: uint,
        start-block: uint,
        stop-block: uint
    }
)

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

;; Create a new payment stream
(define-public (create-stream (recipient principal) (amount uint) (duration uint))
    (let
        (
            (stream-id (+ (var-get stream-nonce) u1))
        )
        (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
        
        (map-set streams stream-id {
            sender: tx-sender,
            recipient: recipient,
            amount-total: amount,
            amount-withdrawn: u0,
            start-block: stacks-block-height,
            stop-block: (+ stacks-block-height duration)
        })
        
        (var-set stream-nonce stream-id)
        (print {event: "stream-created", id: stream-id, recipient: recipient})
        (ok stream-id)
    )
)

;; Withdraw available salary from stream
(define-public (withdraw-from-stream (stream-id uint))
    (let
        (
            (stream (unwrap! (map-get? streams stream-id) ERR_STREAM_NOT_FOUND))
            (available (calculate-available stream-id))
        )
        (asserts! (is-eq (get recipient stream) tx-sender) ERR_NOT_AUTHORIZED)
        (asserts! (> available u0) ERR_INSUFFICIENT_FUNDS)
        
        (try! (as-contract (stx-transfer? available (as-contract tx-sender) tx-sender)))
        
        (map-set streams stream-id (merge stream {
            amount-withdrawn: (+ (get amount-withdrawn stream) available)
        }))
        
        (ok available)
    )
)

;; Calculate available funds to withdraw based on block progression
(define-read-only (calculate-available (stream-id uint))
    (let
        (
            (stream (unwrap! (map-get? streams stream-id) u0))
            (current-block stacks-block-height)
        )
        (if (>= current-block (get stop-block stream))
            (- (get amount-total stream) (get amount-withdrawn stream))
            (let
                (
                    (elapsed (- current-block (get start-block stream)))
                    (duration (- (get stop-block stream) (get start-block stream)))
                    (total-vested (/ (* (get amount-total stream) elapsed) duration))
                )
                (- total-vested (get amount-withdrawn stream))
            )
        )
    )
)

Functions (3)

FunctionAccessArgs
calculate-availableread-onlystream-id: uint
create-streampublicrecipient: principal, amount: uint, duration: uint
withdraw-from-streampublicstream-id: uint