ethers.Contract V5 「TypeError: contract.populateTransaction.xxx is not a Function'」 の解決方法

Posted at 2024 年 02 月 28 日

エラーの原因

ethers.jsを使用して書き込みメソッドを呼び出すと、常に「TypeError: contract.populateTransaction.xxx is not a Function'」というエラーが発生します。

ABIにはメソッドが存在することが確認されていますが、問題は書き込みメソッドではなく、同じ名前のメソッドでもパラメーターの数が異なるオーバーロードされたメソッドにありました。

解決方法

同じ名前のメソッド(オーバーロードされたメソッド)の中から必要なものだけをContractコンストラクタに渡すためにABIをinput.lengthでフィルタリングします。その後、対応する件数名にアクセスすることで、問題を解決できます。

solidity例

function issueTokens(uint256 quantity) public {
    _issueTokens(_msgSender(), quantity);
}

function issueTokens(address recipient, uint256 quantity) public {
    _issueTokens(recipient, quantity);
}
abi例
(src/abis/myAbi.json)

[
  {
    "inputs": [
      {
        "type": "uint256",
        "name": "quantity"
      }
    ],
    "name": "issueTokens",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "type": "address",
        "name": "recipient"
      },
      {
        "type": "uint256",
        "name": "quantity"
      }
    ],
    "name": "issueTokens",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]
TypeScript例

/* import session */
import { ethers } from 'ethers'
import { getEthSignerFromWindow } from 'lib/ethereum'
import myAbi from '../../abis/myAbi.json'

/* ...other methods... */

 // issueTokens() 関数を呼び出し、1つのパラメーターを使用する
  async function handleIssueTokens1(quantity: number) {
    try {
      // signerを取得
      const signer = await getEthSignerFromWindow()
      if (!signer) {
        console.log('Signer is undefined.')
        return
      }

      const contractAddress = '0x.....'

      // パラメーター一つのissueTokens()をABIからフィルタリング
      const filteredAbi = myAbi.filter((item) => {
        return item.name === 'issueTokens' && item.inputs.length === 1
      })

      // コントラクトの作成
      const contract = new ethers.Contract(contractAddress, filteredAbi, signer)
      // 自分のウォレットアドレス
      const walletAddress = await signer.getAddress()
      // トランザクションデータの準備
      const txData = await contract.populateTransaction.issueTokens(quantity)
      // トランザクションの推定ガスを取得
      const estimatedGas = await contract.estimateGas.issueTokens(quantity)

      // ガスリミットを指定してトランザクションオブジェクトを作成
      const transaction = {
        // TOにコントラクトアドレスを設定
        to: contractAddress ,
        data: txData.data,
        gasLimit: estimatedGas,
        from: walletAddress,
        value: ethers.utils.parseEther('0'),
      }

      // トランザクションに署名して送信
      const signedTx = await signer.sendTransaction(transaction)
      const receipt = await signedTx.wait()

      // トランザクションが成功したか確認
      if (receipt.status === 1) {
        console.log('Transaction is successful.')
      } else {
        throw new Error(receipt.logs[0].data)
      }
    } catch (error) {
      console.error('Error fetching data:', error)
    }
  }

// issueTokens() 関数を呼び出し、2つのパラメーターを使用する
async function handleIssueTokens2(recipient: string, quantity: number) {
    try {
      // signerを取得
      const signer = await getEthSignerFromWindow()
      if (!signer) {
        console.log('Signer is undefined.')
        return
      }

      const contractAddress = '0x.....'

      // パラメーター二つのissueTokens()をABIからフィルタリング
      const filteredAbi = myAbi.filter((item) => {
        return item.name === 'issueTokens' && item.inputs.length === 2
      })

      // コントラクトの作成
      const contract = new ethers.Contract(contractAddress, filteredAbi, signer)
      // 自分のウォレットアドレス
      const walletAddress = await signer.getAddress()
      // トランザクションデータの準備
      const txData = await contract.populateTransaction.issueTokens(recipient, quantity)
      // トランザクションの推定ガスを取得
      const estimatedGas = await contract.estimateGas.issueTokens(recipient, quantity)

      // ガスリミットを指定してトランザクションオブジェクトを作成
      const transaction = {
        // TOにコントラクトアドレスを設定
        to: contractAddress ,
        data: txData.data,
        gasLimit: estimatedGas,
        from: walletAddress,
        value: ethers.utils.parseEther('0'),
      }

      // トランザクションに署名して送信
      const signedTx = await signer.sendTransaction(transaction)
      const receipt = await signedTx.wait()

      // トランザクションが成功したか確認
      if (receipt.status === 1) {
        console.log('Transaction is successful.')
      } else {
        throw new Error(receipt.logs[0].data)
      }
    } catch (error) {
      console.error('Error fetching data:', error)
    }
  }
/* ...other methods... */

総括すると、ethers.jsのバージョン5では、「TypeError: contract.populateTransaction.xxx is not a Function」というエラーが発生することがあります。

このエラーは、書き込みメソッドを呼び出す際に、同じ名前でもパラメーターの数が異なるオーバーロードされたメソッドが存在する場合に生じます。

ABIをフィルタリングし、パラメーターの数に基づいて必要なメソッドを選択し、それに応じてアクセスすることで、このエラーを効果的に解決できます。

このアプローチにより、コントラクトとの円滑なやり取りが実現され、Ethereumアプリケーションにおけるトランザクションの成功が可能となります。