clarity-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClarity Patterns Skill
Clarity模式Skill
Canonical pattern library for Clarity smart contract development on Stacks. All patterns and templates are bundled in this skill — no external dependencies.
This is a doc-only skill. Agents read this file and the colocated reference files directly. The CLI interface documents the planned implementation.
bun run clarity-patterns/clarity-patterns.ts <subcommand> [options]这是用于在Stacks上进行Clarity智能合约开发的权威模式库。所有模式和模板都集成在本Skill中——无外部依赖。
这是一个纯文档型Skill。Agent直接读取本文件和同目录下的参考文件。CLI接口记录了规划中的实现方案。
bun run clarity-patterns/clarity-patterns.ts <subcommand> [options]Subcommands
子命令
- — List available patterns and templates (categories:
list [--category <category>],code,registry,templates)testing - — Return a specific pattern with code and notes
get --name <pattern-name> - — Return a complete contract template with source, tests, and checklist
template --name <template-name>
- — 列出可用的模式和模板(分类:
list [--category <category>]、code、registry、templates)testing - — 返回带代码和注释的指定模式
get --name <pattern-name> - — 返回包含源码、测试和检查清单的完整合约模板
template --name <template-name>
Code Patterns
代码模式
Public Function Template
公共函数模板
Standard structure for public functions with guards and error handling.
clarity
(define-public (transfer (amount uint) (to principal))
(begin
(asserts! (is-eq tx-sender owner) ERR_UNAUTHORIZED)
(try! (ft-transfer? TOKEN amount tx-sender to))
(ok true)))- Use for subcalls to propagate errors
try! - Use for guards before state changes
asserts! - Add post-conditions on tx for asset safety
带有守卫和错误处理的公共函数标准结构。
clarity
(define-public (transfer (amount uint) (to principal))
(begin
(asserts! (is-eq tx-sender owner) ERR_UNAUTHORIZED)
(try! (ft-transfer? TOKEN amount tx-sender to))
(ok true)))- 使用处理子调用以传播错误
try! - 在状态变更前使用设置守卫
asserts! - 为资产安全添加交易后置条件
Standardized Events
标准化事件
Emit structured events for off-chain indexing.
clarity
(print {
notification: "contract-event",
payload: {
amount: amount,
sender: tx-sender,
recipient: to
}
})- : string identifier for the event type
notification - : tuple with camelCase keys
payload - Examples: usabtc-token, ccd002-treasury-v3
发出结构化事件供链下索引使用。
clarity
(print {
notification: "contract-event",
payload: {
amount: amount,
sender: tx-sender,
recipient: to
}
})- :事件类型的字符串标识符
notification - :采用小驼峰键名的元组
payload - 示例:usabtc-token、ccd002-treasury-v3
Error Handling with Match
基于Match的错误处理
Handle external call failures gracefully.
clarity
(match (contract-call? .other fn args)
success (ok success)
error (err ERR_EXTERNAL_CALL_FAILED))优雅处理外部调用失败。
clarity
(match (contract-call? .other fn args)
success (ok success)
error (err ERR_EXTERNAL_CALL_FAILED))Bit Flags for Status/Permissions
用于状态/权限的位标志
Pack multiple booleans into a single uint.
clarity
(define-constant STATUS_ACTIVE (pow u2 u0)) ;; 1
(define-constant STATUS_PAID (pow u2 u1)) ;; 2
(define-constant STATUS_VERIFIED (pow u2 u2)) ;; 4
;; Pack multiple flags: (+ STATUS_ACTIVE STATUS_PAID) → u3
;; Check flag: (> (bit-and status STATUS_ACTIVE) u0)
;; Set flag: (var-set status (bit-or (var-get status) NEW_FLAG))
;; Clear flag: (var-set status (bit-and (var-get status) (bit-not FLAG)))Examples: aibtc-action-proposal-voting
将多个布尔值打包到单个uint中。
clarity
(define-constant STATUS_ACTIVE (pow u2 u0)) ;; 1
(define-constant STATUS_PAID (pow u2 u1)) ;; 2
(define-constant STATUS_VERIFIED (pow u2 u2)) ;; 4
;; 打包多个标志:(+ STATUS_ACTIVE STATUS_PAID) → u3
;; 检查标志:(> (bit-and status STATUS_ACTIVE) u0)
;; 设置标志:(var-set status (bit-or (var-get status) NEW_FLAG))
;; 清除标志:(var-set status (bit-and (var-get status) (bit-not FLAG)))Multi-Send Pattern
多发送模式
Send to multiple recipients in one transaction using fold.
clarity
(define-private (send-maybe
(recipient {to: principal, ustx: uint})
(prior (response bool uint)))
(match prior
ok-result (let (
(to (get to recipient))
(ustx (get ustx recipient)))
(try! (stx-transfer? ustx tx-sender to))
(ok true))
err-result (err err-result)))
(define-public (send-many (recipients (list 200 {to: principal, ustx: uint})))
(fold send-maybe recipients (ok true)))使用fold在一笔交易中向多个接收方发送资产。
clarity
(define-private (send-maybe
(recipient {to: principal, ustx: uint})
(prior (response bool uint)))
(match prior
ok-result (let (
(to (get to recipient))
(ustx (get ustx recipient)))
(try! (stx-transfer? ustx tx-sender to))
(ok true))
err-result (err err-result)))
(define-public (send-many (recipients (list 200 {to: principal, ustx: uint})))
(fold send-maybe recipients (ok true)))Parent-Child Maps (Hierarchical Data)
父子映射(分层数据)
Store hierarchical data with pagination support.
clarity
(define-map Parents uint {name: (string-ascii 32), lastChildId: uint})
(define-map Children {parentId: uint, id: uint} uint)
(define-read-only (get-child (parentId uint) (childId uint))
(map-get? Children {parentId: parentId, id: childId}))
(define-private (is-some? (x (optional uint)))
(is-some x))
(define-read-only (get-children (parentId uint) (shift uint))
(filter is-some?
(list
(get-child parentId (+ shift u1))
(get-child parentId (+ shift u2))
(get-child parentId (+ shift u3))
;; ... up to page size
)))存储带有分页支持的分层数据。
clarity
(define-map Parents uint {name: (string-ascii 32), lastChildId: uint})
(define-map Children {parentId: uint, id: uint} uint)
(define-read-only (get-child (parentId uint) (childId uint))
(map-get? Children {parentId: parentId, id: childId}))
(define-private (is-some? (x (optional uint)))
(is-some x))
(define-read-only (get-children (parentId uint) (shift uint))
(filter is-some?
(list
(get-child parentId (+ shift u1))
(get-child parentId (+ shift u2))
(get-child parentId (+ shift u3))
;; ... 直至分页大小
)))Whitelisting (Assets/Contracts)
白名单(资产/合约)
Control which contracts/assets can interact.
clarity
(define-map Allowed {contract: principal, type: uint} bool)
;; Check in function
(asserts! (default-to false (map-get? Allowed {contract: contract, type: type}))
ERR_NOT_ALLOWED)
;; Batch update
(define-public (set-allowed-list (items (list 100 {token: principal, enabled: bool})))
(ok (map set-iter items (ok true))))Examples: ccd002-treasury-v3, aibtc-agent-account
控制哪些合约/资产可以交互。
clarity
(define-map Allowed {contract: principal, type: uint} bool)
;; 在函数中检查
(asserts! (default-to false (map-get? Allowed {contract: contract, type: type}))
ERR_NOT_ALLOWED)
;; 批量更新
(define-public (set-allowed-list (items (list 100 {token: principal, enabled: bool})))
(ok (map set-iter items (ok true))))Trait Whitelisting
Trait白名单
Only allow calls from trusted trait implementations.
clarity
(define-map TrustedTraits principal bool)
;; In functions accepting traits
(asserts! (default-to false (map-get? TrustedTraits (contract-of t)))
ERR_UNTRUSTED)仅允许来自可信Trait实现的调用。
clarity
(define-map TrustedTraits principal bool)
;; 在接受Trait的函数中
(asserts! (default-to false (map-get? TrustedTraits (contract-of t)))
ERR_UNTRUSTED)Delayed Activation
延迟激活
Activate functionality after a Bitcoin block delay.
clarity
(define-constant DELAY u21000) ;; ~146 days in BTC blocks
(define-data-var activation-block uint u0)
;; Set on deploy or init
(var-set activation-block (+ burn-block-height DELAY))
(define-read-only (is-active?)
(>= burn-block-height (var-get activation-block)))Example: usabtc-token
在比特币区块延迟后激活功能。
clarity
(define-constant DELAY u21000) ;; ~146天(按BTC区块计算)
(define-data-var activation-block uint u0)
;; 在部署或初始化时设置
(var-set activation-block (+ burn-block-height DELAY))
(define-read-only (is-active?)
(>= burn-block-height (var-get activation-block)))示例:usabtc-token
Rate Limiting
速率限制
Prevent rapid repeated actions.
clarity
(define-data-var last-action-block uint u0)
(define-public (rate-limited-action)
(begin
(asserts! (> burn-block-height (var-get last-action-block)) ERR_RATE_LIMIT)
(var-set last-action-block burn-block-height)
;; ... action
(ok true)))防止快速重复操作。
clarity
(define-data-var last-action-block uint u0)
(define-public (rate-limited-action)
(begin
(asserts! (> burn-block-height (var-get last-action-block)) ERR_RATE_LIMIT)
(var-set last-action-block burn-block-height)
;; ... 执行操作
(ok true)))DAO Proposals with Historic Balances
带历史余额的DAO提案
Use for snapshot voting.
at-blockclarity
(define-map Proposals uint {
votesFor: uint,
votesAgainst: uint,
status: uint,
liquidTokens: uint,
blockHash: (buff 32)
})
;; Get voting power at proposal creation
(define-read-only (get-vote-power (proposal-id uint) (voter principal))
(let ((proposal (unwrap! (map-get? Proposals proposal-id) u0)))
(at-block (get blockHash proposal)
(contract-call? .token get-balance voter))))
;; Quorum check: (>= (/ (* total-votes u100) liquid-supply) QUORUM_PERCENT)Example: aibtc-action-proposal-voting
使用进行快照投票。
at-blockclarity
(define-map Proposals uint {
votesFor: uint,
votesAgainst: uint,
status: uint,
liquidTokens: uint,
blockHash: (buff 32)
})
;; 获取提案创建时的投票权
(define-read-only (get-vote-power (proposal-id uint) (voter principal))
(let ((proposal (unwrap! (map-get? Proposals proposal-id) u0)))
(at-block (get blockHash proposal)
(contract-call? .token get-balance voter))))
;; 法定人数检查:(>= (/ (* total-votes u100) liquid-supply) QUORUM_PERCENT)Fixed-Point Arithmetic
定点算术
Handle decimal values with scale factor.
clarity
(define-constant SCALE (pow u10 u8)) ;; 8 decimal places
;; Multiply then divide to preserve precision
(define-read-only (calculate-share (amount uint) (percentage uint))
(/ (* amount percentage) SCALE))
;; Convert to/from scaled values
(define-read-only (to-scaled (amount uint))
(* amount SCALE))
(define-read-only (from-scaled (amount uint))
(/ amount SCALE))Example: ccd012-redemption-nyc
使用比例因子处理十进制值。
clarity
(define-constant SCALE (pow u10 u8)) ;; 8位小数
;; 先乘后除以保留精度
(define-read-only (calculate-share (amount uint) (percentage uint))
(/ (* amount percentage) SCALE))
;; 在缩放值与原值间转换
(define-read-only (to-scaled (amount uint))
(* amount SCALE))
(define-read-only (from-scaled (amount uint))
(/ amount SCALE))Treasury Pattern with as-contract
带as-contract的金库模式
Use for contract-controlled funds.
as-contractclarity
(define-public (withdraw (amount uint) (recipient principal))
(begin
(asserts! (is-authorized tx-sender) ERR_UNAUTHORIZED)
(as-contract (stx-transfer? amount (as-contract tx-sender) recipient))))Warning: changes both and to the contract principal.
as-contracttx-sendercontract-caller使用管理合约控制的资金。
as-contractclarity
(define-public (withdraw (amount uint) (recipient principal))
(begin
(asserts! (is-authorized tx-sender) ERR_UNAUTHORIZED)
(as-contract (stx-transfer? amount (as-contract tx-sender) recipient))))警告:会将和都改为合约主体。
as-contracttx-sendercontract-callertx-sender vs contract-caller Decision Framework
tx-sender与contract-caller决策框架
| Call Path | contract-caller | tx-sender |
|---|---|---|
| user -> target | user | user |
| user -> proxy -> target | proxy | user |
| user -> proxy (as-contract) -> target | proxy | proxy |
- tx-sender: Use for auth checks, identity attribution, self-action guards. Preserves human identity through normal proxies. Preferred for composability.
- contract-caller: Use when you need the IMMEDIATE caller identity specifically.
- Security note: Using for self-action guards (e.g., "owner can't give themselves feedback") is bypassable — owner routes through any proxy and
contract-callershows the proxy, not the owner.contract-callercatches this because it preserves the human origin.tx-sender
Examples: ccd002-treasury-v3, aibtc-agent-account
| 调用路径 | contract-caller | tx-sender |
|---|---|---|
| 用户 -> 目标合约 | 用户 | 用户 |
| 用户 -> 代理合约 -> 目标合约 | 代理合约 | 用户 |
| 用户 -> 代理合约(as-contract) -> 目标合约 | 代理合约 | 代理合约 |
- tx-sender:用于权限检查、身份归属、自操作守卫。在普通代理调用中保留用户身份。是组合性开发的首选。
- contract-caller:仅当你需要获取直接调用者身份时使用。
- 安全注意:使用实现自操作守卫(例如“所有者不能给自己反馈”)存在绕过风险——所有者可通过任意代理路由调用,此时
contract-caller显示的是代理合约而非所有者。contract-caller可捕获此情况,因为它保留了初始用户身份。tx-sender
Clarity 4: Asset Restrictions
Clarity 4:资产限制
Restrict what assets a contract call can move.
clarity
(as-contract
(with-stx u1000000) ;; Allow 1 STX
(with-ft .token TOKEN u500) ;; Allow 500 fungible tokens
(with-nft .nft-contract NFT (list u1 u2 u3)) ;; Allow specific NFT IDs
;; ... body
)
;; DANGER: Avoid unless necessary
(with-all-assets-unsafe)限制合约调用可转移的资产。
clarity
(as-contract
(with-stx u1000000) ;; 允许1个STX
(with-ft .token TOKEN u500) ;; 允许500个 fungible token
(with-nft .nft-contract NFT (list u1 u2 u3)) ;; 允许特定NFT ID
;; ... 函数体
)
;; 危险:非必要请勿使用
(with-all-assets-unsafe)Multi-Party Coordination
多方协作
Coordinate actions requiring multiple signatures.
clarity
;; Proposal state
(define-map Intents uint {
participants: (list 20 principal),
accepts: uint, ;; Bitmask of who accepted
status: uint, ;; 0=pending, 1=ready, 2=executed, 3=cancelled
expiry: uint,
payload: (buff 256)
})
;; Accept via signature verification
(define-public (accept (intent-id uint) (signature (buff 65)))
(let (
(intent (unwrap! (map-get? Intents intent-id) ERR_NOT_FOUND))
(msg-hash (sha256 (concat (int-to-ascii intent-id) (get payload intent))))
(signer (try! (secp256k1-recover? msg-hash signature))))
;; Verify signer is participant, update accepts bitmask
(ok true)))Reference: ERC-8001 pattern for decidable multi-party coordination.
协调需要多签名的操作。
clarity
;; 提案状态
(define-map Intents uint {
participants: (list 20 principal),
accepts: uint, ;; 已接受者的位掩码
status: uint, ;; 0=待处理,1=就绪,2=已执行,3=已取消
expiry: uint,
payload: (buff 256)
})
;; 通过签名验证接受提案
(define-public (accept (intent-id uint) (signature (buff 65)))
(let (
(intent (unwrap! (map-get? Intents intent-id) ERR_NOT_FOUND))
(msg-hash (sha256 (concat (int-to-ascii intent-id) (get payload intent))))
(signer (try! (secp256k1-recover? msg-hash signature))))
;; 验证签名者为参与者,更新接受位掩码
(ok true)))参考:用于可判定多方协作的ERC-8001模式。
Registry Patterns
注册器模式
Block Snapshot Pattern
区块快照模式
Capture comprehensive chain state at transaction time. This is the "receipt" that makes a transaction worth the fee.
clarity
;; Full snapshot — comprehensive (use for high-value records)
(define-private (capture-snapshot)
{
stacksBlock: stacks-block-height,
burnBlock: burn-block-height,
tenure: tenure-height,
blockTime: stacks-block-time,
chainId: chain-id,
txSender: tx-sender,
contractCaller: contract-caller,
txSponsor: tx-sponsor?,
stacksBlockHash: (get-stacks-block-info? id-header-hash (- stacks-block-height u1)),
burnBlockHash: (get-burn-block-info? header-hash (- burn-block-height u1))
}
)
;; Standard snapshot — balanced cost
;; {stacksBlock, burnBlock, blockTime, txSender}
;; Minimal snapshot — cheapest
;; {stacksBlock, burnBlock}Previous block hashes are captured because the current block's hash isn't finalized until after the transaction. The previous block's hash is immutable and independently verifiable.
在交易发生时捕获完整的链上状态。这是让交易值得支付手续费的“收据”。
clarity
;; 完整快照——全面记录(用于高价值记录)
(define-private (capture-snapshot)
{
stacksBlock: stacks-block-height,
burnBlock: burn-block-height,
tenure: tenure-height,
blockTime: stacks-block-time,
chainId: chain-id,
txSender: tx-sender,
contractCaller: contract-caller,
txSponsor: tx-sponsor?,
stacksBlockHash: (get-stacks-block-info? id-header-hash (- stacks-block-height u1)),
burnBlockHash: (get-burn-block-info? header-hash (- burn-block-height u1))
}
)
;; 标准快照——平衡成本
;; {stacksBlock, burnBlock, blockTime, txSender}
;; 最小快照——成本最低
;; {stacksBlock, burnBlock}当前区块的哈希要到交易完成后才会最终确定,因此捕获的是前一区块的哈希,该哈希是不可变且可独立验证的。
Principal-Keyed Registry
主体键注册器
Track state per address (heartbeats, profiles, balances). One entry per address, overwrites on subsequent calls.
clarity
(define-map Registry
principal
{
stacksBlock: uint,
burnBlock: uint,
count: uint
}
)
(map-get? Registry address)
(map-set Registry tx-sender {...})按地址跟踪状态(心跳、档案、余额)。每个地址对应一条记录,后续调用会覆盖原有记录。
clarity
(define-map Registry
principal
{
stacksBlock: uint,
burnBlock: uint,
count: uint
}
)
(map-get? Registry address)
(map-set Registry tx-sender {...})Hash-Keyed Registry
哈希键注册器
Track unique data (attestations, commitments). One entry per unique hash, first-write-wins.
clarity
(define-map Registry
(buff 32)
{
attestor: principal,
stacksBlock: uint
}
)
;; First attestor wins
(asserts! (is-none (map-get? Registry hash)) ERR_ALREADY_EXISTS)
(map-set Registry hash {...})跟踪唯一数据(证明、承诺)。每个唯一哈希对应一条记录,先写入者胜出。
clarity
(define-map Registry
(buff 32)
{
attestor: principal,
stacksBlock: uint
}
)
;; 先写入者胜出
(asserts! (is-none (map-get? Registry hash)) ERR_ALREADY_EXISTS)
(map-set Registry hash {...})Composite-Keyed Registry
复合键注册器
Multi-dimensional tracking (votes per proposal, actions per agent).
clarity
(define-map Registry
{entity: principal, action: uint}
{stacksBlock: uint}
)
(map-get? Registry {entity: address, action: action-id})多维跟踪(每个提案的投票数、每个Agent的操作数)。
clarity
(define-map Registry
{entity: principal, action: uint}
{stacksBlock: uint}
)
(map-get? Registry {entity: address, action: action-id})Secondary Index Pattern
二级索引模式
Enable enumeration of entries by address when primary key isn't the address.
clarity
;; Primary: hash -> data
(define-map Attestations (buff 32) {...})
;; Secondary: address + index -> hash
(define-map AttestorIndex
{attestor: principal, index: uint}
(buff 32)
)
;; Counter for next index
(define-map AttestorCount principal uint)
;; On insert:
(let ((idx (default-to u0 (map-get? AttestorCount attestor))))
(map-set AttestorIndex {attestor: attestor, index: idx} hash)
(map-set AttestorCount attestor (+ idx u1)))
;; Enumerate:
(define-read-only (get-attestor-hash-at (attestor principal) (index uint))
(map-get? AttestorIndex {attestor: attestor, index: index}))当主键不是地址时,支持按地址枚举记录。
clarity
;; 主键:哈希 -> 数据
(define-map Attestations (buff 32) {...})
;; 二级索引:地址 + 索引 -> 哈希
(define-map AttestorIndex
{attestor: principal, index: uint}
(buff 32)
)
;; 下一个索引的计数器
(define-map AttestorCount principal uint)
;; 插入时:
(let ((idx (default-to u0 (map-get? AttestorCount attestor))))
(map-set AttestorIndex {attestor: attestor, index: idx} hash)
(map-set AttestorCount attestor (+ idx u1)))
;; 枚举:
(define-read-only (get-attestor-hash-at (attestor principal) (index uint))
(map-get? AttestorIndex {attestor: attestor, index: index}))Global Stats Pattern
全局统计模式
Track aggregate metrics without iterating.
clarity
(define-data-var totalEntries uint u0)
(define-data-var uniqueAddresses uint u0)
;; On new entry:
(var-set totalEntries (+ (var-get totalEntries) u1))
(if isNewAddress
(var-set uniqueAddresses (+ (var-get uniqueAddresses) u1))
true)
;; Read stats:
(define-read-only (get-stats)
{
totalEntries: (var-get totalEntries),
uniqueAddresses: (var-get uniqueAddresses)
}
)无需迭代即可跟踪聚合指标。
clarity
(define-data-var totalEntries uint u0)
(define-data-var uniqueAddresses uint u0)
;; 新增记录时:
(var-set totalEntries (+ (var-get totalEntries) u1))
(if isNewAddress
(var-set uniqueAddresses (+ (var-get uniqueAddresses) u1))
true)
;; 读取统计数据:
(define-read-only (get-stats)
{
totalEntries: (var-get totalEntries),
uniqueAddresses: (var-get uniqueAddresses)
}
)Write Semantics
写入语义
First-Write-Wins (Attestations):
clarity
(define-public (attest (key (buff 32)))
(begin
(asserts! (is-none (map-get? Registry key)) ERR_ALREADY_EXISTS)
(map-set Registry key {...})
(ok true)))Last-Write-Wins (Heartbeats):
clarity
(define-public (check-in)
(begin
(map-set Registry tx-sender {...})
(ok true)))Append-Only (History):
clarity
(define-map History
{address: principal, index: uint}
{...snapshot...}
)
(define-public (record)
(let ((idx (default-to u0 (map-get? HistoryCount tx-sender))))
(map-set History {address: tx-sender, index: idx} {...})
(map-set HistoryCount tx-sender (+ idx u1))
(ok idx)))先写入者胜出(证明类):
clarity
(define-public (attest (key (buff 32)))
(begin
(asserts! (is-none (map-get? Registry key)) ERR_ALREADY_EXISTS)
(map-set Registry key {...})
(ok true)))后写入者胜出(心跳类):
clarity
(define-public (check-in)
(begin
(map-set Registry tx-sender {...})
(ok true)))仅追加(历史记录类):
clarity
(define-map History
{address: principal, index: uint}
{...snapshot...}
)
(define-public (record)
(let ((idx (default-to u0 (map-get? HistoryCount tx-sender))))
(map-set History {address: tx-sender, index: idx} {...})
(map-set HistoryCount tx-sender (+ idx u1))
(ok idx)))Access Control Patterns
访问控制模式
Open (anyone can write):
clarity
(define-public (register)
(ok (map-set Registry tx-sender {...})))Self-Only (registered users update own entries):
clarity
(define-public (update (data (buff 64)))
(begin
(asserts! (is-some (map-get? Registry tx-sender)) ERR_NOT_REGISTERED)
(map-set Registry tx-sender {...})
(ok true)))Admin-Gated:
clarity
(define-data-var admin principal CONTRACT_OWNER)
(define-public (register-address (address principal))
(begin
(asserts! (is-eq tx-sender (var-get admin)) ERR_UNAUTHORIZED)
(map-set Registry address {...})
(ok true)))开放(任何人可写入):
clarity
(define-public (register)
(ok (map-set Registry tx-sender {...})))仅自身(已注册用户可更新自己的记录):
clarity
(define-public (update (data (buff 64)))
(begin
(asserts! (is-some (map-get? Registry tx-sender)) ERR_NOT_REGISTERED)
(map-set Registry tx-sender {...})
(ok true)))管理员 gated:
clarity
(define-data-var admin principal CONTRACT_OWNER)
(define-public (register-address (address principal))
(begin
(asserts! (is-eq tx-sender (var-get admin)) ERR_UNAUTHORIZED)
(map-set Registry address {...})
(ok true)))Testing Reference
测试参考
Testing Pyramid
测试金字塔
Stxer (Historical Simulation) — Mainnet fork, pre-deployment validation
RV (Property-Based Fuzzing) — Invariants, edge cases, battle-grade
Vitest + Clarinet SDK — Integration tests, TypeScript
Clarunit — Unit tests in Clarity itselfStxer(历史模拟) — 主网分叉,部署前验证
RV(基于属性的模糊测试) — 不变量、边缘情况、生产级测试
Vitest + Clarinet SDK — 集成测试、TypeScript
Clarunit — 纯Clarity编写的单元测试When to Use Each Tool
各工具适用场景
| Tool | Use When | Skip When |
|---|---|---|
| Clarinet SDK | Standard testing, CI/CD, type-safe | - |
| Clarunit | Testing Clarity logic in Clarity, simple assertions | Complex multi-account flows |
| RV | Treasuries, DAOs, high-value contracts, finding edge cases | Simple contracts, time pressure |
| Stxer | Pre-mainnet validation, governance simulations | Early development, testnet-only |
| 工具 | 适用场景 | 不适用场景 |
|---|---|---|
| Clarinet SDK | 标准测试、CI/CD、类型安全 | - |
| Clarunit | 用Clarity测试Clarity逻辑、简单断言 | 复杂多账户流程 |
| RV | 金库、DAO、高价值合约、寻找边缘情况 | 简单合约、时间紧张的开发 |
| Stxer | 主网部署前验证、治理模拟 | 早期开发、仅测试网部署 |
Vitest Config
Vitest配置
javascript
import { defineConfig } from "vitest/config";
import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest";
export default defineConfig({
test: {
environment: "clarinet",
singleThread: true,
setupFiles: [vitestSetupFilePath],
environmentOptions: {
clarinet: getClarinetVitestsArgv(),
},
},
});javascript
import { defineConfig } from "vitest/config";
import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest";
export default defineConfig({
test: {
environment: "clarinet",
singleThread: true,
setupFiles: [vitestSetupFilePath],
environmentOptions: {
clarinet: getClarinetVitestsArgv(),
},
},
});Test Structure (Arrange-Act-Assert)
测试结构(Arrange-Act-Assert)
typescript
import { Cl } from "@stacks/transactions";
import { describe, expect, it } from "vitest";
describe("my-contract", function () {
it("transfers tokens correctly", function () {
// ARRANGE
const deployer = simnet.deployer;
const wallet1 = simnet.getAccounts().get("wallet_1")!;
const amount = 100;
// ACT
const result = simnet.callPublicFn(
"my-contract",
"transfer",
[Cl.uint(amount), Cl.principal(wallet1)],
deployer
);
// ASSERT
expect(result.result).toBeOk(Cl.bool(true));
});
});typescript
import { Cl } from "@stacks/transactions";
import { describe, expect, it } from "vitest";
describe("my-contract", function () {
it("transfers tokens correctly", function () {
// 准备(ARRANGE)
const deployer = simnet.deployer;
const wallet1 = simnet.getAccounts().get("wallet_1")!;
const amount = 100;
// 执行(ACT)
const result = simnet.callPublicFn(
"my-contract",
"transfer",
[Cl.uint(amount), Cl.principal(wallet1)],
deployer
);
// 断言(ASSERT)
expect(result.result).toBeOk(Cl.bool(true));
});
});Key Gotchas
关键注意事项
- NO /
beforeAll— simnet resets each test file sessionbeforeEach - Single thread required — for simnet isolation
singleThread: true - Functions over arrow functions for test helpers
- Use and
cvToValue()for Clarity-to-JS conversioncvToJSON()
- 禁止使用/
beforeAll— simnet会在每个测试文件会话开始时重置beforeEach - 必须单线程运行 — 以保证simnet隔离
singleThread: true - 测试辅助函数使用普通函数而非箭头函数
- 使用和
cvToValue()进行Clarity到JS的转换cvToJSON()
Clarity Value Constructors
Clarity值构造器
typescript
import { Cl, cvToValue, cvToJSON } from "@stacks/transactions";
Cl.uint(100) // uint
Cl.int(-50) // int
Cl.bool(true) // bool
Cl.principal("SP123...") // principal
Cl.contractPrincipal("SP123", "name") // contract principal
Cl.stringAscii("hello") // (string-ascii N)
Cl.stringUtf8("hello") // (string-utf8 N)
Cl.bufferFromHex("deadbeef") // (buff N)
Cl.tuple({ amount: Cl.uint(100) }) // tuple
Cl.list([Cl.uint(1), Cl.uint(2)]) // list
Cl.some(Cl.uint(100)) // (some value)
Cl.none() // nonetypescript
import { Cl, cvToValue, cvToJSON } from "@stacks/transactions";
Cl.uint(100) // uint类型
Cl.int(-50) // int类型
Cl.bool(true) // bool类型
Cl.principal("SP123...") // 主体
Cl.contractPrincipal("SP123", "name") // 合约主体
Cl.stringAscii("hello") // (string-ascii N)
Cl.stringUtf8("hello") // (string-utf8 N)
Cl.bufferFromHex("deadbeef") // (buff N)
Cl.tuple({ amount: Cl.uint(100) }) // 元组
Cl.list([Cl.uint(1), Cl.uint(2)]) // 列表
Cl.some(Cl.uint(100)) // (some value)
Cl.none() // noneCustom Matchers
自定义匹配器
typescript
expect(result.result).toBeOk(Cl.uint(100));
expect(result.result).toBeErr(Cl.uint(1));
expect(result.result).toBeBool(true);
expect(result.result).toBeUint(100);
expect(result.result).toBePrincipal("SP123...");typescript
expect(result.result).toBeOk(Cl.uint(100));
expect(result.result).toBeErr(Cl.uint(1));
expect(result.result).toBeBool(true);
expect(result.result).toBeUint(100);
expect(result.result).toBePrincipal("SP123...");RV (Rendezvous) Fuzz Tests
RV(Rendezvous)模糊测试
clarity
;; Property: loan amount always increases correctly
(define-public (test-borrow (amount uint))
(if (is-eq amount u0)
(ok false) ;; Discard invalid input
(let ((initial (get-loan tx-sender)))
(try! (borrow amount))
(asserts! (is-eq (get-loan tx-sender) (+ initial amount))
(err u999))
(ok true))))
;; Invariant: total supply never exceeds cap
(define-read-only (invariant-supply-capped)
(<= (var-get total-supply) MAX_SUPPLY))Run: (properties), (invariants)
npx rv . my-contract testnpx rv . my-contract invariantclarity
;; 属性:贷款金额始终正确增加
(define-public (test-borrow (amount uint))
(if (is-eq amount u0)
(ok false) ;; 丢弃无效输入
(let ((initial (get-loan tx-sender)))
(try! (borrow amount))
(asserts! (is-eq (get-loan tx-sender) (+ initial amount))
(err u999))
(ok true))))
;; 不变量:总供应量绝不超过上限
(define-read-only (invariant-supply-capped)
(<= (var-get total-supply) MAX_SUPPLY))运行:(属性测试)、(不变量测试)
npx rv . my-contract testnpx rv . my-contract invariantClarunit Tests
Clarunit测试
clarity
;; @name Multiplication works correctly
(define-public (test-multiply)
(begin
(asserts! (is-eq u8 (contract-call? .math multiply u2 u4))
(err "2 * 4 should equal 8"))
(ok true)))File: , functions start with .
tests/my-contract_test.clartest-clarity
;; @name 乘法功能正常
(define-public (test-multiply)
(begin
(asserts! (is-eq u8 (contract-call? .math multiply u2 u4))
(err "2 * 4 应等于 8"))
(ok true)))文件:,测试函数以开头。
tests/my-contract_test.clartest-Project Structure (Full Stack)
项目结构(全栈)
my-project/
├── Clarinet.toml
├── vitest.config.js
├── package.json
├── contracts/
│ ├── my-contract.clar
│ └── my-contract.tests.clar # RV tests
├── tests/
│ ├── my-contract.test.ts # Vitest
│ ├── my-contract_test.clar # Clarunit
│ └── clarunit.test.ts # Clarunit runner
└── simulations/
└── my-contract-stxer.ts # Stxermy-project/
├── Clarinet.toml
├── vitest.config.js
├── package.json
├── contracts/
│ ├── my-contract.clar
│ └── my-contract.tests.clar # RV测试
├── tests/
│ ├── my-contract.test.ts # Vitest测试
│ ├── my-contract_test.clar # Clarunit测试
│ └── clarunit.test.ts # Clarunit运行器
└── simulations/
└── my-contract-stxer.ts # Stxer模拟package.json Scripts
package.json脚本
json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:rv": "npx rv . my-contract test",
"test:rv:invariant": "npx rv . my-contract invariant",
"test:stxer": "npx tsx simulations/my-contract-stxer.ts"
}
}json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:rv": "npx rv . my-contract test",
"test:rv:invariant": "npx rv . my-contract invariant",
"test:stxer": "npx tsx simulations/my-contract-stxer.ts"
}
}Contract Templates
合约模板
Full contract templates with source and tests are in colocated files:
| Template | File | Description |
|---|---|---|
| templates/heartbeat-registry.md | Agent heartbeat with full chain context, address enumeration, liveness checks |
| templates/proof-of-existence.md | Document timestamping with SIP-018 signatures, first-write-wins, attestor index |
| templates/registry-minimal.md | Minimal registry combining snapshot + stats + events |
包含源码和测试的完整合约模板位于同目录文件中:
| 模板 | 文件 | 描述 |
|---|---|---|
| templates/heartbeat-registry.md | 带有完整链上上下文、地址枚举、活跃度检查的Agent心跳注册器 |
| templates/proof-of-existence.md | 带有SIP-018签名、先写入者胜出规则、证明者索引的文档时间戳服务 |
| templates/registry-minimal.md | 集成快照、统计和事件的最小化注册器 |
Execution Cost Limits
执行成本限制
| Category | Block Limit | Read-Only Limit |
|---|---|---|
| Runtime | 5,000,000,000 | 1,000,000,000 |
| Read count | 15,000 | 30 |
| Read bytes | 100,000,000 | 100,000 |
| Write count | 15,000 | 0 |
| Write bytes | 15,000,000 | 0 |
| 分类 | 区块限制 | 只读限制 |
|---|---|---|
| 运行时 | 5,000,000,000 | 1,000,000,000 |
| 读取次数 | 15,000 | 30 |
| 读取字节数 | 100,000,000 | 100,000 |
| 写入次数 | 15,000 | 0 |
| 写入字节数 | 15,000,000 | 0 |
Cost Optimization Tips
成本优化技巧
- Inline single-use values — avoid unnecessary bindings
let - Constants over data-vars — constants are cheaper to read
- Bulk operations — single call with list beats multiple calls
- Separate params vs tuples — flat params are cheaper for function calls
- Off-chain computation — move non-essential logic to UI/indexer
- 内联单次使用的值 — 避免不必要的绑定
let - 使用常量而非数据变量 — 常量读取成本更低
- 批量操作 — 单次带列表参数的调用优于多次调用
- 使用扁平参数而非元组 — 函数调用时扁平参数成本更低
- 链下计算 — 将非核心逻辑移至UI或索引器
Quick Reference
快速参考
- Use not
stacks-block-height(deprecated)block-height - Use for token operations,
tx-senderonly when immediate caller identity is neededcontract-caller - Use for error propagation,
try!for guards before state changesasserts! - All public functions must return
(response ok err) - Error codes should be unique constants
- Events: format
{notification: "event-name", payload: {...}} - Check costs with in clarinet console
::get_costs
- 使用而非
stacks-block-height(已废弃)block-height - 代币操作使用,仅当需要直接调用者身份时使用
tx-sendercontract-caller - 使用传播错误,状态变更前使用
try!设置守卫asserts! - 所有公共函数必须返回
(response ok err) - 错误码应使用唯一常量
- 事件采用格式
{notification: "event-name", payload: {...}} - 在clarinet控制台中使用检查成本
::get_costs
References
参考资料
- friedger/clarity-ccip-026 — All 4 testing tools integrated
- kenny-stacks/stacks-starter — Testing setup reference
- aibtcdev/aibtcdev-daos — DAO patterns
- citycoins/protocol — Token and treasury patterns
- clarigen — TypeScript type generation from contracts
- secondlayer — Alternative type generator
- friedger/clarity-ccip-026 — 集成了全部4种测试工具
- kenny-stacks/stacks-starter — 测试设置参考
- aibtcdev/aibtcdev-daos — DAO模式参考
- citycoins/protocol — 代币和金库模式参考
- clarigen — 从合约生成TypeScript类型
- secondlayer — 替代类型生成工具