# v3

The full v3 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.

<pre class="language-bash"><code class="lang-bash">// The 'leo_nft' program.
<a data-footnote-ref href="#user-content-fn-1">program leo_nft.aleo {</a>
    <a data-footnote-ref href="#user-content-fn-2">// dummy address, ie the aleo address equivalent of 0: aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc</a>
    <a data-footnote-ref href="#user-content-fn-3">struct TokenId {</a>
        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.
    <a data-footnote-ref href="#user-content-fn-4">struct BaseURI {</a>
        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
    }

    <a data-footnote-ref href="#user-content-fn-5">struct SymbolBits {</a>
        data: u128 // The sybmol's ascii text represented in bits, and the u128 value of the bitstring.
    }

    <a data-footnote-ref href="#user-content-fn-6">record NFT {</a>
        private owner: address,
        private data: TokenId,
        private edition: scalar, // which edition of the nft this particular one is -- will be 0 for unique NFTs
    }

    // a way to prove ownership of an nft privately
    <a data-footnote-ref href="#user-content-fn-7">record NFT_ownership {</a>
        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
    }

    <a data-footnote-ref href="#user-content-fn-8">mapping nft_totals: field => u8;</a>
    <a data-footnote-ref href="#user-content-fn-9">mapping nft_owners: field => address;</a>
    <a data-footnote-ref href="#user-content-fn-10">mapping settings: u8 => u128;</a>
    <a data-footnote-ref href="#user-content-fn-11">mapping whitelist: address => u8;</a>

    <a data-footnote-ref href="#user-content-fn-12">transition initialize_collection(</a>
        public total: u128,
        public symbol: u128,
        public base_uri: BaseURI,
    ) {
        <a data-footnote-ref href="#user-content-fn-13">assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);</a>
        <a data-footnote-ref href="#user-content-fn-14">return then finalize(total, symbol, base_uri);</a>
    }

    finalize initialize_collection(
        public total: u128,
        public symbol: u128,
        public base_uri: BaseURI,
    ) {
        // Ensure initialize cannot be called twice!
        let is_initialized: u128 = settings.get_or_use(0u8, 0u128);
        assert_eq(is_initialized, 0u128); 

        <a data-footnote-ref href="#user-content-fn-15">settings.set(0u8, 1u128); // Collection has been initialized</a>
        settings.set(1u8, total); // Number of total NFTs (first-editions) that can be minted
        settings.set(2u8, 0u128); // Is the mint live
        settings.set(3u8, symbol); // Symbol for the NFT
        settings.set(4u8, base_uri.data0); // Base URI for NFT
        settings.set(5u8, base_uri.data1);
        settings.set(6u8, base_uri.data2);
        settings.set(7u8, base_uri.data3);
        settings.set(8u8, 0u128); // Block height for mint to go live (given mint is live setting)
        settings.set(9u8, 0u128); // If collection is frozen
    }
    
    // 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);
        <a data-footnote-ref href="#user-content-fn-16">let tokenHash: field = BHP256::hash_to_field(tokenId);</a>
        let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
        return then finalize(tokenEditionHash);
    }

    finalize add_nft(public tokenEditionHash: field) {
        let frozen: u128 = settings.get(9u8);
        assert_eq(frozen, 0u128);

        let existing_editions: u8 = nft_totals.get_or_use(tokenEditionHash, 255u8);
        assert_eq(existing_editions, 255u8);
        nft_totals.set(tokenEditionHash, 1u8);

        // Reduce the amount of total nfts that can be minted
        let remaining: u128 = settings.get(1u8);
        settings.set(1u8, remaining - 1u128);
    }

    transition add_minter(public minter: address, public amount: u8) {
        assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
        return then finalize(minter, amount);
    }

    finalize add_minter(
        public minter: address,
        public amount: u8
        )
    {
        let frozen: u128 = settings.get(9u8);
        assert_eq(frozen, 0u128);

        whitelist.set(minter, amount);
    }

    transition set_mint_status(public status: u128) {
        assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
        return then finalize(status);
    }

    finalize set_mint_status(public status: u128) {
        let frozen: u128 = settings.get(9u8);
        assert_eq(frozen, 0u128);

        settings.set(2u8, status);
    }

    transition set_mint_block(public mint_block: u128) {
        assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
        return then finalize(mint_block);
    }

    finalize set_mint_block(public mint_block: u128) {
        let frozen: u128 = settings.get(9u8);
        assert_eq(frozen, 0u128);

        settings.set(8u8, mint_block);
    }

    transition update_symbol(public symbol: u128) {
        assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
        return then finalize(symbol);
    }

    finalize update_symbol(public symbol: u128) {
        let frozen: u128 = settings.get(9u8);
        assert_eq(frozen, 0u128);

        settings.set(3u8, 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) {
        let frozen: u128 = settings.get(9u8);
        assert_eq(frozen, 0u128);

        settings.set(4u8, base_uri.data0); // Base URI for NFT
        settings.set(5u8, base_uri.data1);
        settings.set(6u8, base_uri.data2);
        settings.set(7u8, base_uri.data3);
    }

    transition freeze() {
        assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);
        return then finalize();
    }

    finalize freeze() {
        let frozen: u128 = settings.get(9u8);
        assert_eq(frozen, 0u128);

        settings.set(9u8, 1u128);
    }

    <a data-footnote-ref href="#user-content-fn-17">transition mint(public tokenId: TokenId, public edition: scalar) -> NFT {</a>
        let tokenHash: field = BHP256::hash_to_field(tokenId);
        let tokenEditionHash: field = BHP256::commit_to_field(tokenHash, edition);
        return NFT {
            owner: self.caller,
            data: tokenId,
            edition
        } then finalize(self.caller, tokenEditionHash);
    }

    finalize mint(public owner: address, public tokenEditionHash: field) {
        // Ensure mint is live
        let is_live: u128 = settings.get(2u8);
        assert_eq(is_live, 1u128);

        // Ensure owner can mint and decrease remaining mints (overflow protection)
        let remaining: u8 = whitelist.get(owner);
        whitelist.set(owner, remaining - 1u8);

        // Ensure more editions can be made and decrease remaining editions (overflow protection)
        let amount: u8 = nft_totals.get(tokenEditionHash);
        nft_totals.set(tokenEditionHash, amount - 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);
    }

    <a data-footnote-ref href="#user-content-fn-18">transition prove_ownership(private nft: NFT, private prove_to: address) -> (NFT, NFT_ownership) {</a>
        return (
            NFT {
                owner: nft.owner,
                data: nft.data,
                edition: nft.edition
            },
            NFT_ownership {
                owner: prove_to,
                nft_owner: nft.owner,
                data: nft.data,
                edition: nft.edition
            }
        );
    }
}
</code></pre>

[^1]: This defines the program id. In this case, the program id is `leo_nft.aleo`. The program id must be unique to be deployed on-chain.

[^2]: Aleo does not yet support removing values from mappings, so instead, this dummy address is used when an address value should not exist in the mapping. This address was generated by bech32m encoding the integer 0.

[^3]: This struct is the TokenId, which corresponds to the unique identifier for an NFT within its collection. This struct holds data1 and data2, which are the ASCII-encoded representations of the text that identifies the NFT.

[^4]: The base uri struct is the ASCII encoded representation of the base url that contains the NFT collection. The base uri combined with the token id should form a full url, without the preceeding https\://, which the Leo Wallet automatically prefixes the base uri + token id url with.

[^5]: This struct is the ASCII encoded text of the symbol that represents the NFT collection.

[^6]: This is the private representation of an NFT. This record holds the token id struct that uniquely represents the NFT within the collection, as well as the edition scalar. If an NFT is unique within its collection, this edition should be 0. Otherwise, for every copy of the NFT, this edition should be incremented by 1.

[^7]: This record is the result of calling a prove ownership function, which will issue this private record to a receiving address.&#x20;

[^8]: This public mapping has the hash of the token id and scalar as the key, and the amount as a u8.

[^9]: This public mapping has the hash of the token id and the edition as the key, and the owner address as the value.

[^10]: This public mapping holds the various settings for the contract.

[^11]: This public mapping holds the addresses in the whitelist, as well as the amount of NFTs the address can mint. This amount will decrease each time the whitelisted address mints.

[^12]: This function passes the initial state to be state in the public mappings.

[^13]: This assert protects the address from being initialized by an address other than the owner.

[^14]: Finalize calls are executed by a node -- they do not generate a proof in the same way that transitions do. However, if the finalize block fails, the proof will not be valid. We will use this to ensure that only valid proofs are generated.

[^15]: Each of the integers in the mapping represents a different setting. When aleo supports named mapping values, we can update this program to be more readable and user-friendly.

[^16]: Structs cannot be stored in mappings, so we hash the token id struct and then hash that together with the edition scalar to provide a single field element that can be stored in public state.

[^17]: This mint is deterministic -- the NFT to be minted is known before calling this function.

[^18]: This function generates a private record issued to the `prove_to` address so that the ownership of an NFT in this contract's collection can be verified.
