-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: network fees from gas station (#292)
* feat: add gas station for networks * refactor: add trace for gas fees * refactor: move networks into common * refactor: default decimals
- Loading branch information
Showing
29 changed files
with
225 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import fetch from "node-fetch"; | ||
import { gasStation } from "../common/gas-station"; | ||
import { BigNumber } from "ethers"; | ||
import { getSupportedNetwork } from "../common/networks"; | ||
import { getFeeData } from "../utils"; | ||
|
||
const mockData = { | ||
standard: { | ||
maxPriorityFee: 1.9666241746, | ||
maxFee: 1.9666241895999999, | ||
}, | ||
fast: { | ||
maxPriorityFee: 2.5184666637333333, | ||
maxFee: 2.518466678733333, | ||
}, | ||
}; | ||
|
||
jest.mock("node-fetch"); | ||
|
||
jest.mock("../common/networks", () => ({ | ||
getSupportedNetworkNameFromId: jest.fn(), | ||
getSupportedNetwork: jest.fn(), | ||
})); | ||
|
||
describe("gasStation", () => { | ||
describe("fetch gas fees", () => { | ||
it("should return undefined if no gasStationUrl is provided", async () => { | ||
const result = await gasStation("")(); | ||
expect(result).toBeUndefined(); | ||
}); | ||
|
||
it("should fetch data from gasStationUrl and return GasStationFeeData", async () => { | ||
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce({ | ||
json: jest.fn().mockResolvedValueOnce(mockData), | ||
} as any); | ||
|
||
const result = await gasStation("mock-url")(); | ||
expect(result).toEqual({ | ||
maxFeePerGas: BigNumber.from("1966624190"), | ||
maxPriorityFeePerGas: BigNumber.from("1966624175"), | ||
}); | ||
}); | ||
|
||
it("should throw an error if fetching fails", async () => { | ||
(fetch as jest.MockedFunction<typeof fetch>).mockRejectedValueOnce(new Error("Fetch error")); | ||
|
||
await expect(gasStation("mock-url")()).rejects.toThrow("Failed to fetch gas station"); | ||
}); | ||
}); | ||
|
||
describe("getFeeData", () => { | ||
const mockProvider = { | ||
getNetwork: jest.fn().mockResolvedValue({ chainId: "123" }), | ||
getFeeData: jest.fn().mockResolvedValue("providerFeeData"), | ||
}; | ||
|
||
beforeEach(() => { | ||
mockProvider.getNetwork.mockClear(); | ||
mockProvider.getFeeData.mockClear(); | ||
}); | ||
|
||
it("should get fee data from provider if gas station is not available", async () => { | ||
(getSupportedNetwork as jest.Mock).mockReturnValueOnce(undefined); | ||
|
||
const res = await getFeeData(mockProvider as any); | ||
|
||
expect(res).toBe("providerFeeData"); | ||
expect(mockProvider.getFeeData).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should use the gas station when it is available", async () => { | ||
const mockGasStation = jest.fn().mockReturnValue("mockGasStationData"); | ||
(getSupportedNetwork as jest.Mock).mockReturnValueOnce({ gasStation: mockGasStation }); | ||
|
||
await getFeeData(mockProvider as any); | ||
|
||
expect(mockProvider.getFeeData).not.toHaveBeenCalled(); | ||
expect(mockGasStation).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should get fee data from provider if gas station returns undefined", async () => { | ||
const mockGasStation = jest.fn().mockReturnValue(undefined); | ||
(getSupportedNetwork as jest.Mock).mockReturnValueOnce({ gasStation: mockGasStation }); | ||
|
||
const res = await getFeeData(mockProvider as any); | ||
|
||
expect(mockProvider.getFeeData).toHaveBeenCalledTimes(1); | ||
expect(mockGasStation).toHaveBeenCalledTimes(1); | ||
expect(res).toBe("providerFeeData"); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { BigNumber, ethers } from "ethers"; | ||
import fetch from "node-fetch"; | ||
|
||
export type GasStationFunction = (gasStationUrl: string) => () => Promise<GasStationFeeData | undefined>; | ||
export type GasStationFeeData = { maxPriorityFeePerGas: BigNumber | null; maxFeePerGas: BigNumber | null }; | ||
|
||
export const gasStation: GasStationFunction = | ||
(gasStationUrl: string, decimals = 9) => | ||
async (): Promise<GasStationFeeData | undefined> => { | ||
try { | ||
if (!gasStationUrl) return undefined; | ||
const res = await fetch(gasStationUrl); | ||
const data = await res.json(); | ||
return { | ||
maxPriorityFeePerGas: safeParseUnits(data.standard.maxPriorityFee.toString(), decimals), | ||
maxFeePerGas: safeParseUnits(data.standard.maxFee.toString(), decimals), | ||
}; | ||
} catch (e) { | ||
throw new Error("Failed to fetch gas station"); | ||
} | ||
}; | ||
|
||
const safeParseUnits = (_value: number | string, decimals: number): BigNumber => { | ||
const value = String(_value); | ||
if (!value.match(/^[0-9.]+$/)) { | ||
throw new Error(`invalid gwei value: ${_value}`); | ||
} | ||
|
||
// Break into [ whole, fraction ] | ||
const comps = value.split("."); | ||
if (comps.length === 1) { | ||
comps.push(""); | ||
} | ||
|
||
// More than 1 decimal point or too many fractional positions | ||
if (comps.length !== 2) { | ||
throw new Error(`invalid gwei value: ${_value}`); | ||
} | ||
|
||
// Pad the fraction to 9 decimal places | ||
while (comps[1].length < decimals) { | ||
comps[1] += "0"; | ||
} | ||
|
||
// Too many decimals and some non-zero ending, take the ceiling | ||
if (comps[1].length > 9 && !comps[1].substring(9).match(/^0+$/)) { | ||
comps[1] = BigNumber.from(comps[1].substring(0, 9)).add(BigNumber.from(1)).toString(); | ||
} | ||
|
||
return ethers.utils.parseUnits(`${comps[0]}.${comps[1]}`, decimals); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.