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

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/contract-dev/upgrades",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Upgrading contracts

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

The address of a contract is determined by its [initial code and state](/foundations/status). A contract can upgrade its code while preserving its address. This is useful for fixing bugs, adding features, or adapting to protocol changes without migrating to a new address.

Upgrades are critical when other contracts reference the contract being upgraded. For example, [NFT items](/standard/tokens/nft/how-it-works) reference their collection contract. The collection admin cannot modify these references stored in existing NFT items. Without upgrades, fixing bugs or adding features would require deploying a new collection and migrating all items—an expensive and complex process. Upgrades solve this by allowing the collection contract to evolve in place while preserving all existing references.

The pattern is also essential for [vanity](/contract-dev/vanity) contracts and protocols such as distributed exchange (DEX) where preserving the contract address is critical.

## How upgrades work

Tolk provides two functions for upgrades.

* `contract.setCodePostponed(code: cell)` — schedules the code to be replaced during the [action phase](/foundations/phases#action-phase). The new code takes effect after the current transaction completes.
* `contract.setData(data: cell)` — immediately replaces the contract's persistent storage. This happens during the [compute phase](/foundations/phases#compute-phase), before the transaction ends.

Key difference: `setCodePostponed()` applies changes *after* the current transaction, while `setData()` applies changes *immediately*. This means the new code won't run until the next message arrives, but the new data is already active.

<Aside type="caution" title="Funds at risk">
  Contract upgrades change code behavior and can affect funds or contract state. Unauthorized upgrades can cause loss of control or funds. Restrict upgrade messages to trusted admin addresses only.
</Aside>

<Aside type="caution" title="Ethics">
  Use delayed upgrades to allow users to [react to compromised admin keys](https://blog.trailofbits.com/2025/06/25/maturing-your-smart-contracts-beyond-private-key-risk/) or [unwanted updates](https://en.wikipedia.org/wiki/Exit_scam).
</Aside>

## Basic upgrade pattern

The contract accepts upgrade messages containing new code and data. Only the admin can trigger upgrades.

### How it works

1. Admin sends upgrade message. The message contains new code, data, or both.
2. Contract verifies sender. Checks that the sender is the admin address.
3. Code is scheduled. If new code is provided, `setCodePostponed()` schedules it for replacement.
4. Data is upgraded. If new data is provided, `setData()` immediately replaces the storage.
5. Transaction completes. The action phase executes, applying the new code.
6. Next message uses new code. Subsequent messages execute with the upgraded logic.

The upgrade happens immediately in a single transaction. The new code becomes active after the transaction completes. Any data replacement happens during the compute phase, so the new data is immediately available when the transaction ends.

If there's not enough Toncoin to execute action phase with the code update, but enough to execute compute phase with data update, the contract state changes will be reverted. Make sure to thoroughly test the upgrade script for possible gas issues, and provide enough Toncoin to execute the upgrade transaction completely.

### Example

```tolk title="Tolk" expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
struct (0x1111) UpgradeContract {
    data: cell?
    code: cell?
}

type AllowedMessages = UpgradeContract

fun onInternalMessage(in: InMessage) {

    val msg = lazy AllowedMessages.fromSlice(in.body);

    match (msg) {

        UpgradeContract => {
            var storage = lazy Storage.load();
            assert (in.senderAddress == storage.adminAddress) throw 1111;
            if (msg.code != null) {
                contract.setCodePostponed(msg.code!);
            }
            if (msg.data != null) {
                contract.setData(msg.data!);
            }
        }

        else => {
            // just accept TON
        }
    }
}

struct Storage {
    adminAddress: address
}

fun Storage.load() {
    return Storage.fromCell(contract.getData());
}

fun Storage.save(self) {
    contract.setData(self.toCell());
}
```

## Delayed upgrades for production safety

When upgrading protocols that are already running and have users, delayed upgrades are a best practice. This provides additional security layers: if an admin is compromised, there is time to react. Users can also see the upgrade and withdraw funds from the protocol if it has been compromised.

The pattern adds a time delay between requesting and approving an upgrade. The admin must first request an upgrade, wait for a timeout period, then approve it.

### How it works

1. Admin requests upgrade. Sends `RequestUpgrade` message with new code and data
2. Contract verifies and stores. Validates admin, ensures no pending request, stores upgrade details with timestamp
3. Timeout period. The contract enforces a waiting period before approval
4. Admin approves upgrade. Sends `ApproveUpgrade` message after timeout expires
5. Contract verifies timeout. Checks that enough time has passed since the request
6. Upgrade applies. Schedules new code with `setCodePostponed()` and upgrades data with `setData()`
7. Request cleared. Removes the pending request from storage

The admin can also send `RejectUpgrade` at any time to cancel a pending upgrade. This three-message flow (request → wait → approve or reject) gives users time to review changes and react if the admin account is compromised.

### Example

```tolk title="Tolk" expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
struct UpgradeContract {
    data: cell?
    code: cell?
}

struct CurrentRequest {
    newUpgrade: UpgradeContract
    timestamp: uint32
}

struct (0x00000001) RequestUpgrade {
    newUpgrade: UpgradeContract
}

struct (0x00000002) RejectUpgrade { }

struct (0x00000003) ApproveUpgrade { }

type AllowedMessages =
    | RequestUpgrade
    | RejectUpgrade
    | ApproveUpgrade

fun onInternalMessage(in: InMessage) {
    val msg = lazy AllowedMessages.fromSlice(in.body);

    match (msg) {

        RequestUpgrade => {
            var storage = lazy Storage.load();

            assert (in.senderAddress == storage.adminAddress) throw 100;
            assert (storage.CurrentRequest == null) throw 101;

            storage.CurrentRequest = {
                newUpgrade: msg.newUpgrade,
                timestamp: blockchain.now()
            };

            storage.save();
        }

        RejectUpgrade => {
            var storage = lazy Storage.load();

            assert (in.senderAddress == storage.adminAddress) throw 100;
            assert (storage.CurrentRequest != null) throw 201;

            storage.CurrentRequest = null;
            storage.save();
        }

        ApproveUpgrade => {
            var storage = lazy Storage.load();

            assert (in.senderAddress == storage.adminAddress) throw 100;
            assert (storage.CurrentRequest != null) throw 301;
            assert (storage.CurrentRequest!.timestamp + storage.timeout < blockchain.now()) throw 302;

            if (storage.CurrentRequest!.newUpgrade.code != null) {
                contract.setCodePostponed(storage.CurrentRequest!.newUpgrade.code!);
            }

            if (storage.CurrentRequest!.newUpgrade.data != null) {
                contract.setData(storage.CurrentRequest!.newUpgrade.data!);
            }
            else {
                storage.CurrentRequest = null;
                storage.save();
            }
        }

        else => {
            // just accepted tons
        }
    }
}

get fun currentRequest() {
    var storage = lazy Storage.load();
    return storage.CurrentRequest;
}

struct Storage {
    adminAddress: address,
    timeout: uint32,
    CurrentRequest: CurrentRequest?
}

fun Storage.load() {
    return Storage.fromCell(contract.getData());
}

fun Storage.save(self) {
    contract.setData(self.toCell());
}
```

## Hot upgrades for frequently upgraded contracts

Standard upgrade methods fail when a contract receives frequent updates. For example, DEX pools that update prices every second or lending protocols that continuously adjust interest rates. The problem: it is not possible to predict what data will be in storage when the upgrade transaction executes.

When an upgrade message with new code and data is sent, other transactions may execute before the upgrade arrives. By the time the upgrade applies, the prepared data may be stale. For a DEX pool, this can overwrite current price data with outdated values, breaking the protocol.

Hot upgrades solve this by scheduling a code change and immediately calling a migration function with the new code. The migration function runs in the same transaction that applies the upgrade. It reads the old storage structure, transforms it to match the new schema, and writes the upgraded storage. This preserves all state changes that happened between preparing the upgrade and executing it.

### How it works

1. Admin sends upgrade message. The message contains new code cell and optional additional data
2. Contract verifies sender. Checks that the sender is the admin address
3. Schedule code change. `setCodePostponed()` schedules the code replacement
4. Switch to new code. `setTvmRegisterC3()` immediately activates the new code in register [C3](/tvm/registers#c3-—-function-selector)
5. Call migration. Invoke `hotUpgradeData()` which now runs with the new code
6. Migration executes. The function reads old storage, transforms it, and writes new storage

The key mechanism: `setTvmRegisterC3()` switches the code register so the migration function executes with the new code in the same transaction. The migration reads the current storage state (preserving all updates), transforms it to the new schema, and saves it. When the transaction completes, the new code becomes permanent through `setCodePostponed()`.

<Aside type="caution">
  Hot upgrades require careful migration logic. Test migrations thoroughly on testnet. If the migration function fails, the contract becomes unusable. The `hotUpgradeData()` function runs only during upgrade messages, not on regular messages, preventing accidental repeated migrations.
</Aside>

### Example

The example shows a counter contract that adds a metadata field through a hot upgrade. The storage structure changes: the original version stores only `adminAddress` and `counter`. The new version adds `metadata` and reorders fields.

Original contract:

```tolk title="main.tolk" expandable theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import "@stdlib/tvm-lowlevel"

struct (0x00001111) HotUpgrade {
    additionalData: cell?
    code: cell
}

struct (0x00002222) IncreaseCounter {}

type AllowedMessages =
    | HotUpgrade
    | IncreaseCounter

// migration function must have method_id
@method_id(2121)
fun hotUpgradeData(additionalData: cell?) { return null; }

fun onInternalMessage(in: InMessage) {

    val msg = lazy AllowedMessages.fromSlice(in.body);

    match (msg) {

        HotUpgrade => {
            var storage = lazy Storage.load();
            assert (in.senderAddress == storage.adminAddress) throw 1111;

            contract.setCodePostponed(msg.code);

            setTvmRegisterC3(transformSliceToContinuation(msg.code.beginParse()));
            hotUpgradeData(msg.additionalData);
        }

        IncreaseCounter => {
            var storage = lazy Storage.load();
            storage.counter += 1;
            storage.save();
        }

        else => {
            // just accept TON
        }
    }
}

get fun counter() {
    var storage = lazy Storage.load();
    return storage.counter;
}

struct Storage {
    adminAddress: address
    counter: uint32
}

fun Storage.load() {
    return Storage.fromCell(contract.getData());
}

fun Storage.save(self) {
    contract.setData(self.toCell());
}
```

The `hotUpgradeData()` function in the original code returns `null` because it does not perform any migration. When the upgrade message arrives:

1. `contract.setCodePostponed(msg.code)` schedules the new code
2. `setTvmRegisterC3()` switches register C3 to the new code immediately
3. `hotUpgradeData(msg.additionalData)` is called and runs with the new code

New contract with migration:

```tolk title="new.tolk" theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
import "@stdlib/tvm-lowlevel"

struct (0x00001111) HotUpgrade {
    additionalData: cell?
    code: cell
}

struct (0x00002222) IncreaseCounter {}

type AllowedMessages =
    | HotUpgrade
    | IncreaseCounter

// migration function must have method_id
@method_id(2121)
fun hotUpgradeData(additionalData: cell?) {
    var oldStorage = lazy oldStorage.load();

    assert (additionalData != null) throw 1112;

    var storage = Storage {
        adminAddress: oldStorage.adminAddress,
        counter: oldStorage.counter,
        metadata: additionalData!
    };

    contract.setData(storage.toCell());
}

struct oldStorage {
    adminAddress: address
    counter: uint32
}

fun oldStorage.load() {
    return oldStorage.fromCell(contract.getData());
}


fun onInternalMessage(in: InMessage) {

    val msg = lazy AllowedMessages.fromSlice(in.body);

    match (msg) {

        HotUpgrade => {
            var storage = lazy Storage.load();
            assert (in.senderAddress == storage.adminAddress) throw 1111;

            contract.setCodePostponed(msg.code);

            setTvmRegisterC3(transformSliceToContinuation(msg.code.beginParse()));
            hotUpgradeData(msg.additionalData);
        }

        IncreaseCounter => {
            var storage = lazy Storage.load();
            storage.counter += 1;
            storage.save();
        }

        else => {
            // just accept TON
        }
    }
}

get fun metadata() {
    var storage = lazy Storage.load();
    return storage.metadata;
}

get fun counter() {
    var storage = lazy Storage.load();
    return storage.counter;
}
struct Storage {
    counter: uint32
    adminAddress: address
    metadata: cell
}

fun Storage.load() {
    return Storage.fromCell(contract.getData());
}

fun Storage.save(self) {
    contract.setData(self.toCell());
}
```

In the new version, `hotUpgradeData()` performs the migration:

1. Loads storage using the old structure (`oldStorage` with `adminAddress` and `counter`)
2. Creates new storage with the additional `metadata` field from `additionalData`
3. Reorders fields (`counter` moves before `adminAddress`)
4. Writes the migrated storage immediately with `contract.setData()`

The migration runs in the same transaction as the upgrade message. Any counter increments that happened between preparing the upgrade and executing it remain in storage because the migration reads the current state, not a pre-prepared snapshot. The migration function explicitly handles the structure change by reading fields from the old layout and writing them in the new layout.

### When to use hot upgrades

Use hot upgrades when:

* The contract receives frequent state updates (DEX pools, oracles, lending protocols)
* Storage changes between preparing and applying the upgrade would cause data loss
* You need to preserve all intermediate state transitions

Use standard upgrades when:

* The contract upgrades infrequently
* You can predict storage state at upgrade time
* Simpler upgrade logic reduces risk

## Combining delayed and hot upgrades

You can combine delayed upgrades with hot upgrades for production protocols that require both safety and structure migration. The delayed pattern provides time for users to review changes, while the hot upgrade mechanism handles storage migration without data loss.

<Aside title={"📁 Complete Example Code"}>
  You can find full working examples demonstrating all upgrade patterns in our [GitHub repository](https://github.com/ton-org/docs-examples/tree/main/contract-dev/Upgrading). This includes implementations for basic, delayed, and hot upgrade patterns.
</Aside>
