Note
This repository represents an example of using a Chainlink product or service. It is provided to help you understand how to interact with Chainlink’s systems so that you can integrate them into your own. This template is provided "AS IS" without warranties of any kind, has not been audited, and may be missing key checks or error handling to make the usage of the product more clear. Take everything in this repository as an example and not something to be copy pasted into a production ready service.
This project demonstrates how to automate preventing DeFi liquidations & optimizing interest rates on multiple blockchains using Chainlink CCIP, Chainlink Automation and Chainlink Functions.
Verify installation by typing:
node -v
and
npm -v
- Install packages
npm install
- Compile contracts
npx hardhat compile
Chainlink Cross-Chain Interoperability Protocol (CCIP) provides a single, simple, and elegant interface through which dApps and web3 entrepreneurs can securely meet all their cross-chain needs, including token transfers and arbitrary messaging.
With Chainlink CCIP, one can:
- Transfer supported tokens
- Send messages (any data)
- Send messages and tokens
CCIP receiver can be:
- Smart contract that implements
CCIPReceiver.sol
- EOA
Note: If you send a message and token(s) to EOA, only tokens will arrive
To use this project, you can consider CCIP as a "black-box" component and be aware of the Router contract only. If you want to dive deep into it, check the Official Chainlink Documentation.
Managing multiple DeFi positions is hard. Managing multiple DeFi positions on multiple protocols is even harder. Managing multiple DeFi positions across multiple blockchains was impossible. Until now! Meet the cross-chain liquidation protection and interest rate optimizer powered by Chainlink CCIP.
Note This project is for educational purposes only and is not ready for production usage.
End-user has debt positions on multiple protocols/chains (e.g. Benqi on Avalanche, Compound on Polygon, etc…). They keep all their liquidity safely in a vault on one chain (e.g. Aave on Ethereum). On each chain where the user has a debt position (e.g. Avalanche and Polygon), Automation monitors the debt ratio. If Automation detects that any of their loans approaches the liquidation threshold (e.g. Benqi on Avalanche), then Automation sends a CCIP message to the user’s liquidity chain (e.g. Ethereum) to request remediating action (Send funds. Fast!).
When the user’s contract on the liquidity chain receives the CCIP message, it withdraws liquidity from Aave and sends a new CCIP message back to Avalanche with the funds. That message includes enough information and tokens to fund the right position and avoid liquidation.
Result: The User can have debt positions on multiple chains while still keeping liquidity on a single chain.
The User needs to: Deploy a liquidation protection smart contract (LPSC) on the liquidity chain. Create an Automation job on each chain where they have debt, by deploying the Monitor contract.
Optionally the User can run a Functions job with JavaScript that checks interest rates on all major lending protocols on all chains, using the executeRequest
function of the LPSC smart contract. If it detects that the User is overpaying interest, it will try to pay back the loan and refinance somewhere else.
There is only one liquidity chain on which we are deploying the LPSC. It is set by default to ethereumSepolia
but you can change it by adjusting the defaultNetwork
property of the config
object in the hardhat.config.ts
file:
const config: HardhatUserConfig = {
defaultNetwork: 'ethereumSepolia', // Liquidity Chain
...
}
There are several Hardhat tasks available for deployment and interaction with this project. But before that, you need to set up some environment variables.
We are going to use the @chainlink/env-enc
package for extra security. It encrypts sensitive data instead of storing them as plain text in the .env
file, by creating a new, .env.enc
file. Although it's not recommended to push this file online, if that accidentally happens your secrets will still be encrypted.
- Set a password for encrypting and decrypting the environment variable file. You can change it later by typing the same command.
npx env-enc set-pw
- Now set the following environment variables:
PRIVATE_KEY
, Source Blockchain RPC URL, Destination Blockchain RPC URL. You can see available options in the.env.example
file:
PRIVATE_KEY=""
ETHEREUM_SEPOLIA_RPC_URL=""
OPTIMISM_GOERLI_RPC_URL=""
ARBITRUM_TESTNET_RPC_URL=""
AVALANCHE_FUJI_RPC_URL=""
POLYGON_MUMBAI_RPC_URL=""
To set these variables, type the following command and follow the instructions in the terminal:
npx env-enc set
After you are done, the .env.enc
file will be automatically generated.
If you want to validate your inputs you can always run the next command:
npx env-enc view
To test this project before the actual usage, there are a couple of mock contracts, the two most notable are MockVault.sol
and MockLending.sol
. MockVault represents the protocol on the liquidity chain where the User stores its funds such as Aave for example, while MockLending represents a protocol on other chains where the User can open debt positions, such as Aave, Compound, Benqi and such.
- To deploy the
MockVault.sol
smart contract you can use thedeploy-mock-vault
task, by running:
npx hardhat deploy-mock-vault
- To deploy the
MockLending.sol
smart contract you can use thedeploy-mock-lending
task, by running:
npx hardhat deploy-mock-lending --network <NETWORK>
Where can be:
- ethereumSepolia
- polygonMumbai
- optimismGoerli
- arbitrumTestnet
- avalancheFuji
- To deploy the
LPSC.sol
smart contract you can use thedeploy-lpsc
task:
npx hardhat deploy-lpsc
--router <CCIP_ROUTER_ADDRESS>
--vault <VAULT_CONTRACT_ADDRESS>
For example, to deploy the LPSC.sol
smart contract to the Liquidity Chain (defaultNetwork: 'ethereumSepolia'
) run:
npx hardhat deploy-lpsc --router 0xD0daae2231E9CB96b94C8512223533293C3693Bf --vault <MOCK_VAULT_ADDRESS>
- To deploy the
MonitorMockLending.sol
smart contract you can usedeploy-monitor-mock-lending
task:
npx hardhat deploy-monitor-mock-lending
--min-health-factor <MIN_HEALTH_FACTOR_BEFORE_LIQUIDATION_SAVING_IS_TRIGGERED>
--router <CCIP_ROUTER_ADDRESS>
--lending-address <MOCK_LENDING_ADDRESS>
--token-address <REPAY_TOKEN_ADDRESS>
--on-behalf-of <YOUR_EOA_ADDRESS>
--lpsc <LPSC_CONTRACT_ADDRESS>
--liquidation-chain-selector <LIQUIDATION_CHAIN_SELECTOR>
--link <LINK_TOKEN_ADDRESS> # Optional
- Chainlink CCIP fees are being paid in LINK tokens. Contracts that send CCIP Cross-Chain Messages need to be funded. In our project, those are
LPSC.sol
and each of the Monitor contracts deployed. To fund them you can either manually send LINK tokens to their addresses from your wallet or use thefund
task:
npx hardhat fund
--receiver <RECEIVER_CONTRACT_ADDRESS>
--amount <AMOUNT_IN_JUELS_TO_SEND>
--link <LINK_TOKEN_ADDRESS> # Optional
For example, to fund the LPSC.sol
smart contract with 0.1 Sepolia LINK, run:
npx hardhat fund --receiver <LPSC_CONTRACT_ADDRESS> --amount 100000000000000000
To fund the MonitorMockLending.sol
smart contract with 0.1 Fuji LINK, run:
npx hardhat fund --receiver <MONITOR_MOCK_LENDING_CONTRACT_ADDRESS> --amount 100000000000000000 --network avalancheFuji
- Register a Custom Logic Chainlink Automation Upkeep by following the guide from the Official Chainlink Documentation
Since all Monitors contracts are Chainlink Automation Compatible, once that is set up, it will start monitoring if any positions need to be saved from liquidation, and you can sit back and relax since from that moment the project is officially set up to auto-pilot mode.
For the purpose of testing, you can manually trigger the mock liquidation-saving process, by running the following command:
npx hardhat trigger --monitor <MONITOR_MOCK_LENDING_CONTRACT_ADDRESS> --network avalancheFuji
- Optionally, you can move your funds on the liquidation chain from one protocol to another using Chainlink Functions
To simulate doing that, run the following command:
npx hardhat functions-simulate --network hardhat
To expand this functionality to multiple chains, to find better yield resources, or to add more features, use the Chainlink Functions Playground for the development of the JavaScript code of Functions-request-source.js
file.
There is always a possibility that this fully automated process throws an error, most likely because you don't have enough funds to perform the liquidation saving. If that happens, don't panic, LPSC has a system to trigger certain actions manually from the owner
account.
Most likely, the failure is because you are low on liquidity to cover debt(s), but there are other possible errors. Once you figure out the cause of failure & fix it, you have two options to manually complete the execution:
Go to CCIP Explorer, search for a failed transaction, connect your wallet and click the 'Trigger Manual Execution' button.
Navigate to the 'Source Transaction Hash' from the CCIP Explorer and try to locate the CCIP Message ID from the event logs
And then call the reply()
function on the LPSC.sol
smart contract, using the reply
task:
npx hardhat reply
--lpsc <LPSC_ADDRESS_ON_SEPOLIA>
--requested-token-address <REQUESTED_TOKEN_ADDRESS_ON_SOURCE_CHAIN> # Avalanche Fuji in our example
--amount <AMOUNT_REQUESTED>
--source-chain-selector <SOURCE_CHAIN_CHAIN_SELECTOR> # 14767482510784806043 for Avalanche Fuji in our example
--monitor-address <MONITOR_BOT_ADDRESS_ON_SOURCE_CHAIN>
--message-id <ID_OF_THE_CCIP_MESSAGE_THAT_FAILED_TO_EXECUTE_ON_SEPOLIA> # from the logs
To manually trigger it for our mock example, run:
npx hardhat reply --lpsc <LPSC_ADDRESS> --requested-token-address 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 --amount 1000000000000000000 --source-chain-selector 14767482510784806043 --monitor-address <MONITOR_MOCK_LENDING_ADDRESS> --message-id <CCIP_MESSAGE_ID>
Where 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 is the CCIP_BnM token address on Avalanche Fuji blockchain.
Other monitor bot smart contracts can be deployed and configured using Chainlink Automation. Currently available are: