Skip to content

Commit

Permalink
Merge pull request #18 from near-daos/feature/pre_release
Browse files Browse the repository at this point in the history
Releasing coin price history API endpoint
  • Loading branch information
okalenyk authored Jan 19, 2022
2 parents d6bb710 + edc14d3 commit f17d4be
Show file tree
Hide file tree
Showing 55 changed files with 651 additions and 186 deletions.
6 changes: 4 additions & 2 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ DATABASE_HOST=localhost
DATABASE_PORT=5437
DATABASE_RUN_MIGRATIONS=true
ORM_MIGRATIONS_DIR=src/migration
DATABASE_MIGRATIONS_LIST=ContractsMigration,ReceiptActionArgsMigration,ContractIdRelationMigration,TransactionProposalVoteMigration,ReceiptActionBlockTimestampMigration
DATABASE_MIGRATIONS_LIST=ReceiptActionArgsMigration,ContractIdRelationMigration,TransactionProposalVoteMigration,ReceiptActionBlockTimestampMigration,NearPriceHistoryMigration

REDIS_CACHE_URL=redis://@localhost:6379/0
REDIS_EVENT_BUS_URL=redis://@localhost:6379/1
Expand All @@ -18,5 +18,7 @@ AGGREGATOR_POLLING_SCHEDULE="0 * * * *"

LOG_LEVELS=log,warn,error

SMART_CONTRACTS=astro
CONTRACT_ENV=mainnet

SODAKI_API_BASE_URL=https://sodaki.com/api
COINGECKO_API_BASE_URL=https://api.coingecko.com/api/v3
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }]
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ data:
PORT: "3000"
NEST_APP_TYPE: aggregator
AGGREGATOR_POLLING_SCHEDULE: "{{ .Values.environment.aggregator_polling_schedule }}"
SMART_CONTRACTS: astro
LOG_LEVELS: "log,warn,error"
6 changes: 6 additions & 0 deletions apps/aggregator/src/aggregator.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {
import { AggregatorService } from './aggregator.service';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ReceiptModule } from '@dao-stats/receipt';
import { CoinPriceHistoryModule } from '@dao-stats/common/coin-price-history.module';
import { ContractModule } from 'apps/api/src/contract/contract.module';
import { ExchangeModule } from 'libs/exchange/src/exchange.module';

@Module({
imports: [
Expand All @@ -40,6 +43,9 @@ import { ReceiptModule } from '@dao-stats/receipt';
DaoStatsModule,
DaoStatsHistoryModule,
HttpCacheModule,
CoinPriceHistoryModule,
ContractModule,
ExchangeModule,
],
providers: [
AggregatorService,
Expand Down
48 changes: 46 additions & 2 deletions apps/aggregator/src/aggregator.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import {
import { TransactionService } from '@dao-stats/transaction';
import { CacheService } from '@dao-stats/cache';
import { ReceiptActionService } from '@dao-stats/receipt';
import { ContractService } from 'apps/api/src/contract/contract.service';
import { CurrencyType } from '@dao-stats/common/types/currency-type';
import { CoinGeckoService, SodakiService } from '@dao-stats/exchange';
import { CoinPriceHistoryService } from '@dao-stats/common/coin-price-history.service';

@Injectable()
export class AggregatorService {
Expand All @@ -31,6 +35,10 @@ export class AggregatorService {
private readonly daoService: DaoService,
private readonly daoStatsService: DaoStatsService,
private readonly daoStatsHistoryService: DaoStatsHistoryService,
private readonly contractService: ContractService,
private readonly sodakiService: SodakiService,
private readonly coinGeckoService: CoinGeckoService,
private readonly coinPriceHistoryService: CoinPriceHistoryService,
) {
const { pollingSchedule } = this.configService.get('aggregator');

Expand All @@ -42,9 +50,11 @@ export class AggregatorService {
}

public async scheduleAggregation(from?: bigint, to?: bigint): Promise<void> {
const { smartContracts } = this.configService.get('aggregator');
const contracts = await this.contractService.find();

for (const contract of contracts) {
const { contractId, coin } = contract;

for (const contractId of smartContracts) {
this.logger.log(`Processing contract: ${contractId}...`);

const { AggregationModule, AggregationService } = await import(
Expand Down Expand Up @@ -130,6 +140,40 @@ export class AggregatorService {
await this.daoStatsHistoryService.createOrUpdate(metric);
}

this.logger.log(
`Retrieving current market price for contract: ${contractId}`,
);

for (const currency in CurrencyType) {
let price: number;

try {
price = await this.sodakiService.getCoinSpotPrice(
coin,
CurrencyType[currency],
);
} catch (e) {
this.logger.warn(e);

this.logger.log(
'Unable to get market price from Sodaki. Switching to CoinGecko for another try.',
);

price = await this.coinGeckoService.getCoinPrice(
coin,
CurrencyType[currency],
);
}

this.coinPriceHistoryService.createOrUpdate({
coin,
currency: CurrencyType[currency],
price,
});

this.logger.log(`Stored market price for ${coin}: ${price}${currency}`);
}

this.logger.log(`Finished processing contract: ${contractId}`);
}

Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { FlowModule } from './flow/flow.module';
import { TvlModule } from './tvl/tvl.module';
import { ApiDaoModule } from './dao/dao.module';
import { TokensModule } from './tokens/tokens.module';
import { MarketModule } from './market/market.module';

@Module({
imports: [
Expand Down Expand Up @@ -69,6 +70,7 @@ import { TokensModule } from './tokens/tokens.module';
FlowModule,
TvlModule,
TokensModule,
MarketModule,
],
providers: [
{
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default class Api {
});
app.enableCors();
app.setGlobalPrefix('/api/v1/:contractId', {
exclude: ['/api/v1/contracts'],
exclude: ['/api/v1/contracts', '/api/v1/market/([^\\s]+)'],
});

if (process.env.NODE_ENV === 'development') {
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/contract/dto/contract.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CoinType } from '@dao-stats/common/types/coin-type';
import { ApiProperty } from '@nestjs/swagger';

export class ContractResponse {
Expand All @@ -12,4 +13,7 @@ export class ContractResponse {

@ApiProperty()
conversionFactor: number;

@ApiProperty()
coin: CoinType;
}
4 changes: 3 additions & 1 deletion apps/api/src/flow/flow.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export class FlowController {
description: 'Bad Request Response based on the query params set',
})
@Get('/')
async totals(@Param(ContractContextPipe) context: ContractContext): Promise<FlowTotalResponse> {
async totals(
@Param(ContractContextPipe) context: ContractContext,
): Promise<FlowTotalResponse> {
return this.flowService.totals(context);
}

Expand Down
32 changes: 14 additions & 18 deletions apps/api/src/general/general.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import {
LeaderboardMetricResponse,
MetricQuery,
MetricResponse,
MetricType,
} from '@dao-stats/common';
import { TransactionService } from '@dao-stats/transaction';
import { GeneralTotalResponse } from './dto';
import { MetricService } from '../common/metric.service';
import { getDailyIntervals, getGrowth, patchMetricDays } from '../utils';
import { getDailyIntervals, getGrowth } from '../utils';

@Injectable()
export class GeneralService {
Expand All @@ -29,15 +28,15 @@ export class GeneralService {
async totals(
context: DaoContractContext | ContractContext,
): Promise<GeneralTotalResponse> {
const dayAgo = moment().subtract(1, 'days');
const weekAgo = moment().subtract(1, 'week');

const [dao, groups, averageGroups, activity, dayAgoActivity] =
const [dao, groups, averageGroups, activity, weekAgoActivity] =
await Promise.all([
this.metricService.total(context, DaoStatsMetric.DaoCount),
this.metricService.total(context, DaoStatsMetric.GroupsCount),
this.metricService.total(context, DaoStatsMetric.GroupsCount, true),
this.transactionService.getContractActivityCount(context, {
to: dayAgo.valueOf(),
to: weekAgo.valueOf(),
}),
this.transactionService.getContractActivityCount(context),
]);
Expand All @@ -46,7 +45,7 @@ export class GeneralService {
dao,
activity: {
count: activity.count,
growth: getGrowth(activity.count, dayAgoActivity.count),
growth: getGrowth(activity.count, weekAgoActivity.count),
},
groups,
averageGroups,
Expand All @@ -68,20 +67,17 @@ export class GeneralService {
context: ContractContext,
metricQuery: MetricQuery,
): Promise<MetricResponse> {
const metrics = await this.transactionService.getContractActivityCountDaily(
context,
metricQuery,
);
const metrics =
await this.transactionService.getContractActivityCountWeekly(
context,
metricQuery,
);

return {
metrics: patchMetricDays(
metricQuery,
metrics.map(({ day, count }) => ({
timestamp: moment(day).valueOf(),
count,
})),
MetricType.Daily,
),
metrics: metrics.map(({ day, count }) => ({
timestamp: moment(day).valueOf(),
count,
})),
};
}

Expand Down
17 changes: 17 additions & 0 deletions apps/api/src/market/dto/coin-price-history.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';

import { CoinType, CurrencyType } from '@dao-stats/common/types';

export class CoinPriceHistoryResponse {
@ApiProperty()
date: number;

@ApiProperty()
coin: CoinType;

@ApiProperty()
currency: CurrencyType;

@ApiProperty()
price: number;
}
1 change: 1 addition & 0 deletions apps/api/src/market/dto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './coin-price-history.dto';
42 changes: 42 additions & 0 deletions apps/api/src/market/market.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiParam,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';

import { CoinType } from '@dao-stats/common/types';
import { CoinPriceQuery } from '@dao-stats/common/dto';

import { MarketService } from './market.service';
import { NoContractContext } from '../decorators';
import { MetricQueryPipe } from '../pipes';
import { CoinPriceHistoryResponse } from './dto';

@ApiTags('Market')
@Controller('/api/v1/market')
export class MarketController {
constructor(private readonly marketService: MarketService) {}

@ApiResponse({
status: 200,
type: [CoinPriceHistoryResponse],
})
@ApiBadRequestResponse({
description: 'Bad Request Response based on the query params set',
})
@ApiParam({
name: 'coin',
description: `Coin Type: e.g ${CoinType.Near}`,
enum: Object.values(CoinType),
})
@NoContractContext()
@Get('/:coin/price')
async coinPrice(
@Param('coin') coin: CoinType = CoinType.Near,
@Query(MetricQueryPipe) query: CoinPriceQuery,
): Promise<CoinPriceHistoryResponse[]> {
return this.marketService.getCoinPriceHistory(coin, query);
}
}
24 changes: 24 additions & 0 deletions apps/api/src/market/market.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheModule, Module } from '@nestjs/common';

import { CacheConfigService } from '@dao-stats/config/cache';
import { Contract, CoinPriceHistoryModule } from '@dao-stats/common';
import { TransactionModule } from '@dao-stats/transaction';

import { MarketService } from './market.service';
import { MarketController } from './market.controller';

@Module({
imports: [
CacheModule.registerAsync({
useClass: CacheConfigService,
}),
TypeOrmModule.forFeature([Contract]),
CoinPriceHistoryModule,
TransactionModule,
],
providers: [MarketService],
controllers: [MarketController],
exports: [MarketService],
})
export class MarketModule {}
34 changes: 34 additions & 0 deletions apps/api/src/market/market.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Injectable } from '@nestjs/common';
import moment from 'moment';

import { CoinPriceHistoryService } from '@dao-stats/common';
import { CoinPriceQuery } from '@dao-stats/common/dto';
import { CoinType } from '@dao-stats/common/types';

import { CoinPriceHistoryResponse } from './dto';

@Injectable()
export class MarketService {
constructor(
private readonly coinPriceHistoryService: CoinPriceHistoryService,
) {}

async getCoinPriceHistory(
coin: CoinType,
query: CoinPriceQuery,
): Promise<CoinPriceHistoryResponse[]> {
const { currency, from, to } = query;

const history = await this.coinPriceHistoryService.findByDateRange(
coin,
currency,
from,
to,
);

return history.map((price) => ({
...price,
date: moment(price.date).valueOf(),
}));
}
}
1 change: 1 addition & 0 deletions apps/api/src/pipes/metric-query.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class MetricQueryPipe implements PipeTransform {
}

return {
...query,
from: from.valueOf(),
to: to.valueOf(),
};
Expand Down
3 changes: 0 additions & 3 deletions apps/api/src/tvl/dto/tvl-total.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ export class TvlTotalResponse {
@ApiProperty()
tvl: TotalMetric;

@ApiProperty()
avgTvl: TotalMetric;

@ApiProperty()
bountiesAndGrantsVl: TotalMetric;

Expand Down
Loading

0 comments on commit f17d4be

Please sign in to comment.