Skip to content

DApp example

TIP

Task

Implement a DApp that can send a transaction with a specified nonce to a designated address.

Requirements

  • Customizable input for the target address
  • Customizable input for nonce (defaults to displaying the next nonce)
  • Use EIP-1559 for miner fees
  • Implement using React Hooks, do not use Class Components
  • Implement using TypeScript
  • Available for online preview

Bonus Points

  • Support for switching network nodes (EIP-3326, EIP-3085)

References

ETH

https://online-judge-with-wuchendi.vercel.app/

ts
const connectWallet = async () => {
  try {
    if (!window.ethereum) {
      // eslint-disable-next-line no-alert
      window.alert('Ethereum not found. Please install MetaMask or a similar extension.')
      return
    }

    setIsLoading(true)
    await window.ethereum.enable()
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const signer = provider.getSigner()

    const [address, network, gasPrice, nextNonce] = await Promise.all([
      signer.getAddress(),
      provider.getNetwork(),
      provider.getGasPrice(),
      provider.getTransactionCount(signer.getAddress()),
    ])

    setWallet(signer)
    setUserAddress(address)
    nextNonce && setNonce(nextNonce)
    setGasPrice(gasPrice.toNumber())
    setNetworkName(network.name)

    await updateAccountInfo(signer, address)

    // eslint-disable-next-line no-console
    console.log(
      `Current network: ${
        network.name
      }, nextNonce: ${nextNonce.toString()}, gasPrice: ${gasPrice.toString()}`
    )
  } catch (error: any) {
    if (error.code === 4001) {
      // eslint-disable-next-line no-alert
      window.alert(error.message || 'User rejected the request.')
    } else {
      console.error(error)
    }
  } finally {
    setIsLoading(false)
  }
}
ts
const sendTransaction = async () => {
  try {
    if (!wallet) {
      throw new Error('Please connect your wallet')
    }

    txHash && setTxHash('')
    setIsLoading(true)

    // const network = await wallet.provider?.getNetwork()
    const currentNonce = nonce !== undefined ? nonce : await wallet.getTransactionCount()

    if (!ethers.utils.isAddress(targetAddress)) {
      // eslint-disable-next-line no-alert
      window.alert('Invalid target address')
      return
    }

    if (tokenAmount.trim() === '') {
      // eslint-disable-next-line no-alert
      window.alert('Token amount is required')
      return
    }

    const parsedTokenAmount = ethers.utils.parseEther(tokenAmount)

    if (parsedTokenAmount.isZero()) {
      // eslint-disable-next-line no-alert
      window.alert('Invalid target amount')
      return
    }

    const feeData = await wallet.getFeeData()

    const tx: ethers.providers.TransactionRequest = {
      type: 2, // type 2 which is EIP-1559 transaction
      to: targetAddress,
      nonce: currentNonce || 0,
      value: parsedTokenAmount,
      // gasPrice,
      maxFeePerGas: feeData.maxFeePerGas || ethers.utils.parseUnits('2'),
      maxPriorityFeePerGas: feeData.maxPriorityFeePerGas || ethers.utils.parseUnits('1'),
      gasLimit: '21000',
      // chainId: network?.chainId,
    }

    // const estimatedGasLimit = await wallet.estimateGas(tx)
    // tx.gasLimit = estimatedGasLimit
    // eslint-disable-next-line no-console
    console.log('🚀 ~ file: page.tsx ~ sendTransaction ~ tx:', tx)

    try {
      const result = await wallet.sendTransaction(tx)

      // TODO!: That's not a good idea
      await wait(15 * 1000)
      await updateAccountInfo(wallet, userAddress)

      setTxHash(result.hash)
    } catch (error: any) {
      // eslint-disable-next-line no-alert
      error?.message && window.alert(error.message)
      // eslint-disable-next-line no-console
      console.log('🚀 ~ file: page.tsx ~ sendTransaction ~ error:', error)
    }
  } catch (error) {
    console.error(error)
  } finally {
    setIsLoading(false)
  }
}
ts
const updateAccountInfo = async (signer: ethers.Signer, address: string) => {
  const provider = signer.provider!
  const [_balance] = await Promise.all([provider.getBalance(address)])
  const formattedBalance = ethers.utils.formatEther(_balance)
  setBalance(formattedBalance)

  const nextNonce = await provider.getTransactionCount(address)
  setNonce(nextNonce)
}

https://online-judge-with-wuchendi-git-feature-link-wuchendi.vercel.app/

ts
const connectWallet = async () => {
  try {
    if (!window.ethereum) {
      // eslint-disable-next-line no-alert
      window.alert('Ethereum not found. Please install MetaMask or a similar extension.')
      return
    }

    setIsLoading(true)
    await window.ethereum.enable()
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const signer = provider.getSigner()

    const [address, network, gasPrice, nextNonce] = await Promise.all([
      signer.getAddress(),
      provider.getNetwork(),
      provider.getGasPrice(),
      provider.getTransactionCount(signer.getAddress()),
    ])

    setWallet(signer)
    setUserAddress(address)
    nextNonce && setNonce(nextNonce)
    setGasPrice(gasPrice.toNumber())
    setNetworkName(network.name)

    await updateAccountInfo(signer, address)

    // eslint-disable-next-line no-console
    console.log(
      `Current network: ${
        network.name
      }, nextNonce: ${nextNonce.toString()}, gasPrice: ${gasPrice.toString()}`
    )
  } catch (error: any) {
    if (error.code === 4001) {
      // eslint-disable-next-line no-alert
      window.alert(error.message || 'User rejected the request.')
    } else {
      console.error(error)
    }
  } finally {
    setIsLoading(false)
  }
}
ts
const sendTransaction = async () => {
  try {
    if (!wallet) {
      throw new Error('Please connect your wallet')
    }

    txHash && setTxHash('')
    setIsLoading(true)

    // const network = await wallet.provider?.getNetwork()
    const currentNonce = nonce !== undefined ? nonce : await wallet.getTransactionCount()

    if (!ethers.utils.isAddress(targetAddress)) {
      // eslint-disable-next-line no-alert
      window.alert('Invalid target address')
      return
    }

    if (tokenAmount.trim() === '') {
      // eslint-disable-next-line no-alert
      window.alert('Token amount is required')
      return
    }

    const parsedTokenAmount = ethers.utils.parseEther(tokenAmount)

    if (parsedTokenAmount.isZero()) {
      // eslint-disable-next-line no-alert
      window.alert('Invalid target amount')
      return
    }

    const feeData = await wallet.getFeeData()

    const linkContract = new ethers.Contract(LINKCONTRACTADDRESS, linkAbi, wallet)

    try {
      const result = await linkContract.transfer(targetAddress, parsedTokenAmount, {
        type: 2, // type 2 which is EIP-1559 transaction
        maxFeePerGas: feeData.maxFeePerGas || ethers.utils.parseUnits('2'),
        maxPriorityFeePerGas:
          feeData.maxPriorityFeePerGas || ethers.utils.parseUnits('1'),
        nonce: currentNonce || undefined,
      })

      // TODO!: That's not a good idea
      await wait(15 * 1000)
      await updateAccountInfo(wallet, userAddress)
      setTxHash(result.hash)
    } catch (error: any) {
      // eslint-disable-next-line no-alert
      error?.message && window.alert(error.message)
      // eslint-disable-next-line no-console
      console.log('🚀 ~ file: page.tsx ~ sendTransaction ~ error:', error)
    }
  } catch (error) {
    console.error(error)
  } finally {
    setIsLoading(false)
  }
}
ts
const updateAccountInfo = async (signer: ethers.Signer, address: string) => {
  try {
    let localLinkAbi = linkAbi

    if (localLinkAbi.length <= 0) {
      const response = await fetch(
        'https://cdn.jsdelivr.net/gh/cdLab996/picture-lib/cdLab996/abi/LINK.json'
        // 'https://raw.githubusercontent.com/cdLab996/picture-lib/main/cdLab996/abi/LINK.json'
      )
      const result = await response.json()
      localLinkAbi = result
      setLinkAbi(result)
    }

    const provider = signer.provider!
    const linkContract = new ethers.Contract(LINKCONTRACTADDRESS, localLinkAbi, provider)

    const [linkBalance, symbol] = await Promise.all([
      linkContract.balanceOf(address),
      linkContract.symbol(),
    ])

    const formattedLinkBalance = ethers.utils.formatUnits(linkBalance, 18)
    setBalance(formattedLinkBalance + ' ' + symbol)

    const nextNonce = await provider.getTransactionCount(address)
    setNonce(nextNonce)
  } catch (error) {
    console.error('Error updating account info:', error)
  }
}