Update Logic

Understanding the smart contract - How to update CIP-68 NFT metadata on Cardano using Aiken spend validators. Deep dive into user vs admin update paths, fee mechanisms, and validation logic.

You've successfully minted CIP-68 tokens and now want to understand how metadata updates work through the spend validator!

Prerequisites: This guide assumes you understand CIP-68 minting logic and have successfully minted tokens using the minting example. The spend validator builds upon the mint validator's foundation.

Ready to implement? Skip to the admin update example or user update example and return here when you want to understand the underlying validation logic.

Conceptual Foundation

What the Spend Validator Does

While the mint validator creates CIP-68 token pairs, the spend validator enables metadata updates by controlling how reference tokens can be spent and recreated.

Core Responsibilities:

  • βœ… Authorizes Updates: Two paths - admin signature OR user token ownership + fee

  • βœ… Preserves Token Integrity: Reference tokens must stay at smart contract address

  • βœ… Maintains CIP-68 Compliance: Updated metadata follows proper structure

The Update Process

Every CIP-68 metadata update follows this sequence:

  1. Spend Existing: Consume the current reference token UTXO

  2. Validate Authority: Check authorization (admin OR user + fee)

  3. Transform Metadata: Apply authorized changes to the datum

  4. Recreate Token: Send updated reference token back to smart contract

Two Authorization Paths

πŸ‘€ User Updates:

  • Must own user token (222) and include it as transaction input

  • Must pay exactly 1 ADA fee to admin address

  • Can only update specific fields (e.g., "nickname")

  • Other metadata fields are preserved

πŸ‘‘ Admin Updates:

  • Admin must sign the transaction with their private key

  • No fee payment required

  • Can update any metadata fields, can modify entire metadata structure and version

Validation Flow Overview

The diagram below shows how the spend validator processes both authorization paths:

Authorization Logic Deep Dive

The spend validator implements two distinct authorization paths, each with specific validation requirements:

πŸ‘€ User Update Path

Users prove ownership and pay fees to update limited metadata fields:

// Validation logic for user updates
UserUpdate { nickname, fee_output_index, output_ref_user_token } -> {
  // Verify 1 ADA fee payment to admin
  let fee_output = self.outputs |> at(fee_output_index)
  let lovelace_paid = fee_output.value |> quantity_of("", "")
  let is_paid_to_admin = fee_output.address.payment_credential == admin_key
  
  // Verify user token ownership
  let user_token_input = find_input(self.inputs, output_ref_user_token)
  let user_token_quantity = user_token_input.output.value |> quantity_of(policy_id, user_token)
  
  // Apply selective metadata changes (only "nickname" allowed)
  let expected_datum = selective_metadata_update(datum, nickname)
  
  // All conditions must pass
  and {
    (lovelace_paid == 1000000)?,
    is_paid_to_admin?,
    (current_datum == expected_datum)?,
    (user_token_quantity == 1)?
  }
}

Requirements: User token ownership + 1 ADA fee + limited field updates

πŸ‘‘ Admin Update Path

Admins use signature authorization for full metadata control:

// Simple signature check for admin updates
AdminUpdate -> list.has(self.extra_signatories, signer)

Requirements: Admin signature only + full metadata permissions

Core Validation (Both Paths)

All updates must also pass these fundamental checks:

  • Address Preservation: input.address == output.address

  • Token Integrity: input.value == output.value (same token, updated metadata)

  • Datum Structure: Valid CIP-68 metadata format

Implementation Examples

User Update Transaction Structure

{
  scriptInteractions: [{
    purpose: "spend",
    // Reference token UTXO to update
    outputRef: "d330e666c15e2f19e54b49aff64e69aa134d25242b7dadd95d6aba570a7c1861#0",
    redeemer: {
      output_index: 0,
      update: {
        // New nickname as hex-encoded ByteArray
        nickname: Buffer.from("my_new_nickname").toString("hex"),
        // Output index for 1 ADA fee payment
        fee_output_index: 1,
        // User token UTXO reference (proves ownership)
        output_ref_user_token: {
          transaction_id: "1750a1c191646ade084c911fd9fd8a7c6b8372923e2305c4f4983663c2b236161",
          output_index: 1
        }
      }
    }
  }],
  
  outputs: [{
    // Updated reference token (same address)
    address: "addr_test1wpw62p4shrvu0kwly378dp9w4k8tdldvrjlm9htucv7kfxggmdaj6",
    assets: [{
      assetName: { name: "test_token", label: 100, format: "utf8" },
      policyId: "4983663c2b236161ad8e26c36dff9aee709a6adef53be2cc33d6499",
      quantity: 1
    }],
    datum: {
      type: "inline",
      value: {
        metadata: [["name", "test_token"], ["nickname", "my_new_nickname"]],
        version: 1
      }
    }
  }, {
    // 1 ADA fee to admin
    address: "addr_test1qpw62p4shrvu0kwly378dp9w4k8tdldvrjlm9htucv7kfxggmdaj6",
    value: { lovelace: 1000000 }
  }],
  
  // Include user token as input
  requiredInputs: ["828258201750a1c191646ade084c911fd9fd8a7c6b8372923e2305c4f4983663c2b236161015820..."]
}

Admin Update Transaction Structure

{
  scriptInteractions: [{
    purpose: "spend",
    outputRef: "d330e666c15e2f19e54b49aff64e69aa134d25242b7dadd95d6aba570a7c1861#0",
    redeemer: {
      output_index: 0,
      update: "AdminUpdate"  // Simple string, no additional fields
    }
  }],
  
  outputs: [{
    address: "addr_test1wpw62p4shrvu0kwly378dp9w4k8tdldvrjlm9htucv7kfxggmdaj6",
    assets: [{
      assetName: { name: "test_token", label: 100, format: "utf8" },
      policyId: "4983663c2b236161ad8e26c36dff9aee709a6adef53be2cc33d6499",
      quantity: 1
    }],
    datum: {
      type: "inline",
      value: {
        // Admin can update any fields and version
        metadata: [["name", "test_token"], ["description", "Updated by admin"], ["rarity", "legendary"]],
        version: 1
      }
    }
  }],
  
  // Admin signature required
  requiredSigners: ["a1b2c3d4e5f6789012345678901234567890123456789012345678901234"]
}

Production Considerations

Security Model

  • User Updates: Token ownership + 1 ADA fee + limited permissions

  • Admin Updates: Signature authorization + full permissions

  • Token Integrity: Reference tokens locked at smart contract address

  • Audit Trail: All metadata changes recorded on-chain

Common Errors

User Update Failures:

  • user_token_quantity != 1 - User doesn't own the token

  • lovelace_paid != 1000000 - Incorrect fee amount

  • current_datum != expected_datum - Unauthorized metadata field update

Admin Update Failures:

  • !list.has(extra_signatories, signer) - Missing admin signature

  • input.address != output.address - Address preservation failed

Core Validation Failures:

  • datum not Cip68Metadata - Invalid metadata structure

  • input.value != output.value - Token integrity violated

Next Steps

User Update ExampleAdmin Update Example

Technical References

Last updated

Was this helpful?