From ad0c709809138c470e3c5f80b385e9b35806af80 Mon Sep 17 00:00:00 2001 From: JulesCrown Date: Tue, 17 Sep 2024 11:23:43 +0200 Subject: [PATCH] merge liquidity and liquidation bot --- services/marketMaker/liquidations.js | 92 ---------- services/marketMaker/service.js | 106 ----------- services/txnBot/.env.template | 4 + services/{marketMaker => txnBot}/.gitignore | 0 .../.graphclientrc.yml | 1 + services/{marketMaker => txnBot}/.graphql | 0 services/{marketMaker => txnBot}/README.md | 0 .../{marketMaker => txnBot}/generateKey.js | 0 services/{marketMaker => txnBot}/package.json | 0 services/txnBot/service.js | 168 ++++++++++++++++++ 10 files changed, 173 insertions(+), 198 deletions(-) delete mode 100644 services/marketMaker/liquidations.js delete mode 100644 services/marketMaker/service.js create mode 100644 services/txnBot/.env.template rename services/{marketMaker => txnBot}/.gitignore (100%) rename services/{marketMaker => txnBot}/.graphclientrc.yml (77%) rename services/{marketMaker => txnBot}/.graphql (100%) rename services/{marketMaker => txnBot}/README.md (100%) rename services/{marketMaker => txnBot}/generateKey.js (100%) rename services/{marketMaker => txnBot}/package.json (100%) create mode 100644 services/txnBot/service.js diff --git a/services/marketMaker/liquidations.js b/services/marketMaker/liquidations.js deleted file mode 100644 index ad897a8..0000000 --- a/services/marketMaker/liquidations.js +++ /dev/null @@ -1,92 +0,0 @@ -require('dotenv').config(); -const { ethers } = require('ethers'); -const express = require('express'); -const { execute } = require('./.graphclient'); -const { bytesToUint256 } = require('harb-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 STAKE_CONTRACT_ADDRESS = process.env.STAKE_CONTRACT_ADDRESS; -const ABI = [ - // Add your contract's ABI here - {"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 contract = new ethers.Contract(STAKE_CONTRACT_ADDRESS, ABI, wallet); - -let startTime = new Date(); -let lastCallTime = null; - -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 contract.payTax(positionId); - await tx.wait(); - lastCallTime = new Date(); - } else { - console.log("not liquidated: "); - console.log(position); - } -} - - -async function main() { - console.log('Service started...'); - - while (true) { - // do stuff here - - // Wait for some time before checking again - await new Promise(resolve => setTimeout(resolve, 300000)); // 5 minute - } -} - -// 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('/query', async (req, res) => { - const result = await execute(myQuery, {}); - res.status(200).json(result); - for (const position of result.data.positions) { - await checkPosition(position); - } -}); - - -app.listen(PORT, () => { - console.log(`HTTP server running on port ${PORT}`); -}); - diff --git a/services/marketMaker/service.js b/services/marketMaker/service.js deleted file mode 100644 index dd4ded4..0000000 --- a/services/marketMaker/service.js +++ /dev/null @@ -1,106 +0,0 @@ -require('dotenv').config(); -const { ethers } = require('ethers'); -const express = require('express'); - -// Load environment variables -const PROVIDER_URL = process.env.PROVIDER_URL; -const PRIVATE_KEY = process.env.PRIVATE_KEY; -const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS; -const ABI = [ - // Add your contract's ABI here - {"type":"function","name":"recenter","inputs":[],"outputs":[],"stateMutability":"nonpayable"} -]; - -// Initialize the provider -const provider = new ethers.JsonRpcProvider(PROVIDER_URL); - -const wallet = new ethers.Wallet(PRIVATE_KEY, provider); -const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet); - -let startTime = new Date(); -let lastCallTime = 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 contract.recenter.estimateGas(); - return true; - } catch (error) { - return false; - } -} - -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 main() { - console.log('Service started...'); - - while (true) { - try { - if (await canCallFunction()) { - console.log('Calling recenter...'); - const tx = await contract.recenter(); - await tx.wait(); - lastCallTime = new Date(); - console.log('recenter called successfully.'); - } else { - console.log('No function can be called at the moment.'); - } - } catch (error) { - console.error('Error in main loop:', error); - } - - // Wait for some time before checking again - await new Promise(resolve => setTimeout(resolve, 60000)); // 1 minute - } -} - -// 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, - lastCallTime: lastCallTime ? lastCallTime.toString() : '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}`); -}); - diff --git a/services/txnBot/.env.template b/services/txnBot/.env.template new file mode 100644 index 0000000..652960e --- /dev/null +++ b/services/txnBot/.env.template @@ -0,0 +1,4 @@ +PROVIDER_URL= +PRIVATE_KEY= +LM_CONTRACT_ADDRESS= +STAKE_CONTRACT_ADDRESS= \ No newline at end of file diff --git a/services/marketMaker/.gitignore b/services/txnBot/.gitignore similarity index 100% rename from services/marketMaker/.gitignore rename to services/txnBot/.gitignore diff --git a/services/marketMaker/.graphclientrc.yml b/services/txnBot/.graphclientrc.yml similarity index 77% rename from services/marketMaker/.graphclientrc.yml rename to services/txnBot/.graphclientrc.yml index 9077c94..236c065 100644 --- a/services/marketMaker/.graphclientrc.yml +++ b/services/txnBot/.graphclientrc.yml @@ -3,6 +3,7 @@ sources: - name: harberg handler: graphql: + # endpoint: https://api.studio.thegraph.com/query/47986/harberg-base-sepolia/version/latest endpoint: https://api.studio.thegraph.com/query/47986/harberg-base-sepolia/version/latest transforms: - autoPagination: diff --git a/services/marketMaker/.graphql b/services/txnBot/.graphql similarity index 100% rename from services/marketMaker/.graphql rename to services/txnBot/.graphql diff --git a/services/marketMaker/README.md b/services/txnBot/README.md similarity index 100% rename from services/marketMaker/README.md rename to services/txnBot/README.md diff --git a/services/marketMaker/generateKey.js b/services/txnBot/generateKey.js similarity index 100% rename from services/marketMaker/generateKey.js rename to services/txnBot/generateKey.js diff --git a/services/marketMaker/package.json b/services/txnBot/package.json similarity index 100% rename from services/marketMaker/package.json rename to services/txnBot/package.json diff --git a/services/txnBot/service.js b/services/txnBot/service.js new file mode 100644 index 0000000..567a803 --- /dev/null +++ b/services/txnBot/service.js @@ -0,0 +1,168 @@ +require('dotenv').config(); +const { ethers } = require('ethers'); +const express = require('express'); +const { execute } = require('./.graphclient'); +const { bytesToUint256 } = require('harb-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 = [ + // Add your contract's ABI here + {"type":"function","name":"recenter","inputs":[],"outputs":[],"stateMutability":"nonpayable"} +]; +const STAKE_ABI = [ + // Add your contract's ABI here + {"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.toString() : 'Never', + lastLiquidationTime: lastLiquidationTime ? lastLiquidationTime.toString() : '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}`); +}); +