cadence
npx skills add https://github.com/outblock/cadence-lang.org --skill cadence
Agent 安装分布
Skill 文档
Cadence Smart Contract Development
Use this skill whenever you are writing, reviewing, debugging, or auditing Cadence code on the Flow blockchain.
Documentation
| Resource | URL |
|---|---|
| Full docs | https://cadence-lang.org/docs |
| LLM-optimized reference | https://cadence-lang.org/llms.txt |
| Full LLM dump | https://cadence-lang.org/llms-full.txt |
| Security best practices | https://cadence-lang.org/docs/security-best-practices |
| Design patterns | https://cadence-lang.org/docs/design-patterns |
| Anti-patterns | https://cadence-lang.org/docs/anti-patterns |
| Testing framework | https://cadence-lang.org/docs/testing-framework |
| Flow NFT Standard | https://github.com/onflow/flow-nft |
| Flow FT Standard | https://github.com/onflow/flow-ft |
Tip: Append
.mdxto anycadence-lang.orgdoc URL to get raw markdown.
AI Tools
Cadence is designed for AI-native development with three integrations:
| Tool | Description |
|---|---|
| Skills | Install with npx skills add outblock/cadence-lang.org |
| MCP Server | https://cadence-mcp.up.railway.app/mcp â docs search, code checking, type inspection |
| LLM Endpoints | /llms.txt (index) and /llms-full.txt (complete docs) |
MCP Quick Start
Claude Code:
claude mcp add cadence-mcp -- npx -y mcp-remote https://cadence-mcp.up.railway.app/mcp
Claude Desktop / Cursor / Antigravity / OpenCode:
{
"mcpServers": {
"cadence": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://cadence-mcp.up.railway.app/mcp"]
}
}
}
MCP Tools: search_docs, get_doc, browse_docs, cadence_check, cadence_hover, cadence_definition, cadence_symbols. All LSP tools support mainnet/testnet imports.
1. Language Fundamentals
Types at a Glance
| Type | Example | Notes |
|---|---|---|
Int |
42 |
Arbitrary precision |
UInt64 |
100 |
64-bit unsigned |
UFix64 |
1.5 |
Fixed-point, 8 decimals. Use for balances |
Bool |
true |
|
String |
"hello" |
UTF-8 |
Address |
0x1234 |
Account address |
[T] |
[1, 2, 3] |
Array |
{K: V} |
{"a": 1} |
Dictionary |
T? |
nil |
Optional â nil-coalesce with ?? |
@T |
Resource â unique, non-copyable | |
&T |
Reference â borrow without moving |
Variables, Optionals, Functions
let x: Int = 42 // immutable
var name: String = "Cadence" // mutable
// Optionals
let val: Int? = nil
let result = val ?? 0 // nil-coalescing
if let v = val { } // optional binding
let forced = val! // force-unwrap â panics, avoid
// Functions
access(all) fun greet(name: String): String {
return "Hello, \(name)!"
}
// view = read-only (safe in scripts and pre/post)
access(all) view fun getBalance(): UFix64 { return self.balance }
// Pre/post conditions
access(all) fun withdraw(amount: UFix64): @Vault {
pre { amount > 0.0: "Amount must be positive" }
post { result.balance == amount: "Withdrawal amount mismatch" }
}
Struct vs Resource
// Struct â copyable value type
struct Point {
let x: Int; let y: Int
init(x: Int, y: Int) { self.x = x; self.y = y }
}
let a = Point(x: 1, y: 2)
let b = a // COPY â both exist independently
// Resource â unique, linear type (cannot copy)
resource Token {
let id: UInt64
init() { self.id = self.uuid }
}
let t1 <- create Token()
let t2 <- t1 // MOVE â t1 is no longer valid
destroy t2 // must explicitly destroy if not stored
Resource rules:
- Resources exist in exactly one place at a time.
- Every resource must be
saved, moved, ordestroyed before the current scope ends. - Use
@in type annotations:@NFT,@{UInt64: NFT}. - Use
<-to move,<-!for force-assign into optional (panics on conflict).
2. Access Control & Entitlements
Access Modifiers
| Modifier | Who can access |
|---|---|
access(all) |
Everyone (public read, callable by anyone) |
access(self) |
Only within the type itself |
access(contract) |
Same contract |
access(account) |
Same account |
access(E) |
Callers holding entitlement E |
Entitlements Pattern
access(all) entitlement Withdraw
access(all) entitlement Owner
access(all) resource Vault {
access(self) var balance: UFix64
// Anyone can read
access(all) view fun getBalance(): UFix64 { return self.balance }
// Anyone can deposit into you
access(all) fun deposit(from: @Vault) {
self.balance = self.balance + from.balance
destroy from
}
// Only authorized callers can withdraw
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
pre { self.balance >= amount: "Insufficient balance" }
self.balance = self.balance - amount
return <- create Vault(balance: amount)
}
}
// Getting an entitled reference
let vaultRef = signer.storage
.borrow<auth(Withdraw) &Vault>(from: /storage/vault)
?? panic("Could not borrow Vault")
References
let ref: &NFT = &myNFT // read-only reference
let authRef: auth(Withdraw) &Vault = &myVault // entitled reference
// From storage
let r = signer.storage.borrow<auth(Withdraw) &Vault>(from: /storage/vault)
?? panic("No vault found")
Matching Access Modifiers Required for Interface Implementations
Implementation members must use exactly the same access modifier as the interface:
access(all) resource interface I {
access(account) fun foo()
}
// BAD â access(all) is more permissive than access(account)
access(all) resource R: I {
access(all) fun foo() {}
}
// GOOD
access(all) resource R: I {
access(account) fun foo() {}
}
3. Capabilities
Capabilities are the mechanism for sharing access to stored objects. Nothing is public by default.
// 1. Save object to storage
signer.storage.save(<- create Vault(balance: 0.0), to: /storage/vault)
// 2. Issue a capability (creates a tracked controller)
let receiverCap = signer.capabilities.storage
.issue<&{FungibleToken.Receiver}>(/storage/vault)
// 3. Publish so others can borrow it
signer.capabilities.publish(receiverCap, at: /public/receiver)
// 4. Borrow from another account
let receiver = getAccount(address).capabilities
.borrow<&{FungibleToken.Receiver}>(/public/receiver)
?? panic("Account has no receiver capability")
// 5. Revoke when needed
let controller = signer.capabilities.storage
.getController(byCapabilityID: capID) ?? panic("Controller not found")
controller.delete()
// Issue an entitled capability (grants Withdraw to holder)
let ownerCap = signer.capabilities.storage
.issue<auth(Withdraw) &Vault>(/storage/vault)
Public Capability Acquisition Returns Non-Optional
capabilities.get<T> returns an invalid capability (not nil) when no capability exists or when T mismatches. Check validity with .check():
let capability = account.capabilities.get<&MyNFT.Collection>(/public/NFTCollection)
if !capability.check() {
// Handle invalid capability (ID == 0, borrow returns nil)
}
4. Transactions & Scripts
Transaction Structure
transaction(amount: UFix64, recipient: Address) {
// Declare fields shared across phases
let vaultRef: auth(FungibleToken.Withdraw) &FungibleToken.Vault
// Access accounts â the ONLY phase with account access
prepare(signer: auth(BorrowValue) &Account) {
self.vaultRef = signer.storage
.borrow<auth(FungibleToken.Withdraw) &FungibleToken.Vault>(
from: /storage/vault
) ?? panic("No vault found")
}
pre { amount > 0.0: "Amount must be positive" }
execute {
let tokens <- self.vaultRef.withdraw(amount: amount)
let receiver = getAccount(recipient).capabilities
.borrow<&{FungibleToken.Receiver}>(/public/receiver)
?? panic("Recipient has no receiver")
receiver.deposit(from: <- tokens)
}
post { /* verify post-conditions */ }
}
Phases: prepare â pre â execute â post
Common Signer Authorizations
prepare(signer: auth(SaveValue) &Account) // save to storage
prepare(signer: auth(BorrowValue) &Account) // borrow from storage
prepare(signer: auth(LoadValue) &Account) // load (remove) from storage
prepare(signer: auth(IssueStorageCapabilityController) &Account) // issue capabilities
prepare(signer: auth(PublishCapability) &Account) // publish capabilities
prepare(signer: auth(Contracts) &Account) // deploy/update contracts
prepare(signer: auth(Keys) &Account) // manage account keys
Scripts (read-only, free, no transaction needed)
access(all) fun main(address: Address): UFix64 {
return getAccount(address).capabilities
.borrow<&{FungibleToken.Balance}>(/public/balance)
?.balance ?? 0.0
}
5. Account Storage
// Save â moves resource into storage
signer.storage.save(<- create NFT(), to: /storage/myNFT)
// Borrow â get a reference without removing
let ref = signer.storage.borrow<&NFT>(from: /storage/myNFT)
// Load â removes and returns (must handle the resource)
let nft <- signer.storage.load<@NFT>(from: /storage/myNFT)!
// Check existence and type
let t: Type? = signer.storage.type(at: /storage/myNFT)
if t != nil { /* already exists */ }
// Iterate all stored values
signer.storage.forEachStored(fun (path: StoragePath, type: Type): Bool {
return true // true = continue, false = stop
})
6. NFT Contract (Production Pattern)
access(all) contract BasicNFT {
access(all) var totalSupply: UInt64
access(all) event Minted(id: UInt64)
access(all) event Withdraw(id: UInt64, from: Address?)
access(all) event Deposit(id: UInt64, to: Address?)
access(all) entitlement Withdraw
access(all) let CollectionStoragePath: StoragePath
access(all) let CollectionPublicPath: PublicPath
access(all) resource NFT {
access(all) let id: UInt64
access(all) let metadata: {String: String}
init(metadata: {String: String}) {
self.id = self.uuid
BasicNFT.totalSupply = BasicNFT.totalSupply + 1
emit Minted(id: self.id)
}
}
access(all) resource Collection {
access(self) var ownedNFTs: @{UInt64: NFT}
access(all) fun deposit(token: @NFT) {
emit Deposit(id: token.id, to: self.owner?.address)
self.ownedNFTs[token.id] <-! token
}
access(Withdraw) fun withdraw(id: UInt64): @NFT {
let token <- self.ownedNFTs.remove(key: id)
?? panic("NFT \(id) not found in collection")
emit Withdraw(id: token.id, from: self.owner?.address)
return <- token
}
access(all) fun getIDs(): [UInt64] { return self.ownedNFTs.keys }
access(all) view fun borrowNFT(_ id: UInt64): &NFT? {
return &self.ownedNFTs[id]
}
init() { self.ownedNFTs <- {} }
}
access(all) fun createEmptyCollection(): @Collection {
return <- create Collection()
}
init() {
self.totalSupply = 0
self.CollectionStoragePath = /storage/basicNFTCollection
self.CollectionPublicPath = /public/basicNFTCollection
}
}
NFT Setup Transaction
transaction {
prepare(signer: auth(SaveValue, IssueStorageCapabilityController, PublishCapability) &Account) {
// Idempotent â skip if already set up
if signer.storage.type(at: BasicNFT.CollectionStoragePath) != nil { return }
signer.storage.save(<- BasicNFT.createEmptyCollection(), to: BasicNFT.CollectionStoragePath)
let cap = signer.capabilities.storage
.issue<&BasicNFT.Collection>(BasicNFT.CollectionStoragePath)
signer.capabilities.publish(cap, at: BasicNFT.CollectionPublicPath)
}
}
Mint & Transfer
// Mint (admin transaction)
transaction(recipient: Address, name: String) {
execute {
let nft <- create BasicNFT.NFT(metadata: {"name": name})
getAccount(recipient).capabilities
.borrow<&BasicNFT.Collection>(BasicNFT.CollectionPublicPath)
?.deposit(token: <- nft)
?? panic("Recipient has no collection")
}
}
// Transfer (user transaction)
transaction(recipient: Address, nftID: UInt64) {
let withdrawRef: auth(BasicNFT.Withdraw) &BasicNFT.Collection
prepare(signer: auth(BorrowValue) &Account) {
self.withdrawRef = signer.storage
.borrow<auth(BasicNFT.Withdraw) &BasicNFT.Collection>(
from: BasicNFT.CollectionStoragePath
) ?? panic("No collection")
}
execute {
let nft <- self.withdrawRef.withdraw(id: nftID)
getAccount(recipient).capabilities
.borrow<&BasicNFT.Collection>(BasicNFT.CollectionPublicPath)
?.deposit(token: <- nft)
?? panic("Recipient has no collection")
}
}
7. Fungible Token (Production Pattern)
access(all) contract BasicToken {
access(all) var totalSupply: UFix64
access(all) entitlement Withdraw
access(all) resource Vault {
access(all) var balance: UFix64
init(balance: UFix64) { self.balance = balance }
access(all) fun deposit(from: @Vault) {
self.balance = self.balance + from.balance
destroy from
}
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
pre { self.balance >= amount: "Insufficient balance" }
self.balance = self.balance - amount
return <- create Vault(balance: amount)
}
}
access(all) fun createEmptyVault(): @Vault {
return <- create Vault(balance: 0.0)
}
init() { self.totalSupply = 0.0 }
}
Cadence 1.0 Token Standard Changes
FungibleToken.Vault and NonFungibleToken.NFT / NonFungibleToken.Collection are now interfaces, not concrete types. Update all references:
// Before (Cadence 0.x)
fun deposit(from: @FungibleToken.Vault)
// After (Cadence 1.0)
fun deposit(from: @{FungibleToken.Vault})
Why vaults beat ledgers:
| Ledger (Solidity) | Vault (Cadence) |
|---|---|
| Balances in a contract mapping | Each user holds their own Vault |
| Reentrancy attacks possible | Resources move atomically |
| Admin can modify any balance | Only the holder can access their Vault |
8. Security Rules (Non-Negotiable)
S1 â Least Privilege
Start access(self), widen only when justified. Never access(all) on state-modifying functions.
// BAD â anyone drains your vault
access(all) fun withdraw(amount: UFix64): @Vault { ... }
// GOOD â requires entitlement
access(Withdraw) fun withdraw(amount: UFix64): @Vault { ... }
S2 â Never Pass Fully Authorized Account Refs as Parameters
// BAD â gives full storage access to callee
access(all) fun setup(admin: auth(Storage) &Account) { ... }
// GOOD â use a narrowly-scoped capability instead
access(all) fun setup(adminCap: Capability<&Admin>) { ... }
S3 â Always Validate with Pre/Post Conditions
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
pre { amount > 0.0: "Amount must be positive" }
pre { self.balance >= amount: "Insufficient balance" }
post { result.balance == amount: "Withdrawal amount mismatch" }
post { self.balance == before(self.balance) - amount: "Balance accounting error" }
}
S4 â Meaningful Panic Messages (Never Force-Unwrap Silently)
// BAD â silent crash
let ref = cap.borrow()!
// GOOD â actionable message
let ref = cap.borrow()
?? panic("Could not borrow Vault â capability may be revoked or incorrect type")
S5 â Private Data Is Not Secret
access(self) controls programmatic access, not blockchain visibility. All storage is publicly readable off-chain. Never store secrets, private keys, or PII on-chain.
S6 â Never Hard-Code Addresses
// BAD
import FungibleToken from 0xf233dcee88fe0abe
// GOOD (uses flow.json aliases, resolved at deployment)
import "FungibleToken"
S7 â Check Before Setup (Idempotent Transactions)
transaction {
prepare(signer: auth(SaveValue) &Account) {
if signer.storage.type(at: /storage/vault) != nil { return }
signer.storage.save(<- MyToken.createEmptyVault(), to: /storage/vault)
}
}
9. Testing
Tests are Cadence files using the Test standard library, executed via Flow CLI.
import Test
let blockchain = Test.newEmulatorBlockchain()
let admin = blockchain.createAccount()
access(all) fun setup() {
let err = blockchain.deployContract(
name: "MyNFT",
code: Test.readFile("../contracts/MyNFT.cdc"),
account: admin,
arguments: []
)
Test.expect(err, Test.beNil())
}
access(all) fun testMint() {
let tx = Test.Transaction(
code: Test.readFile("../transactions/mint.cdc"),
authorizers: [admin.address],
signers: [admin],
arguments: ["My NFT"]
)
Test.expect(blockchain.executeTransaction(tx), Test.beSucceeded())
let events = Test.eventsOfType(Type<MyNFT.Minted>())
Test.assertEqual(1, events.length)
}
access(all) fun testQuery() {
let result = blockchain.executeScript(
Test.readFile("../scripts/get_ids.cdc"),
[admin.address]
)
let ids = result.returnValue! as! [UInt64]
Test.assertEqual(1, ids.length)
}
Common assertions:
Test.assertEqual(expected, actual)
Test.assert(condition, message: "reason")
Test.expect(result, Test.beSucceeded())
Test.expect(result, Test.beFailed())
Test.expect(err, Test.beNil())
Run tests:
flow test tests/my_test.cdc
flow test --cover tests/my_test.cdc
10. Naming Conventions
| Construct | Convention | Example |
|---|---|---|
| Contract, Resource, Struct, Interface, Event | PascalCase |
FlowToken, TokenVault |
| Function, variable, parameter | camelCase |
getBalance(), totalSupply |
| Storage/public paths | let fields on the contract |
let VaultStoragePath: StoragePath |
| Entitlements | PascalCase |
entitlement Withdraw |
Never hardcode path strings in transactions â always reference the contract’s published path constants.
11. Anti-Patterns
A1 â Never Pass Fully Authorized Account Refs as Parameters
A function accepting auth(Storage) &Account can access all storage â drain vaults, steal NFTs.
// BAD â callee can withdraw FLOW or modify anything
access(all) fun transferNFT(id: UInt64, owner: auth(Storage) &Account) { ... }
// GOOD â authenticate via resources/capabilities
access(all) fun transferNFT(id: UInt64, collectionCap: Capability<auth(Withdraw) &Collection>) { ... }
A2 â Public Functions Should Be Read-Only or Explicitly Entitled
Only view functions and functions everyone should call should be access(all). State-modifying functions require entitlements.
A3 â Capability-Typed Public Fields Are Security Holes
Capabilities are value types â a public field can be copied by anyone:
// BAD â anyone copies the capability and calls its functions
access(all) var adminCap: Capability<&Admin>
// GOOD
access(self) var adminCap: Capability<&Admin>
access(Owner) fun getAdminCap(): Capability<&Admin> { return self.adminCap }
A4 â Never Make Admin Creation Functions Public
// BAD â anyone mints admin access
access(all) fun createAdmin(): @Admin { return <- create Admin() }
// GOOD â create once in init, existing admins create new ones
init() {
self.account.storage.save(<- create Admin(), to: /storage/currencyAdmin)
}
A5 â Never Emit Events or Modify Contract State in Struct Initializers
Structs are public and can be created by anyone. Side effects in init() can be exploited:
// BAD â anyone spams events and overflows nextPlayID
access(all) struct Play {
init() {
TopShot.nextPlayID = TopShot.nextPlayID + 1 // BAD
emit PlayCreated(id: self.playID) // BAD
}
}
// GOOD â state changes happen only inside admin resource
access(all) resource Admin {
access(all) fun createPlay() {
var newPlay = Play()
TopShot.nextPlayID = TopShot.nextPlayID + UInt32(1)
emit PlayCreated(id: newPlay.playID)
}
}
A6 â Complex/Capability Fields Must Be access(self)
access(all) on arrays, dictionaries, structs, resources, or capabilities allows direct mutation:
// BAD
access(all) var adminCap: Capability<&Admin>
// GOOD
access(self) var adminCap: Capability<&Admin>
12. Capability Bootstrapping (Inbox API)
When account A needs to send a capability to account B, a single transaction cannot be signed by both accounts simultaneously. Use the Inbox API:
Step 1 â Provider publishes the capability to recipient’s inbox:
import "BasicNFT"
transaction(receiver: Address, name: String) {
prepare(signer: auth(IssueStorageCapabilityController, PublishInboxCapability) &Account) {
let capability = signer.capabilities.storage
.issue<&BasicNFT.Minter>(BasicNFT.minterPath)
let controller = signer.capabilities.storage
.getController(byCapabilityID: capability.id)
?? panic("Controller not found")
controller.setTag(name)
signer.inbox.publish(capability, name: name, recipient: receiver)
}
}
Step 2 â Recipient claims the capability:
import "BasicNFT"
transaction(provider: Address, name: String) {
prepare(signer: auth(ClaimInboxCapability, SaveValue) &Account) {
let capability = signer.inbox.claim<&BasicNFT.Minter>(name, provider: provider)
?? panic("No capability named '\(name)' from \(provider)")
signer.storage.save(capability, to: BasicNFT.minterPath)
}
}
The provider can call
signer.inbox.unpublish(name)to retract before the recipient claims.
13. Performance: Prefer borrow Over load/save
load() moves the resource out of storage (expensive), save() moves it back (expensive again). borrow() returns an in-place reference â always prefer it.
// BAD â unnecessary round-trip moves the entire resource
transaction {
prepare(acct: auth(LoadValue, SaveValue) &Account) {
let vault <- acct.storage.load<@ExampleToken.Vault>(from: /storage/exampleToken)!
let burned <- vault.withdraw(amount: 10)
destroy burned
acct.storage.save(<- vault, to: /storage/exampleToken)
}
}
// GOOD â borrow a reference, mutate in-place, no save needed
transaction {
prepare(acct: auth(BorrowValue) &Account) {
let vault = acct.storage.borrow<&ExampleToken.Vault>(from: /storage/exampleToken)!
let burned <- vault.withdraw(amount: 10)
destroy burned
}
}
14. Measuring Time
Flow produces blocks approximately every 0.8 seconds. Both block timestamp and block height are available on-chain.
let block = getCurrentBlock()
block.timestamp // Unix timestamp (UFix64, seconds)
block.height // Block number (UInt64)
let pastBlock = getBlock(at: 70001)
pastBlock?.timestamp
pastBlock?.height
| Method | Use when |
|---|---|
getCurrentBlock().timestamp |
Acceptable for events lasting hours/days. Accurate to ~10 seconds. |
getCurrentBlock().height |
More manipulation-resistant. Requires off-chain rate estimation. |
Rules:
- Timestamps cannot go backwards and cannot be more than 10 seconds ahead of the previous block.
- Never hardcode an assumed block rate â it changes over time.
- Auctions and time-locks should have an extension mechanism.
15. Contract Upgrades
Compatible Changes (Preferred)
Cadence supports in-place upgrades for additive changes: adding new fields (with defaults), new functions, new events. Use flow contracts update.
Incompatible Changes
Option A â New address (safest):
- Deploy new contract to a new account.
- Increment path suffixes (
/public/MyVault002). - Write upgrade transactions to migrate users’ resources.
Option B â Same address (risky, last resort):
- Delete all resources in the contract account (e.g., Admin resource).
- Delete the contract from the account.
- Deploy the new contract.
â ï¸ If any user account holds
structsorresourcesfrom the old contract version, those will fail to load and crash any transaction that touches them. Only do this before any users have received such objects.
Always communicate upgrades in advance â at least one week before deploying, share the proposed transaction and branch/commit hash with the community.
16. Cadence 1.0 Key Changes Summary
Cadence 1.0 (Crescendo upgrade, September 2024) introduced breaking changes. Key highlights:
| Change | Impact |
|---|---|
| View functions | view keyword enforces no mutations; required in pre/post conditions. |
| Interface inheritance | Interfaces can inherit from other interfaces of the same kind. |
| Entitlements | Replace restricted types; control access via access(E) and auth(E) &T. |
pub/priv removed |
Use access(all) and access(self) instead. |
| Intersection types | {I1, I2} replaces AnyStruct{I1, I2} restricted types. |
| Account API | AuthAccount/PublicAccount replaced by &Account with entitlements. |
| Capability Controllers | Replace linking-based API; use capabilities.storage.issue, publish, getController. |
| Resource reference invalidation | References invalidated when referenced resource is moved. |
| Force destruction | destroy methods removed; resources destroyed implicitly; use ResourceDestroyed event. |
| Token standards | FungibleToken.Vault, NonFungibleToken.NFT, NonFungibleToken.Collection are now interfaces (@{Type}). |
Emulator Import Addresses (Cadence 1.0)
| Contract | Emulator | Testing Framework |
|---|---|---|
FungibleToken |
0xee82856bf20e2aa6 |
0x0000000000000002 |
NonFungibleToken |
0xf8d6e0586b0a20c7 |
0x0000000000000001 |
MetadataViews |