Production-ready smart contract templates for the Flow blockchain. Each template includes comprehensive functionality, security best practices, and detailed documentation.
// NFT Collection Contract
import NonFungibleToken from 0x1d7e57aa55817448
import MetadataViews from 0x1d7e57aa55817448
pub contract FlowDevKitNFT: NonFungibleToken {
pub var totalSupply: UInt64
pub event ContractInitialized()
pub event Withdraw(id: UInt64, from: Address?)
pub event Deposit(id: UInt64, to: Address?)
pub event Minted(id: UInt64, recipient: Address)
pub let CollectionStoragePath: StoragePath
pub let CollectionPublicPath: PublicPath
pub let MinterStoragePath: StoragePath
pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
pub let id: UInt64
pub let name: String
pub let description: String
pub let thumbnail: String
access(self) let royalties: [MetadataViews.Royalty]
access(self) let metadata: {String: AnyStruct}
init(
id: UInt64,
name: String,
description: String,
thumbnail: String,
royalties: [MetadataViews.Royalty],
metadata: {String: AnyStruct},
) {
self.id = id
self.name = name
self.description = description
self.thumbnail = thumbnail
self.royalties = royalties
self.metadata = metadata
}
pub fun getViews(): [Type] {
return [
Type<MetadataViews.Display>(),
Type<MetadataViews.Royalties>(),
Type<MetadataViews.ExternalURL>(),
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.Serial>(),
Type<MetadataViews.Traits>()
]
}
pub fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<MetadataViews.Display>():
return MetadataViews.Display(
name: self.name,
description: self.description,
thumbnail: MetadataViews.HTTPFile(
url: self.thumbnail
)
)
case Type<MetadataViews.Royalties>():
return MetadataViews.Royalties(
self.royalties
)
case Type<MetadataViews.Serial>():
return MetadataViews.Serial(
self.id
)
case Type<MetadataViews.ExternalURL>():
return MetadataViews.ExternalURL("https://flowdevkit.com/nft/".concat(self.id.toString()))
case Type<MetadataViews.NFTCollectionData>():
return MetadataViews.NFTCollectionData(
storagePath: FlowDevKitNFT.CollectionStoragePath,
publicPath: FlowDevKitNFT.CollectionPublicPath,
providerPath: /private/FlowDevKitNFTCollection,
publicCollection: Type<&FlowDevKitNFT.Collection{FlowDevKitNFT.FlowDevKitNFTCollectionPublic}>(),
publicLinkedType: Type<&FlowDevKitNFT.Collection{FlowDevKitNFT.FlowDevKitNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
providerLinkedType: Type<&FlowDevKitNFT.Collection{FlowDevKitNFT.FlowDevKitNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
return <-FlowDevKitNFT.createEmptyCollection()
})
)
case Type<MetadataViews.NFTCollectionDisplay>():
let media = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://flowdevkit.com/logo.png"
),
mediaType: "image/png"
)
return MetadataViews.NFTCollectionDisplay(
name: "FlowDevKit NFT Collection",
description: "A collection of NFTs created with FlowDevKit templates",
externalURL: MetadataViews.ExternalURL("https://flowdevkit.com"),
squareImage: media,
bannerImage: media,
socials: {
"twitter": MetadataViews.ExternalURL("https://twitter.com/flowdevkit")
}
)
case Type<MetadataViews.Traits>():
let traitsView = MetadataViews.dictToTraits(dict: self.metadata, excludedNames: [])
return traitsView
}
return nil
}
}
pub resource interface FlowDevKitNFTCollectionPublic {
pub fun deposit(token: @NonFungibleToken.NFT)
pub fun getIDs(): [UInt64]
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
pub fun borrowFlowDevKitNFT(id: UInt64): &FlowDevKitNFT.NFT? {
post {
(result == nil) || (result?.id == id):
"Cannot borrow FlowDevKitNFT reference: the ID of the returned reference is incorrect"
}
}
}
pub resource Collection: FlowDevKitNFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
init () {
self.ownedNFTs <- {}
}
pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
emit Withdraw(id: token.id, from: self.owner?.address)
return <-token
}
pub fun deposit(token: @NonFungibleToken.NFT) {
let token <- token as! @FlowDevKitNFT.NFT
let id: UInt64 = token.id
let oldToken <- self.ownedNFTs[id] <- token
emit Deposit(id: id, to: self.owner?.address)
destroy oldToken
}
pub fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
}
pub fun borrowFlowDevKitNFT(id: UInt64): &FlowDevKitNFT.NFT? {
if self.ownedNFTs[id] != nil {
let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
return ref as! &FlowDevKitNFT.NFT
}
return nil
}
pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
let FlowDevKitNFT = nft as! &FlowDevKitNFT.NFT
return FlowDevKitNFT as &AnyResource{MetadataViews.Resolver}
}
destroy() {
destroy self.ownedNFTs
}
}
pub fun createEmptyCollection(): @NonFungibleToken.Collection {
return <- create Collection()
}
pub resource NFTMinter {
pub fun mintNFT(
recipient: &{NonFungibleToken.CollectionPublic},
name: String,
description: String,
thumbnail: String,
royalties: [MetadataViews.Royalty]
) {
let metadata: {String: AnyStruct} = {}
let currentBlock = getCurrentBlock()
metadata["mintedBlock"] = currentBlock.height
metadata["mintedTime"] = currentBlock.timestamp
metadata["minter"] = recipient.owner!.address
var newNFT <- create NFT(
id: FlowDevKitNFT.totalSupply,
name: name,
description: description,
thumbnail: thumbnail,
royalties: royalties,
metadata: metadata,
)
let mintedID = newNFT.id
recipient.deposit(token: <-newNFT)
emit Minted(
id: mintedID,
recipient: recipient.owner!.address,
)
FlowDevKitNFT.totalSupply = FlowDevKitNFT.totalSupply + UInt64(1)
}
}
init() {
self.totalSupply = 0
self.CollectionStoragePath = /storage/FlowDevKitNFTCollection
self.CollectionPublicPath = /public/FlowDevKitNFTCollection
self.MinterStoragePath = /storage/FlowDevKitNFTMinter
let collection <- create Collection()
self.account.save(<-collection, to: self.CollectionStoragePath)
self.account.link<&FlowDevKitNFT.Collection{NonFungibleToken.CollectionPublic, FlowDevKitNFT.FlowDevKitNFTCollectionPublic, MetadataViews.ResolverCollection}>(
self.CollectionPublicPath,
target: self.CollectionStoragePath
)
let minter <- create NFTMinter()
self.account.save(<-minter, to: self.MinterStoragePath)
emit ContractInitialized()
}
}// Fungible Token Contract
import FungibleToken from 0xf233dcee88fe0abe
pub contract FlowDevKitToken: FungibleToken {
pub var totalSupply: UFix64
pub event TokensInitialized(initialSupply: UFix64)
pub event TokensWithdrawn(amount: UFix64, from: Address?)
pub event TokensDeposited(amount: UFix64, to: Address?)
pub event TokensMinted(amount: UFix64)
pub event TokensBurned(amount: UFix64)
pub let VaultStoragePath: StoragePath
pub let VaultPublicPath: PublicPath
pub let ReceiverPublicPath: PublicPath
pub let AdminStoragePath: StoragePath
pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance {
pub var balance: UFix64
init(balance: UFix64) {
self.balance = balance
}
pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
self.balance = self.balance - amount
emit TokensWithdrawn(amount: amount, from: self.owner?.address)
return <-create Vault(balance: amount)
}
pub fun deposit(from: @FungibleToken.Vault) {
let vault <- from as! @FlowDevKitToken.Vault
self.balance = self.balance + vault.balance
emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
vault.balance = 0.0
destroy vault
}
destroy() {
if self.balance > 0.0 {
FlowDevKitToken.totalSupply = FlowDevKitToken.totalSupply - self.balance
}
}
}
pub fun createEmptyVault(): @FungibleToken.Vault {
return <-create Vault(balance: 0.0)
}
pub resource Administrator {
pub fun createNewMinter(allowedAmount: UFix64): @Minter {
emit MinterCreated(allowedAmount: allowedAmount)
return <-create Minter(allowedAmount: allowedAmount)
}
pub fun createNewBurner(): @Burner {
emit BurnerCreated()
return <-create Burner()
}
}
pub resource Minter {
pub var allowedAmount: UFix64
pub fun mintTokens(amount: UFix64): @FlowDevKitToken.Vault {
pre {
amount > 0.0: "Amount minted must be greater than zero"
amount <= self.allowedAmount: "Amount minted must be less than the allowed amount"
}
FlowDevKitToken.totalSupply = FlowDevKitToken.totalSupply + amount
self.allowedAmount = self.allowedAmount - amount
emit TokensMinted(amount: amount)
return <-create Vault(balance: amount)
}
init(allowedAmount: UFix64) {
self.allowedAmount = allowedAmount
}
}
pub resource Burner {
pub fun burnTokens(from: @FungibleToken.Vault) {
let vault <- from as! @FlowDevKitToken.Vault
let amount = vault.balance
destroy vault
emit TokensBurned(amount: amount)
}
}
init() {
self.totalSupply = 1000.0
self.VaultStoragePath = /storage/FlowDevKitTokenVault
self.VaultPublicPath = /public/FlowDevKitTokenVault
self.ReceiverPublicPath = /public/FlowDevKitTokenReceiver
self.AdminStoragePath = /storage/FlowDevKitTokenAdmin
let vault <- create Vault(balance: self.totalSupply)
self.account.save(<-vault, to: self.VaultStoragePath)
self.account.link<&FlowDevKitToken.Vault{FungibleToken.Receiver}>(
self.ReceiverPublicPath,
target: self.VaultStoragePath
)
self.account.link<&FlowDevKitToken.Vault{FungibleToken.Balance}>(
self.VaultPublicPath,
target: self.VaultStoragePath
)
let admin <- create Administrator()
self.account.save(<-admin, to: self.AdminStoragePath)
emit TokensInitialized(initialSupply: self.totalSupply)
}
}// Staking Contract
import FungibleToken from 0xf233dcee88fe0abe
import FlowDevKitToken from 0x01
pub contract FlowDevKitStaking {
pub var totalStaked: UFix64
pub var rewardRate: UFix64 // Rewards per second per token
pub var minimumStakingPeriod: UFix64 // In seconds
pub event Staked(user: Address, amount: UFix64, lockPeriod: UFix64)
pub event Unstaked(user: Address, amount: UFix64, rewards: UFix64)
pub event RewardsClaimed(user: Address, amount: UFix64)
pub event RewardRateUpdated(newRate: UFix64)
pub let StakingStoragePath: StoragePath
pub let StakingPublicPath: PublicPath
pub let AdminStoragePath: StoragePath
pub struct StakeInfo {
pub let amount: UFix64
pub let stakeTime: UFix64
pub let lockPeriod: UFix64
pub let lastRewardTime: UFix64
init(amount: UFix64, lockPeriod: UFix64) {
self.amount = amount
self.stakeTime = getCurrentBlock().timestamp
self.lockPeriod = lockPeriod
self.lastRewardTime = getCurrentBlock().timestamp
}
}
pub resource interface StakingPublic {
pub fun getStakeInfo(): StakeInfo?
pub fun calculateRewards(): UFix64
pub fun getTotalStaked(): UFix64
}
pub resource StakingAccount: StakingPublic {
access(self) var stakeInfo: StakeInfo?
access(self) var tokenVault: @FlowDevKitToken.Vault
init() {
self.stakeInfo = nil
self.tokenVault <- FlowDevKitToken.createEmptyVault() as! @FlowDevKitToken.Vault
}
pub fun stake(tokens: @FlowDevKitToken.Vault, lockPeriod: UFix64) {
pre {
self.stakeInfo == nil: "Already staking tokens"
lockPeriod >= FlowDevKitStaking.minimumStakingPeriod: "Lock period too short"
tokens.balance > 0.0: "Cannot stake zero tokens"
}
let amount = tokens.balance
self.tokenVault.deposit(from: <-tokens)
self.stakeInfo = StakeInfo(amount: amount, lockPeriod: lockPeriod)
FlowDevKitStaking.totalStaked = FlowDevKitStaking.totalStaked + amount
emit Staked(user: self.owner!.address, amount: amount, lockPeriod: lockPeriod)
}
pub fun unstake(): @FlowDevKitToken.Vault {
pre {
self.stakeInfo != nil: "No tokens staked"
}
let stakeInfo = self.stakeInfo!
let currentTime = getCurrentBlock().timestamp
assert(
currentTime >= stakeInfo.stakeTime + stakeInfo.lockPeriod,
message: "Tokens are still locked"
)
let rewards = self.calculateRewards()
let stakedAmount = stakeInfo.amount
// Reset staking info
self.stakeInfo = nil
// Update total staked
FlowDevKitStaking.totalStaked = FlowDevKitStaking.totalStaked - stakedAmount
// Withdraw staked tokens
let stakedTokens <- self.tokenVault.withdraw(amount: stakedAmount)
emit Unstaked(user: self.owner!.address, amount: stakedAmount, rewards: rewards)
return <-stakedTokens
}
pub fun claimRewards(): @FlowDevKitToken.Vault {
pre {
self.stakeInfo != nil: "No tokens staked"
}
let rewards = self.calculateRewards()
if rewards > 0.0 {
// Update last reward time
let currentStakeInfo = self.stakeInfo!
self.stakeInfo = StakeInfo(amount: currentStakeInfo.amount, lockPeriod: currentStakeInfo.lockPeriod)
emit RewardsClaimed(user: self.owner!.address, amount: rewards)
// In a real implementation, rewards would come from a reward pool
// For this example, we'll create new tokens (this would require minter access)
return <-FlowDevKitToken.createEmptyVault() as! @FlowDevKitToken.Vault
}
return <-FlowDevKitToken.createEmptyVault() as! @FlowDevKitToken.Vault
}
pub fun getStakeInfo(): StakeInfo? {
return self.stakeInfo
}
pub fun calculateRewards(): UFix64 {
if self.stakeInfo == nil {
return 0.0
}
let stakeInfo = self.stakeInfo!
let currentTime = getCurrentBlock().timestamp
let stakingDuration = currentTime - stakeInfo.lastRewardTime
return stakeInfo.amount * FlowDevKitStaking.rewardRate * stakingDuration
}
pub fun getTotalStaked(): UFix64 {
return self.stakeInfo?.amount ?? 0.0
}
destroy() {
destroy self.tokenVault
}
}
pub fun createStakingAccount(): @StakingAccount {
return <-create StakingAccount()
}
pub resource Administrator {
pub fun updateRewardRate(newRate: UFix64) {
FlowDevKitStaking.rewardRate = newRate
emit RewardRateUpdated(newRate: newRate)
}
pub fun updateMinimumStakingPeriod(newPeriod: UFix64) {
FlowDevKitStaking.minimumStakingPeriod = newPeriod
}
}
// Public functions
pub fun getTotalStaked(): UFix64 {
return self.totalStaked
}
pub fun getRewardRate(): UFix64 {
return self.rewardRate
}
pub fun getMinimumStakingPeriod(): UFix64 {
return self.minimumStakingPeriod
}
init() {
self.totalStaked = 0.0
self.rewardRate = 0.0001 // 0.01% per second
self.minimumStakingPeriod = 86400.0 // 1 day in seconds
self.StakingStoragePath = /storage/FlowDevKitStaking
self.StakingPublicPath = /public/FlowDevKitStaking
self.AdminStoragePath = /storage/FlowDevKitStakingAdmin
let admin <- create Administrator()
self.account.save(<-admin, to: self.AdminStoragePath)
}
}