Build an NFT Output
The following code example will:
- Create a
Client
which will connect to the Shimmer Testnet. - Create a
SecretManager
from a mnemonic. - Build an NFT output.
- Build and post a block with the NFT output created in step 3.
Iota.js
You can also find this guide in the native iota.js library
Code Example
- Rust
- Nodejs
- Python
- Java
Dotenv
This example uses dotenv, which is not safe to use in production environments.
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
//! cargo run --example nft --release
use iota_client::{
block::{
address::{Address, NftAddress},
output::{
unlock_condition::{AddressUnlockCondition, UnlockCondition},
BasicOutputBuilder, NftId, NftOutputBuilder, Output, OutputId,
},
payload::{transaction::TransactionEssence, Payload},
},
node_api::indexer::query_parameters::QueryParameter,
request_funds_from_faucet,
secret::SecretManager,
Client, Result,
};
/// In this example we will create an NFT output
#[tokio::main]
async fn main() -> Result<()> {
// This example uses dotenv, which is not safe for use in production!
// Configure your own mnemonic in the ".env" file. Since the output amount cannot be zero, the seed must contain
// non-zero balance.
dotenv::dotenv().ok();
let node_url = std::env::var("NODE_URL").unwrap();
let faucet_url = std::env::var("FAUCET_URL").unwrap();
// Create a client instance.
let client = Client::builder().with_node(&node_url)?.finish()?;
let secret_manager =
SecretManager::try_from_mnemonic(&std::env::var("NON_SECURE_USE_OF_DEVELOPMENT_MNEMONIC_1").unwrap())?;
let token_supply = client.get_token_supply().await?;
let address = client.get_addresses(&secret_manager).with_range(0..1).get_raw().await?[0];
request_funds_from_faucet(&faucet_url, &address.to_bech32(client.get_bech32_hrp().await?)).await?;
tokio::time::sleep(std::time::Duration::from_secs(20)).await;
//////////////////////////////////
// create new nft output
//////////////////////////////////
let outputs = vec![
// address of the owner of the NFT
NftOutputBuilder::new_with_amount(1_000_000, NftId::null())?
.add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(address)))
// address of the minter of the NFT
// .add_feature(Feature::Issuer(IssuerFeature::new(address)))
.finish_output(token_supply)?,
];
let block = client
.block()
.with_secret_manager(&secret_manager)
.with_outputs(outputs)?
.finish()
.await?;
println!(
"Transaction with new NFT output sent: {node_url}/api/core/v2/blocks/{}",
block.id()
);
let _ = client.retry_until_included(&block.id(), None, None).await?;
//////////////////////////////////
// move funds from an NFT address
//////////////////////////////////
let nft_output_id = get_nft_output_id(block.payload().unwrap())?;
let nft_id = NftId::from(&nft_output_id);
let nft_address = NftAddress::new(nft_id);
let bech32_nft_address = Address::Nft(nft_address).to_bech32(client.get_bech32_hrp().await?);
println!("bech32_nft_address {bech32_nft_address}");
println!(
"Faucet request {:?}",
request_funds_from_faucet(&faucet_url, &bech32_nft_address).await?
);
tokio::time::sleep(std::time::Duration::from_secs(20)).await;
let output_ids_response = client
.basic_output_ids(vec![QueryParameter::Address(bech32_nft_address)])
.await?;
let output_response = client.get_output(&output_ids_response.items[0]).await?;
let output = Output::try_from_dto(&output_response.output, token_supply)?;
let block = client
.block()
.with_secret_manager(&secret_manager)
.with_input(nft_output_id.into())?
.with_input(output_ids_response.items[0].into())?
.with_outputs(vec![
NftOutputBuilder::new_with_amount(1_000_000 + output.amount(), nft_id)?
.add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(address)))
.finish_output(token_supply)?,
])?
.finish()
.await?;
println!(
"Transaction with input(basic output) to NFT output sent: {node_url}/api/core/v2/blocks/{}",
block.id()
);
let _ = client.retry_until_included(&block.id(), None, None).await?;
//////////////////////////////////
// burn NFT
//////////////////////////////////
let nft_output_id = get_nft_output_id(block.payload().unwrap())?;
let output_response = client.get_output(&nft_output_id).await?;
let output = Output::try_from_dto(&output_response.output, token_supply)?;
let outputs = vec![
BasicOutputBuilder::new_with_amount(output.amount())?
.add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(address)))
.finish_output(token_supply)?,
];
let block = client
.block()
.with_secret_manager(&secret_manager)
.with_input(nft_output_id.into())?
.with_outputs(outputs)?
.finish()
.await?;
println!("Burn transaction sent: {node_url}/api/core/v2/blocks/{}", block.id());
let _ = client.retry_until_included(&block.id(), None, None).await?;
Ok(())
}
// helper function to get the output id for the first NFT output
fn get_nft_output_id(payload: &Payload) -> Result<OutputId> {
match payload {
Payload::Transaction(tx_payload) => {
let TransactionEssence::Regular(regular) = tx_payload.essence();
for (index, output) in regular.outputs().iter().enumerate() {
if let Output::Nft(_nft_output) = output {
return Ok(OutputId::new(tx_payload.id(), index.try_into().unwrap())?);
}
}
panic!("No nft output in transaction essence")
}
_ => panic!("No tx payload"),
}
}
Run the Example
Run the example by running the following command:
cargo run --example nft --release
Dotenv
This example uses dotenv, which is not safe to use in production environments.
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
import { Client, initLogger } from '@iota/client';
require('dotenv').config({ path: '../.env' });
// Run with command:
// node ./dist/15_build_nft_output.js
// Build an nft output
async function run() {
initLogger();
if (!process.env.NODE_URL) {
throw new Error('.env NODE_URL is undefined, see .env.example');
}
const client = new Client({
nodes: [process.env.NODE_URL],
});
try {
if (!process.env.NON_SECURE_USE_OF_DEVELOPMENT_MNEMONIC_1) {
throw new Error('.env mnemonic is undefined, see .env.example');
}
const secretManager = {
mnemonic: process.env.NON_SECURE_USE_OF_DEVELOPMENT_MNEMONIC_1,
};
const addresses = await client.generateAddresses(secretManager, {
range: {
start: 0,
end: 1,
},
});
const hexAddress = await client.bech32ToHex(addresses[0]);
const nftOutput = await client.buildNftOutput({
nftId: '0x0000000000000000000000000000000000000000000000000000000000000000',
amount: '1000000',
immutableFeatures: [
{
// MetadataFeature
type: 2,
// `hello` hex encoded
data: '0x68656c6c6f',
},
],
unlockConditions: [
{
type: 0,
address: {
type: 0,
pubKeyHash: hexAddress,
},
},
],
});
console.log(nftOutput);
} catch (error) {
console.error('Error: ', error);
}
}
run();
You can run the example by running the following command from the bindings/node/examples/
folder:
node dist/15_build_nft_output.js
from iota_client import IotaClient, MnemonicSecretManager
# Create an IotaClient instance
client = IotaClient({'nodes': ['https://api.testnet.shimmer.network']})
secret_manager = MnemonicSecretManager("flame fever pig forward exact dash body idea link scrub tennis minute " +
"surge unaware prosper over waste kitten ceiling human knife arch situate civil")
nft_output = client.build_nft_output(
unlock_conditions=[
{
"type": 0,
"address": {
"type": 0,
"pubKeyHash": client.bech32_to_hex("rms1qzpf0tzpf8yqej5zyhjl9k3km7y6j0xjnxxh7m2g3jtj2z5grej67sl6l46"),
},
},
],
# Nft Id needs to be set to 0 when minting
nft_id="0x0000000000000000000000000000000000000000000000000000000000000000",
amount='1000000',
immutable_features=[
{
"type": 2,
# `hello` hex encoded
"data": "0x68656c6c6f"
}
],
features=[
{
"type": 2,
# `hello` hex encoded
"data": "0x68656c6c6f"
}
]
)
# Create and post a block with the nft output
block = client.build_and_post_block(secret_manager, {"outputs": [nft_output]})
print(f'{block}')
You can run the example by running the following command from the binding/python/examples
folder:
python3 10_mint_nft.py
import org.iota.Client;
import org.iota.types.ClientConfig;
import org.iota.types.expections.ClientException;
import org.iota.types.Output;
import org.iota.types.UnlockCondition;
import org.iota.types.expections.InitializeClientException;
import org.iota.types.ids.NftId;
import org.iota.types.output_builder.NftOutputBuilderParams;
import org.iota.types.secret.GenerateAddressesOptions;
import org.iota.types.secret.MnemonicSecretManager;
import org.iota.types.secret.Range;
public class BuildNftOutput {
public static void main(String[] args) throws ClientException, InitializeClientException {
// Build the client.
Client client = new Client(new ClientConfig().withNodes(new String[]{"https://api.testnet.shimmer.network"}));
// Configure a simple NFT output.
MnemonicSecretManager secretManager = new MnemonicSecretManager("endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river");
String hexAddress = client.bech32ToHex(client.generateAddresses(secretManager, new GenerateAddressesOptions().withRange(new Range(0, 1)))[0]);
NftId nftId = new NftId("0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3");
UnlockCondition[] unlockConditions = new UnlockCondition[]{new UnlockCondition("{ type: 0, address: { type: 0, pubKeyHash: \"" + hexAddress + "\" } }")};
NftOutputBuilderParams params = new NftOutputBuilderParams()
.withNftId(nftId)
.withUnlockConditions(unlockConditions);
// Build the output.
Output output = client.buildNftOutput(params);
// Print the output.
System.out.println(output.toString());
}
}
Expected Output
- Rust
- Nodejs
- Python
- Java
Transaction with new NFT output sent: https://api.testnet.shimmer.network/api/core/v2/blocks/0xcfc8599956396ec01a0f4fed2f218f10f43876df28b3e9115d3695f99a536a4b
bech32_nft_address rms1zzke7q909dzj95v6nlcdlafz0ylc6dux5a2qwenq0xfuc9hcr8h76spvz3f
{
type: 6,
amount: '1000000',
nftId: '0x0000000000000000000000000000000000000000000000000000000000000000',
unlockConditions: [ { type: 0, address: [Object] } ],
immutableFeatures: [ { type: 2, data: '0x68656c6c6f' } ]
}
[
'0xa05ab7a0ab2842dedda5bd788b30ad98e32e32a35951896093de40833e9719fa',
{
'protocolVersion': 2,
'parents': [
'0x5031f5dd9defa4a663c33183f32259e35013d940b8453cb2f985f49363a905af',
'0x9a1692a4976bafb78318cdfcb0277e12f80f0db1fc441765503095ec6d326071',
'0xaeb154db6b9f32cd56bb622f209dec6565645296650c132c957d31139dd01ded',
'0xd28cfe9950c7a7be8eceb921107fc5fb6ac5c67013e59ea23e30a39d45a459f2'
],
'payload': {
'type': 6,
'essence': {
'type': 1,
'networkId': '1856588631910923207',
'inputs': [
{
'type': 0,
'transactionId': '0x434e6b652e801b80841d72f29ba7ea82c6222118c8e18177e16225e7e3668252',
'transactionOutputIndex': 0
},
{
'type': 0,
'transactionId': '0x470d590d4a747e52db3e7d908a732311b10ffebe5cad65030c8b7a57484ba125',
'transactionOutputIndex': 0
}
],
'inputsCommitment': '0x6dd5e1990b10aa6cd458cf88d0febfbe5d4b97ab50ead3059a6f1338e93f6892',
'outputs': [
{
'type': 6,
'amount': '1000000',
'nftId': '0x0000000000000000000000000000000000000000000000000000000000000000',
'unlockConditions': [
{
'type': 0,
'address': {
'type': 0,
'pubKeyHash': '0x8297ac4149c80cca8225e5f2da36df89a93cd2998d7f6d488c97250a881e65af'
}
}
],
'features': [
{
'type': 2,
'data': '0x68656c6c6f'
}
],
'immutableFeatures': [
{
'type': 2,
'data': '0x68656c6c6f'
}
]
},
{
'type': 3,
'amount': '50600',
'unlockConditions': [
{
'type': 0,
'address': {
'type': 0,
'pubKeyHash': '0x8297ac4149c80cca8225e5f2da36df89a93cd2998d7f6d488c97250a881e65af'
}
}
]
}
]
},
'unlocks': [
{
'type': 0,
'signature': {
'type': 0,
'publicKey': '0xe62838fda7e8b77bf80e49967f0f089ae2a7230547d5231649732952f6336fae',
'signature': '0x044689897d08227e0134489c4c29398e8ae745727a99dcad7ceb36e4228a0b9a71939ece341f17c6d62c9bef93f9fc72c426c4fae159dcca24bdfed1182e3903'
}
},
{
'type': 1,
'reference': 0
}
]
},
'nonce': '13835058055282177567'
}
]
{
"type": 6,
"amount": "45900",
"nftId": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3",
"unlockConditions": [
{
"type": 0,
"address": {
"type": 0,
"pubKeyHash": "0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3"
}
}
]
}