0

I've built a web3 dapp for the "Rock, paper, scissors, lizard and spock" game. I've made it run on my local system using truffle and ganache but I must deploy it on an Ethereum test like Sepolia. Now this, I don't know how to do.

I can't just deploy it on Sepolia because of some tech issues. Can anyone help me with this?

To be more specific, below is the code for the main function:

const startGame = async (e) => {
e.preventDefault()
resetGameState()

const provider = await detectEthereumProvider()
if (!provider) {
  showToast('Please install MetaMask to use this dapp!', 'error')
  return
}
if (!(amount && opponentAddress && move)) {
  showToast('Cannot start game. Please fill all the fields.', 'error')
  return
}

try {
  await provider.request({ method: 'eth_requestAccounts' })
  const web3 = new Web3(window.ethereum)
  const accounts = await web3.eth.getAccounts()
  const moveInt = moveMapping[move]

  // Deploy Hasher Contract
  const HasherContract = new web3.eth.Contract(Hasher.abi)
  const hasherInstance = await HasherContract.deploy({
    data: Hasher.bytecode,
  }).send({ from: accounts[0] })
  const hasherContractAddress = hasherInstance.options.address
  console.log('acc', accounts)

  const generatedSalt = generateSalt()
  setSalt(generatedSalt)

  const hasherContract = new web3.eth.Contract(
    Hasher.abi,
    hasherContractAddress
  )
  async function getHash(moveInt, salt) {
    return await hasherContract.methods.hash(moveInt, salt).call()
  }
  const moveHash = await getHash(moveInt, generatedSalt)

  // Deploy RPS contract
  const RPSGame = new web3.eth.Contract(RPSContract.abi)
  const rpsGameInstance = await RPSGame.deploy({
    data: RPSContract.bytecode,
    arguments: [moveHash, opponentAddress],
  }).send({ from: accounts[0], value: web3.utils.toWei(amount, 'ether') })

  const message = (
    <div>
      Move registered! <br />
      You played - {move}. <br />
      Wait for your opponent to make their move now.
    </div>
  )

  showToast(message, 'success', 10000)
  saveGameState(generatedSalt, move, rpsGameInstance.options.address)

  setContractInstance(rpsGameInstance)
} catch (error) {
  showToast(error.message, 'error')
} }

I can deploy the hasher contract beforehand and use it but can't do the same for the RPSContract since it takes dynamic args.

Below is the solidity code which has the contracts in it:

pragma solidity ^0.4.26;

contract RPS{
    address public j1; // The first player creating the contract.
    address public j2; // The second player.
    enum Move {Null, Rock, Paper, Scissors, Spock, Lizard} // Possible moves. Note that if the parity of the moves is the same the lower one wins, otherwise the higher one. 
    bytes32 public c1Hash; // Commitment of j1.
    Move public c2; // Move of j2. Move.Null before he played.
    uint256 public stake; // Amout bet by each party.
    uint256 public TIMEOUT = 5 minutes; // If some party takes more than TIMEOUT to respond, the other can call TIMEOUT to win.
    uint256 public lastAction; // The time of the last action. Usefull to determine if someone has timed out.
    
    /** @dev Constructor. Must send the amount at stake when creating the contract. Note that the move and salt must be saved.
     *  @param _c1Hash Must be equal to keccak256(c1,salt) where c1 is the move of the j1.
     */
    function RPS(bytes32 _c1Hash, address _j2) payable {
        stake = msg.value; // La mise correspond à la quantité d'ethers envoyés.
        j1=msg.sender;
        j2=_j2;
        c1Hash=_c1Hash;
        lastAction=now;
    }
    
    /** @dev To be called by j2 and provided stake.
     *  @param _c2 The move submitted by j2.
     */
    function play(Move _c2) payable {
        require(c2==Move.Null); // J2 has not played yet.
        require(_c2!=Move.Null); // A move is selected.
        require(msg.value==stake); // J2 has paid the stake.
        require(msg.sender==j2); // Only j2 can call this function.
            
        c2=_c2;
        lastAction=now;
    }
    
    /** @dev To be called by j1. Reveal the move and send the ETH to the winning party or split them.
     *  @param _c1 The move played by j1.
     *  @param _salt The salt used when submitting the commitment when the constructor was called.
     */
    function solve(Move _c1, uint256 _salt) {
        require(_c1!=Move.Null); // J1 should have made a valid move.
        require(c2!=Move.Null); // J2 must have played.
        
        require(msg.sender==j1); // J1 can call this.
        require(keccak256(_c1,_salt)==c1Hash); // Verify the value is the commited one.
        
        // If j1 or j2 throws at fallback it won't get funds and that is his fault.
        // Despite what the warnings say, we should not use transfer as a throwing fallback would be able to block the contract, in case of tie.
        if (win(_c1,c2))
            j1.send(2*stake);
        else if (win(c2,_c1))
            j2.send(2*stake);
        else {
            j1.send(stake);
            j2.send(stake);
        }
        stake=0;
    }
    
    /** @dev Let j2 get the funds back if j1 did not play.
     */
    function j1Timeout() {
        require(c2!=Move.Null); // J2 already played.
        require(now > lastAction + TIMEOUT); // Timeout time has passed.
        j2.send(2*stake);
        stake=0;
    }
    
    /** @dev Let j1 take back the funds if j2 never play.
     */
    function j2Timeout() {
        require(c2==Move.Null); // J2 has not played.
        require(now > lastAction + TIMEOUT); // Timeout time has passed.
        j1.send(stake);
        stake=0;
    }
    
    /** @dev Is this move winning over the other.
     *  @param _c1 The first move.
     *  @param _c2 The move the first move is considered again.
     *  @return w True if c1 beats c2. False if c1 is beaten by c2 or in case of tie.
     */
    function win(Move _c1, Move _c2) constant returns (bool w) {
        if (_c1 == _c2)
            return false; // They played the same so no winner.
        else if (_c1==Move.Null)
            return false; // They did not play.
        else if (uint(_c1)%2==uint(_c2)%2) 
            return (_c1<_c2);
        else
            return (_c1>_c2);
    }
    
}

contract Hasher{
    /** @dev Give the commitement. Must only be called locally.
     *  @param _c The move.
     *  @param _salt The salt to increase entropy.
     */
    function hash(uint8 _c, uint256 _salt) constant returns(bytes32) {
        return keccak256(_c,_salt);
    }
}

Edit: I'm adding what how I've tried modifying the code to support the testnet:

.
.
.
const seploriaRPC =
    'https://eth-sepolia.g.alchemy.com/v2/<key>' 
  const seploriaNetworkId = 11155111
.
.
.
const startGame = async (e) => {
    e.preventDefault()
    resetGameState()

    // Use window.ethereum to access the provider from MetaMask
    if (window.ethereum) {
      try {
        // Request account access if needed
        await window.ethereum.request({ method: 'eth_requestAccounts' })
        const web3 = new Web3(window.ethereum)
        const accounts = await web3.eth.getAccounts()
        const moveInt = moveMapping[move]

        console.log('Sending transaction from Metamask account: ', accounts[0])

        // Deploy Hasher Contract
        const HasherContract = new web3.eth.Contract(Hasher.abi)
        const hasherInstance = await HasherContract.deploy({
          data: Hasher.bytecode,
        }).send({ from: accounts[0] })

        const hasherContractAddress = hasherInstance.options.address

        console.log('Hasher contract deployed at: ', hasherContractAddress)

        const generatedSalt = generateSalt()
        setSalt(generatedSalt)

        const hasherContract = new web3.eth.Contract(
          Hasher.abi,
          hasherContractAddress
        )

        async function getHash(moveInt, salt) {
          return await hasherContract.methods.hash(moveInt, salt).call()
        }

        const moveHash = await getHash(moveInt, generatedSalt)

        console.log('Hash generated: ', moveHash)

        // Deploy RPS contract
        const RPSGame = new web3.eth.Contract(RPSContract.abi)
        const rpsGameInstance = await RPSGame.deploy({
          data: RPSContract.bytecode,
          arguments: [moveHash, opponentAddress, hasherContractAddress],
        }).send({
          from: accounts[0],
          value: web3.utils.toWei(amount, 'ether'),
          gasPrice: 100000, // replace with actual gas price
          gas: 100000000000, // replace with actual gas limit
        })

        const message = (
          <div>
            Move registered! <br />
            You played - {move}. <br />
            Wait for your opponent to make their move now.
          </div>
        )

        showToast(message, 'success', 10000)
        saveGameState(generatedSalt, move, rpsGameInstance.options.address)

        setContractInstance(rpsGameInstance)
      } catch (error) {
        showToast(error.message, 'error')
      }
    } else {
      showToast('Please install MetaMask to use this dapp!', 'error')
    }
  }
6
  • What is the behavior you are experience when you try to deploy to Sepolia? Commented Jan 3 at 18:13
  • @ruby_newbie I've added what I've tried doing in the original post as an edit. When I run that code, I get an error saying"Code coudn't be stored" when I try to first deploy the hasher contract. Commented Jan 3 at 19:55
  • Is your seploriaRPC literally 'eth-sepolia.g.alchemy.com/v2/<key>' or do you have your key value in there instead of <key> Commented Jan 3 at 21:53
  • 1
    @ruby_newbie I do have my key value, I took it out for this post. Commented Jan 4 at 5:17
  • I'm not sure why you want to deploy from the frontend - that's really not typical. I recommend to deploy from some backend and then just utilize the contract with whatever parameters. Furthermore, Truffle is deprecated. Furthemore, Solidity version 0.4 is years old - I highly recommend to switch to newer. Commented Jan 4 at 11:24

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.