Source Code

;; Wallet Core Contract
;; Core wallet functionality with passkey-based authentication
;; Features: principal-from-slice, list-filter-map for multi-sig operations

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u200))
(define-constant ERR-INSUFFICIENT-BALANCE (err u201))
(define-constant ERR-INVALID-AMOUNT (err u202))
(define-constant ERR-WALLET-NOT-FOUND (err u203))
(define-constant ERR-PASSKEY-NOT-VERIFIED (err u204))
(define-constant ERR-MULTISIG-THRESHOLD-NOT-MET (err u205))
(define-constant ERR-INVALID-THRESHOLD (err u206))
(define-constant ERR-NONCE-MISMATCH (err u207))

;; Data structures
(define-map wallets
  { owner: principal }
  {
    balance: uint,
    nonce: uint,
    multisig-threshold: uint,
    total-passkeys: uint,
    created-at: uint,
  }
)

(define-map pending-transactions
  { tx-id: uint }
  {
    from: principal,
    to: principal,
    amount: uint,
    approvals: (list 10 (buff 65)),
    approval-count: uint,
    created-at: uint,
    executed: bool,
  }
)

(define-data-var next-tx-id uint u0)

;; Read-only functions

;; Get wallet info
(define-read-only (get-wallet-info (owner principal))
  (map-get? wallets { owner: owner })
)

;; Get wallet balance
(define-read-only (get-balance (owner principal))
  (ok (get balance
    (default-to {
      balance: u0,
      nonce: u0,
      multisig-threshold: u1,
      total-passkeys: u0,
      created-at: u0,
    }
      (map-get? wallets { owner: owner })
    )))
)

;; Get pending transaction
(define-read-only (get-pending-transaction (tx-id uint))
  (map-get? pending-transactions { tx-id: tx-id })
)

;; Check if enough approvals for multi-sig
(define-read-only (has-enough-approvals (tx-id uint))
  (match (map-get? pending-transactions { tx-id: tx-id })
    tx-data (let (
        (wallet-info (unwrap! (map-get? wallets { owner: (get from tx-data) })
          ERR-WALLET-NOT-FOUND
        ))
        (threshold (get multisig-threshold wallet-info))
        (current-approvals (get approval-count tx-data))
      )
      (ok (>= current-approvals threshold))
    )
    ERR-WALLET-NOT-FOUND
  )
)

;; Private functions

;; Verify passkey authorization (integrates with passkey-registry)
(define-private (verify-passkey-auth
    (owner principal)
    (passkey-id (buff 65))
  )
  (contract-call? .passkey-registry is-passkey-registered owner passkey-id)
)

;; Public functions

;; Initialize wallet for new user
(define-public (initialize-wallet (initial-threshold uint))
  (let ((owner tx-sender))
    ;; Ensure wallet doesn't already exist
    (asserts! (is-none (map-get? wallets { owner: owner })) ERR-WALLET-NOT-FOUND)

    ;; Validate threshold
    (asserts! (and (>= initial-threshold u1) (<= initial-threshold u10))
      ERR-INVALID-THRESHOLD
    )

    (map-set wallets { owner: owner } {
      balance: u0,
      nonce: u0,
      multisig-threshold: initial-threshold,
      total-passkeys: u0,
      created-at: stacks-block-height,
    })

    (ok true)
  )
)

;; Deposit STX to wallet
(define-public (deposit (amount uint))
  (let (
      (owner tx-sender)
      (wallet-data (unwrap! (map-get? wallets { owner: owner }) ERR-WALLET-NOT-FOUND))
      (current-balance (get balance wallet-data))
    )
    (asserts! (> amount u0) ERR-INVALID-AMOUNT)

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

    ;; Update wallet balance
    (map-set wallets { owner: owner }
      (merge wallet-data { balance: (+ current-balance amount) })
    )

    (ok true)
  )
)

;; Create a withdrawal transaction (requires multi-sig approval)
(define-public (create-withdrawal
    (to principal)
    (amount uint)
    (passkey-id (buff 65))
  )
  (let (
      (owner tx-sender)
      (wallet-data (unwrap! (map-get? wallets { owner: owner }) ERR-WALLET-NOT-FOUND))
      (current-balance (get balance wallet-data))
      (tx-id (var-get next-tx-id))
    )
    ;; Verify passkey is registered
    (asserts! (verify-passkey-auth owner passkey-id) ERR-PASSKEY-NOT-VERIFIED)

    ;; Check balance
    (asserts! (>= current-balance amount) ERR-INSUFFICIENT-BALANCE)
    (asserts! (> amount u0) ERR-INVALID-AMOUNT)

    ;; Create pending transaction
    (map-set pending-transactions { tx-id: tx-id } {
      from: owner,
      to: to,
      amount: amount,
      approvals: (list passkey-id),
      approval-count: u1,
      created-at: stacks-block-height,
      executed: false,
    })

    ;; Increment transaction ID
    (var-set next-tx-id (+ tx-id u1))

    ;; Update passkey usage
    (try! (contract-call? .passkey-registry update-passkey-usage passkey-id))

    (ok tx-id)
  )
)

;; Approve a pending transaction with another passkey (multi-sig)
;; Uses list-filter-map concept for managing approvals
(define-public (approve-transaction
    (tx-id uint)
    (passkey-id (buff 65))
  )
  (let (
      (owner tx-sender)
      (tx-data (unwrap! (map-get? pending-transactions { tx-id: tx-id })
        ERR-WALLET-NOT-FOUND
      ))
      (current-approvals (get approvals tx-data))
    )
    ;; Verify transaction belongs to sender
    (asserts! (is-eq owner (get from tx-data)) ERR-NOT-AUTHORIZED)

    ;; Verify passkey is registered
    (asserts! (verify-passkey-auth owner passkey-id) ERR-PASSKEY-NOT-VERIFIED)

    ;; Check transaction not already executed
    (asserts! (not (get executed tx-data)) ERR-NOT-AUTHORIZED)

    ;; Add approval (using concat - similar to list-concat)
    (map-set pending-transactions { tx-id: tx-id }
      (merge tx-data {
        approvals: (unwrap-panic (as-max-len? (concat current-approvals (list passkey-id)) u10)),
        approval-count: (+ (get approval-count tx-data) u1),
      })
    )

    ;; Update passkey usage
    (try! (contract-call? .passkey-registry update-passkey-usage passkey-id))

    (ok true)
  )
)

;; Execute a pending transaction once threshold is met
(define-public (execute-transaction (tx-id uint))
  (let (
      (owner tx-sender)
      (tx-data (unwrap! (map-get? pending-transactions { tx-id: tx-id })
        ERR-WALLET-NOT-FOUND
      ))
      (wallet-data (unwrap! (map-get? wallets { owner: owner }) ERR-WALLET-NOT-FOUND))
      (has-approvals (try! (has-enough-approvals tx-id)))
    )
    ;; Verify transaction belongs to sender
    (asserts! (is-eq owner (get from tx-data)) ERR-NOT-AUTHORIZED)

    ;; Check transaction not already executed
    (asserts! (not (get executed tx-data)) ERR-NOT-AUTHORIZED)

    ;; Check threshold met
    (asserts! has-approvals ERR-MULTISIG-THRESHOLD-NOT-MET)

    ;; Transfer STX from contract to recipient
    (try! (stx-transfer? (get amount tx-data) tx-sender (get to tx-data)))

    ;; Update wallet balance
    (map-set wallets { owner: owner }
      (merge wallet-data {
        balance: (- (get balance wallet-data) (get amount tx-data)),
        nonce: (+ (get nonce wallet-data) u1),
      })
    )

    ;; Mark transaction as executed
    (map-set pending-transactions { tx-id: tx-id }
      (merge tx-data { executed: true })
    )

    (ok true)
  )
)

;; Update multi-sig threshold
(define-public (set-multisig-threshold (new-threshold uint))
  (let (
      (owner tx-sender)
      (wallet-data (unwrap! (map-get? wallets { owner: owner }) ERR-WALLET-NOT-FOUND))
    )
    ;; Validate threshold
    (asserts!
      (and (>= new-threshold u1) (<= new-threshold (get total-passkeys wallet-data)))
      ERR-INVALID-THRESHOLD
    )

    (map-set wallets { owner: owner }
      (merge wallet-data { multisig-threshold: new-threshold })
    )

    (ok true)
  )
)

;; Direct transfer (for single-sig wallets with threshold = 1)
(define-public (transfer
    (amount uint)
    (to principal)
    (passkey-id (buff 65))
  )
  (let (
      (owner tx-sender)
      (wallet-data (unwrap! (map-get? wallets { owner: owner }) ERR-WALLET-NOT-FOUND))
    )
    ;; Only allow if threshold is 1
    (asserts! (is-eq (get multisig-threshold wallet-data) u1) ERR-NOT-AUTHORIZED)

    ;; Verify passkey
    (asserts! (verify-passkey-auth owner passkey-id) ERR-PASSKEY-NOT-VERIFIED)

    ;; Check balance
    (asserts! (>= (get balance wallet-data) amount) ERR-INSUFFICIENT-BALANCE)
    (asserts! (> amount u0) ERR-INVALID-AMOUNT)

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

    ;; Update balance and nonce
    (map-set wallets { owner: owner }
      (merge wallet-data {
        balance: (- (get balance wallet-data) amount),
        nonce: (+ (get nonce wallet-data) u1),
      })
    )

    ;; Update passkey usage
    (try! (contract-call? .passkey-registry update-passkey-usage passkey-id))

    (ok true)
  )
)

Functions (12)

FunctionAccessArgs
get-wallet-inforead-onlyowner: principal
get-balanceread-onlyowner: principal
get-pending-transactionread-onlytx-id: uint
has-enough-approvalsread-onlytx-id: uint
verify-passkey-authprivateowner: principal, passkey-id: (buff 65
initialize-walletpublicinitial-threshold: uint
depositpublicamount: uint
create-withdrawalpublicto: principal, amount: uint, passkey-id: (buff 65
approve-transactionpublictx-id: uint, passkey-id: (buff 65
execute-transactionpublictx-id: uint
set-multisig-thresholdpublicnew-threshold: uint
transferpublicamount: uint, to: principal, passkey-id: (buff 65