How to interact with UUPS upgradeable contract using web3?

I have an ERC20 token already deployed on the Ropsten testnet with two versions.

V1 is a simple unproxied ERC20 token and looks like this:

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    constructor() ERC20("MyToken", "MTK") {}

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);

I can interact with this contract using web3:

const Web3 = require('web3');
const MyToken = require('./build/contracts/MyToken.json');
const HDWalletProvider = require('@truffle/hdwallet-provider');

const provider = new HDWalletProvider(process.env.ACCOUNT_SECRET, process.env.INFURA_URL);
const web3 = new Web3(provider);
const contract = new web3.eth.Contract(MyToken.abi, process.env.CONTRACT_ADDRESS);

For example, here is a call that retrieves the owner of the contract:

await contract.methods.owner().call();

On the other hand, V2 is a UUPS upgradeable contract which looks like this:

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

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function initialize() initializer public {
        __ERC20_init("MyToken", "MTK");

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);

    function _authorizeUpgrade(address newImplementation)

To interact with V2 using the same web3 nodejs code, I used the V2 ABI and contract address. However, whenever I retrieve the owner using the same code, it always returns the zero address.

I think the call should be proxied or something, but I don't know how and I can't find resources (docs/tutorials) on this. Please help.

Note: To be clear, I'm not trying to upgrade from V1 to V2. I just used versioning to differentiate them in my post. Also, I use truffle instead of hardhat for migrations and tests.

Bumping this thread.

Some more info:

  • Contracts V1 and V2 are generated from Nothing was modified.
  • V2 passes the get owner, symbol, and name truffle tests.

Please use our Upgrades Plugin for Truffle in order to deploy a UUPS contract.

Hi, thanks for your response. I've already deployed the contract using truffle upgrades plugins. My problem is interacting with the deployed contract with web3.

You need to use V2 ABI but proxy address, not implementation address, which is what you seem to be using based on the effect of getting owner zero.

1 Like

Sorry, I wasn't able to check this post earlier. Your solution worked! I know understand. Thank you very much.