Source Code

;; =============================================
;; StacksBatchPay - STX Only Batch Payroll Vault
;; 0.5% fee | Complete mirror of BaseBatchPay
;; =============================================

(define-data-var treasury principal 'SPGDS0Y17973EN5TCHNHGJJ9B31XWQ5YX8A36C9B)   

(define-constant FEE_BPS u50)        ;; 0.5%
(define-constant FEE_DENOM u10000)
(define-constant MAX_BATCH u60)      ;; safe & cheap

(define-constant ERR_LENGTH (err u101))
(define-constant ERR_AMOUNT (err u102))
(define-constant ERR_RECIPIENT (err u103))

;; payer => employee => active
(define-map employees {payer: principal, employee: principal} bool)

;; History (payer => batch-id => batch details)
(define-map batch-counter principal uint)

(define-map payment-history 
  {payer: principal, batch-id: uint}
  {
    recipients: (list 60 principal),
    amounts: (list 60 uint),
    names: (list 60 (string-utf8 64)),
    timestamp: uint,
    total: uint,
    fee: uint
  })

;; ====================== HELPERS ======================
(define-private (get-to (p {to: principal, amount: uint, name: (string-utf8 64)})) (get to p))
(define-private (get-amount (p {to: principal, amount: uint, name: (string-utf8 64)})) (get amount p))
(define-private (get-name (p {to: principal, amount: uint, name: (string-utf8 64)})) (get name p))

;; Validate + auto-add employee
(define-private (validate-and-add (p {to: principal, amount: uint, name: (string-utf8 64)}) (acc (response bool uint)))
  (match acc
    prev
      (let ((to (get to p))
            (amt (get amount p)))
        (asserts! (not (is-eq to tx-sender)) ERR_RECIPIENT)
        (asserts! (> amt u0) ERR_AMOUNT)
        (map-set employees {payer: tx-sender, employee: to} true)
        (ok true))
    e (err e)))

;; Send one payment
(define-private (send-payment (p {to: principal, amount: uint, name: (string-utf8 64)}) (acc (response bool uint)))
  (match acc
    prev (stx-transfer? (get amount p) tx-sender (get to p))
    e (err e)))

;; ====================== EMPLOYEE MGMT ======================
(define-public (add-employee (emp principal))
  (ok (map-set employees {payer: tx-sender, employee: emp} true)))

(define-public (remove-employee (emp principal))
  (ok (map-delete employees {payer: tx-sender, employee: emp})))

(define-read-only (is-employee (emp principal))
  (default-to false (map-get? employees {payer: tx-sender, employee: emp})))

;; ====================== BATCH PAY ======================
(define-public (batch-pay 
    (payments (list 60 {to: principal, amount: uint, name: (string-utf8 64)})))
  
  (let (
    (payer tx-sender)
    (total (fold + (map get-amount payments) u0))
    (fee (/ (* total FEE_BPS) FEE_DENOM))
    (counter (default-to u0 (map-get? batch-counter payer)))
    (new-id (+ counter u1))
  )
    (asserts! (and (> (len payments) u0) (<= (len payments) MAX_BATCH)) ERR_LENGTH)

    ;; Auto-add employees + validation
    (try! (fold validate-and-add payments (ok true)))

    ;; 0.5% fee to your treasury
    (try! (stx-transfer? fee payer (var-get treasury)))

    ;; Pay everyone in one tx
    (try! (fold send-payment payments (ok true)))
;; Save to on-chain history
(map-set payment-history {payer: payer, batch-id: new-id}
  {
    recipients: (map get-to payments),
    amounts: (map get-amount payments),
    names: (map get-name payments),
    timestamp: burn-block-height,   
    total: total,
    fee: fee
  })

    (map-set batch-counter payer new-id)

    ;; Event (frontend/indexer friendly)
    (print {
      event: "batch-paid-stx",
      payer: payer,
      batch-id: new-id,
      recipients: (map get-to payments),
      amounts: (map get-amount payments),
      names: (map get-name payments),
      total: total,
      fee: fee
    })

    (ok true)
  ))

;; ====================== READ FUNCTIONS ======================
(define-read-only (get-batch-count (payer principal))
  (default-to u0 (map-get? batch-counter payer)))

(define-read-only (get-batch (payer principal) (batch-id uint))
  (map-get? payment-history {payer: payer, batch-id: batch-id}))

(define-read-only (get-treasury)
  (var-get treasury))

;; (Optional) Change treasury - only current treasury can call
(define-public (set-treasury (new-treasury principal))
  (begin
    (asserts! (is-eq tx-sender (var-get treasury)) (err u403))
    (ok (var-set treasury new-treasury))
  ))

Functions (7)

FunctionAccessArgs
add-employeepublicemp: principal
remove-employeepublicemp: principal
is-employeeread-onlyemp: principal
get-batch-countread-onlypayer: principal
get-batchread-onlypayer: principal, batch-id: uint
get-treasuryread-only
set-treasurypublicnew-treasury: principal