Source Code

;; Token Stream Contract
;; Handles milestone-based token streaming for startup funding

(define-constant ERR-UNAUTHORIZED (err u2001))
(define-constant ERR-STREAM-NOT-FOUND (err u2002))
(define-constant ERR-INVALID-AMOUNT (err u2003))
(define-constant ERR-STREAM-ACTIVE (err u2004))
(define-constant ERR-STREAM-PAUSED (err u2005))

;; Stream status: 0 = pending, 1 = active, 2 = paused, 3 = completed, 4 = cancelled
(define-map streams
    { stream-id: uint }
    {
        sender: principal,
        recipient: principal,
        token-contract: principal,
        total-amount: uint,
        released-amount: uint,
        start-time: uint,
        end-time: uint,
        milestone-trigger: uint, ;; startup-id + milestone-index encoded
        status: uint,
        created-at: uint
    }
)

(define-data-var next-stream-id uint u1)

;; Create a new token stream tied to a milestone
(define-public (create-stream
    (recipient principal)
    (token-contract principal)
    (total-amount uint)
    (start-time uint)
    (end-time uint)
    (milestone-trigger uint)
)
    (let
        (
            (stream-id (var-get next-stream-id))
            (timestamp (unwrap! (get-block-info? time u0) ERR-UNAUTHORIZED))
        )
        (asserts! (> total-amount u0) ERR-INVALID-AMOUNT)
        (asserts! (> end-time start-time) ERR-INVALID-AMOUNT)
        (begin
            (map-set streams
                { stream-id: stream-id }
                {
                    sender: tx-sender,
                    recipient: recipient,
                    token-contract: token-contract,
                    total-amount: total-amount,
                    released-amount: u0,
                    start-time: start-time,
                    end-time: end-time,
                    milestone-trigger: milestone-trigger,
                    status: u0,
                    created-at: timestamp
                }
            )
            (var-set next-stream-id (+ stream-id u1))
            (ok stream-id)
        )
    )
)

;; Activate stream when milestone is verified
(define-public (activate-stream (stream-id uint))
    (let
        (
            (stream (unwrap! (map-get? streams { stream-id: stream-id }) ERR-STREAM-NOT-FOUND))
        )
        (asserts! (is-eq (get status stream) u0) ERR-INVALID-AMOUNT)
        (ok (map-set streams
            { stream-id: stream-id }
            {
                sender: (get sender stream),
                recipient: (get recipient stream),
                token-contract: (get token-contract stream),
                total-amount: (get total-amount stream),
                released-amount: (get released-amount stream),
                start-time: (get start-time stream),
                end-time: (get end-time stream),
                milestone-trigger: (get milestone-trigger stream),
                status: u1,
                created-at: (get created-at stream)
            }
        ))
    )
)

;; Calculate and release tokens based on time elapsed
(define-public (release-tokens (stream-id uint))
    (let
        (
            (stream (unwrap! (map-get? streams { stream-id: stream-id }) ERR-STREAM-NOT-FOUND))
            (current-time (unwrap! (get-block-info? time u0) ERR-UNAUTHORIZED))
            (elapsed-time (- current-time (get start-time stream)))
            (total-duration (- (get end-time stream) (get start-time stream)))
        )
        (asserts! (is-eq (get status stream) u1) ERR-STREAM-PAUSED)
        (asserts! (>= current-time (get start-time stream)) ERR-INVALID-AMOUNT)
        (let
            (
                (release-amount (if
                    (>= current-time (get end-time stream))
                    (- (get total-amount stream) (get released-amount stream))
                    (/ (* (get total-amount stream) elapsed-time) total-duration)
                ))
                (new-released (+ (get released-amount stream) release-amount))
                (final-status (if
                    (>= new-released (get total-amount stream))
                    u3
                    (get status stream)
                ))
            )
            (ok (map-set streams
                { stream-id: stream-id }
                {
                    sender: (get sender stream),
                    recipient: (get recipient stream),
                    token-contract: (get token-contract stream),
                    total-amount: (get total-amount stream),
                    released-amount: new-released,
                    start-time: (get start-time stream),
                    end-time: (get end-time stream),
                    milestone-trigger: (get milestone-trigger stream),
                    status: final-status,
                    created-at: (get created-at stream)
                }
            ))
        )
    )
)

;; Get stream information
(define-read-only (get-stream (stream-id uint))
    (map-get? streams { stream-id: stream-id })
)

;; Pause stream (sender only)
(define-public (pause-stream (stream-id uint))
    (let
        (
            (stream (unwrap! (map-get? streams { stream-id: stream-id }) ERR-STREAM-NOT-FOUND))
        )
        (asserts! (is-eq tx-sender (get sender stream)) ERR-UNAUTHORIZED)
        (asserts! (is-eq (get status stream) u1) ERR-STREAM-ACTIVE)
        (ok (map-set streams
            { stream-id: stream-id }
            {
                sender: (get sender stream),
                recipient: (get recipient stream),
                token-contract: (get token-contract stream),
                total-amount: (get total-amount stream),
                released-amount: (get released-amount stream),
                start-time: (get start-time stream),
                end-time: (get end-time stream),
                milestone-trigger: (get milestone-trigger stream),
                status: u2,
                created-at: (get created-at stream)
            }
        ))
    )
)

;; Cancel stream (sender only, before activation)
(define-public (cancel-stream (stream-id uint))
    (let
        (
            (stream (unwrap! (map-get? streams { stream-id: stream-id }) ERR-STREAM-NOT-FOUND))
        )
        (asserts! (is-eq tx-sender (get sender stream)) ERR-UNAUTHORIZED)
        (asserts! (is-eq (get status stream) u0) ERR-INVALID-AMOUNT)
        (ok (map-set streams
            { stream-id: stream-id }
            {
                sender: (get sender stream),
                recipient: (get recipient stream),
                token-contract: (get token-contract stream),
                total-amount: (get total-amount stream),
                released-amount: (get released-amount stream),
                start-time: (get start-time stream),
                end-time: (get end-time stream),
                milestone-trigger: (get milestone-trigger stream),
                status: u4,
                created-at: (get created-at stream)
            }
        ))
    )
)

Functions (5)

FunctionAccessArgs
activate-streampublicstream-id: uint
release-tokenspublicstream-id: uint
get-streamread-onlystream-id: uint
pause-streampublicstream-id: uint
cancel-streampublicstream-id: uint