flow-ft
flow-ft copied to clipboard
FEATURE: Fungible Token Switchboard Contract
Issue To Be Solved
Currently, if a user wants to receive a particular fungible token, they must run a specific setup transaction to initialize the specific vault in their account. This is a bit awkward for users who want to pay someone in a different fungible token than the one they have specified. This problem is especially pronounced in the discussion of how royalties should be paid for NFT sales. A user provides a {FungibleToken.Receiver}
capability to receive royalties from, but if the sale is performed with a different token type than the one specified, the marketplace has to either look for the other token type through the receiver's owner.address
field, or just not pay royalties, which is not ideal.
Want a solution that will allow anyone to deposit a generic fungible token into a standard receiver path that will forward the tokens to the correct vault in the account. The solution should be as generic and scalable as possible while not exposing any users to any ddos-esque risk or security risk.
Suggest A Solution
- [ ] Create a design document for the smart contract and write an initial proposal. Post the proposal here.
- [ ] Discuss with the community and Flow team members to align on important features.
- [ ] Within this epic, create and estimate issues for:
- The development and testing of the contract
- The documentation of the contract and transactions
Needs to integrate with the proposed solution for Royalties with the generic receiver
Design proposal Document in Notion
Unanswered Questions
Would the contract have a strict list of fungible tokens that it supports that an admin can update when needed, or would we leave that in the hands of the owner to specify which fungible tokens they want to support?
Would the resource create a new vault for a user when someone tries to send them tokens that their account doesn't already support?
Context
This is preventing royalties from being as useful and powerful as possible
Would the resource create a new vault for a user when someone tries to send them tokens that their account doesn't already support?
This unfortunately cannot be because of storage fees :( With permission less deployment it is a very dangerous DoS opportunity on accounts.
Would the contract have a strict list of fungible tokens that it supports that an admin can update when needed, or would we leave that in the hands of the owner to specify which fungible tokens they want to support?
This is currently the best option, another idea we recently discussed was the sender
to pay for storage fees. And making this contract both for NFT and FT.
another idea we recently discussed was the sender to pay for storage fees. And making this contract both for NFT and FT.
Can you elaborate? Do you mean that if the contract does create and store a new vault if an account doesn't support it, we could have the sender also send some FLOW to pay for it so we can still support that?
@joshuahannan yeah , basically, if someone is sending me something new, there can be a function on switchboard contract where they can pay for the storage. Something like deposit with 2 parameters, NFT and some flow token vault, which will return excess flow tokens vault.
So this can allow some kind of airdrops if sender pays the storage cost.
Blocker I see is only cadence crashing on type from removed contract. ( but probably will be fixed with secure cadence or stable one )
Here is the design proposal for the FT Switchboard, please @bluesign let me know your thoughts on it!
Fungible Token Switchboard Contract Design Document
Fungible Token Switchboard
Owner: @Álvaro Lillo
Secondary Owner: @Joshua Hannan
Repo Location: https://github.com/onflow/flow-ft
Testnet Address:
Mainnet Address:
Objective
If a user wants to receive a particular fungible token, they must run a specific setup transaction to initialize the specific vault in their account. We want to allow anyone to deposit a generic fungible token into a standard receiver path that will forward the tokens to the correct vault in the account.
Motivation
This is a bit awkward for users who want to pay someone in a different fungible token than the one they have specified. This problem is especially pronounced in the discussion of how royalties should be paid for NFT sales.
User Benefit
Users will be able to confidently know that they can transact with multiple token types through the same receiver. The Flow community can begin to standardize fungible token transactions so that projects and users who need to send a new token to an account do not have to keep track of every single path and contract that want to potentially use to deposit. Users can also control what tokens they are comfortable accepting through a standard interface instead of having to use multiple contracts’ boilerplate transactions.
Additionally, this solution directly benefits users who want to receive royalties for NFT purchases. Right now if the beneficiary of a sale cut (as a royalty beneficiary) was going to receive a payment of a different FT token than the one from the capability they set up as receiver the transaction would be aborted by the FT deposit function pre-condition:
// Assert that the concrete type of the deposited vault is the same
// as the vault that is accepting the deposit
pre {
from.isInstance(self.getType()):
"Cannot deposit an incompatible token type"
}
Creating a new resource implementing {FungibleToken.Receiver}
will allow NFT royalties receivers to provide a generic receiver that could receive a variety of FTs instead of just one.
Design Proposal
We will write a new contract “FungibleTokenSwitchboard” that will feature a resource “Switchboard” conforming to {FungibleToken.Receiver}
but instead of storing a vault balance, it will store a record of these capabilities: Capability<&AnyResource{FungibleToken.Receiver}>
.
The deposit function enforced by the {FungibleToken.Receiver}
will then check the type of the @FungibleToken.Vault
received as an argument and route it to the proper Capability<&AnyResource{FungibleToken.Receiver}>
. If there is no capability matching the deposited FT, the transaction will fail.
I believe we should explore the idea exposed by bluesign on the github issue about allowing payers to pay for the creation of a new Vault of a FT no yet used by the seller. Probably this will make a nice topic of discussion with the community, to see if this will be a desired feature. This would be a deposit function with an extra argument for a @FlowToken.Vault
for paying for the transaction fees from creating the new vault, something like:
pub fun depositNewVaultWithStorageFees(from: @FungibleToken.Vault, fees: @FlowToken.Vault)
The owner of the Switchboard resource will be able to call the function addVaultCapability(switch: Capability<&{FungibleToken.Receiver}>
in order to store a new one of these capabilities.
The Switchboard resource also should expose a getVaultCapabilities(): [Capability<&{FungibleToken.Receiver}>]
in order to allow buyers to know if the token they are willing to use for the payment is supported by the seller / payment receiver.
Drawbacks
In a first version of this contract, only allowing payers to deposit certain types of FTs approved by the seller I believe there won’t be any drawbacks, as payment receivers could use this as a regular {FungibleToken.Receiver}
in a transparent way for any other contracts involved in the transaction.
However if we explore the possibility of allowing payers to create new Vaults into sellers accounts storage fees will be needed to take into account, in order to avoid any possible ddos-esque risk for sellers that could see their accounts flooded by unwanted Vaults.
If the contract can also create new FTs in a user’s account, it would not be able to create the usual receivers and capabilities unless it had access to the AuthAccount
, which probably would not be good.
Performance Implications
Could a Switchboard owner set deliberately a too long list of capabilities that will make checking for the proper one an unnecessary waste of computing increasing the cost of the transaction?
Dependencies
The only thing that will directly depend on the switchboard out of the gate is [the setup that we proposed for royalties](https://github.com/onflow/flow-nft#important-royalty-instructions-for-royalty-receivers) but since this is for any fungible token, any transaction or project that sends tokens would need to be made aware of this. it isn’t a breaking change for them, but could be beneficial for them to know about
Engineering Impact
The contract will be maintained and tested by @Álvaro Lillo
Best Practices
Cadence design patterns and anti-patterns should be observed during the coding of this contract, otherwise this will not affect any best practices of developing Flow.
Tutorials and Examples
Working examples on how to use this contract and the Switchboard resource will be included in the onflow/flow-ft repo showing developers how to utilize it instead of a traditional Vault resource.
Compatibility
Thanks to Cadence capabilities/interfaces this contract will be fully interoperable with any other older contract.
User Impact
Marketplaces will be able to expand their payment options for NFTs #onFlow, so probably main concern here is keeping them informed about the new feature.
Questions and Discussion Topics
As exposed before, the possibility of allowing payers to deposit absolutely any FT paying for the vault creation should be discussed with the community to see if that’s a desirable feature.
@alilloig nice proposal. Unfortunately after thinking on sender pays storage part, I concluded that there is currently no way to enforce that with cadence. I think we can remove that part.
Capability solution is looking very nice, can't wait to see the contract.
@bluesign Why don't you think you can enforce that with Cadence? You could just use a function like this:
pub fun depositNewVaultWithStorageFees(from: @FungibleToken.Vault, fees: @FlowToken.Vault)
The resource could store it temporarily, then if the owner wants to store it in their actual account, they could run a separate transaction to initialize the storage path properly. This deposit function might also need an import address and contract name argument so the owner knows which contract to work with, but it still seems possible to me.
@joshuahannan It is possible maybe but I saw problems when trying some attack scenarios:
- How to calculate the fee ? We can do naive storageUsed before and storageUsed after calculation, but it is totally avoidable. Then I can fill up your storage to the last byte. ( which is really bad experience )
Imagine you can stop someone from getting royalties etc.
- How to get rid of unwanted Vault?
What if the Contract owner puts a
panic
on destroy ? Though this is a generic problem I guess, not sure if it is still possible after secure cadence.
I mean on happy path it is working good, but there are few risks like above.
How to calculate the fee ? We can do naive storageUsed before and storageUsed after calculation, but it is totally avoidable. Then I can fill up your storage to the last byte. ( which is really bad experience )
I think we can pretty easily calculate the size of a typical capability and vault and just charge an amount of FLOW that is equivalent to that, even if it doesn't cause the storage to overflow.
How to get rid of unwanted Vault?
Just put a method on the switchboard resource that destroys an unwanted vault. I think we'll just have to accept the risk that there might be a panic on destroy
I agree, on happy path it works, though when we open the gates, generic receiver is pretty risky.
I can even deploy contract, create some Fungible, send to your switchboard, then delete contract. Then your switchboard probably dies.
I think maybe after this stuff fixed can be, but I see now it is very dangerous.
I can even deploy contract, create some Fungible, send to your switchboard, then delete contract. Then your switchboard probably dies.
Would that break the entire switchboard? Seems like that would only break the one vault that belongs to the contract that was removed. I've never tried something like that before though, so I don't know for sure.
I think depends on how we store, if we store in resource field ( as array or dictionary ) , breaks all switchboard I guess. If we store on storage separate and load on demand, I think only that vault.