Smart Contract
ContractManager
A.befbaccb5032a457.ContractManager
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}