Tech deep-dive on web3 dApp development

During the last couple of months, I have been developing a sample web3 dApp project to learn:

  • How to develop Smart Contracts as backend for web3 dApp?
  • How to develop a VueJS based web3 dApp to interact with MetaMask wallet and Smart Contracts?
  • How to deploy Smart Contracts to multiple EVM-compliant blockchains?
  • What are the differences between different blockchains?

Use Cases

The sample dApp is called "Decentralized Bookstore", allowing anyone to sell/buy books using cryptocurrencies. A NFT is minted at the same time as the book is purchased. The user can import the NFT into MetaMask wallet, shown in tab “My Books”. Here are the main use cases and the related UX screenshots:

  • Sell Book: powered by Bookstore smart contract's sellBook method, a book owner sells "Harry Potter" at the website, setting the price at 0.05 ETH.
Sell Book - 1 of 4
Sell Book - 1 of 4
Sell Book - 2 of 4
Sell Book - 2 of 4
Sell Book - 3 of 4
Sell Book - 3 of 4
Sell Book - 3 of 4
Sell Book - 3 of 4
  • Buy Book: powered by Bookstore contract's buyBook method, another user purchases the book "Harry Potter", paying 0.05 ETH to the seller and receives an NFT as proof of purchase:
Buy Book - 1 of 4
Buy Book - 1 of 4
Buy Book - 2 of 4
Buy Book - 2 of 4
Buy Book - 3 of 4
Buy Book - 3 of 4
Buy Book - 4 of 4
Buy Book - 4 of 4
  • Import NFT: the buyer imports the minted NFT into MetaMask wallet:
Import NFT - 1 of 4
Import NFT - 1 of 4
Import NFT - 2 of 4
Import NFT - 2 of 4
Import NFT - 3 of 4
Import NFT - 3 of 4
Import NFT - 4 of 4
Import NFT - 4 of 4

Application Architecture

The Architecture of a Web 3.0 application - created by Preethi Kasireddy
The Architecture of a Web 3.0 application - created by Preethi Kasireddy

The front-end is a Vue 3 based Single Page Application (SPA) which can be deployed to any server or IPFS as a static web site. After the SPA is loaded in browser, it interacts with the backend Bookstore smart contract which has been deployed to following blockchains:

  • Layer 1 (L1)
    • Ropsten
    • Rinkeby
    • Kovan
    • Goerli
  • Layer 2 (L2)
    • Arbitrum Rinkeby
    • Optimism Kovan
  • Sidechain
    • Poloygon Mumbai
    • Meter

Learnings & Know-hows

Comparison of Transaction Speed & Fee on Different Blockchains

As shown in the screenshots above, the dApp captures the transaction times and displays Etherscan links from which we can find out transaction fees. Here is a summary table to compare transaction speed & fee on different blockchains:

Comparison of 8 Blockchain Testnets
Comparison of 8 Blockchain Testnets

For any blockchain network, the transaction confirmation times and fees vary from time to time, depending on how busy the network is. The above table only provides some sense on the transaction speeds in different networks. During the development, I have observed the following:

  • Among tested L1 networks, Rinkeby and Kovan are generally faster than Ropsten and Goeli.
    • As a PoW L1 network, Ropsten is usually slow. Sometimes the transaction confirmation can take more than 200 seconds.
  • Among tested L2 networks, Arbitrum Rinkeby testnet delivers very fast transaction speed, usually transaction can be confirmed within 1 second; while Optimism Kovan's transaction speed is similar to Kovan's, ie, 10+ seconds, its transaction fee is much lower than that of Arbitrum's (~ 1/56K)
    • L1/L2 ethers can be exchanged at Arbitrum Bridge, Optimism Bridge or third party bridges.
    • Deposit from L1 to L2 balance takes only couple of minutes, while withdraw from L2 to L1 balance will take ~7 days to process
      • This withdrawal waiting period is to allow time for fraud proofs to be submitted and maintain security of the network.
      • To receive the funds on L1, the user must send a transaction on L1 which will incur a second transaction fee - see below a screenshot captured at Optimism Bridge:
L2 Balance Withdraw at Optimism Bridge
L2 Balance Withdraw at Optimism Bridge
  • Both sidechains (Ploygon Mumbai and MTR) deliver transaction speeds similar to L1 PoS blockchains

How to Deploy Smart Contract to Different Blockchains?

With an Infura developer account properly set up, we can set up package.json and truffle-config as below to deploy smart contracts to different blockchains:

Node/Truffle Config Files
Node/Truffle Config Files

How Can Front-end Interact with Smart Contract in Different Blockchains with JavaScript code?

Once a smart contract is deployed to all supported blockchain networks, the compiled contract JSON files can be copied to front end codebase and then deployed along with other assets:

Shell Script to Sync Compiled Contract JSON
Shell Script to Sync Compiled Contract JSON

At runtime, after web3.js (Ethereum JavaScript API)  is properly initialized, the front-end will be able to retrieve public testnet contract address from the compiled contract JSON:

getContractInfo(contractJson) {
  this.initBcInfo()
  const currencySymbol = this.bcInfo.currencySymbol
  const contractName = contractJson.contractName
  const envKey = 'VITE_BOOKSTORE_CONTRACT_ADDRESS'
  let contractAddr = import.meta.env[envKey + '_' + currencySymbol]
  if (!contractAddr || contractAddr == '') {
    const network = contractJson.networks[this.bcInfo.networkId]
    contractAddr = network ? network.address : import.meta.env[envKey]
  }
  const result = { contractAddr, contractName }
  console.log(
    `getContractInfo(${currencySymbol}): ${JSON.stringify(result)}`,
  )
  return result
},

Then it can create web3.eth.Contract object as below, after which the front-end will be able to call any public method defined in the smart contract:

initContractJson(compiledContractJson, contractInfo) {
  var abiArray = compiledContractJson['abi']
  if (abiArray == undefined) {
    const error = 'BcExplorer error: missing ABI in the compiled Truffle JSON.'
    console.error(error)
    throw new Error(error)
  }
 
  const { contractAddr, contractName } = contractInfo
  if (!this.web3().utils.isAddress(contractAddr)) {
    const error = `wrong contract address - contractAddr: ${contractAddr}`
    console.error(error)
    throw new Error(error)
  }
 
  const contract = new this.web3inst.eth.Contract(abiArray, contractAddr)
  contract.currencySymbol = this.info.currencySymbol
  this.contractInst[contractName] = contract
  this.contractAddr[contractName] = contractAddr
  console.log(`contract with name ${contractName} initialized`)
}

As different blockchains require different gas fees, instead of suggesting them from the code,  it would be better to let them handled by MetaMask by setting both maxPriorityFeePerGas and maxFeePerGas options to null:

    invokeSmartContract(
      contractName,
      method,
      getContractInfo,
      updateTransactionStatus,
    ) {
      const contractInfo = getContractInfo(contractName, method)
      const option = {
        from: contractInfo.address,
        maxPriorityFeePerGas: null,
        maxFeePerGas: null,
      }
      if (contractInfo.value) {
        option.value = contractInfo.value
      }
 
      contractInfo.method
        .send(option, (error, txHash) =>
          this.sendTransactionCallback(
            contractName,
            method,
            error,
            txHash,
            updateTransactionStatus,
          ),
        )
        .then((txReceipt) =>
          this.handleTransactionReceipt(
            contractName,
            method,
            contractInfo.bookId,
            txReceipt,
            updateTransactionStatus,
          ),
        )
        .catch((error) => {
          this.userInteractionCompleted({ result: 'error', error })
          if (updateTransactionStatus) {
            updateTransactionStatus(error, null)
          }
        })
    },
  },
}

Where to find the sample dApp?

  • GitHub repo for both Solidity-based Smart contracts and Vue3-based SPA:
  • dApp deployed at Netlify:

Hope you have enjoyed the reading and will find this sample dApp useful. If you have any question/comment on the code, feel free to reach out to me via twitter: @inflaton_sg. Have a good one!

Subscribe to inflaton
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.