Smart Contract

FungibleTokenSwitchboard

A.f233dcee88fe0abe.FungibleTokenSwitchboard

Deployed

3d ago
Feb 25, 2026, 01:40:38 PM UTC

Dependents

7 imports
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