Smart Contract
BaitCoin
A.44100f14f70e3f78.BaitCoin
1import FungibleToken from 0xf233dcee88fe0abe
2import MetadataViews from 0x1d7e57aa55817448
3import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
4import FUSD from 0x3c5959b568896393
5
6access(all) contract BaitCoin: FungibleToken {
7
8 /// The event that is emitted when new tokens are minted
9 access(all) event TokensMinted(amount: UFix64, type: String)
10
11 /// The event that is emitted when FUSD is swapped for BaitCoin
12 access(all) event FUSDSwappedForBaitCoin(fusdAmount: UFix64, baitCoinAmount: UFix64, account: Address)
13
14 /// The event that is emitted when BaitCoin is swapped for FUSD
15 access(all) event BaitCoinSwappedForFUSD(baitCoinAmount: UFix64, fusdAmount: UFix64, account: Address)
16
17 /// Total supply of BaitCoins 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 access(all) let MinterStoragePath: StoragePath
25
26 access(all) view fun getContractViews(resourceType: Type?): [Type] {
27 return [
28 Type<FungibleTokenMetadataViews.FTView>(),
29 Type<FungibleTokenMetadataViews.FTDisplay>(),
30 Type<FungibleTokenMetadataViews.FTVaultData>(),
31 Type<FungibleTokenMetadataViews.TotalSupply>()
32 ]
33 }
34
35 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
36 switch viewType {
37 case Type<FungibleTokenMetadataViews.FTView>():
38 return FungibleTokenMetadataViews.FTView(
39 ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
40 ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
41 )
42 case Type<FungibleTokenMetadataViews.FTDisplay>():
43 let media = MetadataViews.Media(
44 file: MetadataViews.HTTPFile(
45 // Change this to your own SVG image
46 url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
47 ),
48 mediaType: "image/svg+xml"
49 )
50 let medias = MetadataViews.Medias([media])
51 return FungibleTokenMetadataViews.FTDisplay(
52 // Change these to represent your own token
53 name: "BaitCoin",
54 symbol: "BAIT",
55 description: "BaitCoin is the primary currency for the DerbyFish ecosystem.",
56 externalURL: MetadataViews.ExternalURL("https://derbyfish.example.com"),
57 logos: medias,
58 socials: {
59 "twitter": MetadataViews.ExternalURL("https://twitter.com/derbyfish")
60 }
61 )
62 case Type<FungibleTokenMetadataViews.FTVaultData>():
63 return FungibleTokenMetadataViews.FTVaultData(
64 storagePath: self.VaultStoragePath,
65 receiverPath: self.VaultPublicPath,
66 metadataPath: self.VaultPublicPath,
67 receiverLinkedType: Type<&BaitCoin.Vault>(),
68 metadataLinkedType: Type<&BaitCoin.Vault>(),
69 createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
70 return <-BaitCoin.createEmptyVault(vaultType: Type<@BaitCoin.Vault>())
71 })
72 )
73 case Type<FungibleTokenMetadataViews.TotalSupply>():
74 return FungibleTokenMetadataViews.TotalSupply(
75 totalSupply: BaitCoin.totalSupply
76 )
77 }
78 return nil
79 }
80
81 access(all) resource Vault: FungibleToken.Vault {
82
83 /// The total balance of this vault
84 access(all) var balance: UFix64
85
86 // initialize the balance at resource creation time
87 init(balance: UFix64) {
88 self.balance = balance
89 }
90
91 /// Called when a fungible token is burned via the `Burner.burn()` method
92 access(contract) fun burnCallback() {
93 if self.balance > 0.0 {
94 BaitCoin.totalSupply = BaitCoin.totalSupply - self.balance
95 }
96 self.balance = 0.0
97 }
98
99 access(all) view fun getViews(): [Type] {
100 return BaitCoin.getContractViews(resourceType: nil)
101 }
102
103 access(all) fun resolveView(_ view: Type): AnyStruct? {
104 return BaitCoin.resolveContractView(resourceType: nil, viewType: view)
105 }
106
107 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
108 let supportedTypes: {Type: Bool} = {}
109 supportedTypes[self.getType()] = true
110 return supportedTypes
111 }
112
113 access(all) view fun isSupportedVaultType(type: Type): Bool {
114 return self.getSupportedVaultTypes()[type] ?? false
115 }
116
117 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
118 return amount <= self.balance
119 }
120
121 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @BaitCoin.Vault {
122 self.balance = self.balance - amount
123 return <-create Vault(balance: amount)
124 }
125
126 access(all) fun deposit(from: @{FungibleToken.Vault}) {
127 let vault <- from as! @BaitCoin.Vault
128 self.balance = self.balance + vault.balance
129 vault.balance = 0.0
130 destroy vault
131 }
132
133 access(all) fun createEmptyVault(): @BaitCoin.Vault {
134 return <-create Vault(balance: 0.0)
135 }
136 }
137
138 access(all) resource Minter {
139 /// Internal minting function - only accessible within this resource
140 access(self) fun mintTokensInternal(amount: UFix64): @BaitCoin.Vault {
141 BaitCoin.totalSupply = BaitCoin.totalSupply + amount
142 let vault <-create Vault(balance: amount)
143 emit TokensMinted(amount: amount, type: vault.getType().identifier)
144 return <-vault
145 }
146 }
147
148 access(all) fun createEmptyVault(vaultType: Type): @BaitCoin.Vault {
149 return <- create Vault(balance: 0.0)
150 }
151
152 /// Get the FUSD balance stored in the contract
153 access(all) fun getContractFUSDBalance(): UFix64 {
154 let fusdVault = self.account.storage.borrow<&FUSD.Vault>(from: /storage/BaitCoinFUSDVault)
155 ?? panic("Could not borrow contract FUSD vault")
156 return fusdVault.balance
157 }
158
159 /// Public swap function: FUSD for BaitCoin
160 access(all) fun swapFUSDForBaitCoin(from: @FUSD.Vault, recipient: Address) {
161 let fusdAmount = from.balance
162
163 // Get reference to contract's FUSD vault and deposit received FUSD
164 let contractFUSDVault = self.account.storage.borrow<&FUSD.Vault>(from: /storage/BaitCoinFUSDVault)
165 ?? panic("Could not borrow reference to contract's FUSD vault")
166 contractFUSDVault.deposit(from: <-from)
167
168 // Mint equivalent BaitCoin internally
169 self.totalSupply = self.totalSupply + fusdAmount
170 let newBaitCoin <- create Vault(balance: fusdAmount)
171 emit TokensMinted(amount: fusdAmount, type: newBaitCoin.getType().identifier)
172
173 // Get recipient's BaitCoin receiver
174 let recipientReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(self.VaultPublicPath)
175 ?? panic("Could not borrow receiver capability for recipient")
176
177 // Deposit BaitCoin to recipient
178 recipientReceiver.deposit(from: <-newBaitCoin)
179
180 emit FUSDSwappedForBaitCoin(fusdAmount: fusdAmount, baitCoinAmount: fusdAmount, account: recipient)
181 }
182
183 /// Public swap function: BaitCoin for FUSD
184 access(all) fun swapBaitCoinForFUSD(from: @BaitCoin.Vault, recipient: Address) {
185 let baitCoinAmount = from.balance
186
187 // Burn the received BaitCoin
188 self.totalSupply = self.totalSupply - baitCoinAmount
189 destroy from
190
191 // Get reference to contract's FUSD vault and withdraw equivalent FUSD
192 let contractFUSDVault = self.account.storage.borrow<auth(FungibleToken.Withdraw) &FUSD.Vault>(from: /storage/BaitCoinFUSDVault)
193 ?? panic("Could not borrow reference to contract's FUSD vault")
194
195 let fusdToSend <- contractFUSDVault.withdraw(amount: baitCoinAmount)
196
197 // Get recipient's FUSD receiver
198 let recipientReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(/public/fusdReceiver)
199 ?? panic("Could not borrow FUSD receiver capability for recipient")
200
201 // Deposit FUSD to recipient
202 recipientReceiver.deposit(from: <-fusdToSend)
203
204 emit BaitCoinSwappedForFUSD(baitCoinAmount: baitCoinAmount, fusdAmount: baitCoinAmount, account: recipient)
205 }
206
207 init() {
208 self.totalSupply = 0.0
209
210 self.VaultStoragePath = /storage/BaitCoinVault
211 self.VaultPublicPath = /public/BaitCoinVault
212 self.ReceiverPublicPath = /public/BaitCoinReceiver
213 self.MinterStoragePath = /storage/BaitCoinMinter
214
215 // Create the Vault with the total supply of tokens and save it in storage
216 //
217 let vault <- create Vault(balance: self.totalSupply)
218 emit TokensMinted(amount: vault.balance, type: vault.getType().identifier)
219 self.account.storage.save(<-vault, to: self.VaultStoragePath)
220
221 // Create a public capability to the stored Vault that exposes
222 // the `deposit` method and getAcceptedTypes method through the `Receiver` interface
223 // and the `balance` method through the `Balance` interface
224 //
225 let BaitCoinCap = self.account.capabilities.storage.issue<&BaitCoin.Vault>(self.VaultStoragePath)
226 self.account.capabilities.publish(BaitCoinCap, at: self.VaultPublicPath)
227
228 // Create a FUSD vault for the contract to store received FUSD
229 let fusdVault <- FUSD.createEmptyVault(vaultType: Type<@FUSD.Vault>())
230 self.account.storage.save(<-fusdVault, to: /storage/BaitCoinFUSDVault)
231
232 let minter <- create Minter()
233 self.account.storage.save(<-minter, to: self.MinterStoragePath)
234 }
235}
236