keypom-js
v1.5.0-rc.1
Published
Keypom JS SDK
Downloads
13
Readme
Check out our official Keypom Documentation for tutorials, concepts and more!.
- About
- Installation
- Getting Started
- Tests
- Costs
- How Linkdrops Work
- Contributing
About
To view our debut talk at NEARCON 2022, click here. To read more about the Keypom Protocol, refer to the official GitHub repository
The Keypom JavaScript SDK is a library that allows developers to easily interact with the Keypom Protocol. The SDK abstracts away all the complex interactions that come with interacting with the protocol. It allows developers to tap into the power of Keypom with as little barrier to entry as possible by providing easy to use plug-and-play functions.
Developers should be able to focus on building without needing to understand exactly how interactions with the Keypom protocol work.
The Keypom SDK was built with flexibility in mind. We want to support integrating Keypom whether you're creating a dApp built with the with the wallet-selector, creating a backend, or if you're simply using a local node script.
To build the complete TypeDocs locally, run the following command:
yarn build-docs && cd doc && python -m http.server 4200
Alternatively, you can visit the official Keypom docs which host the type docs found here.
Installation
To install the Keypom SDK, simply run the following command:
npm install keypom-js
# or
yarn add keypom-js
# or
pnpm add keypom-js
This should add the following dependency to your package.json
where the version number will be the latest SDK release.
"dependencies": {
"keypom-js": "*.*.*"
},
Getting Started
The first thing to do when getting started with the SDK is to call the initKeypom
function. This will initialize the state and establish a connection to the NEAR blockchain. This must be done before using any other function that interacts with the chain. The exception is if you're only using utility functions such as generateKeys
which does not interact with the blockchain.
Initializing the SDK
When calling initKeypom
, there are several arguments that can be passed in as outlined below.
near?
(NEAR): A pre-existing NEAR connection object to use. By default, the SDK will create a new connection based on thenetwork
passed in.network
(string): The network to connect to, eithermainnet
ortestnet
.funder?
(Funder): Object containing important information about the funder's account. If specified, the SDK will use this account to sign all transactions unless otherwise overridden by passing in awallet
oraccount
argument in the SDK methods.keypomContractId?
(string): Instead of using the most up-to-date, default Keypom contract, you can specify a specific account ID to use. If an older version is specified, some features of the SDK might not be usable. It is important to note that the SDK only works for official Keypom contracts.
There are several different scenarios that will determine what should be passed into initKeypom
:
- If you only wish to invoke
view
methods and retrieve information from the Protocol without signing any transactions, nofunder
object is required. - If the funder changes a lot, you may wish to pass in a custom
near
object that has akeyStore
containing the keys for each funder. When you then call SDK methods, you can pass in differentaccount
objects. - If you're using the SDK on a frontend with wallet-selector, don't pass in a
funder
object and instead pass in awallet
object when calling SDK methods.
View Functions
Once the SDK has been initialized, you can start calling methods. The simplest functions to call are those that require no signature and are used to retrieve information from the protocol. A list of view functions available and what they do can be found below.
getKeyBalance
- Returns the balance associated with given key. This is used by the NEAR wallet to display the amount of the linkdrop.getKeyTotalSupply
- Query for the total supply of keys currently on the Keypom contract.getKeys
- Paginate through all active keys on the contract and return a vector of key info.getKeyInformation
- Returns the KeyInfo corresponding to a specific public key.getKeyInformationBatch
- Returns a vector of KeyInfo corresponding to a set of public keys passed in.getDropInformation
- Get information about a specific drop by passing in either a drop ID, public key, or secret key.getKeySupplyForDrop
- Returns the total supply of active keys for a given drop.getKeysForDrop
- Paginate through all keys in a specific drop, returning an array of KeyInfo.getDropSupplyForOwner
- Returns the total supply of active drops for a given account IDgetDrops
- Paginate through drops owned by an account. If specified, information for the first 50 keys in each drop can be returned as well.getNftSupplyForDrop
- Return the total supply of token IDs for a given NFT drop.getNftTokenIDsForDrop
- Paginate through token IDs in an NFT drop to return a vector of token IDs.getUserBalance
- Query for a user's current balance on the Keypom contractgetContractSourceMetadata
- Returns the source metadata for the Keypom contract that the SDK has been initialized on. This includes valuable information such as which specific version the contract is on and link to exactly which GitHub commit is deployed.
A quick example of showing how to initialize the SDK and call a view function is shown below:
// Initialize the SDK on testnet. No funder is passed in since we're only doing view calls.
await initKeypom({
network: "testnet",
});
// Query for the Keypom contract's source metadata
const metadata = await getContractSourceMetadata();
console.log('metadata: ', metadata)
Creating Drops
The core of Keypom revolves around creating drops. This is where the true power of the protocol comes in. With the SDK, the complex logic and data structures have been simplified and abstracted away from the developer. With this in mind, to create a drop, you need to call the createDrop
function. It has a suite of arguments depending on what the desired outcome should be:
account?
(Account) - Valid NEAR account object that if passed in, will be used to sign the txn instead of the funder account.wallet?
(BrowserWalletBehaviour | Wallet) - If using a browser wallet through wallet selector and that wallet should sign the transaction, pass in the object.numKeys
(number) - Specify how many keys should be generated for the drop. IfpublicKeys
is not passed in,numKeys
number of keys are automatically created. The behaviour of this automatic creation depends on if the funder has rootEntropy set OR rootEntropy is passed in. In this case, the keys will be deterministically generated using the drop ID, key nonce, and entropy. Otherwise, each key will be generated randomly.publicKeys?
(string[]) - Pass in a custom set of publicKeys to add to the drop. If this is not passed in, keys will be generated based on thenumKeys
parameter.depositPerUseNEAR?
(number) - Specify how much $NEAR should be contained in each link. Unit in $NEAR (i.e1
= 1 $NEAR)depositPerUseYocto?
(string) - Specify how much $yoctoNEAR should be contained in each link. Unit in yoctoNEAR (1 yoctoNEAR = 1e-24 $NEAR)dropId?
(string) - Specify a custom drop ID rather than using one from the SDK. If no drop ID is passed in, an ID equal toDate.now()
will be used.config?
(DropConfig) - Allows specific drop behaviors to be configured such as the number of uses each key / link will have.metadata?
(string) - Specify a string of metadata to attach to the drop. This can be whatever you would like and is optional. Often this is stringified JSON.simpleData?
(SimpleData) - For creating a simple drop, this contains necessary configurable information about the drop.ftData?
(FTData) - For creating a fungible token drop, this contains necessary configurable information about the drop.nftData?
(NFTData) - For creating a non-fungible token drop, this contains necessary configurable information about the drop.fcData?
(FCData) - For creating a function call drop, this contains necessary configurable information about the drop.rootEntropy?
(string) - Specify a custom entropy to use for generating keys (will overload the funder's rootEntropy if applicable). This parameter only matters if the publicKeys variable is not passed in.basePassword?
(string) - For doing password protected drops, this is the base string that will be used to generate all the passwords. It will be double hashed with the public key and specific key use to generate the password for each key.passwordProtectedUses?
(number[]) - For doing password protected drops, specifies exactly which uses will be password protected. The uses are NOT zero indexed (i.e 1st use = 1). Each use will have a different, unique password generated via double hashing the base password + public key + key use.useBalance?
(boolean) - If the account has a balance within the Keypom contract, set this to true to avoid the need to attach a deposit. If the account doesn't have enough balance, an error will throw.returnTransactions?
(boolean) - If true, the transaction will be returned instead of being signed and sent. This is useful for getting the requiredDeposit from the return value without actually signing the transaction.successUrl?
(string) - When signing with a wallet, a success URl can be included that the user will be redirected to once the transaction has been successfully signed.
While there are many fields and it may be overwhelming, seeing examples will help clear things up.
The last thing to note is the return value of createDrop
since it contains important information that can be used to interact with the drop. The return value is an object with the following fields:
/**
* Information returned when creating a drop or adding keys via `createDrop` and `addKeys` respectively.
*/
export interface CreateOrAddReturn {
/** The responses to any transactions that were signed and sent to the network. */
responses?: any,
/** Information about the transactions if `returnTransactions` is specified in the arguments. This will result in the information being returned instead of signed and sent. */
transactions?: Transaction[],
/** The required deposit that should be attached to the transaction. */
requiredDeposit?: string,
/** Any keys that were automatically generated. */
keys?: {
/** Actual KeyPair objects that can be used to sign messages, verify signatures, and get the public and private keys */
keyPairs: NearKeyPair[];
/** Set of public keys that were generated */
publicKeys: string[];
/** Set of private keys that were generated */
secretKeys: string[];
},
/** The drop ID for the drop that is being interacted with. */
dropId: string
}
Creating a simple drop with 10 random keys
In this example, a simple $NEAR drop with 10 keys will be created. To start, the Keypom SDK is initialized with a funder object (since a transaction will be signed). This funder object contains no rootEntropy
field since all the keys should be random.
The next step is to create the drop with 10 keys. numKeys
is passed in as well as depositPerUseNEAR
to indicate that each key should contain 1 $NEAR.
// Initialize the SDK for the given network and NEAR connection. No entropy passed in so any auto-generated keys will be completely random unless otherwise overwritten.
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
// Create a drop with 10 completely random keys. The return value `keys` contains information about the generated keys
const {keys} = await createDrop({
numKeys: 10,
depositPerUseNEAR: 1,
});
console.log('public keys: ', keys.publicKeys);
console.log('private keys: ', keys.secretKeys);
Creating a simple drop with 5 deterministically generated keys
In this example, a simple $NEAR drop will be created whereby all the keys are deterministically generated based off the funder's rootEntropy
(think a password). This allows the funder to be able to recover the keys easily assuming they know the password.
To start, initKeypom
is called and the funder object is passed in and contains a root entropy set to my-global-secret-password
. This means that any keys that are auto-generated will be based off this entropy rather than being random.
Once the SDK is initialized, the drop is created with 5 keys and 1 $NEAR per key. To double check if everything worked, the generateKeys
method can be leveraged where each key can be generated from the rootEntropy
, the dropId
, and the corresponding key nonce (starting at 0). This second piece of entropy that is unique to each key is known as metaEntropy
. We can generated it by setting an array equal to: ["1_0", "1_1", "1_2", "1_3", "1_4"]
where 1
is the drop ID and the second number is the key nonce (separated by an underscore since that's how the SDK does things). What happens behind the scenes is that the root entropy is combined with the meta entropy for each key and hashed to generate the key.
Using generateKeys
, the resulting keys can be compared with the ones generated by the SDK and they turn out to be the same.
// Initialize the SDK for the given network and NEAR connection. Root entropy is passed into the funder account so any generated keys will be based off that entropy.
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey:"d25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1",
rootEntropy: "my-global-secret-password"
}
});
// Create a simple drop with 5 keys. Each key will be derived based on the rootEntropy of the funder, drop ID, and key nonce.
const { keys: keysFromDrop, dropId } = await createDrop({
numKeys: 5,
depositPerUseNEAR: 1,
});
// Deterministically Generate the Private Keys:
const nonceDropIdMeta = Array.from({length: 5}, (_, i) => `${dropId}_${i}`);
const manualKeys = await generateKeys({
numKeys: 5,
rootEntropy: "my-global-secret-password",
metaEntropy: nonceDropIdMeta
})
// Get the public and private keys from the keys generated by the drop
const {publicKeys, secretKeys} = keysFromDrop;
// Get the public and private keys from the keys that were manually generated
const {publicKeys: pubKeysGenerated, secretKeys: secretKeysGenerated} = manualKeys;
// These should match!
console.log('secretKeys: ', secretKeys)
console.log('secretKeysGenerated: ', secretKeysGenerated)
// These should match!
console.log('publicKeys: ', publicKeys)
console.log('pubKeysGenerated: ', pubKeysGenerated)
Creating a simple drop with pre-created keys
This example showcases how you can create a simple drop and pass in keys that are generated ahead of time rather than having them be auto-generated. The first step is always to call initKeypom
. Once that's finished, the keypairs can be generated by calling generateKeys
and passing in the number of keys to create. Those public keys can then be passed into createDrop
.
// Initialize the SDK for the given network and NEAR connection. No entropy passed in so any auto generated keys will be completely random unless otherwise overwritten.
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
// Generate 10 random keys
const {publicKeys} = await generateKeys({
numKeys: 10
});
// Create a drop using the keys that were generated. Since keys are passed in, the return value won't contain information about the keys.
await createDrop({
publicKeys,
depositPerUseNEAR: 1,
});
Creating a simple drop with a password protected key
This example shows how you can create a password-protected key. This means that only the person with both the private key and the password can claim the $NEAR. The first step is always to call initKeypom
. After that, a basePassword
can be passed into createDrop
. By passing in that parameter, the SDK will enable password protection whereby the password is equal to a hash of 'basePassword + publicKey + keyUse'
.
// Initialize the SDK for the given network and NEAR connection
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
const basePassword = "my-cool-password123";
// Create a simple drop with 1 $NEAR and pass in a base password to create a unique password for each use of each key
const {keys} = await createDrop({
numKeys: 1,
depositPerUseNEAR: 1,
basePassword
});
// Create the password to pass into claim which is a hash of the basePassword, public key and whichever use we are on
let currentUse = 1;
let passwordForClaim = await hashPassword(basePassword + keys.publicKeys[0] + currentUse.toString());
Claiming Linkdrops
Once a drop has been created and there are keys available, they can be claimed by calling the SDK's claim
function. This can be used to either claim the assets to an existing NEAR account or create an entirely new account. The parameters for the function are below.
secretKey
(string) - The private key associated with the Keypom link. This can either contain theed25519:
prefix or not.accountId?
(string) - The account ID of an existing account that will be used to claim the drop.newAccountId?
(string) - If passed in, a new account ID will be created and the drop will be claimed to that account. This must be an account that does not exist yet. This must be passed in conjunction withnewPublicKey
.newPublicKey?
(string) - If creating a new account, a public key must be passed in to be used as the full access key for the newly created account.password?
(string) - If a password is required to use the key, it can be passed in.
As with creating drops, it helps to look at examples to understand the different scenarios whereby calling claim
makes sense.
Claiming a Linkdrop to an Existing Account
In this example, you'll create a simple $NEAR drop with 1 key. The key will be automatically generated and will be completely random since no entropy is used. The first step is to initialize the SDK and call createDrop
. The return value will contain keys
since they're being auto-generated.
Once the drop is created, the secret key can be used to claim to an existing account benjiman.testnet
.
// Initialize the SDK for the given network and NEAR connection
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
// Create a simple drop with 1 $NEAR
const {keys} = await createDrop({
numKeys: 1,
depositPerUseNEAR: 1,
});
// Claim the drop to the passed in account ID
await claim({
secretKey: keys.secretKeys[0],
accountId: "benjiman.testnet"
})
Claiming a Linkdrop and Onboarding a New User
This example will show how to onboard a new user by claiming a simple linkdrop. To start, you'll create a simple $NEAR drop with 1 key. The key will be automatically generated and will be completely random since no entropy is used.
Once the drop is created, claim
should be called and the new account ID and public key should be passed in. This public key can either be a completely new keypair or the same keypair that was used to claim the linkdrop. In this tutorial, a new keypair is generated.
// Initialize the SDK for the given network and NEAR connection
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
// Create a simple drop with 1 $NEAR
const {keys} = await createDrop({
numKeys: 1,
depositPerUseNEAR: 1,
});
// create a new keypair which will be used for the new account
const {publicKeys, secretKeys} = await generateKeys({
numKeys: 1
});
// Claim the drop using the secret key from the drop creation step. The newly created account will have a keypair
// from the generateKeys step.
await claim({
secretKey: keys.secretKeys[0],
newAccountId: "my-newly-creating-account.testnet",
newPublicKey: publicKeys[0]
})
Claiming a Password Protected Linkdrop
This example aims to show how a password protected drop can be created and claimed. The first step is always to initialize the SDK. Once that's finished, a base password for the key can be used and passed into createDrop
.
When claiming the linkdrop, the password must be passed in otherwise the claim will be unsuccessful. This password is made up of the base password, public key, and current use the key is on (relevant for multi-use keys).
// Initialize the SDK for the given network and NEAR connection
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
const basePassword = "my-cool-password123";
// Create a simple drop with 1 $NEAR and pass in a base password to create a unique password for each use of each key
const {keys} = await createDrop({
numKeys: 1,
depositPerUseNEAR: 1,
basePassword
});
// Create the password to pass into claim which is a hash of the basePassword, public key and whichever use we are on
let currentUse = 1;
let passwordForClaim = await hashPassword(basePassword + keys.publicKeys[0] + currentUse.toString());
// Claim the drop to the passed in account ID and use the password we generated above.
await claim({
secretKey: keys.secretKeys[0],
accountId: "benjiman.testnet",
password: passwordForClaim
})
Deleting Keys and Drops
Often times, not all the keys in your drop will be used. What happens to the excess keys? Keypom allows you to delete keys from a drop and get fully refunded for any unclaimed assets. This is done by calling the SDK's deleteKeys
and deleteDrops
respectively. The difference between the two is outlined below.
deleteKeys
allows you to delete a set of specific keys from a drop and get refunded for the assets. This does not delete the drop as a whole.deleteDrops
allows you to delete a set of drops and all the keys contained within them. It does this by recursively callingdeleteKeys
for each drop until they're empty and then deleting the drop itself.
The parameters for both functions are outlined below:
Delete Keys
account?
(Account) - Valid NEAR account object that if passed in, will be used to sign the txn instead of the funder account.wallet?
(BrowserWalletBehaviour | Wallet) - If using a browser wallet through wallet selector and that wallet should sign the transaction, pass in the object.publicKeys
(string[] | string) - Specify a set of public keys to delete. If deleting a single publicKey, the string can be passed in without wrapping it in an array.dropId
(string) - Which drop ID do the keys belong to?withdrawBalance?
(boolean) - Whether or not to withdraw any remaining balance on the Keypom contract.
An example of deleting keys can be seen below where a simple drop with 5 keys is created and one key is deleted.
// Initialize the SDK for the given network and NEAR connection
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
// Create the simple drop with 5 random keys
const {keys, dropId} = await createDrop({
numKeys: 5,
depositPerUseNEAR: 1,
});
await deleteKeys({
dropId,
publicKeys: keys.publicKeys[0] // Can be wrapped in an array as well
})
Delete Drops
account?
(Account) - Valid NEAR account object that if passed in, will be used to sign the txn instead of the funder account.wallet?
(BrowserWalletBehaviour | Wallet) - If using a browser wallet through wallet selector and that wallet should sign the transaction, pass in the object.drops?
(ProtocolReturnedDrop[]) - If the set of drop information for the drops you want to delete (fromgetDropInformation
orgetDrops
) is already known to the client, it can be passed in instead of the drop IDs to reduce computation.dropIds?
(string[]) - Specify a set of drop IDs to delete.withdrawBalance?
(boolean) - Whether or not to withdraw any remaining balance on the Keypom contract.
An example of deleting drops can be seen below where 5 simple drops are created and then they're all deleted in one call to deleteDrops
.
// Initialize the SDK for the given network and NEAR connection
await initKeypom({
network: "testnet",
funder: {
accountId: "benji_demo.testnet",
secretKey: "ed25519:5yARProkcALbxaSQ66aYZMSBPWL9uPBmkoQGjV3oi2ddQDMh1teMAbz7jqNV9oVyMy7kZNREjYvWPqjcA6LW9Jb1"
}
});
// loop to create 5 simple drops each with 5 more keys than the next
for(var i = 0; i < 5; i++) {
// create 10 keys with no entropy (all random)
const {publicKeys} = await generateKeys({
numKeys: 5 * (i+1) // First drop will have 5, then 10, then 15 etc..
});
// Create the simple
await createDrop({
publicKeys,
depositPerUseNEAR: 1,
});
}
let drops = await getDrops({accountId: "benji_demo.testnet"});
await deleteDrops({
drops
})
// Get the number of drops the account has after deletion (should be zero)
const numDrops = await getDropSupply({
accountId: "benjiman.testnet"
});
console.log('numDrops: ', numDrops)
Account Balances for Smooth UX
In order to make the UX of using Keypom seamless, the SDK supports the Protocol's debiting account model. All costs and refunds can 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 SDK's addToBalance
and withdrawBalance
functions.
This account balance system is not used by default and must be explicitly enabled by passing in the useBalance
flag to any corresponding functions. The benefits of using the account balance system is that for browser based wallets, you can skip redirects.
A very common scenario is creating a drop with many keys at once. To avoid having to redirect the user many times (since only 100 keys can be added to a drop at once), you can call createDrop
and pass in returnTransactions
set to true. This will result in the transaction being returned instead of signed and sent. At this point, you can get the required deposit from the returned object and use that in addToBalance
.
At this point, the user will only be redirected once for the call to addToBalance
. Once they returned, you can call createDrop
in conjunction with addKeys
with useBalance
set to true.
Utility Functions
There are several functions and variables that have been exported in order to make developing easier. Below are a few notable functions:
nearAPI
(variable) - Contains a suite of functionalities coming fromnear-api-js
. This includes, but is not limited to the KeyPair object, formatting functions, different keystores etc.updateFunder()
- Allows you to update the funder account for the SDK. This is useful if you want to use a different account to sign transactions.useKeypom()
- Returns all the environment variables that are used by the SDK. This includes the funder object, Keypom contract, keystore, NEAR connection etc.hashPassword()
- Generate a sha256 hash of a passed in string. This also supports hex encoded strings.formatNearAmount()
- Converts a human readable NEAR amount (i.e 1.5 $NEAR) to yoctoNEAR.parseNearAmount()
- Converts a yoctoNEAR amount to a human readable NEAR amount (i.e 1500000000000000000000000 yoctoNEAR -> 1.5 $NEAR).generateKeys()
- Generate a desired number of keypairs. These can be created with or without entropy.
Tests
The SDK comes equipped with a number of tests. These can be used as reference for different scenarios that arise with Keypom. To run the tests, you'll need to have a few prerequisites.
Running the Tests
First, you'll need a valid NEAR account's secret key and account ID. These need to exported as environment variables as the tests will be running on testnet and the transactions need to be signed. Export the following environment variables:
export TEST_ACCOUNT_ID="YOUR_ACCOUNT_ID"
export TEST_ACCOUNT_PRVKEY="YOUR_SECRET_KEY"
Once completed, install the dependencies:
npm install
At this point, everything is in order so that the tests can be run. The following command can be used to begin running the tests:
npm run test
If all went well, the following should be outputted once all the tests have run:
✔ delete 1 key from simple drop (3.7s)
requiredDeposit: 1856805594168075000000050
Receipt: uN5cwkUFXB2gY4LE8vfuCGvA8BcqaTVgoExKQGu2Cct
Log [v1-3.keypom.testnet]: User balance incremented by 1.8568055. Old: 0 new: 1.8568055
✔ Create drop and return requiredDeposit so it can be added to balance (10.5s)
✔ invalid args being passed in
─
18 tests passed
Costs
It is important to note that the Keypom contracts are 100% FEE FREE and will remain that way for the forseeable future. These contracts are a public good and are 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.
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.
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.
You can use markdownlint-cli to check for common markdown style inconsistency.
License
This project is licensed under the GPL License.