It looks like the same issue still occurs in 2023, moreover I realised that now interacting with Smart Contracts on https://polkadot.js.org/apps/ is broken as well. The only way to interact with them is by using https://contracts-ui.substrate.io/ which apparently has some kind of workaround for that WeightV2 issue. Nevertheless, as a fulfilment of the reply from Adam Wierzbicki, you need to add following parts to make your code work.

First of all import these at the top of the code block

    import { BN, BN_ONE } from "@polkadot/util";
    import type { WeightV2 } from '@polkadot/types/interfaces'

Somewhere below define two new variables

    const MAX_CALL_WEIGHT = new BN(5_000_000_000_000).isub(BN_ONE);
    const PROOFSIZE = new BN(1_000_000);

Execute query on the given function to retrieve the required gas limit

    const storageDepositLimit = null;
    
      const { gasRequired } = await contract.query.flip(
        account.address,
        {
          gasLimit: api?.registry.createType('WeightV2', {
            refTime: MAX_CALL_WEIGHT,
            proofSize: PROOFSIZE,
          }) as WeightV2,
          storageDepositLimit,
        }
      );

Define the gas limit variable and execute transaction call

    const gasLimit = api?.registry.createType('WeightV2', gasRequired) as WeightV2
    
    await contract.tx
      .flip({
        gasLimit,
        storageDepositLimit
      })
      .signAndSend(alice, async (res) => {
        if (res.status.isInBlock) {
          console.log('in a block')
        } else if (res.status.isFinalized) {
          console.log('finalized')
        }
      });

If you need to only read some data from the Smart contract you can query it like in the code block below

    const { result, output } = await contract.query.get(
        alice.address,
        {
          gasLimit: api?.registry.createType('WeightV2', {
            refTime: MAX_CALL_WEIGHT,
            proofSize: PROOFSIZE,
          }) as WeightV2,
          storageDepositLimit,
        }
      );
    
        // The actual result from RPC as `ContractExecResult`
      console.log(result.toHuman());
    
      // check if the call was successful
      if (result.isOk) {
        // output the return value
        console.log("Success", output?.toHuman());
      } else {
        console.error("Error", result.asErr);
      }