Files
smom-dbis-138/scripts/reserve/keeper-service.js
defiQUG 8dc7562702
Some checks failed
Verify Deployment / Verify Deployment (push) Has been cancelled
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
Update OpenZeppelin contracts submodule to a dirty state
2025-12-12 16:25:54 -08:00

212 lines
7.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Price Feed Keeper Service
* Automatically updates price feeds at regular intervals
*
* Usage:
* node scripts/reserve/keeper-service.js
*
* Environment Variables:
* RPC_URL_138 - ChainID 138 RPC endpoint
* KEEPER_PRIVATE_KEY - Keeper wallet private key
* PRICE_FEED_KEEPER_ADDRESS - PriceFeedKeeper contract address
* UPDATE_INTERVAL - Update interval in seconds (default: 30)
*/
const { ethers } = require('ethers');
const fs = require('fs');
const path = require('path');
// Load environment variables
require('dotenv').config();
// Configuration
const CONFIG = {
rpcUrl: process.env.RPC_URL_138 || 'http://localhost:8545',
keeperPrivateKey: process.env.KEEPER_PRIVATE_KEY,
keeperAddress: process.env.PRICE_FEED_KEEPER_ADDRESS,
updateInterval: parseInt(process.env.UPDATE_INTERVAL || '30') * 1000, // Convert to milliseconds
maxRetries: 3,
retryDelay: 5000, // 5 seconds
};
// ABI for PriceFeedKeeper
const KEEPER_ABI = [
"function checkUpkeep() external view returns (bool needsUpdate, address[] memory assets)",
"function performUpkeep() external returns (bool success, address[] memory updatedAssets)",
"function updateAssets(address[] calldata assets) external",
"function getTrackedAssets() external view returns (address[] memory)",
"function needsUpdate(address asset) external view returns (bool)",
"function updateInterval() external view returns (uint256)",
"function maxUpdatesPerCall() external view returns (uint256)",
"event PriceFeedsUpdated(address[] assets, uint256 timestamp)"
];
class PriceFeedKeeperService {
constructor() {
if (!CONFIG.keeperPrivateKey) {
throw new Error('KEEPER_PRIVATE_KEY environment variable is required');
}
if (!CONFIG.keeperAddress) {
throw new Error('PRICE_FEED_KEEPER_ADDRESS environment variable is required');
}
this.provider = new ethers.JsonRpcProvider(CONFIG.rpcUrl);
this.wallet = new ethers.Wallet(CONFIG.keeperPrivateKey, this.provider);
this.keeper = new ethers.Contract(CONFIG.keeperAddress, KEEPER_ABI, this.wallet);
this.isRunning = false;
this.updateCount = 0;
this.errorCount = 0;
}
async start() {
console.log('=== Price Feed Keeper Service ===');
console.log(`RPC URL: ${CONFIG.rpcUrl}`);
console.log(`Keeper Address: ${CONFIG.keeperAddress}`);
console.log(`Wallet Address: ${this.wallet.address}`);
console.log(`Update Interval: ${CONFIG.updateInterval / 1000} seconds`);
console.log('');
// Verify keeper contract
try {
const trackedAssets = await this.keeper.getTrackedAssets();
const updateInterval = await this.keeper.updateInterval();
const maxUpdates = await this.keeper.maxUpdatesPerCall();
console.log(`Tracked Assets: ${trackedAssets.length}`);
trackedAssets.forEach((asset, i) => {
console.log(` ${i + 1}. ${asset}`);
});
console.log(`Update Interval: ${updateInterval.toString()} seconds`);
console.log(`Max Updates Per Call: ${maxUpdates.toString()}`);
console.log('');
} catch (error) {
console.error('Error verifying keeper contract:', error.message);
process.exit(1);
}
this.isRunning = true;
console.log('Keeper service started. Press Ctrl+C to stop.');
console.log('');
// Start update loop
this.updateLoop();
}
async updateLoop() {
while (this.isRunning) {
try {
await this.performUpkeep();
} catch (error) {
console.error(`Error in update loop: ${error.message}`);
this.errorCount++;
}
// Wait for next interval
await this.sleep(CONFIG.updateInterval);
}
}
async performUpkeep() {
try {
// Check if upkeep is needed
const [needsUpdate, assets] = await this.keeper.checkUpkeep();
if (!needsUpdate || assets.length === 0) {
console.log(`[${new Date().toISOString()}] No updates needed`);
return;
}
console.log(`[${new Date().toISOString()}] Updating ${assets.length} asset(s)...`);
// Perform upkeep
let retries = 0;
let success = false;
while (retries < CONFIG.maxRetries && !success) {
try {
const tx = await this.keeper.performUpkeep();
console.log(` Transaction sent: ${tx.hash}`);
const receipt = await tx.wait();
if (receipt.status === 1) {
success = true;
this.updateCount++;
// Parse events
const events = receipt.logs.filter(log => {
try {
const parsed = this.keeper.interface.parseLog(log);
return parsed.name === 'PriceFeedsUpdated';
} catch {
return false;
}
});
if (events.length > 0) {
const event = this.keeper.interface.parseLog(events[0]);
console.log(` Updated assets: ${event.args.assets.length}`);
console.log(` Timestamp: ${event.args.timestamp.toString()}`);
}
console.log(` ✓ Update successful (Gas: ${receipt.gasUsed.toString()})`);
} else {
throw new Error('Transaction failed');
}
} catch (error) {
retries++;
if (retries < CONFIG.maxRetries) {
console.log(` Retry ${retries}/${CONFIG.maxRetries}...`);
await this.sleep(CONFIG.retryDelay);
} else {
throw error;
}
}
}
if (!success) {
throw new Error('Failed after retries');
}
} catch (error) {
console.error(` ✗ Update failed: ${error.message}`);
this.errorCount++;
throw error;
}
}
async stop() {
console.log('\nStopping keeper service...');
this.isRunning = false;
console.log('\n=== Statistics ===');
console.log(`Total Updates: ${this.updateCount}`);
console.log(`Total Errors: ${this.errorCount}`);
console.log(`Success Rate: ${this.updateCount > 0 ? ((this.updateCount / (this.updateCount + this.errorCount)) * 100).toFixed(2) : 0}%`);
process.exit(0);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Main execution
if (require.main === module) {
const keeper = new PriceFeedKeeperService();
// Handle graceful shutdown
process.on('SIGINT', () => keeper.stop());
process.on('SIGTERM', () => keeper.stop());
// Start keeper
keeper.start().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = PriceFeedKeeperService;