Smart Contract

ContractManager

A.befbaccb5032a457.ContractManager

Deployed

1h ago
Feb 28, 2026, 09:42:23 PM UTC

Dependents

0 imports
1import FlowToken from 0x1654653399040a61
2import FungibleToken from 0xf233dcee88fe0abe
3import FungibleTokenRouter from 0x707c0b39a8d689cb
4import HybridCustody from 0xd8a7e05a7ac670c0
5import MetadataViews from 0x1d7e57aa55817448
6import ViewResolver from 0x1d7e57aa55817448
7import AddressUtils from 0xa340dc0a4ec828ab
8import CapabilityFactory from 0xd8a7e05a7ac670c0
9import CapabilityFilter from 0xd8a7e05a7ac670c0
10import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
11
12access(all) contract ContractManager {
13    access(all) let StoragePath: StoragePath
14    access(all) let PublicPath: PublicPath
15
16    access(all) let OwnerStoragePath: StoragePath
17    access(all) let OwnerPublicPath: PublicPath
18
19    access(all) entitlement Manage
20
21    access(all) event ManagerSaved(uuid: UInt64, contractAddress: Address, ownerAddress: Address)
22
23    access(all) resource Manager {
24        access(self) let acct: Capability<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>
25        access(self) let routerCap: Capability<auth(FungibleTokenRouter.Owner) &FungibleTokenRouter.Router>
26
27        access(all) let data: {String: AnyStruct}
28        access(all) let resources: @{String: AnyResource}
29
30        access(Manage) fun borrowContractAccount(): auth(Contracts) &Account {
31            return self.acct.borrow()!
32        }
33
34        access(Manage) fun addOverride(type: Type, addr: Address) {
35            let router = self.routerCap.borrow() ?? panic("fungible token router is not valid")
36            router.addOverride(type: type, addr: addr)
37        }
38
39        access(Manage) fun getSwitchboard(): auth(FungibleTokenRouter.Owner) &FungibleTokenRouter.Router {
40            return self.routerCap.borrow()!
41        }
42
43        access(all) fun addFlowTokensToAccount(_ tokens: @FlowToken.Vault) {
44            self.acct.borrow()!.storage.borrow<&{FungibleToken.Receiver}>(from: /storage/flowTokenVault)!.deposit(from: <-tokens)
45        }
46
47        access(all) fun getAccount(): &Account {
48            return getAccount(self.acct.address)
49        }
50
51        // Should be called after saving a ContractManager resource to signal that a new address stores (and therefore "owns")
52        // this manager resource's acct capability. Without this, it is not possible to track the original creator of a contract
53        // when using the ContractManager
54        access(Manage) fun onSave() {
55            let acct = self.acct.borrow()!
56
57            acct.storage.load<Address>(from: ContractManager.OwnerStoragePath)
58            acct.storage.save(self.owner!.address, to: ContractManager.OwnerStoragePath)
59
60            if !acct.capabilities.get<&Address>(ContractManager.OwnerPublicPath).check() {
61                acct.capabilities.unpublish(ContractManager.OwnerPublicPath)
62                acct.capabilities.publish(
63                    acct.capabilities.storage.issue<&Address>(ContractManager.OwnerStoragePath),
64                    at: ContractManager.OwnerPublicPath
65                )
66            }
67
68            self.configureHybridCustody(acct: acct)
69            emit ManagerSaved(uuid: self.uuid, contractAddress: self.acct.address, ownerAddress: self.owner!.address)
70        }
71
72        init(tokens: @FlowToken.Vault, defaultRouterAddress: Address) {
73            pre {
74                tokens.balance >= 0.001: "minimum balance of 0.001 required for initialization"
75            }
76
77            let acct = Account(payer: ContractManager.account)
78            self.acct = acct.capabilities.account.issue<auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account>()
79            assert(self.acct.check(), message: "failed to setup account capability")
80
81            acct.storage.borrow<&{FungibleToken.Receiver}>(from: /storage/flowTokenVault)!.deposit(from: <-tokens)
82
83            // setup a provider capability so that tokens are accessible via hybrid custody
84            acct.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault)
85
86            let router <- FungibleTokenRouter.createRouter(defaultAddress: defaultRouterAddress)
87            acct.storage.save(<-router, to: FungibleTokenRouter.StoragePath)
88
89            let receiver = acct.capabilities.storage.issue<&{FungibleToken.Receiver}>(FungibleTokenRouter.StoragePath)
90            assert(receiver.check(), message: "invalid switchboard receiver capability")
91            acct.capabilities.publish(receiver, at: FungibleTokenRouter.PublicPath)
92
93            self.routerCap = acct.capabilities.storage.issue<auth(FungibleTokenRouter.Owner) &FungibleTokenRouter.Router>(FungibleTokenRouter.StoragePath)
94
95            self.data = {}
96            self.resources <- {}
97        }
98
99        access(self) fun configureHybridCustody(acct: auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account) {
100            if acct.storage.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) == nil {
101                let ownedAccount <- HybridCustody.createOwnedAccount(acct: self.acct)
102                acct.storage.save(<-ownedAccount, to: HybridCustody.OwnedAccountStoragePath)
103            }
104
105            let owned = acct.storage.borrow<auth(HybridCustody.Owner) &HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath)
106                ?? panic("owned account not found")
107
108            let thumbnail = MetadataViews.HTTPFile(url: "https://avatars.flowty.io/6.x/thumbs/png?seed=".concat(self.acct.address.toString()))
109            let display = MetadataViews.Display(name: "Creator Hub", description: "Created by the Flowty Creator Hub", thumbnail: thumbnail)
110            owned.setDisplay(display)
111
112            if !acct.capabilities.get<&{HybridCustody.OwnedAccountPublic, ViewResolver.Resolver}>(HybridCustody.OwnedAccountPublicPath).check() {
113                acct.capabilities.unpublish(HybridCustody.OwnedAccountPublicPath)
114                acct.capabilities.storage.issue<&{HybridCustody.BorrowableAccount, HybridCustody.OwnedAccountPublic, ViewResolver.Resolver}>(HybridCustody.OwnedAccountStoragePath)
115                acct.capabilities.publish(
116                    acct.capabilities.storage.issue<&{HybridCustody.OwnedAccountPublic, ViewResolver.Resolver}>(HybridCustody.OwnedAccountStoragePath),
117                    at: HybridCustody.OwnedAccountPublicPath
118                )
119            }
120
121            // make sure that only the owner of this resource is a valid parent
122            let parents = owned.getParentAddresses()
123            let owner = self.owner!.address
124            var foundOwner = false
125            for parent in parents {
126                if parent == owner {
127                    foundOwner = true
128                    continue
129                }
130
131                // found a parent that should not be present
132                owned.removeParent(parent: parent)
133            }
134
135            if foundOwner {
136                return
137            }
138
139            // Flow maintains a set of pre-configured filter and factory resources that we will use:
140            // https://github.com/onflow/hybrid-custody?tab=readme-ov-file#hosted-capabilityfactory--capabilityfilter-implementations
141            var factoryAddress = ContractManager.account.address
142            var filterAddress = ContractManager.account.address
143            if let network = AddressUtils.getNetworkFromAddress(ContractManager.account.address) {
144                switch network {
145                    case "TESTNET":
146                        factoryAddress = Address(0x1b7fa5972fcb8af5)
147                        filterAddress = Address(0xe2664be06bb0fe62)
148                        break
149                    case "MAINNET":
150                        factoryAddress = Address(0x071d382668250606)
151                        filterAddress = Address(0x78e93a79b05d0d7d)
152                        break
153                }
154            }
155
156            owned.publishToParent(
157                parentAddress: owner,
158                factory: getAccount(factoryAddress!).capabilities.get<&CapabilityFactory.Manager>(CapabilityFactory.PublicPath),
159                filter: getAccount(filterAddress!).capabilities.get<&{CapabilityFilter.Filter}>(CapabilityFilter.PublicPath)
160            )
161        }
162
163        // Configure a given fungible token vault so that it can be received by this contract account
164        access(Manage) fun configureVault(vaultType: Type) {
165            pre {
166                vaultType.isSubtype(of: Type<@{FungibleToken.Vault}>()): "vault must be a fungible token"
167            }
168
169            let address = AddressUtils.parseAddress(vaultType)!
170            let name = vaultType.identifier.split(separator: ".")[2]
171
172            let ftContract = getAccount(address).contracts.borrow<&{FungibleToken}>(name: name)
173                ?? panic("vault contract does not implement FungibleToken")
174            let data = ftContract.resolveContractView(resourceType: vaultType, viewType: Type<FungibleTokenMetadataViews.FTVaultData>())! as! FungibleTokenMetadataViews.FTVaultData
175
176            let acct = self.acct.borrow()!
177            if acct.storage.type(at: data.storagePath) == nil {
178                acct.storage.save(<- ftContract.createEmptyVault(vaultType: vaultType), to: data.storagePath)
179            }
180
181            if !acct.capabilities.get<&{FungibleToken.Receiver}>(data.receiverPath).check() {
182                acct.capabilities.unpublish(data.receiverPath)
183                acct.capabilities.publish(
184                    acct.capabilities.storage.issue<&{FungibleToken.Receiver}>(data.storagePath),
185                    at: data.receiverPath
186                )
187            }
188
189            if !acct.capabilities.get<&{FungibleToken.Receiver}>(data.metadataPath).check() {
190                acct.capabilities.unpublish(data.metadataPath)
191                acct.capabilities.publish(
192                    acct.capabilities.storage.issue<&{FungibleToken.Vault}>(data.storagePath),
193                    at: data.metadataPath
194                )
195            }
196
197            // is there a valid provider capability for this vault type?
198            var foundProvider = false
199            for controller in acct.capabilities.storage.getControllers(forPath: data.storagePath) {
200                if controller.borrowType.isSubtype(of: Type<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>()) {
201                    foundProvider = true
202                    break
203                }
204            }
205
206            if foundProvider {
207                return
208            }
209
210            // we did not find a provider, issue one so that its parent account is able to access it.
211            acct.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(data.storagePath)
212        }
213    }
214
215    access(all) fun createManager(tokens: @FlowToken.Vault, defaultRouterAddress: Address): @Manager {
216        return <- create Manager(tokens: <- tokens, defaultRouterAddress: defaultRouterAddress)
217    }
218
219    init() {
220        let identifier = "ContractManager_".concat(self.account.address.toString())
221        self.StoragePath = StoragePath(identifier: identifier)!
222        self.PublicPath = PublicPath(identifier: identifier)!
223
224        let ownerIdentifier = "ContractManager_Owner_".concat(self.account.address.toString())
225        self.OwnerStoragePath = StoragePath(identifier: ownerIdentifier)!
226        self.OwnerPublicPath = PublicPath(identifier: ownerIdentifier)!
227    }
228}