Creating the Drop
Introduction
In this section you'll take the first step to creating the ticketing experience by designing the drop. This drop will be tailored according to the functionality and specifications found in the Solution Architecture.
Recall that the drop needs the following properties:
- A Function Call drop must be used and configured so that each key has 2 uses.
- The first key use is password protected and
null
(for when the host scans attendees into the event). - The second key use will have a small amount of $NEAR to cover account creation costs and sponsor the first few transactions.
- The second key use will also call
nft_mint
on an NFT contract which will send the new or existing account a POAP.
The first key use being null
will allow for a lightweight transaction on-chain to reflect the key use decrement without needing to transfer any $NEAR to anyone or create any accounts.
With this in mind, the aim of this tutorial will be to write a node script that will create the drop matching the above properties. This process can be broken down into three stages:
1) Connect to the NEAR blockchain.
2) Create the drop with function call data.
3) Create the NFT collection for the post attendance gift.
Starting at the keypom-docs-examples
directory, navigate to the ticket-app-skeleton/utils
folder and open the createTickDrop.js
file.
cd advanced-tutorials/ticket-app-skeleton/utils
There, you can see the following skeleton code in the file createTickDrop.js
.
loading...
Connecting to NEAR
In this section, you'll be addressing the first step which is connecting to the NEAR network.
This is done with NEAR-API-JS
and consists of:
Selecting which network to connect to (testnet or mainnet).
Specifying the location where the keys are stored for the drop funder's account. This location is commonly in the
~/.near-credentials
folder on your local machine.
loading...
Creating the Drop
In this section, you'll create the function call drop to meet the functional requirements defined earlier.
Function Call Drop Basics
Initializing the SDK
This process starts with calling the initKeypom
function and will always be the first function you call to interact with the Keypom SDK.
initKeypom
initializes the SDK to allow for interactions with the Keypom smart contracts. Without it, none of the other SDK functions would work as expected.
After initKeypom
is called, the FC Drop can be created by calling createDrop
and adding an fcData
parameter.
// Change this to your account ID
const FUNDER_ACCOUNT_ID = "minqi.testnet";
const NETWORK_ID = "testnet"
async function createTickDrop() {
// Initiate connection to the NEAR blockchain.
const CREDENTIALS_DIR = ".near-credentials";
const credentialsPath = path.join(homedir, CREDENTIALS_DIR);
...
...
let near = new Near(nearConfig);
const fundingAccount = new Account(near.connection, FUNDER_ACCOUNT_ID)
await initKeypom({
near,
network: NETWORK_ID
});
}
Defining the Function Call Data
One the SDK has been initialized and the NEAR connection established, it's time to create the function call drop. This is done by passing in fcData
into create drop. It is an object that defines the methods that will be called for any given key use:
fcData
└── methods
For multi-use keys, each specific use can have a different set of methods that will be called. These methods are executed one by one. As an example, a key with 3 uses can be seen:
nft_mint
null
create_account_advanced
->setup
->nft_mint
The first time the key is used, an NFT will be minted. The second use will simply advance the key with nothing called. The third time the key is used, it will first call create_account_advanced
followed by the setup
method and then finally nft_mint
all in the same transaction.
This is represented with a 2D array, where the inner array is the set of methods per key use. The above example would be represented as:
methods: [
[
"nft_mint"
],
null,
[
"create_account_advanced",
"setup",
"nft_mint"
]
]
In reality, each method is not simply a string as shown above. The methods require the following parameters:
receiverId
: The contract receiving the function call.methodName
: The function to be called on the receiver contract.args
: A stringified JSON object of all the arguments to be passed intomethodName
.attachedDeposit
: The yoctoNear deposit attached to the function call when the key is used.
For more information on the methods
parameter, please see the TypeDocs
In summary, the fcData
should look something like this, with a generic method for the POAP which you'll explore in the next section:
fcData: {
methods: [
null,
[
{
receiverId: SOME_NFT_CONTRACT,
methodName: "nft_mint",
args: SOME_ARGS,
attachedDeposit: SOME_DEPOSIT
}
],
]
}
POAP
Now that the basics for creating an FC drop have been covered, it's time to define what the second key use method data should look like.
Each NFT that is given out to participants will share the same artwork, title, description etc. They will be part of the same series and the only thing that differs between them is their unique ID. For a full tutorial about the series contract, see NEAR's NFT tutorial.
To mint these NFTs, there is a contract deployed to nft-v2.keypom.testnet
. When creating an FC drop, it is important to understand the interface of the receiver contract. In this case, the mint function has the following parameters:
pub fn nft_mint(
&mut self,
mint_id: U64,
receiver_id: AccountId
)
Here, the mint_id
is needed to identify and tell the NFT contract which series an NFT should belong to. The receiver_id
field is needed to identify which account should receive the minted NFT.
The mint_id
field should be set as the drop's dropId
and the receiver_id
should be the attendee's NEAR accountId
. At first glance, this might seem impossible because you don't know the attendee's accounts ahead of time. To solve this, you can use what's known as Keypom Arguments which are important pieces of information that can be passed into specified fields when a key is used.
The following optional Keypom arguments are exposed for each individual method in the fcData
alongside the required receiverId, methodName, attachedDeposit etc.
They tell Keypom where to inject certain parameters for each function call.
accountIdField
: The name of the account that is claiming the linkdrop.dropIdField
: ThedropId
of the drop that the claim is being called on.keyIdField
The unique identifier,keyId
, of the key that is being used to claim.funderIdField
theaccountId
of the person funding the drop.
In this case, the dropIdField
should be set to mint_id
and accountIdField
set to receiver_id
. This will result in the drop's ID being passed into the parameter mint_id
and attendee's NEAR account passed into the receiver_id
field.
In summary, the final fcData
should look as follows.
fcData: {
methods: [
null,
[
{
receiverId: `nft-v2.keypom.testnet`,
methodName: "nft_mint",
args: "",
dropIdField: "mint_id",
accountIdField: "receiver_id",
attachedDeposit: parseNearAmount("0.1")
}
],
]
}
If you wish to use a different NFT contract for your POAP, ensure you know the contract's interface and tailor the methods
arguments accordingly.
Password Protected Keys
To ensure each key has two uses with a password protected first use, the following config
, basePassword
and passwordProtectedUses
can be added to createDrop
.
await createDrop(
// 2 Uses per key
config: {
usesPerKey: 2
},
// Create base password and ensure only first key use is password protected
basePassword: "event-password",
passwordProtectedUses: [1],
fcData: {
...
}
)
As the drop creator, you have the option of creating a password and applying it to the keys in your drop. This password is unique for each key use and comprises of a master password, the drop ID and the use (1st, 2nd, 3rd etc.)
This behavior can be applied by providing the core component, a basePassword
, into the createDrop
arguments. In the ticketing case, only the first use should be protected which can be achieved by passing [1]
into the passwordProtectedUses
parameter.
Once a key has been password protected, you must pass in hash(basePassword + publicKey + current_key_use)
in order to successfully claim it.
With this model, if the host were to simply know the basePassword
, it could deterministically generate this hash for any given ticket link. Furthermore, if the host was the only person who knew the password, nobody could claim the key without going through them first.
For an in-depth explanation around password protected keys, see the Typedocs.
Final Drop Structure
Putting it all together, the final drop structure should look something like this:
loading...
Making NFT Series
Up until now, the drop is pointing to an NFT series that doesn't exist yet. If a key were to be claimed, the function call would throw an error. In this section, you'll be creating the series of NFTs to be used as POAPs.
The Keypom SDK provides a function to create an NFT series specifically for function call drops, called createNFTSeries
. It requires the following parameters:
dropId
: The drop ID for the drop that should have the NFT series associated with it.metadata
: The metadata that all minted NFTs will have.
metadata
is an object with these properties:
title
: The title for the NFTs in the series.description
: Description for all NFTs in the series.media
: link to the artwork in the form of an IPFS CID.copies
: Number of NFTs in the series.
The code for creating the series is shown below.
loading...
Once both the series and drop are created, the key can be used to mint on-demand POAPs to wallets.
Adding More Than 50 Tickets
Due to gas restriction, createDrop
is limited to 50 password protected keys (tickets). Rather than create multiple drops for the same event, you can simply add more keys to a single drop. This allows you to track your event metrics using a single dropId
.
To do this, you can use the following code:
// Loop to add 200 keys
let numKeys = 200
let keysAdded = 0;
let allSecretKeys = [];
while (keysAdded < numKeys) {
const keysToAdd = Math.min(50, numKeys - keysAdded);
const {secretKeys, publicKeys} = await generateKeys({
numKeys: keysToAdd,
});
await addKeys({
account: fundingAccount,
dropId,
publicKeys
});
keysAdded += keysToAdd;
allSecretKeys = allSecretKeys.concat(secretKeys);
}
For more, see this example or
Creating Ticket Links
The last step in this process is to create the links themselves so that you can easily distribute the tickets to people. You can control the format of the URL, for now localhost:1234
will be used.
You can utilize the formatLinkdropUrl
function for convenience. It can take a custom URL that contains CONTRACT_ID
and SECRET_KEY
and it will replace them with the contract ID and secret keys passed in.
loading...
Final Code
Putting everything together, the final code for the drop should be:
loading...
Conclusion
So far, you've broken down the ticketing system into functional requirements and used them to write a script to create the drop.
In the next tutorial, you'll be testing the drop you just created, starting with creating a script to emulate the host scanning a ticket.