Smart Contract
USDCFlow
A.f1ab99c82dee3526.USDCFlow
1import FungibleToken from 0xf233dcee88fe0abe
2import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import Burner from 0xf233dcee88fe0abe
5import ViewResolver from 0x1d7e57aa55817448
6import FlowEVMBridgeHandlerInterfaces from 0x1e4aa0b87d10b141
7
8/// The `USDCFlow` smart contract is integrated directly
9/// with the Flow VM bridge as the bridged version of Flow EVM USDC
10/// which is itself the bridged version of official USDC from Ethereum Mainnet.
11
12/// This is not the official Circle USDC, only a bridged version
13/// that is still backed by official USDC on the other side of the bridge
14
15access(all) contract USDCFlow: FungibleToken, ViewResolver {
16
17 /// Total supply of USDCFlows in existence
18 access(all) var totalSupply: UFix64
19
20 /// Storage and Public Paths
21 access(all) let VaultStoragePath: StoragePath
22 access(all) let VaultPublicPath: PublicPath
23 access(all) let ReceiverPublicPath: PublicPath
24
25 /// The event that is emitted when new tokens are minted
26 access(all) event Minted(amount: UFix64, mintedUUID: UInt64)
27 access(all) event Burned(amount: UFix64, burntUUID: UInt64)
28
29 access(all) view fun getContractViews(resourceType: Type?): [Type] {
30 return [
31 Type<FungibleTokenMetadataViews.FTView>(),
32 Type<FungibleTokenMetadataViews.FTDisplay>(),
33 Type<FungibleTokenMetadataViews.FTVaultData>(),
34 Type<FungibleTokenMetadataViews.TotalSupply>()
35 ]
36 }
37
38 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
39 switch viewType {
40 case Type<FungibleTokenMetadataViews.FTView>():
41 return FungibleTokenMetadataViews.FTView(
42 ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
43 ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
44 )
45 case Type<FungibleTokenMetadataViews.FTDisplay>():
46 let media = MetadataViews.Media(
47 file: MetadataViews.HTTPFile(
48 url: "https://uploads-ssl.webflow.com/5f734f4dbd95382f4fdfa0ea/66bfae00953c3d7bd09e7ac4_USDC-and-FLOW.svg"
49 ),
50 mediaType: "image/svg+xml"
51 )
52 let medias = MetadataViews.Medias([media])
53 return FungibleTokenMetadataViews.FTDisplay(
54 name: "USDC.e (Flow)",
55 symbol: "USDC.e",
56 description: "This fungible token representation of Standard Bridged USDC is bridged from Flow EVM.",
57 externalURL: MetadataViews.ExternalURL("https://github.com/circlefin/stablecoin-evm/blob/master/doc/bridged_USDC_standard.md"),
58 logos: medias,
59 socials: {},
60 )
61 case Type<FungibleTokenMetadataViews.FTVaultData>():
62 return FungibleTokenMetadataViews.FTVaultData(
63 storagePath: USDCFlow.VaultStoragePath,
64 receiverPath: USDCFlow.ReceiverPublicPath,
65 metadataPath: USDCFlow.VaultPublicPath,
66 receiverLinkedType: Type<&USDCFlow.Vault>(),
67 metadataLinkedType: Type<&USDCFlow.Vault>(),
68 createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
69 return <-USDCFlow.createEmptyVault(vaultType: Type<@USDCFlow.Vault>())
70 })
71 )
72 case Type<FungibleTokenMetadataViews.TotalSupply>():
73 return FungibleTokenMetadataViews.TotalSupply(
74 totalSupply: USDCFlow.totalSupply
75 )
76 }
77 return nil
78 }
79
80 access(all) resource Vault: FungibleToken.Vault {
81
82 /// The total balance of this vault
83 access(all) var balance: UFix64
84
85 /// Initialize the balance at resource creation time
86 init(balance: UFix64) {
87 self.balance = balance
88 }
89
90 /// Called when a fungible token is burned via the `Burner.burn()` method
91 /// The total supply will only reflect the supply in the Cadence version
92 /// of the USDCFlow smart contract
93 access(contract) fun burnCallback() {
94 if self.balance > 0.0 {
95 assert(USDCFlow.totalSupply >= self.balance, message: "Cannot burn more than the total supply")
96 emit Burned(amount: self.balance, burntUUID: self.uuid)
97 USDCFlow.totalSupply = USDCFlow.totalSupply - self.balance
98 }
99 self.balance = 0.0
100 }
101
102 /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
103 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
104 let supportedTypes: {Type: Bool} = {}
105 supportedTypes[self.getType()] = true
106 return supportedTypes
107 }
108
109 /// Returns whether the specified type can be deposited
110 access(all) view fun isSupportedVaultType(type: Type): Bool {
111 return self.getSupportedVaultTypes()[type] ?? false
112 }
113
114 /// Asks if the amount can be withdrawn from this vault
115 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
116 return amount <= self.balance
117 }
118
119 access(all) fun createEmptyVault(): @USDCFlow.Vault {
120 return <-create Vault(balance: 0.0)
121 }
122
123 /// withdraw
124 /// @param amount: The amount of tokens to be withdrawn from the vault
125 /// @return The Vault resource containing the withdrawn funds
126 ///
127 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
128 self.balance = self.balance - amount
129 return <-create Vault(balance: amount)
130 }
131
132 /// deposit
133 /// @param from: The Vault resource containing the funds that will be deposited
134 ///
135 access(all) fun deposit(from: @{FungibleToken.Vault}) {
136 let vault <- from as! @USDCFlow.Vault
137 self.balance = self.balance + vault.balance
138 destroy vault
139 }
140
141 /// Gets an array of all the Metadata Views implemented by USDCFlow
142 ///
143 /// @return An array of Types defining the implemented views. This value will be used by
144 /// developers to know which parameter to pass to the resolveView() method.
145 ///
146 access(all) view fun getViews(): [Type] {
147 return USDCFlow.getContractViews(resourceType: nil)
148 }
149
150 /// Resolves Metadata Views out of the USDCFlow
151 ///
152 /// @param view: The Type of the desired view.
153 /// @return A structure representing the requested view.
154 ///
155 access(all) fun resolveView(_ view: Type): AnyStruct? {
156 return USDCFlow.resolveContractView(resourceType: nil, viewType: view)
157 }
158 }
159
160 access(all) resource Minter: FlowEVMBridgeHandlerInterfaces.TokenMinter {
161
162 /// Required function for the bridge to be able to work with the Minter
163 access(all) view fun getMintedType(): Type {
164 return Type<@USDCFlow.Vault>()
165 }
166
167 /// Function for the bridge to mint tokens that are bridged from Flow EVM
168 access(FlowEVMBridgeHandlerInterfaces.Mint) fun mint(amount: UFix64): @{FungibleToken.Vault} {
169 let newTotalSupply = USDCFlow.totalSupply + amount
170 USDCFlow.totalSupply = newTotalSupply
171
172 let vault <-create Vault(balance: amount)
173
174 emit Minted(amount: amount, mintedUUID: vault.uuid)
175 return <-vault
176 }
177
178 /// Function for the bridge to burn tokens that are bridged back to Flow EVM
179 access(all) fun burn(vault: @{FungibleToken.Vault}) {
180 let toBurn <- vault as! @USDCFlow.Vault
181 let amount = toBurn.balance
182
183 // This function updates USDCFlow.totalSupply
184 Burner.burn(<-toBurn)
185 }
186 }
187
188 /// createEmptyVault
189 ///
190 /// @return The new Vault resource with a balance of zero
191 ///
192 access(all) fun createEmptyVault(vaultType: Type): @Vault {
193 let r <-create Vault(balance: 0.0)
194 return <-r
195 }
196
197 init() {
198 self.totalSupply = 0.0
199 self.VaultStoragePath = /storage/usdcFlowVault
200 self.VaultPublicPath = /public/usdcFlowMetadata
201 self.ReceiverPublicPath = /public/usdcFlowReceiver
202
203 let minter <- create Minter()
204 self.account.storage.save(<-minter, to: /storage/usdcFlowMinter)
205
206 // Create the Vault with the total supply of tokens and save it in storage.
207 let vault <- create Vault(balance: self.totalSupply)
208 self.account.storage.save(<-vault, to: self.VaultStoragePath)
209
210 let tokenCap = self.account.capabilities.storage.issue<&USDCFlow.Vault>(self.VaultStoragePath)
211 self.account.capabilities.publish(tokenCap, at: self.ReceiverPublicPath)
212 self.account.capabilities.publish(tokenCap, at: self.VaultPublicPath)
213 }
214}
215