Smart Contract

BaitCoin

A.ed2202de80195438.BaitCoin

Valid From

128,691,675

Deployed

1w ago
Feb 20, 2026, 04:36:33 AM UTC

Dependents

33 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5// Note: USDF is an EVM bridged token at address 0x1e4aa0b87d10b141
6// We'll interact with it through the FungibleToken interface
7
8access(all) contract BaitCoin: FungibleToken {
9
10    // Token metadata
11    access(all) let name: String
12    access(all) let symbol: String
13    access(all) let decimals: UInt8
14    access(all) var logoUrl: String
15    access(all) var metadata: String
16    
17    // Total supply tracking
18    access(all) var totalSupply: UFix64
19
20    // Storage 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    access(all) let USDCVaultStoragePath: StoragePath
26
27    // Events
28    access(all) event TokensInitialized(initialSupply: UFix64)
29    access(all) event USDFToBaitSwap(user: Address, usdfAmount: UFix64, baitAmount: UFix64)
30    access(all) event BaitToUSDFSwap(user: Address, baitAmount: UFix64, usdfAmount: UFix64)
31    access(all) event LogoUrlUpdated(newLogoUrl: String)
32    access(all) event MetadataUpdated(newMetadata: String)
33    
34    // Minter resource for minting tokens
35    access(all) resource Minter {
36        access(all) fun mintTokens(amount: UFix64): @{FungibleToken.Vault} {
37            BaitCoin.totalSupply = BaitCoin.totalSupply + amount
38            return <-create Vault(balance: amount)
39        }
40    }
41
42    // Admin resource for minting/burning and admin management
43    access(all) resource Admin {
44        access(all) fun mintBait(amount: UFix64, recipient: Address) {
45            BaitCoin.totalSupply = BaitCoin.totalSupply + amount
46            
47            let recipientAccount = getAccount(recipient)
48            let receiver = recipientAccount.capabilities.get<&{FungibleToken.Receiver}>(BaitCoin.ReceiverPublicPath)
49                .borrow() ?? panic("Could not borrow receiver reference")
50            
51            let tempVault <- create Vault(balance: amount)
52            receiver.deposit(from: <-tempVault)
53        }
54        
55        access(all) fun burnBait(amount: UFix64, from: Address) {
56            // Note: This function requires the transaction to have proper authorization
57            // to withdraw from the target account. The actual burning should be done
58            // in the transaction that calls this function.
59            panic("This function should be called from a transaction with proper authorization")
60        }
61        
62        access(all) fun setLogoUrl(newLogoUrl: String) {
63            BaitCoin.logoUrl = newLogoUrl
64            emit LogoUrlUpdated(newLogoUrl: newLogoUrl)
65        }
66        
67        access(all) fun setMetadata(newMetadata: String) {
68            BaitCoin.metadata = newMetadata
69            emit MetadataUpdated(newMetadata: newMetadata)
70        }
71    }
72    
73    // Admin management resource
74    access(all) resource AdminManager {
75        // Note: These functions require proper authorization and should be called from transactions
76        // that have the necessary capabilities
77        access(all) fun addAdmin(adminAddress: Address, adminCapability: Capability<&BaitCoin.Admin>) {
78            // This function should be called from a transaction with proper authorization
79            // The transaction signer must have the capability to publish at the target account
80            panic("This function should be called from a transaction with proper authorization")
81        }
82        
83        access(all) fun removeAdmin(adminAddress: Address) {
84            // This function should be called from a transaction with proper authorization
85            // The transaction signer must have the capability to unpublish at the target account
86            panic("This function should be called from a transaction with proper authorization")
87        }
88    }
89    
90    // Main vault resource
91    access(all) resource Vault: FungibleToken.Vault, ViewResolver.Resolver {
92        access(all) var balance: UFix64
93
94        init(balance: UFix64) {
95            self.balance = balance
96        }
97
98        access(all) view fun getViews(): [Type] {
99            return [
100                Type<FungibleTokenMetadataViews.FTDisplay>(),
101                Type<FungibleTokenMetadataViews.FTVaultData>()
102            ]
103        }
104
105        access(all) fun resolveView(_ view: Type): AnyStruct? {
106            switch view {
107                case Type<FungibleTokenMetadataViews.FTDisplay>():
108                    let media = MetadataViews.Media(
109                        file: MetadataViews.HTTPFile(url: BaitCoin.logoUrl),
110                        mediaType: "image/png"
111                    )
112                    return FungibleTokenMetadataViews.FTDisplay(
113                        name: BaitCoin.name,
114                        symbol: BaitCoin.symbol,
115                        description: BaitCoin.metadata,
116                        externalURL: MetadataViews.ExternalURL("https://derby.fish"),
117                        logos: MetadataViews.Medias([media]),
118                        socials: {
119                            "website": MetadataViews.ExternalURL("https://derby.fish/bait-coin-logo.png"),
120                            "twitter": MetadataViews.ExternalURL("https://twitter.com/derby_fish")
121                        }
122                    )
123                case Type<FungibleTokenMetadataViews.FTVaultData>():
124                    return FungibleTokenMetadataViews.FTVaultData(
125                        storagePath: BaitCoin.VaultStoragePath,
126                        receiverPath: BaitCoin.ReceiverPublicPath,
127                        metadataPath: BaitCoin.VaultPublicPath,
128                        receiverLinkedType: Type<&BaitCoin.Vault>(),
129                        metadataLinkedType: Type<&BaitCoin.Vault>(),
130                        createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
131                            return <-BaitCoin.createEmptyVault(vaultType: Type<@BaitCoin.Vault>())
132                        })
133                    )
134            }
135            return nil
136        }
137
138        access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
139            return {Type<@BaitCoin.Vault>(): true}
140        }
141
142        access(all) view fun isSupportedVaultType(type: Type): Bool {
143            return type == Type<@BaitCoin.Vault>()
144        }
145
146        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
147            return amount <= self.balance
148        }
149
150        access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @BaitCoin.Vault {
151            pre {
152                amount <= self.balance: "Amount withdrawn must be less than or equal to the balance of the Vault"
153            }
154            self.balance = self.balance - amount
155            return <-create Vault(balance: amount)
156        }
157
158        access(all) fun deposit(from: @{FungibleToken.Vault}) {
159            let vault <- from as! @BaitCoin.Vault
160            self.balance = self.balance + vault.balance
161            vault.balance = 0.0
162            destroy vault
163        }
164
165        access(all) fun createEmptyVault(): @{FungibleToken.Vault} {
166            return <-create Vault(balance: 0.0)
167        }
168
169        access(all) fun createEmptyVaultWithType(vaultType: Type): @{FungibleToken.Vault} {
170        pre {
171            vaultType == Type<@BaitCoin.Vault>(): "Vault type mismatch"
172        }
173        return <-create Vault(balance: 0.0)
174    }
175
176
177    }
178
179    access(all) fun swapUSDFToBait(usdfVault: @{FungibleToken.Vault}, userAddress: Address): @{FungibleToken.Vault} {
180        let usdfAmount = (usdfVault).balance
181        
182        if usdfAmount <= 0.0 {
183            panic("Amount must be greater than zero")
184        }
185        
186        // Get the user's BAIT vault
187        let userAccount = getAccount(userAddress)
188        log("Attempting to get BAIT receiver for user: ".concat(userAddress.toString()))
189        log("Looking for receiver at path: ".concat(BaitCoin.ReceiverPublicPath.toString()))
190        
191        let receiverCapability = userAccount.capabilities.get<&{FungibleToken.Receiver}>(BaitCoin.ReceiverPublicPath)
192        if receiverCapability != nil {
193            log("Receiver capability found: true")
194        } else {
195            log("Receiver capability found: false")
196        }
197        
198        let baitReceiver = receiverCapability
199            .borrow() ?? panic("Could not borrow BAIT receiver reference. Please run setup_vault.cdc first to create your BAIT vault.")
200        
201        // Deposit USDF to contract's vault for future BAIT to USDF swaps
202        // Store the USDF in the original EVM vault path
203        let contractUSDFVault = BaitCoin.account.storage.borrow<&{FungibleToken.Vault}>(from: /storage/EVMVMBridgedToken_2aabea2058b5ac2d339b163c6ab6f2b6d53aabedVault)
204        if contractUSDFVault == nil {
205            // Create the USDF vault if it doesn't exist by saving the incoming vault
206            BaitCoin.account.storage.save(<-usdfVault, to: /storage/EVMVMBridgedToken_2aabea2058b5ac2d339b163c6ab6f2b6d53aabedVault)
207        } else {
208            // Deposit to existing vault
209            contractUSDFVault!.deposit(from: <-usdfVault)
210        }
211        
212        // Mint equivalent amount of BAIT
213        BaitCoin.totalSupply = BaitCoin.totalSupply + usdfAmount
214        let baitVault <- create Vault(balance: usdfAmount)
215        
216        // Send BAIT to user
217        baitReceiver.deposit(from: <-baitVault)
218        
219        emit USDFToBaitSwap(user: userAddress, usdfAmount: usdfAmount, baitAmount: usdfAmount)
220        
221        // Return empty vault for transaction completion
222        return <-create Vault(balance: 0.0)
223    }
224    
225    access(all) fun swapBaitToUSDF(baitVault: @{FungibleToken.Vault}, userAddress: Address): @{FungibleToken.Vault} {
226        let baitAmount = (baitVault).balance
227        
228        if baitAmount <= 0.0 {
229            panic("Amount must be greater than zero")
230        }
231        
232        // Get the user's USDF vault
233        let userAccount = getAccount(userAddress)
234        let usdfReceiverCapability = userAccount.capabilities.get<&{FungibleToken.Receiver}>(/public/usdfReceiver)
235        let usdfReceiver = usdfReceiverCapability
236            .borrow() ?? panic("Could not borrow USDF receiver reference. Please run createAllVault.cdc first to create your USDF vault.")
237        
238        // Burn the BAIT tokens (reduce total supply)
239        BaitCoin.totalSupply = BaitCoin.totalSupply - baitAmount
240        destroy baitVault
241        
242        // Withdraw equivalent USDF from contract's vault
243        let contractUSDFVault = BaitCoin.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(from: /storage/EVMVMBridgedToken_2aabea2058b5ac2d339b163c6ab6f2b6d53aabedVault)
244            ?? panic("Could not borrow contract USDF vault")
245        
246        let usdfVault <- contractUSDFVault.withdraw(amount: baitAmount)
247        
248        // Send USDF to user
249        usdfReceiver.deposit(from: <-usdfVault)
250        
251        emit BaitToUSDFSwap(user: userAddress, baitAmount: baitAmount, usdfAmount: baitAmount)
252        
253        // Return empty vault for transaction completion
254        return <-create Vault(balance: 0.0)
255    }
256    
257    
258    access(all) fun getTokenInfo(): {String: String} {
259        return {
260            "name": self.name,
261            "symbol": self.symbol,
262            "logoUrl": self.logoUrl,
263            "metadata": self.metadata,
264            "totalSupply": self.totalSupply.toString(),
265            "decimals": self.decimals.toString()
266        }
267    }
268    
269    // FungibleTokenMetadataViews.Resolver implementation
270    access(all) fun getViews(): [Type] {
271        return [Type<FungibleTokenMetadataViews.FTView>()]
272    }
273    
274    access(all) view fun getContractViews(resourceType: Type?): [Type] {
275        return [
276            Type<FungibleTokenMetadataViews.FTDisplay>(),
277            Type<FungibleTokenMetadataViews.FTVaultData>(),
278            Type<FungibleTokenMetadataViews.TotalSupply>()
279        ]
280    }
281    
282    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
283        switch viewType {
284            case Type<FungibleTokenMetadataViews.FTDisplay>():
285                let media = MetadataViews.Media(
286                    file: MetadataViews.HTTPFile(url: self.logoUrl),
287                    mediaType: "image/png"
288                )
289                return FungibleTokenMetadataViews.FTDisplay(
290                    name: self.name,
291                    symbol: self.symbol,
292                    description: self.metadata,
293                    externalURL: MetadataViews.ExternalURL("https://derby.fish"),
294                    logos: MetadataViews.Medias([media]),
295                    socials: {
296                        "website": MetadataViews.ExternalURL("https://derby.fish/bait-coin-logo.png"),
297                        "twitter": MetadataViews.ExternalURL("https://twitter.com/derby_fish")
298                    }
299                )
300            case Type<FungibleTokenMetadataViews.FTVaultData>():
301                return FungibleTokenMetadataViews.FTVaultData(
302                    storagePath: self.VaultStoragePath,
303                    receiverPath: self.ReceiverPublicPath,
304                    metadataPath: self.VaultPublicPath,
305                    receiverLinkedType: Type<&BaitCoin.Vault>(),
306                    metadataLinkedType: Type<&BaitCoin.Vault>(),
307                    createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
308                        return <-BaitCoin.createEmptyVault(vaultType: Type<@BaitCoin.Vault>())
309                    })
310                )
311            case Type<FungibleTokenMetadataViews.TotalSupply>():
312                return FungibleTokenMetadataViews.TotalSupply(totalSupply: self.totalSupply)
313        }
314        return nil
315    }
316    
317    access(all) fun getSupportedVaultTypes(): [Type] {
318        return [Type<@BaitCoin.Vault>()]
319    }
320
321    access(all) fun createEmptyVault(vaultType: Type): @BaitCoin.Vault {
322        return <-create Vault(balance: 0.0)
323    }
324    
325    // Admin function to burn tokens and reduce total supply
326    access(all) fun burnTokens(amount: UFix64) {
327        self.totalSupply = self.totalSupply - amount
328    }
329    
330    // Admin function to withdraw USDF from contract
331    access(all) fun withdrawUSDF(amount: UFix64, recipient: Address) {
332        let contractUSDFVault = BaitCoin.account.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(from: /storage/EVMVMBridgedToken_2aabea2058b5ac2d339b163c6ab6f2b6d53aabedVault)
333            ?? panic("Could not borrow contract USDF vault")
334        
335        let usdfVault <- contractUSDFVault.withdraw(amount: amount)
336        
337        let recipientAccount = getAccount(recipient)
338        let receiver = recipientAccount.capabilities.get<&{FungibleToken.Receiver}>(/public/usdfReceiver)
339            .borrow() ?? panic("Could not borrow recipient's USDF receiver reference")
340        
341        receiver.deposit(from: <-usdfVault)
342    }
343    
344    // Initialize the contract
345    init() {
346        self.name = "BAIT Coin"
347        self.symbol = "BAIT"
348        self.decimals = 8
349        self.logoUrl = "https://derby.fish/bait-coin-logo.png"
350        self.metadata = "BAIT COIN - A 1:1 pegged USDF token for the DerbyFish (https://derby.fish) ecosystem."
351        self.totalSupply = 0.0
352
353        // Set storage paths
354        self.VaultStoragePath = /storage/baitCoinVault
355        self.VaultPublicPath = /public/baitCoinVault
356        self.ReceiverPublicPath = /public/baitCoinReceiver
357        self.MinterStoragePath = /storage/baitCoinMinter
358        self.USDCVaultStoragePath = /storage/baitCoinUSDCVault
359
360        // Create and store the minter resource
361        let minter <- create Minter()
362        self.account.storage.save(<-minter, to: self.MinterStoragePath)
363
364        // Create and store the admin resource
365        let admin <- create Admin()
366        self.account.storage.save(<-admin, to: /storage/baitCoinAdmin)
367        let adminCapability = self.account.capabilities.storage.issue<&BaitCoin.Admin>(/storage/baitCoinAdmin)
368        self.account.capabilities.publish(adminCapability, at: /public/baitCoinAdmin)
369        
370        // Create and store the admin manager resource
371        let adminManager <- create AdminManager()
372        self.account.storage.save(<-adminManager, to: /storage/baitCoinAdminManager)
373        let adminManagerCapability = self.account.capabilities.storage.issue<&BaitCoin.AdminManager>(/storage/baitCoinAdminManager)
374        self.account.capabilities.publish(adminManagerCapability, at: /public/baitCoinAdminManager)
375        
376        // Note: USDF vault will be created when first USDF tokens are received
377        // The vault will be created dynamically in the swap functions
378        
379        emit TokensInitialized(initialSupply: self.totalSupply)
380    }
381}