require('dotenv').config(); const { ethers } = require('ethers'); const express = require('express'); const { execute } = require('./.graphclient'); const { bytesToUint256 } = require('kraiken-lib'); const myQuery = ` query GetPositions { positions(first: 5000, where: {status: "Active"}) { id share lastTaxTime taxRate status } } ` // Load environment variables const PROVIDER_URL = process.env.PROVIDER_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; const LM_CONTRACT_ADDRESS = process.env.LM_CONTRACT_ADDRESS; const STAKE_CONTRACT_ADDRESS = process.env.STAKE_CONTRACT_ADDRESS; const LM_ABI = [ {"type":"function","name":"recenter","inputs":[],"outputs":[],"stateMutability":"nonpayable"} ]; const STAKE_ABI = [ {"inputs":[{"internalType":"uint256","name":"positionId","type":"uint256"}],"name":"payTax","outputs":[],"stateMutability":"nonpayable","type":"function"} ]; // Initialize the provider const provider = new ethers.JsonRpcProvider(PROVIDER_URL); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); const liquidityManager = new ethers.Contract(LM_CONTRACT_ADDRESS, LM_ABI, wallet); const stakeContract = new ethers.Contract(STAKE_CONTRACT_ADDRESS, STAKE_ABI, wallet); let startTime = new Date(); let lastRecenterTime = null; let lastLiquidationTime = null; async function checkFunds() { const balance = await provider.getBalance(wallet.address); return ethers.formatEther(balance); } async function canCallFunction() { try { // this will throw if the function is not callable await liquidityManager.recenter.estimateGas(); return true; } catch (error) { return false; } } async function checkPosition(position) { let taxRate = parseFloat(position.taxRate); let lastTaxTime = position.lastTaxTime; let passed = (Date.now() / 1000) - lastTaxTime; if (passed > 365 * 24 * 60 * 60 / taxRate ) { const hexString = position.id; const cleanHexString = hexString.startsWith('0x') ? hexString.substring(2) : hexString; const bytes = new Uint8Array(Math.ceil(cleanHexString.length / 2)); for (let i = 0, j = 0; i < cleanHexString.length; i += 2, j++) { bytes[j] = parseInt(cleanHexString.slice(i, i + 2), 16); } let positionId = bytesToUint256(bytes); console.log(`Calling payTax on ${positionId}`); const tx = await stakeContract.payTax(positionId); await tx.wait(); lastLiquidationTime = new Date(); return 1; } else { return 0; } } function formatDuration(ms) { let seconds = Math.floor(ms / 1000); let minutes = Math.floor(seconds / 60); let hours = Math.floor(minutes / 60); let days = Math.floor(hours / 24); seconds = seconds % 60; minutes = minutes % 60; hours = hours % 24; return `${days} days, ${hours} hours, ${minutes} minutes`; } async function liquidityLoop() { try { if (await canCallFunction()) { console.log('Calling recenter...'); const tx = await liquidityManager.recenter(); await tx.wait(); lastRecenterTime = new Date(); console.log('recenter called successfully.'); } else { console.log(`No liquidity can be moved at the moment. - ${(new Date()).toISOString()}`); } } catch (error) { console.error('Error in liquidity loop:', error); } } async function liquidationLoop() { let counter = 0; try { const result = await execute(myQuery, {}); for (const position of result.data.positions) { counter = counter + await checkPosition(position); } if (counter == 0) { console.log(`No tax can be claimed at the moment. - ${(new Date()).toISOString()}`); } } catch (error) { console.error('Error in liquididation loop:', error); } } async function main() { console.log('Service started...'); await liquidityLoop(); await liquidationLoop(); setInterval(liquidityLoop, 3 * 60000); // 3 minute setInterval(liquidationLoop, 20 * 60000); // 20 minutes } // Start the main loop main().catch(async (error) => { console.error('Fatal error:', error); }); // Set up the Express server const app = express(); const PORT = process.env.PORT || 3000; app.get('/status', async (req, res) => { try { const balance = await checkFunds(); const uptime = formatDuration(new Date() - startTime); const status = { balance: `${balance} ETH`, uptime: uptime, lastRecenterTime: lastRecenterTime ? lastRecenterTime.toISOString() : 'Never', lastLiquidationTime: lastLiquidationTime ? lastLiquidationTime.toISOString() : 'Never' }; if (parseFloat(balance) < 0.1) { res.status(500).send(`Low Ethereum Balance: ${balance} ETH`); } else { res.status(200).json(status); } } catch (error) { res.status(500).send(`Error checking funds: ${error.message}`); } }); app.listen(PORT, () => { console.log(`HTTP server running on port ${PORT}`); });