Module Overview
Module Name LicenseContract
Module Reference 10e567a6d7
Verification Status Verified
Age 17 days
Sender AesirX
More Info
Deployed in block 27,532,954
Deployed in tx d09c
Methods 12
Contracts 1
Module Build Info
Verification Status:Verified
Build Image Used:docker.io/concordium/verifiable-sc:1.74.1
Build Command Used:cargo --locked build --target wasm32-unknown-unknown --release --target-dir /b/t
Archive Hash:11bf708848b715d58336b4dca1d38fc68359f38197490602998b661d4a30bba8
Link to Source Code:Source Code
Explanation:Source and module match.
Module Source Code
//! A NFT smart contract example using the Concordium Token Standard CIS2.
//!
//! # Description
//! An instance of this smart contract can contain a number of different token
//! each identified by a token ID. A token is then globally identified by the
//! contract address together with the token ID.
//!
//! In this example the contract is initialized with no tokens, and tokens can
//! be minted through a `mint` contract function, which will only succeed for
//! the contract owner. No functionality to burn token is defined in this
//! example.
//!
//! Note: The word 'address' refers to either an account address or a
//! contract address.
//!
//! As follows from the CIS2 specification, the contract has a `transfer`
//! function for transferring an amount of a specific token type from one
//! address to another address. An address can enable and disable one or more
//! addresses as operators. An operator of some address is allowed to transfer
//! any tokens owned by this address.

#![cfg_attr(not(feature = "std"), no_std)]

// extern crate alloc;
// use alloc::vec::Vec;
// use bs58;

use concordium_cis2::*;
use concordium_std::*;

/// The baseurl for the token metadata, gets appended with the token ID as hex
/// encoding before emitted in the TokenMetadata event.
const TOKEN_METADATA_BASE_URL: &str = "https://web3id.backend.aesirx.io:8001/licenses/";

const OFFICIAL_OWNER_BYTES: [u8; 32] = [
    0xba, 0x9f, 0x01, 0xbe, 0x1d, 0xb2, 0x58, 0x65,
    0xd1, 0x4d, 0x2f, 0x4b, 0x9e, 0xb7, 0x0c, 0x3f,
    0x84, 0x03, 0xfb, 0xe1, 0xd6, 0xab, 0x26, 0xa5,
    0x6d, 0xcd, 0x8d, 0x9e, 0x2f, 0x90, 0x9f, 0xf4,
];
const OFFICIAL_OWNER: AccountAddress = AccountAddress(OFFICIAL_OWNER_BYTES);

/// List of supported standards by this contract address.
const SUPPORTS_STANDARDS: [StandardIdentifier<'static>; 2] =
    [CIS0_STANDARD_IDENTIFIER, CIS2_STANDARD_IDENTIFIER];

// Types

/// Contract token ID type.
/// To save bytes we use a token ID type limited to a `u32`.
type ContractTokenId = TokenIdU32;

/// Contract token amount.
/// Since the tokens are non-fungible the total supply of any token will be at
/// most 1 and it is fine to use a small type for representing token amounts.
type ContractTokenAmount = TokenAmountU8;

// Web3Id, essentially a string
type Web3Id = String;

#[derive(Debug, Serialize, Clone, SchemaType)]
pub struct TokenMetadata {
    /// The URL following the specification RFC1738.
    #[concordium(size_length = 2)]
    pub url: String,
    /// A optional hash of the content.
    #[concordium(size_length = 2)]
    pub hash: String,
}

/// The parameter for the contract function `mint` which mints a token to a given address
#[derive(Serial, Deserial, SchemaType)]
struct MintParams {
    /// Owner of the newly minted token.
    owner: AccountAddress,
    /// Token
    token: ContractTokenId,
    /// Web3Id
    web3id: Web3Id,
}

/// Parameter type for the burn function
#[derive(Serial, Deserial, SchemaType)]
struct BurnParams {
    token_id: ContractTokenId,
    owner: Address,
    amount: ContractTokenAmount,
}

/// The state for each address.
#[derive(Serial, DeserialWithState, Deletable)]
#[concordium(state_parameter = "S")]
struct AddressState<S> {
    /// The tokens owned by this address.
    owned_tokens: StateSet<ContractTokenId, S>,
    /// The address which are currently enabled as operators for this address.
    operators: StateSet<Address, S>,
}

impl<S: HasStateApi> AddressState<S> {
    fn empty(state_builder: &mut StateBuilder<S>) -> Self {
        AddressState {
            owned_tokens: state_builder.new_set(),
            operators: state_builder.new_set(),
        }
    }
}

/// The contract state.
// Note: The specification does not specify how to structure the contract state
// and this could be structured in a more space efficient way depending on the use case.
#[derive(Serial, DeserialWithState)]
#[concordium(state_parameter = "S")]
struct State<S> {
    /// The state for each address.
    state: StateMap<Address, AddressState<S>, S>,
    /// All of the token IDs
    all_tokens: StateSet<ContractTokenId, S>,
    /// Map with contract addresses providing implementations of additional
    /// standards.
    implementors: StateMap<StandardIdentifierOwned, Vec<ContractAddress>, S>,
    // Metadata
    metadata: StateMap<ContractTokenId, TokenMetadata, S>,
    // Valid global operators for minting
    operators: StateSet<Address, S>,
    /// The owner of the contract
    owner: Address,
}

/// The parameter type for the contract function `setImplementors`.
/// Takes a standard identifier and list of contract addresses providing
/// implementations of this standard.
#[derive(Debug, Serialize, SchemaType)]
struct SetImplementorsParams {
    /// The identifier for the standard.
    id: StandardIdentifierOwned,
    /// The addresses of the implementors of the standard.
    implementors: Vec<ContractAddress>,
}

/// The custom errors the contract can produce.
#[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType)]
enum CustomContractError {
    /// Failed parsing the parameter.
    #[from(ParseError)]
    ParseParams,
    /// Failed logging: Log is full.
    LogFull,
    /// Failed logging: Log is malformed.
    LogMalformed,
    /// Failing to mint new tokens because one of the token IDs already exists
    /// in this contract.
    TokenIdAlreadyExists,
    /// Failed to invoke a contract.
    InvokeContractError,
    // Invalid Web3 ID
    InvalidWeb3Id,
    /// License not found
    LicenseNotFound,
    Unauthorized,
}

/// Wrapping the custom errors in a type with CIS2 errors.
type ContractError = Cis2Error<CustomContractError>;

type ContractResult<A> = Result<A, ContractError>;

/// Mapping the logging errors to CustomContractError.
impl From<LogError> for CustomContractError {
    fn from(le: LogError) -> Self {
        match le {
            LogError::Full => Self::LogFull,
            LogError::Malformed => Self::LogMalformed,
        }
    }
}

/// Mapping errors related to contract invocations to CustomContractError.
impl<T> From<CallContractError<T>> for CustomContractError {
    fn from(_cce: CallContractError<T>) -> Self {
        Self::InvokeContractError
    }
}

/// Mapping CustomContractError to ContractError
impl From<CustomContractError> for ContractError {
    fn from(c: CustomContractError) -> Self {
        Cis2Error::Custom(c)
    }
}

fn build_token_metadata_url(token_id: &ContractTokenId) -> String {
    // Convert from little-endian to big-endian (natural order)
    let token_value = token_id.0.swap_bytes();
    // Format as an 8-digit hexadecimal string (lowercase) with leading zeros.
    format!("{}{:08x}", TOKEN_METADATA_BASE_URL, token_value)
}

// Functions for creating, updating and querying the contract state.
impl<S: HasStateApi> State<S> {
    /// Creates a new state with no tokens and a specified owner.
    fn empty(state_builder: &mut StateBuilder<S>, owner: Address) -> Self {
        State {
            state: state_builder.new_map(),
            all_tokens: state_builder.new_set(),
            implementors: state_builder.new_map(),
            metadata: state_builder.new_map(),
            operators: state_builder.new_set(),
            owner,
        }
    }

    /// Internal burn helper function. Invokes the burn functionality of the state.
/// Logs a Burn event. The function assumes that the burn is authorized.
    fn burn(
        &mut self,
        token: &ContractTokenId,
        owner: &Address,
    ) -> ContractResult<()> {
        ensure!(self.contains_token(token), ContractError::InvalidTokenId);

        if let Some(mut address_state) = self.state.get_mut(owner) {
            ensure!(
                address_state.owned_tokens.remove(token),
                ContractError::InsufficientFunds
            );
        } else {
            bail!(ContractError::InsufficientFunds)
        }

        // Remove token from all tokens
        self.all_tokens.remove(token);
        
        // Remove token metadata
        self.metadata.remove(token);

        Ok(())
    }


    /// Mint a new token with a given address as the owner
    fn mint(
        &mut self,
        token: ContractTokenId,
        metadata_url: &String,
        owner: &Address,
        state_builder: &mut StateBuilder<S>,
    ) -> ContractResult<()> {
        ensure!(
            self.all_tokens.insert(token),
            CustomContractError::TokenIdAlreadyExists.into()
        );

        let metadata_url = build_token_metadata_url(&token);
        let metadata = TokenMetadata {
            url: metadata_url,
            hash: String::from(""),
        };

        self.metadata.insert(token, metadata.clone());

        let mut owner_state = self
            .state
            .entry(*owner)
            .or_insert_with(|| AddressState::empty(state_builder));
        owner_state.owned_tokens.insert(token);
        Ok(())
    }

    /// Check that the token ID currently exists in this contract.
    #[inline(always)]
    fn contains_token(&self, token_id: &ContractTokenId) -> bool {
        self.all_tokens.contains(token_id)
    }

    /// Get the current balance of a given token ID for a given address.
    /// Results in an error if the token ID does not exist in the state.
    /// Since this contract only contains NFTs, the balance will always be
    /// either 1 or 0.
    fn balance(
        &self,
        token_id: &ContractTokenId,
        address: &Address,
    ) -> ContractResult<ContractTokenAmount> {
        ensure!(self.contains_token(token_id), ContractError::InvalidTokenId);
        let balance = self
            .state
            .get(address)
            .map(|address_state| u8::from(address_state.owned_tokens.contains(token_id)))
            .unwrap_or(0);
        Ok(balance.into())
    }

    /// Check if a given address is an operator of a given owner address.
    fn is_operator(&self, address: &Address, owner: &Address) -> bool {
        self.state
            .get(owner)
            .map(|address_state| address_state.operators.contains(address))
            .unwrap_or(false)
    }

    /// Update the state with a transfer of some token.
    /// Results in an error if the token ID does not exist in the state or if
    /// the from address have insufficient tokens to do the transfer.
    fn transfer(
        &mut self,
        token_id: &ContractTokenId,
        amount: ContractTokenAmount,
        from: &Address,
        to: &Address,
        state_builder: &mut StateBuilder<S>,
    ) -> ContractResult<()> {
        ensure!(self.contains_token(token_id), ContractError::InvalidTokenId);
        // A zero transfer does not modify the state.
        if amount == 0.into() {
            return Ok(());
        }
        // Since this contract only contains NFTs, no one will have an amount greater
        // than 1. And since the amount cannot be the zero at this point, the
        // address must have insufficient funds for any amount other than 1.
        ensure_eq!(amount, 1.into(), ContractError::InsufficientFunds);

        {
            let mut from_address_state = self
                .state
                .get_mut(from)
                .ok_or(ContractError::InsufficientFunds)?;
            // Find and remove the token from the owner, if nothing is removed, we know the
            // address did not own the token..
            let from_had_the_token = from_address_state.owned_tokens.remove(token_id);
            ensure!(from_had_the_token, ContractError::InsufficientFunds);
        }

        // Add the token to the new owner.
        let mut to_address_state = self
            .state
            .entry(*to)
            .or_insert_with(|| AddressState::empty(state_builder));
        to_address_state.owned_tokens.insert(*token_id);
        Ok(())
    }

    /// Update the state adding a new operator for minting tokens
    /// Succeeds even if the `operator` is already an operator for the
    /// `address`.
    fn add_global_operator(&mut self, operator: &Address) {
        self.operators.insert(*operator);
    }

    /// Update the state removing an operator for minting tokens
    /// Succeeds even if the `operator` is _not_ an operator for the
    /// `address`.
    fn remove_global_operator(&mut self, operator: &Address) {
        self.operators.remove(operator);
    }
    /// Update the state adding a new operator for a given address.
    /// Succeeds even if the `operator` is already an operator for the
    /// `address`.
    fn add_operator(
        &mut self,
        owner: &Address,
        operator: &Address,
        state_builder: &mut StateBuilder<S>,
    ) {
        let mut owner_state = self
            .state
            .entry(*owner)
            .or_insert_with(|| AddressState::empty(state_builder));
        owner_state.operators.insert(*operator);
    }

    /// Update the state removing an operator for a given address.
    /// Succeeds even if the `operator` is _not_ an operator for the `address`.
    fn remove_operator(&mut self, owner: &Address, operator: &Address) {
        self.state.entry(*owner).and_modify(|address_state| {
            address_state.operators.remove(operator);
        });
    }

    /// Check if state contains any implementors for a given standard.
    fn have_implementors(&self, std_id: &StandardIdentifierOwned) -> SupportResult {
        if let Some(addresses) = self.implementors.get(std_id) {
            SupportResult::SupportBy(addresses.to_vec())
        } else {
            SupportResult::NoSupport
        }
    }

    /// Set implementors for a given standard.
    fn set_implementors(
        &mut self,
        std_id: StandardIdentifierOwned,
        implementors: Vec<ContractAddress>,
    ) {
        self.implementors.insert(std_id, implementors);
    }
}

/// Build a string from TOKEN_METADATA_BASE_URL appended with the web3id
/// encoded as hex.
// fn build_token_metadata_url(web3id: &Web3Id) -> String {
//     let mut token_metadata_url = String::from(TOKEN_METADATA_BASE_URL);
//     token_metadata_url.push_str(&web3id.to_string());
//     token_metadata_url
// }

/// Function to evaluate a web3 id format
// fn check_web3id(s: &str) -> bool {
//     if s.starts_with('@') && s.len() >= 4 && s.len() <= 21 {
//         let username = &s[1..];
//         if username.chars().all(|c| c.is_alphanumeric() || c == '_') {
//             return true;
//         }
//     }
//     false
// }

// Contract functions

/// Initialize contract instance with no token types initially.
#[init(
    contract = "LicenseContract",
    event = "Cis2Event<ContractTokenId, ContractTokenAmount>"
)]
fn contract_init<S: HasStateApi>(
    ctx: &impl HasInitContext,
    state_builder: &mut StateBuilder<S>,
) -> InitResult<State<S>> {
    // Use the init_origin as the default owner
    let default_owner = ctx.init_origin();
    // Create the initial state with the deployer as the owner
    let state = State::empty(state_builder, Address::Account(default_owner));

    Ok(state)
}

#[derive(Serialize, SchemaType)]
struct ViewAddressState {
    owned_tokens: Vec<ContractTokenId>,
    operators: Vec<Address>,
}

#[derive(Serialize, SchemaType)]
struct ViewState {
    state: Vec<(Address, ViewAddressState)>,
    all_tokens: Vec<ContractTokenId>,
    operators: Vec<Address>,
}

#[receive(
    contract = "LicenseContract",
    name = "burn",
    parameter = "BurnParams",
    error = "ContractError",
    enable_logger,
    mutable
)]
fn contract_burn<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State<S>, StateApiType = S>,
    logger: &mut impl HasLogger,
) -> ContractResult<()> {
    // Parse the parameter.
    let BurnParams { token_id, owner, amount } = ctx.parameter_cursor().get()?;
    
    let state = host.state();

    // Get the sender who invoked this contract function.
    let sender = ctx.sender();

    // Authenticate the sender for the token burns.
    ensure!(owner == sender, ContractError::Unauthorized);

    // Burn the token
    host.state_mut().burn(&token_id, &owner)?;

    // Log the burn event with proper event emission
    logger.log(&Cis2Event::Burn(BurnEvent {
        token_id,  // Using TokenIdU32
        amount,
        owner,
    }))?;

    Ok(())
}

/// View function that returns the entire contents of the state. Meant for
/// testing.
#[receive(
    contract = "LicenseContract",
    name = "view",
    return_value = "ViewState"
)]
fn contract_view<S: HasStateApi>(
    _ctx: &impl HasReceiveContext,
    host: &impl HasHost<State<S>, StateApiType = S>,
) -> ReceiveResult<ViewState> {
    let state = host.state();

    let mut inner_state = Vec::new();
    for (k, a_state) in state.state.iter() {
        let owned_tokens = a_state.owned_tokens.iter().map(|x| *x).collect();
        let operators = a_state.operators.iter().map(|x| *x).collect();
        inner_state.push((
            *k,
            ViewAddressState {
                owned_tokens,
                operators,
            },
        ));
    }
    let all_tokens = state.all_tokens.iter().map(|x| *x).collect();
    let operators = state.operators.iter().map(|x| *x).collect();

    Ok(ViewState {
        state: inner_state,
        all_tokens,
        operators,
    })
}

/// Mint new tokens with a given address as the owner of these tokens.
/// Can only be called by the contract owner.
/// Logs a `Mint` and a `TokenMetadata` event for each token.
/// The url for the token metadata is the token ID encoded in hex, appended on
/// the `TOKEN_METADATA_BASE_URL`.
///
/// It rejects if:
/// - The sender is not the contract instance owner.
/// - Fails to parse parameter.
/// - Any of the tokens fails to be minted, which could be if:
///     - The minted token ID already exists.
///     - Fails to log Mint event
///     - Fails to log TokenMetadata event
///
/// Note: Can at most mint 32 token types in one call due to the limit on the
/// number of logs a smart contract can produce on each function call.
#[receive(
    contract = "LicenseContract",
    name = "mint",
    parameter = "MintParams",
    error = "ContractError",
    enable_logger,
    mutable
)]
fn contract_mint<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State<S>, StateApiType = S>,
    logger: &mut impl HasLogger,
) -> ContractResult<()> {
    // Get the contract owner
    let owner = ctx.owner();
    // Get the sender of the transaction
    let sender = ctx.sender();

    let (state, builder) = host.state_and_builder();

    if sender != state.owner && !state.operators.contains(&sender) {
        return Err(ContractError::Unauthorized); // Use the stored owner and operators for authorization
    }

    // Only the owner account and global operators can mint
    // ensure!(
    //     sender.matches_account(&owner) || state.operators.contains(&sender),
    //     ContractError::Unauthorized
    // );

    // Parse the parameter.
    let params: MintParams = ctx.parameter_cursor().get()?;

    let token_id = params.token;
    let web3id = params.web3id;
    // let token_be = u32::from_be_bytes(token_id.to_le_bytes());

    // ensure!(
    //     // check_web3id(&web3id),
    //     CustomContractError::InvalidWeb3Id.into()
    // );

    // let metadata_url = build_token_metadata_url(&web3id);
    let metadata_url = build_token_metadata_url(&token_id);

    let token_owner: Address = Address::Account(params.owner);

    // Mint the token in the state.
    state.mint(token_id, &metadata_url, &token_owner, builder)?;

    // Event for minted NFT.
    logger.log(&Cis2Event::Mint(MintEvent {
        token_id,
        amount: ContractTokenAmount::from(1),
        owner: token_owner,
    }))?;

    // Metadata URL for the NFT.
    logger.log(&Cis2Event::TokenMetadata::<_, ContractTokenAmount>(
        TokenMetadataEvent {
            token_id,
            metadata_url: MetadataUrl {
                url: metadata_url,
                hash: None,
            },
        },
    ))?;
    Ok(())
}

type TransferParameter = TransferParams<ContractTokenId, ContractTokenAmount>;

/// Execute a list of token transfers, in the order of the list.
///
/// Logs a `Transfer` event and invokes a receive hook function for every
/// transfer in the list.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Any of the transfers fail to be executed, which could be if:
///     - The `token_id` does not exist.
///     - The sender is not the owner of the token, or an operator for this
///       specific `token_id` and `from` address.
///     - The token is not owned by the `from`.
/// - Fails to log event.
/// - Any of the receive hook function calls rejects.
#[receive(
    contract = "LicenseContract",
    name = "transfer",
    parameter = "TransferParameter",
    error = "ContractError",
    enable_logger,
    mutable
)]
fn contract_transfer<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State<S>, StateApiType = S>,
    logger: &mut impl HasLogger,
) -> ContractResult<()> {
    // Parse the parameter.
    let TransferParams(transfers): TransferParameter = ctx.parameter_cursor().get()?;
    // Get the sender who invoked this contract function.
    let sender = ctx.sender();

    for Transfer {
        token_id,
        amount,
        from,
        to,
        data,
    } in transfers
    {
        let (state, builder) = host.state_and_builder();
        
        // Authenticate the sender for this transfer
        // ensure!(from == sender, ContractError::Unauthorized);

        if from != state.owner  {
            return Err(ContractError::Unauthorized); // Use the stored owner and operators for authorization
        }

        let to_address = to.address();
        
        // Update the contract state
        state.transfer(&token_id, amount, &from, &to_address, builder)?;

        // Log transfer event
        logger.log(&Cis2Event::Transfer(TransferEvent {
            token_id,
            amount,
            from,
            to: to_address,
        }))?;

        // If the receiver is a contract: invoke the receive hook function.
        if let Receiver::Contract(address, function) = to {
            let parameter = OnReceivingCis2Params {
                token_id,
                amount,
                from,
                data,
            };
            host.invoke_contract(
                &address,
                &parameter,
                function.as_entrypoint_name(),
                Amount::zero(),
            )?;
        }
    }
    Ok(())
}

/// Enable or disable addresses as operators of the sender address.
/// Logs an `UpdateOperator` event.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Fails to log event.
#[receive(
    contract = "LicenseContract",
    name = "updateOperator",
    parameter = "UpdateOperatorParams",
    error = "ContractError",
    enable_logger,
    mutable
)]
fn contract_update_operator<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State<S>, StateApiType = S>,
    logger: &mut impl HasLogger,
) -> ContractResult<()> {
    // Parse the parameter.
    let UpdateOperatorParams(params) = ctx.parameter_cursor().get()?;
    // Get the sender who invoked this contract function.
    let sender = ctx.sender();
    let (state, builder) = host.state_and_builder();
    for param in params {
        // Update the operator in the state.
        match param.update {
            OperatorUpdate::Add => state.add_operator(&sender, &param.operator, builder),
            OperatorUpdate::Remove => state.remove_operator(&sender, &param.operator),
        }

        // Log the appropriate event
        logger.log(
            &Cis2Event::<ContractTokenId, ContractTokenAmount>::UpdateOperator(
                UpdateOperatorEvent {
                    owner: sender,
                    operator: param.operator,
                    update: param.update,
                },
            ),
        )?;
    }

    Ok(())
}

/// Takes a list of queries. Each query is an owner address and some address to
/// check as an operator of the owner address.
///
/// It rejects if:
/// - It fails to parse the parameter.
#[receive(
    contract = "LicenseContract",
    name = "operatorOf",
    parameter = "OperatorOfQueryParams",
    return_value = "OperatorOfQueryResponse",
    error = "ContractError"
)]
fn contract_operator_of<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &impl HasHost<State<S>, StateApiType = S>,
) -> ContractResult<OperatorOfQueryResponse> {
    // Parse the parameter.
    let params: OperatorOfQueryParams = ctx.parameter_cursor().get()?;
    // Build the response.
    let mut response = Vec::with_capacity(params.queries.len());
    for query in params.queries {
        // Query the state for address being an operator of owner.
        let is_operator = host.state().is_operator(&query.address, &query.owner);
        response.push(is_operator);
    }
    let result = OperatorOfQueryResponse::from(response);
    Ok(result)
}

/// Parameter type for the CIS-2 function `balanceOf` specialized to the subset
/// of TokenIDs used by this contract.
type ContractBalanceOfQueryParams = BalanceOfQueryParams<ContractTokenId>;
/// Response type for the CIS-2 function `balanceOf` specialized to the subset
/// of TokenAmounts used by this contract.
type ContractBalanceOfQueryResponse = BalanceOfQueryResponse<ContractTokenAmount>;

/// Get the balance of given token IDs and addresses.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Any of the queried `token_id` does not exist.
#[receive(
    contract = "LicenseContract",
    name = "balanceOf",
    parameter = "ContractBalanceOfQueryParams",
    return_value = "ContractBalanceOfQueryResponse",
    error = "ContractError"
)]
fn contract_balance_of<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &impl HasHost<State<S>, StateApiType = S>,
) -> ContractResult<ContractBalanceOfQueryResponse> {
    // Parse the parameter.
    let params: ContractBalanceOfQueryParams = ctx.parameter_cursor().get()?;
    // Build the response.
    let mut response = Vec::with_capacity(params.queries.len());
    for query in params.queries {
        // Query the state for balance.
        let amount = host.state().balance(&query.token_id, &query.address)?;
        response.push(amount);
    }
    let result = ContractBalanceOfQueryResponse::from(response);
    Ok(result)
}

/// Parameter type for the CIS-2 function `tokenMetadata` specialized to the
/// subset of TokenIDs used by this contract.
type ContractTokenMetadataQueryParams = TokenMetadataQueryParams<ContractTokenId>;

/// Get the token metadata URLs and checksums given a list of token IDs.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Any of the queried `token_id` does not exist.
#[receive(
    contract = "LicenseContract",
    name = "tokenMetadata",
    parameter = "ContractTokenMetadataQueryParams",
    return_value = "TokenMetadataQueryResponse",
    error = "ContractError"
)]
fn contract_token_metadata<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &impl HasHost<State<S>, StateApiType = S>,
) -> ContractResult<TokenMetadataQueryResponse> {
    // Parse the parameter.
    let params: ContractTokenMetadataQueryParams = ctx.parameter_cursor().get()?;
    // Build the response.
    let mut response = Vec::with_capacity(params.queries.len());
    for token_id in params.queries {
        // Check the token exists.
        ensure!(
            host.state().contains_token(&token_id),
            ContractError::InvalidTokenId
        );

        let metadata_url: MetadataUrl = host
            .state()
            .metadata
            .get(&token_id)
            .map(|metadata| MetadataUrl {
                hash: None,
                url: metadata.url.to_owned(),
            })
            .ok_or(ContractError::InvalidTokenId)?;
        response.push(metadata_url);
    }
    let result = TokenMetadataQueryResponse::from(response);
    Ok(result)
}

/// Get the supported standards or addresses for a implementation given list of
/// standard identifiers.
///
/// It rejects if:
/// - It fails to parse the parameter.
#[receive(
    contract = "LicenseContract",
    name = "supports",
    parameter = "SupportsQueryParams",
    return_value = "SupportsQueryResponse",
    error = "ContractError"
)]
fn contract_supports<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &impl HasHost<State<S>, StateApiType = S>,
) -> ContractResult<SupportsQueryResponse> {
    // Parse the parameter.
    let params: SupportsQueryParams = ctx.parameter_cursor().get()?;

    // Build the response.
    let mut response = Vec::with_capacity(params.queries.len());
    for std_id in params.queries {
        if SUPPORTS_STANDARDS.contains(&std_id.as_standard_identifier()) {
            response.push(SupportResult::Support);
        } else {
            response.push(host.state().have_implementors(&std_id));
        }
    }
    let result = SupportsQueryResponse::from(response);
    Ok(result)
}

/// Set the addresses for an implementation given a standard identifier and a
/// list of contract addresses.
///
/// It rejects if:
/// - Sender is not the owner of the contract instance.
/// - It fails to parse the parameter.
#[receive(
    contract = "LicenseContract",
    name = "setImplementors",
    parameter = "SetImplementorsParams",
    error = "ContractError",
    mutable
)]
fn contract_set_implementor<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State<S>, StateApiType = S>,
) -> ContractResult<()> {
    // Authorize the sender.
    // ensure!(
    //     ctx.sender().matches_account(&ctx.owner()),
    //     ContractError::Unauthorized
    // );
    let sender = ctx.sender();

    if !ctx.sender().matches_account(&ctx.owner()) {
        return Err(ContractError::Unauthorized); // Use the stored owner and operators for authorization
    }
    // Parse the parameter.
    let params: SetImplementorsParams = ctx.parameter_cursor().get()?;
    // Update the implementors in the state
    host.state_mut()
        .set_implementors(params.id, params.implementors);
    Ok(())
}

/// The parameter type for the contract function `upgrade`.
/// Takes the new module and optionally a migration function to call in the new
/// module after the upgrade.
#[derive(Serialize, SchemaType)]
struct UpgradeParams {
    /// The new module reference.
    module:  ModuleReference,
    /// Optional entrypoint to call in the new module after upgrade.
    migrate: Option<(OwnedEntrypointName, OwnedParameter)>,
}

#[receive(
    contract = "LicenseContract",
    name = "upgrade",
    parameter = "UpgradeParams",
    low_level
)]
fn contract_upgrade(
    ctx: &ReceiveContext,
    host: &mut LowLevelHost,
) -> ReceiveResult<()> {
    // Check that only the owner is authorized to upgrade the smart contract.
    // ensure!(ctx.sender().matches_account(&ctx.owner()));
    let sender = ctx.sender();

    if !sender.matches_account(&ctx.owner()) {
        // Optionally log a message or handle unauthorized access
        return Ok(()); // Exit the function without performing the upgrade
    }
    // Parse the parameter.
    let params: UpgradeParams = ctx.parameter_cursor().get()?;
    // Trigger the upgrade.
    host.upgrade(params.module)?;
    // Call the migration function if provided.
    if let Some((func, parameters)) = params.migrate {
        host.invoke_contract_raw(
            &ctx.self_address(),
            parameters.as_parameter(),
            func.as_entrypoint_name(),
            Amount::zero(),
        )?;
    }
    Ok(())
}

// Function to update the owner
// #[receive(
//     contract = "LicenseContract",
//     name = "updateOwner",
//     parameter = "()",
//     error = "CustomContractError",
//     mutable
// )]
// fn update_owner<S: HasStateApi>(
//     ctx: &impl HasReceiveContext,
//     host: &mut impl HasHost<State<S>, StateApiType = S>,
// ) -> Result<(), CustomContractError> {
//     // Ensure only the current stored owner can call this function.
//     let caller = ctx.sender();
//     if caller != host.state().owner {
//         return Err(CustomContractError::Unauthorized);
//     }

//     // Hardcoded new owner address (Base58Check encoded).
//     // Note: Ensure there are no extraneous spaces.
//     let new_owner_address = "4MwARWeXdMs3YZ5MPPn2561ceani6AJAVTNPtwS6tceaG2qatK".trim();

//     // Decode the Base58 string into bytes.
//     let decoded_bytes = bs58::decode(new_owner_address)
//         .into_vec()
//         .map_err(|_| CustomContractError::ParseParams)?;

//     // The expected decoded length is 36 bytes: 
//     // 1 byte for the version, 32 bytes for the account payload, and 3 bytes for the checksum.
//     if decoded_bytes.len() != 36 {
//         return Err(CustomContractError::ParseParams);
//     }

//     // Skip the first byte (the version) and take the next 32 bytes as the payload.
//     let raw_bytes = &decoded_bytes[1..33];

//     // Use the built-in from_bytes function to deserialize these 32 bytes into an AccountAddress.
//     let new_owner = concordium_std::from_bytes::<AccountAddress>(raw_bytes)
//         .map_err(|_| CustomContractError::ParseParams)?;

//     // Update the contract's stored owner.
//     host.state_mut().owner = Address::Account(new_owner);

//     Ok(())
// }

#[receive(
    contract = "LicenseContract",
    name = "updateOwner",
    parameter = "()",
    error = "CustomContractError",
    mutable
)]
fn update_owner<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State<S>, StateApiType = S>,
) -> Result<(), CustomContractError> {
    // Ensure only the current stored owner can call this function.
    let caller = ctx.sender();
    if caller != host.state().owner {
        return Err(CustomContractError::Unauthorized);
    }

    // Update the contract's stored owner to the hardcoded official owner.
    host.state_mut().owner = Address::Account(OFFICIAL_OWNER);
    Ok(())
}

Module Schema
Initialization

Parameters
None
Errors
None
Event
{
  "Enum": [
    {
      "TokenMetadata": {
        "metadata_url": {
          "hash": {
            "Enum": [
              {
                "None": []
              },
              {
                "Some": [
                  "<String of size 64 containing lowercase hex characters.>"
                ]
              }
            ]
          },
          "url": "<String>"
        },
        "token_id": "<String with lowercase hex>"
      }
    },
    {
      "UpdateOperator": {
        "operator": {
          "Enum": [
            {
              "Account": [
                "<AccountAddress>"
              ]
            },
            {
              "Contract": [
                {
                  "index": "<UInt64>",
                  "subindex": "<UInt64>"
                }
              ]
            }
          ]
        },
        "owner": {
          "Enum": [
            {
              "Account": [
                "<AccountAddress>"
              ]
            },
            {
              "Contract": [
                {
                  "index": "<UInt64>",
                  "subindex": "<UInt64>"
                }
              ]
            }
          ]
        },
        "update": {
          "Enum": [
            {
              "Remove": []
            },
            {
              "Add": []
            }
          ]
        }
      }
    },
    {
      "Burn": {
        "amount": "<String of size at most 74 containing an unsigned integer.>",
        "owner": {
          "Enum": [
            {
              "Account": [
                "<AccountAddress>"
              ]
            },
            {
              "Contract": [
                {
                  "index": "<UInt64>",
                  "subindex": "<UInt64>"
                }
              ]
            }
          ]
        },
        "token_id": "<String with lowercase hex>"
      }
    },
    {
      "Mint": {
        "amount": "<String of size at most 74 containing an unsigned integer.>",
        "owner": {
          "Enum": [
            {
              "Account": [
                "<AccountAddress>"
              ]
            },
            {
              "Contract": [
                {
                  "index": "<UInt64>",
                  "subindex": "<UInt64>"
                }
              ]
            }
          ]
        },
        "token_id": "<String with lowercase hex>"
      }
    },
    {
      "Transfer": {
        "amount": "<String of size at most 74 containing an unsigned integer.>",
        "from": {
          "Enum": [
            {
              "Account": [
                "<AccountAddress>"
              ]
            },
            {
              "Contract": [
                {
                  "index": "<UInt64>",
                  "subindex": "<UInt64>"
                }
              ]
            }
          ]
        },
        "to": {
          "Enum": [
            {
              "Account": [
                "<AccountAddress>"
              ]
            },
            {
              "Contract": [
                {
                  "index": "<UInt64>",
                  "subindex": "<UInt64>"
                }
              ]
            }
          ]
        },
        "token_id": "<String with lowercase hex>"
      }
    }
  ]
}
Methods

Parameters
{
  "amount": "<String of size at most 74 containing an unsigned integer.>",
  "owner": {
    "Enum": [
      {
        "Account": [
          "<AccountAddress>"
        ]
      },
      {
        "Contract": [
          {
            "index": "<UInt64>",
            "subindex": "<UInt64>"
          }
        ]
      }
    ]
  },
  "token_id": "<String with lowercase hex>"
}
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
None

Parameters
None
Errors
None
Return
{
  "all_tokens": [
    "<String with lowercase hex>"
  ],
  "operators": [
    {
      "Enum": [
        {
          "Account": [
            "<AccountAddress>"
          ]
        },
        {
          "Contract": [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            }
          ]
        }
      ]
    }
  ],
  "state": [
    [
      {
        "Enum": [
          {
            "Account": [
              "<AccountAddress>"
            ]
          },
          {
            "Contract": [
              {
                "index": "<UInt64>",
                "subindex": "<UInt64>"
              }
            ]
          }
        ]
      },
      {
        "operators": [
          {
            "Enum": [
              {
                "Account": [
                  "<AccountAddress>"
                ]
              },
              {
                "Contract": [
                  {
                    "index": "<UInt64>",
                    "subindex": "<UInt64>"
                  }
                ]
              }
            ]
          }
        ],
        "owned_tokens": [
          "<String with lowercase hex>"
        ]
      }
    ]
  ]
}

Parameters
{
  "owner": "<AccountAddress>",
  "token": "<String with lowercase hex>",
  "web3id": "<String>"
}
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
None

Parameters
[
  {
    "amount": "<String of size at most 74 containing an unsigned integer.>",
    "data": "<String with lowercase hex>",
    "from": {
      "Enum": [
        {
          "Account": [
            "<AccountAddress>"
          ]
        },
        {
          "Contract": [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            }
          ]
        }
      ]
    },
    "to": {
      "Enum": [
        {
          "Account": [
            "<AccountAddress>"
          ]
        },
        {
          "Contract": [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            },
            "<String>"
          ]
        }
      ]
    },
    "token_id": "<String with lowercase hex>"
  }
]
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
None

Parameters
[
  {
    "operator": {
      "Enum": [
        {
          "Account": [
            "<AccountAddress>"
          ]
        },
        {
          "Contract": [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            }
          ]
        }
      ]
    },
    "update": {
      "Enum": [
        {
          "Remove": []
        },
        {
          "Add": []
        }
      ]
    }
  }
]
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
None

Parameters
[
  {
    "address": {
      "Enum": [
        {
          "Account": [
            "<AccountAddress>"
          ]
        },
        {
          "Contract": [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            }
          ]
        }
      ]
    },
    "owner": {
      "Enum": [
        {
          "Account": [
            "<AccountAddress>"
          ]
        },
        {
          "Contract": [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            }
          ]
        }
      ]
    }
  }
]
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
[
  "<Bool>"
]

Parameters
[
  {
    "address": {
      "Enum": [
        {
          "Account": [
            "<AccountAddress>"
          ]
        },
        {
          "Contract": [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            }
          ]
        }
      ]
    },
    "token_id": "<String with lowercase hex>"
  }
]
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
[
  "<String of size at most 74 containing an unsigned integer.>"
]

Parameters
[
  "<String with lowercase hex>"
]
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
[
  {
    "hash": {
      "Enum": [
        {
          "None": []
        },
        {
          "Some": [
            "<String of size 64 containing lowercase hex characters.>"
          ]
        }
      ]
    },
    "url": "<String>"
  }
]

Parameters
[
  "<String>"
]
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
[
  {
    "Enum": [
      {
        "NoSupport": []
      },
      {
        "Support": []
      },
      {
        "SupportBy": [
          [
            {
              "index": "<UInt64>",
              "subindex": "<UInt64>"
            }
          ]
        ]
      }
    ]
  }
]

Parameters
{
  "id": "<String>",
  "implementors": [
    {
      "index": "<UInt64>",
      "subindex": "<UInt64>"
    }
  ]
}
Errors
{
  "Enum": [
    {
      "InvalidTokenId": []
    },
    {
      "InsufficientFunds": []
    },
    {
      "Unauthorized": []
    },
    {
      "Custom": [
        {
          "Enum": [
            {
              "ParseParams": []
            },
            {
              "LogFull": []
            },
            {
              "LogMalformed": []
            },
            {
              "TokenIdAlreadyExists": []
            },
            {
              "InvokeContractError": []
            },
            {
              "InvalidWeb3Id": []
            },
            {
              "LicenseNotFound": []
            },
            {
              "Unauthorized": []
            }
          ]
        }
      ]
    }
  ]
}
Return
None

Parameters
{
  "migrate": {
    "Enum": [
      {
        "None": []
      },
      {
        "Some": [
          [
            "<String>",
            "<String with lowercase hex>"
          ]
        ]
      }
    ]
  },
  "module": "<String of size 64 containing lowercase hex characters.>"
}
Errors
None
Return
None

Parameters
[]
Errors
{
  "Enum": [
    {
      "ParseParams": []
    },
    {
      "LogFull": []
    },
    {
      "LogMalformed": []
    },
    {
      "TokenIdAlreadyExists": []
    },
    {
      "InvokeContractError": []
    },
    {
      "InvalidWeb3Id": []
    },
    {
      "LicenseNotFound": []
    },
    {
      "Unauthorized": []
    }
  ]
}
Return
None
Module Usage