From 16e8da7486e2aaefd691cc519901aa4b2d4724f1 Mon Sep 17 00:00:00 2001 From: avcdsld Date: Thu, 8 Aug 2024 03:13:22 +0900 Subject: [PATCH] fix error --- .../contracts/flow/contracts/SakutaroPoem.cdc | 6 +- .../flow/contracts/SakutaroPoemReplica.cdc | 2 + .../contracts/flow/contracts/core/Burner.cdc | 50 +++ .../flow/contracts/core/FlowToken.cdc | 297 ++++++++++++++++++ .../flow/contracts/core/FungibleToken.cdc | 250 +++++++++++++++ packages/contracts/flow/flow.json | 24 ++ 6 files changed, 627 insertions(+), 2 deletions(-) create mode 100644 packages/contracts/flow/contracts/core/Burner.cdc create mode 100644 packages/contracts/flow/contracts/core/FlowToken.cdc create mode 100644 packages/contracts/flow/contracts/core/FungibleToken.cdc diff --git a/packages/contracts/flow/contracts/SakutaroPoem.cdc b/packages/contracts/flow/contracts/SakutaroPoem.cdc index edead6e..486f4cb 100644 --- a/packages/contracts/flow/contracts/SakutaroPoem.cdc +++ b/packages/contracts/flow/contracts/SakutaroPoem.cdc @@ -7,7 +7,7 @@ // \____/ \__,_||_|\_\ \__,_| \__| \__,_||_| \___/ // // -import FungibleToken from 0xee82856bf20e2aa6 +import "FlowToken" import "NonFungibleToken" import "ViewResolver" import "MetadataViews" @@ -159,6 +159,8 @@ access(all) contract SakutaroPoem: NonFungibleToken { let id: UInt64 = token.id let oldToken <- self.ownedNFTs[id] <- token destroy oldToken + let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)! + SakutaroPoem.emitNFTUpdated(authTokenRef) } access(all) view fun getIDs(): [UInt64] { @@ -237,7 +239,7 @@ access(all) contract SakutaroPoem: NonFungibleToken { self.CollectionStoragePath = /storage/SakutaroPoemCollection self.totalSupply = 0 - let receiver = self.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + let receiver = self.account.capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver) self.royalties = [MetadataViews.Royalty(receiver: receiver, cut: 0.1, description: "39")] self.account.storage.save(<- create Collection(), to: self.CollectionStoragePath) diff --git a/packages/contracts/flow/contracts/SakutaroPoemReplica.cdc b/packages/contracts/flow/contracts/SakutaroPoemReplica.cdc index 528a4b2..404694e 100644 --- a/packages/contracts/flow/contracts/SakutaroPoemReplica.cdc +++ b/packages/contracts/flow/contracts/SakutaroPoemReplica.cdc @@ -108,6 +108,8 @@ access(all) contract SakutaroPoemReplica: NonFungibleToken { let id: UInt64 = token.id let oldToken <- self.ownedNFTs[id] <- token destroy oldToken + let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)! + SakutaroPoemReplica.emitNFTUpdated(authTokenRef) } access(all) view fun getIDs(): [UInt64] { diff --git a/packages/contracts/flow/contracts/core/Burner.cdc b/packages/contracts/flow/contracts/core/Burner.cdc new file mode 100644 index 0000000..66ba5f2 --- /dev/null +++ b/packages/contracts/flow/contracts/core/Burner.cdc @@ -0,0 +1,50 @@ +/// Burner is a contract that can facilitate the destruction of any resource on flow. +/// +/// Contributors +/// - Austin Kline - https://twitter.com/austin_flowty +/// - Deniz Edincik - https://twitter.com/bluesign +/// - Bastian Müller - https://twitter.com/turbolent +access(all) contract Burner { + /// When Crescendo (Cadence 1.0) is released, custom destructors will be removed from cadece. + /// Burnable is an interface meant to replace this lost feature, allowing anyone to add a callback + /// method to ensure they do not destroy something which is not meant to be, + /// or to add logic based on destruction such as tracking the supply of a FT Collection + /// + /// NOTE: The only way to see benefit from this interface + /// is to always use the burn method in this contract. Anyone who owns a resource can always elect **not** + /// to destroy a resource this way + access(all) resource interface Burnable { + access(contract) fun burnCallback() + } + + /// burn is a global method which will destroy any resource it is given. + /// If the provided resource implements the Burnable interface, + /// it will call the burnCallback method and then destroy afterwards. + access(all) fun burn(_ toBurn: @AnyResource?) { + if toBurn == nil { + destroy toBurn + return + } + let r <- toBurn! + + if let s <- r as? @{Burnable} { + s.burnCallback() + destroy s + } else if let arr <- r as? @[AnyResource] { + while arr.length > 0 { + let item <- arr.removeFirst() + self.burn(<-item) + } + destroy arr + } else if let dict <- r as? @{HashableStruct: AnyResource} { + let keys = dict.keys + while keys.length > 0 { + let item <- dict.remove(key: keys.removeFirst())! + self.burn(<-item) + } + destroy dict + } else { + destroy r + } + } +} \ No newline at end of file diff --git a/packages/contracts/flow/contracts/core/FlowToken.cdc b/packages/contracts/flow/contracts/core/FlowToken.cdc new file mode 100644 index 0000000..8d055e7 --- /dev/null +++ b/packages/contracts/flow/contracts/core/FlowToken.cdc @@ -0,0 +1,297 @@ +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" +import FungibleTokenMetadataViews from "FungibleTokenMetadataViews" + +access(all) contract FlowToken: FungibleToken { + + // Total supply of Flow tokens in existence + access(all) var totalSupply: UFix64 + + // Event that is emitted when tokens are withdrawn from a Vault + access(all) event TokensWithdrawn(amount: UFix64, from: Address?) + + // Event that is emitted when tokens are deposited to a Vault + access(all) event TokensDeposited(amount: UFix64, to: Address?) + + // Event that is emitted when new tokens are minted + access(all) event TokensMinted(amount: UFix64) + + // Event that is emitted when a new minter resource is created + access(all) event MinterCreated(allowedAmount: UFix64) + + // Event that is emitted when a new burner resource is created + access(all) event BurnerCreated() + + // Vault + // + // Each user stores an instance of only the Vault in their storage + // The functions in the Vault and governed by the pre and post conditions + // in FungibleToken when they are called. + // The checks happen at runtime whenever a function is called. + // + // Resources can only be created in the context of the contract that they + // are defined in, so there is no way for a malicious user to create Vaults + // out of thin air. A special Minter resource needs to be defined to mint + // new tokens. + // + access(all) resource Vault: FungibleToken.Vault { + + // holds the balance of a users tokens + access(all) var balance: UFix64 + + // initialize the balance at resource creation time + init(balance: UFix64) { + self.balance = balance + } + + /// Called when a fungible token is burned via the `Burner.burn()` method + access(contract) fun burnCallback() { + if self.balance > 0.0 { + FlowToken.totalSupply = FlowToken.totalSupply - self.balance + } + self.balance = 0.0 + } + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + return {self.getType(): true} + } + + access(all) view fun isSupportedVaultType(type: Type): Bool { + if (type == self.getType()) { return true } else { return false } + } + + /// Asks if the amount can be withdrawn from this vault + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool { + return amount <= self.balance + } + + // withdraw + // + // Function that takes an integer amount as an argument + // and withdraws that amount from the Vault. + // It creates a new temporary Vault that is used to hold + // the money that is being transferred. It returns the newly + // created Vault to the context that called so it can be deposited + // elsewhere. + // + access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { + self.balance = self.balance - amount + + // If the owner is the staking account, do not emit the contract defined events + // this is to help with the performance of the epoch transition operations + // Either way, event listeners should be paying attention to the + // FungibleToken.Withdrawn events anyway because those contain + // much more comprehensive metadata + // Additionally, these events will eventually be removed from this contract completely + // in favor of the FungibleToken events + if let address = self.owner?.address { + if address != 0xf8d6e0586b0a20c7 && + address != 0xf4527793ee68aede && + address != 0x9eca2b38b18b5dfe && + address != 0x8624b52f9ddcd04a + { + emit TokensWithdrawn(amount: amount, from: address) + } + } else { + emit TokensWithdrawn(amount: amount, from: nil) + } + return <-create Vault(balance: amount) + } + + // deposit + // + // Function that takes a Vault object as an argument and adds + // its balance to the balance of the owners Vault. + // It is allowed to destroy the sent Vault because the Vault + // was a temporary holder of the tokens. The Vault's balance has + // been consumed and therefore can be destroyed. + access(all) fun deposit(from: @{FungibleToken.Vault}) { + let vault <- from as! @FlowToken.Vault + self.balance = self.balance + vault.balance + + // If the owner is the staking account, do not emit the contract defined events + // this is to help with the performance of the epoch transition operations + // Either way, event listeners should be paying attention to the + // FungibleToken.Deposited events anyway because those contain + // much more comprehensive metadata + // Additionally, these events will eventually be removed from this contract completely + // in favor of the FungibleToken events + if let address = self.owner?.address { + if address != 0xf8d6e0586b0a20c7 && + address != 0xf4527793ee68aede && + address != 0x9eca2b38b18b5dfe && + address != 0x8624b52f9ddcd04a + { + emit TokensDeposited(amount: vault.balance, to: address) + } + } else { + emit TokensDeposited(amount: vault.balance, to: nil) + } + vault.balance = 0.0 + destroy vault + } + + /// Get all the Metadata Views implemented by FlowToken + /// + /// @return An array of Types defining the implemented views. This value will be used by + /// developers to know which parameter to pass to the resolveView() method. + /// + access(all) view fun getViews(): [Type]{ + return FlowToken.getContractViews(resourceType: nil) + } + + /// Get a Metadata View from FlowToken + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveView(_ view: Type): AnyStruct? { + return FlowToken.resolveContractView(resourceType: nil, viewType: view) + } + + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { + return <-create Vault(balance: 0.0) + } + } + + // createEmptyVault + // + // Function that creates a new Vault with a balance of zero + // and returns it to the calling context. A user must call this function + // and store the returned Vault in their storage in order to allow their + // account to be able to receive deposits of this token type. + // + access(all) fun createEmptyVault(vaultType: Type): @FlowToken.Vault { + return <-create Vault(balance: 0.0) + } + + /// Gets a list of the metadata views that this contract supports + access(all) view fun getContractViews(resourceType: Type?): [Type] { + return [Type(), + Type(), + Type(), + Type()] + } + + /// Get a Metadata View from FlowToken + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { + switch viewType { + case Type(): + return FungibleTokenMetadataViews.FTView( + ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTDisplay?, + ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? + ) + case Type(): + let media = MetadataViews.Media( + file: MetadataViews.HTTPFile( + url: FlowToken.getLogoURI() + ), + mediaType: "image/svg+xml" + ) + let medias = MetadataViews.Medias([media]) + return FungibleTokenMetadataViews.FTDisplay( + name: "FLOW Network Token", + symbol: "FLOW", + description: "FLOW is the native token for the Flow blockchain. It is required for securing the network, transaction fees, storage fees, staking, FLIP voting and may be used by applications built on the Flow Blockchain", + externalURL: MetadataViews.ExternalURL("https://flow.com"), + logos: medias, + socials: { + "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain") + } + ) + case Type(): + let vaultRef = FlowToken.account.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the contract's Vault!") + return FungibleTokenMetadataViews.FTVaultData( + storagePath: /storage/flowTokenVault, + receiverPath: /public/flowTokenReceiver, + metadataPath: /public/flowTokenBalance, + receiverLinkedType: Type<&{FungibleToken.Receiver, FungibleToken.Vault}>(), + metadataLinkedType: Type<&{FungibleToken.Balance, FungibleToken.Vault}>(), + createEmptyVaultFunction: (fun (): @{FungibleToken.Vault} { + return <-vaultRef.createEmptyVault() + }) + ) + case Type(): + return FungibleTokenMetadataViews.TotalSupply(totalSupply: FlowToken.totalSupply) + } + return nil + } + + access(all) resource Administrator { + // createNewMinter + // + // Function that creates and returns a new minter resource + // + access(all) fun createNewMinter(allowedAmount: UFix64): @Minter { + emit MinterCreated(allowedAmount: allowedAmount) + return <-create Minter(allowedAmount: allowedAmount) + } + } + + // Minter + // + // Resource object that token admin accounts can hold to mint new tokens. + // + access(all) resource Minter { + + // the amount of tokens that the minter is allowed to mint + access(all) var allowedAmount: UFix64 + + // mintTokens + // + // Function that mints new tokens, adds them to the total supply, + // and returns them to the calling context. + // + access(all) fun mintTokens(amount: UFix64): @FlowToken.Vault { + pre { + amount > UFix64(0): "Amount minted must be greater than zero" + amount <= self.allowedAmount: "Amount minted must be less than the allowed amount" + } + FlowToken.totalSupply = FlowToken.totalSupply + amount + self.allowedAmount = self.allowedAmount - amount + emit TokensMinted(amount: amount) + return <-create Vault(balance: amount) + } + + init(allowedAmount: UFix64) { + self.allowedAmount = allowedAmount + } + } + + /// Gets the Flow Logo XML URI from storage + access(all) view fun getLogoURI(): String { + return FlowToken.account.storage.copy(from: /storage/flowTokenLogoURI) ?? "" + } + + init() { + self.totalSupply = 0.0 + + // Create the Vault with the total supply of tokens and save it in storage + // + let vault <- create Vault(balance: self.totalSupply) + + self.account.storage.save(<-vault, to: /storage/flowTokenVault) + + // Create a public capability to the stored Vault that only exposes + // the `deposit` method through the `Receiver` interface + // + let receiverCapability = self.account.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + self.account.capabilities.publish(receiverCapability, at: /public/flowTokenReceiver) + + // Create a public capability to the stored Vault that only exposes + // the `balance` field through the `Balance` interface + // + let balanceCapability = self.account.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + self.account.capabilities.publish(balanceCapability, at: /public/flowTokenBalance) + + let admin <- create Administrator() + self.account.storage.save(<-admin, to: /storage/flowTokenAdmin) + + } +} \ No newline at end of file diff --git a/packages/contracts/flow/contracts/core/FungibleToken.cdc b/packages/contracts/flow/contracts/core/FungibleToken.cdc new file mode 100644 index 0000000..db3fc10 --- /dev/null +++ b/packages/contracts/flow/contracts/core/FungibleToken.cdc @@ -0,0 +1,250 @@ +/** + +# The Flow Fungible Token standard + +## `FungibleToken` contract + +If a users wants to deploy a new token contract, their contract +needs to implement the FungibleToken interface and their tokens +need to implement the interfaces defined in this contract. + +/// Contributors (please add to this list if you contribute!): +/// - Joshua Hannan - https://github.com/joshuahannan +/// - Bastian Müller - https://twitter.com/turbolent +/// - Dete Shirley - https://twitter.com/dete73 +/// - Bjarte Karlsen - https://twitter.com/0xBjartek +/// - Austin Kline - https://twitter.com/austin_flowty +/// - Giovanni Sanchez - https://twitter.com/gio_incognito +/// - Deniz Edincik - https://twitter.com/bluesign +/// - Jonny - https://github.com/dryruner +/// +/// Repo reference: https://github.com/onflow/flow-ft + +## `Vault` resource interface + +Each fungible token resource type needs to implement the `Vault` resource interface. + +## `Provider`, `Receiver`, and `Balance` resource interfaces + +These interfaces declare pre-conditions and post-conditions that restrict +the execution of the functions in the Vault. + +They are separate because it gives the user the ability to share +a reference to their Vault that only exposes the fields functions +in one or more of the interfaces. + +It also gives users the ability to make custom resources that implement +these interfaces to do various things with the tokens. +For example, a faucet can be implemented by conforming +to the Provider interface. + +*/ + +import "ViewResolver" +import "Burner" + +/// FungibleToken +/// +/// Fungible Token implementations should implement the fungible token +/// interface. +access(all) contract interface FungibleToken: ViewResolver { + + // An entitlement for allowing the withdrawal of tokens from a Vault + access(all) entitlement Withdraw + + /// The event that is emitted when tokens are withdrawn from a Vault + access(all) event Withdrawn(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64, balanceAfter: UFix64) + + /// The event that is emitted when tokens are deposited to a Vault + access(all) event Deposited(type: String, amount: UFix64, to: Address?, toUUID: UInt64, depositedUUID: UInt64, balanceAfter: UFix64) + + /// Event that is emitted when the global burn method is called with a non-zero balance + access(all) event Burned(type: String, amount: UFix64, fromUUID: UInt64) + + /// Balance + /// + /// The interface that provides standard functions\ + /// for getting balance information + /// + access(all) resource interface Balance { + access(all) var balance: UFix64 + } + + /// Provider + /// + /// The interface that enforces the requirements for withdrawing + /// tokens from the implementing type. + /// + /// It does not enforce requirements on `balance` here, + /// because it leaves open the possibility of creating custom providers + /// that do not necessarily need their own balance. + /// + access(all) resource interface Provider { + + /// Function to ask a provider if a specific amount of tokens + /// is available to be withdrawn + /// This could be useful to avoid panicing when calling withdraw + /// when the balance is unknown + /// Additionally, if the provider is pulling from multiple vaults + /// it only needs to check some of the vaults until the desired amount + /// is reached, potentially helping with performance. + /// + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool + + /// withdraw subtracts tokens from the implementing resource + /// and returns a Vault with the removed tokens. + /// + /// The function's access level is `access(Withdraw)` + /// So in order to access it, one would either need the object itself + /// or an entitled reference with `Withdraw`. + /// + access(Withdraw) fun withdraw(amount: UFix64): @{Vault} { + post { + // `result` refers to the return value + result.balance == amount: + "Withdrawal amount must be the same as the balance of the withdrawn Vault" + } + } + } + + /// Receiver + /// + /// The interface that enforces the requirements for depositing + /// tokens into the implementing type. + /// + /// We do not include a condition that checks the balance because + /// we want to give users the ability to make custom receivers that + /// can do custom things with the tokens, like split them up and + /// send them to different places. + /// + access(all) resource interface Receiver { + + /// deposit takes a Vault and deposits it into the implementing resource type + /// + access(all) fun deposit(from: @{Vault}) + + /// getSupportedVaultTypes returns a dictionary of Vault types + /// and whether the type is currently supported by this Receiver + access(all) view fun getSupportedVaultTypes(): {Type: Bool} + + /// Returns whether or not the given type is accepted by the Receiver + /// A vault that can accept any type should just return true by default + access(all) view fun isSupportedVaultType(type: Type): Bool + } + + /// Vault + /// + /// Ideally, this interface would also conform to Receiver, Balance, Transferor, Provider, and Resolver + /// but that is not supported yet + /// + access(all) resource interface Vault: Receiver, Provider, Balance, ViewResolver.Resolver, Burner.Burnable { + + /// Field that tracks the balance of a vault + access(all) var balance: UFix64 + + /// Called when a fungible token is burned via the `Burner.burn()` method + /// Implementations can do any bookkeeping or emit any events + /// that should be emitted when a vault is destroyed. + /// Many implementations will want to update the token's total supply + /// to reflect that the tokens have been burned and removed from the supply. + /// Implementations also need to set the balance to zero before the end of the function + /// This is to prevent vault owners from spamming fake Burned events. + access(contract) fun burnCallback() { + pre { + emit Burned(type: self.getType().identifier, amount: self.balance, fromUUID: self.uuid) + } + post { + self.balance == 0.0: "The balance must be set to zero during the burnCallback method so that it cannot be spammed" + } + self.balance = 0.0 + } + + /// getSupportedVaultTypes returns a dictionary of vault types and whether this receiver accepts the indexed type + /// The default implementation is included here because vaults are expected + /// to only accepted their own type, so they have no need to provide an implementation + /// for this function + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + // Below check is implemented to make sure that run-time type would + // only get returned when the parent resource conforms with `FungibleToken.Vault`. + if self.getType().isSubtype(of: Type<@{FungibleToken.Vault}>()) { + return {self.getType(): true} + } else { + // Return an empty dictionary as the default value for resource who don't + // implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc. + return {} + } + } + + /// Checks if the given type is supported by this Vault + access(all) view fun isSupportedVaultType(type: Type): Bool { + return self.getSupportedVaultTypes()[type] ?? false + } + + /// withdraw subtracts `amount` from the Vault's balance + /// and returns a new Vault with the subtracted balance + /// + access(Withdraw) fun withdraw(amount: UFix64): @{Vault} { + pre { + self.balance >= amount: + "Amount withdrawn must be less than or equal than the balance of the Vault" + } + post { + result.getType() == self.getType(): "Must return the same vault type as self" + // use the special function `before` to get the value of the `balance` field + // at the beginning of the function execution + // + self.balance == before(self.balance) - amount: + "New Vault balance must be the difference of the previous balance and the withdrawn Vault balance" + emit Withdrawn( + type: result.getType().identifier, + amount: amount, + from: self.owner?.address, + fromUUID: self.uuid, + withdrawnUUID: result.uuid, + balanceAfter: self.balance + ) + } + } + + /// deposit takes a Vault and adds its balance to the balance of this Vault + /// + access(all) fun deposit(from: @{FungibleToken.Vault}) { + // Assert that the concrete type of the deposited vault is the same + // as the vault that is accepting the deposit + pre { + from.isInstance(self.getType()): + "Cannot deposit an incompatible token type" + } + post { + emit Deposited( + type: before(from.getType().identifier), + amount: before(from.balance), + to: self.owner?.address, + toUUID: self.uuid, + depositedUUID: before(from.uuid), + balanceAfter: self.balance + ) + self.balance == before(self.balance) + before(from.balance): + "New Vault balance must be the sum of the previous balance and the deposited Vault" + } + } + + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + access(all) fun createEmptyVault(): @{Vault} { + post { + result.balance == 0.0: "The newly created Vault must have zero balance" + result.getType() == self.getType(): "The newly created Vault must have the same type as the creating vault" + } + } + } + + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} { + post { + result.getType() == vaultType: "The returned vault does not match the desired type" + result.balance == 0.0: "The newly created Vault must have zero balance" + } + } +} \ No newline at end of file diff --git a/packages/contracts/flow/flow.json b/packages/contracts/flow/flow.json index a1d5d77..4e8ffe7 100644 --- a/packages/contracts/flow/flow.json +++ b/packages/contracts/flow/flow.json @@ -6,6 +6,30 @@ } }, "contracts": { + "FungibleToken": { + "source": "./contracts/core/FungibleToken.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "Burner": { + "source": "./contracts/core/Burner.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "FlowToken": { + "source": "./contracts/core/FlowToken.cdc", + "aliases": { + "emulator": "0x0ae53cb6e3f42a79", + "testnet": "0x7e60df042a9c0868", + "mainnet": "0x1654653399040a61" + } + }, "NonFungibleToken": { "source": "./contracts/core/NonFungibleToken.cdc", "aliases": {