> For the complete documentation index, see [llms.txt](https://dev.ada-anvil.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dev.ada-anvil.io/guides/nft-and-ft/mint-nft-cip-68/smart-contract/user-update-example.md).

# User Update Example

{% hint style="info" %}
**Want to understand the smart contract validation logic first?** Check out the [Smart Contract Logic Guide](/guides/nft-and-ft/mint-nft-cip-68/smart-contract/update-logic.md) to learn how the contract validates user updates. Otherwise, you can dive right into the implementation below!
{% endhint %}

### Full Code Example

{% hint style="info" %}
**Want to see the complete code first?** Check out the [full working implementation](https://github.com/Cardano-Forge/anvil-api-examples/blob/main/smart-contracts/cip-68/mint-cip68/customer-update.ts) on GitHub, then come back here for the step-by-step breakdown.
{% endhint %}

### Implementation Overview

This example demonstrates **user-driven metadata updates** where token holders can update their own NFT metadata by:

* Proving ownership of the user token (222)
* Paying a 1 ADA fee to the admin
* Updating limited metadata fields (e.g., nickname)
* Following the spend validator's authorization rules

### Prerequisites

This guide assumes you have completed the [Minting Example](/guides/nft-and-ft/mint-nft-cip-68/smart-contract/mint-example.md) and have:

1. **Existing CIP-68 tokens** - Reference and user tokens from a successful mint
2. **User token ownership** - The user token (222) must be in the customer's wallet
3. **Reference token location** - Current UTXO of the reference token at the script address
4. **Blockfrost API access** - For dynamic UTXO discovery
5. **Understanding of user updates** - Review [update logic](/guides/nft-and-ft/mint-nft-cip-68/smart-contract/update-logic.md) for validation details

### Step-by-Step Implementation

Let's build a user-driven CIP-68 metadata update script step by step. Each section explains the concept and shows the corresponding code.

#### Step 1: Setup and Configuration

Create update file with the required imports and configuration:

```typescript
import { Buffer } from "node:buffer";
import {
  FixedTransaction,
  PrivateKey,
} from "npm:@emurgo/cardano-serialization-lib-nodejs@14.1.1";

// Import wallet files and constants
import customer from "./wallet-customer.json" with { type: "json" };
import adminWallet from "./wallet-mint-sc-policy.json" with { type: "json" };
import { API_URL, HEADERS } from "../../../utils/constant.ts";

// Configuration
const CUSTOMER_ADDRESS = customer.base_address_preprod;
const ADMIN_KEY_HASH = adminWallet.key_hash;
const ADMIN_ADDRESS = adminWallet.base_address_preprod;
const assetName = "cip68_1753999309623"; // Your minted asset name
const nickname = "customer_updated"; // New nickname to set

// Import Blockfrost utility for dynamic UTXO fetching
import { 
  getUtxos, 
  findUserTokenUTXO, 
  findReferenceTokenUTXO 
} from "../../../fetch-utxos-from-the-backend/utxos/blockfrost.ts";

// Blockfrost configuration
const BLOCKFROST_BASE_URL = "https://cardano-preprod.blockfrost.io/api/v0";
const BLOCKFROST_API_KEY = Deno.env.get("BLOCKFROST_PROJECT_ID");

if (!BLOCKFROST_API_KEY) {
  console.error("❌ BLOCKFROST_PROJECT_ID environment variable is required");
  Deno.exit(1);
}
```

**Key differences from admin updates:**

* **Customer wallet** signs the transaction (not admin)
* **Admin address** receives the 1 ADA fee payment
* **Blockfrost integration** for dynamic UTXO discovery
* **Asset name** must match the originally minted token

#### Step 2: Apply Parameters and Find UTXOs

Apply parameters to the blueprint and locate the required UTXOs:

```typescript
// Load blueprint and apply parameters (same as other examples)
import blueprint from "../aiken-mint-cip-68/plutus.json" with { type: "json" };

console.log("🔄 Applying parameters to blueprint...");
const applyParamsResponse = await fetch(`${API_URL}/blueprints/apply-params`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    params: { [blueprint.validators[0].hash]: [ADMIN_KEY_HASH] },
    blueprint: blueprint
  }),
});

const applyParamsResult = await applyParamsResponse.json();
const parameterizedScript = applyParamsResult.preloadedScript;
const POLICY_ID = Object.keys(applyParamsResult.addresses)[0];
const SC_ADDRESS = applyParamsResult.addresses[POLICY_ID].bech32;

console.log("Parameterized script hash:", POLICY_ID);
console.log("Script address:", SC_ADDRESS);

// Find reference token UTXO at script address
const referenceTokenUTXO = await findReferenceTokenUTXO(
  BLOCKFROST_BASE_URL,
  BLOCKFROST_API_KEY,
  POLICY_ID,
  assetName,
  SC_ADDRESS
);

// Find user token UTXO at customer address
const userTokenUTXO = await findUserTokenUTXO(
  BLOCKFROST_BASE_URL,
  BLOCKFROST_API_KEY,
  POLICY_ID,
  assetName,
  CUSTOMER_ADDRESS
);

// Get user token UTXO in string format for requiredInputs
const utxos = await getUtxos(BLOCKFROST_BASE_URL, BLOCKFROST_API_KEY, CUSTOMER_ADDRESS);
const utxoString = `000de140${Buffer.from(assetName).toString("hex")}`;
const userUtxo = utxos.find((utxo) => utxo.includes(utxoString));

if (!userUtxo) {
  console.error("❌ User token UTXO not found at customer address");
  Deno.exit(1);
}

console.log(`Reference token: ${referenceTokenUTXO.transaction_id}#${referenceTokenUTXO.output_index}`);
console.log(`User token: ${userTokenUTXO.transaction_id}#${userTokenUTXO.output_index}`);
```

**What's happening here:**

* **Reference token discovery**: Finds the current reference token UTXO at the script address
* **User token discovery**: Locates the user token in the customer's wallet (proves ownership)
* **UTXO string format**: Gets the hex-encoded UTXO string needed for `requiredInputs`
* **Validation**: Ensures both tokens exist before proceeding

#### Step 3: Build the User Update Transaction

Now we build the spend transaction with user authorization:

```typescript
const input = {
  changeAddress: CUSTOMER_ADDRESS,
  message: "CIP-68 Customer Update Example",
  
  // SCRIPT INTERACTIONS: Tell the validator how to spend the reference token
  scriptInteractions: [
    {
      purpose: "spend", // Spending a UTXO locked at smart contract address
      
      // REFERENCE TOKEN UTXO: The UTXO containing the reference token to update
      outputRef: {
        txHash: referenceTokenUTXO.transaction_id,
        index: referenceTokenUTXO.output_index,
      },
      
      // REDEEMER: Authorization data for the spend validator
      redeemer: {
        type: "json",
        value: {
          output_index: 0, // Index of output containing updated reference token
          update: {
            // USER UPDATE: Triggers UserUpdate validation branch
            nickname: Buffer.from(nickname).toString("hex"), // New nickname in hex
            fee_output_index: 1, // Index of output paying 1 ADA fee to admin
            
            // USER TOKEN REFERENCE: Validator uses this to verify ownership
            output_ref_user_token: {
              transaction_id: userTokenUTXO.transaction_id,
              output_index: userTokenUTXO.output_index,
            },
          },
        },
      },
    },
  ],
```

**Key redeemer components:**

* **nickname**: The updated value in hex format (limited user permission)
* **fee\_output\_index**: Points to the 1 ADA fee payment output
* **output\_ref\_user\_token**: References the user token for ownership verification

#### Step 4: Define Transaction Outputs

Create the outputs for the updated reference token and fee payment:

```typescript
  // TRANSACTION OUTPUTS: What the transaction creates
  outputs: [
    {
      // OUTPUT 0: Updated reference token back to script address
      address: SC_ADDRESS,
      assets: [
        {
          assetName: { name: assetName, label: 100, format: "utf8" },
          policyId: POLICY_ID,
          quantity: 1,
        },
      ],
      // UPDATED DATUM: Contains new metadata with updated nickname
      datum: {
        type: "inline",
        shape: {
          validatorHash: POLICY_ID,
          purpose: "spend",
        },
        value: {
          metadata: [
            ["name", assetName], // Must match original minted asset name
            ["nickname", "customer_updated"], // Updated field
          ],
          version: 1,
        },
      },
    },
    {
      // OUTPUT 1: 1 ADA fee payment to admin
      address: ADMIN_ADDRESS,
      lovelace: "1000000", // Exactly 1 ADA - validator verifies this amount
    },
  ],

  requiredSigners: [], // No admin signature required for user updates
  referenceInputs: [],
  requiredInputs: [userUtxo!], // Include user token as input (proves ownership)
  preloadedScripts: [parameterizedScript],
};
```

**Critical output requirements:**

* **Output 0**: Updated reference token with new metadata at script address
* **Output 1**: Exactly 1 ADA fee payment to admin address
* **Datum preservation**: Original asset name must be preserved
* **User token input**: Must be included via `requiredInputs` for ownership proof

#### Step 5: Build, Sign, and Submit Transaction

Build and submit the user update transaction:

```typescript
// Build 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 with customer's private key (not admin)
const txToSubmitOnChain = FixedTransaction.from_bytes(
  Buffer.from(transaction.complete, "hex")
);
txToSubmitOnChain.sign_and_add_vkey_signature(
  PrivateKey.from_bech32(customer.skey)
);

// Submit to network
const submitted = await fetch(`${API_URL}/transactions/submit`, {
  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("✅ Customer update transaction submitted successfully");
console.log("Transaction hash:", output.transactionId);
```

**What's happening here:**

* **Customer signature**: Only the customer signs (proves they own the user token)
* **No admin signature**: Admin authorization comes from fee payment, not signature
* **Validation on-chain**: Smart contract validates ownership and fee payment

### Running the Example

#### Prerequisites Setup

1. **Complete the minting example first** to have CIP-68 tokens available
2. **Set up environment variables**:

   ```bash
   # PowerShell/Windows
   $env:BLOCKFROST_PROJECT_ID='your_blockfrost_api_key'

   # Bash/macOS/Linux
   export BLOCKFROST_PROJECT_ID='your_blockfrost_api_key'
   ```
3. **Update configuration**:
   * Set `assetName` to match your minted token
   * Modify `nickname` to your desired update value
   * Ensure wallet files are properly configured
4. **Run the user update script**:

   ```bash
   deno run --allow-all user-update.ts
   ```

#### Expected Output

```
🔄 Applying parameters to blueprint...
✅ Parameters applied successfully
Parameterized script hash: 4983663c2b236161ad8e26c36dff9aee709a6adef53be2...
Script address: addr_test1wpycxe3u9v3kzcdd3cnvxm0lnth8pxn2mm6nhch9f6pagxq...
Reference token: 1750a1c191646ade084c911fd9fd8a7c6b8372923e2305c4f4...#0
User token: 1750a1c191646ade084c911fd9fd8a7c6b8372923e2305c4f4...#1
✅ Transaction built successfully
✅ Customer update transaction submitted successfully
Transaction hash: b2cb92868e58238edb5646775847fca911c46c4d2206bb66b64c4df0af06187b
```

### User vs Admin Updates

This user update approach differs from admin updates in three key ways:

| Aspect            | User Updates                    | Admin Updates        |
| ----------------- | ------------------------------- | -------------------- |
| **Authorization** | Token ownership + 1 ADA fee     | Admin signature only |
| **Permissions**   | Limited fields (e.g., nickname) | Full metadata access |
| **Cost**          | 1 ADA fee to admin              | Free for admin       |

The economic barrier prevents spam while giving users control over their metadata.

### Troubleshooting

#### Common Validation Failures

If your transaction fails validation, check these common issues:

* **`user_token_quantity != 1`** - User token not included in transaction inputs
* **`lovelace_paid != 1000000`** - Fee amount is not exactly 1 ADA
* **`!is_paid_to_admin`** - Fee payment sent to wrong address
* **`current_datum != expected_datum`** - Attempting to update restricted metadata fields

{% hint style="info" %}
For detailed validator logic, see the [Update Logic Guide](/guides/nft-and-ft/mint-nft-cip-68/smart-contract/update-logic.md).
{% endhint %}

### Key Requirements

* **Include user token** in `requiredInputs` for ownership proof
* **Pay exactly 1 ADA** (1,000,000 lovelace) to admin address
* **Update only permitted fields** as defined by the smart contract
* **Track UTXO locations** dynamically (tokens move with each update)

### Next Steps

You now have a complete user-driven update implementation. For production applications, consider:

* **Batch Updates**: Multiple metadata fields in one transaction
* **Update History**: Track metadata changes for audit trails
* **Fee Estimation**: Display costs before transaction submission
* **User Interface**: Build a frontend for non-technical users

### References

* [Update Logic Guide](/guides/nft-and-ft/mint-nft-cip-68/smart-contract/update-logic.md) - Deep dive into Aiken implementation
* [Admin Update Example](/guides/nft-and-ft/mint-nft-cip-68/smart-contract/admin-update-example.md) - Admin-driven updates
* [CIP-68 Specification](https://cips.cardano.org/cips/cip68/) - Official standard
* [Working Implementation](https://github.com/Cardano-Forge/anvil-api-examples/blob/main/smart-contracts/cip-68/mint-cip68/customer-update.ts) - Complete code
