Clarity Patterns

23 patterns extracted from production Stacks contracts

sBTC Token SM3VDX...sbtc-token

#1 — Dual-Token Lock/Unlock

token state-machine

Instead of tracking locked amounts in a map, mint a separate sbtc-token-locked fungible token. Lock = burn liquid + mint locked. Elegant state machine.

(define-fungible-token sbtc-token)
(define-fungible-token sbtc-token-locked)

(define-public (protocol-lock (amount uint) (owner principal) ...)
  (begin
    (try! (ft-burn? sbtc-token amount owner))
    (ft-mint? sbtc-token-locked amount owner)))

#2 — Authorization: tx-sender OR contract-caller

auth composability
(asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) ERR_NOT_OWNER)

Allows both direct calls and contract-mediated transfers. Critical for composability — other contracts can move tokens on behalf of users.

#3 — Protocol Registry

auth access-control
(try! (contract-call? .sbtc-registry is-protocol-caller contract-flag contract-caller))

Centralized access control via a registry contract. contract-flag (buff 1) = granular permissions per protocol contract. Better than hardcoding authorized addresses.

#4 — Batch Operations with Indexed Errors

batch error-handling
(define-constant ERR_TRANSFER_INDEX_PREFIX u1000)
;; Error u1003 = transfer at index 3 failed

Processes up to 200 transfers per tx. Error includes the index that failed (1000 + index). Uses fold with accumulator tracking position.

#5 — SIP-010 Compliance

standard

Standard interface: transfer, get-name, get-symbol, get-decimals, get-balance, get-total-supply, get-token-uri. Memo is (optional (buff 34)) — just printed for indexers.

#6 — Mutable Metadata

upgradeable

token-name, token-symbol, token-uri are define-data-var, not constants. Protocol can update without redeploying.

ALEX AMM Pool v2 SP102V...amm-pool-v2-01

#7 — Registry/Vault Separation (Controller Pattern)

architecture upgradeable

AMM has zero state vars for pool data. All reads/writes delegated to a registry contract, all funds in a vault contract. Deploy new AMM logic without migrating state or funds.

;; AMM = logic only
(define-read-only (get-pool-details ...)
    (contract-call? .amm-registry-v2-01 get-pool-details ...))
(as-contract (try! (contract-call? .amm-vault-v2-01 transfer-ft ...)))

#8 — Fixed-Point Math (8 Decimals)

math
(define-constant ONE_8 u100000000)
(define-private (mul-down (a uint) (b uint)) (/ (* a b) ONE_8))
(define-private (mul-up (a uint) (b uint))
    (let ((product (* a b)))
        (if (is-eq product u0) u0 (+ u1 (/ (- product u1) ONE_8)))))

mul-down/mul-up and div-down/div-up for directional rounding. Convention: fees use mul-up (round against user), outputs use mul-down.

#9 — On-Chain ln/exp/pow

math advanced

Full natural log, exponential, and arbitrary power functions in Clarity. Uses lookup tables for powers of 2 + Taylor series. MILD_EXPONENT_BOUND prevents overflow.

#10 — Switchable AMM Invariant

amm math
(define-read-only (get-invariant (balance-x uint) (balance-y uint) (t uint))
    (if (>= t (get-switch-threshold))
        ;; Stable-like: linear + product
        (+ (mul-down (- ONE_8 t) (+ balance-x balance-y))
           (mul-down t (mul-down balance-x balance-y)))
        ;; Weighted: sum of powers (Balancer-style)
        (+ (pow-down balance-x (- ONE_8 t))
           (pow-down balance-y (- ONE_8 t)))))

Single factor parameter morphs between weighted pool and stable pool. One contract, two AMM types.

#11 — Multi-Hop Routing

composability amm

Built-in up to 4-pool routes in a single tx. Read-only helpers (get-helper-a/b/c) for quoting, public helpers (swap-helper-a/b/c) for execution. Fee aggregation helpers sum fees across hops.

#12 — Ratio Limits + Slippage + Blocklist

safety
(asserts! (< dx (mul-down (get balance-x pool) (get max-in-ratio pool))) ERR-MAX-IN-RATIO)
(asserts! (< dy (mul-down (get balance-y pool) (get max-out-ratio pool))) ERR-MAX-OUT-RATIO)
(asserts! (<= (default-to u0 min-dy) dy) ERR-EXCEEDS-MAX-SLIPPAGE)

Layered safety: ratio limits (cap trade size vs pool), slippage (user-set), blocklist (deny-list), time windows (start/end block), global pause.

#13 — Oracle (EMA)

oracle

Exponential moving average price oracle. oracle-average weights historical vs instant. Updated every swap. Opt-in per pool.

#14 — LP Position: Invariant-Based Initial Mint

amm lp

First LP gets tokens equal to the invariant value (not sqrt like Uniswap). Subsequent LPs: proportional to share of balance-x. Auto-calculates dy to match pool ratio.

#15 — Fee Rebate to LPs

fees

Fees split: fee-rebate portion stays in pool (goes to LPs), rest goes to protocol reserve. Configurable per pool.

Market Factory v18-bias SP3N5C...market-factory-v18-bias

#16 — LMSR Cost Function

math prediction-market
;; cost_tilde = cost(qY+rY, qN+rN) - cost(rY, rN)
;; cost = b * ln(exp(qY/b) + exp(qN/b))

Logarithmic Market Scoring Rule with bias offset. b = liquidity depth. The tilde version subtracts the bias baseline so virtual shares only affect pricing.

#17 — Fixed-Point at 1e6 Scale

math

6-decimal fixed point (vs ALEX's 8-decimal). Smaller numbers, less overflow risk. exp via range reduction: exp(x) = 2^k * exp(r) where r ∈ [-ln2, ln2). bit-shift-left for powers of 2.

#18 — Dual-Unit System (STX Math, uSTX Transfers)

architecture
(define-constant UNIT u1000000)
(define-private (stx->ustx (amt-stx uint)) (* amt-stx UNIT))
(define-private (ustx->stx (amt-ustx uint)) (/ amt-ustx UNIT))

Clean boundary: LMSR math in integer STX, all money movement in uSTX. Convert at the edge, never mix.

#19 — Bias via Virtual Liquidity

prediction-market
;; rDiff = b * ln(pYes / (1 - pYes))
;; If pYes > 50%: rY = rDiff, rN = 0
;; If pYes < 50%: rY = 0, rN = rDiff

Set initial probability (1-99%) before trading. Bias locks after setting. Only affects pricing — redemption always pays 1 STX/share.

#20 — Closed-Form Buy Inversion

math optimization
;; Given budget, compute shares: x = b * ln(E(1+D) - D)
;; Then refine ±1 for rounding

Analytical inverse of LMSR cost function. No binary search needed. 2-step refinement handles integer rounding. Much more gas-efficient.

#21 — Per-Trade Solvency Guard

safety
(asserts! (and (>= pool1 (* ys1 UNIT)) (>= pool1 (* ns1 UNIT)))
    ERR-TRADE-INSOLVENT)

After every trade: pool must cover ALL outstanding shares at 1 STX each. Catches integer rounding before it compounds into insolvency.

#22 — Fee Splitting with Lockable Config

fees governance

Protocol fee splits 3 ways (drip + BRC-20 + team, must sum to 100%). lock-fees-config is a one-way irreversible lock. Sell fees clamped via minu(fee, base).

#23 — Map-Based Multi-Market Factory

architecture
(define-map m-status  { m: uint } { s: (string-ascii 10) })
(define-map yes-holdings { m: uint, user: principal } { bal: uint })

One contract serves unlimited markets. Composite keys for per-user-per-market state. No per-market deployments needed.

Lessons