Skip to content

Commit

Permalink
fix types
Browse files Browse the repository at this point in the history
Signed-off-by: MarcoMandar <[email protected]>
  • Loading branch information
MarcoMandar committed Nov 1, 2024
1 parent b860c17 commit 73564bc
Show file tree
Hide file tree
Showing 2 changed files with 401 additions and 332 deletions.
106 changes: 82 additions & 24 deletions core/src/providers/token.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Connection } from "@solana/web3.js";
// import fetch from "cross-fetch";
import { IAgentRuntime, Memory, Provider, State } from "../core/types";
import settings from "../core/settings";
import BigNumber from "bignumber.js";
import * as fs from "fs";
import NodeCache from "node-cache";
import * as path from "path";
import settings from "../core/settings.ts";
import { IAgentRuntime, Memory, Provider, State } from "../core/types.ts";
import {
DexScreenerData,
HolderData,
ProcessedTokenData,
TokenSecurityData,
TokenTradeData,
} from "../types/token.ts";
import { fileURLToPath } from "url";
DexScreenerData,
// DexScreenerPair,
HolderData,
} from "../types/token";
import NodeCache from "node-cache";
import * as fs from "fs";
import * as path from "path";

const PROVIDER_CONFIG = {
BIRDEYE_API: "https://public-api.birdeye.so",
Expand All @@ -39,25 +40,28 @@ export class TokenProvider {
private tokenAddress: string
) {
this.cache = new NodeCache({ stdTTL: 300 }); // 5 minutes cache
const __filename = fileURLToPath(import.meta.url);

const __dirname = path.dirname(__filename);

this.cacheDir = path.join(__dirname, "../../tokencache");
this.cacheDir = path.join(__dirname, "cache");
if (!fs.existsSync(this.cacheDir)) {
fs.mkdirSync(this.cacheDir);
}
}

private readCacheFromFile<T>(cacheKey: string): T | null {
const filePath = path.join(this.cacheDir, `${cacheKey}.json`);
console.log({ filePath });
if (fs.existsSync(filePath)) {
const fileContent = fs.readFileSync(filePath, "utf-8");
const parsed = JSON.parse(fileContent);
const now = Date.now();
if (now < parsed.expiry) {
console.log(
`Reading cached data from file for key: ${cacheKey}`
);
return parsed.data as T;
} else {
console.log(
`Cache expired for key: ${cacheKey}. Deleting file.`
);
fs.unlinkSync(filePath);
}
}
Expand All @@ -71,6 +75,7 @@ export class TokenProvider {
expiry: Date.now() + 300000, // 5 minutes in milliseconds
};
fs.writeFileSync(filePath, JSON.stringify(cacheData), "utf-8");
console.log(`Cached data written to file for key: ${cacheKey}`);
}

private getCachedData<T>(cacheKey: string): T | null {
Expand Down Expand Up @@ -102,6 +107,7 @@ export class TokenProvider {
private async fetchWithRetry(
url: string,
options: RequestInit = {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> {
let lastError: Error;

Expand All @@ -127,9 +133,11 @@ export class TokenProvider {
const data = await response.json();
return data;
} catch (error) {
console.error(`Attempt ${i + 1} failed:`, error);
lastError = error as Error;
if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) {
const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i);
console.log(`Waiting ${delay}ms before retrying...`);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
Expand All @@ -147,6 +155,9 @@ export class TokenProvider {
const cacheKey = `tokenSecurity_${this.tokenAddress}`;
const cachedData = this.getCachedData<TokenSecurityData>(cacheKey);
if (cachedData) {
console.log(
`Returning cached token security data for ${this.tokenAddress}.`
);
return cachedData;
}
const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_SECURITY_ENDPOINT}${this.tokenAddress}`;
Expand All @@ -165,14 +176,18 @@ export class TokenProvider {
top10HolderPercent: data.data.top10HolderPercent,
};
this.setCachedData(cacheKey, security);
console.log(`Token security data cached for ${this.tokenAddress}.`);

return security;
}

async fetchTokenTradeData(runtime: IAgentRuntime): Promise<TokenTradeData> {
async fetchTokenTradeData(): Promise<TokenTradeData> {
const cacheKey = `tokenTradeData_${this.tokenAddress}`;
const cachedData = this.getCachedData<TokenTradeData>(cacheKey);
if (cachedData) {
console.log(
`Returning cached token trade data for ${this.tokenAddress}.`
);
return cachedData;
}

Expand All @@ -181,7 +196,7 @@ export class TokenProvider {
method: "GET",
headers: {
accept: "application/json",
"X-API-KEY": runtime.getSetting("BIRDEYE_API_KEY") || "",
"X-API-KEY": settings.BIRDEYE_API_KEY || "",
},
};

Expand Down Expand Up @@ -405,11 +420,15 @@ export class TokenProvider {
const cacheKey = `dexScreenerData_${this.tokenAddress}`;
const cachedData = this.getCachedData<DexScreenerData>(cacheKey);
if (cachedData) {
console.log("Returning cached DexScreener data.");
return cachedData;
}

const url = `https://api.dexscreener.com/latest/dex/search?q=${this.tokenAddress}`;
try {
console.log(
`Fetching DexScreener data for token: ${this.tokenAddress}`
);
const data = await fetch(url)
.then((res) => res.json())
.catch((err) => {
Expand Down Expand Up @@ -488,6 +507,7 @@ export class TokenProvider {
const cacheKey = `holderList_${this.tokenAddress}`;
const cachedData = this.getCachedData<HolderData[]>(cacheKey);
if (cachedData) {
console.log("Returning cached holder list.");
return cachedData;
}

Expand All @@ -497,8 +517,10 @@ export class TokenProvider {
let cursor;
//HELIOUS_API_KEY needs to be added
const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIOUS_API_KEY || ""}`;
console.log({ url });

try {
// eslint-disable-next-line no-constant-condition
while (true) {
const params = {
limit: limit,
Expand All @@ -509,7 +531,7 @@ export class TokenProvider {
if (cursor != undefined) {
params.cursor = cursor;
}

console.log(`Fetching holders - Page ${page}`);
if (page > 2) {
break;
}
Expand All @@ -534,9 +556,17 @@ export class TokenProvider {
!data.result.token_accounts ||
data.result.token_accounts.length === 0
) {
console.log(
`No more holders found. Total pages fetched: ${page - 1}`
);
break;
}

console.log(
`Processing ${data.result.token_accounts.length} holders from page ${page}`
);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
data.result.token_accounts.forEach((account: any) => {
const owner = account.owner;
const balance = parseFloat(account.amount);
Expand All @@ -561,6 +591,8 @@ export class TokenProvider {
balance: balance.toString(),
}));

console.log(`Total unique holders fetched: ${holders.length}`);

// Cache the result
this.setCachedData(cacheKey, holders);

Expand Down Expand Up @@ -620,27 +652,47 @@ export class TokenProvider {
}
}

async getProcessedTokenData(
runtime: IAgentRuntime
): Promise<ProcessedTokenData> {
async getProcessedTokenData(): Promise<ProcessedTokenData> {
try {
console.log(
`Fetching security data for token: ${this.tokenAddress}`
);
const security = await this.fetchTokenSecurity();

const tradeData = await this.fetchTokenTradeData(runtime);
console.log(`Fetching trade data for token: ${this.tokenAddress}`);
const tradeData = await this.fetchTokenTradeData();

console.log(
`Fetching DexScreener data for token: ${this.tokenAddress}`
);
const dexData = await this.fetchDexScreenerData();

console.log(
`Analyzing holder distribution for token: ${this.tokenAddress}`
);
const holderDistributionTrend =
await this.analyzeHolderDistribution(tradeData);

console.log(
`Filtering high-value holders for token: ${this.tokenAddress}`
);
const highValueHolders =
await this.filterHighValueHolders(tradeData);

console.log(
`Checking recent trades for token: ${this.tokenAddress}`
);
const recentTrades = await this.checkRecentTrades(tradeData);

console.log(
`Counting high-supply holders for token: ${this.tokenAddress}`
);
const highSupplyHoldersCount =
await this.countHighSupplyHolders(security);

console.log(
`Determining DexScreener listing status for token: ${this.tokenAddress}`
);
const isDexScreenerListed = dexData.pairs.length > 0;
const isDexScreenerPaid = dexData.pairs.some(
(pair) => pair.boosts && pair.boosts.active > 0
Expand All @@ -658,6 +710,7 @@ export class TokenProvider {
isDexScreenerPaid,
};

// console.log("Processed token data:", processedData);
return processedData;
} catch (error) {
console.error("Error processing token data:", error);
Expand Down Expand Up @@ -725,12 +778,14 @@ export class TokenProvider {
}
output += `\n`;

console.log("Formatted token data:", output);
return output;
}

async getFormattedTokenReport(runtime: IAgentRuntime): Promise<string> {
async getFormattedTokenReport(): Promise<string> {
try {
const processedData = await this.getProcessedTokenData(runtime);
console.log("Generating formatted token report...");
const processedData = await this.getProcessedTokenData();
return this.formatTokenData(processedData);
} catch (error) {
console.error("Error generating token report:", error);
Expand All @@ -740,15 +795,18 @@ export class TokenProvider {
}

const tokenAddress = PROVIDER_CONFIG.TOKEN_ADDRESSES.Example;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC);
const tokenProvider: Provider = {
get: async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
runtime: IAgentRuntime,
_message: Memory,
_state?: State
): Promise<string> => {
try {
const provider = new TokenProvider(tokenAddress);
return provider.getFormattedTokenReport(runtime);
return provider.getFormattedTokenReport();
} catch (error) {
console.error("Error fetching token data:", error);
return "Unable to fetch token information. Please try again later.";
Expand Down
Loading

0 comments on commit 73564bc

Please sign in to comment.