Guides
Update 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:
- 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#
Technical References#
- CIP-68 Specification - Official standard
- Aiken Smart Contract Language - Contract development