# v4

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.

<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 data-footnote-ref href="#user-content-fn-7">record NFT_mint {</a>
        private owner: address,
        private amount: u8,
    }

    <a data-footnote-ref href="#user-content-fn-8">record NFT_claim {</a>
        private owner: address,
        private claim: field
    }

    // a way to prove ownership of an nft privately
    <a data-footnote-ref href="#user-content-fn-9">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-10">mapping nft_owners: field => address</a>;

    // keys: setting index
    // values: setting value
    <a data-footnote-ref href="#user-content-fn-11">mapping general_settings: u8 => u128;</a>

    // keys: index of the nft to mint
    // values: hash of the token id + edition
    <a data-footnote-ref href="#user-content-fn-12">mapping nfts_to_mint: u128 => field;</a>

    // keys: fields that represent claims for having minted an nft
    // values: fields that are the hash of the nft that was minted
    <a data-footnote-ref href="#user-content-fn-13">mapping claims_to_nfts: field => field;</a>

    // 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
    <a data-footnote-ref href="#user-content-fn-14">mapping toggle_settings: u8 => u32;</a>

    <a data-footnote-ref href="#user-content-fn-15">transition initialize_collection(</a>
        public total: u128,
        public symbol: u128,
        public base_uri: BaseURI,
    ) {
        <a data-footnote-ref href="#user-content-fn-16">assert_eq(self.caller, aleo1gy3d0s00s2k7rmgqznnx2q8htmjm2p5rk8q40u5yklqhe44utvys0dmzdy);</a>
        <a data-footnote-ref href="#user-content-fn-17">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 toggle_settings_status: u32 = toggle_settings.get_or_use(0u8, 0u32);
        <a data-footnote-ref href="#user-content-fn-18">let is_initialized: u32 = toggle_settings_status &#x26; 1u32;</a>
        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.
        <a data-footnote-ref href="#user-content-fn-19">toggle_settings.set(0u8, 5u32);</a>
        <a data-footnote-ref href="#user-content-fn-20">toggle_settings.set(1u8, 0u32); // block height when mint is allowed</a>
    }
    
    // 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-21">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) {
        // Ensure collection is initialized and not frozen.
        let toggle_settings_status: u32 = toggle_settings.get(0u8);
        // initialized &#x26; frozen flags = 0b0000...1001 = 9u32
        // what the settings should be = 0b0000...0001 = 1u32
        let is_initialized_and_not_frozen: u32 = toggle_settings_status &#x26; 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);
    }

    <a data-footnote-ref href="#user-content-fn-22">transition add_minter(private minter: address, public amount: u8) -> NFT_mint {</a>
        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 &#x26; frozen flags = 0b0000...1001 = 9u32
        // what the settings should be = 0b0000...0001 = 1u32
        let is_initialized_and_not_frozen: u32 = toggle_settings_status &#x26; 9u32;
        assert_eq(is_initialized_and_not_frozen, 1u32);
    }

    // call this function to toggle minting, the whitelist requirement, or to permanently freeze the contract
    <a data-footnote-ref href="#user-content-fn-23">transition update_toggle_settings(public toggle_settings_bitstring: u32) {</a>
        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 &#x26; frozen flags = 0b0000...1001 = 9u32
        // what the settings should be = 0b0000...0001 = 1u32
        let is_initialized_and_not_frozen: u32 = toggle_settings_status &#x26; 9u32;
        assert_eq(is_initialized_and_not_frozen, 1u32);

        // Ensure updated settings are not uninitializing the collection.
        let still_initialized: u32 = toggle_settings_bitstring &#x26; 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 &#x26; frozen flags = 0b0000...1001 = 9u32
        // what the settings should be = 0b0000...0001 = 1u32
        <a data-footnote-ref href="#user-content-fn-24">let is_initialized_and_not_frozen: u32 = toggle_settings_status &#x26; 9u32;</a>
        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 &#x26; frozen flags = 0b0000...1001 = 9u32
        // what the settings should be = 0b0000...0001 = 1u32
        let is_initialized_and_not_frozen: u32 = toggle_settings_status &#x26; 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 &#x26; frozen flags = 0b0000...1001 = 9u32
        // what the settings should be = 0b0000...0001 = 1u32
        let is_initialized_and_not_frozen: u32 = toggle_settings_status &#x26; 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);
    }

   <a data-footnote-ref href="#user-content-fn-25"> transition open_mint(private hiding_nonce: scalar) -> NFT_claim {</a>
        // 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);
        <a data-footnote-ref href="#user-content-fn-26">let claim: field = BHP256::commit_to_field(address_hash, hiding_nonce);</a>
        return NFT_claim {
            owner: self.caller,
            claim
        } then finalize(claim);
    }

    <a data-footnote-ref href="#user-content-fn-27">// note, much of this code should be combined in a closure, but finalize closures are not yet stable on aleo.</a>
    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 &#x3C;= 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 &#x26; whitelist &#x26; minting &#x26; initialized flags = 0b0000...1111 = 15u32
        // what the settings should be = 0b0000...0011 = 3u32
        let collection_settings_met: u32 = toggle_settings_status &#x26; 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
        <a data-footnote-ref href="#user-content-fn-28">let randomNum: u128 = ChaCha::rand_u128();</a>
        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));
    }

    <a data-footnote-ref href="#user-content-fn-29">transition mint(nft_mint: NFT_mint, private hiding_nonce: scalar) -> (NFT_mint, NFT_claim) {</a>
        // 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 &#x3C;= 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 &#x26; minting &#x26; 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 &#x26; 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));
    }

    <a data-footnote-ref href="#user-content-fn-30">transition claim_nft(nft_claim: NFT_claim, private tokenId: TokenId, private edition: scalar) -> NFT {</a>
        <a data-footnote-ref href="#user-content-fn-31">let tokenHash: field = BHP256::hash_to_field(tokenId);</a>
        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);
        <a data-footnote-ref href="#user-content-fn-32">claims_to_nfts.set(claim, 0field);</a>
    }

    // 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.
    <a data-footnote-ref href="#user-content-fn-33">transition authorize(</a>
        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);
    }
}
</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 allows the owner to mint on the whitelist. This is a key difference from v3 -- the whitelist is made by sending private mint records to addresses that should be on the whitelist.

[^8]: After privately minting an NFT, this claim record is issued to the minter. The NFT is not yet in a private record form owned by the minter, but the claim allows the minter to anonymously convert the public NFT to a private NFT record.

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

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

[^11]: This public mapping holds various general settings for the contract, like the base uri.

[^12]: This public mapping holds the token id + edition hashes in indices incrementing from 0 to the number of mintable NFTs. This is used to ensure that the random mint process selects a valid NFT to mint.

[^13]: This public state mapping holds the claims that have been set for minted NFTs. These NFTs are held in public state and are redeemable by the claim record issued to the minter. The claim field value is not tied to an individual address though, so the minter remains private.

[^14]: This public state mapping holds toggle settings and the mint block number. The toggle settings are stored in a u32 bitstring, with 0 meaning off and 1 meaning on for a given setting. See the comments for which index corresponds to which setting.

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

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

[^17]: 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.

[^18]: By fetching the settings and asserting they match what we expect, we can prevent improper calls of functions and safeguard the program.

[^19]: The bitstring 0101 is the default setting for v4 -- the contract is not frozen, the whitelist is a requirement to mint, minting is not turned on, and the contract is initialized.

[^20]: This toggle setting is for the block height when minting is allowed. The minting flag and the block height must both be valid for a mint to proceed.

[^21]: 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.

[^22]: For minting that requires a whitelist, this function must be called. A mint record is issued to the address to be included in the whitelist.

[^23]: This is another key difference from v3 -- all the toggleable settings (binary on/off) can be set at a single time by calling this function.

[^24]: This is an example of a contract protection with the toggle settings. By bitwise and-ing the current values with the allowed values, we can ensure the contract is in the correct state to update the mint block.

[^25]: v4 allows for open minting or whitelist minting, depending on the whitelist requirement set in the toggle settings. If open minting is allowed, a user does not need a mint record.

[^26]: The claim is a way for a minter to stake their claim to a minted NFT. The claim is a hash of the minter's address and a hiding nonce, which is a scalar. The purpose of the hiding nonce is to make the hash pseudo-random, and not tied to a particular address. Consider a minter claiming multiple NFTs without using a hiding nonce -- other people could view the public state and glean information that a single minter has claimed multiple NFTs.

[^27]: Aleo is continuously improving, but at the time of contract deployment, finalize closures were not stable -- the following code should be organized into a closure to reduce repeated code.

[^28]: This line spits out a random u128. Note, this is vulnerable to censorship by the node running the finalize block, but until a randomness oracle is deployed on-chain, this represents an improvement over v3.

[^29]: This function is very much the same as open\_mint, except a mint record is required.

[^30]: For both open minting and whitelist minting, once an NFT is minted by a user, they have a claim record. Their claim is mapped to a random NFT that was minted for them, and that claim record must be spent to "redeem" the NFT.

[^31]: The token id must correspond to the NFT that the claimer had randomly minted.

[^32]: The 0field is used here to null out the claim mapping. Once remove is fully supported, we will instead simply remove the key-value pair from the mapping.

[^33]: This is a novel pair of functions that we introduce in v4. This generates an inclusion proof that can be used off-chain to show ownership of the NFT record without revealing which NFT. Additionally, the finalize will always fail so that the NFT is not burned when calling this method.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://art.privacypride.com/program-walkthrough/v4.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
