@fin.cx/opendata
Version:
A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stoc
627 lines • 53.5 kB
JavaScript
import * as plugins from '../../plugins.js';
/**
* Custom error for rate limit exceeded responses
*/
class RateLimitError extends Error {
waitTime;
retryAfter;
constructor(message, waitTime, retryAfter) {
super(message);
this.waitTime = waitTime;
this.retryAfter = retryAfter;
this.name = 'RateLimitError';
}
}
/**
* Rate limiter for CoinGecko API
* Free tier (Demo): 30 requests per minute
* Without registration: 5-15 requests per minute
*/
class RateLimiter {
requestTimes = [];
maxRequestsPerMinute;
consecutiveRateLimitErrors = 0;
constructor(maxRequestsPerMinute = 30) {
this.maxRequestsPerMinute = maxRequestsPerMinute;
}
async waitForSlot() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Remove requests older than 1 minute
this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);
// If we've hit the limit, wait
if (this.requestTimes.length >= this.maxRequestsPerMinute) {
const oldestRequest = this.requestTimes[0];
const waitTime = 60000 - (now - oldestRequest) + 100; // +100ms buffer
await plugins.smartdelay.delayFor(waitTime);
return this.waitForSlot(); // Recursively check again
}
// Record this request
this.requestTimes.push(now);
}
/**
* Get time in milliseconds until next request slot is available
*/
getTimeUntilNextSlot() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Clean old requests
const recentRequests = this.requestTimes.filter(time => time > oneMinuteAgo);
if (recentRequests.length < this.maxRequestsPerMinute) {
return 0; // Slot available now
}
// Calculate wait time until oldest request expires
const oldestRequest = recentRequests[0];
return Math.max(0, 60000 - (now - oldestRequest) + 100);
}
/**
* Handle rate limit error with exponential backoff
* Returns wait time in milliseconds
*/
handleRateLimitError() {
this.consecutiveRateLimitErrors++;
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 60s (max)
const baseWait = 1000; // 1 second
const exponent = this.consecutiveRateLimitErrors - 1;
const backoff = Math.min(baseWait * Math.pow(2, exponent), 60000 // max 60 seconds
);
// After 3 consecutive 429s, reduce rate limit to 80% as safety measure
if (this.consecutiveRateLimitErrors >= 3) {
const newLimit = Math.floor(this.maxRequestsPerMinute * 0.8);
if (newLimit < this.maxRequestsPerMinute) {
console.warn(`Adjusting rate limit from ${this.maxRequestsPerMinute} to ${newLimit} requests/min due to repeated 429 errors`);
this.maxRequestsPerMinute = newLimit;
}
}
return backoff;
}
/**
* Reset consecutive error count on successful request
*/
resetErrors() {
if (this.consecutiveRateLimitErrors > 0) {
this.consecutiveRateLimitErrors = 0;
}
}
}
/**
* CoinGecko Crypto Price Provider
*
* Documentation: https://docs.coingecko.com/v3.0.1/reference/endpoint-overview
*
* Features:
* - Current crypto prices (single and batch)
* - Historical price data with OHLCV
* - 13M+ tokens, 240+ networks, 1600+ exchanges
* - Accepts both ticker symbols (BTC, ETH) and CoinGecko IDs (bitcoin, ethereum)
* - 24/7 market data (crypto never closes)
*
* Rate Limits:
* - Free tier (no key): 5-15 requests/minute
* - Demo plan (free with registration): ~30 requests/minute, 10,000/month
* - Paid plans: Higher limits
*
* API Authentication:
* - Optional API key for Demo/paid plans
* - Header: x-cg-demo-api-key (Demo) or x-cg-pro-api-key (paid)
*/
export class CoinGeckoProvider {
config;
name = 'CoinGecko';
priority = 90; // High priority for crypto, between Yahoo (100) and Marketstack (80)
requiresAuth = false; // API key is optional
rateLimit = {
requestsPerMinute: 30, // Demo plan default
requestsPerDay: 10000 // Demo plan monthly quota / 30
};
logger = console;
baseUrl = 'https://api.coingecko.com/api/v3';
apiKey;
rateLimiter;
// Coin mapping cache
coinMapCache = new Map(); // ticker/id -> coingecko id
coinListLoadedAt = null;
coinListCacheTTL = 24 * 60 * 60 * 1000; // 24 hours
// Priority map for common crypto tickers (to avoid conflicts)
priorityTickerMap = new Map([
['btc', 'bitcoin'],
['eth', 'ethereum'],
['usdt', 'tether'],
['bnb', 'binancecoin'],
['sol', 'solana'],
['usdc', 'usd-coin'],
['xrp', 'ripple'],
['ada', 'cardano'],
['doge', 'dogecoin'],
['trx', 'tron'],
['dot', 'polkadot'],
['matic', 'matic-network'],
['ltc', 'litecoin'],
['shib', 'shiba-inu'],
['avax', 'avalanche-2'],
['link', 'chainlink'],
['atom', 'cosmos'],
['uni', 'uniswap'],
['etc', 'ethereum-classic'],
['xlm', 'stellar']
]);
constructor(apiKey, config) {
this.config = config;
this.apiKey = apiKey;
this.rateLimiter = new RateLimiter(this.rateLimit.requestsPerMinute);
}
/**
* Unified data fetching method supporting all request types
*/
async fetchData(request) {
switch (request.type) {
case 'current':
return this.fetchCurrentPrice(request);
case 'batch':
return this.fetchBatchCurrentPrices(request);
case 'historical':
return this.fetchHistoricalPrices(request);
case 'intraday':
return this.fetchIntradayPrices(request);
default:
throw new Error(`Unsupported request type: ${request.type}`);
}
}
/**
* Fetch current price for a single crypto
*/
async fetchCurrentPrice(request) {
return this.fetchWithRateLimitRetry(async () => {
// Resolve ticker to CoinGecko ID
const coinId = await this.resolveCoinId(request.ticker);
// Build URL
const params = new URLSearchParams({
ids: coinId,
vs_currencies: 'usd',
include_market_cap: 'true',
include_24hr_vol: 'true',
include_24hr_change: 'true',
include_last_updated_at: 'true'
});
const url = `${this.baseUrl}/simple/price?${params}`;
// Wait for rate limit slot
await this.rateLimiter.waitForSlot();
// Make request
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.headers(this.buildHeaders())
.timeout(this.config?.timeout || 10000)
.get();
const responseData = await response.json();
// Check for rate limit error
if (this.isRateLimitError(responseData)) {
const waitTime = this.rateLimiter.handleRateLimitError();
throw new RateLimitError(`Rate limit exceeded for ${request.ticker}`, waitTime);
}
if (!responseData[coinId]) {
throw new Error(`No data found for ${request.ticker} (${coinId})`);
}
return this.mapToStockPrice(request.ticker, coinId, responseData[coinId], 'live');
}, `current price for ${request.ticker}`);
}
/**
* Fetch batch current prices for multiple cryptos
*/
async fetchBatchCurrentPrices(request) {
return this.fetchWithRateLimitRetry(async () => {
// Resolve all tickers to CoinGecko IDs
const coinIds = await Promise.all(request.tickers.map(ticker => this.resolveCoinId(ticker)));
// Build URL with comma-separated IDs
const params = new URLSearchParams({
ids: coinIds.join(','),
vs_currencies: 'usd',
include_market_cap: 'true',
include_24hr_vol: 'true',
include_24hr_change: 'true',
include_last_updated_at: 'true'
});
const url = `${this.baseUrl}/simple/price?${params}`;
// Wait for rate limit slot
await this.rateLimiter.waitForSlot();
// Make request
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.headers(this.buildHeaders())
.timeout(this.config?.timeout || 15000)
.get();
const responseData = await response.json();
// Check for rate limit error
if (this.isRateLimitError(responseData)) {
const waitTime = this.rateLimiter.handleRateLimitError();
throw new RateLimitError(`Rate limit exceeded for batch request`, waitTime);
}
const prices = [];
// Map responses back to original tickers
for (let i = 0; i < request.tickers.length; i++) {
const ticker = request.tickers[i];
const coinId = coinIds[i];
if (responseData[coinId]) {
try {
prices.push(this.mapToStockPrice(ticker, coinId, responseData[coinId], 'live'));
}
catch (error) {
this.logger.warn(`Failed to parse data for ${ticker}:`, error);
}
}
else {
this.logger.warn(`No data returned for ${ticker} (${coinId})`);
}
}
if (prices.length === 0) {
throw new Error('No valid price data received from batch request');
}
return prices;
}, `batch prices for ${request.tickers.length} tickers`);
}
/**
* Fetch historical prices with OHLCV data
*/
async fetchHistoricalPrices(request) {
return this.fetchWithRateLimitRetry(async () => {
const coinId = await this.resolveCoinId(request.ticker);
// Calculate days between dates
const days = Math.ceil((request.to.getTime() - request.from.getTime()) / (1000 * 60 * 60 * 24));
// Build URL
const params = new URLSearchParams({
vs_currency: 'usd',
days: days.toString(),
interval: 'daily' // Explicit daily granularity for historical data
});
const url = `${this.baseUrl}/coins/${coinId}/market_chart?${params}`;
// Wait for rate limit slot
await this.rateLimiter.waitForSlot();
// Make request
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.headers(this.buildHeaders())
.timeout(this.config?.timeout || 20000)
.get();
const responseData = await response.json();
// Check for rate limit error
if (this.isRateLimitError(responseData)) {
const waitTime = this.rateLimiter.handleRateLimitError();
throw new RateLimitError(`Rate limit exceeded for historical ${request.ticker}`, waitTime);
}
if (!responseData.prices || !Array.isArray(responseData.prices)) {
this.logger.error(`Invalid API response for ${request.ticker}:`, JSON.stringify(responseData).substring(0, 500));
throw new Error(`Invalid response format for ${request.ticker}: ${JSON.stringify(responseData).substring(0, 200)}`);
}
const prices = [];
const priceData = responseData.prices;
const marketCapData = responseData.market_caps || [];
const volumeData = responseData.total_volumes || [];
// Warn if processing large amount of historical data
const maxRecords = this.config?.maxRecords || 10000;
if (priceData.length > maxRecords) {
this.logger.warn(`Historical request for ${request.ticker} returned ${priceData.length} records, ` +
`which exceeds maxRecords limit of ${maxRecords}. Processing first ${maxRecords} only.`);
}
// Process each data point (up to maxRecords)
const recordsToProcess = Math.min(priceData.length, maxRecords);
for (let i = 0; i < recordsToProcess; i++) {
const [timestamp, price] = priceData[i];
const date = new Date(timestamp);
// Filter by date range
if (date < request.from || date > request.to)
continue;
const marketCap = marketCapData[i]?.[1];
const volume = volumeData[i]?.[1];
// Calculate previous close for change calculation
const previousClose = i > 0 ? priceData[i - 1][1] : price;
const change = price - previousClose;
const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
prices.push({
ticker: request.ticker.toUpperCase(),
price: price,
currency: 'USD',
change: change,
changePercent: changePercent,
previousClose: previousClose,
timestamp: date,
provider: this.name,
marketState: 'REGULAR', // Crypto markets are always open
// OHLCV data (note: market_chart doesn't provide OHLC, only close prices)
volume: volume,
dataType: 'eod',
fetchedAt: new Date(),
companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1)
});
}
return prices;
}, `historical prices for ${request.ticker}`);
}
/**
* Fetch intraday prices with hourly intervals
*/
async fetchIntradayPrices(request) {
return this.fetchWithRateLimitRetry(async () => {
const coinId = await this.resolveCoinId(request.ticker);
// Map interval to days parameter (CoinGecko auto-granularity)
// For hourly data, request 1-7 days
let days = 1;
switch (request.interval) {
case '1min':
case '5min':
case '10min':
case '15min':
case '30min':
throw new Error('CoinGecko only supports hourly intervals in market_chart. Use interval: "1hour"');
case '1hour':
days = 1; // Last 24 hours with hourly granularity
break;
}
// Build URL (omit interval param for automatic granularity based on days)
const params = new URLSearchParams({
vs_currency: 'usd',
days: days.toString()
});
const url = `${this.baseUrl}/coins/${coinId}/market_chart?${params}`;
// Wait for rate limit slot
await this.rateLimiter.waitForSlot();
// Make request
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.headers(this.buildHeaders())
.timeout(this.config?.timeout || 15000)
.get();
const responseData = await response.json();
// Check for rate limit error
if (this.isRateLimitError(responseData)) {
const waitTime = this.rateLimiter.handleRateLimitError();
throw new RateLimitError(`Rate limit exceeded for intraday ${request.ticker}`, waitTime);
}
if (!responseData.prices || !Array.isArray(responseData.prices)) {
this.logger.error(`Invalid API response for ${request.ticker}:`, JSON.stringify(responseData).substring(0, 500));
throw new Error(`Invalid response format for ${request.ticker}: ${JSON.stringify(responseData).substring(0, 200)}`);
}
const prices = [];
const priceData = responseData.prices;
const marketCapData = responseData.market_caps || [];
const volumeData = responseData.total_volumes || [];
// Apply default limit if user didn't specify one (performance optimization)
const effectiveLimit = request.limit || this.config?.defaultIntradayLimit || 1000;
// Warn if fetching large amount of data without explicit limit
if (!request.limit && priceData.length > effectiveLimit) {
this.logger.warn(`Intraday request for ${request.ticker} returned ${priceData.length} records but no limit specified. ` +
`Applying default limit of ${effectiveLimit}. Consider adding a limit to the request for better performance.`);
}
// Apply limit (take most recent data)
const limit = Math.min(effectiveLimit, priceData.length);
const dataToProcess = priceData.slice(-limit);
for (let i = 0; i < dataToProcess.length; i++) {
const actualIndex = priceData.length - limit + i;
const [timestamp, price] = dataToProcess[i];
const date = new Date(timestamp);
const marketCap = marketCapData[actualIndex]?.[1];
const volume = volumeData[actualIndex]?.[1];
const previousClose = i > 0 ? dataToProcess[i - 1][1] : price;
const change = price - previousClose;
const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
prices.push({
ticker: request.ticker.toUpperCase(),
price: price,
currency: 'USD',
change: change,
changePercent: changePercent,
previousClose: previousClose,
timestamp: date,
provider: this.name,
marketState: 'REGULAR',
volume: volume,
dataType: 'intraday',
fetchedAt: new Date(),
companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1)
});
}
return prices;
}, `intraday prices for ${request.ticker}`);
}
/**
* Check if CoinGecko API is available
*/
async isAvailable() {
try {
const url = `${this.baseUrl}/simple/price?ids=bitcoin&vs_currencies=usd`;
await this.rateLimiter.waitForSlot();
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.headers(this.buildHeaders())
.timeout(5000)
.get();
const responseData = await response.json();
return responseData.bitcoin?.usd !== undefined;
}
catch (error) {
this.logger.warn('CoinGecko provider is not available:', error);
return false;
}
}
/**
* Check if a market/network is supported
* CoinGecko supports 240+ networks
*/
supportsMarket(market) {
// CoinGecko has extensive crypto network coverage
const supportedNetworks = [
'CRYPTO', 'BTC', 'ETH', 'BSC', 'POLYGON', 'AVALANCHE',
'SOLANA', 'ARBITRUM', 'OPTIMISM', 'BASE'
];
return supportedNetworks.includes(market.toUpperCase());
}
/**
* Check if a ticker format is supported
* Supports both ticker symbols (BTC) and CoinGecko IDs (bitcoin)
*/
supportsTicker(ticker) {
// Accept alphanumeric with hyphens (for coin IDs like 'wrapped-bitcoin')
return /^[A-Za-z0-9\-]{1,50}$/.test(ticker);
}
/**
* Resolve ticker symbol or CoinGecko ID to canonical CoinGecko ID
* Supports both formats: "BTC" -> "bitcoin", "bitcoin" -> "bitcoin"
*/
async resolveCoinId(tickerOrId) {
const normalized = tickerOrId.toLowerCase();
// Check priority map first (for common cryptos)
if (this.priorityTickerMap.has(normalized)) {
const coinId = this.priorityTickerMap.get(normalized);
this.coinMapCache.set(normalized, coinId);
return coinId;
}
// Check cache
if (this.coinMapCache.has(normalized)) {
return this.coinMapCache.get(normalized);
}
// Check if it's already a valid CoinGecko ID (contains hyphens or is all lowercase with original case)
if (normalized.includes('-') || normalized === tickerOrId) {
// Assume it's a CoinGecko ID, cache it
this.coinMapCache.set(normalized, normalized);
return normalized;
}
// Load coin list if needed
if (!this.coinListLoadedAt ||
Date.now() - this.coinListLoadedAt.getTime() > this.coinListCacheTTL) {
await this.loadCoinList();
}
// Try to find in cache after loading
if (this.coinMapCache.has(normalized)) {
return this.coinMapCache.get(normalized);
}
// Not found - return as-is and let API handle the error
this.logger.warn(`Could not resolve ticker ${tickerOrId} to CoinGecko ID, using as-is`);
return normalized;
}
/**
* Load complete coin list from CoinGecko API
*/
async loadCoinList() {
try {
const url = `${this.baseUrl}/coins/list`;
await this.rateLimiter.waitForSlot();
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.headers(this.buildHeaders())
.timeout(10000)
.get();
const coinList = await response.json();
// Clear cache before rebuilding to prevent memory leak
// Keep only entries that are in priorityTickerMap
const priorityEntries = new Map();
for (const [key, value] of this.priorityTickerMap) {
priorityEntries.set(key, value);
}
this.coinMapCache.clear();
// Restore priority mappings
for (const [key, value] of priorityEntries) {
this.coinMapCache.set(key, value);
}
// Build mapping: symbol -> id
for (const coin of coinList) {
const symbol = coin.symbol.toLowerCase();
const id = coin.id.toLowerCase();
// Don't overwrite priority mappings
if (!this.priorityTickerMap.has(symbol)) {
this.coinMapCache.set(symbol, id);
}
// Always cache the ID mapping (id -> id for when users pass CoinGecko IDs directly)
this.coinMapCache.set(id, id);
}
this.coinListLoadedAt = new Date();
this.logger.info(`Loaded ${coinList.length} coins from CoinGecko (cache: ${this.coinMapCache.size} entries)`);
}
catch (error) {
this.logger.error('Failed to load coin list from CoinGecko:', error);
// Don't throw - we can still work with direct IDs
}
}
/**
* Map CoinGecko simple/price response to IStockPrice
*/
mapToStockPrice(ticker, coinId, data, dataType) {
const price = data.usd;
const change24h = data.usd_24h_change || 0;
// Calculate previous close from 24h change
const changePercent = change24h;
const change = (price * changePercent) / 100;
const previousClose = price - change;
// Parse last updated timestamp
const timestamp = data.last_updated_at
? new Date(data.last_updated_at * 1000)
: new Date();
return {
ticker: ticker.toUpperCase(),
price: price,
currency: 'USD',
change: change,
changePercent: changePercent,
previousClose: previousClose,
timestamp: timestamp,
provider: this.name,
marketState: 'REGULAR', // Crypto markets are 24/7
// Volume and market cap
volume: data.usd_24h_vol,
dataType: dataType,
fetchedAt: new Date(),
// Company identification (use coin name)
companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1),
companyFullName: `${coinId.charAt(0).toUpperCase() + coinId.slice(1)} (${ticker.toUpperCase()})`
};
}
/**
* Build HTTP headers with optional API key
*/
buildHeaders() {
const headers = {
'Accept': 'application/json'
};
if (this.apiKey) {
// Use Demo or Pro API key header
// CoinGecko accepts both x-cg-demo-api-key and x-cg-pro-api-key
headers['x-cg-demo-api-key'] = this.apiKey;
}
return headers;
}
/**
* Check if response indicates a rate limit error (429)
*/
isRateLimitError(responseData) {
return responseData?.status?.error_code === 429;
}
/**
* Wrapper for fetch operations with automatic rate limit retry and exponential backoff
*/
async fetchWithRateLimitRetry(fetchFn, operationName, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const result = await fetchFn();
this.rateLimiter.resetErrors();
return result;
}
catch (error) {
lastError = error;
if (error instanceof RateLimitError) {
const attemptInfo = `${attempt + 1}/${maxRetries}`;
this.logger.warn(`Rate limit hit for ${operationName}, waiting ${error.waitTime}ms before retry ${attemptInfo}`);
if (attempt < maxRetries - 1) {
await plugins.smartdelay.delayFor(error.waitTime);
continue;
}
else {
this.logger.error(`Max retries (${maxRetries}) exceeded for ${operationName} due to rate limiting`);
throw error;
}
}
// Non-rate-limit errors: throw immediately
throw error;
}
}
throw lastError;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXIuY29pbmdlY2tvLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvc3RvY2tzL3Byb3ZpZGVycy9wcm92aWRlci5jb2luZ2Vja28udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQVc1Qzs7R0FFRztBQUNILE1BQU0sY0FBZSxTQUFRLEtBQUs7SUFHdkI7SUFDQTtJQUhULFlBQ0UsT0FBZSxFQUNSLFFBQWdCLEVBQ2hCLFVBQW1CO1FBRTFCLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUhSLGFBQVEsR0FBUixRQUFRLENBQVE7UUFDaEIsZUFBVSxHQUFWLFVBQVUsQ0FBUztRQUcxQixJQUFJLENBQUMsSUFBSSxHQUFHLGdCQUFnQixDQUFDO0lBQy9CLENBQUM7Q0FDRjtBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFdBQVc7SUFDUCxZQUFZLEdBQWEsRUFBRSxDQUFDO0lBQzVCLG9CQUFvQixDQUFTO0lBQzdCLDBCQUEwQixHQUFXLENBQUMsQ0FBQztJQUUvQyxZQUFZLHVCQUErQixFQUFFO1FBQzNDLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxvQkFBb0IsQ0FBQztJQUNuRCxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVc7UUFDdEIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sWUFBWSxHQUFHLEdBQUcsR0FBRyxLQUFLLENBQUM7UUFFakMsc0NBQXNDO1FBQ3RDLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLEdBQUcsWUFBWSxDQUFDLENBQUM7UUFFMUUsK0JBQStCO1FBQy9CLElBQUksSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7WUFDMUQsTUFBTSxhQUFhLEdB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