v4
Last updated
Last updated
The full v4 NFT program is shown below and fully annotated. The code is explained so that you may understand and modify this program for your own use.
// The 'leo_nft' program.
data1: u128, // Part 1 of the image data -- i.e., a way to link this nft to image or aws
data2: u128,
}
// base uri ascii bits. Include as many data pieces as necessary to encapsulate the uri. Padded with 0s at the end.
data0: u128, // Part 1 of the base uri in bits. Bits should be the representation of the hexadecimal bytes for the ASCII text of the URL
data1: u128,
data2: u128,
data3: u128
}
data: u128 // The sybmol's ascii text represented in bits, and the u128 value of the bitstring.
}
private owner: address,
private data: TokenId,
private edition: scalar, // which edition of the nft this particular one is -- will be 0 for unique NFTs
}
private owner: address,
private amount: u8,
}
private owner: address,
private claim: field
}
// a way to prove ownership of an nft privately
private owner: address,
private nft_owner: address,
private data: TokenId,
private edition: scalar, // which edition of the nft this particular one is -- will be 0 for unique NFTs
}
;
// keys: setting index
// values: setting value
// keys: index of the nft to mint
// values: hash of the token id + edition
// keys: fields that represent claims for having minted an nft
// values: fields that are the hash of the nft that was minted
// keys: just two, 0u8 which corresponds to different on/off settings for the contract, and 1u8
// which corresponds to the mint block height.
// values: the bitstring that represents the settings that can be toggled
// in order of least significant bit index to most significant bit:
// 0: collection has been initialized
// 1: can minters mint
// 2: do minters have to have a mint record (i.e. is the private whitelist a requirement)
// 3: is the collection frozen
public total: u128,
public symbol: u128,
public base_uri: BaseURI,
) {
}
finalize initialize_collection(
public total: u128,
public symbol: u128,
public base_uri: BaseURI,
) {
// Ensure initialize cannot be called twice!
let toggle_settings_status: u32 = toggle_settings.get_or_use(0u8, 0u32);
assert_eq(is_initialized, 0u32);
general_settings.set(0u8, 0u128); // number of mintable NFTs (all editions)
general_settings.set(1u8, total); // Number of total NFTs (first-editions) that can be minted
general_settings.set(2u8, symbol); // Symbol for the NFT
general_settings.set(3u8, base_uri.data0); // Base URI for NFT
general_settings.set(4u8, base_uri.data1);
general_settings.set(5u8, base_uri.data2);
general_settings.set(6u8, base_uri.data3);
// initialized flag = 0b0000...0001 = 1u32
// minting flag = 0b0000...0010 = 2u32
// whitelist flag = 0b0000...0100 = 4u32
// frozen flag = 0b0000...1000 = 8u32
// defaults -- not frozen, whitelist required, not minting, initialized
// 0b0000...0101 = 5u32.
}
// Load the data into the mapping
// Enables someone to mint an NFT with provided image data
transition add_nft(public tokenId: TokenId, public edition: scalar) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
return then finalize(tokenEditionHash);
}
finalize add_nft(public tokenEditionHash: field) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
// Reduce the amount of total nfts that can be initialized
let remaining: u128 = general_settings.get(1u8);
general_settings.set(1u8, remaining - 1u128);
// Add this NFT to the mintable NFT collection
let mintable_num: u128 = general_settings.get(0u8);
nfts_to_mint.set(mintable_num, tokenEditionHash);
general_settings.set(0u8, mintable_num + 1u128);
}
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return NFT_mint {
owner: minter,
amount,
} then finalize();
}
finalize add_minter()
{
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
}
// call this function to toggle minting, the whitelist requirement, or to permanently freeze the contract
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(toggle_settings_bitstring);
}
finalize update_toggle_settings(public toggle_settings_bitstring: u32) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
// Ensure updated settings are not uninitializing the collection.
let still_initialized: u32 = toggle_settings_bitstring & 1u32;
assert_eq(still_initialized, 1u32);
toggle_settings.set(0u8, toggle_settings_bitstring);
}
transition set_mint_block(public mint_block: u32) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(mint_block);
}
finalize set_mint_block(public mint_block: u32) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
assert_eq(is_initialized_and_not_frozen, 1u32);
toggle_settings.set(1u8, mint_block);
}
transition update_symbol(public symbol: u128) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(symbol);
}
finalize update_symbol(public symbol: u128) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
general_settings.set(2u8, symbol);
}
transition update_base_uri(public base_uri: BaseURI) {
assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
return then finalize(base_uri);
}
finalize update_base_uri(public base_uri: BaseURI) {
// Ensure collection is initialized and not frozen.
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// initialized & frozen flags = 0b0000...1001 = 9u32
// what the settings should be = 0b0000...0001 = 1u32
let is_initialized_and_not_frozen: u32 = toggle_settings_status & 9u32;
assert_eq(is_initialized_and_not_frozen, 1u32);
general_settings.set(3u8, base_uri.data0); // Base URI for NFT
general_settings.set(4u8, base_uri.data1);
general_settings.set(5u8, base_uri.data2);
general_settings.set(6u8, base_uri.data3);
}
// CAUTION: If the minter selects the same hiding nonce,
// that minter will not be able to mint all of their NFTs without claiming some first.
// Additionally, some privacy will be lost as the claim is a deterministic hash and is held in public state.
let address_hash: field = BHP256::hash_to_field(self.caller);
return NFT_claim {
owner: self.caller,
claim
} then finalize(claim);
}
finalize open_mint(public claim: field) {
// Ensure mint block height is less than current block height
let mint_block: u32 = toggle_settings.get(1u8);
let passed_height_check: bool = mint_block <= block.height;
assert_eq(passed_height_check, true);
// Ensure collection is not frozen, whitelist is not required, minting is allowed, and is initialized,
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// frozen & whitelist & minting & initialized flags = 0b0000...1111 = 15u32
// what the settings should be = 0b0000...0011 = 3u32
let collection_settings_met: u32 = toggle_settings_status & 15u32;
assert_eq(collection_settings_met, 3u32);
// Ensure this claim has not already been made
let existing_claim: field = claims_to_nfts.get_or_use(claim, 0field);
assert_eq(existing_claim, 0field);
// Randomly select an NFT to mint
let old_mintable_sum: u128 = general_settings.get_or_use(0u8, 0u128);
let randomIndex: u128 = randomNum % old_mintable_sum;
let tokenEditionHash: field = nfts_to_mint.get(randomIndex);
claims_to_nfts.set(claim, tokenEditionHash);
// Decrease the number of mintable nfts
let new_mintable_num: u128 = old_mintable_sum - 1u128;
general_settings.set(0u8, new_mintable_num);
// Replace the minted nft from the mintable nfts with the last mintable nft.
// This is done to ensure that the minted nft is not minted again.
// If the minted nft was the last mintable nft, it still won't be minted again because the next random index must stay
// within the bounds of the mintable nfts, set by the new mintable num.
nfts_to_mint.set(randomIndex, nfts_to_mint.get(new_mintable_num));
}
// CAUTION: For security purposes, the hiding nonce should be unique for each mint.
let address_hash: field = BHP256::hash_to_field(self.caller);
let claim: field = BHP256::commit_to_field(address_hash, hiding_nonce);
// overflow protection for minting
return (
NFT_mint {
owner: nft_mint.owner,
amount: nft_mint.amount - 1u8
},
NFT_claim {
owner: nft_mint.owner,
claim
}) then finalize(claim);
}
finalize mint(public claim: field) {
// Ensure mint block height is less than current block height
let mint_block: u32 = toggle_settings.get(1u8);
let passed_height_check: bool = mint_block <= block.height;
assert_eq(passed_height_check, true);
// Ensure collection is not frozen, minting is allowed, and is initialized,
let toggle_settings_status: u32 = toggle_settings.get(0u8);
// frozen & minting & initialized flags = 0b0000...1011 = 11u32
// what the settings should be = 0b0000...0011 = 3u32
let is_initialized_and_not_frozen_and_minting: u32 = toggle_settings_status & 11u32;
assert_eq(is_initialized_and_not_frozen_and_minting, 3u32);
// Ensure this claim has not already been made
let existing_claim: field = claims_to_nfts.get_or_use(claim, 0field);
assert_eq(existing_claim, 0field);
// Randomly select an NFT to mint
let randomNum: u128 = ChaCha::rand_u128();
let old_mintable_sum: u128 = general_settings.get_or_use(0u8, 0u128);
let randomIndex: u128 = randomNum % old_mintable_sum;
let tokenEditionHash: field = nfts_to_mint.get(randomIndex);
claims_to_nfts.set(claim, tokenEditionHash);
// Decrease the number of mintable nfts
let new_mintable_num: u128 = old_mintable_sum - 1u128;
general_settings.set(0u8, new_mintable_num);
// Replace the minted nft from the mintable nfts with the last mintable nft.
// This is done to ensure that the minted nft is not minted again.
// If the minted nft was the last mintable nft, it still won't be minted again because the next random index must stay
// within the bounds of the mintable nfts, set by the new mintable num.
nfts_to_mint.set(randomIndex, nfts_to_mint.get(new_mintable_num));
}
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
return NFT {
owner: nft_claim.owner,
data: tokenId,
edition
} then finalize(nft_claim.claim, tokenEditionHash);
}
finalize claim_nft(public claim: field, tokenEditionHash: field) {
// ensure that the claimed nft matches the claim
let claimedNFT: field = claims_to_nfts.get(claim);
assert_eq(claimedNFT, tokenEditionHash);
}
// Proof that you own an nft without revealing the nft.
// this burn never actually destroys the nft as the finalize
// block is guaranteed to fail.
nft: NFT,
nonce: u64.public
)
{
return then finalize();
}
finalize authorize(
)
{
// fails on purpose, so that the nft is not burned.
assert_eq(0u8, 1u8);
}
transition transfer_private(
nft: NFT,
private receiver: address
) -> NFT
{
return NFT {
owner: receiver,
data: nft.data,
edition: nft.edition
};
}
transition transfer_public(
private receiver: address,
private data: TokenId,
private edition: scalar
)
{
let tokenHash: field = BHP256::hash_to_field(data);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
let caller: address = self.caller;
return then finalize(receiver, tokenEditionHash, caller);
}
finalize transfer_public(
public receiver: address,
public tokenEditionHash: field,
public caller: address
)
{
assert_eq(caller, nft_owners.get(tokenEditionHash));
nft_owners.set(tokenEditionHash, receiver);
}
transition convert_private_to_public(
nft: NFT
)
{
let tokenHash: field = BHP256::hash_to_field(nft.data);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, nft.edition);
return then finalize(nft.owner, tokenEditionHash);
}
finalize convert_private_to_public(
public owner: address,
public tokenEditionHash: field
)
{
nft_owners.set(tokenEditionHash, owner);
}
transition convert_public_to_private(
private owner: address,
private data: TokenId,
private edition: scalar
) -> NFT
{
assert_eq(owner, self.caller);
let tokenHash: field = BHP256::hash_to_field(data);
let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
return NFT {
owner,
data,
edition
} then finalize(owner, tokenEditionHash);
}
finalize convert_public_to_private(
public owner: address,
public tokenEditionHash: field
)
{
assert_eq(owner, nft_owners.get(tokenEditionHash));
// mapping::remove is not implemented yet, so instead we set the owner to be a dummy address that cannot publicly transfer or convert to private
nft_owners.set(tokenEditionHash, aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc);
}
}