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.

Want to understand the smart contract validation logic first? Check out the Update Logic Guide to learn how the contract ensures proper authorization for metadata updates. Otherwise, you can dive right into the implementation below!

Full Code Example

Want to see the complete code first? Check out the full working implementation on GitHub, then come back here for the step-by-step breakdown.

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:

  1. Existing CIP-68 tokens - Reference and user tokens from a successful mint

  2. Current reference token UTXO - Transaction hash and output index where the reference token currently resides

  3. 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

  1. 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_HASH and TX_OUTPUT_INDEX in your script

  2. Modify the metadata in the datum.value.metadata section as desired

  3. Run 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: d330e666c15e2f19e54b49aff64e69aa134d25242b7dadd95d6aba570a7c1861

Understanding the Update Process

Spend Validator Logic

The CIP-68 update process uses a spend validator that:

  1. Validates Authorization: Checks that the admin has signed the transaction

  2. Preserves Token Integrity: Ensures the reference token stays at the smart contract

  3. Allows Metadata Changes: Permits updates to the inline datum containing metadata

  4. 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

  1. Input: Spend the existing reference token UTXO

  2. Validation: Smart contract validates admin signature

  3. Output: Create new reference token UTXO with updated metadata

  4. 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:

  1. Add UTXO discovery to automatically find current reference token locations

  2. Implement user updates with fee payment and user token validation

  3. Add batch operations for updating multiple tokens

  4. Create metadata validation to ensure proper format and constraints

  5. Implement update history tracking for audit trails

References

Last updated

Was this helpful?