TAC provides TacProxyV1Upgradeable for contracts that need to be upgraded after deployment. Use this base contract when you need upgrade functionality.

Basic Upgradeable Structure

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { TacProxyV1Upgradeable } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1Upgradeable.sol";
import { TacHeaderV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol";

contract MyUpgradeableProxy is
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable,
    TacProxyV1Upgradeable
{
    // Your state variables go here
    mapping(string => uint256) public userBalances;
    uint256 public totalProcessed;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(address owner, address crossChainLayer)
        public
        initializer
    {
        __UUPSUpgradeable_init();
        __Ownable_init(owner);
        __TacProxyV1Upgradeable_init(crossChainLayer);

        // Initialize your contract state
        totalProcessed = 0;
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override
        onlyOwner
    {}

    // Your proxy functions go here
    function processRequest(bytes calldata tacHeader, bytes calldata arguments)
        external
        _onlyCrossChainLayer
    {
        TacHeaderV1 memory header = _decodeTacHeader(tacHeader);
        uint256 amount = abi.decode(arguments, (uint256));

        userBalances[header.tvmCaller] += amount;
        totalProcessed += amount;

        emit RequestProcessed(header.tvmCaller, amount);
    }

    event RequestProcessed(string indexed user, uint256 amount);
}

Key Differences from Basic Contracts

1. Multiple Inheritance (Order Matters!)

contract MyUpgradeableProxy is
    Initializable,           // Must be first
    OwnableUpgradeable,      // Access control
    UUPSUpgradeable,         // Upgrade mechanism
    TacProxyV1Upgradeable    // TAC functionality (last)
The inheritance order is critical and must follow OpenZeppelin’s recommended pattern.

2. Constructor vs Initialize

// ❌ DON'T use constructor for setup
constructor(address crossChainLayer) {
    // This won't work properly with upgradeable contracts!
}

// ✅ DO use initializer function
function initialize(address owner, address crossChainLayer)
    public
    initializer
{
    __UUPSUpgradeable_init();
    __Ownable_init(owner);
    __TacProxyV1Upgradeable_init(crossChainLayer);
}

3. Constructor Disabling

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
    _disableInitializers(); // Prevents initialization of implementation
}
This prevents the implementation contract from being initialized, which could be a security risk.

4. Upgrade Authorization

function _authorizeUpgrade(address newImplementation)
    internal
    override
    onlyOwner
{}
This function controls who can upgrade the contract. Only the owner can upgrade in this example.

What’s Next?

You now understand both basic and upgradeable proxy patterns: