merge liquidity and liquidation bot
This commit is contained in:
parent
af351b9cfc
commit
ad0c709809
10 changed files with 173 additions and 198 deletions
|
|
@ -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}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -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}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
4
services/txnBot/.env.template
Normal file
4
services/txnBot/.env.template
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
PROVIDER_URL=
|
||||||
|
PRIVATE_KEY=
|
||||||
|
LM_CONTRACT_ADDRESS=
|
||||||
|
STAKE_CONTRACT_ADDRESS=
|
||||||
|
|
@ -3,6 +3,7 @@ sources:
|
||||||
- name: harberg
|
- name: harberg
|
||||||
handler:
|
handler:
|
||||||
graphql:
|
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
|
endpoint: https://api.studio.thegraph.com/query/47986/harberg-base-sepolia/version/latest
|
||||||
transforms:
|
transforms:
|
||||||
- autoPagination:
|
- autoPagination:
|
||||||
168
services/txnBot/service.js
Normal file
168
services/txnBot/service.js
Normal file
|
|
@ -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}`);
|
||||||
|
});
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue