ガス代を推測してトランザクションを快適に通す
前置き
ethers.js だけを使ってトランザクションを通そうとすると、なかなかトランザクションが通らなかったり、そもそも詰まることが多いと思います。
PENTA ではモバイルアプリからトランザクションを通す必要がありますが、トランザクションが通らないことが多かったため、アプリの UX を大きく下げてしまっていました。
これは ethers.js のガス代の計算アルゴリズムが簡易的なものかつ、ethereum チェーンのみを想定して実装しているためです。
これらの事象は特に Polygon などで顕著で、一生トランザクションが通ることはないでしょう。そこで、この記事では Hardhat や Metamask のようにガス代を動的に計算する方法を紹介します。
方針
Metamask や Hardhat などはガス代を正しく計算しているので、かなりの確率でトランザクションを通すことができます。
それに倣って ethers.js のProvider
を改造して、正しいガス代を計算できるようにします。
Metamask に関してはコードが多く、具体的な計算式などはわからなかったのですが、Hardhat の方はガス代を計算している箇所を見つけることができました。
なので、これを参考にして、Provider
を改造していきます。
Hardhat の手法を分析する
実装に移る前に軽く Hardhat のコードを解読しておきます。eth_feeHistory
を使って直近のブロックのガス代を取得して、それらをもとにマージンを取った値を返しています。
おそらく、Metamask はこのマージンの分の値をfirst
,slow
などで切り替えれるようになっています。
このコードだと要件に対して煩雑なので、要点を切り取って実装したいと思います。
実装する
ethers.js のProvider
は内部でgetFeeData
メソッドを使ってガス代を計算しています。
なので、これを改造して、eth_feeHistory
を使ってガス代を計算するようにします。
また、実装では追加で rainbow が出していたコードも参考にしています。
import { ethers } from "ethers";
class AutoGasSuggestProvider extends ethers.providers.JsonRpcProvider {
async getFeeData(): Promise<ethers.providers.FeeData> {
// 直前のブロックのガス代を取得する
const history = (await this.send("eth_feeHistory", [
"0x1",
"latest",
[25], //上位25%のチップを払ったトランザクションを取得する
])) as {
baseFeePerGas: string[];
reward: string[][];
};
//直前のブロックのデータにマージンをかけている
const feeData = {
gasPrice: null,
lastBaseFeePerGas: null,
maxFeePerGas: BigNumber.from(history.baseFeePerGas[1]).mul(2),
maxPriorityFeePerGas: BigNumber.from(history.reward[0][0]),
};
return feeData;
}
}
このマージンのパラメータだと Polygon や Astar などのチェーンでうまく動いていました。
ですが、チェーンやトランザクションの重要度によって変わるので、調査したうえで調整するのがベストだと思います。
おわりに
Ethereum などの DApps などにおいてシームレスな UX を実現するにはトランザクションをいかに快適に通すかが重要になります。
今回のような Tips を共有することで業界全体の前進の手助けをできれば幸いです。
Discussion