Source Code

;; Multi-signature vault contract
;; Implements a multisig wallet for managing STX and SIP-010 tokens
;;
;; Clarity Version: 3 (local dev) / 4 (planned for mainnet)
;; Clarity 4 Features: See CLARITY4-IMPLEMENTATION-PLAN.md for planned features:
;;   - restrict-assets? (Issue #7) - Post-conditions for token transfers
;;   - stacks-block-time (Issue #15) - Transaction expiration
;;   - contract-hash? (Issue #7) - Token contract verification
   ;;   - to-ascii? (Issues #2, #6, #7) - Enhanced logging

;; SIP-010 trait import - will be used for token transfers
;; Note: Trait syntax will be fixed when implementing Issue #7 (token transfers)
;; (use-trait sip-010-trait-ft-standard 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard)

;; ============================================
;; ========== temp test ==========
;; Temporary counter state for testing
(define-data-var counter uint u0)

(define-public (increment-counter)
    (begin
        (var-set counter (+ (var-get counter) u1))
        (ok (var-get counter))
    )
)

(define-public (decrement-counter)
    (begin
        (if (> (var-get counter) u0)
            (var-set counter (- (var-get counter) u1))
            (var-set counter u0)
        )
        (ok (var-get counter))
    )
)

(define-read-only (get-counter)
    (ok (var-get counter))
)
;; ========== temp test ==========
;; ============================================
;; Constants
;; ============================================
(define-constant CONTRACT_OWNER 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM)
(define-constant MAX_SIGNERS u100)
(define-constant MIN_SIGNATURES_REQUIRED u1)

;; ============================================
;; Error Constants
;; ============================================
(define-constant ERR_OWNER_ONLY (err u1))
(define-constant ERR_ALREADY_INITIALIZED (err u2))
(define-constant ERR_TOO_MANY_SIGNERS (err u3))
(define-constant ERR_INVALID_THRESHOLD (err u4))
(define-constant ERR_NOT_INITIALIZED (err u5))
(define-constant ERR_NOT_SIGNER (err u6))
(define-constant ERR_INVALID_AMOUNT (err u7))
(define-constant ERR_INVALID_TXN_TYPE (err u8))
(define-constant ERR_INVALID_TXN_ID (err u9))
(define-constant ERR_TXN_ALREADY_EXECUTED (err u10))
(define-constant ERR_INSUFFICIENT_SIGNATURES (err u11))
(define-constant ERR_INVALID_SIGNATURE (err u12))
(define-constant ERR_INVALID_TOKEN (err u13))

;; ============================================
;; Data Variables
;; ============================================
(define-data-var initialized bool false)
(define-data-var signers (list 100 principal) (list))
(define-data-var threshold uint u0)
(define-data-var txn-id uint u0)

;; ============================================
;; Maps
;; ============================================
(define-map transactions
  uint
  {
    type: uint,
    amount: uint,
    recipient: principal,
    token: (optional principal),
    executed: bool
  }
)

(define-map txn-signers
  (tuple (txn-id uint) (signer principal))
  bool
)

;; ============================================
;; Public Functions
;; ============================================

;; Issue #1: Initialize the multisig contract
(define-public (initialize
    (signers-list (list 100 principal))
    (threshold-value uint)
)
    (begin
        ;; Verify contract owner
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
        ;; Check initialization status (must be false)
        (asserts! (not (var-get initialized)) ERR_ALREADY_INITIALIZED)
        ;; Validate signers list length (max 100 using MAX_SIGNERS)
        (let ((signers-count (len signers-list)))
            (asserts! (<= signers-count MAX_SIGNERS) ERR_TOO_MANY_SIGNERS)
            ;; Validate threshold (min 1 using MIN_SIGNATURES_REQUIRED, max should be <= signers count)
            (asserts! (>= threshold-value MIN_SIGNATURES_REQUIRED) ERR_INVALID_THRESHOLD)
            (asserts! (<= threshold-value signers-count) ERR_INVALID_THRESHOLD)
            ;; Set signers and threshold in storage
            (var-set signers signers-list)
            (var-set threshold threshold-value)
            ;; Mark contract as initialized (set initialized to true)
            (var-set initialized true)
            (ok true)
        )
    )
)

;; Issue #2: Submit a new transaction proposal
(define-public (submit-txn
    (txn-type uint)
    (amount uint)
    (recipient principal)
    (token (optional principal))
)
    (begin
        ;; Verify contract is initialized
        (asserts! (var-get initialized) ERR_NOT_INITIALIZED)
        ;; Verify caller is a signer
        (let ((caller tx-sender))
            (asserts! (is-some (index-of (var-get signers) caller)) ERR_NOT_SIGNER)
            ;; Validate amount > 0
            (asserts! (> amount u0) ERR_INVALID_AMOUNT)
            ;; Validate transaction type (0 = STX transfer, 1 = SIP-010 transfer)
            (asserts! (or (is-eq txn-type u0) (is-eq txn-type u1)) ERR_INVALID_TXN_TYPE)
            ;; For type 1 (SIP-010), validate that token contract is provided
            (if (is-eq txn-type u1)
                (asserts! (is-some token) ERR_INVALID_TOKEN)
                true
            )
            ;; Get current txn-id from storage
            (let ((current-id (var-get txn-id)))
                ;; Store transaction in transactions map
                (map-set transactions current-id {
                    type: txn-type,
                    amount: amount,
                    recipient: recipient,
                    token: token,
                    executed: false
                })
                ;; Increment txn-id by 1
                (var-set txn-id (+ current-id u1))
                ;; Print transaction details for logging
                (print {txn-id: current-id, type: txn-type, amount: amount, recipient: recipient, token: token})
                (ok current-id)
            )
        )
    )
)

;; Issue #3: Hash a stored transaction for signature verification
(define-read-only (hash-txn (target-id uint))
    (let (
        (txn (unwrap! (map-get? transactions target-id) ERR_INVALID_TXN_ID))
        (txn-buff (unwrap! (to-consensus-buff? txn) ERR_INVALID_TXN_ID))
    )
        (ok (sha256 txn-buff))
    )
)

;; Issue #4: Extract and verify signer from signature
(define-read-only (extract-signer
    (message-hash (buff 32))
    (signature (buff 65))
)
    (match (secp256k1-recover? message-hash signature)
        pubkey
            (match (principal-of? pubkey)
                signer
                    (if (is-some (index-of (var-get signers) signer))
                        (ok signer)
                        ERR_INVALID_SIGNATURE
                    )
                none ERR_INVALID_SIGNATURE
            )
        err-code ERR_INVALID_SIGNATURE
    )
)

Functions (7)

FunctionAccessArgs
increment-counterpublic
decrement-counterpublic
get-counterread-only
initializepublicsigners-list: (list 100 principal
submit-txnpublictxn-type: uint, amount: uint, recipient: principal, token: (optional principal
hash-txnread-onlytarget-id: uint
extract-signerread-onlymessage-hash: (buff 32