(a) carefully crafted: it's ok to take functional shortcuts, but structurally the code must be sound; (b) production-ready: in the sense that thorough error handling and tests are a must, not a good to have; (c) simple: please avoid using big opinionated frameworks like NestJS; (d) well-documented: please add comments to your code, and document any next steps you would take or any areas you didn’t have time to implement fully.
Remember, we're looking for an example of the type of work you strive for, not just a proof of concept. Please aim to demonstrate the quality and standard of work you’d bring to the team.
If you have any questions or doubts, please reach out to María maria@li.finance.
We have a Smart Contract which is responsible for collecting fees for our transactions when certain conditions are met. This contract is called the FeeCollector and is deployed to all EVM chains that we support. You can see if for example on Polygon.
Every time a transaction is submitted that includes fee collection, an event is emitted on this contract. You can see those events here.
💡 Transactions are grouped in EVM Chains in so called blocks. Every block has a unique tag. Emitted events on a smart contract can be queried for a range of blocks.We would like to have the functionality to scan those emitted events, store them in our database and request them based on the integrator that the fees were collected for.
- We would like to have a tool that scrapes the contract for emitted
FeesCollectedevent on a given chain - The tool should be able to be started at any time to retrieve new events
- The tool should work efficiently and not scan the same blocks again
- The retrieved events should be stored in a MongoDB database using Typegoose
- Optional 1: Write a small REST endpoint that allows to retrieve all collected events for a given
integrator - Optional 2: Wrap the application into a usable Docker image
- The solution should be built in TypeScript, it should include all information on how to run it
- You can focus on the Polygon Chain for now, but the concept should take into consideration that we want this for all our EVM chains
- You can use
77000000as the oldest block to take into consideration
The below code snippets provides some examples how to load the FeeCollector events from Polygon. Feel free to use the code for your solution but feel free to change anything to fit your and the applications needs.
import { BigNumber, ethers } from 'ethers' // please use ethers v5 to ensure compatibility
import { FeeCollector__factory } from 'lifi-contract-types'
import { BlockTag } from '@ethersproject/abstract-provider'
const CONTRACT_ADDRESS = '0xbD6C7B0d2f68c2b7805d88388319cfB6EcB50eA9'
const POLYGON_RPC = 'https://polygon-rpc.com'
interface ParsedFeeCollectedEvents {
token: string; // the address of the token that was collected
integrator: string; // the integrator that triggered the fee collection
integratorFee: BigNumber; // the share collector for the integrator
lifiFee: BigNumber; // the share collected for lifi
}
/**
* For a given block range all `FeesCollected` events are loaded from the Polygon FeeCollector
* @param fromBlock
* @param toBlock
*/
const loadFeeCollectorEvents = (fromBlock: BlockTag, toBlock: BlockTag): Promise<ethers.Event[]> => {
const feeCollector = new ethers.Contract(
CONTRACT_ADDRESS,
FeeCollector__factory.createInterface(),
new ethers.providers.JsonRpcProvider(POLYGON_RPC)
)
const filter = feeCollector.filters.FeesCollected()
return feeCollector.queryFilter(filter, fromBlock, toBlock)
}
/**
* Takes a list of raw events and parses them into ParsedFeeCollectedEvents
* @param events
*/
const parseFeeCollectorEvents = (
events: ethers.Event[],
): ParsedFeeCollectedEvents[] => {
const feeCollectorContract = new ethers.Contract(
CONTRACT_ADDRESS,
FeeCollector__factory.createInterface(),
new ethers.providers.JsonRpcProvider(POLYGON_RPC)
)
return events.map(event => {
const parsedEvent = feeCollectorContract.interface.parseLog(event)
const feesCollected: ParsedFeeCollectedEvents = {
token: parsedEvent.args[0],
integrator: parsedEvent.args[1],
integratorFee: BigNumber.from(parsedEvent.args[2]),
lifiFee: BigNumber.from(parsedEvent.args[3]),
}
return feesCollected
})
}