In this page, we will show you how to create a NFT collection from a resource account and allow users to mint with APTOS network.
You can skip the post and found the full code here.
write the module
First, let's implement a module to create NFT collection and allow minting. We named it IMCODING NFT
.
Create a Move package,it contains a Move.toml
package manifest file and the directory stored module source file named sources
.
imcoding_nft
├── Move.toml
├── sources
├── imcoding_nft.move
Move.toml
[package]
name = "IMCODING NFT"
version = "0.0.1"
[dependencies]
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "main" }
AptosToken = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-token", rev = "main" }
[addresses]
imcoding_nft = "_"
deployer = "_"
In the manifest file, we import two dependencies: aptos_framework
and aptos_token
. And we declare two named addresses, deployer
is the original account which create a resource account to publish the module, imcoding_nft
is the resource account.
create the NFT collection
When we pulish the package, the function init_module()
will be called. So we create the NFT collection in init_module
. We use aptos_token::token
module to create NFT collection.
// file: imcoding_nft.move
module imcoding_nft::minting {
use std::string::{Self, String};
use std::vector;
use aptos_token::token::{Self, TokenDataId};
use aptos_framework::resource_account;
use aptos_framework::account;
struct NFTMinter has key {
signer_cap: account::SignerCapability,
collection: String,
}
fun init_module(resource_account: &signer) {
// the collection name
let collection_name = string::utf8(b"IMCODING NFT Collection");
// the collection description
let description = string::utf8(b"NFT issued by imcoding.online");
// the collection properity uri
let collection_uri = string::utf8(b"https://imcoding.online/properity/collection.svg");
// defined as 0 if there is no limit to the supply
let maximum_supply = 1024;
// https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token.move#L263
let mutate_setting = vector<bool>[ false, false, false ];
let resource_signer_cap = resource_account::retrieve_resource_account_cap(resource_account, @deployer);
let resource_signer = account::create_signer_with_capability(&resource_signer_cap);
token::create_collection(&resource_signer, collection_name, description, collection_uri, maximum_supply, mutate_setting);
move_to(resource_account, NFTMinter {
signer_cap: resource_signer_cap,
collection: collection_name,
});
}
}
In the last few lines ot the init_module
function, we retrieve the resource account created by the deployer
, and use the resource account to create the NFT collection. Then we publish a NFTMinter
resource which stored the signer capability under the resource account.
allow users to mint
Let's provide a function to allow users to mint NFT.
// file: imcoding_nft.move
//...
use std::signer::{address_of};
//...
public entry fun mint_nft(receiver: &signer) acquires NFTMinter {
let receiver_addr = address_of(receiver);
let nft_minter = borrow_global_mut<NFTMinter>(@imcoding_nft);
let resource_signer = account::create_signer_with_capability(&nft_minter.signer_cap);
let resource_account_address = address_of(&resource_signer);
let token_name = string::utf8(b"IMCODING NFT");
let token_description = string::utf8(b"");
let token_uri = string::utf8(b"https://imcoding.online/properity/nft.svg");
let token_data_id = token::create_tokendata(
&resource_signer,
nft_minter.collection,
token_name,
token_description,
1,
token_uri,
resource_account_address,
1,
0,
token::create_token_mutability_config(
&vector<bool>[ false, false, false, false, true ]
),
vector::empty<String>(),
vector::empty<vector<u8>>(),
vector::empty<String>(),
);
let token_id = token::mint_token(&resource_signer, token_data_id, 1);
token::direct_transfer(&resource_signer, receiver, token_id, 1);
}
The function use the signer capability stored in the NFTMinter
resource to mint a token and direct transfer to the receiver.
publish the module
We should use create_resource_account_and_publish_package()
function defined in resource_account
module to create resource account and publish pakcage under the resource account.
Let's create a rust project to build the Move package and invoke the function to publish the package.
$ cargo new deploy
Run the above command, then we have a directory with the following file structure:
deploy
├── src
├── main.rs
├── .gitignore
├── Cargo.toml
Add dependencies in the Cargo.toml
file.
[package]
name = "deploy"
version = "0.1.0"
edition = "2021"
[dependencies]
aptos-sdk = { git = "https://github.com/aptos-labs/aptos-core", branch = "mainnet" }
framework = { git = "https://github.com/aptos-labs/aptos-core", branch = "mainnet" }
cached-packages = { git = "https://github.com/aptos-labs/aptos-core", branch = "mainnet" }
hex = "0.4.3"
url = "2.3.1"
anyhow = "1.0"
tokio = { version = "1", features = ["full"] }
In the main.rs
file, we first create a resource account:
let key_bytes =
hex::decode(env::var("DEPLOYER_PRIVATE_KEY").unwrap()).unwrap();
let private_key: Ed25519PrivateKey = (&key_bytes[..]).try_into().unwrap();
let address: AccountAddress = env::var("DEPLOYER_ADDRESS").unwrap().parse().unwrap();
let mut account = LocalAccount::new(address.into(), private_key, 0);
let seed:Vec<u8> = Vec::new();
let resource_address = create_resource_address(account.address(), &seed);
println!("resource account: {:?}", resource_address.to_string());
We read the private key and address of the deployer account from environment and create a resource account of the ddeployeraccount with a seed of type Vec<u8>
.
Then we build the Move package and get the bytecode and metadata of the built package.
let mut build_options = BuildOptions::default();
build_options.named_addresses.
insert("imcoding_nft".to_string(), resource_address);
build_options.named_addresses.
insert("deployer".to_string(), address);
let package = BuiltPackage::build(
PathBuf::from("../"),
build_options,
).expect("building package must succeed");
let code = package.extract_code();
let metadata = package.extract_metadata().
expect("extracting package metadata must succeed");
At last, we generate a create_resource_account_and_publish_package
transaction and publish it to the blockchain.
let payload = aptos_stdlib::resource_account_create_resource_account_and_publish_package(
seed,
bcs::to_bytes(&metadata).expect("package metadata has BCS"),
code,
);
let base_url = env::var("APTOS_NODE_URL").unwrap_or(String::from("http://127.0.0.1:8080"));
let rest_url = Url::parse(&base_url).expect("url must valid");
let client = rest_client::Client::new(rest_url).clone();
let chain_id = client.get_index()
.await
.context("Failed to get chain ID")?
.inner()
.chain_id;
let expiration = SystemTime::now().
duration_since(UNIX_EPOCH).unwrap().as_secs() + 10;
let tx = TransactionBuilder::new(
payload, expiration, ChainId::new(chain_id),
).sender(address).sequence_number(account.sequence_number() + 1).
max_gas_amount(2_00_000);
let signed_tx = account.borrow_mut().sign_with_transaction_builder(tx);
let pending_tx = client.submit(&signed_tx).await.context("failed to submit transfer transaction")?.into_inner();
println!("tx: {:?}!", pending_tx.hash.to_string());
client.wait_for_transaction(&pending_tx).await.context("failed when waiting for the transaction")?;
Let's run the deploy script:
$ export DEPLOYER_ADDRESS=59d8469fa18f5613f72f1b2daa2b9227b2a7229c55b102cfcf86e685b38d9515
$ export DEPLOYER_PRIVATE_KEY=b05f9b88c0a50e56a54df017aaa5e864e232d1d4eba99210f62331c8d99440ce
$ export APTOS_NODE_URL=http://127.0.0.1:8080
$ cargo run
If all goes well, the console will output the resource account and transaction hash.
resource account: "e678abebea551c752030dfe6c78147e62b393b74163a4f167ab3444e0eda55a9"
tx: "0x1233ab41de4c5b8346b7110b3f163ab835c24265bd93c7e2c99cd0b287af9ed2"!
View this transaction in the blockchain explorer. In the Events
tab, we can find a 0x3::token::CreateCollectionEvent
which has the data:
{
"collection_name": "IMCODING NFT Collection",
"creator": "0xe678abebea551c752030dfe6c78147e62b393b74163a4f167ab3444e0eda55a9",
"description": "NFT issued by imcoding.online",
"maximum": "1024",
"uri": "https://imcoding.online/properity/collection.svg"
}
mint nft
Invoke the mint_nft
function in module minting
which publish under the resource account 0xe678...da55a9
to mint the NFT.
$ aptos move run --function-id 0xe678abebea551c752030dfe6c78147e62b393b74163a4f167ab3444e0eda55a9::minting::mint_nft
After successful execution, the transaction will contain an event named 0x3::token::CreateTokenDataEvent
. The event data is roughly as follows:
{
"description": "",
"id": {
"collection": "IMCODING NFT Collection",
"creator": "0xe678abebea551c752030dfe6c78147e62b393b74163a4f167ab3444e0eda55a9",
"name": "IMCODING NFT"
},
"maximum": "1",
"mutability_config": {
"description": false,
"maximum": false,
"properties": true,
"royalty": false,
"uri": false
},
"name": "IMCODING NFT",
"property_keys": [],
"property_types": [],
"property_values": [],
"royalty_payee_address": "0xe678abebea551c752030dfe6c78147e62b393b74163a4f167ab3444e0eda55a9",
"royalty_points_denominator": "1",
"royalty_points_numerator": "0",
"uri": "https://imcoding.online/properity/nft.svg"
}
So far, we have successfully publish an NFT minting module and mint an NFT as a user. The full code can be found here.