Smart Contract
FungibleTokenSwitchboard
A.f233dcee88fe0abe.FungibleTokenSwitchboard
1import FungibleToken from 0xf233dcee88fe0abe
2
3/// The contract that allows an account to receive payments in multiple fungible
4/// tokens using a single `{FungibleToken.Receiver}` capability.
5/// This capability should ideally be stored at the
6/// `FungibleTokenSwitchboard.ReceiverPublicPath = /public/GenericFTReceiver`
7/// but it can be stored anywhere.
8///
9access(all) contract FungibleTokenSwitchboard {
10
11 // Storage and Public Paths
12 access(all) let StoragePath: StoragePath
13 access(all) let PublicPath: PublicPath
14 access(all) let ReceiverPublicPath: PublicPath
15
16 access(all) entitlement Owner
17
18 /// The event that is emitted when a new vault capability is added to a
19 /// switchboard resource.
20 ///
21 access(all) event VaultCapabilityAdded(type: Type, switchboardOwner: Address?,
22 capabilityOwner: Address?)
23
24 /// The event that is emitted when a vault capability is removed from a
25 /// switchboard resource.
26 ///
27 access(all) event VaultCapabilityRemoved(type: Type, switchboardOwner: Address?,
28 capabilityOwner: Address?)
29
30 /// The event that is emitted when a deposit can not be completed.
31 ///
32 access(all) event NotCompletedDeposit(type: Type, amount: UFix64,
33 switchboardOwner: Address?)
34
35 /// The interface that enforces the method to allow anyone to check on the
36 /// available capabilities of a switchboard resource and also exposes the
37 /// deposit methods to deposit funds on it.
38 ///
39 access(all) resource interface SwitchboardPublic {
40 access(all) view fun getVaultTypesWithAddress(): {Type: Address}
41 access(all) view fun getSupportedVaultTypes(): {Type: Bool}
42 access(all) view fun isSupportedVaultType(type: Type): Bool
43 access(all) fun deposit(from: @{FungibleToken.Vault})
44 access(all) fun safeDeposit(from: @{FungibleToken.Vault}): @{FungibleToken.Vault}?
45 access(all) view fun safeBorrowByType(type: Type): &{FungibleToken.Receiver}?
46 }
47
48 /// The resource that stores the multiple fungible token receiver
49 /// capabilities, allowing the owner to add and remove them and anyone to
50 /// deposit any fungible token among the available types.
51 ///
52 access(all) resource Switchboard: FungibleToken.Receiver, SwitchboardPublic {
53
54 /// Dictionary holding the fungible token receiver capabilities,
55 /// indexed by the fungible token vault type.
56 ///
57 access(contract) var receiverCapabilities: {Type: Capability<&{FungibleToken.Receiver}>}
58
59 /// Adds a new fungible token receiver capability to the switchboard
60 /// resource.
61 ///
62 /// @param capability: The capability to expose a certain fungible
63 /// token vault deposit function through `{FungibleToken.Receiver}` that
64 /// will be added to the switchboard.
65 ///
66 access(Owner) fun addNewVault(capability: Capability<&{FungibleToken.Receiver}>) {
67 // Borrow a reference to the vault pointed to by the capability we
68 // want to store inside the switchboard
69 let vaultRef = capability.borrow()
70 ?? panic("FungibleTokenSwitchboard.Switchboard.addNewVault: Cannot borrow reference to vault from capability! "
71 .concat("Make sure that the capability path points to a Vault that has been properly initialized. "))
72
73 // Check if there is a previous capability for this token
74 if (self.receiverCapabilities[vaultRef.getType()] == nil) {
75 // use the vault reference type as key for storing the
76 // capability and then
77 self.receiverCapabilities[vaultRef.getType()] = capability
78 // emit the event that indicates that a new capability has been
79 // added
80 emit VaultCapabilityAdded(type: vaultRef.getType(),
81 switchboardOwner: self.owner?.address,
82 capabilityOwner: capability.address)
83 } else {
84 // If there was already a capability for that token, panic
85 panic("FungibleTokenSwitchboard.Switchboard.addNewVault: Cannot add new Vault capability! "
86 .concat("There is already a vault in the Switchboard for this type <")
87 .concat(vaultRef.getType().identifier).concat(">."))
88 }
89 }
90
91 /// Adds a number of new fungible token receiver capabilities by using
92 /// the paths where they are stored.
93 ///
94 /// @param paths: The paths where the public capabilities are stored.
95 /// @param address: The address of the owner of the capabilities.
96 ///
97 access(Owner) fun addNewVaultsByPath(paths: [PublicPath], address: Address) {
98 // Get the account where the public capabilities are stored
99 let owner = getAccount(address)
100 // For each path, get the saved capability and store it
101 // into the switchboard's receiver capabilities dictionary
102 for path in paths {
103 let capability = owner.capabilities.get<&{FungibleToken.Receiver}>(path)
104 // Borrow a reference to the vault pointed to by the capability
105 // we want to store inside the switchboard
106 // If the vault was borrowed successfully...
107 if let vaultRef = capability.borrow() {
108 // ...and if there is no previous capability added for that token
109 if (self.receiverCapabilities[vaultRef!.getType()] == nil) {
110 // Use the vault reference type as key for storing the
111 // capability
112 self.receiverCapabilities[vaultRef!.getType()] = capability
113 // and emit the event that indicates that a new
114 // capability has been added
115 emit VaultCapabilityAdded(type: vaultRef.getType(),
116 switchboardOwner: self.owner?.address,
117 capabilityOwner: address,
118 )
119 }
120 }
121 }
122 }
123
124 /// Adds a new fungible token receiver capability to the switchboard
125 /// resource specifying which `Type` of `@{FungibleToken.Vault}` can be
126 /// deposited to it. Use it to include in your switchboard "wrapper"
127 /// receivers such as a `@TokenForwarding.Forwarder`. It can also be
128 /// used to overwrite the type attached to a certain capability without
129 /// having to remove that capability first.
130 ///
131 /// @param capability: The capability to expose a certain fungible
132 /// token vault deposit function through `{FungibleToken.Receiver}` that
133 /// will be added to the switchboard.
134 ///
135 /// @param type: The type of fungible token that can be deposited to that
136 /// capability, rather than the `Type` from the reference borrowed from
137 /// said capability
138 ///
139 access(Owner) fun addNewVaultWrapper(capability: Capability<&{FungibleToken.Receiver}>,
140 type: Type) {
141 // Check if the capability is working
142 assert (
143 capability.check(),
144 message:
145 "FungibleTokenSwitchboard.Switchboard.addNewVaultWrapper: Cannot borrow reference to a vault from the provided capability! "
146 .concat("Make sure that the capability path points to a Vault that has been properly initialized.")
147 )
148 // Use the type parameter as key for the capability
149 self.receiverCapabilities[type] = capability
150 // emit the event that indicates that a new capability has been
151 // added
152 emit VaultCapabilityAdded(
153 type: type,
154 switchboardOwner: self.owner?.address,
155 capabilityOwner: capability.address,
156 )
157 }
158
159 /// Adds zero or more new fungible token receiver capabilities to the
160 /// switchboard resource specifying which `Type`s of `@{FungibleToken.Vault}`s
161 /// can be deposited to it. Use it to include in your switchboard "wrapper"
162 /// receivers such as a `@TokenForwarding.Forwarder`. It can also be
163 /// used to overwrite the types attached to certain capabilities without
164 /// having to remove those capabilities first.
165 ///
166 /// @param paths: The paths where the public capabilities are stored.
167 /// @param types: The types of the fungible token to be deposited on each path.
168 /// @param address: The address of the owner of the capabilities.
169 ///
170 access(Owner) fun addNewVaultWrappersByPath(paths: [PublicPath], types: [Type],
171 address: Address) {
172 // Get the account where the public capabilities are stored
173 let owner = getAccount(address)
174 // For each path, get the saved capability and store it
175 // into the switchboard's receiver capabilities dictionary
176 for i, path in paths {
177 let capability = owner.capabilities.get<&{FungibleToken.Receiver}>(path)
178 // Borrow a reference to the vault pointed to by the capability
179 // we want to store inside the switchboard
180 // If the vault was borrowed successfully...
181 if let vaultRef = capability.borrow() {
182 // Use the vault reference type as key for storing the capability
183 self.receiverCapabilities[types[i]] = capability
184 // and emit the event that indicates that a new capability has been added
185 emit VaultCapabilityAdded(
186 type: types[i],
187 switchboardOwner: self.owner?.address,
188 capabilityOwner: address,
189 )
190 }
191 }
192 }
193
194 /// Removes a fungible token receiver capability from the switchboard
195 /// resource.
196 ///
197 /// @param capability: The capability to a fungible token vault to be
198 /// removed from the switchboard.
199 ///
200 access(Owner) fun removeVault(capability: Capability<&{FungibleToken.Receiver}>) {
201 // Borrow a reference to the vault pointed to by the capability we
202 // want to remove from the switchboard
203 let vaultRef = capability.borrow()
204 ?? panic ("FungibleTokenSwitchboard.Switchboard.addNewVaultWrapper: Cannot borrow reference to a vault from the provided capability! "
205 .concat("Make sure that the capability path points to a Vault that has been properly initialized."))
206
207 // Use the vault reference to find the capability to remove
208 self.receiverCapabilities.remove(key: vaultRef.getType())
209 // Emit the event that indicates that a new capability has been
210 // removed
211 emit VaultCapabilityRemoved(
212 type: vaultRef.getType(),
213 switchboardOwner: self.owner?.address,
214 capabilityOwner: capability.address,
215 )
216 }
217
218 /// Takes a fungible token vault and routes it to the proper fungible
219 /// token receiver capability for depositing it.
220 ///
221 /// @param from: The deposited fungible token vault resource.
222 ///
223 access(all) fun deposit(from: @{FungibleToken.Vault}) {
224 // Get the capability from the ones stored at the switchboard
225 let depositedVaultCapability = self.receiverCapabilities[from.getType()]
226 ?? panic ("FungibleTokenSwitchboard.Switchboard.deposit: Cannot deposit Vault! "
227 .concat("The deposited vault of type <").concat(from.getType().identifier)
228 .concat("> is not available on this Fungible Token switchboard. ")
229 .concat("The recipient needs to initialize their account and switchboard to hold and receive the deposited vault type."))
230
231 // Borrow the reference to the desired vault
232 let vaultRef = depositedVaultCapability.borrow()
233 ?? panic ("FungibleTokenSwitchboard.Switchboard.deposit: Cannot borrow reference to a vault "
234 .concat("from the type of the deposited Vault <").concat(from.getType().identifier)
235 .concat(">. Make sure that the capability path points to a Vault that has been properly initialized."))
236
237 vaultRef.deposit(from: <-from)
238 }
239
240 /// Takes a fungible token vault and tries to route it to the proper
241 /// fungible token receiver capability for depositing the funds,
242 /// avoiding panicking if the vault is not available.
243 ///
244 /// @param vaultType: The type of the ft vault that wants to be
245 /// deposited.
246 ///
247 /// @return The deposited fungible token vault resource, without the
248 /// funds if the deposit was successful, or still containing the funds
249 /// if the reference to the needed vault was not found.
250 ///
251 access(all) fun safeDeposit(from: @{FungibleToken.Vault}): @{FungibleToken.Vault}? {
252 // Try to get the proper vault capability from the switchboard
253 // If the desired vault is present on the switchboard...
254 if let depositedVaultCapability = self.receiverCapabilities[from.getType()] {
255 // We try to borrow a reference to the vault from the capability
256 // If we can borrow a reference to the vault...
257 if let vaultRef = depositedVaultCapability.borrow() {
258 // We deposit the funds on said vault
259 vaultRef.deposit(from: <-from.withdraw(amount: from.balance))
260 }
261 }
262 // if deposit failed for some reason
263 if from.balance > 0.0 {
264 emit NotCompletedDeposit(
265 type: from.getType(),
266 amount: from.balance,
267 switchboardOwner: self.owner?.address,
268 )
269 return <-from
270 }
271 destroy from
272 return nil
273 }
274
275 /// Checks that the capability tied to a type is valid
276 ///
277 /// @param vaultType: The type of the ft vault whose capability needs to be checked
278 ///
279 /// @return a boolean marking the capability for a type as valid or not
280 access(all) view fun checkReceiverByType(type: Type): Bool {
281 if self.receiverCapabilities[type] == nil {
282 return false
283 }
284
285 return self.receiverCapabilities[type]!.check()
286 }
287
288 /// Gets the receiver assigned to a provided vault type.
289 /// This is necessary because without it, it is not possible to look under the hood and see if a capability
290 /// is of an expected type or not. This helps guard against infinitely chained TokenForwarding or other invalid
291 /// malicious kinds of updates that could prevent listings from being made that are valid on storefronts.
292 ///
293 /// @param vaultType: The type of the ft vault whose capability needs to be checked
294 ///
295 /// @return an optional receiver capability for consumers of the switchboard to check/validate on their own
296 access(all) view fun safeBorrowByType(type: Type): &{FungibleToken.Receiver}? {
297 if !self.checkReceiverByType(type: type) {
298 return nil
299 }
300
301 return self.receiverCapabilities[type]!.borrow()
302 }
303
304 /// A getter function to know which tokens a certain switchboard
305 /// resource is prepared to receive along with the address where
306 /// those tokens will be deposited.
307 ///
308 /// @return A dictionary mapping the `{FungibleToken.Receiver}`
309 /// type to the receiver owner's address
310 ///
311 access(all) view fun getVaultTypesWithAddress(): {Type: Address} {
312 let effectiveTypesWithAddress: {Type: Address} = {}
313 // Check if each capability is live
314 for vaultType in self.receiverCapabilities.keys {
315 if self.receiverCapabilities[vaultType]!.check() {
316 // and attach it to the owner's address
317 effectiveTypesWithAddress[vaultType] = self.receiverCapabilities[vaultType]!.address
318 }
319 }
320 return effectiveTypesWithAddress
321 }
322
323 /// A getter function that returns the token types supported by this resource,
324 /// which can be deposited using the 'deposit' function.
325 ///
326 /// @return Dictionary of FT types that can be deposited.
327 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
328 let supportedVaults: {Type: Bool} = {}
329 for receiverType in self.receiverCapabilities.keys {
330 if self.receiverCapabilities[receiverType]!.check() {
331 if receiverType.isSubtype(of: Type<@{FungibleToken.Vault}>()) {
332 supportedVaults[receiverType] = true
333 }
334 if receiverType.isSubtype(of: Type<@{FungibleToken.Receiver}>()) {
335 let receiverRef = self.receiverCapabilities[receiverType]!.borrow()!
336 let subReceiverSupportedTypes = receiverRef.getSupportedVaultTypes()
337 for subReceiverType in subReceiverSupportedTypes.keys {
338 if subReceiverType.isSubtype(of: Type<@{FungibleToken.Vault}>()) {
339 supportedVaults[subReceiverType] = true
340 }
341 }
342 }
343 }
344 }
345 return supportedVaults
346 }
347
348 /// Returns whether or not the given type is accepted by the Receiver
349 /// A vault that can accept any type should just return true by default
350 access(all) view fun isSupportedVaultType(type: Type): Bool {
351 let supportedVaults = self.getSupportedVaultTypes()
352 if let supported = supportedVaults[type] {
353 return supported
354 } else { return false }
355 }
356
357 init() {
358 // Initialize the capabilities dictionary
359 self.receiverCapabilities = {}
360 }
361
362 }
363
364 /// Function that allows to create a new blank switchboard. A user must call
365 /// this function and store the returned resource in their storage.
366 ///
367 access(all) fun createSwitchboard(): @Switchboard {
368 return <-create Switchboard()
369 }
370
371 init() {
372 self.StoragePath = /storage/fungibleTokenSwitchboard
373 self.PublicPath = /public/fungibleTokenSwitchboardPublic
374 self.ReceiverPublicPath = /public/GenericFTReceiver
375 }
376}
377