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!
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:
Spend Existing: Consume the current reference token UTXO
Validate Authority: Check authorization (admin OR user + fee)
Transform Metadata: Apply authorized changes to the datum
Recreate Token: Send updated reference token back to smart contract
Key Insight: Updates don't modify tokens in-place. They spend the old reference token and create a new one with updated metadata.
See it in action: Check out Admin Update Example and User Update Example for complete implementations.
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 tokenlovelace_paid != 1000000
- Incorrect fee amountcurrent_datum != expected_datum
- Unauthorized metadata field update
Admin Update Failures:
!list.has(extra_signatories, signer)
- Missing admin signatureinput.address != output.address
- Address preservation failed
Core Validation Failures:
datum not Cip68Metadata
- Invalid metadata structureinput.value != output.value
- Token integrity violated
Next Steps
User Update ExampleAdmin Update ExampleTechnical References
CIP-68 Specification - Official standard
Aiken Smart Contract Language - Contract development
Last updated
Was this helpful?