Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEP-452 Linkdrop Standard #452

Merged
merged 29 commits into from
Jan 27, 2024
Merged
Changes from 12 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
de47911
initial impl
BenKurrek Jan 24, 2023
de454c1
renamed nep to PR number
BenKurrek Jan 24, 2023
c0d764b
Apply suggestions from code review
BenKurrek Jan 25, 2023
f4df068
Update neps/nep-0452.md
BenKurrek Jan 27, 2023
54322db
applied suggestions from code review
BenKurrek Jan 27, 2023
62398fd
marked get key balance as deprecated
BenKurrek Jan 27, 2023
7eac04f
added required gas field to key info
BenKurrek Feb 3, 2023
f2f0486
adding security implications and example scenarios (#459)
kenobijon Feb 3, 2023
8a2b1d7
review comments from frol
BenKurrek Feb 23, 2023
f52586a
swapped deletion of key with transfer of assets
BenKurrek Mar 13, 2023
e1b4b18
Update neps/nep-0452.md
BenKurrek Mar 13, 2023
5877eba
removed reference PR
BenKurrek Mar 13, 2023
11d006c
Apply suggestions from code review
BenKurrek Mar 28, 2023
e5bf766
implemented 471 and renamed rational and alternatives to just alterna…
BenKurrek Mar 28, 2023
8e50122
applied all code review
BenKurrek Mar 28, 2023
382d4a8
made required gas non optional
BenKurrek Mar 29, 2023
3968d96
final review passthrough
BenKurrek Apr 17, 2023
845cd53
ts formatter
BenKurrek Apr 18, 2023
2bd6fcd
Apply suggestions from code review
BenKurrek Apr 19, 2023
1bf0268
code review
BenKurrek Apr 19, 2023
c61f9fb
fixed past vs present tense for example scenarios
BenKurrek Apr 20, 2023
07f4da2
Update neps/nep-0452.md
BenKurrek Apr 20, 2023
ac9a8bd
applied code reviews
BenKurrek Apr 21, 2023
17c0294
rename yoctoNEAR -> yoctonear
BenKurrek Apr 21, 2023
09ee654
Merge branch 'master' into ben/linkdrop-nep
frol Jun 2, 2023
d513e1b
added final review from frol
BenKurrek Jun 14, 2023
75e6e9b
code formatter
BenKurrek Jun 14, 2023
256a24b
Merge branch 'master' into ben/linkdrop-nep
gagdiez Jan 27, 2024
f349084
fix lint
njelich Jan 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 221 additions & 0 deletions neps/nep-0452.md
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
---
NEP: 452
Title: Linkdrop Standard
Author: Ben Kurrek <ben.kurrek@near.org>, Ken Miyachi <ken.miyachi@near.foundation>
DiscussionsTo: https://gov.near.org/t/official-linkdrop-standard/32463/1
Status: Draft
Type: Standards Track
Category: Contract
Version: 1.0.0
Created: 24-Jan-2023
Updated: 24-Jan-2023
---

## Summary

A standard interface for linkdrops that support $NEAR, fungible tokens, non-fungible tokens, and is extensible for linkdrops of any type.

## Motivation

Linkdrops are an extremely powerful tool that enable seamless onboarding and instant crypto experiences with the click of a link. The original [near-linkdrop](https://github.com/near/near-linkdrop) contract provides a minimal interface allowing users to embed $NEAR within an access key and create a simple Web2 style link that can then be used as a means of onboarding. This simple $NEAR linkdrop is not enough as many artists, developers, event coordinators, and applications want to drop more digital assets such as NFTs, FTs, tickets etc.

As linkdrop implementations start to push the boundaries of what’s possible, new data structures, methods, and interfaces are being developed. There needs to be a standard data model and interface put into place to ensure assets can be claimed independent of the contract they came from. If not, integrating any application with linkdrops will require customized solutions, which would become cumbersome for the developer and deteriorate the user onboarding experience. The linkdrop standard addresses these issues by providing a simple and extensible standard data model and interface.

The initial discussion can be found [here](https://gov.near.org/t/official-linkdrop-standard/32463/1).

## Rationale and Alternatives
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

* Why is this design the best in the space of possible designs?
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

This design allows for flexibility and extensibility of the standard while providing a set of criteria that cover the majority of current linkdrop use cases. The design was heavily inspired by current, functional NEPs such as the Fungible Token and Non-Fungible Token standards.

* What other designs have been considered and what is the rationale for not choosing them?
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

A generic data struct that all drop types needed to inherit from. This struct contained a name and some metadata in the form of stringified JSON. This made it easily extensible for any new types down the road. The rationale for not choosing this design was both simplicity and flexibility. Having one data struct required keys to be of one type only when in reality, they can be many at once. In addition, having a generic, open-ended metadata field could lead to many interpretations and different designs. We chose to use a KeyInfo struct that can be easily extensible and can cover all use-cases by having optional vectors of different data types. The proposed standard is simple, supports drops with multiple assets, and is backwards compatible with all previous linkdrops, and can be extended very easily.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

A standard linkdrop creation interface. A standardized linkdrop creation interface would provide data models and functions to ensure linkdrops were created and stored in a specific format. The rationale for not choosing this design was that is was too restrictive. Standardizing linkdrop creation adds complexity and reduces flexibility by restricting linkdrop creators in the process in which linkdrops are created, and potentially limiting linkdrop functionality. The functionality of the linkdrop creation, such as refunding of assets, access keys, and batch creation, should be chosen by the linkdrop creator and live within the linkdrop creator platform. Further, linkdrop creation is often not displayed to end users and there is not an inherent value proposition for a standardized linkdrop creation interface from a client perspective.

* What is the impact of not doing this?
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

The impact of not doing this is creating a fragmented ecosystem of linkdrops, increasing the friction for user onboarding. Linkdrop claim pages (e.g. wallet providers) would have to implement custom integrations for every linkdrop provider platform. Inherently this would lead to a bad user experience when new users are onboarding and interacting with linkdrops in general.

## Specification
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

```ts
/// Information about a specific public key.
type KeyInfo = {
/// How much Gas should be attached when the key is used to call `claim` or `create_account_and_claim`.
/// If not provided, a default value of 100 TGas will be attached to the transaction.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
required_gas: string | null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you see this gas being split between the calls? There are two arrays that require cross-contract calls (NFTData[] and FTData[]), and there is also some gas to be preallocated for the account creation callback and assets transfer callback functions, so there is a lot to unpack here.

P.S. It feels that it is quite trivial to end up with a linkdrop that won't have enough gas to transfer all the assets in a single transaction. Thus, one area for future possibilities would be a multi-stage withdrawal.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not provided, will the gas for each leg of the transaction be deducted from the deposit? I would clarify this behavior in the comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly believe that the gas distribution and the inner workings of the logic should not be addressed in this standard. In my eyes, this standard allows anyone to claim a linkdrop regardless of where it originated from (keypom, near / testnet, neardrop etc...). If you have a linkdrop that follows this standard, you will know how to claim it and what to expect.

How gas is distributed should be left to the smart contract developer in my opinion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How gas is distributed should be left to the smart contract developer in my opinion.

@BenKurrek Well, this is exactly who I care about, the developers of the smart contract who would be in a tough situation where they only have the total gas allocation, and they need to somehow figure out how to spend it wisely. While alternatively, we might have required_gas specified on the FTData / NFTData level. How does Keypom handle it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly believe that the gas distribution and the inner workings of the logic should not be addressed in this standard.

After a second thought, I agree with this statement. Consider adding a hint to the readers that it is expected that it is the responsibility of a linkdrop contract to define how exactly to calculate the required gas (either automatically on the contract side with linkdrop creation, or even move this responsibility to the client code of linkdrop creation)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that we should not standarize the gas logic here. However we should document the issue here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, remove the field at all, is it a so useful field? The user won't be dissatisfied with attaching the 100Tgas by default, or 300TGas in a case of failure.

For instance, what happens if ft_transfer_call fails? Nothing bad, the front-end developer specifies more Gas. I also foresee the gas amount is going to be hard-coded (predictable).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference is to standardize how much gas to attach but leave it to the developers to choose how they distribute that gas throughout the transaction. I can leave a note explaining that it's the developer's responsibility but not force them through a certain flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a note under the field. Let me know if this suffices.

BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// yoctoNEAR$ amount that will be sent to the claiming account (either new or existing)
/// when the key is successfully used.
balance: string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered renaming it to near_amount or even introducing NEARData type to match FTData type where amount is used?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

near_amount would make sense here and possibly minimize potential confusion with FT drops

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good suggestion although the amount will be in yocto... do you think this would be obvious enough?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, we can go with yoctonear_amount then

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yNEAR or yoctoNEAR, y_near

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like yoctoNEAR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenKurrek I just noticed that you've changed it to yoctoNEAR - it's against Rust naming convention, it's better to go with yoctonear or yoctonear_amount.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do yoctonear


/// If using the NFT standard extension, a set of NFTData can be linked to the public key
/// indicating that all those assets will be sent to the claiming account (either new or
/// existing) when the key is successfully used.
nft_data: NFTData[] | null,
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

/// If using the FT standard extension, a set of FTData can be linked to the public key
/// indicating that all those assets will be sent to the claiming account (either new or
/// existing) when the key is successfully used.
ft_data: FTData[] | null,

/// ... other types can be introduced and the standard is easily extendable.
}


/// Data outlining a specific Non-Fungible Token that should be sent to the claiming account
/// (either new or existing) when a key is successfully used.
type NFTData = {
/// the id of the token to transfer
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
token_id: string,
/// The valid NEAR account indicating the Non-Fungible Token contract.
contract_id: string
}


/// Data outlining Fungible Tokens that should be sent to the claiming account
/// (either new or existing) when a key is successfully used.
type FTData = {
/// The number of tokens to transfer, wrapped in quotes and treated
/// like a string, although the number will be stored as an unsigned integer
/// with 128 bits.
Comment on lines +102 to +104
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The number of tokens to transfer, wrapped in quotes and treated
/// like a string, although the number will be stored as an unsigned integer
/// with 128 bits.
/// The number of tokens to transfer as a stringified integer number. Max value: 2^128-1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this directly from the FT standard - do you still wanna change it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the suggestion is more meaningful.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robert-zaremba I feel both explanations are equally cumbersome 🤷‍♂️ , so I would leave the original one for now since it is an extract from an existing NEP, and think about how we can improve this situation with stringified numbers universally for all the NEPs going forward.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, the original is text is not clear. Should be changed. Replicating bad documentation should not be an excuse.

amount: string,
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// The valid NEAR account indicating the Fungible Token contract.
contract_id: string
}



BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

/****************/
/* VIEW METHODS */
/****************/

/// Deprecated - use `get_key_information` instead. This method provides backwards compatibility with previous implementations.
/// Returns the $yoctoNEAR amount associated with a given public key
/// Panics if the key does not exist.
/// The Public key must be in a binary format with base58 string serialization with human-readable curve. The key types currently supported are secp256k1 and ed25519. Ed25519 public keys accepted are 32 bytes and secp256k1 keys are the uncompressed 64 format.
function get_key_balance(key: string) -> string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we include this in a new standard if it is deprecated? let's remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I included it as to be backwards compatible with legacy contracts. If we change it, we're forcing every contract to change if they want to be compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frol do you have opinions here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's better explain it:

  1. either we move it to "recommended methods" -- this way we won't require every contract to implement it, and inform the community to rather not relay on it and not use it.
  2. or we better explain that this is required. in that case we should not call it "deprecated", because it is a valid method and provide correct response.

Personally I prefer 2.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenKurrek I think we should drop the deprecated method from the spec, and migrate wallets to the new method (we should update get_key_information in the near-linkdrop to return yoctoNEAR and deploy it)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seams the spec was not updated, but the comment was resolved. Reopening it.

BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

/// Returns the KeyInfo associated with a given public key
/// Panics if the key does not exist
/// The Public key must be in a binary format with base58 string serialization with human-readable curve. The key types currently supported are secp256k1 and ed25519. Ed25519 public keys accepted are 32 bytes and secp256k1 keys are the uncompressed 64 format.
function get_key_information(key: string) -> KeyInfo
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

/******************/
/* CHANGE METHODS */
/******************/

/// Transfer all assets linked to the signer’s public key to an *existing* NEAR account.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// If the transfer fails for whatever reason, it is up to the smart contract developer to
/// choose what should happen. For example, the contract can choose to keep the assets
/// or send them back to the original linkdrop creator.
///
/// Requirements:
/// * The predecessor account *MUST* be the current contract ID.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// * The assets being sent *MUST* be associated with the signer’s public key.
/// * The assets *MUST* be sent to the `account_id` passed in.
///
/// Arguments:
/// * `account_id` the account that should receive the linkdrop assets.
///
/// Returns `true` if the claim was successful meaing all assets were sent to the `account_id`.
function claim(account_id: string) -> Promise<boolean>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not match the near-linkdrop contract implementation, which returns an empty string result on claim since it defers the transaction result to the instantiated TRANSFER receipt. Given that there is not so much value in the boolean (why would you return false instead of failing the function call?), I think there is no reason to break the compatibility.

P.S. On the other hand, create_account_and_claim returns a boolean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a tricky one... On the Keypom protocol, we do manual bookkeeping of an access key's allowance since this information isn't exposed in the trie. This is so that we can refund the funder for any unspent allowance. This bookkeeping won't work if the transaction fails since the state changes will be reverted.

I'm unsure as to how to proceed..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, indeed, it is a tough compatibility issue now. I have a solution, but I cannot say I love it.

You can still return an error without crashing the current receipt - you crash a cross-contract receipt. I.e. claim function would update the storage, and initiate a cross-contract call that will crash. It is ugly and requires more gas for the cross-contract call in case of an error, but you should have enough gas to do that as you don't call the transfers and don't call the callback handler.

Given there is a misalignment on the near-linkdrop contract between the claim and create_account_and_claim calls, I would consider updating the claim implementation by adding the callback that will return boolean (you will want to reach out to wallets to check if this change affects them)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattlockyer It seems that you have a strong opinion on maintaining 100% compatibility with the near-linkdrop contract. What do you think about this breaking change?

@BenKurrek Given that panic is always possible (e.g. not enough gas), it is fair to expect that clients must also implement handlers for those (in addition to the boolean value). The problem is that the current clients only implemented the handlers for panics, and not for booleans, but once the standard is published, we can share it with the implementors to update their code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the existing contract upgradeable? Do we have a list of products which adopted it? What is the risk of breaking the compatibility now? If it's only a handful and we can easily update them, then breaking the compatibility for having a long term benefits is OK.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keypom wants to save something in its state while still signaling that the claim was not successful

What they want to save? Would help to understand the issue

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robert-zaremba There was a message from Ben above: #452 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenKurrek We had a discussion with @frol and @robert-zaremba and we arrived at the following conclusion:

  1. Promise<KeyInfo> would be a good choice as a return type for both claim and create_account_and_claim methods.
  2. Errors must be signalized with panic, e.g. in the final callback handler after all cross-contract FT transfers.
  3. Symmetry: both methods must have the same return type.
  4. Giving up near-linkdrop backward compatibility as it leads to standardizing an inconsistent API.

That said, it's undecided whether the KeyInfo would contain a value of one of the following options:

  1. a final state of the assets at the end of the claim ("0" balances on each successfully transferred asset in the KeyInfo) that would be the same as returning the get_key_information() value.
  2. or successfully transferred balances (non-zero balances in the KeyInfo which is the opposite case).

Both options have cons and pros. From the client side, it is important to display to the users what they got instead of what they did not get with a free linkdrop. Yet, we can see the argument for returning the "remaining" assets: it's easier to implement, also, combined with the result of a prior call to get_key_information, a client can figure out which assets were transferred.

We would love to hear your perspective on that.


Finally, let me clarify our reasoning for not considering bool and void as a return type:

  1. bool is not extendable, and it's hard to reason the meaning of "true": did it succeed to claim, or did it allow to claim, what's the actual status of transfers, etc.?
  2. void doesn't get benefits compared to KeyInfo, the latter reveals the status of the claim, also potentially saving additional RPC calls.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I still think we should consider advocating a Result<_,_> type (of the last receipt) which will not trigger panic, but may signal transaction failure.
Panics / reverts are kind of a standard in the Ethereum synchronous execution model, but I think we should do more research about pros/cons for such approach.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After further discussion, I propose to keep things simple and keep boolean as the return value of claim and create_account_and_claim functions. Here is my reasoning:

  • This NEP is a reflection of the existing ecosystem and breaking it is not in our interest
  • Errors in claim method are rare as usually dApps check if the claim is still available before submitting the transaction
  • Having boolean return value for claim aligns it with the create_account_and_claim, which uses boolean as its return value since day one

This was the only remaining concern blocking this NEP, and @fadeevab @abacabadabacaba agreed that we don't need to block it any longer.




BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// Creates a new NEAR account and transfers all assets linked to the signer’s public key to
/// the *newly created account*. If the transfer fails for whatever reason, it is up to the
/// smart contract developer to choose what should happen. For example, the contract can
/// choose to keep the assets or return them to the original linkdrop creator.
///
/// Requirements
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// * The predecessor account *MUST* be the current contract ID.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// * The assets being sent *MUST* be associated with the signer’s public key.
/// * The assets *MUST* be sent to the `new_account_id` passed in.
/// * The newly created account *MUST* have a new access key added to its account (either
/// full or limited access) in the same receipt that the account was created in.
///
/// Arguments
/// * `new_account_id`: the valid NEAR account which is being created and should
/// receive the linkdrop assets
/// * `new_public_key`: the valid public key that should be used for the access key added to
/// The Public key must be in a binary format with base58 string serialization with human-readable curve. The key types currently supported are secp256k1 and ed25519. Ed25519 public keys accepted are 32 bytes and secp256k1 keys are the uncompressed 64 format.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
/// the newly created account.
///
/// Returns `true` if the claim was successful meaning the `new_account_id` was created and all assets were sent to it.
function create_account_and_claim(new_account_id: string, new_public_key: string) -> Promise<boolean>
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
```

## Example Scenarios
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

*Pre-requisite Steps*: Linkdrop creation:
The linkdrop creator that has an account and some $NEAR:

- creates a keypair locally (`pubKey1`, `privKey1`). (The keypair is not written to chain at this time)
- calls a method, passing the `pubKey1` and desired $NEAR amount as arguments, to a contract that implements the linkdrop standard.
- The contract will map the `pubKey1` to the desired balance for the linkdrop.
- The contract will then add the `pubKey1` as a function call access key with the ability to call `claim` and `create_account_and_claim`. This means that anyone with the `privKey1` (see above), can sign a transaction on behalf of this contract (signer id set to contract id) with a function call to call one of the mentioned functions to claim the assets.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

### Claiming a linkdrop without a NEAR Account

A user with *no* wallet that is claiming the assets associated with the public key:
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

- generates a new keypair (`pubKey2`, `privKey2`) locally. (The keypair is not written to chain at this time)
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
- chooses a new account ID such as benji.near.
- signs a transaction on behalf of the linkdrop contract (contract is set to signer_id) using `privKey1`. The method will be `create_account_and_claim`.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
- the args of this function call will contain both `pubKey2` (which will be used to create a full access key for the new account) and the account ID itself.
- the linkdrop contract will delete the access key associated with `pubKey1` so that it cannot be used again.
- the linkdrop contract will create the new account and transfer the funds to it alongside any other assets.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved
- the user will be able to sign transactions on behalf of the new account using `privKey2`.

### Claiming a linkdrop with a NEAR Account
frol marked this conversation as resolved.
Show resolved Hide resolved

A user with an *existing* wallet that is claiming the assets associated with the public key:

- signs a transaction on behalf of the linkdrop contract (contract is set to signer_id) using `privKey1`. The method will be `claim`.
- the args of this function call will simply contain the user's existing account ID.
- the linkdrop contract will delete the access key associated with `pubKey1` so that it cannot be used again.
- the linkdrop contract will transfer the funds to that account alongside any other assets.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved


## Reference Implementation

Below are some references for linkdrop contracts.

- [Link Drop Contract](https://github.com/near/near-linkdrop)
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

- [Keypom Contract](https://github.com/keypom/keypom)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth linking to a specific commit state as who knows how the implementations will evolve in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't implemented this specific standard as it's not finalized but once we finish with the interface, I'll make a PR into keypom and then link the commit here.



## Security Implications
frol marked this conversation as resolved.
Show resolved Hide resolved

1. Linkdrop Creation
Linkdrop creation involves creating keypairs associated with mapping to assets such as $NEAR, FTs, NFTs and access keys. There are security implications with the access keys associated with the keypairs. These access keys should be function call access keys and be limited to specific functionality. For example, keypairs can be generated with function call access keys which have permission to only call `claim` and `create_account_and_claim`. Another important security implication of linkdrop creation is to ensure that only one key is mapped to a set of assets at any given time. Linkdrop creation can happen in real-time or prior to being received. The mass creation of linkdrops involves creating multiple keypairs associated with mapping to assets and access keys. The security implications mentioned above are increased with the mass creation of linkdrops.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

2. Linkdrop Key Management
Key management is a critical safety component of linkdrops. The linkdrop creator should implement a key management strategy for linkdrop keys upon the receiver claiming the linkdrops assets. For example, one strategy may be to delete the linkdrop keypair in the same block the receiver claims the linkdrop assets, so a single linkdrop cannot be used multiple times. In another example, a linkdrop creator may allow for a linkdrop to be used X number of times, where they could track the number of calls to `claim` or `create_account_and_claim` and delete the keypair after X uses of the linkdrop. It is very important to implement your key management strategy in `claim` and `create_account_and_claim` as opposed to a callback function to ensure the linkdrop keys are not improperly used. Implementing key management in a callback has a security implication of such as 'double-spending' a linkdrop.
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

3. Asset Refunds & Failed Claims
Given that linkdrops could contain multiple different assets such as NFTs, or fungible tokens, sending assets might happen across multiple blocks. If the claim was unsuccessful (such as passing in an invalid account ID), it is important to ensure that all state is properly managed and assets are optionally refunded depending on the linkdrop contract's implementation.

## Future possibilities
frol marked this conversation as resolved.
Show resolved Hide resolved

- Linkdrop creation interface

- Bulk linkdrop management (create, update, claim)

- Function call data types
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

- Optional configurations added to KeyInfo which can include multi-usekeys, time-based claiming etc…

- Standard process for how links connect to claim pages (i.e a standardized URL such as an app’s baseUrl/contractId= [LINKDROP_CONTRACT]&secretKey=[SECRET_KEY]

- Standard for deleting keys and refunding assets.
frol marked this conversation as resolved.
Show resolved Hide resolved

## Copyright
[copyright]: #copyright
BenKurrek marked this conversation as resolved.
Show resolved Hide resolved

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).