-
Notifications
You must be signed in to change notification settings - Fork 462
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(iam): using moralis SDK for eth price and caching in memory for …
…5 minutes (#1658)
- Loading branch information
1 parent
a87921b
commit 1c8b754
Showing
14 changed files
with
1,515 additions
and
199 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 |
---|---|---|
@@ -1,12 +1,14 @@ | ||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ | ||
|
||
module.exports = { | ||
// [...] | ||
"preset": 'ts-jest', | ||
"extensionsToTreatAsEsm": [".ts"], | ||
"globals": { | ||
"ts-jest": { | ||
"useESM": true | ||
} | ||
} | ||
} | ||
transform: { | ||
"^.+\\.tsx?$": [ | ||
"ts-jest", | ||
{ | ||
useESM: true, | ||
}, | ||
], | ||
}, | ||
preset: "ts-jest", | ||
extensionsToTreatAsEsm: [".ts"], | ||
}; |
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,63 @@ | ||
import { getEASFeeAmount } from "../src/utils/easFees"; | ||
import { utils } from "ethers"; | ||
import Moralis from "moralis"; | ||
|
||
jest.mock("moralis", () => ({ | ||
EvmApi: { | ||
token: { | ||
getTokenPrice: jest.fn().mockResolvedValue({ | ||
result: { usdPrice: 3000 }, | ||
}), | ||
}, | ||
}, | ||
})); | ||
|
||
describe("EthPriceLoader", () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.advanceTimersByTime(1000 * 60 * 6); // Advance by 6 minutes | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe("getEASFeeAmount", () => { | ||
it("should calculate the correct EAS Fee amount based on the current ETH price", async () => { | ||
const usdFeeAmount = 2; | ||
const result = await getEASFeeAmount(usdFeeAmount); | ||
|
||
const expectedEthFeeAmount = usdFeeAmount / 3000; | ||
const expectedBigNumberValue = utils.parseEther(expectedEthFeeAmount.toFixed(18)); | ||
|
||
expect(result).toEqual(expectedBigNumberValue); | ||
}); | ||
|
||
it("should handle Moralis errors gracefully", async () => { | ||
(Moralis.EvmApi.token.getTokenPrice as jest.Mock).mockRejectedValueOnce(new Error("Failed fetching price")); | ||
|
||
await expect(getEASFeeAmount(2)).rejects.toThrow("Failed to get ETH price"); | ||
}); | ||
}); | ||
|
||
it("should call Moralis API only once if getEASFeeAmount is called multiple times in succession before cachePeriod is reached", async () => { | ||
await getEASFeeAmount(2); | ||
await getEASFeeAmount(3); | ||
await getEASFeeAmount(4); | ||
|
||
expect(Moralis.EvmApi.token.getTokenPrice).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should call Moralis API again if cachePeriod is exceeded", async () => { | ||
// We're making the first call | ||
await getEASFeeAmount(2); | ||
|
||
// Fast-forwarding time to exceed the cache period of 5 minutes | ||
jest.advanceTimersByTime(1000 * 60 * 6); // Advance by 6 minutes | ||
|
||
// Making the second call after the cache period | ||
await getEASFeeAmount(2); | ||
|
||
expect(Moralis.EvmApi.token.getTokenPrice).toHaveBeenCalledTimes(2); | ||
}); | ||
}); |
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 |
---|---|---|
@@ -1,11 +1,22 @@ | ||
// ---- Main App from index | ||
import { app } from "./index"; | ||
import Moralis from "moralis"; | ||
|
||
// default port to listen on | ||
const port = process.env.IAM_PORT || 80; | ||
|
||
// start the Express server | ||
app.listen(port, () => { | ||
const startServer = async (): Promise<void> => { | ||
await Moralis.start({ | ||
apiKey: process.env.MORALIS_API_KEY, | ||
}); | ||
|
||
app.listen(port, () => { | ||
// eslint-disable-next-line no-console | ||
console.log(`server started at http://localhost:${port}`); | ||
}); | ||
}; | ||
|
||
startServer().catch((error) => { | ||
// eslint-disable-next-line no-console | ||
console.log(`server started at http://localhost:${port}`); | ||
console.error(error); | ||
}); |
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 |
---|---|---|
@@ -1,21 +1,53 @@ | ||
import axios from "axios"; | ||
import { utils } from "ethers"; | ||
import { BigNumber } from "@ethersproject/bignumber"; | ||
import Moralis from "moralis"; | ||
import { IAMError } from "./scorerService"; | ||
|
||
export async function getEASFeeAmount(usdAmount: number): Promise<BigNumber> { | ||
const ethUSD: { | ||
data: { | ||
ethereum: { | ||
usd: number; | ||
}; | ||
}; | ||
} = await axios.get("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=USD", { | ||
headers: { | ||
// TODO add API key | ||
Accept: "application/json", | ||
}, | ||
}); | ||
|
||
const ethAmount = usdAmount / ethUSD.data.ethereum.usd; | ||
return utils.parseEther(ethAmount.toFixed(18)); | ||
const FIVE_MINUTES = 1000 * 60 * 5; | ||
const WETH_CONTRACT = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; | ||
|
||
class EthPriceLoader { | ||
cachedPrice: number; | ||
lastUpdated: number; | ||
cachePeriod = FIVE_MINUTES; | ||
|
||
constructor() { | ||
this.cachedPrice = 0; | ||
this.lastUpdated = 0; | ||
} | ||
|
||
async getPrice(): Promise<number> { | ||
if (this.#needsUpdate()) { | ||
this.cachedPrice = await this.#requestCurrentPrice(); | ||
this.lastUpdated = Date.now(); | ||
} | ||
return this.cachedPrice; | ||
} | ||
|
||
#needsUpdate(): boolean { | ||
return Date.now() - this.lastUpdated > this.cachePeriod; | ||
} | ||
|
||
async #requestCurrentPrice(): Promise<number> { | ||
try { | ||
const { result } = await Moralis.EvmApi.token.getTokenPrice({ | ||
chain: "0x1", | ||
address: WETH_CONTRACT, | ||
}); | ||
|
||
return result.usdPrice; | ||
} catch (e) { | ||
let message = "Failed to get ETH price"; | ||
if (e instanceof Error) message += `, ${e.name}: ${e.message}`; | ||
throw new IAMError(message); | ||
} | ||
} | ||
} | ||
|
||
const ethPriceLoader = new EthPriceLoader(); | ||
|
||
export async function getEASFeeAmount(usdFeeAmount: number): Promise<BigNumber> { | ||
const ethPrice = await ethPriceLoader.getPrice(); | ||
const ethFeeAmount = usdFeeAmount / ethPrice; | ||
return utils.parseEther(ethFeeAmount.toFixed(18)); | ||
} |
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.