# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dev.ada-anvil.io/guides/nft-and-ft/mint-nft-cip-68/smart-contract/user-update-example.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
