About the Project
Lighter.Fi is a decentralized DCA and Limit Order platform powered by the most efficient multi-AMM on-chain swap protocol. Our novel swap protocol provides on-chain access to the off-chain computations of a DeFi aggregator, enabling effortless multi-AMM swaps directly from smart contracts solving the problem of the complexity of making efficient contract-to-contract swaps and building applications on top of it.
Inspiration
Our project began with a simple premise: executing swaps directly from a smart contract (without going through an SDK, for example) is too complex and inefficient.
Why: Many complex input parameters are needed. The computation to calculate the path and input parameters is performed off-chain and then usually passed on-chain through an SDK. The easiest way is to hardcode some parameters (losing efficiency) or create vertical integration on a specific Automated Market Maker (AMM).
This approach does not allow for the creation of decentralized and automated applications built on a swap protocol because each time there needs to be a trusted party passing the swap parameters. Therefore, we wanted to build an efficient and multi-AMM swap protocol that allows extremely simple interactions from smart contracts. To demonstrate the potential of such a protocol, we built a DeFi platform that enables DCA (Dollar-Cost Averaging) and Limit Order strategies. Doing this without having built the swap protocol would have been impossible or at least highly inefficient.
How We Built It
We start with a simple assertion: building the swap protocol and the DCA/Limit Order on top would not have been possible without fully utilizing the Chainlink stack and infrastructure.
The swap protocol leverages a Chainlink Function: we built a Function that takes simple required parameters (tokenFrom, tokenTo, tokenFromAmount), calls the LIFI APIs, and returns the desired swap calldata through fulfillRequest, emitting a Response event. The calldata is stored in the smart contract, and a Log-trigger (which reads the event emitted by fulfill) triggers the execution of the swap calldata through a Solidity call. This way, we can access the off-chain computations of a DeFi aggregator.
The DCA strategy uses Chainlink automation with a custom-logic trigger to execute the desired swap at fixed time intervals set by the user. Consequently, it is an automation that triggers the swap protocol Function.
The Limit Order utilizes the same Chainlink automation custom-logic trigger and Chainlink price feed to check if the price set by the user is reached and to execute the desired swap. Therefore, it is an automation with logic that checks the price feed and triggers the swap protocol.
In summary, our platform consists of a single smart contract and is built on:
- 1 Chainlink Function for the swap (with two on-chain transactions, one to retrieve the calldata and one to execute the calldata via a Log-trigger).
- 1 Log-trigger Automation to execute the on-chain swap
- 1 custom-logic automation for DCA and Limit Order + 1 price feed for Limit Order price check
The application is deployed on the Avalanche Mainnet at the following address.
Furthermore, to build a part of analytics and display strategy data to users, we deployed and implemented a subgraph capable of displaying: All strategies per user divided between DCA and Limit Order Trading volume on the application Trading volume for each strategy Past executions both for DCA and Limit Order
The subgraph is deployed here:
Challenges We Ran Into
We faced several challenges, most of which were related to Chainlink (and we solved them!): Maximum buffer size returned by Chainlink function: The return, i.e., the swap calldata, from our API call to LIFI had a size greater than 256 bytes. Initially, we thought of solving the problem by creating middleware that could compress the output and calling our middleware from the Function via an API. However, this would have been a very opaque and centralized solution. Therefore, what we did was to cut the calldata into small pieces within the Function and return only the dynamic parts of the call. The constant pieces were stored in the smart contract, and the entire calldata was reconstructed directly in Solidity. Gas limit for fulfill request: The gas limit for the fulfill of Function is 300k gas. The swap alone costs 300k gas. Consequently, we could not return the response of the function and execute the swap in a single call. To solve the problem, we save the result of fulfill, i.e., the swap calldata, in the contract storage for each user and emit an event. Then, we built a Log-trigger that, when the fulfill event is emitted, triggers the second part, i.e., the execution of the actual swap, through a Solidity call. Using two different Automation upkeeper contracts on the same contract, especially on the same checkUpkeep and performUpkeep functions: We have two upkeepers that we use, one for the time-trigger of DCA and one with custom logic-trigger that checks a price feed for Limit Order. To use two upkeepers on the same contract, we built a single performUpkeep that can execute two different logics (DCA and Limit Order) depending on the performData received as input. This allowed us to optimize the contract to the maximum without having to create two different performUpkeeps or split the contracts. Managing all service subscriptions for Chainlink services for all users on a single contract to avoid creating/having users create subscriptions that could create friction in user experience (UX). Testing our contract: We wanted to have 100% test coverage on our project. However, testing a project built almost entirely on chainlink infra is not easy. To do this we did unit testing of the contract, tested all chainlink logic using mock contracts in local, and finally tested at length on testnet (Polygon Mumbai) the flow of the contract
Accomplishments We're Proud Of
A single contract that can execute two different strategies and the swap protocol. Successfully making the Function work even when the return buffer of the Function is larger than 256 bytes. Maximizing the use of the Chainlink stack using in the same contract 1 custom-logic automation checking for 2 different strategies (DCA/Limit Order) and 1 Log-trigger Automation to actually execute the swap after the fulFillRequest event
What We Learned
We learned and applied almost the entire Chainlink stack (except data streams and CCIP) on a single optimized contract. In particular, we understood and worked around the current limitations of Function, especially regarding the return buffer limit and the gas limit in the callback (fulfillRequest).
What's Next
We would like to continue the project in the future because there is currently no platform that offers DCA, Limit Order, and potentially other types of strategies in the same decentralized platform. We would like to implement a fee payment by users to make the platform sustainable because currently, we cover all the costs of all Chainlink operations that pass through our Chainlink Subscriptions. We would like to decouple the swap protocol to make it the definitive on-chain protocol for executing swaps from smart contracts and turn it into a DeFi primitive that can be used by other developers to build DeFi applications on top, such as DCA, Limit Order, DAO treasury management, swap and deposit in a contract (like swapping a token for $LINK and depositing to fulfill a subscription), and in general, any application that requires a swap from smart contracts. We also aim to improve the frontend to offer a better UX/UI to Lighter.fi users.
Log in or sign up for Devpost to join the conversation.