diff --git a/package.json b/package.json index ccfef37e..851e1cb7 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "docker:build": "pnpm m run docker:build" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^7.16.1", - "@typescript-eslint/parser": "^7.16.1", - "eslint": "^8.57.0", + "eslint": "^9.17.0", + "@eslint/js": "^9.17.0", "eslint-config-prettier": "^9.1.0", + "typescript-eslint": "^8.18.1", "eslint-plugin-prettier": "^5.1.2", "eslint-plugin-vue": "^9.27.0", "prettier": "^3.3.3", diff --git a/packages/api/.eslintrc.json b/packages/api/.eslintrc.json deleted file mode 100644 index 1525f5de..00000000 --- a/packages/api/.eslintrc.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:vue/vue3-essential" - ], - "parserOptions": { - "ecmaVersion": "latest", - "parser": "@typescript-eslint/parser", - "sourceType": "module" - }, - "plugins": ["@typescript-eslint", "vue"], - "rules": { - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", - "no-async-promise-executor": "off" - } -} diff --git a/packages/api/eslint.config.js b/packages/api/eslint.config.js new file mode 100644 index 00000000..78735859 --- /dev/null +++ b/packages/api/eslint.config.js @@ -0,0 +1,26 @@ +import tseslint from 'typescript-eslint' +import pluginJs from '@eslint/js' +import eslintConfigPrettier from 'eslint-config-prettier' + +export default [ + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + eslintConfigPrettier, + { files: ['**/*.ts'] }, + { + ignores: ['node_modules', 'dist', 'src/generated'], + }, + { + languageOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'no-async-promise-executor': 'off', + }, + }, +] diff --git a/packages/api/jest.config.js b/packages/api/jest.config.js index 6b1c2898..5289f349 100644 --- a/packages/api/jest.config.js +++ b/packages/api/jest.config.js @@ -1,12 +1,14 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -require('dotenv').config({ path: './test/.test.env' }) +import dotenv from 'dotenv' +dotenv.config({ path: '.test.env' }) -module.exports = { +import { v4 as uuid } from 'uuid' + +export default { preset: 'ts-jest', testEnvironment: 'node', moduleNameMapper: { // See https://stackoverflow.com/a/73203803 // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451 - uuid: require.resolve('uuid'), + uuid: uuid, }, } diff --git a/packages/api/migrate-mongo-config.js b/packages/api/migrate-mongo-config.js index 4e7f5b53..174ed98d 100644 --- a/packages/api/migrate-mongo-config.js +++ b/packages/api/migrate-mongo-config.js @@ -1,14 +1,13 @@ -require('dotenv/config') +import dotenv from 'dotenv' +dotenv.config() -const config = { +export const config = { mongodb: { - url: process.env.MONGO_URI, - databaseName: process.env.MONGO_INITDB_DATABASE, + url: dotenv.env.MONGO_URI, + databaseName: dotenv.env.MONGO_INITDB_DATABASE, }, migrationsDir: 'migrations', changelogCollectionName: 'changelog', migrationFileExtension: '.js', useFileHash: false, } - -module.exports = config diff --git a/packages/api/migrations/1_rename-boba-networks.js b/packages/api/migrations/1_rename-boba-networks.js index 4340ee0b..5140b108 100644 --- a/packages/api/migrations/1_rename-boba-networks.js +++ b/packages/api/migrations/1_rename-boba-networks.js @@ -1,4 +1,4 @@ -module.exports = { +export default { async up(db) { const resultRequestCollection = db.collection('result_request') await Promise.all( diff --git a/packages/api/migrations/2_rename-conflux-networks.js b/packages/api/migrations/2_rename-conflux-networks.js index 52ddab0b..29eba837 100644 --- a/packages/api/migrations/2_rename-conflux-networks.js +++ b/packages/api/migrations/2_rename-conflux-networks.js @@ -1,4 +1,4 @@ -module.exports = { +export default { async up(db) { const resultRequestCollection = db.collection('result_request') const updateConfluxTethysPromises = ( diff --git a/packages/api/nodemon.json b/packages/api/nodemon.json index 7ef22da1..91043c67 100644 --- a/packages/api/nodemon.json +++ b/packages/api/nodemon.json @@ -2,5 +2,7 @@ "watch": ["src"], "ext": "ts", "ignore": [".git", "node_modules"], - "exec": "./node_modules/.bin/ts-node --require dotenv/config ./src/index.ts" + "execMap": { + "ts": "pnpm node-ts -r dotenv/config ./src/index.ts" + } } diff --git a/packages/api/package.json b/packages/api/package.json index 78361955..6532238c 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,16 +1,17 @@ { "name": "api", "description": "", - "main": "index.js", + "exports": "./index.js", + "type": "module", "scripts": { "build": "tsc -p tsconfig.build.json", "codegen": "graphql-codegen --config ./codegen.yml", - "lint:js": "eslint --ext \".ts\" --ignore-path .gitignore .", - "lint:prettier": "prettier --check --ignore-path .gitignore .", + "lint:js": "eslint -c eslint.config.js --ignore-pattern .gitignore .", + "lint:prettier": "prettier --check .", "lint": "pnpm lint:js && pnpm lint:prettier", "lintfix": "prettier --write --list-different . && pnpm lint:js --fix", "dev": "nodemon", - "start": "node dist/index.js", + "start": "node dist/src/index.js", "test": "jest --runInBand", "prepare": "pnpm run codegen", "docker:build": "docker build . -t witnet/data-feeds-explorer-api --no-cache" @@ -27,10 +28,13 @@ "nodemon": "^3.1.4", "ts-jest": "29.2.2", "ts-node": "10.9.2", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "typescript-eslint": "^8.18.1", + "uuid": "^11.1.0" }, "dependencies": { "@apollo/server": "^4.10.4", + "@witnet/sdk": "1.0.0-beta.0", "axios": "^1.7.2", "dataloader": "2.2.2", "dotenv": "^16.0.3", @@ -41,6 +45,7 @@ "node-cache": "^5.1.2", "web3": "^4.11.0", "web3-eth-contract": "^4.6.0", + "web3-providers-http": "^4.2.0", "web3-utils": "^4.3.1" } } diff --git a/packages/api/src/database.ts b/packages/api/src/database.ts index 0a8d925e..9bb21704 100644 --- a/packages/api/src/database.ts +++ b/packages/api/src/database.ts @@ -1,5 +1,5 @@ import { MongoClient } from 'mongodb' -import { Db } from '../types' +import { Db } from '../types.js' export class MongoManager { db: Db diff --git a/packages/api/src/fetchSvgs.ts b/packages/api/src/fetchSvgs.ts index 13a84c95..8140e87a 100644 --- a/packages/api/src/fetchSvgs.ts +++ b/packages/api/src/fetchSvgs.ts @@ -1,5 +1,5 @@ import axios from 'axios' -import { removeRepeatedElements } from './utils' +import { removeRepeatedElements } from './utils/index.js' export const DEFAULT_SVG = '' diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 146d4875..2e34faa7 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,23 +1,28 @@ -require('dotenv/config') - -import Web3 from 'web3' -import { MongoManager } from './database' -import { FeedRepository } from './repository/Feed' -import { ResultRequestRepository } from './repository/ResultRequest' -import { createServer } from './server' +import dotenv from 'dotenv' +dotenv.config() + +import { Web3 } from 'web3' +import { MongoManager } from './database.js' +import { FeedRepository } from './repository/Feed.js' +import { ResultRequestRepository } from './repository/ResultRequest.js' +import { createServer } from './server.js' import { RouterDataFeedsConfig, Repositories, FeedInfo, NetworksConfig, -} from '../types' -import { Web3Middleware } from './web3Middleware/index' -import { normalizeNetworkConfig } from './utils/index' -import { fetchFeedsLegacy, fetchDataFeedsRouterConfig } from './readDataFeeds' -import { SvgCache } from './svgCache' -import { NetworkRouter } from './web3Middleware/NetworkRouter' -import { Configuration } from './web3Middleware/Configuration' -import { FeedsState } from './repository/feedState' +} from '../types.js' +import { Web3Middleware } from './web3Middleware/index.js' +import { normalizeNetworkConfig } from './utils/index.js' +import { + fetchFeedsLegacy, + fetchDataFeedsRouterConfig, +} from './readDataFeeds.js' +import { SvgCache } from './svgCache.js' +import { NetworkRouter } from './web3Middleware/NetworkRouter.js' +import { Configuration } from './web3Middleware/Configuration.js' +import { FeedsState } from './repository/feedState.js' +import { SourcesRepository } from './repository/Sources.js' class DataFeedsExplorer { routers: Array @@ -79,13 +84,15 @@ class DataFeedsExplorer { this.repositories = { feedRepository: new FeedRepository(this.feedsState), resultRequestRepository: new ResultRequestRepository(db), + sourcesRepository: new SourcesRepository(db), } this.repositories.feedRepository.setLegacyFeeds(legacyFeeds) this.repositories.feedRepository.setV2Feeds(v2Feeds) + this.repositories.feedRepository.setConfiguration(this.configuration) const web3Middleware = new Web3Middleware( this.configuration, - { repositories: this.repositories, Web3: Web3 }, + { repositories: this.repositories, Web3 }, legacyFeeds, ) diff --git a/packages/api/src/loaders/index.ts b/packages/api/src/loaders/index.ts index 56ae796a..2c12253d 100644 --- a/packages/api/src/loaders/index.ts +++ b/packages/api/src/loaders/index.ts @@ -1,10 +1,10 @@ import DataLoader from 'dataloader' -import { SvgCache } from '../svgCache' +import { SvgCache } from '../svgCache.js' import { Loaders, Repositories, ResultRequestDbObjectNormalized, -} from '../../types' +} from '../../types.js' export class LoadersFactory { repositories: Repositories @@ -42,7 +42,10 @@ export class LoadersFactory { feedFullName, ), ), - + sources: this.genericLoader>>( + async (feedFullName: string) => + await this.repositories.sourcesRepository.getSources(feedFullName), + ), requests: this.genericLoader< Promise> >(async (filter: { feedFullName: string; timestamp: number }) => { diff --git a/packages/api/src/readDataFeeds.ts b/packages/api/src/readDataFeeds.ts index f883230e..b0d06935 100644 --- a/packages/api/src/readDataFeeds.ts +++ b/packages/api/src/readDataFeeds.ts @@ -1,8 +1,8 @@ import axios from 'axios' import path from 'path' import fs from 'fs' -import { RouterDataFeedsConfig, FeedInfo, FeedInfoConfig } from '../types' -import { normalizeConfig } from './utils' +import { RouterDataFeedsConfig, FeedInfo, FeedInfoConfig } from '../types.js' +import { normalizeConfig } from './utils/index.js' const CONFIG_URL = process.env.TEST_BRANCH ? `https://raw.github.com/witnet/data-feeds-explorer/${process.env.TEST_BRANCH}/packages/api/src/dataFeedsRouter.json` diff --git a/packages/api/src/repository/Feed.ts b/packages/api/src/repository/Feed.ts index d0d284f0..81724895 100644 --- a/packages/api/src/repository/Feed.ts +++ b/packages/api/src/repository/Feed.ts @@ -1,9 +1,16 @@ -import { FeedsState } from './feedState' -import { PaginatedFeedsObject, FeedInfo, ConfigByFullName } from '../../types' +import { FeedsState } from './feedState.js' +import { + PaginatedFeedsObject, + FeedInfo, + ConfigByFullName, + Network, + FeedsFilters, +} from '../../types.js' +import { Configuration } from '../web3Middleware/Configuration.js' export class FeedRepository { feedsState: FeedsState - // TODO: replace string with Network + dataFeeds: Record>> dataFeedsByNetwork: Record> configByFullName: ConfigByFullName @@ -15,15 +22,43 @@ export class FeedRepository { initialize() { const feeds = this.feedsState.listFeeds() + this.dataFeeds = feeds.reduce( + ( + acc: Record>>, + feedInfo: FeedInfo, + ) => { + let value + const isPairKeyPresent: boolean = !!acc[feedInfo.name] + const isNetworkAndPairKeyPresent: boolean = + isPairKeyPresent && !!acc[feedInfo.name][feedInfo.network] + if (isNetworkAndPairKeyPresent) { + value = [...acc[feedInfo.name][feedInfo.network], feedInfo] + } else { + value = [feedInfo] + } + return { + ...acc, + [feedInfo.name]: { + ...acc[feedInfo.name], + [feedInfo.network]: value, + }, + } + }, + {}, + ) + this.dataFeedsByNetwork = feeds.reduce( - (acc: Record>, feedInfo: FeedInfo) => ({ - ...acc, - [feedInfo.network]: acc[feedInfo.network] - ? [...acc[feedInfo.network], feedInfo] - : [feedInfo], - }), + (acc: Record>, feedInfo: FeedInfo) => { + return { + ...acc, + [feedInfo.network]: acc[feedInfo.network] + ? [...acc[feedInfo.network], feedInfo] + : [feedInfo], + } + }, {}, ) + this.configByFullName = feeds.reduce( (acc, feedInfo) => ({ ...acc, @@ -43,19 +78,67 @@ export class FeedRepository { .find((feed) => feed.feedFullName === feedFullName) } - async getFeedsByNetwork( - // starts in 1 - network: string, - ): Promise { - let feeds: Array + async getFilteredFeeds({ + network, + pair, + mainnet, + }: FeedsFilters): Promise { + let feeds: Array = [] if (network === 'all') { - feeds = Object.values(this.dataFeedsByNetwork).flat() + if (pair) { + feeds = Object.values(this.dataFeeds[pair]).flat() + } else { + feeds = this.feedsState.listFeeds() + } + } else { + if (network && pair) { + feeds = this.dataFeeds[pair][network] + } else if (network) { + feeds = this.dataFeedsByNetwork[network] + } else if (pair) { + feeds = Object.values(this.dataFeeds[pair]).flat() + } + } + return this.getPaginatedFeedsByEnv(feeds, mainnet) + } + + getPaginatedFeedsByEnv(feeds: FeedInfo[], mainnet: boolean | null) { + if (mainnet === null) { + return { + feeds: feeds || [], + total: feeds ? feeds.length : 0, + } + } + if (mainnet) { + return this.getMainnetFeeds(feeds) } else { - feeds = this.dataFeedsByNetwork[network] + return this.getTestnetFeeds(feeds) } + } + + getConfigurationFromNetwork(network: Network) { + return this.feedsState.getConfiguration().getNetworkConfiguration(network) + } + + getTestnetFeeds(feeds: Array): PaginatedFeedsObject { + const filteredFeeds: FeedInfo[] = + feeds?.filter( + (feed) => !this.getConfigurationFromNetwork(feed.network).mainnet, + ) ?? [] return { - feeds: feeds || [], - total: feeds ? feeds.length : 0, + feeds: filteredFeeds, + total: filteredFeeds.length, + } + } + + getMainnetFeeds(feeds: Array): PaginatedFeedsObject { + const filteredFeeds: FeedInfo[] = + feeds?.filter( + (feed) => this.getConfigurationFromNetwork(feed.network).mainnet, + ) ?? [] + return { + feeds: filteredFeeds, + total: filteredFeeds.length, } } @@ -98,4 +181,9 @@ export class FeedRepository { this.feedsState.setV2Feeds(v2Feeds) this.initialize() } + + setConfiguration(configuration: Configuration) { + this.feedsState.setConfiguration(configuration) + this.initialize() + } } diff --git a/packages/api/src/repository/ResultRequest.ts b/packages/api/src/repository/ResultRequest.ts index f75041bd..e796c393 100644 --- a/packages/api/src/repository/ResultRequest.ts +++ b/packages/api/src/repository/ResultRequest.ts @@ -4,8 +4,9 @@ import { Db, Collection, WithoutId, -} from '../../types' -import { containFalsyValues } from './containFalsyValues' + PaginatedRequests, +} from '../../types.js' +import { containFalsyValues } from './containFalsyValues.js' export class ResultRequestRepository { // TODO: find a better way to deal with auto generated ids @@ -47,21 +48,49 @@ export class ResultRequestRepository { .map(this.normalizeId) } + async getFeedRequestsPageByPair( + pair: string, + page: number, + size: number, + ): Promise { + const query = { feedFullName: { $regex: pair } } + return { + requests: ( + await this.collection + .find(query) + .sort({ timestamp: -1 }) + .skip(size * (page - 1)) + .limit(size) + .toArray() + ).map(this.normalizeId), + total: (await this.collection.find(query).toArray()).length, + } + } + async getFeedRequestsPage( feedFullName: string, page: number, size: number, - ): Promise> { - return ( - await this.collection - .find({ - feedFullName, - }) - .sort({ timestamp: -1 }) - .skip(size * (page - 1)) - .limit(size) - .toArray() - ).map(this.normalizeId) + ): Promise { + return { + requests: ( + await this.collection + .find({ + feedFullName, + }) + .sort({ timestamp: -1 }) + .skip(size * (page - 1)) + .limit(size) + .toArray() + ).map(this.normalizeId), + total: ( + await this.collection + .find({ + feedFullName, + }) + .toArray() + ).length, + } } async getLastResult( diff --git a/packages/api/src/repository/Sources.ts b/packages/api/src/repository/Sources.ts new file mode 100644 index 00000000..8825c4d1 --- /dev/null +++ b/packages/api/src/repository/Sources.ts @@ -0,0 +1,47 @@ +import { SourcesDbObject, Db, Collection, WithoutId } from '../../types.js' + +export class SourcesRepository { + collection: Collection> + + constructor(db: Db) { + this.collection = db.collection('sources') + } + + async getSources(feedFullName: string): Promise | null> { + const sources = await this.collection + .findOne({ + feedFullName, + }) + .catch((e) => { + console.log(`Error in getSources: ${feedFullName}`, e) + return null + }) + return sources.sources + } + + async insertSources( + feedFullName: string, + sources: Array, + ): Promise { + await this.collection + .updateOne( + { + feedFullName, + }, + { + $set: { + feedFullName, + sources, + }, + }, + { + upsert: true, + }, + ) + .catch((e) => { + console.log(`Error in insertSources: ${feedFullName}`, e) + return null + }) + return null + } +} diff --git a/packages/api/src/repository/feedState.ts b/packages/api/src/repository/feedState.ts index af3ec8eb..0a9b7f3f 100644 --- a/packages/api/src/repository/feedState.ts +++ b/packages/api/src/repository/feedState.ts @@ -1,8 +1,10 @@ -import { FeedInfo } from '../../types' +import { FeedInfo } from '../../types.js' +import { Configuration } from '../web3Middleware/Configuration.js' export class FeedsState { private legacyFeeds: Array private v2Feeds: Array + private configuration: Configuration constructor() { this.legacyFeeds = [] @@ -17,10 +19,18 @@ export class FeedsState { this.legacyFeeds = legacyFeeds } + setConfiguration(configuration: Configuration) { + this.configuration = configuration + } + getV2Feeds(): Array { return this.v2Feeds } + getConfiguration(): Configuration { + return this.configuration + } + getLegacyFeeds(): Array { return this.legacyFeeds } diff --git a/packages/api/src/resolvers.ts b/packages/api/src/resolvers.ts index 777cd805..c645f08f 100644 --- a/packages/api/src/resolvers.ts +++ b/packages/api/src/resolvers.ts @@ -1,9 +1,13 @@ -import { Context } from '../types' +import { Context } from '../types.js' const resolvers = { Query: { feeds: async (_parent, args, { feedRepository }: Context) => { - return await feedRepository.getFeedsByNetwork(args.network) + return await feedRepository.getFilteredFeeds({ + network: args.network, + pair: args.pair, + mainnet: args.mainnet, + }) }, networks: (_parent, _args, { config }: Context) => { @@ -40,6 +44,9 @@ const resolvers = { lastResult: async (parent, _args, { loaders }: Context) => { return (await loaders.lastResult.load(parent.feedFullName))?.result }, + sources: async (parent, _args, { loaders }: Context) => { + return (await loaders.sources.load(parent.feedFullName)) ?? [] + }, lastResultTimestamp: async (parent, _args, { loaders }: Context) => { return (await loaders.lastResult.load(parent.feedFullName))?.timestamp }, diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 16419511..16bfbce1 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -1,12 +1,12 @@ import { ApolloServer } from '@apollo/server' -import typeDefs from './typeDefs' +import typeDefs from './typeDefs.js' import { DIRECTIVES } from '@graphql-codegen/typescript-mongodb' -import resolvers from './resolvers' -import { Context, Loaders, NetworksConfig, Repositories } from '../types' +import resolvers from './resolvers.js' +import { Context, Loaders, NetworksConfig, Repositories } from '../types.js' import { startStandaloneServer } from '@apollo/server/standalone' -import { LoadersFactory } from './loaders' -import SvgCache from './svgCache' -import { Configuration } from './web3Middleware/Configuration' +import { LoadersFactory } from './loaders/index.js' +import SvgCache from './svgCache.js' +import { Configuration } from './web3Middleware/Configuration.js' export async function createServer( repositories: Repositories, diff --git a/packages/api/src/svgCache.ts b/packages/api/src/svgCache.ts index 95789649..b310d8d7 100644 --- a/packages/api/src/svgCache.ts +++ b/packages/api/src/svgCache.ts @@ -1,5 +1,5 @@ import NodeCache from 'node-cache' -import { DEFAULT_SVG, fetchSvgs } from './fetchSvgs' +import { DEFAULT_SVG, fetchSvgs } from './fetchSvgs.js' export class SvgCache { cache: NodeCache diff --git a/packages/api/src/typeDefs.ts b/packages/api/src/typeDefs.ts index d61a5edb..14f44e4b 100644 --- a/packages/api/src/typeDefs.ts +++ b/packages/api/src/typeDefs.ts @@ -22,6 +22,7 @@ const typeDefs = gql` finality: String! requests(timestamp: Int!): [ResultRequest]! logo: String! + sources: [String!]! } type Total { @@ -50,16 +51,19 @@ const typeDefs = gql` timestamp: String! @column } - # type DataRequest @entity(embedded: true) { - # retrieval: String! @column - # aggregation: String! @column - # tally: String! @column - # } + type PaginatedResultRequest { + requests: [ResultRequest] + total: Int! + } type Query { feed(feedFullName: String!): Feed - feeds(network: String): FeedsPage! - requests(feedFullName: String!, page: Int!, size: Int!): [ResultRequest]! + feeds(network: String, mainnet: Boolean, pair: String): FeedsPage! + requests( + feedFullName: String! + page: Int! + size: Int! + ): PaginatedResultRequest! networks: [NetworksConfig]! } ` diff --git a/packages/api/src/utils/index.ts b/packages/api/src/utils/index.ts index b7ca52c7..40ed2a19 100644 --- a/packages/api/src/utils/index.ts +++ b/packages/api/src/utils/index.ts @@ -8,7 +8,7 @@ import { NetworksConfig, Chain, FeedConfig, -} from '../../types' +} from '../../types.js' // parse network name to fit schema export function parseNetworkName(value) { return value.toLowerCase().split('.').join('-') @@ -160,6 +160,17 @@ export function isZeroHash(hash: string) { ) } +export async function waitWithTimeout( + promise: Promise, + timeout: number, +): Promise { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout), + ) + + return Promise.race([promise, timeoutPromise]) +} + export function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } diff --git a/packages/api/src/web3Middleware/Configuration.ts b/packages/api/src/web3Middleware/Configuration.ts index 6692968e..b7d42b72 100644 --- a/packages/api/src/web3Middleware/Configuration.ts +++ b/packages/api/src/web3Middleware/Configuration.ts @@ -4,10 +4,13 @@ import { Network, NetworksConfig, RouterDataFeedsConfig, -} from '../../types' -import { getNetworksListByChain, sortAlphabeticallyByLabel } from '../utils' -import { NetworkInfo } from './NetworkRouter' -import { getProvider } from './provider' +} from '../../types.js' +import { + getNetworksListByChain, + sortAlphabeticallyByLabel, +} from '../utils/index.js' +import { NetworkInfo } from './NetworkRouter.js' +import { getProvider } from './provider.js' export class Configuration { private configurationFile: RouterDataFeedsConfig diff --git a/packages/api/src/web3Middleware/NetworkRouter.ts b/packages/api/src/web3Middleware/NetworkRouter.ts index faaad7af..fff4bedb 100644 --- a/packages/api/src/web3Middleware/NetworkRouter.ts +++ b/packages/api/src/web3Middleware/NetworkRouter.ts @@ -1,10 +1,11 @@ -import Web3 from 'web3' -import WitnetPriceFeedsABI from './../abi/WitnetPriceFeeds.json' -import { FeedInfo, Network, Repositories } from '../../types' +import { Web3, HttpProvider } from 'web3' +import WitnetPriceFeedsABI from './../abi/WitnetPriceFeeds.json' with { type: 'json' } +import { Witnet } from '@witnet/sdk' +import { FeedInfo, Network, Repositories } from '../../types.js' import { toHex } from 'web3-utils' -import { createFeedFullName } from '../utils' -import { PriceFeed } from './PriceFeed' -import { Configuration } from './Configuration' +import { createFeedFullName, waitWithTimeout } from '../utils/index.js' +import { PriceFeed } from './PriceFeed.js' +import { Configuration } from './Configuration.js' enum ResultStatus { Void = 0, @@ -74,10 +75,12 @@ export class NetworkRouter { if (!provider) { throw new Error(`Missing provider for ${networkName}`) } - const web3: Web3 = new this.Web3(new Web3.providers.HttpProvider(provider)) + const httpProvider = new HttpProvider(provider) + const web3: Web3 = new this.Web3(httpProvider) // TODO: why this type isn't working? this.contract = new web3.eth.Contract(WitnetPriceFeedsABI as any, address) - ;(this.pollingPeriod = pollingPeriod), (this.repositories = repositories) + this.pollingPeriod = pollingPeriod + this.repositories = repositories this.network = key this.configuration = configuration this.provider = provider @@ -92,19 +95,26 @@ export class NetworkRouter { const snapshot = await this.getSnapshot() const insertPromises = snapshot.feeds .filter((feed) => isFeedWithPrice(feed) && feed.timestamp !== '0') - .map((feed: SupportedFeed & LatestPrice) => ({ - feedFullName: createFeedFullName( - this.network, - feed.caption.split('-').reverse()[1], - feed.caption.split('-').reverse()[0], - ), - drTxHash: toHex(feed.tallyHash).slice(2), - // TODO: deprecate mandatory legacy field in database - requestId: '0', - result: feed.value.toString(), - timestamp: feed.timestamp.toString(), - })) + .map( + (feed: SupportedFeed & { sources: Array } & LatestPrice) => ({ + feedFullName: createFeedFullName( + this.network, + feed.caption.split('-').reverse()[1], + feed.caption.split('-').reverse()[0], + ), + drTxHash: toHex(feed.tallyHash).slice(2), + sources: feed.sources, + // TODO: deprecate mandatory legacy field in database + requestId: '0', + result: feed.value.toString(), + timestamp: feed.timestamp.toString(), + }), + ) .map((resultRequest) => { + this.repositories.sourcesRepository.insertSources( + resultRequest.feedFullName, + resultRequest.sources, + ) return this.repositories.resultRequestRepository.insertIfLatest( resultRequest, ) @@ -131,11 +141,19 @@ export class NetworkRouter { const feedIds = supportedFeeds.map((feed) => feed.id) const latestPrices = await this.latestPrices(feedIds) + const radonRequests: Array = await Promise.all( + feedIds.map((id) => this.getRadonRequest({ id })), + ) + const sources = radonRequests.map( + (request) => request?.sources.map((source) => source?.url) ?? [], + ) + return { network: this.network, feeds: supportedFeeds.map((supportedFeed, index) => ({ ...supportedFeed, ...latestPrices[index], + sources: sources[index], })), } } @@ -161,7 +179,7 @@ export class NetworkRouter { // Wrap supportedFeeds contract method async getSupportedFeeds(): Promise> { try { - const supportedFeeds = await Web3.utils.waitWithTimeout( + const supportedFeeds = await waitWithTimeout( this.contract.methods.supportedFeeds().call(), 10000, ) @@ -179,6 +197,26 @@ export class NetworkRouter { } } + // Wrap lookupWitnetBytecode contract method + async getRadonRequest({ + id, + }: { + id: string + }): Promise { + try { + const byteCode = await this.contract.methods + .lookupWitnetBytecode(id) + .call() + return Witnet.Radon.RadonRequest.fromBytecode(byteCode) + } catch (e) { + console.log( + `Error in lookupWitnetBytecode \n\tnetwork: ${this.network}\n\tprovider: ${this.provider}\n\taddress: ${this.address}\n\tError:`, + e, + ) + return null + } + } + // Wrap latestPrices contract method private async latestPrices(ids: Array): Promise> { try { diff --git a/packages/api/src/web3Middleware/PriceFeed.ts b/packages/api/src/web3Middleware/PriceFeed.ts index 454124bc..00bcc076 100644 --- a/packages/api/src/web3Middleware/PriceFeed.ts +++ b/packages/api/src/web3Middleware/PriceFeed.ts @@ -1,8 +1,8 @@ -import { AbiItem, FeedInfo, Network } from '../../types' -import { SupportedFeed } from './NetworkRouter' -import WitnetPriceFeedsABI from './../abi/WitnetPriceFeeds.json' -import { createFeedFullName } from '../utils' -import { Configuration } from './Configuration' +import { AbiItem, FeedInfo, Network } from '../../types.js' +import { SupportedFeed } from './NetworkRouter.js' +import WitnetPriceFeedsABI from './../abi/WitnetPriceFeeds.json' with { type: 'json' } +import { createFeedFullName } from '../utils/index.js' +import { Configuration } from './Configuration.js' export class PriceFeed { feedFullName: string diff --git a/packages/api/src/web3Middleware/index.ts b/packages/api/src/web3Middleware/index.ts index ff1f6b6d..60cbe20a 100644 --- a/packages/api/src/web3Middleware/index.ts +++ b/packages/api/src/web3Middleware/index.ts @@ -1,5 +1,5 @@ import { ObjectId } from 'mongodb' -import Web3 from 'web3' +import { Web3, HttpProvider } from 'web3' import { toHex } from 'web3-utils' import { Contracts, @@ -7,11 +7,11 @@ import { FeedInfo, Repositories, ContractInfo, -} from '../../types' -import { isZeroAddress } from '../utils/index' -import { getProvider } from './provider' -import { NetworkRouter } from './NetworkRouter' -import { Configuration } from './Configuration' +} from '../../types.js' +import { isZeroAddress } from '../utils/index.js' +import { getProvider } from './provider.js' +import { NetworkRouter } from './NetworkRouter.js' +import { Configuration } from './Configuration.js' export class Web3Middleware { public repositories: Repositories @@ -156,7 +156,7 @@ export class Web3Middleware { let web3: Web3 | undefined //FIXME: make timeout work if (provider) { - web3 = new this.Web3(new Web3.providers.HttpProvider(provider)) + web3 = new this.Web3(new HttpProvider(provider)) } //FIXME: use web3 timeout instead of custom setTimeout(() => { diff --git a/packages/api/src/web3Middleware/provider.ts b/packages/api/src/web3Middleware/provider.ts index 624b5c59..840f3f0d 100644 --- a/packages/api/src/web3Middleware/provider.ts +++ b/packages/api/src/web3Middleware/provider.ts @@ -1,4 +1,4 @@ -import { Network } from '../../types' +import { Network } from '../../types.js' export function getProvider(network: Network) { // The provider env variable name should be the network name specified in the config plus '_PROVIDER' ex.MOONBEAM_MOONBASE_PROVIDER diff --git a/packages/api/test/containFalsyValues.spec.ts b/packages/api/test/containFalsyValues.spec.ts index 97c978fb..4fa99482 100644 --- a/packages/api/test/containFalsyValues.spec.ts +++ b/packages/api/test/containFalsyValues.spec.ts @@ -1,4 +1,4 @@ -import { containFalsyValues } from '../src/repository/containFalsyValues' +import { containFalsyValues } from '../src/repository/containFalsyValues.js' describe('containFalsyValues', () => { it('should return FALSE when no falsy value is found on the object', () => { diff --git a/packages/api/test/dotenv-config.js b/packages/api/test/dotenv-config.js index e959b3ee..be0e5c0b 100644 --- a/packages/api/test/dotenv-config.js +++ b/packages/api/test/dotenv-config.js @@ -1,2 +1,2 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -require('dotenv').config({ path: '.test.env' }) +import dotenv from 'dotenv' +dotenv.config({ path: '.test.env' }) diff --git a/packages/api/test/getNetworksConfig.spec.ts b/packages/api/test/getNetworksConfig.spec.ts index 8e2a4346..0da75ba3 100644 --- a/packages/api/test/getNetworksConfig.spec.ts +++ b/packages/api/test/getNetworksConfig.spec.ts @@ -1,6 +1,6 @@ -import { normalizeNetworkConfig } from '../src/utils/index' -import dataFeedsRouterConfig from '../test/web3Middleware/dataFeedsRouter.json' -import { RouterDataFeedsConfig } from '../types' +import { normalizeNetworkConfig } from '../src/utils/index.js' +import dataFeedsRouterConfig from '../test/web3Middleware/dataFeedsRouter.json' assert { type: 'json' } +import { RouterDataFeedsConfig } from '../types.js' describe('validateNetworkConfig', () => { it('check if the structure is correct', async () => { diff --git a/packages/api/test/networkRouter.spec.ts b/packages/api/test/networkRouter.spec.ts index 788e239d..b97b6583 100644 --- a/packages/api/test/networkRouter.spec.ts +++ b/packages/api/test/networkRouter.spec.ts @@ -1,11 +1,11 @@ -import { Network, Repositories, RouterDataFeedsConfig } from '../types' +import { Network, Repositories, RouterDataFeedsConfig } from '../types.js' import { NetworkInfo, NetworkRouter, NetworkSnapshot, -} from '../src/web3Middleware/NetworkRouter' -import { Configuration } from '../src/web3Middleware/Configuration' -import dataFeedsRouter from './web3Middleware/dataFeedsRouter.json' +} from '../src/web3Middleware/NetworkRouter.js' +import { Configuration } from '../src/web3Middleware/Configuration.js' +import dataFeedsRouter from './web3Middleware/dataFeedsRouter.json' assert { type: 'json' } // FIXME: create a proper mock for web3 import web3 from 'web3' diff --git a/packages/api/test/validateFeedsConfig.spec.ts b/packages/api/test/validateFeedsConfig.spec.ts index 6a08b382..639e19f9 100644 --- a/packages/api/test/validateFeedsConfig.spec.ts +++ b/packages/api/test/validateFeedsConfig.spec.ts @@ -1,6 +1,6 @@ -import { normalizeConfig } from '../src/utils/index' -import dataFeedsRouterConfig from '../test/web3Middleware/dataFeedsRouter.json' -import { RouterDataFeedsConfig } from '../types' +import { normalizeConfig } from '../src/utils/index.js' +import dataFeedsRouterConfig from '../test/web3Middleware/dataFeedsRouter.json' assert { type: 'json' } +import { RouterDataFeedsConfig } from '../types.js' describe.skip('validateDataFeedsConfig', () => { it('check if the structure is correct', async () => { diff --git a/packages/api/test/web3Middleware/index.spec.ts b/packages/api/test/web3Middleware/index.spec.ts index 601deab8..16fc83c7 100644 --- a/packages/api/test/web3Middleware/index.spec.ts +++ b/packages/api/test/web3Middleware/index.spec.ts @@ -1,14 +1,14 @@ // FIXME: create a proper mock for web3 -import Web3 from 'web3' -import { FeedInfo, Db, RouterDataFeedsConfig } from '../../types' -import { FeedRepository } from '../../src/repository/Feed' -import { ResultRequestRepository } from '../../src/repository/ResultRequest' -import { Web3Middleware } from '../../src/web3Middleware/index' -import { normalizeConfig } from '../../src/utils' -import dataFeedsRouter from './dataFeedsRouter.json' +import { type Web3 } from 'web3' +import { FeedInfo, Db, RouterDataFeedsConfig } from '../../types.js' +import { FeedRepository } from '../../src/repository/Feed.js' +import { ResultRequestRepository } from '../../src/repository/ResultRequest.js' +import { Web3Middleware } from '../../src/web3Middleware/index.js' +import { normalizeConfig } from '../../src/utils/index.js' +import dataFeedsRouter from './dataFeedsRouter.json' with { type: 'json' } import { ObjectId } from 'mongodb' -import { Configuration } from '../../src/web3Middleware/Configuration' -import { FeedsState } from '../../src/repository/feedState' +import { Configuration } from '../../src/web3Middleware/Configuration.js' +import { FeedsState } from '../../src/repository/feedState.js' const dataFeeds = normalizeConfig(dataFeedsRouter as RouterDataFeedsConfig) @@ -48,7 +48,7 @@ const Web3Mock = jest.fn(() => ({ eth: { Contract: contractMock, }, -})) as unknown as typeof Web3 +})) as unknown as Web3 const originalenv = process.env diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 0041766e..054aa3f5 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { - "lib": ["ES2021", "DOM"], - "skipLibCheck": true, - "target": "es5", - "module": "commonjs", + "lib": ["esnext"], + "target": "ES2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", "esModuleInterop": true, - "moduleResolution": "node", + "skipLibCheck": true, "sourceMap": true, "outDir": "dist", "noUnusedLocals": true, @@ -15,5 +15,5 @@ "downlevelIteration": true, "types": ["jest"] }, - "include": ["src/", "types.ts", "**/*.spec.ts", "**/*.test.ts"] + "include": ["src/", "**/*.ts", "types.ts", "**/*.spec.ts", "**/*.test.ts"] } diff --git a/packages/api/types.ts b/packages/api/types.ts index 317e63f2..8a0b5a30 100644 --- a/packages/api/types.ts +++ b/packages/api/types.ts @@ -1,11 +1,13 @@ +import { ObjectId } from 'mongodb' import { AbiItem } from 'web3-utils' -import { ResultRequestDbObject } from './src/generated/types' -import { FeedRepository } from './src/repository/Feed' -import { ResultRequestRepository } from './src/repository/ResultRequest' +import { ResultRequestDbObject } from './src/generated/types.js' +import { FeedRepository } from './src/repository/Feed.js' +import { ResultRequestRepository } from './src/repository/ResultRequest.js' import DataLoader from 'dataloader' -import { Configuration } from './src/web3Middleware/Configuration' +import { Configuration } from './src/web3Middleware/Configuration.js' +import { SourcesRepository } from './src/repository/Sources.js' -export * from './src/generated/types' +export * from './src/generated/types.js' export { Db, Collection, WithId } from 'mongodb' export { AbiItem } from 'web3-utils' export { Contract } from 'web3-eth-contract' @@ -24,6 +26,7 @@ export type Loaders = { string > logos: DataLoader + sources: DataLoader } export interface Context { @@ -40,6 +43,10 @@ export type ConfigByFullName = { [key: string]: FeedInfo } +export type NetworkConfigByNetwork = { + [key: string]: Configuration +} + export enum Network { ArbitrumOne = 'arbitrum-one', ArbitrumGoerli = 'arbitrum-goerli', @@ -134,6 +141,12 @@ export type NetworksConfig = { export type FeedInfo = FeedInfoGeneric> +export type SourcesDbObject = { + _id: ObjectId + feedFullName: string + urls: Array +} + export type FeedInfoConfig = FeedInfoGeneric export type PaginatedFeedsObject = { @@ -150,9 +163,15 @@ export type ResultRequestDbObjectNormalized = ResultRequestDbObject & { id: string } +export type PaginatedRequests = { + requests: Array + total: number +} + export type Repositories = { feedRepository: FeedRepository resultRequestRepository: ResultRequestRepository + sourcesRepository?: SourcesRepository } export type ContractsState = { @@ -254,6 +273,12 @@ export type LegacyRouterDataFeedsConfig = { chains: Record } +export type FeedsFilters = { + network: string | null + pair: string | null + mainnet: boolean +} + export type FeedInfosWithoutAbis = Array< Omit > diff --git a/packages/ui/.eslintrc.cjs b/packages/ui/.eslintrc.cjs deleted file mode 100644 index f7904474..00000000 --- a/packages/ui/.eslintrc.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - node: true, - }, - parser: 'vue-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - }, - extends: ['@nuxtjs/eslint-config-typescript', 'plugin:prettier/recommended'], - plugins: [], - rules: {}, -} diff --git a/packages/ui/api/index.ts b/packages/ui/api/index.ts index 80cdd11e..c2e4ec87 100644 --- a/packages/ui/api/index.ts +++ b/packages/ui/api/index.ts @@ -4,27 +4,42 @@ import feedQuery from './queries/feed' import feedsQuery from './queries/feeds' import networksQuery from './queries/networks' import feedRequestsQuery from './queries/requests' -import type { Ecosystem, Feed, FeedRequests, Network } from '~/types' +import type { + Ecosystem, + Feed, + FeedRequest, + FeedsRequestInfo, + Network, +} from '~/types' function getApiEndpoint() { return useRuntimeConfig().public.apiBase } -export const getAllFeedsRequests = async ({ network }: { network: string }) => - (await request( +export const getAllFeedsRequests = async ({ + network, + mainnet, + pair, +}: { + network: string | null + mainnet: boolean | null + pair: string | null +}) => { + const result = (await request( getApiEndpoint(), feedsQuery, { network, + mainnet, + pair, }, { accept: 'application/json' }, - )) as { - feeds: FeedRequests[] - total: number - } + )) as FeedsRequestInfo + return result.feeds +} export const getEcosystems = async () => { - const result: { feeds: any } = await request( + const result: { feeds: unknown } = await request( getApiEndpoint(), ecosystemsQuery, { @@ -38,10 +53,12 @@ export const getEcosystems = async () => { } } -export const getNetworks = async () => - (await request(getApiEndpoint(), networksQuery)) as { +export const getNetworks = async () => { + console.log('api endpont', getApiEndpoint()) + return (await request(getApiEndpoint(), networksQuery)) as { networks: Network[] } +} export const getFeedInfo = async ({ timestamp, @@ -68,8 +85,8 @@ export const getFeedRequests = async ({ feedFullName: string page: number size: number -}) => - (await request( +}) => { + const result = (await request( getApiEndpoint(), feedRequestsQuery, { @@ -79,5 +96,10 @@ export const getFeedRequests = async ({ }, { accept: 'application/json' }, )) as { - requests: FeedRequests[] + requests: { + requests: FeedRequest[] + total: number + } } + return result.requests +} diff --git a/packages/ui/api/queries/feed.ts b/packages/ui/api/queries/feed.ts index 8ef9bf3b..d3d9674e 100644 --- a/packages/ui/api/queries/feed.ts +++ b/packages/ui/api/queries/feed.ts @@ -27,6 +27,7 @@ export default gql` blockExplorer color logo + sources } } ` diff --git a/packages/ui/api/queries/feeds.ts b/packages/ui/api/queries/feeds.ts index 9b813c63..62f11eb7 100644 --- a/packages/ui/api/queries/feeds.ts +++ b/packages/ui/api/queries/feeds.ts @@ -1,8 +1,8 @@ import { gql } from 'graphql-request' export default gql` - query feeds($network: String!) { - feeds(network: $network) { + query feeds($network: String, $mainnet: Boolean, $pair: String) { + feeds(network: $network, mainnet: $mainnet, pair: $pair) { feeds { isRouted feedFullName @@ -18,6 +18,7 @@ export default gql` heartbeat finality logo + sources } total } diff --git a/packages/ui/api/queries/requests.ts b/packages/ui/api/queries/requests.ts index f51e53e4..83611767 100644 --- a/packages/ui/api/queries/requests.ts +++ b/packages/ui/api/queries/requests.ts @@ -3,10 +3,14 @@ import { gql } from 'graphql-request' export default gql` query requests($feedFullName: String!, $page: Int!, $size: Int!) { requests(feedFullName: $feedFullName, page: $page, size: $size) { - feedFullName - result - drTxHash - timestamp + requests { + feedFullName + result + drTxHash + requestId + timestamp + } + total } } ` diff --git a/packages/ui/app/router.options.ts b/packages/ui/app/router.options.ts new file mode 100644 index 00000000..82303178 --- /dev/null +++ b/packages/ui/app/router.options.ts @@ -0,0 +1,14 @@ +import type { RouterConfig } from '@nuxt/schema' + +export default { + scrollBehavior(to) { + if (to.hash) { + return { + el: to.hash, + behavior: 'smooth', + } + } else { + return { top: 0 } + } + }, +} diff --git a/packages/ui/assets/styles/main.scss b/packages/ui/assets/styles/main.scss index b641bda4..739a499b 100644 --- a/packages/ui/assets/styles/main.scss +++ b/packages/ui/assets/styles/main.scss @@ -1,78 +1,14 @@ -@import url('https://fonts.googleapis.com/css2?family=Almarai:wght@300;400;700&display=swap'); -@import '~/assets/styles/colors.scss'; +@use '~/assets/styles/colors.scss' as *; -@font-face { - font-family: 'Avenir Next Variable W05 Itali'; - src: - url('~/assets/fonts/5616356/cf7047b5-ced0-4cb1-b66e-688e29567e96.woff2') - format('woff2'), - url('~/assets/fonts/5616356/8a2ab6e1-4ba1-439f-a73b-29918942af3b.woff') - format('woff'); - font-display: swap; -} - -@font-face { - font-family: 'Avenir Next Variable W05835004'; - src: - url('~/assets/fonts/5835004/42801c92-f679-41d6-ab36-6c7ca7c7fa6c.woff2') - format('woff2'), - url('~/assets/fonts/5835004/20522a37-c9e1-4059-b349-66eee8da006b.woff') - format('woff'); - font-display: swap; -} - -/* roboto-regular - latin */ -@font-face { - font-family: 'Roboto Mono'; - font-style: normal; - font-weight: 400; - src: - url('~/assets/fonts/roboto-mono/roboto-mono-v22-latin-regular.woff2') - format('woff2'), - url('~/assets/fonts/roboto-mono/roboto-mono-v22-latin-regular.woff') - format('woff'); - font-display: swap; -} - -/* almarai-300 - arabic */ -@font-face { - font-family: 'Almarai'; - font-style: normal; - font-weight: 300; - src: - url('~/assets/fonts/almarai/almarai-v12-arabic-300.woff2') format('woff2'), - url('~/assets/fonts/almarai/almarai-v12-arabic-300.woff') format('woff'); - font-display: swap; -} - -/* almarai-regular - arabic */ -@font-face { - font-family: 'Almarai'; - font-style: normal; - font-weight: 400; - src: - url('~/assets/fonts/almarai/almarai-v12-arabic-regular.woff2') - format('woff2'), - url('~/assets/fonts/almarai/almarai-v12-arabic-regular.woff') format('woff'); - font-display: swap; -} - -/* almarai-700 - arabic */ -@font-face { - font-family: 'Almarai'; - font-style: normal; - font-weight: 700; - src: - url('~/assets/fonts/almarai/almarai-v12-arabic-700.woff2') format('woff2'), - url('~/assets/fonts/almarai/almarai-v12-arabic-700.woff') format('woff'); - font-display: swap; +html { + height: 100%; + margin: 0; + padding: 0; + color: white; + background-color: #3d3d3d; } :root { - --text-size-title: 24px; - --text-size: 16px; - --text-size-small: 12px; - --text-size-medium: 14px; --bg: #2d2c39; --responsive-menu: #fdfdfd; --text: #fdfdfd; @@ -83,285 +19,21 @@ } .dark-mode { - --bg: #2d2c39; - --responsive-menu: #fdfdfd; - --text: #fdfdfd; - --light-text: #bebebe; - --link: #e54343; - --text-background: #2d2c39d3; - --default-border: 1px solid #fdfdfd; - --white-text: #fdfdfd; --brightness-theme: 0.7; --light-icon: #bebebe5f; - //Pagination - - --pagination-number: #fdfdfd; - --pagination-button-active: #41bea5; - --pagination-number-active: #41bea5; - --pagination-number-active-text: #2d2c39; - --app-background-color: #2d2c39; - --pagination-button: #fdfdfd; - - //Tooltip - - --tooltip-background: #ffffff10; - --tooltip-bg: #2d2c39; - - // Select - - --selected-background: #37414c; - --selected-text: #fdfdfd; - --selected-icon: #62b1b058; - --selected-border: 2px solid #62b1b015; - --selected-options-background: #37414c; - --selected-options-text: #fdfdfd; - --selected-options-border: 2px solid #62b1b015; - --selected-options-shadow: 1px 1px 10 2px #62b1b029; - --selected-options-highlight: #62b1b015; - // Network options - --network-color: #bebebe; - --network-border: 1px solid transparent; --network-background: #bebebe0d; - --network-selected-color: #fdfdfd; - --network-selected-border: 1px solid #00d4aa22; - --network-selected-background: #00d4aa16; - - // FeedCard - - --card-border: 1px solid #00d4aa22; - --delay-status-border: 1px solid #dfc44b23; - --error-status-border: 1px solid #df4b4b23; - --card-background: #00d4aa08; - --card-box-selected-shadow: 0 0 11px #cacaca18; - //Fieledset - - --fieldset-title: #fdfdfd; - --fieldset-background: #ffffff10; - - //Logo - - --logo-main: #fdfdfd; - --logo-dot: #41bea5; - - //NavBar - - --nav-bar-background: #2d2c39; - --nav-bar-slash-color: #5fbfae; - - // links - - --text-hover: #41bea5; - --text-medium-emphasis: #fdfdfdc0; - - // links - - --transaction-blur-background: #ffffff08; - - //ThemeSwitch - - --icon-color: #fdfdfd; - --icon-background: #37414c94; - - //Transaction - - --witnet-transaction: #41bea5; - - //ContractAddress - - --contract-address: #41bea5; - - //Switcher - - --switcher-item-background: #41bea5; - --switcher-item-color: white; - - //Button - - --btn-primary-color: #ededed; - --btn-primary-background-color: #00d4aa16; - --btn-primary-background-color-active: #00d4aa36; - --btn-primary-border-color: #00d4aa16; - --btn-secondary-color: #4ab6a1; - --btn-secondary-background-color: #4ab6a1; - --btn-secondary-border-color: #4ab6a1; - - //footer - --footer-bg: #4ab6a02c; - --footer-border: 8px solid #00d4aa16; - - //language selector - --language-selector-bg: #2d2c39; - - //sidebar - --tab-gradient: linear-gradient(90deg, #2d2c39d3 95%, #4ab6a02c); - --tab-background: #2d2c39; - --tab-gradient-selected: linear-gradient( - 90deg, - rgba(45, 44, 57, 0) 0%, - rgba(74, 182, 160, 0.05) 20%, - rgba(74, 182, 160, 0.1) 24%, - rgba(74, 182, 160, 0.15) 30%, - rgba(74, 182, 160, 0.2) 35% - ); - - //breadcrumbs - --selected-option: #4ab6a1; - - //Placeholder - --placeholder-bg-color: #bebebe0d; - --placeholder-gradient: linear-gradient( - to right, - rgba(84, 84, 85, 0) 0%, - rgba(84, 84, 85, 0.07) 20%, - rgba(84, 84, 85, 0.12) 24%, - rgba(84, 84, 85, 0.17) 30%, - rgba(84, 84, 85, 0.22) 35% - ); } .light-mode { - --bg: #fdfdfd; - --responsive-menu: #2d2c39; - --text: #112338; - --light-text: #666; - --link: #e54343; - --text-background: #ffffffd3; - --default-border: 1px solid #2d2c39; - --white-text: #fdfdfd; --brightness-theme: 0.1; --light-icon: #94939374; - //Pagination - - --pagination-number: #2d2c39; - --pagination-button-active: #41bea5; - --pagination-number-active: #41bea5; - --pagination-number-active-text: #fdfdfd; - --pagination-button: #2d2c39; - - //Tooltip - - --tooltip-background: #2d2c3910; - --tooltip-bg: #f6f6f7; - - // Select - - --selected-background: linear-gradient(90deg, #3f92a5 0%, #41bea5 57%); - --selected-text: #fdfdfd; - --selected-icon: #fdfdfd; - --selected-border: none; - --selected-options-background: #f6f6f6; - --selected-options-text: #1f1f24; - --selected-options-border: 1px solid #fdfdfd; - --selected-options-shadow: 1px 1px 10 2px #1f1f24; - --selected-options-highlight: #1f1f2414; - // Network options - --network-color: #999; - --network-border: 1px solid transparent; --network-background: #bbb3; - --network-selected-color: #41bea5; - --network-selected-border: 1px solid #41bea5; - --network-selected-background: #fdfdfd; - - // FeedCard - - --card-border: 1px solid #41bea5; - --delay-status-border: 1px solid #dfc44b; - --error-status-border: 1px solid #df4b4b; - --card-background: transparent; - --name-color: #2d2c39; - --value-color: #2d2c39; - --card-box-shadow: #98979716 0 4px 4px; - --card-box-selected-shadow: 0 0 11px rgba(152, 151, 151, 0.271); - - //Fieledset - - --fieldset-title: #2d2c39; - --fieldset-background: #11233808; - - //Logo - --logo-dot: #41bea5; - --logo-main: #112338; - - //NavBar - - --nav-bar-background: #fdfdfd; - --nav-bar-slash-color: #5fbfae; - - // links - - --text-hover: #41bea5; - --text-medium-emphasis: #112338a9; - - // links - - --transaction-blur-background: #11233805; - - //ThemeSwitch - - --icon-color: #2d2c39; - --icon-background: #ffffffd3; - - //Transaction - - --witnet-transaction: #41bea5; - - //ContractAddress - - --contract-address: #41bea5; - - //switcher - - --switcher-item-background: #41bea5; - --switcher-item-color: #2d2c39; - - //Button - - --btn-primary-color: #fdfdfd; - --btn-primary-background-color: #41bea5; - --btn-primary-background-color-active: #41bea5dd; - --btn-primary-border-color: #41bea5; - --btn-secondary-color: #41bea5; - --btn-secondary-background-color: #41bea5; - --btn-secondary-border-color: hsl(168, 43%, 50%); - - //footer - --footer-bg: #112338; - --footer-border: 8px solid #41bea5; - - //language selector - --language-selector-bg: #112338; - - //sidebar - --tab-gradient: linear-gradient(90deg, #fdfdfd 95%, #cbf5ed); - --tab-background: #fdfdfd; - --tab-gradient-selected: linear-gradient( - 90deg, - #fdfdfd 0%, - rgba(74, 182, 160, 0.8) 20%, - rgba(74, 182, 160, 0.9) 24%, - rgba(74, 182, 160, 0.97) 30%, - rgba(74, 182, 161, 1) 35% - ); - - //breadcrumbs - --selected-option: #41bea5; - - //Placeholder - --placeholder-bg-color: rgba(128, 128, 128, 0.051); - --placeholder-gradient: linear-gradient( - to right, - rgba(128, 128, 128, 0) 0%, - rgba(128, 128, 128, 0.001) 20%, - rgba(128, 128, 128, 0.005) 24%, - rgba(128, 128, 128, 0.008) 30%, - rgba(128, 128, 128, 0.03) 35% - ); } body { @@ -369,48 +41,10 @@ body { color: rgba(0, 0, 0, 0.8); } -html { - height: 100%; - margin: 0; - padding: 0; - color: white; - background-color: black; -} - -body { - font-family: 'Almarai'; -} - -h1 { - font-weight: bold; - font-family: 'Avenir Next Variable W05835004', Arial, Helvetica, sans-serif; -} -p { - font-family: 'Almarai', sans-serif; - line-height: 1.5; - &.title { - font-family: 'Avenir Next Variable W05835004', Arial, Helvetica, sans-serif; - } -} -.btn { - font-family: 'Almarai', sans-serif; -} -.title { - font-family: 'Avenir Next Variable W05835004', Arial, Helvetica, sans-serif; -} .bold { font-weight: bold; } -.light-text { - color: var(--light-text); -} -.text { - color: var(--text); -} -a { - text-decoration: none; - font-size: 1rem; -} + .tv-lightweight-charts { a#tv-attr-logo { display: none; diff --git a/packages/ui/assets/styles/tailwind.css b/packages/ui/assets/styles/tailwind.css index b5c61c95..f950f327 100644 --- a/packages/ui/assets/styles/tailwind.css +++ b/packages/ui/assets/styles/tailwind.css @@ -1,3 +1,80 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer components { + html { + @apply scroll-smooth; + } + /* Titles */ + h1 { + @apply font-title text-4xl font-semibold sm:text-3xl text-black-950 dark:text-white-100; + line-height: 1.1; + } + .title { + @apply font-title text-2xl font-semibold sm:text-xl text-black-950 dark:text-white-100; + line-height: 1.1; + } + .title-small { + @apply font-title text-lg font-semibold sm:text-sm text-black-950 dark:text-white-100; + line-height: 1.1; + } + .text { + @apply text-base text-black-800 dark:text-black-300; + line-height: 1.5; + } + .text-highlighted { + @apply text-base font-bold text-black-900 dark:text-white-100; + line-height: 1.5; + } + .text-bold { + @apply text-base font-bold text-black-800 dark:text-black-300; + line-height: 1.5; + } + .text-small { + @apply text-sm text-black-800 dark:text-black-300; + line-height: 1.5; + } + .text-small-bold { + @apply text-sm font-bold text-black-800 dark:text-black-300; + line-height: 1.5; + } + .text-2-sm { + @apply text-xs text-black-800 dark:text-black-300; + line-height: 1.5; + } + .text-2-sm-bold { + @apply text-xs font-bold text-black-800 dark:text-black-300; + line-height: 1.5; + } + .bg { + @apply bg-white-50 dark:bg-black-950; + } + .border { + @apply border-solid border-black-950 dark:border-white-50; + } + .feed-card { + @apply border-2 border-black-950 dark:border-wit-blue-500 bg-white-50 dark:bg-wit-blue-500 dark:bg-opacity-5; + } + .operational { + @apply border-2 border-black-950 dark:border-wit-blue-500; + } + .delay { + @apply border-2 border-orange-50 dark:border-orange-950; + } + .error { + @apply border-2 border-red-50 dark:border-red-950; + } + .fieldset { + @apply bg-white-100 dark:bg-black-800; + } + .logo { + @apply fill-black-950 dark:fill-white-50; + } + .link-text { + @apply dark:text-wit-blue-500 text-wit-blue-600; + } + .placeholder { + @apply bg-black-500/5 dark:bg-black-200/5 bg-gradient-to-r from-black-500/5 to-black-600/10 dark:from-black-200/5 dark:to-black-300/10 dark:bg-opacity-5; + } +} diff --git a/packages/ui/assets/svg/all.svg b/packages/ui/assets/svg/all.svg new file mode 100644 index 00000000..1ca57bd2 --- /dev/null +++ b/packages/ui/assets/svg/all.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/assets/svg/empty.svg b/packages/ui/assets/svg/empty.svg new file mode 100644 index 00000000..c787afce --- /dev/null +++ b/packages/ui/assets/svg/empty.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/ui/assets/svg/witnet-logo.svg b/packages/ui/assets/svg/witnet-logo.svg index 7d06cc4d..3783f055 100644 --- a/packages/ui/assets/svg/witnet-logo.svg +++ b/packages/ui/assets/svg/witnet-logo.svg @@ -1 +1,6 @@ - \ No newline at end of file + + + + + + diff --git a/packages/ui/assets/svg/worldchain.svg b/packages/ui/assets/svg/worldchain.svg new file mode 100644 index 00000000..4846ed20 --- /dev/null +++ b/packages/ui/assets/svg/worldchain.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/components/AnimatedPlaceholder.vue b/packages/ui/components/AnimatedPlaceholder.vue index c4e3cedd..bfb1637e 100644 --- a/packages/ui/components/AnimatedPlaceholder.vue +++ b/packages/ui/components/AnimatedPlaceholder.vue @@ -27,13 +27,10 @@ defineProps({ } } .placeholder { - background-color: var(--placeholder-bg-color); height: v-bind(height); width: v-bind(width); border-radius: v-bind(borderRadius); - background-image: var(--placeholder-gradient); background-size: 200% 200%; - box-shadow: var(--card-box-shadow); animation: bgAnimate 1.2s linear infinite; } diff --git a/packages/ui/components/CopyTooltip.vue b/packages/ui/components/CopyTooltip.vue index 07818e62..4a91fc5a 100644 --- a/packages/ui/components/CopyTooltip.vue +++ b/packages/ui/components/CopyTooltip.vue @@ -12,21 +12,21 @@
{{ $t('copied') }}
{{ $t('copy') }} @@ -103,11 +103,8 @@ export default { .copy-tooltip { position: absolute; display: inline-block; - font-size: var(--text-size); padding: 8px; border-radius: 4px; - background-color: var(--bg); - color: var(--text); } .container { display: flex; @@ -115,7 +112,6 @@ export default { .copy { padding: 16px; margin: -16px; - font-size: var(--text-size-small); cursor: pointer; } diff --git a/packages/ui/components/CustomButton.vue b/packages/ui/components/CustomButton.vue deleted file mode 100644 index a62b9cdf..00000000 --- a/packages/ui/components/CustomButton.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - diff --git a/packages/ui/components/DataCount.vue b/packages/ui/components/DataCount.vue new file mode 100644 index 00000000..af7ab181 --- /dev/null +++ b/packages/ui/components/DataCount.vue @@ -0,0 +1,14 @@ + + diff --git a/packages/ui/components/DataFeedDescription.vue b/packages/ui/components/DataFeedDescription.vue index e88a32e6..426f6bb9 100644 --- a/packages/ui/components/DataFeedDescription.vue +++ b/packages/ui/components/DataFeedDescription.vue @@ -4,21 +4,37 @@ :keypath="description.i18nPath" scope="global" tag="p" - class="feed-description" + class="feed-description text" > +

+ {{ $t('data_feed_details.feed_sources', [sources.length]) }} + {{ formatedLink(link.toString()) + }}, . +

- diff --git a/packages/ui/components/DataFeedDetails.vue b/packages/ui/components/DataFeedDetails.vue index 83d60c43..cda764da 100644 --- a/packages/ui/components/DataFeedDetails.vue +++ b/packages/ui/components/DataFeedDetails.vue @@ -1,6 +1,6 @@ - - diff --git a/packages/ui/components/DataFeeds.vue b/packages/ui/components/DataFeeds.vue index 1758362e..94e4bd96 100644 --- a/packages/ui/components/DataFeeds.vue +++ b/packages/ui/components/DataFeeds.vue @@ -1,79 +1,83 @@ diff --git a/packages/ui/components/EmptyState.vue b/packages/ui/components/EmptyState.vue new file mode 100644 index 00000000..211e0aa2 --- /dev/null +++ b/packages/ui/components/EmptyState.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/ui/components/ExternalLink.vue b/packages/ui/components/ExternalLink.vue new file mode 100644 index 00000000..c376a57b --- /dev/null +++ b/packages/ui/components/ExternalLink.vue @@ -0,0 +1,24 @@ + + diff --git a/packages/ui/components/FeedCard/FeedIcon.vue b/packages/ui/components/FeedCard/FeedIcon.vue new file mode 100644 index 00000000..bfa2fad5 --- /dev/null +++ b/packages/ui/components/FeedCard/FeedIcon.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/packages/ui/components/FeedCard/WFeedCard.vue b/packages/ui/components/FeedCard/WFeedCard.vue new file mode 100644 index 00000000..3ac5aa08 --- /dev/null +++ b/packages/ui/components/FeedCard/WFeedCard.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/packages/ui/components/FeedFilters.vue b/packages/ui/components/FeedFilters.vue new file mode 100644 index 00000000..bb55f11f --- /dev/null +++ b/packages/ui/components/FeedFilters.vue @@ -0,0 +1,39 @@ + + + diff --git a/packages/ui/components/FeedSkeleton.vue b/packages/ui/components/FeedSkeleton.vue deleted file mode 100644 index 3472143e..00000000 --- a/packages/ui/components/FeedSkeleton.vue +++ /dev/null @@ -1,35 +0,0 @@ - - diff --git a/packages/ui/components/FeedsSection.vue b/packages/ui/components/FeedsSection.vue new file mode 100644 index 00000000..0a85ba70 --- /dev/null +++ b/packages/ui/components/FeedsSection.vue @@ -0,0 +1,40 @@ + + + diff --git a/packages/ui/components/FieldsetCard.vue b/packages/ui/components/FieldsetCard.vue index b83f00ad..270fe1d0 100644 --- a/packages/ui/components/FieldsetCard.vue +++ b/packages/ui/components/FieldsetCard.vue @@ -1,8 +1,10 @@ @@ -20,21 +22,15 @@ export default { diff --git a/packages/ui/components/HeroSection.vue b/packages/ui/components/HeroSection.vue new file mode 100644 index 00000000..45753ab4 --- /dev/null +++ b/packages/ui/components/HeroSection.vue @@ -0,0 +1,69 @@ + + + diff --git a/packages/ui/components/InfoTooltip.vue b/packages/ui/components/InfoTooltip.vue index 683b752e..b3bb09bb 100644 --- a/packages/ui/components/InfoTooltip.vue +++ b/packages/ui/components/InfoTooltip.vue @@ -1,16 +1,16 @@