> ## Documentation Index
> Fetch the complete documentation index at: https://partner.tren.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Metadata Changes

> Trench token metadata can change after launch when holders vote it through. Watch ProposalSettledEvent and refetch whenever executed is true.

Token metadata on Trench is not fixed at launch. Holders vote on a token's name, symbol, image, description, and links, and when a proposal passes, the new values are written on-chain. Anything you cached before that point is wrong from that moment on.

<Warning>
  This applies to every integration that displays a Trench token, not only the ones that surface governance. If you cache a token's name or image and never refresh it, your users will keep seeing the old values indefinitely. Nothing in the trading endpoints will tell you, because the mint does not change.
</Warning>

## The Rule

Watch for `ProposalSettledEvent` from the Trench governance program. When one arrives with `executed: true`, the proposal passed and its action was written on-chain, so refetch that token's metadata.

## Why Settlement Is the Moment

Voting closing does not apply anything. A proposal sits closed but unapplied until someone calls [settle](/api-reference/governance-vote), which computes the outcome and, if it passed, writes the change. Settling is permissionless, so it can happen minutes or days after voting ends, and anyone can trigger it, including you.

There is therefore no fixed delay between a vote ending and metadata changing, which is why a timer based on `votingEndsAtUnix` is not a substitute for watching for the event.

## The Event

The governance program at `Fmy9Mi1RbcHPEht6ydGmrLGFCiU3P9JSUomVDEsYwF2J` emits `ProposalSettledEvent` once per settlement.

| Field                   | Type   | Meaning                                                                              |
| ----------------------- | ------ | ------------------------------------------------------------------------------------ |
| `governance`            | pubkey | The governance account that settled                                                  |
| `proposal`              | pubkey | The proposal that settled                                                            |
| `proposer`              | pubkey | Wallet that created it                                                               |
| `proposal_index`        | u64    | Index within the governance                                                          |
| `action`                | enum   | `UpdateContent`, `ExtendMutability`, or `FinalizeMetadata`                           |
| `outcome`               | enum   | `Passed`, `Failed`, `Expired`, or `Superseded`                                       |
| `executed`              | bool   | Whether the action was applied                                                       |
| `content_revision`      | u64    | The governance's content revision after settlement                                   |
| `finalized`             | bool   | Whether metadata is now permanently locked                                           |
| `mutable_until`         | i64    | When metadata locks, after any extension                                             |
| `mutability_extended`   | bool   | Whether the window has now been extended                                             |
| Seven `*_locked` flags  | bool   | Per-field locks for name, symbol, image, description, website, twitter, and telegram |
| `yes_votes`, `no_votes` | u64    | Final tally                                                                          |
| `participating_supply`  | u64    | Total weight cast                                                                    |
| `participating_voters`  | u32    | Distinct qualifying voters                                                           |

The field to branch on is `executed`. Most settlements change nothing, because the proposal failed, expired, or was superseded, and only `executed: true` means state was written.

<Note>
  The event carries the governance state as it stands after settlement, so `content_revision` and the lock flags can be applied straight to your cache without a follow-up read. Only the metadata itself has to be fetched.
</Note>

## What Each Action Changes

| Action             | What `executed: true` means                       | Refetch metadata                              |
| ------------------ | ------------------------------------------------- | --------------------------------------------- |
| `UpdateContent`    | Name, symbol, and the metadata URI were rewritten | Yes                                           |
| `ExtendMutability` | The mutability window moved                       | No, but update your cached `mutableUntilUnix` |
| `FinalizeMetadata` | Metadata is now locked permanently                | No, but stop expecting further changes        |

Refetching on every `executed: true` regardless of action is simpler and always correct. The distinction only matters if a refetch is expensive for you.

## What to Refetch

A content change touches two places, and both have to be re-read.

<Steps>
  <Step title="The metadata account">
    Name and symbol live on-chain, alongside the URI. The account address is `metadata` on the [governance read](/api-reference/governance-read) response.
  </Step>

  <Step title="The JSON at the URI">
    Image, description, and links live off-chain in the document the URI points at. A content change pins new JSON, so the URI itself changes.
  </Step>
</Steps>

<Warning>
  Key your image cache on the metadata URI rather than on the mint. New content is pinned at a new address, so a cache keyed on the mint will keep serving the old image even after you have refetched everything else correctly.
</Warning>

## contentRevision Is Your Cache Key

The governance object exposes `contentRevision`, which increments on every applied content change. Store it alongside whatever you cached and you can establish staleness by comparison rather than by guesswork.

```javascript theme={null}
const { governance } = await trench.get(`/partner/v1/governance/${mint}`);

if (governance.contentRevision !== cached.contentRevision) {
  await refreshTokenMetadata(mint);
}
```

This matters because subscriptions are lossy. A websocket drops messages across reconnects, deploys, and outages, so a revision comparison is what catches the changes your listener missed.

## Watching in Practice

Subscribe to program logs for the governance program and decode every `Program data:` line you receive. Decoding needs the governance IDL, which Trench provides, so ask your contact for it if you do not have it yet.

```javascript theme={null}
import { readFileSync } from "node:fs";
import { BorshEventCoder } from "@coral-xyz/anchor";
import { Connection, PublicKey } from "@solana/web3.js";

const GOVERNANCE_PROGRAM_ID = new PublicKey(
  "Fmy9Mi1RbcHPEht6ydGmrLGFCiU3P9JSUomVDEsYwF2J",
);
const PREFIX = "Program data: ";

const idl = JSON.parse(readFileSync("./trench_governance.json", "utf8"));
const coder = new BorshEventCoder(idl);
const connection = new Connection(process.env.SOLANA_RPC_URL, "confirmed");

connection.onLogs(GOVERNANCE_PROGRAM_ID, ({ err, logs }) => {
  if (err) return;

  for (const log of logs) {
    if (!log.startsWith(PREFIX)) continue;

    const event = coder.decode(log.slice(PREFIX.length));
    if (event?.name !== "ProposalSettledEvent") continue;
    if (!event.data.executed) continue;

    onGovernanceSettled({
      governance: event.data.governance.toBase58(),
      contentRevision: event.data.content_revision.toString(),
      contentChanged: "UpdateContent" in event.data.action,
    });
  }
}, "confirmed");
```

Two details catch people out. As of `@coral-xyz/anchor` 0.32, the decoder hands back the IDL's snake\_case field names rather than the camelCase Anchor uses elsewhere, so the field is `content_revision` and not `contentRevision`. Enum values also arrive as an object keyed by the variant name, which is why the action is tested with `in` rather than compared against a string.

<Warning>
  The event cannot be read positionally. An `UpdateContent` action carries variable-length strings, so every field after `action`, including `executed` and `content_revision`, sits at an offset that depends on the proposal's own contents. Decode it properly rather than slicing bytes at fixed offsets.
</Warning>

### Subscribing per Token

If you follow a small, fixed set of tokens, an account subscription skips decoding altogether. The governance account is written on every settlement, so a change notification is your cue to re-read.

```javascript theme={null}
const { governance } = await trench.get(`/partner/v1/governance/${mint}`);

connection.onAccountChange(
  new PublicKey(governance.governance),
  () => refreshTokenMetadata(mint),
  "confirmed",
);
```

This costs one subscription per token, so it does not stretch to an open-ended catalogue, but it is the shortest path to correct behaviour when the set is small.

Whichever you choose, reconcile on a schedule as well. Re-reading `contentRevision` for your tracked tokens on a slow loop costs little and bounds how long a missed update can survive.

## When You Can Stop Watching

A token whose governance reports `finalized: true` has locked its metadata permanently, and no further content change is possible. The same becomes true once `mutableUntilUnix` has passed, whether or not anyone voted. Both are safe points at which to drop a token from your refresh set.
