Awesome
<p align="center"> <img src="assets/claimed-linkdrop.png" alt="Logo" style="width: 35%; height: 35%"> <br /> </p> <div align="center"> <h1> Keypom </h1> Limitless possibilities in the palm of your hand. </div> <div align="center"> <br /> </div> <details open="open"> <summary>Table of Contents</summary>- About
- Our Solution
- Costs
- How Linkdrops Work
- Getting Started
- Query Information From Keypom
- Running Tests
- Contributing
- Acknowledgements
About
<tr> <td>To view our debut talk at NEARCON 2022, click here.
Keypom is an access key factory created as a result of 3 common problems that arose in the ecosystem.
- People want a cheap, customizable, and unique onboarding experience for users.
- Companies don't want to expose full access keys in their backend servers.
- dApps want a smooth UX with zero barrier to entry onboarding.
The contract was initially created as a way to handle the 1 $NEAR minimum deposit required for creating linkdrops using the regular linkdrop contract.
If users wanted to create linkdrops, they needed to attach a minimum of 1 $NEAR. This made it costly and unscalable for projects that wanted to mass onboard onto NEAR. Keypom, on the other hand, has been highly optimized to allow for the lowest possible costs.
Introduction
Blockchain technology comes with many benefits such as sovereign ownership, digital rights, privacy, freedom, peer to peer coordination and much more. The problem with this technology, however, is that there is an extremely high barrier to entry for an everyday individual. None of it matters if nobody can onboard.
It’s confusing to create and fund a crypto wallet. People are unfamiliar with the process, technical jargon, and the general flow. NEAR’s account model is powerful, but extremely underutilized because it’s complex for developers to take full advantage of. Keypom wraps this up in a single API call.
With NEAR’s goal of onboarding 1 billion users to Web3, there needs to be a solution to this high barrier to entry for developers building on NEAR and users onboarding to their apps and the NEAR ecosystem.
Below is a table outlining the minimum costs to onboard a new user onto NEAR with a named account.
1 Account | 1,000 Accounts | 1,000,000 Accounts | |
---|---|---|---|
Traditional Linkdrop | ~1 NEAR | ~1,003 NEAR | ~1,002,840 NEAR |
Keypom | ~0.0035 NEAR | ~3.5 NEAR | ~3,500 NEAR |
~99.65% Cheaper | ~99.65% Cheaper | ~99.65% Cheaper |
Keypom allows anyone to create highly customizable onboarding experiences for their users. These experiences can be both for new, or existing users. If someone already has a wallet, they can still use a Keypom link to experience an app, and then transfer the assets later.
Comparable Solutions
Keypom | NEAR Drop | Satori | |
---|---|---|---|
NEAR Drop | ✅ | ✅ | ❌ |
FT Drop | ✅ | ❌ | ❌ |
NFT Drop | ✅ | ❌ | ✅ |
Function Call Drop | ✅ | ❌ | ❌ |
Embeddable in Dapps | ✅ | ❌ | ❌ |
Wallet Selector Integration | ✅ | ❌ | ❌ |
No Fee | ✅ | Maybe? | ❌ |
No Backend / 3rd Party | ✅ | ✅ | ❌ |
Campaigns | ✅ | ✅ | ✅ |
Multi-Step e.g. Tickets click > scan > claim | ✅ | ❌ | ❌ |
Password Protected Drops | ✅ | ❌ | ❌ |
Timed Drops e.g. recurring payments | ✅ | ❌ | ❌ |
Custom Names e.g. user.myapp.near | ✅ | ❌ | ❌ |
Our Solution
Keypom allows for the creation of highly customizable access keys. These keys can be thought of as having their own smart contracts. Each access key derives from what's known as a drop. These drops outline the different functionalities and behaviors the key will have. A drop can be thought of as a bucket that access keys belong to. You can create many different buckets and fill them each with their own keys. Each key will act in accordance to the drop, or bucket, it belongs to.
A drop can be one of four different types:
- Simple drop.
- Non Fungible Token drop.
- Fungible Token drop.
- Function Call drop.
Shared Drop Customization
While each type of drop has its own set of customizable features, there are some that are shared by all drops These are outlined below.
/// Each time a key is used, how much $NEAR should be sent to the claiming account (can be 0).
pub deposit_per_use: u128,
/// How much Gas should be attached when the key is used. The default is 100 TGas as this is
/// what's used by the NEAR wallet.
pub required_gas: Gas,
/// The drop as a whole can have a config as well
pub config: Option<DropConfig>,
/// Metadata for the drop in the form of stringified JSON. The format is completely up to the
/// user and there are no standards for format.
pub metadata: LazyOption<DropMetadata>,
Within the config, there are a suite of features that can be customized as well:
/// How many uses can each key have before it's deleted. If None, default to 1.
pub uses_per_key: Option<u64>,
/// Override the global root account that sub-accounts will have (near or testnet). This allows
/// users to create specific drops that can create sub-accounts of a predefined root.
/// For example, Fayyr could specify a root of `fayyr.near` By which all sub-accounts will then
/// be `ACCOUNT.fayyr.near`
pub root_account_id: Option<AccountId>,
// /Any time based configurations
pub time: Option<TimeConfig>,
/// Public sale config options
pub sale: Option<PublicSaleConfig>,
/// Any usage specific configurations
pub usage: Option<UsageConfig>,
Time Based Customizations
Keypom allows users to customize time-based configurations as outlined below.
pub struct TimeConfig {
/// Minimum block timestamp before keys can be used. If None, keys can be used immediately
/// Measured in number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC.
pub start: Option<u64>,
/// Block timestamp that keys must be before. If None, keys can be used indefinitely
/// Measured in number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC.
pub end: Option<u64>,
/// Time interval between each key use. If None, there is no delay between key uses.
/// Measured in number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC.
pub throttle: Option<u64>,
/// Interval of time after the `start_timestamp` that must pass before a key can be used.
/// If multiple intervals pass, the key can be used multiple times. This has nothing to do
/// With the throttle timestamp. It only pertains to the start timestamp and the current
/// timestamp. The last_used timestamp is not taken into account.
/// Measured in number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC.
pub interval: Option<u64>,
}
Usage Based Customizations
In addition to time-based configurations, the funder can customize behaviors pertaining to key usages.
pub struct UsageConfig {
/// Can the access key only call the claim method_name? Default to both method_name callable
pub permissions: Option<ClaimPermissions>,
/// If claim is called, refund the deposit to the owner's balance. If None, default to false.
pub refund_deposit: Option<bool>,
/// Should the drop be automatically deleted when all the keys are used? This is defaulted to false and
/// Must be overwritten
pub auto_delete_drop: Option<bool>,
/// When this drop is deleted and it is the owner's *last* drop, automatically withdraw their balance.
pub auto_withdraw: Option<bool>,
/// When calling `create_account` on the root account, which keypom args should be attached to the payload.
pub account_creation_fields: Option<KeypomArgs>,
}
Primary Market Public Sale for Keys
The last type of customization available to the funder is the ability to create a public sale for access keys in a drop. The funder can create a drop and let people add keys to it on an as-needed basis. The sale configurations are outlined below.
pub struct PublicSaleConfig {
/// Maximum number of keys that can be added to this drop. If None, there is no max.
pub max_num_keys: Option<u64>,
/// Amount of $NEAR that the user needs to attach (if they are not the funder) on top of costs. This amount will be
/// Automatically sent to the funder's balance. If None, the keys are free to the public.
pub price_per_key: Option<u128>,
/// Which accounts are allowed to add keys?
pub allowlist: Option<LookupSet<AccountId>>,
/// Which accounts are NOT allowed to add keys?
pub blocklist: Option<LookupSet<AccountId>>,
/// Should the revenue generated be sent to the funder's account balance or
/// automatically withdrawn and sent to their NEAR wallet?
pub auto_withdraw_funds: Option<bool>,
/// Minimum block timestamp before the public sale starts. If None, keys can be added immediately
/// Measured in number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC.
pub start: Option<u64>,
/// Block timestamp dictating the end of the public sale. If None, keys can be added indefinitely
/// Measured in number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC.
pub end: Option<u64>,
}
Use-Cases for Public Sales
Giving the funder the ability to sell access keys to a drop introduces a ton of awesome use-cases and has a slew of benefits:
- Everything is decentralized and on-chain. There is no need to trust a third party to hold the keys.
- Keys are created on an as-needed basis. This drastically reduces up-front costs for the drop funder.
- Since keys are created only when users want them, there is no chance that through distribution, the private key gets compromised. The key is created when the user purchases it.
- Everything can remain anonymous and private since people can purchase access keys with their crypto wallets.
Having a public sale allows for an on-chain distribution mechanism for access keys. Let's look at two examples where this can be used.
Example 1: Ticketing
Imagine there is an event organizer that wants to host with a guest-list of 100,000 people. Without doing a public sale, the organizer would need to spend a lot of $NEAR up-front to create all 100 thousand access keys. At this point, they would need to find a way to distribute all the keys.
With a public sale, the organizer can set a price per key, an allowlist, a blocklist, and even a start date for when the sale goes live. At this point, the keys would be lazily purchased by people coming to the event. This not only reduces the up-front cost for the funder but it can also provide more accurate data on how many people are actually coming to the event.
Example 2: Selling Function Calls
Access keys can be used for much more than just POAPs, onboarding or tickets. When using FC Drops, the keys can execute functions on external contracts. This feature can be used in conjunction with the public sale to create a marketplace for gated function calls.
Imagine a simple guest-book smart contract that only allowed people to sign the book if they had a valid Keypom access key. Whoever signed the guest-book had access to all VIP events at NEARCon. You could lock access to signing the guest-book behind a Keypom drop and setup a public sale.
Example 3: NFT Collections
A very common scenario is an artist launching a new NFT collection. The artist can setup a custom marketplace whereby the keys are lazily minted and sold to the public. They can then create a custom website that takes a Keypom link and brings the user through a unique, creative experience before the NFT is minted and a wallet is optionally created. People that purchase the links can either use them to send the NFT to their existing wallet or create an entirely new wallet.
Simple Drops
The most basic type of drop is the simple kind. Any keys that are part of a simple drop can
only be used for 1 thing: transferring $NEAR. Once the key is claimed, the claiming account
will receive the $NEAR specified in the deposit_per_use
. Simple drops are a great way to send
$NEAR to claiming accounts while not storing a lot of information on the contract. Below are a
couple use cases.
Backend Servers
Let's say you have a backend server that should send 10 $NEAR to the first 3 people that redeem an NFT. Rather than exposing your full access key in the backend server, you could create a simple drop that either has 3 keys or 1 key that is claimable 3 times. In the drop, you'd specify that each time the key is claimed, the specified account would receive 10 $NEAR.
Recurring Payments
Recurring payments are quite a common situation. If you need to send someone 10 $NEAR once a
month for 6 months, you could create a simple drop that has a usage config with an interval
of 1 month.
In addition, you can set the time based config to have a start
of next week. Everytime the key is used,
10 $NEAR is sent to the account. If the contractor missed a month's payment, they can claim the key late but
can never use the key more than what is intended.
Quick Onboarding
If you need to quickly onboard users onto NEAR, you could create a simple drop with a
small amount of $NEAR (enough to create a wallet) and set the usage's permissions to be
create_account_and_claim
. This means that the key can only be used to create accounts.
You can then add keys as you wish to the drop and give them out to users so they can create
accounts and be onboarded onto NEAR.
Lazy Registering Keys
A unique use-case for simple drops is the ability to lazy register key uses. This allows the funder to batch
create many keys at a time while only paying for basic fees such as the storage used and the key's allowance.
The funder would not need to pay for the deposit_per_use
of each key up front. They can instead register individual
key uses as they are needed.
With this scenario, if an organization wanted to onboard users with a linkdrop valued at 10 $NEAR, they could create 1000 keys
without needing to pay 1000 * 10 = 10,000 $NEAR up-front. They could then register keys on an as-needed basis. If they need to
register 25 keys at a time, they can do this by simply calling the register_uses
function.
Non-Fungible Token Drops
Non-Fungible Token drops are a special type that allows users to "preload" the drop with NFTs. These tokens will then be automatically sent to the claiming user. The claiming flow is fairly similar to simple drops in that users can either create an account or claim to an existing one.
NFT drops are essentially a wrapper around simple drops. All the functionalities that simple drops have are carried over but now, users can receive an NFT as well as $NEAR. This brings introduces some customization and uniqueness to the use-cases.
How does it work?
Every drop has a field known as registered_uses
. This tells the contract how many uses the
drop has across all its keys. For basic simple drops that are not lazy registering keys, this field
doesn't matter since all the uses are paid for up-front when the drop is created or when keys are added.
With NFT drops, however, there is a 2 step process:
- Firstly, the drop is created and all the $NEAR required is pre-paid for. This is the same as
simple drops, however, the
registered_uses
are set to 0. - Once the drop is created, the owner must send the contract the NFTs in order for keys to be
usable. This process is done through the
nft_transfer_call
workflow baked into the NFT standards. It's up to the owner to facilitate this process.
Whenever the contract receives tokens, it will push the ID to a vector. These IDs are popped off whenever a key is used. A user will receive the most recent token sent to the contract as the vector is acting like a stack.
NFT Config
Along with the default global configurations for drops, if you'd like to create an NFT drop, you must specify the following pieces of information when the drop is created.
pub struct NFTDataConfig {
/// Which account ID will be sending the NFTs to the contract. If this is not specified, anyone can send NFTs for the specific drop.
pub sender_id: Option<AccountId>,
/// Which contract will the NFTs live on
pub contract_id: AccountId,
}
By specifying this information, the drop is locked into only accepting NFTs from the specific contract and optionally from a specified sender account.
Use Cases
NFT drops work really well for when you want to send a pre-existing NFT to a user along with some $NEAR. Since NFT drops are a light wrapper around simple drops, most of the use-cases are the same although people can now get NFTs as well. This means you can onboard a user with some $NEAR and they get an NFT too.
Fungible Token Drops
A Fungible Token drop is also a light wrapper around the simple drop. It works very similarly to how its NFT counterpart does. First, you'll need to create the drop and then you can fund it with assets and register key uses.
You can preload a drop with as many FTs as you'd like even if you don't have the keys yet. This will spike the
registered_uses
and then you can create keys and slowly eat away from this "total supply" overtime. If the
drop runs out, you can send it more FTs to top up. All the keys in the FT drop will share from this supply
and everytime a key is used, the registered_uses
will decrement and the "total supply" will get smaller.
How does it work?
As mentioned in the NFT section, every drop has a field known as registered_uses
. This tells the contract
how many uses the drop has across all its keys. For basic simple drops that are not lazy registering keys, this field
doesn't matter since all the uses are paid for up-front when the drop is created or when keys are added.
With FT drops, however, there is a 2 step process:
- Firstly, the drop is created and all the $NEAR required is pre-paid for. This is the same as
simple drops, however, the
registered_uses
are set to 0. - Once the drop is created, the owner must send the contract the FTs in order for keys to be
usable. This process is done through the
ft_transfer_call
workflow baked into the FT standards. It's up to the owner to facilitate this process.
FT Config
Along with the default global configurations for drops, if you'd like to create a FT drop, you must specify the following pieces of information when the drop is created.
pub struct FTDataConfig {
/// The contract that the FTs live on.
pub contract_id: AccountId,
/// The account ID that will be sending the FTs to the contract. If this is not specified, anyone can send FTs for the specific drop.
pub sender_id: Option<AccountId>,
/// How many FTs should the contract send *each time* a key is used.
pub balance_per_use: U128,
}
By specifying this information, the drop is locked into only accepting FTs from the specific contract and optionally from a specified sender account. you can send as many FTs as you'd like and can over-pay, you must send at least enough FTs in one call to cover 1 use. As an example, if a drop is created such that 10 FTs will be sent when a key is used, you must send at least 10 and cannot break it up into separate calls where you send 5 one time and 5 another.
Use Cases
FT drops have some awesome flexibility due to the fact that they support all the functionalities of the Simple drops, just with more use-cases and possibilities. Let's look at some use cases to see how fungible token drops can be used.
Recurring Payments
Recurring payments are quite a common situation. Let's say you need to send someone $50 USDC every week. You
could create a key with 5 uses that has a time config interval
of 1 week. You would then pre-load maybe the
first week's deposit of $50 USDC and register 1 use or you could send $500 USDC for the first 10 weeks. At that
point, you would simply hand over the key to the user and they can claim once a week.
Backend Servers
Taking the recurring payments problem to another level, imagine that instead of leaving the claims up to the
contractor, you wanted to automatically pay them through a backend server. They would give you their NEAR account
and you would send them FTs. The problem is that you don't want to expose your full access key in the server.
By creating a FT drop, you can store only the function call access key created by Keypom in the server.
Your backend would them use the key to call the claim
function and pass in the user's account ID to send
them the FTs.
Creating a Wallet with FTs
Another awesome use-case is to allow users to be onboarded onto NEAR and also receive FTs. As an example, You could do a promotion where you're giving away $10 USDC to the first 100 users that sign up to your mailing list. You can also give away QR codes at events that contain a new fungible token that you're launching. You can simply create a FT drop and pre-load it with the FT of your choice. In addition, you can give it 0.02 $NEAR for new wallets that are created.
You can pair this with setting the usage config's refund_deposit
flag to true which would make it so that if anyone claims
the fungible tokens and they already have a wallet, it will automatically refund you the 0.02 $NEAR. That money should
only be used for the creation of new wallets. Since your focus is on the fungible tokens, you don't want to force users
to create a new wallet if they have one already by specifying the usage permissions to be create_account_and_claim
but instead,
you want to be refunded in case they do.
Function Call Drops
Function call drops are by far the most powerful feature that Keypom provides. FC drops allow any method on any contract to be executed (with some exceptions). In addition, there are a huge variety of customizations and features you can choose from when defining the drop that come on top of the global options. The possibilities are almost endless. State of the art NFT ticketing, lazy minting NFTs, auto registration into DAOs, analytics for marketing at events and much more.
How do FC Drops work?
Unlike NFT and FT drops, the function calls must have everything paid for upfront. There is no two step process so the creation is similar to Simple drops. Once the drop is created and keys are added, you can immediately start using it.
Function Call Config
When creating the drop, you have quite a lot of customization available. At the top level, there is a FC drop global config similar to how the general config works.
pub struct FCConfig {
/// How much GAS should be attached to the function call if it's a regular claim.
/// If this is used, you *cannot* go through conventional linkdrop apps such as mynearwallet
/// since those *always* attach 100 TGas no matter what. In addition, you will only be able to
/// call `claim` if this is specified. You cannot have an `attached_gas` parameter and also
/// call `create_account_and_claim.
pub attached_gas: Option<Gas>,
}
Method Data
In addition to the global config, the user can specify a set of what's known as MethodData
. This represents the
information for the function being called. Within this data, there are also a few optional configurations you can use
to extend your use cases. You'll see how powerful these can be in the use cases section.
pub struct MethodData {
/// Contract that will be called
pub receiver_id: AccountId,
/// Method to call on receiver_id contract
pub method_name: String,
/// Arguments to pass in (stringified JSON)
pub args: String,
/// Amount of yoctoNEAR to attach along with the call
pub attached_deposit: U128,
/// Specifies what field the claiming account ID should go in when calling the function
/// If None, this isn't attached to the args
pub account_id_field: Option<String>,
/// Specifies what field the drop ID should go in when calling the function. To insert into nested objects, use periods to separate. For example, to insert into args.metadata.field, you would specify "metadata.field"
/// If Some(String), attach drop ID to args. Else, don't attach.
pub drop_id_field: Option<String>,
/// Specifies what field the key ID should go in when calling the function. To insert into nested objects, use periods to separate. For example, to insert into args.metadata.field, you would specify "metadata.field"
/// If Some(String), attach key ID to args. Else, don't attach.
pub key_id_field: Option<String>,
// Specifies what field the funder id should go in when calling the function. To insert into nested objects, use periods to separate. For example, to insert into args.metadata.field, you would specify "metadata.field"
// If Some(string), attach the funder ID to the args. Else, don't attach.
pub funder_id_field: Option<String>,
// What permissions does the user have when providing custom arguments to the function call?
// By default, the user cannot provide any custom arguments
pub user_args_rule: Option<UserArgsRule>,
}
The MethodData keeps track of the method being called, receiver, arguments, and attached deposit. In addition, there are some optional fields that can be used to extend the use cases. If you have a contract that requires some more context from Keypom such as the funder ID, drop ID, key ID, and account ID that used the key, these can all be specified.
We've kept it generic such that you can specify the actual argument name that these will be passed in as. For example, if you
had a contract that would lazy mint an NFT and it required the account to be passed in as receiver_id
, you could specify
an account_id_field
set to receiver_id
such that Keypom will automatically pass in the account ID that used the key under the
field receiver_id
. Similarly, inserting fields into nested arguments is quite trivial.
Let's say you wanted to insert the account ID that claimed the drop into the receiver_id
under metadata for the following args:
args: {
"token_id": "foobar",
"metadata": {
"receiver_id": INSERT_HERE
}
}
You could specify the account_id_field
as metadata.receiver_id
and Keypom will automatically create the receiver_id
field and insert it into metadata
. This would work whether or not metadata
was already present in the args.
NOTE: The location for inserting the arguments cannot collide with another entry. In the above example,
token_id.receiver_id
could NOT be specified sincetoken_id
is mapped tofoobar
already.
This logic extends to the drop ID, and key Id as well.
Key Uses
For every key use, you can specify a vector of MethodData
which allows you to execute multiple function calls each
time a key is used. These calls are scheduled 1 by 1 using a simple for loop. This means that most of the time, the function
calls will be executed in the order specified in the vector but it is not guaranteed.
It's important to note that the Gas available is split evenly between all the function calls and if there are too many, you might run into issues with not having enough Gas. You're responsible for ensuring that this doesn't happen.
The vector of MethodData
is optional for each key use. If a key use has null
rather than Some(Vector<MethodData>)
,
it will decrement the uses and work as normal such that the timestamp,
start` etc. are enforced. The only
difference is that after the key uses are decremented and these checks are performed, the execution finishes early. The null
case does not create an account or send any funds. It doesn't invoke any function calls and simply returns once the
checks are done. This makes the null case act as a "burner" where you disregard any logic. This has many uses which will
be explored in the use cases section.
If a key has more than 1 use, you can specify a different vector of MethodData
for each use. As an example, you could
specify that the first use will result in a null case and the second use will result in a lazy minting function being called.
If you have multiple uses but want them all to do the same thing, you don't have to repeat the same data. Passing in only 1
vector of MethodData
will result in all the uses inheriting that data.
Security for FC Drops
Since all FC drops will be signed by the Keypom contract, there are a few restrictions in place to avoid malicious behaviors. To avoid users from stealing registered assets from other drops, the following methods cannot be called via FC Drops:
/// Which methods are prohibited from being called by an FC drop
const DEFAULT_PROHIBITED_FC_METHODS: [&str; 6] = [
"nft_transfer",
"nft_transfer_call",
"nft_approve",
"nft_transfer_payout",
"ft_transfer",
"ft_transfer_call",
];
In addition, the Keypom contract cannot be the receiver of any function call. This is to avoid people from calling private methods through FC Drops.
Keypom Arguments
When a key is used and a function is called, there is a data structure that is automatically attached to the arguments.
This is known as the keypom_args
. It contains the information that the drop creator specified in the MethodData
.
pub struct KeypomArgs {
pub account_id_field: Option<String>,
pub drop_id_field: Option<String>,
pub key_id_field: Option<String>,
pub funder_id_field: Option<String>
}
Motivation
Let's say there was an exclusive NFT contract that allowed the Keypom contract to mint NFTs as part of an FC drop. Only Keypom
was given access to mint the NFTs so they could be given out as linkdrops. The organizer only wanted links that were part of their
drop to be valid. For this reason, the NFT contract would only mint if Keypom called the nft_mint
function and there was a field
series
passed in and it was equal to the drop ID created by the organizer.
Let's say the owner created an exclusive drop that happened to have a drop ID of 5. They could then go to the NFT contract and restrict NFTs to only be minted if:
series
had a value of 5.- The Keypom contract was the one calling the function.
In order for this to work, when creating the drop, the owner would need to specify that thedrop_id_field
was set to a value of series
such that the drop ID is correctly passed into the function.
The problem with this approach is that the NFT contract has no way of knowing which arguments were sent by the user when the drop
was created as part of the MethodData
argsand which arguments are automatically populated by the Keypom contract. There is nothing stopping a malicious user from creating a new drop that has an ID of 6 but hardcoding in the actual arguments that
seriesshould have a value of 5. In this case, the malicious drop would have *no*
drop_id_fieldand the NFT contract would have no way of knowing that the
series` value is malicious.
This can be prevented if a new field is introduced representing what was automatically injected by the Keypom contract itself. At the
end of the day, Keypom will always send correct information to the receiving contracts. If those contracts have a way to know what has
been sent by Keypom and what has been manually set by users, the problem is solved. In the above scenario, the NFT contract would simply add
an assertion that the keypom_args
had the account_id_field
set to Some(series)
meaning that the incoming series
field was set by Keypom
and not by a malicious user.
User Provided Arguments
In the MethodData
, there is an optional field that determines whether or not users can provide their own arguments when claiming a linkdrop and what that behaviour will look like. This is known as the user_args_rule
and can be one of the following:
/// When a user provides arguments for FC drops in `claim` or `create_account_and_claim`, what behaviour is expected?
/// For `AllUser`, any arguments provided by the user will completely overwrite any previous args provided by the drop creator.
/// For `FunderPreferred`, any arguments provided by the user will be concatenated with the arguments provided by the drop creator. If there are any duplicate args, the drop funder's arguments will be used.
/// For `UserPreferred`, any arguments provided by the user will be concatenated with the arguments provided by the drop creator, but if there are any duplicate keys, the user's arguments will overwrite the drop funder's.
pub enum UserArgsRule {
AllUser,
FunderPreferred,
UserPreferred
}
By default, if user_args_rule
is None
/ not provided, any user provided arguments will be completely disregarded. It would act as if the user provided no args in the first place.
These user arguments must be passed in via the fc_args
field in claim
and create_account_and_claim
. This field is of type Option<Vec<Option<String>>>
indicating that it's optional to provide the args and for each claim, a set of args can be provided. If, for a specific method, args shouldn't be passed in, the vector can have None
as the value. The order of the args must match the order of the methods that will be executed.
NOTE: If a user provides
fc_args
, the length of the vector MUST match the number of methods being executed during the claim.
All User
If user_args_rule
is set to AllUser
, any arguments provided by the user will completely overwrite any previous args provided by the drop creator. If no args as passed in by the user, the drop creator's original args will be used.
As an example, if the method data was:
args: JSON.stringify({
"foo": "bar",
"baz": {
"foo": "bar
}
})
And the user provided the following args:
fc_args: JSON.stringify({
"new_field": "new_value"
})
Keypom would completely overwrite the funder's previous args and use the user's fc_args
instead.
Funder Preferred
If user_args_rule
is set to FunderPreferred
, any arguments provided by the user will be concatenated with the arguments provided by the drop creator. If there are any duplicate args, the drop funder's arguments will be prioritized / used.
As an example, if the funder args were:
args: JSON.stringify({
"funder_field": "funder_value",
"object": {
"funder_field": "funder_value"
}
})
And the user provided the following args:
fc_args: JSON.stringify({
"funder_field": "user_value",
"object": {
"funder_field": "user_value",
"user_field": "user_value"
}
})
Keypom would take the user args and merge them together with the funder's but prioritize any fields that are funder specified. The resulting output would be:
args: JSON.stringify({
"funder_field": "funder_value",
"object": {
"funder_field": "funder_value",
"user_field": "user_value"
}
})
User Preferred
If user_args_rule
is set to UserPreferred
, any arguments provided by the user will be concatenated with the arguments provided by the drop creator, but if there are any duplicate keys, the user's arguments will overwrite the drop funder's.
As an example, if the funder args were:
args: JSON.stringify({
"funder_field": "funder_value",
"object": {
"funder_field": "funder_value"
}
})
And the user provided the following args:
fc_args: JSON.stringify({
"object": {
"funder_field": "user_value",
"user_field": "user_value"
}
})
Keypom would take the user args and merge them together with the funder's but prioritize any fields that are user specified. The resulting output would be:
args: JSON.stringify({
"funder_field": "funder_value",
"object": {
"funder_field": "user_value",
"user_field": "user_value"
}
})
FC Drop Use Cases
Function call drops are the bread and butter of the Keypom contract. They are the most powerful and complex drops that can currently be created. With this complexity, there are an almost infinite number of use-cases that arise.
Proof of Attendance Protocols
A very common use case in the space is what's known as Proof of Attendance. Often times when people go to events, they want a way to prove that they were there. Some traditional approaches would be to submit your wallet address and you would be sent an NFT or some other form of proof at a later date. The problem with this is that it has a very high barrier to entry. Not everyone has a wallet.
With Keypom, you can create a function call drop that allows people to onboard onto NEAR if they don't have a wallet or if they do, they can simply use that. As part of the onboarding / claiming process, they would receive some sort of proof of attendance such as an NFT. This can be lazy minted on-demand such that storage isn't paid up-front for all the tokens.
At this point, the event organizers or the funder can distribute links to people that attend the event in-person. These links would then be claimed by users and they would receive the proof of attendance.
Auto Registration into DAOs
DAOs are a raging topic in crypto. The problem with DAOs, however, is there is a barrier to entry for users that aren't familiar with the specific chain they're built on top of. Users might not have wallets or understand how to interact with contracts. On the contrary, they might be very well versed or immersed in the DAO's topics. They shouldn't be required to create a wallet and learn the onboarding process.
With Keypom, you can create a function call drop with the main purpose of registering users into a DAO. For people that have a wallet, this will act as an easy way of registering them with the click of a link. For users that don't have a wallet and are unfamiliar with NEAR, they can be onboarded and registered into the DAO with the same click of a link.
Multisig Contracts
Another amazing use-case for Keypom is allowing multisig contracts to have ZERO barrier to entry. Often times when using a multisig contract, you will entrust a key to a trusted party. This party might have no idea what NEAR is or how to interact with your contract. With Keypom, you can create a drop that will allow them to sign their transaction with a click of a link. No NEAR wallet is needed and no knowledge of the chain is required.
At the end of the day, from the users perspective, they are given a link and when they click it, their portion of the multisig transaction is signed. The action is only performed on the multisig contract once all links have been clicked. This is an extremely powerful way of doing accomplishing multisig transactions with zero barrier to entry.
The users don't even need to create a new account. They can simply call claim
when the link is clicked which will fire the cross-contract call
to the multisig contract and pass in the keypom arguments that will be cross-checked by that contract.
NFT Ticketing
The problem with current NFT ticketing systems is that they require users to have a wallet. This is a huge barrier to entry for people that are attending events but don't have wallets. In addition, there is often no proof of attendance for the event as the NFT is burned in order to get into the event which requires an internet connection.
Keypom aims to solve these problems by having a ticketing system that has the following features.
- No wallet is needed to enter the event or receive a POAP.
- No wifi is needed at the door.
- An NFT is minted on-demand for each user that attends the event.
- Users can optionally onboard onto NEAR if they don't have a wallet.
In addition, some way to provide analytics to event organizers that contains information such as links that were:
- Given out but not clicked at all.
- Clicked but not attended.
- Partially claimed indicating the number of people that attended but did not onboard or receive a POAP.
- Fully claimed indicating the number of people that attended and received a POAP.
In order to accomplish this, you can create a drop that has 3 uses per key. These uses would be:
- Array(
null
) - Array(
null
) - Array(function call to POAP contract to lazy mint an NFT)
The event organizer would create the links and distribute them to people however they see fit. When a user receives the link, the first
claim is automatically fired. This is a null
case so nothing happens except for the fact that the key uses are decremented. At this point,
the organizer knows that the user has clicked the link since the uses have been decremented.
The next claim happens only when the user is at the door. Keypom would expose a QR code that can only be scanned by the bouncer's phone. This QR code would appear once the first link is clicked and contains the private key for the link. At the event, they wouldn't need any wifi to get in as they only need to show the bouncer the QR code. Once the bouncer scans it, the site would ensure that they have exactly 2 out of the 3 uses left. If they don't, they're not let in. At that point, a use is decremented from the key and the next time they visit the ticket page (when they have internet), they would be able to claim the final use and be onboarded / receive a POAP.
<p align="center"> <img src="assets/flowcharts/ticketing.png" style="width: 65%; height: 65%" alt="Logo"> </p>Password Protected Keys
Password protecting key uses is an extremely powerful feature that can unlock many use-cases. Keypom has baked flexibility and customization into the contract such that almost all use-cases involving password protection can be accomplished. Whenever a key is added to a drop, it can have a unique password for each individual use, or it can one password for all uses in general.
How Does It Work?
The Keypom implementation has been carefully designed so that users can't look at the NEAR Explorer to view what was passed into the contract either when the drop was created or when a key was used to try and copy those passwords. We also want passwords to be unique across keys so that if you know the password for 1 key, it doesn't work on a different key. In order to accomplish this, we use the concept of hashing.
Imagine you have a drop with 2 keys and you want to password protect each key. Rather than forcing the drop funder to input a unique password for each key and having them remember each one, we can have them input a single base password and derive unique passwords from it that are paired with the key's public key.
This is the most scalable option as it allows the drop funder to only need to remember 1 password and they can derive all the other ones using the hashing algorithm and public key.
In the above scenario, let's say the funder inputs the base password as mypassword1
. If a user wanted to claim the first key, they would need to input
into the contract:
hash("mypassword1" + key1_public_key)
The funder would need to give the user this hash somehow (such as embedding it into the link or having an app that can derive it). It's important to note that the funder should probably NOT give them the base password otherwise the user could derive the passwords for all other keys (assuming those keys have the same base password).
What is Stored On-Chain?
How does Keypom verify that the user passed in the correct password? If the funder were to simply pass in hash("mypassword1" + key1_public_key)
into the
contract as an argument when the key is created, users could just look at the NEAR Explorer and copy that value.
Instead, the funder needs to pass in a double hash when the key is created: hash(hash("mypassword1" + key1_public_key))
.
This is the value that is stored on-chain and when the user tries to claim the key, they would pass in just the single hash: hash("mypassword1" + key1_public_key)
.
The contract would then compute hash(hash("mypassword1" + key1_public_key))
and compare it to the value stored on-chain. If they match, the key is claimed.
Using this method, the base password is not exposed to the user, nobody can look on-chain or at the NEAR explorer and derive the password, and the password is unique across multiple keys.
Passwords Per Key Use
Unlike the passwords per key which is the same for all uses of a key, the drop creator can specify a password for each individual key use. This password follows
the same pattern as the passwords per key in that the funder inputs a hash(hash(SOMETHING))
and then the user would input hash(SOMETHING)
and the contract
would hash this and compare it to the value stored on-chain.
The difference is that each individual key use can have a different value stored on-chain such that the user can be forced to input a different hash each time.
This SOMETHING
that is hashed can be similar to the global password per key example but this time, the desired key use is added: hash("mypassword1" + key1_public_key + use_number)
In order to pass in the passwords per use, a new data structure is introduced so you only need to pass in passwords for the uses that have them. This is known as the
JsonPasswordForUse
and is as follows:
pub struct JsonPasswordForUse {
/// What is the password for this use (such as `hash("mypassword1" + key1_public_key + use_number)`)
pub pw: String,
/// Which use does this pertain to
pub key_use: u64
}
Adding Your First Password
Whenever keys are added to Keypom, if there's passwords involved, they must be passed in using the following format.
passwords_per_use: Option<Vec<Option<Vec<JsonPasswordForUse>>>>,
passwords_per_key: Option<Vec<Option<String>>>,
Each key that is being added either has a password, or doesn't. This is through the Vec<Option<>
. This vector MUST be the same length as the number of keys created.This doesn't
mean that every key needs a password, but the Vector must be the same length as the keys.
As an example, if you wanted to add 3 keys to a drop and wanted only the first and last key to have a password_per_key, you would pass in:
passwords_per_key: Some(vec![Some(hash(hash(STUFF))), None, Some(hash(hash(STUFF2)))])
Complex Example
To help solidify the concept of password protected keys, let's go through a complex example. Imagine Alice created a drop with a uses_per_key
of 3.
She wants to create 4 keys:
- Key A: No password protection.
- Key B: Password for uses 1 and 2.
- Key C: Password for use 1 only.
- Key D: Password that doesn't depend on the use.
In this case, for Keys B and C, they will have the same base password but Alice wants to switch things up and have a different base password for Key D.
When these keys are added on-chain, the passwords_per_key
will be passed in as such:
passwords_per_key: Some(vec![
None, // Key A
None, // Key B
None, // Key C
// Key D
Some(
hash(hash("key_d_base_password" + key_d_public_key))
),
]),
The passwords for Key B and Key C will be passed in as such:
passwords_per_use: Some(vec![
None, // Key A
// Key B
vec![
{
pw: hash(hash("keys_bc_base_password" + key_b_public_key + "1")),
key_use: 1
},
{
pw: hash(hash("keys_bc_base_password" + key_b_public_key + "2")),
key_use: 2
}
]
// Key C
vec![
{
pw: hash(hash("keys_bc_base_password" + key_c_public_key + "1")),
key_use: 1
}
]
None // Key D
]),
The drop funder would then give the keys out to people:
Key A
Alice gives Bob Key A and he would be able to claim it 3 times with no password required.
Key D
Alice gives Charlie Key D and he would be able to claim it 3 times with the hashed global key password: hash("key_d_base_password" + key_d_public_key)
.
When Charlie uses the key, he would input the password hash("key_d_base_password" + key_d_public_key)
and the contract would hash that and check to see
if it matches what is stored on-chain (which it does).
If anyone tried to look at what Charlie passes in through the explorer, it wouldn't work since his hash contains the public key for key D and as such it is only valid for Key D.
Similarly, if Charlie tried to look at the explorer when Alice created the keys and attempted to pass in hash(hash("key_d_base_password" + key_d_public_key))
,
the contract would attempt to hash this and it would NOT match up with what's in the storage.
Key B
Alice gives Eve Key B and she would need a password for claim 1 and 2. For the first claim, she needs to pass in: hash("keys_bc_base_password" + key_b_public_key + "1")
.
The contract would then check and see if the hashed version of this matches up with what's stored on-chain for that use.
The second time Eve uses the key, she needs to pass in hash("keys_bc_base_password" + key_b_public_key + "2")
and the same check is done.
If Eve tries to pass in hash("keys_bc_base_password" + key_b_public_key + "1")
for the second key use, the contract would hash it and check:
hash(hash("keys_bc_base_password" + key_b_public_key + "1")) == hash(hash("keys_bc_base_password" + key_b_public_key + "2"))
Which is incorrect and the key would not be claimed.
Once Eve uses the key 2 times, the last claim is not password protected and she's free to claim it.
Key C is similar to Key B except that it only has 1 password for the first use.
Use-Cases
Password protecting key uses is a true game changer for a lot of use-cases spanning from ticketing to simple marketing and engagement.
Ticketing and POAPs
Imagine you had an event and wanted to give out exclusive POAPs to people that came. You didn't want to force users to:
- Have a NEAR wallet
- Have wifi at the door.
- Burn NFTs or tokens to get into the event.
The important thing to note is that by using password protected key uses, you can GUARANTEE that anyone that received a POAP had to PHYSICALLY show up to the event. This is because the POAP would be guarded by a password.
You could create a ticketing event using Keypom as outlined in the Ticketing section and have a key with 2 uses. The first use would be password protected and the second use is not. The first use will get you through the door and into the event and the second contains the exclusive POAP and can onboard you. This means that anyone with the ticket, or key, can only receive the POAP if they know the password.
You can have a scanner app that would scan people's tickets (tickets are just the private key). In this scanner app, the base password is stored and whenever the ticket is scanned, the public key is taken and the following hash is created:
hash(base password + public key)
This hash is then used to claim a use of the key and you will be let into the party. The scanner app can deterministically generate all the necessary hashes for all the tickets by simply scanning the QR code (which has the private key exposed). The tickets are worthless unless you actually show up to the event and are scanned.
Once you're scanned, you can refresh your ticket page and the use the second key claim which is not password protected. This use contains the exclusive POAP and you can onboard onto NEAR.
Marketing and Engagement
Let's say that you're at an event and want people to show up to your talks and learn about your project. You can have a scanner app similar to the one mentioned in the ticketing scenario that derives the password for any use on any key.
At the beginning of the event, you can give out a bunch of keys that have progressively increasing rewards gated by a password. At the end, the last key use contains a special reward that is only unlocked if the user has claimed all the previous key uses.
In order for these uses to be unlocked, People must show up to your talks and get scanned. The scanner will derive the necessary password and unlock the rewards. Users will only get the exclusive reward if they come to ALL your talks.
This idea can be further expanded outside the physical realm to boost engagement on your websites as an example:
You want users to interact with new features of your site or join your mailing list.
You can have links where uses are ONLY unlocked if the user interacts with special parts of your site such as buying a new NFT or joining your mailing list or clicking an easter egg button on your site etc.
dApp Free Trials for Users
In the upcoming Keypom V2.0, dApps will be able to integrate the Keypom wallet selector plugging to allow for free trials for their users. One of the biggest pain-points with Web3 at the moment is the fact that users need to fund wallets before they interact with a dApp.
In Web2, a user can find value in an application by using it before they go through the messy onboarding process. Why can't Web3 be the same?
Keypom will allow apps to create links that will automatically sign users into their applications and give them a free trial of the app. The user will be able to interact with things, spend $NEAR, sign transactions and gather assets through the trial. A unique feature of this is that the user will never be redirected to the NEAR wallet to approve transactions.
Keypom will provide a seamless user experience where users can find value in applications. Once the free trial is over and users have collected assets / $NEAR through interacting with the dApp, they can THEN choose to onboard.
With Keypom's technology, users will be locked into only interacting with the dApp specified in the link. Users can't rug the application and steal the $NEAR embedded in the link. The funds are allocated for 1 thing and 1 thing only: free trials of that one specific dApp.
<p align="center"> <img src="assets/flowcharts/trial_accounts.png" style="width: 65%; height: 65%" alt="Logo"> </p>Costs
It is important to note that the Keypom contract is 100% FEE FREE and will remain that way for the forseeable future. This contract is a public good and is meant to inspire change in the NEAR ecosystem.
With that being said, there are several mandatory costs that must be taken into account when using Keypom. These costs are broken down into two categories: per key and per drop.
NOTE: Creating an empty drop and then adding 100 keys in separate calls will incur the same cost as creating a drop with 100 keys in the same call.
Per Drop
When creating an empty drop, there is only one cost to keep in mind regardless of the drop type:
- Storage cost (~0.006 $NEAR for simple drops)
Per Key
Whenever keys are added to a drop (either when the drop is first created or at a later date), the costs are outlined below.
Key Costs for Simple Drop
- $NEAR sent whenever the key is used (can be 0).
- Access key allowance (~0.0187 $NEAR per use).
- Storage for creating access key (0.001 $NEAR).
- Storage cost (~0.006 $NEAR for simple drops)
Additional Costs for NFT Drops
Since keys aren't registered for use until after the contract has received the NFT, we don't know how much storage the token IDs will use on the contract. To combat this, the Keypom contract will automatically measure the storage used up for storing each token ID in the nft_on_transfer
function and that $NEAR will be taken from the funder's balance.
Additional Costs for FT Drops
Since accounts claiming FTs may or may not be registered on the Fungible Token contract, Keypom will automatically try to register all accounts. This means that the drop creators must front the cost of registering users depending on the storage_balance_bounds
returned from the FT contract. This applies to every use for every key.
In addition, Keypom must be registered on the FT contract. If you create a FT drop and are the first person to ever do so for a specific FT contract on Keypom, Keypom will be automatically registered when the drop is created. This is a one time cost and once it is done, no other account will need to register Keypom for that specific FT contract.
Additional Costs for FC Drops
Drop creators have a ton of customization available to them when creation Function Call drops. A cost that they might incur is the attached deposit being sent alongside the function call. Keypom will charge creators for all the attached deposits they specify.
NOTE: The storage costs are dynamically calculated and will vary depending on the information you store on-chain.
Deleting Keys and Drops
Creators have the ability to delete drops and keys at any time. In this case, all the initial costs they incurred for the remaining keys will be refunded to them (minus Gas fees of course).
Automatic Refunds When Keys are Used
One way that Keypom optimizes the fee structure is by performing automatic refunds for some of the initial costs that creators pay for when keys are used. All the storage that is freed along with any unused allowance is automatically sent back to the creator whenever a key is used. This model drastically reduces the overall costs of creating drops and creates incentives for the keys to be used.
Account Balances for Smooth UX
In order to make the UX of using Keypom seamless, the contract introduces a debiting account model. All costs and refunds go through your account's balance which is stored on the contract. This balance can be topped up or withdrawn at any moment using the add_to_balance()
and withdraw_from_balance()
functions.
This account balance is not required, however. You can create a drop by attaching a deposit to the call. Keep in mind that this will create an account balance for you behind the scenes, however.
</td> </tr> </table>Built With
How Linkdrops Work
For some background as to how linkdrops works on NEAR:
The funder that has an account and some $NEAR:
- creates a keypair locally
(pubKey1, privKey1)
. The blockchain doesn't know of this key's existence yet since it's all local for now. - calls
send
on the contract and passes in thepubKey1
as an argument as well as the desiredbalance
for the linkdrop.- The contract will map the
pubKey1
to the desiredbalance
for the linkdrop. - The contract will then add the
pubKey1
as a function call access key with the ability to callclaim
andcreate_account_and_claim
. This means that anyone with theprivKey1
that was created locally, can claim this linkdrop.
- The contract will map the
- Funder will then create a link to send to someone that contains this
privKey1
. The link follows the following format:
wallet.testnet.near.org/linkdrop/{fundingContractAccountId}/{linkdropKeyPairSecretKey}?redirectUrl={redirectUrl}
fundingContractAccountId
: The contract accountId that was used to send the funds.linkdropKeyPairSecretKey
: The corresponding secret key to the public key sent to the contract.redirectUrl
: The url that wallet will redirect to after funds are successfully claimed to an existing account. The URL is sent the accountId used to claim the funds as a query param.
The receiver of the link that is claiming the linkdrop:
- Receives the link which includes
privKey1
and sends them to the NEAR wallet. - Wallet creates a new keypair
(pubKey2, privKey2)
locally. The blockchain doesn't know of this key's existence yet since it's all local for now. - Receiver will then choose an account ID such as
new_account.near
. - Wallet will then use the
privKey1
which has access to callclaim
andcreate_account_and_claim
in order to callcreate_account_and_claim
on the contract.- It will pass in
pubKey2
which will be used to create a full access key for the new account.
- It will pass in
- The contract will create the new account and transfer the funds to it alongside any NFT or fungible tokens pre-loaded.
Getting Started
There are several ways to get started using Keypom. You can use the NEAR CLI, our Keypom application, our Keypom SDK and more. In this section, we will go over how you can interact with Keypom and create drops using the NEAR-API-JS library and write simple node scripts.
Prerequisites
In order to successfully interact with this contract using the deploy scripts, you should have the following:
Deploy Scripts
There are 4 deploy scripts that have been made available for you to use and easily create Keypom links. These are for:
- Simple Drops
- NFT Drops
- FT Drops
- Function Call Drops
Each drop type deploy script has a version using NEAR-API-JS
, and a version using the Keypom-JS SDK
.
The file tree for these scripts is shown below.
/deploy
├── ft
│ └── configurations.js
│ └── ft-create-sdk.js
│ └── ft-create.js
│
├── function-call
│ └── configurations.js
│ └── fc-create-sdk.js
│ └── fc-create.js
│
├── nft
│ └── configurations.js
│ └── nft-create-sdk-minted.js
│ └── nft-create-sdk-owned.js
│ └── nft-create.js
│
├── simple
│ └── configurations.js
│ └── simple-create-sdk.js
│ └── simple-create.js
│
├── utils
In order to use these scripts, open the deploy/
directory and modify the configurations.js
file for the drop you want to create. In this file, you can specify important information such as the number of keys you wish to create, the amount of $NEAR you want to send, how many uses per key etc.
You must specify the account that you will fund the drops with under the FUNDING_ACCOUNT_ID
variable. This account needs to have keys stored in your ~/.near-credentials
folder. To do this, simply run near login
on your terminal and follow the prompts using the NEAR CLI.
Once the configurations.js
file has been modified to your liking, navigate back to the
root directory and run the deploy script.
For simple drops:
// Using NEAR-API-JS
yarn simple
// Using SDK
yarn simple-sdk
For FT drops:
// Using NEAR-API-JS
yarn ft
// Using SDK
yarn ft-sdk
For NFT drops:
// Using NEAR-API-JS
yarn nft
// Using SDK
yarn nft-sdk
For Function Call drops:
// Using NEAR-API-JS
yarn fc
// Using SDK
yarn fc-sdk
Query Information From Keypom
Keypom allows users to query a suite of different information from the contract. This information can be broken down into two separate objects that are returned. JsonDrops and JsonKeys.
pub struct JsonDrop {
// Drop ID for this drop
pub drop_id: DropId,
// owner of this specific drop
pub owner_id: AccountId,
// Balance for all keys of this drop. Can be 0 if specified.
pub deposit_per_use: U128,
// Every drop must have a type
pub drop_type: JsonDropType,
// The drop as a whole can have a config as well
pub config: Option<DropConfig>,
// Metadata for the drop
pub metadata: Option<DropMetadata>,
// How many uses are registered
pub registered_uses: u64,
// Ensure this drop can only be used when the function has the required gas to attach
pub required_gas: Gas,
// Keep track of the next nonce to give out to a key
pub next_key_id: u64,
}
pub struct JsonKeyInfo {
// Drop ID for the specific drop
pub drop_id: DropId,
pub pk: PublicKey,
// How many uses this key has left. Once 0 is reached, the key is deleted
pub remaining_uses: u64,
// When was the last time the key was used
pub last_used: u64,
// How much allowance does the key have left. When the key is deleted, this is refunded to the funder's balance.
pub allowance: u128,
// Nonce for the current key.
pub key_id: u64,
}
Key Specific
get_key_balance(key: PublicKey)
: Returns the $NEAR that will be sent to the claiming account when the key is usedget_key_total_supply()
: Returns the total number of keys currently on the contractget_keys(from_index: Option<U128>, limit: Option<u64>)
: Paginate through all keys on the contract and return a vector of key infoget_key_information(key: PublicKey)
: Return the key info for a specific keyget_key_information_batch(keys: Vec<PublicKey>)
: Return a vector of key info for a set of public keys
Drop Specific
get_drop_information(drop_id: Option<DropId>, key: Option<PublicKey>)
: Return the drop info for a specific drop. This can be queried for by either passing in the drop ID or a public key.get_key_supply_for_drop(drop_id: DropId)
: Return the total number of keys for a specific dropget_keys_for_drop(drop_id: DropId, from_index: Option<U128>, limit: Option<u64>)
: Paginate through all keys for a specific drop and return a vector of key infoget_drop_supply_for_owner(account_id: AccountId)
: Return the total number of drops for a specific accountget_drops_for_owner(account_id: AccountId, from_index: Option<U128>, limit: Option<u64>)
: Paginate through all drops for a specific account and return a vector of drop infoget_nft_supply_for_drop(drop_id: DropId)
: Get the total number of NFTs registered for a given drop.get_nft_token_ids_for_drop(drop_id: DropId, from_index: Option<U128>, limit: Option<u64>)
: Paginate through token IDs for a given dropget_next_drop_id()
: Get the next drop ID that will be used for a new drop
Utility
get_root_account()
: Get the global root account that all created accounts with be based off.get_user_balance()
: Get the current user balance for a specific account.
Running the Keypom Tests
We have put together a suite of test cases that can be found in the __tests__
folder. These range anywhere from simple config tests all the way to full blown ticketing and POAPs.
In the __tests__
folder, there are sub-folders with each type of test. Some of these sub-folders contain a utils
folder with some utility functions used.
All the tests use workspaces-js
. In order to run all the tests, run the following command.
yarn && yarn test
This will run through each test 1 by 1. If you wish to only run a set of specific tests, the full list of commands can be found below.
"test:internals"
"test:stage1"
"test:stage1:simple"
"test:ticketing"
"test:poaps"
"test:configs"
"test:nft-drops"
"test:ft-drops"
"test:profiling"
"test:passwords"
Contributing
First off, thanks for taking the time to contribute! Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are greatly appreciated.
Please try to create bug reports that are:
- Reproducible. Include steps to reproduce the problem.
- Specific. Include as much detail as possible: which version, what environment, etc.
- Unique. Do not duplicate existing opened issues.
- Scoped to a Single Bug. One bug per report.
Please adhere to this project's code of conduct.
You can use markdownlint-cli to check for common markdown style inconsistency.
License
This project is licensed under the GPL License.
Acknowledgements
Thanks for these awesome resources that were used during the development of the Keypom Contract: