EDUCHAIN Agent Kit
- src
import axios from 'axios';
import * as subgraph from './subgraph.js';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
// Default configuration
let config = {
apiUrl: 'https://min-api.cryptocompare.com/data/pricemultifull',
apiKey: '', // Optional API key
symbols: {
EDU: 'EDU',
USD: 'USD'
}
};
// Path to the config file
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, '..', 'config', 'external_market_config.json');
// Load configuration from file if it exists
try {
if (fs.existsSync(configPath)) {
const configData = fs.readFileSync(configPath, 'utf8');
config = { ...config, ...JSON.parse(configData) };
} else {
// Create config directory if it doesn't exist
const configDir = path.join(__dirname, '..', 'config');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// Save default config
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
}
} catch (error) {
console.error('Error loading external market config:', error);
}
/**
* Update the external market API configuration
* @param newConfig New configuration options
*/
export function updateConfig(newConfig: Partial<typeof config>): void {
config = { ...config, ...newConfig };
// Save updated config to file
try {
const configDir = path.join(__dirname, '..', 'config');
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
} catch (error) {
console.error('Error saving external market config:', error);
}
}
/**
* Get the current external market API configuration
*/
export function getConfig(): typeof config {
return { ...config };
}
/**
* Fetch external market data for EDU from CryptoCompare
*/
export async function getExternalMarketData(): Promise<{
price: number;
change24h: number;
high24h: number;
low24h: number;
volume24h: number;
marketCap: number;
lastUpdate: string;
source: string;
}> {
try {
const { apiUrl, apiKey, symbols } = config;
// Build request URL
const url = `${apiUrl}?fsyms=${symbols.EDU}&tsyms=${symbols.USD}`;
// Set up headers with API key if provided
const headers: Record<string, string> = {};
if (apiKey) {
headers['authorization'] = `Apikey ${apiKey}`;
}
// Make API request
const response = await axios.get(url, { headers });
// Extract data
const data = response.data;
if (!data.RAW || !data.RAW[symbols.EDU] || !data.RAW[symbols.EDU][symbols.USD]) {
throw new Error('Invalid response format from external API');
}
const rawData = data.RAW[symbols.EDU][symbols.USD];
return {
price: rawData.PRICE || 0,
change24h: rawData.CHANGE24HOUR || 0,
high24h: rawData.HIGH24HOUR || 0,
low24h: rawData.LOW24HOUR || 0,
volume24h: rawData.VOLUME24HOUR || 0,
marketCap: rawData.MKTCAP || 0,
lastUpdate: new Date(rawData.LASTUPDATE * 1000).toISOString(),
source: 'CryptoCompare'
};
} catch (error) {
console.error('Error fetching external market data:', error);
throw error;
}
}
/**
* Check for arbitrage opportunities between CEX and SailFish DEX
* @param threshold Minimum price difference percentage to consider as an arbitrage opportunity
*/
export async function checkArbitrageOpportunities(threshold: number = 1.0): Promise<{
cexPrice: number;
dexPrice: number;
priceDifference: number;
priceDifferencePercentage: number;
arbitrageOpportunity: boolean;
direction: 'BUY_CEX_SELL_DEX' | 'BUY_DEX_SELL_CEX' | 'NONE';
potentialProfit: number;
timestamp: string;
}> {
try {
// Get external market data
const externalData = await getExternalMarketData();
const cexPrice = externalData.price;
// Get DEX price from SailFish
const dexPrice = await subgraph.getEthPrice();
const dexPriceNumber = parseFloat(dexPrice || '0');
// Calculate price difference
const priceDifference = Math.abs(cexPrice - dexPriceNumber);
const priceDifferencePercentage = (priceDifference / Math.min(cexPrice, dexPriceNumber)) * 100;
// Determine arbitrage opportunity
const arbitrageOpportunity = priceDifferencePercentage >= threshold;
// Determine direction
let direction: 'BUY_CEX_SELL_DEX' | 'BUY_DEX_SELL_CEX' | 'NONE' = 'NONE';
if (arbitrageOpportunity) {
direction = cexPrice < dexPriceNumber ? 'BUY_CEX_SELL_DEX' : 'BUY_DEX_SELL_CEX';
}
// Calculate potential profit (simplified)
const potentialProfit = arbitrageOpportunity ? priceDifference : 0;
return {
cexPrice,
dexPrice: dexPriceNumber,
priceDifference,
priceDifferencePercentage,
arbitrageOpportunity,
direction,
potentialProfit,
timestamp: new Date().toISOString()
};
} catch (error) {
console.error('Error checking arbitrage opportunities:', error);
throw error;
}
}