Admin Update Example
This guide provides a complete Deno implementation for updating CIP-68 NFT metadata using spend validators with parameterized smart contracts and the Anvil API.
Full Code Example
Implementation Overview
This example demonstrates:
Spending existing reference tokens for metadata updates
Using spend validators with proper redeemer structures
Admin authorization patterns for metadata changes
Complete end-to-end update workflow
Prerequisites
This guide assumes you have completed the Minting Example. Additionally, you need:
Existing CIP-68 tokens - Reference and user tokens from a successful mint
Current reference token UTXO - Transaction hash and output index where the reference token currently resides
Understanding of spend validators - Updates consume and recreate reference tokens with new metadata
Step-by-Step Implementation
Let's build a CIP-68 metadata update script step by step. Each section will explain the concept and show the corresponding code.
Step 1: Setup and Configuration
Using the same setup from the minting example:
import { Buffer } from "node:buffer";
import { FixedTransaction, PrivateKey } from "npm:@emurgo/[email protected]";
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 - Update these values for your specific tokens
const CUSTOMER_ADDRESS = customer.base_address_preprod; // Change address for transaction
const ADMIN_KEY_HASH = adminWallet.key_hash; // Admin authorization key
const ASSET_NAME = "test"; // Name of the CIP-68 token to update
// NEW: Reference token UTXO to update
const TX_HASH = "d330e666c15e2f19e54b49aff64e69aa134d25242b7dadd95d6aba570a7c1861";
const TX_OUTPUT_INDEX = "0";Key difference from minting:
TX_HASH and TX_OUTPUT_INDEX specify which existing reference token to update
Step 2: Apply Parameters to Blueprint
The apply-params process is identical to the minting example:
const applyParamsResult = await fetch(`${API_URL}/blueprints/apply-params`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
params: { [blueprint.validators[0].hash]: [ADMIN_KEY_HASH] },
blueprint: blueprint
}),
}).then(res => res.json());
const parameterizedScript = applyParamsResult.preloadedScript;
const SCRIPT_HASH = Object.keys(applyParamsResult.addresses)[0];
const SC_ADDRESS = applyParamsResult.addresses[SCRIPT_HASH].bech32;Result: Same parameterized script and address as your original mint
Step 3: Build the Update Transaction
Now we build the spend transaction that will update the metadata. The key insight is that we spend the existing reference token and recreate it with new metadata:
const input = {
changeAddress: CUSTOMER_ADDRESS,
message: "CIP-68 Admin Update Example",
// Script Interactions - Define how to interact with the smart contract during transaction execution
scriptInteractions: [
{
// Purpose: "spend" tells Cardano this is spending a UTXO locked at a smart contract address
purpose: "spend",
// Hash: The parameterized script hash of our CIP-68 validator
hash: SCRIPT_HASH,
// OutputRef: Specifies exactly which UTXO we want to spend (the reference token)
outputRef: {
txHash: TX_HASH, // Transaction hash containing the reference token UTXO
index: TX_OUTPUT_INDEX, // Output index within that transaction (usually 0)
},
// Redeemer: The data passed to the smart contract's spend validator for authorization
redeemer: {
type: "json",
value: {
// output_index: Tells the validator which transaction output contains the updated reference token
output_index: 0,
// update: "AdminUpdate" triggers admin authorization path in the smart contract
// This makes the validator check that the transaction is signed by the admin key
// (as opposed to "UserUpdate" which would require fee payment)
update: "AdminUpdate"
},
},
},
],What's happening here:
Script interactions: Tells the smart contract we're spending a UTXO locked at the contract address
OutputRef: Specifies exactly which reference token UTXO we want to update
Redeemer: Contains the authorization data - "AdminUpdate" means admin signature is required
Step 4: Define the Updated Output
We need to send the reference token back to the smart contract with updated metadata:
outputs: [
{
// Send the updated reference token back to the smart contract address with the new metadata
address: SC_ADDRESS,
assets: [
{
assetName: {name: assetName, label: 100, format: "utf8"},
policyId: SCRIPT_HASH,
quantity: 1,
},
],
// Details how and where the datum is stored to be used later.
datum: {
type: "inline",
shape: {
validatorHash: SCRIPT_HASH,
purpose: "spend", // Marked as spend purpose so the spend validator can update the metadata later.
},
value: {
// Updated CIP-68 Metadata goes here. Follows official CIP-68 spec metadata format. (metadata, version, datum)
metadata: [
["name", "test"],
["nickname", "admin_updated"] // Updated nickname to show the admin update
],
version: 1
}
},
},
],
// Admin must sign to pass smart contract validation
requiredSigners: [META_MANAGER_KEY_HASH],
preloadedScripts: [parameterizedScript]
};What's happening here:
Output: The reference token is sent back to the smart contract with updated metadata
Datum: Contains the new metadata following CIP-68 specification
Required signers: Admin must sign the transaction for authorization
Preloaded scripts: Include the parameterized script for validation
Step 5: Build and Submit Transaction
Now we build, sign, and submit the transaction:
const contractDeployed = await fetch(`${API_URL}/transactions/build`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(input),
});
if (!contractDeployed.ok) {
const errorText = await contractDeployed.text();
console.error("❌ Transaction build failed:", errorText);
Deno.exit(1);
}
const transaction = await contractDeployed.json();
console.log("✅ Transaction built successfully");
// Sign the transaction using CSL.
const txToSubmitOnChain = FixedTransaction.from_bytes(Buffer.from(transaction.complete, "hex"));
txToSubmitOnChain.sign_and_add_vkey_signature(PrivateKey.from_bech32(adminWallet.skey));
// Submit the transaction to the blockchain.
const urlSubmit = `${API_URL}/transactions/submit`;
const submitted = await fetch(urlSubmit, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({transaction: txToSubmitOnChain.to_hex()}),
});
const output = await submitted.json();
if (!submitted.ok) {
console.error("❌ Transaction submission failed:", output);
Deno.exit(1);
}
console.log("✅ Admin update transaction submitted successfully");
console.log("Transaction hash:", output.transactionId);What's happening here:
The tx-builder validates our spend transaction and creates a Cardano transaction
We sign with the admin's private key (required for "AdminUpdate" authorization)
The transaction is submitted to the Cardano network
On success, we get back a transaction hash for tracking
Running the Example
Find your reference token UTXO:
Use CardanoScan to search for your policy ID from the mint
Locate the reference token (label 100) at the smart contract address
Update
TX_HASHandTX_OUTPUT_INDEXin your script
Modify the metadata in the
datum.value.metadatasection as desiredRun the update script:
deno run --allow-all admin-update.ts
Expected Output
🔄 Applying parameters to blueprint...
✅ Parameters applied successfully
Parameterized script hash: 5da506b0b8d9c7d9df247c7684aead8eb6fdac1cbfb2dd7cc33d6499
Script address: addr_test1wpw62p4shrvu0kwly378dp9w4k8tdldvrjlm9htucv7kfxggmdaj6
✅ Transaction built successfully
✅ Admin update transaction submitted successfully
Transaction hash: d330e666c15e2f19e54b49aff64e69aa134d25242b7dadd95d6aba570a7c1861Understanding the Update Process
Spend Validator Logic
The CIP-68 update process uses a spend validator that:
Validates Authorization: Checks that the admin has signed the transaction
Preserves Token Integrity: Ensures the reference token stays at the smart contract
Allows Metadata Changes: Permits updates to the inline datum containing metadata
Maintains CIP-68 Compliance: Enforces proper metadata structure and versioning
Authorization Patterns
Admin Update (shown in this example):
Requires admin signature
No fee payment needed
Full metadata update permissions
User Update (alternative pattern):
Requires user token ownership proof
Fee payment to admin
Limited metadata update permissions
Transaction Flow
Input: Spend the existing reference token UTXO
Validation: Smart contract validates admin signature
Output: Create new reference token UTXO with updated metadata
Result: Metadata is updated while preserving token integrity
Key Requirements
Track UTXO locations: Reference tokens move with each update
Use same script hash: From your original mint parameterization
Admin signature required: Only the minting admin can perform admin updates
Next Steps
This example demonstrates the complete CIP-68 admin update workflow. For production use:
Add UTXO discovery to automatically find current reference token locations
Implement user updates with fee payment and user token validation
Add batch operations for updating multiple tokens
Create metadata validation to ensure proper format and constraints
Implement update history tracking for audit trails
References
CIP-68 Specification - Official standard
Smart Contract Logic Guide - Validator implementation details
Minting Example - How to create CIP-68 tokens
Last updated
Was this helpful?

