Skip to main content
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 Difference from Basic Contracts

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

Deployment

Deployment differs from the basic example. UUPS pattern is used in the example below:
import { Signer } from "ethers";
import { MyUpgradeableProxy } from "../../typechain-types";
import { deployUpgradable } from '@tonappchain/evm-ccl'
import { DeployProxyOptions } from "@openzeppelin/hardhat-upgrades/dist/utils";
import hre from 'hardhat';

const proxyOptsUUPS: DeployProxyOptions = {
    kind: "uups",
    unsafeAllow: ["constructor"]
};

export async function deployMyUpgradeableProxy(
    deployer: Signer,
    crossChainLayerAddress: string,
    owner?: string,
): Promise<MyUpgradeableProxy> {
    const myUpgradeableProxy = await deployUpgradable<MyUpgradeableProxy>(
        deployer,
        hre.artifacts.readArtifactSync('MyUpgradeableProxy'),
        [owner || await deployer.getAddress(), crossChainLayerAddress],
        proxyOptsUUPS,
        undefined,
        true // verbose
    );

    await myUpgradeableProxy.waitForDeployment();
    return myUpgradeableProxy;
}

main().catch(console.error);

What’s Next?

Advanced Custom Proxy

Learn even more sophisticated development patterns