@flowtyio/flow-contracts 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ /// This contract defines a simple interface which can be implemented by any resource to prevent it from being
2
+ /// onboarded to the Flow-EVM bridge
3
+ ///
4
+ /// NOTE: This is suggested only for cases where your asset (NFT/FT) incorporates non-standard logic that would
5
+ /// break your project if not handles properly
6
+ /// e.g. assets are reclaimed after a certain period of time, NFTs share IDs, etc.
7
+ ///
8
+ access(all)
9
+ contract interface IBridgePermissions {
10
+ /// Contract-level method enabling implementing contracts to identify whether they allow bridging for their
11
+ /// project's assets. Implementers may consider adding a hook which would later enable an update to this value
12
+ /// should either the project be updated or the bridge be updated to handle the asset's non-standard logic which
13
+ /// would otherwise prevent them from supporting VM bridging at the outset.
14
+ ///
15
+ access(all)
16
+ view fun allowsBridging(): Bool {
17
+ return false
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ import "EVM"
2
+
3
+ /// Contract interface denoting a cross-VM implementation, exposing methods to query EVM-associated addresses
4
+ ///
5
+ access(all)
6
+ contract interface ICrossVM {
7
+
8
+ /// Retrieves the corresponding EVM contract address, assuming a 1:1 relationship between VM implementations
9
+ ///
10
+ access(all)
11
+ view fun getEVMContractAddress(): EVM.EVMAddress
12
+ }
@@ -0,0 +1,19 @@
1
+ import "EVM"
2
+
3
+ import "ICrossVM"
4
+
5
+ /// A simple contract interface for a Cadence contract that represents an asset bridged from Flow EVM such as an ERC20
6
+ /// or ERC721 token.
7
+ ///
8
+ access(all) contract interface ICrossVMAsset : ICrossVM {
9
+ /// Returns the name of the asset
10
+ access(all) view fun getName(): String
11
+ /// Returns the symbol of the asset
12
+ access(all) view fun getSymbol(): String
13
+
14
+ access(all) resource interface AssetInfo {
15
+ access(all) view fun getName(): String
16
+ access(all) view fun getSymbol(): String
17
+ access(all) view fun getEVMContractAddress(): EVM.EVMAddress
18
+ }
19
+ }
@@ -0,0 +1,141 @@
1
+ import "ViewResolver"
2
+ import "MetadataViews"
3
+ import "NonFungibleToken"
4
+
5
+ /// This contract is a utility for serializing primitive types, arrays, and common metadata mapping formats to JSON
6
+ /// compatible strings. Also included are interfaces enabling custom serialization for structs and resources.
7
+ ///
8
+ /// Special thanks to @austinkline for the idea and initial implementation.
9
+ ///
10
+ access(all)
11
+ contract Serialize {
12
+
13
+ /// Method that returns a serialized representation of the given value or nil if the value is not serializable
14
+ ///
15
+ access(all)
16
+ fun tryToJSONString(_ value: AnyStruct): String? {
17
+ // Recursively serialize array & return
18
+ if value.getType().isSubtype(of: Type<[AnyStruct]>()) {
19
+ return self.arrayToJSONString(value as! [AnyStruct])
20
+ }
21
+ // Recursively serialize map & return
22
+ if value.getType().isSubtype(of: Type<{String: AnyStruct}>()) {
23
+ return self.dictToJSONString(dict: value as! {String: AnyStruct}, excludedNames: nil)
24
+ }
25
+ // Handle primitive types & optionals
26
+ switch value.getType() {
27
+ case Type<Never?>():
28
+ return "\"nil\""
29
+ case Type<String>():
30
+ return "\"".concat(value as! String).concat("\"")
31
+ case Type<String?>():
32
+ return "\"".concat(value as? String ?? "nil").concat("\"")
33
+ case Type<Character>():
34
+ return "\"".concat((value as! Character).toString()).concat("\"")
35
+ case Type<Bool>():
36
+ return "\"".concat(value as! Bool ? "true" : "false").concat("\"")
37
+ case Type<Address>():
38
+ return "\"".concat((value as! Address).toString()).concat("\"")
39
+ case Type<Address?>():
40
+ return "\"".concat((value as? Address)?.toString() ?? "nil").concat("\"")
41
+ case Type<Int8>():
42
+ return "\"".concat((value as! Int8).toString()).concat("\"")
43
+ case Type<Int16>():
44
+ return "\"".concat((value as! Int16).toString()).concat("\"")
45
+ case Type<Int32>():
46
+ return "\"".concat((value as! Int32).toString()).concat("\"")
47
+ case Type<Int64>():
48
+ return "\"".concat((value as! Int64).toString()).concat("\"")
49
+ case Type<Int128>():
50
+ return "\"".concat((value as! Int128).toString()).concat("\"")
51
+ case Type<Int256>():
52
+ return "\"".concat((value as! Int256).toString()).concat("\"")
53
+ case Type<Int>():
54
+ return "\"".concat((value as! Int).toString()).concat("\"")
55
+ case Type<UInt8>():
56
+ return "\"".concat((value as! UInt8).toString()).concat("\"")
57
+ case Type<UInt16>():
58
+ return "\"".concat((value as! UInt16).toString()).concat("\"")
59
+ case Type<UInt32>():
60
+ return "\"".concat((value as! UInt32).toString()).concat("\"")
61
+ case Type<UInt64>():
62
+ return "\"".concat((value as! UInt64).toString()).concat("\"")
63
+ case Type<UInt128>():
64
+ return "\"".concat((value as! UInt128).toString()).concat("\"")
65
+ case Type<UInt256>():
66
+ return "\"".concat((value as! UInt256).toString()).concat("\"")
67
+ case Type<UInt>():
68
+ return "\"".concat((value as! UInt).toString()).concat("\"")
69
+ case Type<Word8>():
70
+ return "\"".concat((value as! Word8).toString()).concat("\"")
71
+ case Type<Word16>():
72
+ return "\"".concat((value as! Word16).toString()).concat("\"")
73
+ case Type<Word32>():
74
+ return "\"".concat((value as! Word32).toString()).concat("\"")
75
+ case Type<Word64>():
76
+ return "\"".concat((value as! Word64).toString()).concat("\"")
77
+ case Type<Word128>():
78
+ return "\"".concat((value as! Word128).toString()).concat("\"")
79
+ case Type<Word256>():
80
+ return "\"".concat((value as! Word256).toString()).concat("\"")
81
+ case Type<UFix64>():
82
+ return "\"".concat((value as! UFix64).toString()).concat("\"")
83
+ default:
84
+ return nil
85
+ }
86
+ }
87
+
88
+ /// Returns a serialized representation of the given array or nil if the value is not serializable
89
+ ///
90
+ access(all)
91
+ fun arrayToJSONString(_ arr: [AnyStruct]): String? {
92
+ var serializedArr = "["
93
+ let arrLength = arr.length
94
+ for i, element in arr {
95
+ let serializedElement = self.tryToJSONString(element)
96
+ if serializedElement == nil {
97
+ if i == arrLength - 1 && serializedArr.length > 1 && serializedArr[serializedArr.length - 2] == "," {
98
+ // Remove trailing comma as this element could not be serialized
99
+ serializedArr = serializedArr.slice(from: 0, upTo: serializedArr.length - 2)
100
+ }
101
+ continue
102
+ }
103
+ serializedArr = serializedArr.concat(serializedElement!)
104
+ // Add a comma if there are more elements to serialize
105
+ if i < arr.length - 1 {
106
+ serializedArr = serializedArr.concat(", ")
107
+ }
108
+ }
109
+ return serializedArr.concat("]")
110
+ }
111
+
112
+ /// Returns a serialized representation of the given String-indexed mapping or nil if the value is not serializable.
113
+ /// The interface here is largely the same as as the `MetadataViews.dictToTraits` method, though here
114
+ /// a JSON-compatible String is returned instead of a `Traits` array.
115
+ ///
116
+ access(all)
117
+ fun dictToJSONString(dict: {String: AnyStruct}, excludedNames: [String]?): String? {
118
+ if excludedNames != nil {
119
+ for k in excludedNames! {
120
+ dict.remove(key: k)
121
+ }
122
+ }
123
+ var serializedDict = "{"
124
+ let dictLength = dict.length
125
+ for i, key in dict.keys {
126
+ let serializedValue = self.tryToJSONString(dict[key]!)
127
+ if serializedValue == nil {
128
+ if i == dictLength - 1 && serializedDict.length > 1 && serializedDict[serializedDict.length - 2] == "," {
129
+ // Remove trailing comma as this element could not be serialized
130
+ serializedDict = serializedDict.slice(from: 0, upTo: serializedDict.length - 2)
131
+ }
132
+ continue
133
+ }
134
+ serializedDict = serializedDict.concat(self.tryToJSONString(key)!).concat(": ").concat(serializedValue!)
135
+ if i < dict.length - 1 {
136
+ serializedDict = serializedDict.concat(", ")
137
+ }
138
+ }
139
+ return serializedDict.concat("}")
140
+ }
141
+ }
@@ -0,0 +1,221 @@
1
+ import "ViewResolver"
2
+ import "MetadataViews"
3
+ import "NonFungibleToken"
4
+ import "FungibleTokenMetadataViews"
5
+
6
+ import "Serialize"
7
+
8
+ /// This contract defines methods for serializing NFT metadata as a JSON compatible string, according to the common
9
+ /// OpenSea metadata format. NFTs and metadata views can be serialized by reference via contract methods.
10
+ ///
11
+ access(all) contract SerializeMetadata {
12
+
13
+ /// Serializes the metadata (as a JSON compatible String) for a given NFT according to formats expected by EVM
14
+ /// platforms like OpenSea. If you are a project owner seeking to expose custom traits on bridged NFTs and your
15
+ /// Trait.value is not natively serializable, you can implement a custom serialization method with the
16
+ /// `{SerializableStruct}` interface's `serialize` method.
17
+ ///
18
+ /// Reference: https://docs.opensea.io/docs/metadata-standards
19
+ ///
20
+ ///
21
+ /// @returns: A JSON compatible data URL string containing the serialized display & collection display views as:
22
+ /// `data:application/json;utf8,{
23
+ /// \"name\": \"<display.name>\",
24
+ /// \"description\": \"<display.description>\",
25
+ /// \"image\": \"<display.thumbnail.uri()>\",
26
+ /// \"external_url\": \"<nftCollectionDisplay.externalURL.url>\",
27
+ /// \"attributes\": [{\"trait_type\": \"<trait.name>\", \"value\": \"<trait.value>\"}, {...}]
28
+ /// }`
29
+ access(all)
30
+ fun serializeNFTMetadataAsURI(_ nft: &{NonFungibleToken.NFT}): String {
31
+ // Serialize the display values from the NFT's Display & NFTCollectionDisplay views
32
+ let nftDisplay = nft.resolveView(Type<MetadataViews.Display>()) as! MetadataViews.Display?
33
+ let collectionDisplay = nft.resolveView(Type<MetadataViews.NFTCollectionDisplay>()) as! MetadataViews.NFTCollectionDisplay?
34
+ let display = self.serializeFromDisplays(nftDisplay: nftDisplay, collectionDisplay: collectionDisplay)
35
+
36
+ // Get the Traits view from the NFT, returning early if no traits are found
37
+ let traits = nft.resolveView(Type<MetadataViews.Traits>()) as! MetadataViews.Traits?
38
+ let attributes = self.serializeNFTTraitsAsAttributes(traits ?? MetadataViews.Traits([]))
39
+
40
+ // Return an empty string if nothing is serializable
41
+ if display == nil && attributes == nil {
42
+ return ""
43
+ }
44
+ // Init the data format prefix & concatenate the serialized display & attributes
45
+ var serializedMetadata = "data:application/json;utf8,{"
46
+ if display != nil {
47
+ serializedMetadata = serializedMetadata.concat(display!)
48
+ }
49
+ if display != nil && attributes != nil {
50
+ serializedMetadata = serializedMetadata.concat(", ")
51
+ }
52
+ if attributes != nil {
53
+ serializedMetadata = serializedMetadata.concat(attributes)
54
+ }
55
+ return serializedMetadata.concat("}")
56
+ }
57
+
58
+ /// Serializes the display & collection display views of a given NFT as a JSON compatible string. If nftDisplay is
59
+ /// present, the value is returned as token-level metadata. If nftDisplay is nil and collectionDisplay is present,
60
+ /// the value is returned as contract-level metadata. If both values are nil, nil is returned.
61
+ ///
62
+ /// @param nftDisplay: The NFT's Display view from which values `name`, `description`, and `thumbnail` are serialized
63
+ /// @param collectionDisplay: The NFT's NFTCollectionDisplay view from which the `externalURL` is serialized
64
+ ///
65
+ /// @returns: A JSON compatible string containing the serialized display & collection display views as either:
66
+ /// \"name\": \"<nftDisplay.name>\", \"description\": \"<nftDisplay.description>\", \"image\": \"<nftDisplay.thumbnail.uri()>\", \"external_url\": \"<collectionDisplay.externalURL.url>\",
67
+ /// \"name\": \"<collectionDisplay.name>\", \"description\": \"<collectionDisplay.description>\", \"image\": \"<collectionDisplay.squareImage.file.uri()>\", \"external_link\": \"<collectionDisplay.externalURL.url>\",
68
+ ///
69
+ access(all)
70
+ fun serializeFromDisplays(nftDisplay: MetadataViews.Display?, collectionDisplay: MetadataViews.NFTCollectionDisplay?): String? {
71
+ // Return early if both values are nil
72
+ if nftDisplay == nil && collectionDisplay == nil {
73
+ return nil
74
+ }
75
+
76
+ // Initialize JSON fields
77
+ let name = "\"name\": "
78
+ let description = "\"description\": "
79
+ let image = "\"image\": "
80
+ let externalURL = "\"external_url\": "
81
+ let externalLink = "\"external_link\": "
82
+ var serializedResult = ""
83
+
84
+ // Append results from the token-level Display view to the serialized JSON compatible string
85
+ if nftDisplay != nil {
86
+ serializedResult = serializedResult
87
+ .concat(name).concat(Serialize.tryToJSONString(nftDisplay!.name)!).concat(", ")
88
+ .concat(description).concat(Serialize.tryToJSONString(nftDisplay!.description)!).concat(", ")
89
+ .concat(image).concat(Serialize.tryToJSONString(nftDisplay!.thumbnail.uri())!)
90
+ // Append the `externa_url` value from NFTCollectionDisplay view if present
91
+ if collectionDisplay != nil {
92
+ return serializedResult.concat(", ")
93
+ .concat(externalURL).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!)
94
+ }
95
+ }
96
+
97
+ if collectionDisplay == nil {
98
+ return serializedResult
99
+ }
100
+
101
+ // Without token-level view, serialize as contract-level metadata
102
+ return serializedResult
103
+ .concat(name).concat(Serialize.tryToJSONString(collectionDisplay!.name)!).concat(", ")
104
+ .concat(description).concat(Serialize.tryToJSONString(collectionDisplay!.description)!).concat(", ")
105
+ .concat(image).concat(Serialize.tryToJSONString(collectionDisplay!.squareImage.file.uri())!).concat(", ")
106
+ .concat(externalLink).concat(Serialize.tryToJSONString(collectionDisplay!.externalURL.url)!)
107
+ }
108
+
109
+ /// Serializes given Traits view as a JSON compatible string. If a given Trait is not serializable, it is skipped
110
+ /// and not included in the serialized result.
111
+ ///
112
+ /// @param traits: The Traits view to be serialized
113
+ ///
114
+ /// @returns: A JSON compatible string containing the serialized traits as:
115
+ /// `\"attributes\": [{\"trait_type\": \"<trait.name>\", \"value\": \"<trait.value>\"}, {...}]`
116
+ ///
117
+ access(all)
118
+ fun serializeNFTTraitsAsAttributes(_ traits: MetadataViews.Traits): String {
119
+ // Serialize each trait as an attribute, building the serialized JSON compatible string
120
+ var serializedResult = "\"attributes\": ["
121
+ let traitsLength = traits.traits.length
122
+ for i, trait in traits.traits {
123
+ let value = Serialize.tryToJSONString(trait.value)
124
+ if value == nil {
125
+ // Remove trailing comma if last trait is not serializable
126
+ if i == traitsLength - 1 && serializedResult[serializedResult.length - 1] == "," {
127
+ serializedResult = serializedResult.slice(from: 0, upTo: serializedResult.length - 1)
128
+ }
129
+ continue
130
+ }
131
+ serializedResult = serializedResult.concat("{")
132
+ .concat("\"trait_type\": ").concat(Serialize.tryToJSONString(trait.name)!)
133
+ .concat(", \"value\": ").concat(value!)
134
+ .concat("}")
135
+ if i < traits!.traits.length - 1 {
136
+ serializedResult = serializedResult.concat(",")
137
+ }
138
+ }
139
+ return serializedResult.concat("]")
140
+ }
141
+
142
+ /// Serializes the FTDisplay view of a given fungible token as a JSON compatible data URL. The value is returned as
143
+ /// contract-level metadata.
144
+ ///
145
+ /// @param ftDisplay: The tokens's FTDisplay view from which values `name`, `symbol`, `description`, and
146
+ /// `externaURL` are serialized
147
+ ///
148
+ /// @returns: A JSON compatible data URL string containing the serialized view as:
149
+ /// `data:application/json;utf8,{
150
+ /// \"name\": \"<ftDisplay.name>\",
151
+ /// \"symbol\": \"<ftDisplay.symbol>\",
152
+ /// \"description\": \"<ftDisplay.description>\",
153
+ /// \"external_link\": \"<ftDisplay.externalURL.url>\",
154
+ /// }`
155
+ access(all)
156
+ fun serializeFTDisplay(_ ftDisplay: FungibleTokenMetadataViews.FTDisplay): String {
157
+ let name = "\"name\": "
158
+ let symbol = "\"symbol\": "
159
+ let description = "\"description\": "
160
+ let externalLink = "\"external_link\": "
161
+
162
+ return "data:application/json;utf8,{"
163
+ .concat(name).concat(Serialize.tryToJSONString(ftDisplay.name)!).concat(", ")
164
+ .concat(symbol).concat(Serialize.tryToJSONString(ftDisplay.symbol)!).concat(", ")
165
+ .concat(description).concat(Serialize.tryToJSONString(ftDisplay.description)!).concat(", ")
166
+ .concat(externalLink).concat(Serialize.tryToJSONString(ftDisplay.externalURL.url)!)
167
+ .concat("}")
168
+ }
169
+
170
+ /// Derives a symbol for use as an ERC20 or ERC721 symbol from a given string, presumably a Cadence contract name.
171
+ /// Derivation is a process of slicing the first 4 characters of the string and converting them to uppercase.
172
+ ///
173
+ /// @param fromString: The string from which to derive a symbol
174
+ ///
175
+ /// @returns: A derived symbol for use as an ERC20 or ERC721 symbol based on the provided string, presumably a
176
+ /// Cadence contract name
177
+ ///
178
+ access(all) view fun deriveSymbol(fromString: String): String {
179
+ let defaultLen = 4
180
+ let len = fromString.length < defaultLen ? fromString.length : defaultLen
181
+ return self.toUpperAlphaNumerical(fromString, upTo: len)
182
+ }
183
+
184
+ /// Returns the uppercase alphanumeric version of a given string. If upTo is nil or exceeds the length of the string,
185
+ /// the entire string is converted to uppercase.
186
+ ///
187
+ /// @param str: The string to convert to uppercase
188
+ /// @param upTo: The maximum number of characters to convert to uppercase
189
+ ///
190
+ /// @returns: The uppercase version of the given string
191
+ ///
192
+ access(all) view fun toUpperAlphaNumerical(_ str: String, upTo: Int?): String {
193
+ let len = upTo ?? str.length
194
+ var upper: String = ""
195
+ for char in str {
196
+ if upper.length == len {
197
+ break
198
+ }
199
+ let bytes = char.utf8
200
+ if bytes.length != 1 {
201
+ continue
202
+ }
203
+ let byte = bytes[0]
204
+ if byte >= 97 && byte <= 122 {
205
+ // Convert lower case to upper case
206
+ let upperChar = String.fromUTF8([byte - UInt8(32)])!
207
+ upper = upper.concat(upperChar)
208
+ } else if byte >= 65 && byte <= 90 {
209
+ // Keep upper case
210
+ upper = upper.concat(char.toString())
211
+ } else if byte >= 48 && byte <= 57 {
212
+ // Keep numbers
213
+ upper = upper.concat(String.fromCharacters([char]))
214
+ } else {
215
+ // Skip non-alphanumeric characters
216
+ continue
217
+ }
218
+ }
219
+ return upper
220
+ }
221
+ }
@@ -0,0 +1,232 @@
1
+ import "FungibleToken"
2
+ import "FungibleTokenMetadataViews"
3
+ import "MetadataViews"
4
+ import "Burner"
5
+ import "ViewResolver"
6
+ import "FlowEVMBridgeHandlerInterfaces"
7
+ import "FlowEVMBridgeConfig"
8
+
9
+ /// After the Crescendo migration, the `USDCFlow` smart contract
10
+ /// will integrate directly with the Flow VM bridge to become
11
+ /// the bridged version of Flow EVM USDC. These tokens will be backed
12
+ /// by real USDC via Flow EVM.
13
+
14
+ /// This is not the official Circle USDC, only a bridged version
15
+ /// that is still backed by official USDC on the other side of the bridge
16
+
17
+ access(all) contract USDCFlow: FungibleToken, ViewResolver {
18
+
19
+ /// Total supply of USDCFlows in existence
20
+ access(all) var totalSupply: UFix64
21
+
22
+ /// Storage and Public Paths
23
+ access(all) let VaultStoragePath: StoragePath
24
+ access(all) let VaultPublicPath: PublicPath
25
+ access(all) let ReceiverPublicPath: PublicPath
26
+
27
+ /// The event that is emitted when new tokens are minted
28
+ access(all) event Minted(amount: UFix64, mintedUUID: UInt64)
29
+ access(all) event Burned(amount: UFix64, burntUUID: UInt64)
30
+
31
+ access(all) view fun getContractViews(resourceType: Type?): [Type] {
32
+ return [
33
+ Type<FungibleTokenMetadataViews.FTView>(),
34
+ Type<FungibleTokenMetadataViews.FTDisplay>(),
35
+ Type<FungibleTokenMetadataViews.FTVaultData>(),
36
+ Type<FungibleTokenMetadataViews.TotalSupply>()
37
+ ]
38
+ }
39
+
40
+ access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
41
+ switch viewType {
42
+ case Type<FungibleTokenMetadataViews.FTView>():
43
+ return FungibleTokenMetadataViews.FTView(
44
+ ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
45
+ ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
46
+ )
47
+ case Type<FungibleTokenMetadataViews.FTDisplay>():
48
+ let media = MetadataViews.Media(
49
+ file: MetadataViews.HTTPFile(
50
+ url: "https://uploads-ssl.webflow.com/5f734f4dbd95382f4fdfa0ea/66bfae00953c3d7bd09e7ac4_USDC-and-FLOW.svg"
51
+ ),
52
+ mediaType: "image/svg+xml"
53
+ )
54
+ let medias = MetadataViews.Medias([media])
55
+ return FungibleTokenMetadataViews.FTDisplay(
56
+ name: "USDC (Flow)",
57
+ symbol: "USDCf",
58
+ description: "This fungible token representation of Standard Bridged USDC is bridged from Flow EVM.",
59
+ externalURL: MetadataViews.ExternalURL("https://github.com/circlefin/stablecoin-evm/blob/master/doc/bridged_USDC_standard.md"),
60
+ logos: medias,
61
+ socials: {},
62
+ )
63
+ case Type<FungibleTokenMetadataViews.FTVaultData>():
64
+ return FungibleTokenMetadataViews.FTVaultData(
65
+ storagePath: USDCFlow.VaultStoragePath,
66
+ receiverPath: USDCFlow.ReceiverPublicPath,
67
+ metadataPath: USDCFlow.VaultPublicPath,
68
+ receiverLinkedType: Type<&USDCFlow.Vault>(),
69
+ metadataLinkedType: Type<&USDCFlow.Vault>(),
70
+ createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
71
+ return <-USDCFlow.createEmptyVault(vaultType: Type<@USDCFlow.Vault>())
72
+ })
73
+ )
74
+ case Type<FungibleTokenMetadataViews.TotalSupply>():
75
+ return FungibleTokenMetadataViews.TotalSupply(
76
+ totalSupply: USDCFlow.totalSupply
77
+ )
78
+ }
79
+ return nil
80
+ }
81
+
82
+ access(all) resource Vault: FungibleToken.Vault {
83
+
84
+ /// The total balance of this vault
85
+ access(all) var balance: UFix64
86
+
87
+ /// Initialize the balance at resource creation time
88
+ init(balance: UFix64) {
89
+ self.balance = balance
90
+ }
91
+
92
+ /// Called when a fungible token is burned via the `Burner.burn()` method
93
+ /// The total supply will only reflect the supply in the Cadence version
94
+ /// of the USDCFlow smart contract
95
+ access(contract) fun burnCallback() {
96
+ if self.balance > 0.0 {
97
+ assert(USDCFlow.totalSupply >= self.balance, message: "Cannot burn more than the total supply")
98
+ emit Burned(amount: self.balance, burntUUID: self.uuid)
99
+ USDCFlow.totalSupply = USDCFlow.totalSupply - self.balance
100
+ }
101
+ self.balance = 0.0
102
+ }
103
+
104
+ /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
105
+ access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
106
+ let supportedTypes: {Type: Bool} = {}
107
+ supportedTypes[self.getType()] = true
108
+ return supportedTypes
109
+ }
110
+
111
+ /// Returns whether the specified type can be deposited
112
+ access(all) view fun isSupportedVaultType(type: Type): Bool {
113
+ return self.getSupportedVaultTypes()[type] ?? false
114
+ }
115
+
116
+ /// Asks if the amount can be withdrawn from this vault
117
+ access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
118
+ return amount <= self.balance
119
+ }
120
+
121
+ access(all) fun createEmptyVault(): @USDCFlow.Vault {
122
+ return <-create Vault(balance: 0.0)
123
+ }
124
+
125
+ /// withdraw
126
+ /// @param amount: The amount of tokens to be withdrawn from the vault
127
+ /// @return The Vault resource containing the withdrawn funds
128
+ ///
129
+ access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
130
+ self.balance = self.balance - amount
131
+ return <-create Vault(balance: amount)
132
+ }
133
+
134
+ /// deposit
135
+ /// @param from: The Vault resource containing the funds that will be deposited
136
+ ///
137
+ access(all) fun deposit(from: @{FungibleToken.Vault}) {
138
+ let vault <- from as! @USDCFlow.Vault
139
+ self.balance = self.balance + vault.balance
140
+ destroy vault
141
+ }
142
+
143
+ /// Gets an array of all the Metadata Views implemented by USDCFlow
144
+ ///
145
+ /// @return An array of Types defining the implemented views. This value will be used by
146
+ /// developers to know which parameter to pass to the resolveView() method.
147
+ ///
148
+ access(all) view fun getViews(): [Type] {
149
+ return USDCFlow.getContractViews(resourceType: nil)
150
+ }
151
+
152
+ /// Resolves Metadata Views out of the USDCFlow
153
+ ///
154
+ /// @param view: The Type of the desired view.
155
+ /// @return A structure representing the requested view.
156
+ ///
157
+ access(all) fun resolveView(_ view: Type): AnyStruct? {
158
+ return USDCFlow.resolveContractView(resourceType: nil, viewType: view)
159
+ }
160
+ }
161
+
162
+ access(all) resource Minter: FlowEVMBridgeHandlerInterfaces.TokenMinter {
163
+
164
+ /// Required function for the bridge to be able to work with the Minter
165
+ access(all) view fun getMintedType(): Type {
166
+ return Type<@USDCFlow.Vault>()
167
+ }
168
+
169
+ /// Function for the bridge to mint tokens that are bridged from Flow EVM
170
+ access(FlowEVMBridgeHandlerInterfaces.Mint) fun mint(amount: UFix64): @{FungibleToken.Vault} {
171
+ let newTotalSupply = USDCFlow.totalSupply + amount
172
+ USDCFlow.totalSupply = newTotalSupply
173
+
174
+ let vault <-create Vault(balance: amount)
175
+
176
+ emit Minted(amount: amount, mintedUUID: vault.uuid)
177
+ return <-vault
178
+ }
179
+
180
+ /// Function for the bridge to burn tokens that are bridged back to Flow EVM
181
+ access(all) fun burn(vault: @{FungibleToken.Vault}) {
182
+ let toBurn <- vault as! @USDCFlow.Vault
183
+ let amount = toBurn.balance
184
+
185
+ // This function updates USDCFlow.totalSupply
186
+ Burner.burn(<-toBurn)
187
+ }
188
+ }
189
+
190
+ /// Sends the USDCFlow Minter to the Flow/EVM bridge
191
+ /// without giving any account access to the minter
192
+ /// before it is safely in the decentralized bridge
193
+ access(all) fun sendMinterToBridge(_ bridgeAddress: Address) {
194
+ let minter <- create Minter()
195
+ // borrow a reference to the bridge's configuration admin resource from public Capability
196
+ let bridgeAdmin = getAccount(bridgeAddress).capabilities.borrow<&FlowEVMBridgeConfig.Admin>(
197
+ FlowEVMBridgeConfig.adminPublicPath
198
+ ) ?? panic("FlowEVMBridgeConfig.Admin could not be referenced from ".concat(bridgeAddress.toString()))
199
+
200
+ // sets the USDCFlow as the minter resource for all USDCFlow bridge requests
201
+ // prior to transferring the Minter, a TokenHandler will be set for USDCFlow during the bridge's initial
202
+ // configuration, setting the stage for this minter to be sent.
203
+ bridgeAdmin.setTokenHandlerMinter(targetType: Type<@USDCFlow.Vault>(), minter: <-minter)
204
+ }
205
+
206
+ /// createEmptyVault
207
+ ///
208
+ /// @return The new Vault resource with a balance of zero
209
+ ///
210
+ access(all) fun createEmptyVault(vaultType: Type): @Vault {
211
+ let r <-create Vault(balance: 0.0)
212
+ return <-r
213
+ }
214
+
215
+ init() {
216
+ self.totalSupply = 0.0
217
+ self.VaultStoragePath = /storage/usdcFlowVault
218
+ self.VaultPublicPath = /public/usdcFlowMetadata
219
+ self.ReceiverPublicPath = /public/usdcFlowReceiver
220
+
221
+ let minter <- create Minter()
222
+ self.account.storage.save(<-minter, to: /storage/usdcFlowMinter)
223
+
224
+ // Create the Vault with the total supply of tokens and save it in storage.
225
+ let vault <- create Vault(balance: self.totalSupply)
226
+ self.account.storage.save(<-vault, to: self.VaultStoragePath)
227
+
228
+ let tokenCap = self.account.capabilities.storage.issue<&USDCFlow.Vault>(self.VaultStoragePath)
229
+ self.account.capabilities.publish(tokenCap, at: self.ReceiverPublicPath)
230
+ self.account.capabilities.publish(tokenCap, at: self.VaultPublicPath)
231
+ }
232
+ }