onflow / freshmint

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use account address instead of specific keys for airdrop support

aishairzay opened this issue · comments

I have a few opinions of how I think the airdrops contract should work, some notes on how I would change it below:

  • https://github.com/packagelabs/freshmint/blob/alpha/cadence/nft-air-drop/NFTAirDrop.cdc#L29 - accept an account address instead of a public key. I believe this is more intuitive of a system that an account can claim.
    • I could imagine that maybe this was using a key instead of address to let a not yet existing account to be able to claim later, but that would assume that the user's private key is somehow known ahead of creating the account, which I don't think is feasible. Am I missing some other use-case for using a key versus an account?
  • Assuming we use an account instead of a public key for claiming, we can then verify account weights >= 1000 on the signature(s) provided, this will make it so a DAO account can properly do a multisign to claim, rather than allowing a single owner of the corresponding private key to claim.
  • https://github.com/packagelabs/freshmint/blob/alpha/cadence/nft-air-drop/NFTAirDrop.cdc#L40 - Again assuming we switch to an account based system, I believe this receiver being passed in should be unneeded if an account is passed in. Instead accept a public path for where an NFT receiver should be found in the account to deposit the item i.e. getAccount(<signing_account_address>).getCapability<NonFungibleToken.CollectionPublic>(collectionPublicPath).deposit()
    • This is better because the use of the address from the capability owner is not a reliable check of account ownership IMO as it is used here: https://github.com/packagelabs/freshmint/blob/alpha/cadence/nft-air-drop/NFTAirDrop.cdc#L51 . A contract may be calling this directly and not actually be the owner of the receiver that is passed in, and using a getAccount approach will ensure we are depositing to a receiver intended by the receiver, which could in turn end up depositing elsewhere depending on what capability is linked on the claiming account.

Thank you for this feedback!

accept an account address instead of a public key

I should have documented this contract better before sharing, sorry about that. Here's the intended usage:

  1. The minter generates a unique public / private key pair for each minted NFT. These keys aren't tied to any account -- they're ephemeral single-use "claim keys".
  2. The public key is stored on chain, the private key is given to the intended recipient. The key can be encoded in a secret claim URL that is sent via email, QR code, etc.
  3. The user is then able to claim the NFT with or without an existing Flow account. If they don't have a Flow wallet, FCL will create one for them and submit the transaction in the same UX.

I'm very open to feedback on this style of airdrop. I'm not even sure it's correct to call it an airdrop. The intended use case isn't necessarily high-value NFTs, but rather larger airdrops to users who aren't yet onboarded to Flow.

There are other ways to accomplish that use case, but I wanted to find a solution that was entirely on-chain. The main weakness is that the private key can't be revoked or invalidated if it's lost or stolen.

Instead accept a public path for where an NFT receiver should be found in the account to deposit the item

This is a really good point and something I wanted feedback on. I think this change makes sense even with the claim key approach described above.

The intended usage makes sense - I think this style of airdrop can also lead to typable claim codes rather than link specific ones, where you use the code along with some salt specific to this claim to generate a key-pair. Maybe claim code mint would be a better name with that in mind.

The main weakness is that the private key can't be revoked or invalidated if it's lost or stolen.

Just spitballing some ideas on this, if the key for access(self) let claims: {UInt64: [UInt8]} was instead the public key, then we might be able to revoke easily. And the user can easily provide the public key along with their signed data. I know a key as a [UInt8] might not work well, but representing the public key as a string could work.

I think this change makes sense even with the claim key approach described above.

Along with this, I believe we can verify the message is using the valid private key, and then use the given account address within the signature to do a getAccount style airdrop of the NFT to them. This would replace the need to ingest the address from here: https://github.com/packagelabs/freshmint/blob/alpha/cadence/nft-air-drop/NFTAirDrop.cdc#L51 and also will still work in an idempotent way, where even if the transaction fails and someone else submits the same signature, it would deposit to the proper account.

For this line: https://github.com/packagelabs/freshmint/blob/alpha/cadence/nft-air-drop/NFTAirDrop.cdc#L64
Assuming it stops using tokenID as the key for claims as I mentioned above, It might be nice to abstract the withdraw into a passed in function or an interface to support different ways of retrieving this NFT. This can be withdrawing a specific ID from a collection, or could be minting a new ID when the claim happens (which would let you give out more claim codes than NFTs if needed, and is less error-prone in accidentally sending the same ID multiple times).