23 patterns extracted from production Stacks contracts
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)))
(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.
(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.
(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.
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.
token-name, token-symbol, token-uri are define-data-var, not constants. Protocol can update without redeploying.
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 ...)))
(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.
Full natural log, exponential, and arbitrary power functions in Clarity. Uses lookup tables for powers of 2 + Taylor series. MILD_EXPONENT_BOUND prevents overflow.
(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.
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.
(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.
Exponential moving average price oracle. oracle-average weights historical vs instant. Updated every swap. Opt-in per pool.
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.
Fees split: fee-rebate portion stays in pool (goes to LPs), rest goes to protocol reserve. Configurable per pool.
;; 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.
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.
(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.
;; 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.
;; 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.
(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.
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).
(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.