Mint Example
This guide provides a complete Deno implementation for minting CIP-68 NFTs using parameterized smart contracts with the Anvil API.
Full Code Example
Implementation Overview
This example demonstrates:
Applying parameters to uploaded blueprints
Building transactions with preloaded scripts
Dual-wallet signing for smart contract authorization
Complete end-to-end minting workflow
Prerequisites
Before running this example, ensure you have:
Deno installed - Download here
Anvil API key - Get one from the Anvil dashboard
Two wallets with testnet ADA: (See Wallet CLI)
Customer wallet (pays fees, receives user token)
Admin wallet (provides key_hash parameter, signs transaction)
Uploaded CIP-68 blueprint - The original blueprint must be uploaded to the Anvil API first. See blueprint Management
Understanding of parameterized contracts - Parameters are applied at transaction time, not deployment time
Step-by-Step Implementation
Let's build a parameterized CIP-68 minting script step by step. Each section will explain the concept and show the corresponding code.
Step 1: Setup and Imports
First, we need to import the required dependencies and load our configuration files.
Create cip68-parameterized-mint.ts
:
import { Buffer } from "node:buffer";
import {
FixedTransaction,
PrivateKey,
} from "npm:@emurgo/[email protected]";
// Import wallet configurations and blueprint
import customer from "./wallet-customer.json" with { type: "json" };
import adminWallet from "./wallet-mint-sc-policy.json" with { type: "json" };
import blueprint from "../aiken-mint-cip-68/plutus.json" with { type: "json" };
import { API_URL, HEADERS } from "../../../utils/constant.ts";
// Configuration - Customize these values for your project
const CUSTOMER_ADDRESS = customer.base_address_preprod; // Wallet that pays fees and receives user token
const ADMIN_KEY_HASH = adminWallet.key_hash; // Admin key for smart contract authorization
const ASSET_NAME = "test"; // Base name for your CIP-68 token pair
What's happening here:
We import Cardano Serialization Library for transaction signing
Load wallet files (customer pays fees, admin provides authorization)
Load the original blueprint (compiled from Aiken smart contract)
Set up API configuration and asset name
Step 2: Apply Parameters to Blueprint
The blueprint that we uploaded is generic. In order to make it custom for this collection. We need to apply the parameters to the smart contract, parameterized contracts are customized at transaction time. We apply the admin's key hash to create a personalized version of the contract:
console.log("π Applying parameters to blueprint...");
const originalHash = blueprint.validators[0].hash;
const applyParamsResponse = await fetch(
`${API_URL}/blueprints/apply-params`,
{
method: "POST",
headers: HEADERS,
body: JSON.stringify({
params: { [originalHash]: [ADMIN_KEY_HASH] }, // Apply admin key to parameterize contract
blueprint: blueprint
}),
},
);
if (!applyParamsResponse.ok) {
console.error("β Failed to apply parameters:", applyParamsResponse.status, applyParamsResponse.statusText);
const errorText = await applyParamsResponse.text();
console.error("Error details:", errorText);
Deno.exit(1);
}
const applyParamsResult = await applyParamsResponse.json();
console.log("β
Parameters applied successfully");
// Extract the parameterized script and address
const parameterizedScript = applyParamsResult.preloadedScript;
const newHashes = Object.keys(applyParamsResult.addresses);
const SCRIPT_HASH = newHashes[0];
const SC_ADDRESS = applyParamsResult.addresses[SCRIPT_HASH].bech32;
console.log("Parameterized script hash:", SCRIPT_HASH);
console.log("Script address:", SC_ADDRESS);
What's happening here:
We call
/blueprints/apply-params
with the original blueprint and admin key hashThe endpoint generates a new script with the admin key "baked in" as a parameter
We get back a new script hash, address, and complete parameterized script
Important: This parameterized script is NOT saved to the database - we must use it directly
Expected output:
π Applying parameters to blueprint...
β
Parameters applied successfully
Parameterized script hash: 5da506b0b8d9c7d9df247c7684aead8eb6fdac1cbfb2dd7cc33d6499
Script address: addr_test1wpw62p4shrvu0kwly378dp9w4k8tdldvrjlm9htucv7kfxggmdaj6
Step 3: Build the Transaction Payload
Now we build the CIP-68 minting transaction. The key insight is that we include the parameterized script as a preloadedScript
so the tx-builder can validate it:
const input = {
changeAddress: CUSTOMER_ADDRESS,
//utxos: [], // Required for LIVE network - provide actual UTxOs
message: "Cip68 example",
// Mint both reference and user tokens
mint: [
{
// Reference token -> This is the token that will be spent to update metadata
version: "cip68",
assetName: {name: assetName, label: 100, format: "utf8"},
policyId: SCRIPT_HASH,
type: "plutus",
quantity: 1,
},
{
// User token -> This is the token that will be sent to the user
version: "cip68",
assetName: {name: assetName, label: 222, format: "utf8"},
policyId: SCRIPT_HASH,
type: "plutus",
quantity: 1,
},
],
// Script Interactions - Specify redeemer data for smart contract validation during minting
scriptInteractions: [
{
purpose: "mint",
hash: SCRIPT_HASH,
redeemer: {
type: "json",
value: {
// Specify the asset name to be minted - Used to validate that the minting transaction
// is minting the intended CIP-68 assets.
assetnames: [[Buffer.from(assetName).toString("hex"), 0]]
},
},
},
],
// Override Mint Array's 'reference token' (Label 100) output
// to modify the datum as spendable in case of a metadata update.
// This will be sent to the smart contract address.
outputs: [
{
address: SC_ADDRESS,
assets: [
{
assetName: { name: ASSET_NAME, label: 100, format: "utf8" }, // Reference token (100)
policyId: SCRIPT_HASH,
quantity: 1,
},
],
datum: {
type: "inline",
shape: {
validatorHash: SCRIPT_HASH,
purpose: "spend",
},
value: {
metadata: [
["name", ASSET_NAME], // Must match asset name for CIP-68 compliance
["nickname", "test_nickname"], // Custom metadata field
],
version: 1
}
},
},
],
requiredSigners: [ADMIN_KEY_HASH], // Admin must sign
preloadedScripts: [parameterizedScript]
};
What's happening here:
Mint array: Creates both reference (100) and user (222) tokens with the same base name
Script interactions: Tells the smart contract which assets we're minting via the redeemer
Outputs: Explicitly routes the reference token to the script address with metadata datum
Required signers: Ensures the admin (whose key was used in parameters) signs the transaction
Preloaded scripts: Includes our parameterized script so tx-builder can validate it
Key insight: The user token (222) automatically goes to the change address, while the reference token (100) needs an explicit output with custom datum in order to go to the script address.
Step 4: Build the Transaction
Finally, we build the transaction:
// Build the transaction
console.log("π¨ Building transaction...");
const contractDeployed = await fetch(`${API_URL}/transactions/build`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(input),
});
if (!contractDeployed.ok) {
console.error("β Failed to build transaction:", contractDeployed.status, contractDeployed.statusText);
const errorText = await contractDeployed.text();
console.error("Error details:", errorText);
Deno.exit(1);
}
const contractResult = await contractDeployed.json();
console.log("β
Transaction built successfully");
What's happening here:
The tx-builder validates our payload and creates a Cardano transaction
It includes the parameterized script from our
preloadedScripts
The transaction is returned as a hex-encoded string ready for signing
Step 5: Sign with Both Wallets
CIP-68 smart contracts require signatures from both the customer (who pays fees) and the admin (who authorizes minting):
// Sign the transaction using CSL.
const txToSubmitOnChain = FixedTransaction.from_bytes(Buffer.from(contractResult.complete, "hex"));
txToSubmitOnChain.sign_and_add_vkey_signature(PrivateKey.from_bech32(customer.skey));
txToSubmitOnChain.sign_and_add_vkey_signature(PrivateKey.from_bech32(adminWallet.skey));
console.log("β
Transaction signed with both keys");
What's happening here:
We deserialize the transaction from hex format
Load both private keys (customer and admin)
Add both signatures to the witness set
Create the final signed transaction
Step 6: Submit to Network
Finally, we submit the signed transaction to the Cardano network:
// Submit to network
console.log("π‘ Submitting to network...");
const submitResponse = await fetch(`${API_URL}/transactions/submit`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ transaction: signedTx.to_hex() }),
});
if (!submitResponse.ok) {
console.error("β Failed to submit transaction:", submitResponse.status, submitResponse.statusText);
const errorText = await submitResponse.text();
console.error("Error details:", errorText);
Deno.exit(1);
}
const submitResult = await submitResponse.json();
console.log("π Transaction submitted successfully!");
console.log("Transaction hash:", submitResult.transactionId);
console.log("Reference Token (100):", `${SCRIPT_HASH}.${Buffer.from(assetName).toString("hex")}64`);
console.log("User Token (222):", `${SCRIPT_HASH}.${Buffer.from(assetName).toString("hex")}de`);
What's happening here:
The signed transaction is submitted to the Cardano network
On success, we get back a transaction hash for tracking
Both CIP-68 tokens are now minted and distributed according to our rules
Expected output:
π‘ Submitting to network...
π Transaction submitted successfully!
Transaction hash: a1b2c3d4e5f6789...
Reference Token (100): 5da506b0b8d9c7d9df247c7684aead8eb6fdac1cbfb2dd7cc33d649974657374064
User Token (222): 5da506b0b8d9c7d9df247c7684aead8eb6fdac1cbfb2dd7cc33d649974657374de
Running the Example
Prerequisites Setup
Upload the blueprint first using a separate upload script:
deno run --allow-all upload-blueprint.ts --blueprint=../aiken-mint-cip-68/plutus.json
Set up wallet files:
wallet-customer.json
- Customer wallet (pays fees, receives user token)wallet-mint-sc-policy.json
- Admin wallet (provides key_hash parameter)
Update configuration:
Ensure
API_URL
andHEADERS
are configured in your constants fileModify
assetName
and metadata as desired
Run the parameterized minting script:
deno run --allow-all cip68-parameterized-mint.ts
Expected Output
π Applying parameters to blueprint...
β
Parameters applied successfully
Parameterized script hash: 5da506b0b8d9c7d9df247c7684aead8eb6fdac1cbfb2dd7cc33d6499
Script address: addr_test1wpw62p4shrvu0kwly378dp9w4k8tdldvrjlm9htucv7kfxggmdaj6
β
Transaction built successfully
π CIP-68 Asset Minted Successfully!
Transaction Hash: 74dcb95427b9eee36137803617a900460b5d58ed266598ebf4146d659b613520
Reference Token (100): 5da506b0b8d9c7d9df247c7684aead8eb6fdac1cbfb2dd7cc33d6499.74657374064
User Token (222): 5da506b0b8d9c7d9df247c7684aead8eb6fdac1cbfb2dd7cc33d6499.746573740de
β
Transaction submitted successfully
Next Steps
This example demonstrates the complete parameterized CIP-68 workflow. For production use:
Add robust error handling for apply-params and transaction failures
Implement parameter validation to ensure correct admin keys
Add transaction monitoring to track minting status on-chain
Create reusable functions for parameter application and script generation
Implement batch operations for multiple asset minting
References
Last updated
Was this helpful?