Minting Contract

This section will guide you through implementing a component for minting tokens with user input validation and signature verification.

Objective

This tutorial aims to create a minting dApp that utilizes Concordium's ID layer. Users must prove they are older than 18 to mint a token.

Approach

Instead of directly accessing ID proofs from the smart contract, you'll implement the logic using the verifier backend server. This approach ensures that users can only mint tokens if they provide proof of being older than 18 through the verifier backend server.

Here's how it will work:

  1. Owner Setup:

    • The owner of the verifier backend server and the smart contract instance (dApp owner) are assumed to be the same.

    • The owner uses an account's sign and verify keys when setting up the verifier backend server.

  2. Contract Initialization:

    • When creating a new contract instance, the owner sends their verify key (public key), which the contract stores in its state to verify signatures.

  3. Minting Process:

    • When a user wants to mint a token, the dApp requests a challenge and a statement from the verifier backend.

    • Using the challenge, the dApp prompts the user to accept the information from their wallet.

    • The proof is shared with the verifier backend. If verified, the verifier signs the user's public address with the sign key provided during server startup.

    • This signature becomes the input for the minting function.

    • The smart contract verifies the signature using the stored public key in its state to ensure it's from the verifier and valid.

    • The mint() function is executed if verification passes, allowing the user to mint tokens.

Follow the steps below to implement this approach.

Step 1: Create the Smart Contract Project

  1. Create a New Project: The following command initializes a new Rust project named "cis2-multi" for the smart contract:

cargo new cis2-multi
  1. Add Dependencies: Update the "Cargo.toml" file with the necessary dependencies:

[features]
default = ["std"]
std = ["concordium-std/std", "concordium-cis2/std"]

[dependencies]
concordium-std = { version = "*", default-features = false }
concordium-cis2 = { version = "*", default-features = false }
hex = "*"

[lib]
crate-type=["cdylib", "rlib"]

[profile.release]
codegen-units = 1
opt-level = "s"

This is how your "Cargo.toml" file will look:

Step 2: Modify the State Struct

  1. Create lib.rs File: Copy and paste the following code by doing the following:

    • Start modification with the State struct

    • Add a variable for the verify_key to the state's empty() function.

Note: Unlike a regular empty() function, this one takes the verify_key as a parameter.

  1. Add Verify Key to State: Update the State struct in lib.rs to include a variable for the verify key and add it to the empty() function.

#[derive(Serial, DeserialWithState, StateClone)]
#[concordium(state_parameter = "S")]
struct State<S> {
    // Other fields...
    verify_key: PublicKeyEd25519,
}

impl<S: HasStateApi> State<S> {
    fn empty(state_builder: &mut StateBuilder<S>, verify_key: PublicKeyEd25519) -> Self {
        State {
            // Other fields initialization...
            verify_key,
        }
    }
    // Other state functions...
}

Step 3: Create Structs for Input Parameters

  1. Create InitParams Struct: Define a struct to receive the verify key as an input parameter during contract initialization:

#[derive(Serial, Deserial, SchemaType)]
struct InitParams {
    verify_key: PublicKeyEd25519,
}
  1. Add Signature to Mint Parameters: Modify the MintParams struct to include the signature for verification during minting.

#[derive(Serial, Deserial, SchemaType)]
struct MintParams {
    owner: Address,
    tokens: collections::BTreeMap<ContractTokenId, (TokenMetadata, ContractTokenAmount)>,
    signature: SignatureEd25519,
}

Step 4: Update the Mint Function

  1. Update Mint Function:

    Modify the contract_mint function to include signature verification and minting logic based on the provided parameters.

    #[receive(
        contract = "CIS2-Multi",
        name = "mint",
        crypto_primitives,
        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,
        crypto_primitives: &impl HasCryptoPrimitives,
    ) -> ContractResult<()> {
        // Minting logic here...
    }

By following these steps, you can update the cis2-multi smart contract to incorporate the minting functionality using Concordium's ID layer and verifier backend server.

Last updated