Temporary Capabilities

(I’m posting here before creating a FLIP to get some feedback on the idea!)

Summary

While progress has been made in standardizing NFT and fungible token vault resource interfaces, there is no standard capability or set of capabilities that is always exposed by each project’s setup script. This results in a transaction pattern where some temporary capability link must be created for each collection and then unlinked at the end of the transaction. I think it would be good to create some syntactical sugar to combine these two actions into a temporaryLink function that would allow creation of a capability that is automatically unlinked at the end of the transaction or perhaps after being used once.

Details on the problem

You can see an example of this link/unlink pattern in this transaction, which attempts to purchase multiple NFTs from different listings in one transaction. Because some NFT collections automatically link NonFungibleToken.CollectionPublic, some link NonFungibleToken.Receiver, and some link both or instead only link some custom capability, the transaction creates a temporary NonFungibleToken.Receiver link for each collection involved, paths are stored, and then all are unlinked at the end of the transaction.

This same transaction pattern could also be applied to any transaction involving multiple fungible tokens, as the lack of standardized public and private linked capabilities also applies to those contracts. For example, if you wanted to create a contract that executes one or more purchases / sales based on some condition (such as a stop-loss order), you would have to make sure the end users have all the proper provider / receiver capabilities, potentially involving multiple one-off setup transactions.

Finally, the temporaryLink functionality would also help to keep unnecessary, often duplicated, capabilities from filling up a user’s account storage and increasing the size of the entire blockchain state. If every smart contract engineer was no longer responsible for cleaning up the capabilities they create after use, the only capabilities that would be persistently stored on-chain would be those intentionally stored for future use.

Questions about the solution

My main question, aside from whether this is a good idea in the first place, is whether the temporary capability should be automatically destroyed at the end of the transaction or after a single use. I realize that having temporary capabilities exist until they are used once would likely require more updates to Flow/Cadence than just automatically destroying temporary capabilities at the end of the transaction, but I’m wondering if there are security-related advantages or disadvantages to each approach.

Also, I’m curious about whether the community thinks there should be further restrictions on this functionality. Would these temporary capabilities have to be for PrivatePaths only? Would they even need a CapabilityPath if they aren’t meant to be stored for future use? Are there other key questions I’m missing?

Thank you for reading, and thanks in advance for any feedback!

2 Likes

why not link user’s account correctly ? ( fix the linking permanently I mean, preferably with confirmation from user )

1 Like

Due to the lack of a standard set of exposed capabilities as well as the lack of generic type variables, it’s difficult to link the user’s account correctly in a dynamic way.

If a transaction were to use the linked types specified in NFTCatalog, then almost every improperly linked NFT collection would require an explicit contract import to access the custom interfaces that most collections link in addition to the standard NonFungibleToken interfaces. This becomes particularly difficult when interfacing with Dapper Wallet, as they require every transaction they sign to be pre-approved and static, so this approach could potentially involve submitting one transaction per NFT collection to Dapper for approval, which would quickly become hard to manage. Plus, this approach requires the end user to sign a bunch of setup transactions, which creates unnecessary friction.

The dynamic solution would be to simply “fix” any improperly linked collection with only the standard NonFungibleToken interfaces, but that would inevitably result in problems with future transactions written by the original collection that would likely require their custom interfaces.

The easy solution that I fear many developers will be forced to go with is to just link their own custom set of capabilities for each collection they interact with, so they can do so in a predictable and dynamic way:

let providerCapabilities = []
for contractInfo in collectionsToSell {
    let customProviderPath = PrivatePath(identifier: "myCustomCapability".concat(contractInfo.name))!
    var customProviderCap = signer.getCapability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>(customProviderPath)
    if (!customProviderCap.check()) {
        signer.unlink(customProviderPath)
        customProviderCap = signer.link<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>(customProviderPath, target: contractInfo.storagePath)!
    }
    providerCapabilities.push(customProviderCap)
}

This solution is nice in that it allows a single transaction to ensure the capabilities exist or are created for basically any collection, but I worry that it will result in user account storage getting filled up with unnecessary bloat, which could become a problem over time with massive user adoption.

We use this last method at evaluate.xyz, and while we unlink the temporary capabilities at the end of the transaction if possible (i.e. if making a purchase), certain delayed actions make the unlinking step unfeasible, like authorizing a future NFT swap to happen under certain conditions. (I should clarify that we only use custom capabilities that we don’t immediately unlink for improperly linked collections.)

Ah yeah I agree, this is a big problem. ( I think this one should be the one we should fix )

This is the second part of the problem. Temporarily capabilities only solve half of the problem.

I think new Capability Controllers FLIP ( https://github.com/onflow/flow/pull/798#issue-1123369661) will cover some of it. ( first part ) now private capabilities will not be linked to a path but issued. Something like this:

let countCap = issuer.capabilities.issue<&{HasCount}>(/storage/counter)

but it still has the problem of little bloat. But I think it will be good to think solution over the new Capability Controllers system.

2 Likes

I agree that I think capability controllers solve part of this, but I feel like we need to be better at encouraging projects to link the proper interfaces for their users instead of custom ones. We don’t require custom links anymore since metadata is included in NonFungibleToken.INFT and we have a safe borrow method also, unless there is some custom functionality that a lot of projects use that I am missing.

as part of the capability controller discussion, have we discussed having a expiry date for capabilities? that could help this proposal

1 Like