diff --git a/.env.example b/.env.example index 954bd21..9ac2c04 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,16 @@ NODE_ENV=development NODE_PORT=3001 -NODE_HOST=localhost \ No newline at end of file +NODE_HOST=localhost + +COINGECKO_BASE_URL=https://api.coingecko.com/api/v3/ +COINGECKO_API_KEY= +COINGECKO_API_KEY_QUERY_NAME=x_cg_demo_api_key + +ALCHEMY_URL= +PRIVATE_KEY= + +# Sepolia data +TICKER_USD_FEED_REGISTRY=0xF3D020838782213d3da52daa079A9c07F0A8e67e +TICKER_PRICE_STORAGE=0xa909e0bC9a35cC161dE9eA85cA76AB7A9b5b0121 +CONTRACTS_DEPLOYMENT_BLOCK=4840144 \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 6d73fc9..ebda3af 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -3,6 +3,7 @@ module.exports = { parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], root: true, + ignorePatterns: ["dist/**/*"], rules: { "@typescript-eslint/no-explicit-any": "off", "object-curly-spacing": ["warn", "always"], diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..ae8f15b --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,56 @@ +name: PR Tests + +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + dependencies: + runs-on: ubuntu-latest + + container: + image: node:18 + + strategy: + matrix: + node-version: [18.x] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Cache node modules + uses: actions/cache@v1 + with: + path: node_modules + key: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install yarn + run: npm install -g yarn + + - name: Install dependencies + run: yarn install + + lint: + runs-on: ubuntu-latest + needs: dependencies + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Load node modules + uses: actions/cache@v1 + with: + path: node_modules + key: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Code passes lint check + run: yarn lint \ No newline at end of file diff --git a/.gitignore b/.gitignore index 224b152..50e18f2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ node_modules .vscode # Ignore built ts files -dist/**/* \ No newline at end of file +dist \ No newline at end of file diff --git a/package.json b/package.json index cd66809..07965e5 100644 --- a/package.json +++ b/package.json @@ -4,24 +4,34 @@ "main": "index.js", "license": "MIT", "scripts": { + "build": "tsc", + "start": "node dist/index.js", "dev": "nodemon", "lint": "eslint .", - "lint:fix": "yarn lint --fix" + "lint:fix": "yarn lint --fix", + "sc:extract": "typechain --target=ethers-v6 './src/contracts/abi/json/*.json' --out-dir './src/contracts'" }, "devDependencies": { + "@typechain/ethers-v6": "^0.5.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/node-cron": "^3.0.11", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", "eslint": "^8.55.0", "nodemon": "^3.0.2", "ts-node": "^10.9.2", + "typechain": "^8.3.2", "typescript": "^5.3.3" }, "dependencies": { + "axios": "^1.6.2", "body-parser": "^1.20.2", "cors": "^2.8.5", "dotenv": "^16.3.1", - "express": "^4.18.2" + "ethers": "^6.9.0", + "express": "^4.18.2", + "node-cron": "^3.0.3", + "redux": "^5.0.0" } } diff --git a/src/app.ts b/src/app.ts index 958cd96..720e92c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,6 +3,7 @@ import express, { Router, Request, Response } from "express"; import bodyParser from "body-parser"; import cors from "cors"; import api from "./api"; +import { initApp } from "./jobs/app.jobs"; const { NODE_PORT, NODE_HOST } = CONFIG; const app = express(); @@ -16,8 +17,9 @@ app.use(bodyParser.json()); const apiRouter = Router(); apiRouter.use("/api", api); -app.use(apiRouter); +initApp(); +app.use(apiRouter); // 404 app.use("*", (request: Request, response: Response) => { return response.status(404).json({ message: "Not Found" }); diff --git a/src/config/index.ts b/src/config/index.ts index 89f4207..e654aad 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -18,7 +18,15 @@ const corsOptions: CorsOptions = { const { NODE_PORT, NODE_HOST, - NODE_ENV + NODE_ENV, + COINGECKO_BASE_URL, + COINGECKO_API_KEY, + COINGECKO_API_KEY_QUERY_NAME, + ALCHEMY_URL, + PRIVATE_KEY, + TICKER_USD_FEED_REGISTRY, + TICKER_PRICE_STORAGE, + CONTRACTS_DEPLOYMENT_BLOCK } = process.env; const ENV: ApplicationEnv = NODE_ENV as ApplicationEnv || ApplicationEnv.DEVELOPMENT; @@ -28,4 +36,12 @@ export const CONFIG = { NODE_HOST, CORS_OPTIONS: corsOptions, NODE_ENV: ENV, + COINGECKO_BASE_URL, + COINGECKO_API_KEY, + COINGECKO_API_KEY_QUERY_NAME, + ALCHEMY_URL, + PRIVATE_KEY, + TICKER_USD_FEED_REGISTRY, + TICKER_PRICE_STORAGE, + CONTRACTS_DEPLOYMENT_BLOCK: Number(CONTRACTS_DEPLOYMENT_BLOCK) }; diff --git a/src/constants/coingecko.ts b/src/constants/coingecko.ts new file mode 100644 index 0000000..a410d36 --- /dev/null +++ b/src/constants/coingecko.ts @@ -0,0 +1,14 @@ +export enum CoinGeckoFiatCurrencies { + USD = "usd" +} + +const chainlinkTickerToCoingeckoMap: {[symbol: string]: string} = { + "BTC": "bitcoin", + "ETH": "ethereum", + "LINK": "chainlink", + "SNX": "havven" +} + +export function getCoingeckoIdByChainlinkTicker(ticker: string) { + return chainlinkTickerToCoingeckoMap[ticker]; +} \ No newline at end of file diff --git a/src/contracts/abi/ChainlinkAggregatorV3Abi.ts b/src/contracts/abi/ChainlinkAggregatorV3Abi.ts new file mode 100644 index 0000000..0883248 --- /dev/null +++ b/src/contracts/abi/ChainlinkAggregatorV3Abi.ts @@ -0,0 +1,186 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumberish, + BytesLike, + FunctionFragment, + Result, + Interface, + ContractRunner, + ContractMethod, + Listener, +} from "ethers"; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedListener, + TypedContractMethod, +} from "../common"; + +export interface ChainlinkAggregatorV3AbiInterface extends Interface { + getFunction( + nameOrSignature: + | "decimals" + | "description" + | "getRoundData" + | "latestRoundData" + | "version" + ): FunctionFragment; + + encodeFunctionData(functionFragment: "decimals", values?: undefined): string; + encodeFunctionData( + functionFragment: "description", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "getRoundData", + values: [BigNumberish] + ): string; + encodeFunctionData( + functionFragment: "latestRoundData", + values?: undefined + ): string; + encodeFunctionData(functionFragment: "version", values?: undefined): string; + + decodeFunctionResult(functionFragment: "decimals", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "description", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "getRoundData", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "latestRoundData", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "version", data: BytesLike): Result; +} + +export interface ChainlinkAggregatorV3Abi extends BaseContract { + connect(runner?: ContractRunner | null): ChainlinkAggregatorV3Abi; + waitForDeployment(): Promise; + + interface: ChainlinkAggregatorV3AbiInterface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on( + event: TCEvent, + listener: TypedListener + ): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once( + event: TCEvent, + listener: TypedListener + ): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners( + event: TCEvent + ): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners( + event?: TCEvent + ): Promise; + + decimals: TypedContractMethod<[], [bigint], "view">; + + description: TypedContractMethod<[], [string], "view">; + + getRoundData: TypedContractMethod< + [_roundId: BigNumberish], + [ + [bigint, bigint, bigint, bigint, bigint] & { + roundId: bigint; + answer: bigint; + startedAt: bigint; + updatedAt: bigint; + answeredInRound: bigint; + } + ], + "view" + >; + + latestRoundData: TypedContractMethod< + [], + [ + [bigint, bigint, bigint, bigint, bigint] & { + roundId: bigint; + answer: bigint; + startedAt: bigint; + updatedAt: bigint; + answeredInRound: bigint; + } + ], + "view" + >; + + version: TypedContractMethod<[], [bigint], "view">; + + getFunction( + key: string | FunctionFragment + ): T; + + getFunction( + nameOrSignature: "decimals" + ): TypedContractMethod<[], [bigint], "view">; + getFunction( + nameOrSignature: "description" + ): TypedContractMethod<[], [string], "view">; + getFunction( + nameOrSignature: "getRoundData" + ): TypedContractMethod< + [_roundId: BigNumberish], + [ + [bigint, bigint, bigint, bigint, bigint] & { + roundId: bigint; + answer: bigint; + startedAt: bigint; + updatedAt: bigint; + answeredInRound: bigint; + } + ], + "view" + >; + getFunction( + nameOrSignature: "latestRoundData" + ): TypedContractMethod< + [], + [ + [bigint, bigint, bigint, bigint, bigint] & { + roundId: bigint; + answer: bigint; + startedAt: bigint; + updatedAt: bigint; + answeredInRound: bigint; + } + ], + "view" + >; + getFunction( + nameOrSignature: "version" + ): TypedContractMethod<[], [bigint], "view">; + + filters: {}; +} diff --git a/src/contracts/abi/TickerPriceStorageAbi.ts b/src/contracts/abi/TickerPriceStorageAbi.ts new file mode 100644 index 0000000..07c47d8 --- /dev/null +++ b/src/contracts/abi/TickerPriceStorageAbi.ts @@ -0,0 +1,132 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumberish, + BytesLike, + FunctionFragment, + Result, + Interface, + EventFragment, + ContractRunner, + ContractMethod, + Listener, +} from "ethers"; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedLogDescription, + TypedListener, + TypedContractMethod, +} from "../common"; + +export interface TickerPriceStorageAbiInterface extends Interface { + getFunction(nameOrSignature: "set"): FunctionFragment; + + getEvent(nameOrSignatureOrTopic: "TickerPriceUpdated"): EventFragment; + + encodeFunctionData( + functionFragment: "set", + values: [string, BigNumberish] + ): string; + + decodeFunctionResult(functionFragment: "set", data: BytesLike): Result; +} + +export namespace TickerPriceUpdatedEvent { + export type InputTuple = [ticker: string, newPrice: BigNumberish]; + export type OutputTuple = [ticker: string, newPrice: bigint]; + export interface OutputObject { + ticker: string; + newPrice: bigint; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export interface TickerPriceStorageAbi extends BaseContract { + connect(runner?: ContractRunner | null): TickerPriceStorageAbi; + waitForDeployment(): Promise; + + interface: TickerPriceStorageAbiInterface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on( + event: TCEvent, + listener: TypedListener + ): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once( + event: TCEvent, + listener: TypedListener + ): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners( + event: TCEvent + ): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners( + event?: TCEvent + ): Promise; + + set: TypedContractMethod< + [ticker: string, price: BigNumberish], + [void], + "nonpayable" + >; + + getFunction( + key: string | FunctionFragment + ): T; + + getFunction( + nameOrSignature: "set" + ): TypedContractMethod< + [ticker: string, price: BigNumberish], + [void], + "nonpayable" + >; + + getEvent( + key: "TickerPriceUpdated" + ): TypedContractEvent< + TickerPriceUpdatedEvent.InputTuple, + TickerPriceUpdatedEvent.OutputTuple, + TickerPriceUpdatedEvent.OutputObject + >; + + filters: { + "TickerPriceUpdated(string,uint256)": TypedContractEvent< + TickerPriceUpdatedEvent.InputTuple, + TickerPriceUpdatedEvent.OutputTuple, + TickerPriceUpdatedEvent.OutputObject + >; + TickerPriceUpdated: TypedContractEvent< + TickerPriceUpdatedEvent.InputTuple, + TickerPriceUpdatedEvent.OutputTuple, + TickerPriceUpdatedEvent.OutputObject + >; + }; +} diff --git a/src/contracts/abi/TickerUSDFeedRegistryAbi.ts b/src/contracts/abi/TickerUSDFeedRegistryAbi.ts new file mode 100644 index 0000000..f60e64e --- /dev/null +++ b/src/contracts/abi/TickerUSDFeedRegistryAbi.ts @@ -0,0 +1,150 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BytesLike, + FunctionFragment, + Result, + Interface, + EventFragment, + AddressLike, + ContractRunner, + ContractMethod, + Listener, +} from "ethers"; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedLogDescription, + TypedListener, + TypedContractMethod, +} from "../common"; + +export interface TickerUSDFeedRegistryAbiInterface extends Interface { + getFunction( + nameOrSignature: "getTickerFeed" | "setTickerFeed" + ): FunctionFragment; + + getEvent(nameOrSignatureOrTopic: "TickerFeedUpdated"): EventFragment; + + encodeFunctionData( + functionFragment: "getTickerFeed", + values: [string] + ): string; + encodeFunctionData( + functionFragment: "setTickerFeed", + values: [string, AddressLike] + ): string; + + decodeFunctionResult( + functionFragment: "getTickerFeed", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "setTickerFeed", + data: BytesLike + ): Result; +} + +export namespace TickerFeedUpdatedEvent { + export type InputTuple = [ticker: string, feedAddress: AddressLike]; + export type OutputTuple = [ticker: string, feedAddress: string]; + export interface OutputObject { + ticker: string; + feedAddress: string; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export interface TickerUSDFeedRegistryAbi extends BaseContract { + connect(runner?: ContractRunner | null): TickerUSDFeedRegistryAbi; + waitForDeployment(): Promise; + + interface: TickerUSDFeedRegistryAbiInterface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on( + event: TCEvent, + listener: TypedListener + ): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once( + event: TCEvent, + listener: TypedListener + ): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners( + event: TCEvent + ): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners( + event?: TCEvent + ): Promise; + + getTickerFeed: TypedContractMethod<[ticker: string], [string], "view">; + + setTickerFeed: TypedContractMethod< + [ticker: string, feedAddress: AddressLike], + [void], + "nonpayable" + >; + + getFunction( + key: string | FunctionFragment + ): T; + + getFunction( + nameOrSignature: "getTickerFeed" + ): TypedContractMethod<[ticker: string], [string], "view">; + getFunction( + nameOrSignature: "setTickerFeed" + ): TypedContractMethod< + [ticker: string, feedAddress: AddressLike], + [void], + "nonpayable" + >; + + getEvent( + key: "TickerFeedUpdated" + ): TypedContractEvent< + TickerFeedUpdatedEvent.InputTuple, + TickerFeedUpdatedEvent.OutputTuple, + TickerFeedUpdatedEvent.OutputObject + >; + + filters: { + "TickerFeedUpdated(string,address)": TypedContractEvent< + TickerFeedUpdatedEvent.InputTuple, + TickerFeedUpdatedEvent.OutputTuple, + TickerFeedUpdatedEvent.OutputObject + >; + TickerFeedUpdated: TypedContractEvent< + TickerFeedUpdatedEvent.InputTuple, + TickerFeedUpdatedEvent.OutputTuple, + TickerFeedUpdatedEvent.OutputObject + >; + }; +} diff --git a/src/contracts/abi/json/chainlinkAggregatorV3.abi.json b/src/contracts/abi/json/chainlinkAggregatorV3.abi.json new file mode 100644 index 0000000..ddc3755 --- /dev/null +++ b/src/contracts/abi/json/chainlinkAggregatorV3.abi.json @@ -0,0 +1,113 @@ +[ + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "description", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint80", + "name": "_roundId", + "type": "uint80" + } + ], + "name": "getRoundData", + "outputs": [ + { + "internalType": "uint80", + "name": "roundId", + "type": "uint80" + }, + { + "internalType": "int256", + "name": "answer", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "uint80", + "name": "answeredInRound", + "type": "uint80" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestRoundData", + "outputs": [ + { + "internalType": "uint80", + "name": "roundId", + "type": "uint80" + }, + { + "internalType": "int256", + "name": "answer", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "uint80", + "name": "answeredInRound", + "type": "uint80" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/contracts/abi/json/tickerPriceStorage.abi.json b/src/contracts/abi/json/tickerPriceStorage.abi.json new file mode 100644 index 0000000..4ef6f39 --- /dev/null +++ b/src/contracts/abi/json/tickerPriceStorage.abi.json @@ -0,0 +1,50 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "tickerFeedRegistry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "ticker", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPrice", + "type": "uint256" + } + ], + "name": "TickerPriceUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "ticker", + "type": "string" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/src/contracts/abi/json/tickerUSDFeedRegistry.abi.json b/src/contracts/abi/json/tickerUSDFeedRegistry.abi.json new file mode 100644 index 0000000..d5d4d6f --- /dev/null +++ b/src/contracts/abi/json/tickerUSDFeedRegistry.abi.json @@ -0,0 +1,63 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "ticker", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "feedAddress", + "type": "address" + } + ], + "name": "TickerFeedUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "ticker", + "type": "string" + } + ], + "name": "getTickerFeed", + "outputs": [ + { + "internalType": "address", + "name": "feedAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "ticker", + "type": "string" + }, + { + "internalType": "address", + "name": "feedAddress", + "type": "address" + } + ], + "name": "setTickerFeed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/src/contracts/common.ts b/src/contracts/common.ts new file mode 100644 index 0000000..56b5f21 --- /dev/null +++ b/src/contracts/common.ts @@ -0,0 +1,131 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + FunctionFragment, + Typed, + EventFragment, + ContractTransaction, + ContractTransactionResponse, + DeferredTopicFilter, + EventLog, + TransactionRequest, + LogDescription, +} from "ethers"; + +export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent> + extends DeferredTopicFilter {} + +export interface TypedContractEvent< + InputTuple extends Array = any, + OutputTuple extends Array = any, + OutputObject = any +> { + (...args: Partial): TypedDeferredTopicFilter< + TypedContractEvent + >; + name: string; + fragment: EventFragment; + getFragment(...args: Partial): EventFragment; +} + +type __TypechainAOutputTuple = T extends TypedContractEvent< + infer _U, + infer W +> + ? W + : never; +type __TypechainOutputObject = T extends TypedContractEvent< + infer _U, + infer _W, + infer V +> + ? V + : never; + +export interface TypedEventLog + extends Omit { + args: __TypechainAOutputTuple & __TypechainOutputObject; +} + +export interface TypedLogDescription + extends Omit { + args: __TypechainAOutputTuple & __TypechainOutputObject; +} + +export type TypedListener = ( + ...listenerArg: [ + ...__TypechainAOutputTuple, + TypedEventLog, + ...undefined[] + ] +) => void; + +export type MinEthersFactory = { + deploy(...a: ARGS[]): Promise; +}; + +export type GetContractTypeFromFactory = F extends MinEthersFactory< + infer C, + any +> + ? C + : never; +export type GetARGsTypeFromFactory = F extends MinEthersFactory + ? Parameters + : never; + +export type StateMutability = "nonpayable" | "payable" | "view"; + +export type BaseOverrides = Omit; +export type NonPayableOverrides = Omit< + BaseOverrides, + "value" | "blockTag" | "enableCcipRead" +>; +export type PayableOverrides = Omit< + BaseOverrides, + "blockTag" | "enableCcipRead" +>; +export type ViewOverrides = Omit; +export type Overrides = S extends "nonpayable" + ? NonPayableOverrides + : S extends "payable" + ? PayableOverrides + : ViewOverrides; + +export type PostfixOverrides, S extends StateMutability> = + | A + | [...A, Overrides]; +export type ContractMethodArgs< + A extends Array, + S extends StateMutability +> = PostfixOverrides<{ [I in keyof A]-?: A[I] | Typed }, S>; + +export type DefaultReturnType = R extends Array ? R[0] : R; + +// export interface ContractMethod = Array, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { +export interface TypedContractMethod< + A extends Array = Array, + R = any, + S extends StateMutability = "payable" +> { + (...args: ContractMethodArgs): S extends "view" + ? Promise> + : Promise; + + name: string; + + fragment: FunctionFragment; + + getFragment(...args: ContractMethodArgs): FunctionFragment; + + populateTransaction( + ...args: ContractMethodArgs + ): Promise; + staticCall( + ...args: ContractMethodArgs + ): Promise>; + send(...args: ContractMethodArgs): Promise; + estimateGas(...args: ContractMethodArgs): Promise; + staticCallResult(...args: ContractMethodArgs): Promise; +} diff --git a/src/contracts/factories/ChainlinkAggregatorV3Abi__factory.ts b/src/contracts/factories/ChainlinkAggregatorV3Abi__factory.ts new file mode 100644 index 0000000..375c190 --- /dev/null +++ b/src/contracts/factories/ChainlinkAggregatorV3Abi__factory.ts @@ -0,0 +1,140 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Interface, type ContractRunner } from "ethers"; +import type { + ChainlinkAggregatorV3Abi, + ChainlinkAggregatorV3AbiInterface, +} from "../abi/ChainlinkAggregatorV3Abi"; + +const _abi = [ + { + inputs: [], + name: "decimals", + outputs: [ + { + internalType: "uint8", + name: "", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "description", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint80", + name: "_roundId", + type: "uint80", + }, + ], + name: "getRoundData", + outputs: [ + { + internalType: "uint80", + name: "roundId", + type: "uint80", + }, + { + internalType: "int256", + name: "answer", + type: "int256", + }, + { + internalType: "uint256", + name: "startedAt", + type: "uint256", + }, + { + internalType: "uint256", + name: "updatedAt", + type: "uint256", + }, + { + internalType: "uint80", + name: "answeredInRound", + type: "uint80", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "latestRoundData", + outputs: [ + { + internalType: "uint80", + name: "roundId", + type: "uint80", + }, + { + internalType: "int256", + name: "answer", + type: "int256", + }, + { + internalType: "uint256", + name: "startedAt", + type: "uint256", + }, + { + internalType: "uint256", + name: "updatedAt", + type: "uint256", + }, + { + internalType: "uint80", + name: "answeredInRound", + type: "uint80", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "version", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +export class ChainlinkAggregatorV3Abi__factory { + static readonly abi = _abi; + static createInterface(): ChainlinkAggregatorV3AbiInterface { + return new Interface(_abi) as ChainlinkAggregatorV3AbiInterface; + } + static connect( + address: string, + runner?: ContractRunner | null + ): ChainlinkAggregatorV3Abi { + return new Contract( + address, + _abi, + runner + ) as unknown as ChainlinkAggregatorV3Abi; + } +} diff --git a/src/contracts/factories/TickerPriceStorageAbi__factory.ts b/src/contracts/factories/TickerPriceStorageAbi__factory.ts new file mode 100644 index 0000000..2773b00 --- /dev/null +++ b/src/contracts/factories/TickerPriceStorageAbi__factory.ts @@ -0,0 +1,77 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Interface, type ContractRunner } from "ethers"; +import type { + TickerPriceStorageAbi, + TickerPriceStorageAbiInterface, +} from "../abi/TickerPriceStorageAbi"; + +const _abi = [ + { + inputs: [ + { + internalType: "address", + name: "tickerFeedRegistry", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "string", + name: "ticker", + type: "string", + }, + { + indexed: false, + internalType: "uint256", + name: "newPrice", + type: "uint256", + }, + ], + name: "TickerPriceUpdated", + type: "event", + }, + { + inputs: [ + { + internalType: "string", + name: "ticker", + type: "string", + }, + { + internalType: "uint256", + name: "price", + type: "uint256", + }, + ], + name: "set", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +export class TickerPriceStorageAbi__factory { + static readonly abi = _abi; + static createInterface(): TickerPriceStorageAbiInterface { + return new Interface(_abi) as TickerPriceStorageAbiInterface; + } + static connect( + address: string, + runner?: ContractRunner | null + ): TickerPriceStorageAbi { + return new Contract( + address, + _abi, + runner + ) as unknown as TickerPriceStorageAbi; + } +} diff --git a/src/contracts/factories/TickerUSDFeedRegistryAbi__factory.ts b/src/contracts/factories/TickerUSDFeedRegistryAbi__factory.ts new file mode 100644 index 0000000..1324c14 --- /dev/null +++ b/src/contracts/factories/TickerUSDFeedRegistryAbi__factory.ts @@ -0,0 +1,90 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Interface, type ContractRunner } from "ethers"; +import type { + TickerUSDFeedRegistryAbi, + TickerUSDFeedRegistryAbiInterface, +} from "../abi/TickerUSDFeedRegistryAbi"; + +const _abi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "string", + name: "ticker", + type: "string", + }, + { + indexed: false, + internalType: "address", + name: "feedAddress", + type: "address", + }, + ], + name: "TickerFeedUpdated", + type: "event", + }, + { + inputs: [ + { + internalType: "string", + name: "ticker", + type: "string", + }, + ], + name: "getTickerFeed", + outputs: [ + { + internalType: "address", + name: "feedAddress", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "ticker", + type: "string", + }, + { + internalType: "address", + name: "feedAddress", + type: "address", + }, + ], + name: "setTickerFeed", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + +export class TickerUSDFeedRegistryAbi__factory { + static readonly abi = _abi; + static createInterface(): TickerUSDFeedRegistryAbiInterface { + return new Interface(_abi) as TickerUSDFeedRegistryAbiInterface; + } + static connect( + address: string, + runner?: ContractRunner | null + ): TickerUSDFeedRegistryAbi { + return new Contract( + address, + _abi, + runner + ) as unknown as TickerUSDFeedRegistryAbi; + } +} diff --git a/src/contracts/factories/index.ts b/src/contracts/factories/index.ts new file mode 100644 index 0000000..97b57c2 --- /dev/null +++ b/src/contracts/factories/index.ts @@ -0,0 +1,6 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { ChainlinkAggregatorV3Abi__factory } from "./ChainlinkAggregatorV3Abi__factory"; +export { TickerPriceStorageAbi__factory } from "./TickerPriceStorageAbi__factory"; +export { TickerUSDFeedRegistryAbi__factory } from "./TickerUSDFeedRegistryAbi__factory"; diff --git a/src/contracts/index.ts b/src/contracts/index.ts new file mode 100644 index 0000000..fc6eaad --- /dev/null +++ b/src/contracts/index.ts @@ -0,0 +1,10 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { ChainlinkAggregatorV3Abi } from "./abi/ChainlinkAggregatorV3Abi"; +export type { TickerPriceStorageAbi } from "./abi/TickerPriceStorageAbi"; +export type { TickerUSDFeedRegistryAbi } from "./abi/TickerUSDFeedRegistryAbi"; +export * as factories from "./factories"; +export { ChainlinkAggregatorV3Abi__factory } from "./factories/ChainlinkAggregatorV3Abi__factory"; +export { TickerPriceStorageAbi__factory } from "./factories/TickerPriceStorageAbi__factory"; +export { TickerUSDFeedRegistryAbi__factory } from "./factories/TickerUSDFeedRegistryAbi__factory"; diff --git a/src/jobs/app.jobs.ts b/src/jobs/app.jobs.ts new file mode 100644 index 0000000..0a59a2d --- /dev/null +++ b/src/jobs/app.jobs.ts @@ -0,0 +1,13 @@ +import { initTickerState, setupTickerFetchingJob } from "./ticker.jobs"; +import {setupOffchainPriceFetchingJob} from "./price.jobs"; + +const initAppState = async () => { + await initTickerState(); +}; + +export const initApp = async () => { + await initAppState(); + + await setupTickerFetchingJob(); + await setupOffchainPriceFetchingJob() +}; \ No newline at end of file diff --git a/src/jobs/price.jobs.ts b/src/jobs/price.jobs.ts new file mode 100644 index 0000000..41b2278 --- /dev/null +++ b/src/jobs/price.jobs.ts @@ -0,0 +1,24 @@ +import CoinGeckoService from "../services/coingecko/coingecko.service"; +import CronService from "../services/cron.service"; +import store from "../redux/store"; +import {getCoingeckoIdByChainlinkTicker} from "../constants/coingecko"; +import {setCurrentOffchainPrice} from "../redux/prices/prices.redux.actions"; + +export const setupOffchainPriceFetchingJob = async () => { + const coinGecko = new CoinGeckoService(); + + CronService.scheduleRecurringJob(async () => { + const tickerSymbols = store.getState().tickers.symbols; + const tickerCoinGeckoIds = tickerSymbols.map(getCoingeckoIdByChainlinkTicker); + + const offchainPrices = await coinGecko.getPrices(tickerCoinGeckoIds); + tickerCoinGeckoIds.forEach((id, index) => { + const symbol = tickerSymbols[index]; + const currentPrice = offchainPrices[id]?.usd; + + if (currentPrice) { + store.dispatch(setCurrentOffchainPrice(symbol, currentPrice)); + } + }) + }) +} \ No newline at end of file diff --git a/src/jobs/ticker.jobs.ts b/src/jobs/ticker.jobs.ts new file mode 100644 index 0000000..0983d23 --- /dev/null +++ b/src/jobs/ticker.jobs.ts @@ -0,0 +1,19 @@ +import TickersService from "../services/tickers/tickers.service"; +import store from "../redux/store"; +import { addTicker } from "../redux/tickers/tickers.redux.actions"; +import { TickerRegistryData } from "../services/tickers/tickers.service.types"; + +const tickersService = new TickersService(); + +const updateReduxTickerState = (tickerData: TickerRegistryData) => { + store.dispatch(addTicker(tickerData.tickerSymbol, tickerData.chainlinkFeedAddress)); +}; + +export const initTickerState = async () => { + const tickers = await tickersService.getAllTickers(); + tickers.forEach(updateReduxTickerState); +}; + +export const setupTickerFetchingJob = async () => { + tickersService.listenForTickerUpdates(updateReduxTickerState).then(); +}; \ No newline at end of file diff --git a/src/redux/prices/prices.redux.actions.ts b/src/redux/prices/prices.redux.actions.ts new file mode 100644 index 0000000..a9bf849 --- /dev/null +++ b/src/redux/prices/prices.redux.actions.ts @@ -0,0 +1,21 @@ +import {EPricesReduxActions, SetCurrentOffchainPriceAction, SetCurrentOnchainPriceAction} from "./prices.redux.types"; + +export function setCurrentOnchainPrice(tickerSymbol: string, onchainPrice: number): SetCurrentOnchainPriceAction { + return { + type: EPricesReduxActions.SET_CURRENT_ONCHAIN_PRICE, + payload: { + tickerSymbol, + onchainPrice + } + } +} + +export function setCurrentOffchainPrice(tickerSymbol: string, offchainPrice: number): SetCurrentOffchainPriceAction { + return { + type: EPricesReduxActions.SET_CURRENT_OFFCHAIN_PRICE, + payload: { + tickerSymbol, + offchainPrice + } + } +} \ No newline at end of file diff --git a/src/redux/prices/prices.redux.reducer.ts b/src/redux/prices/prices.redux.reducer.ts new file mode 100644 index 0000000..d88a8fe --- /dev/null +++ b/src/redux/prices/prices.redux.reducer.ts @@ -0,0 +1,44 @@ +import {EPricesReduxActions, PricesReduxActions, PricesReduxReducerState} from "./prices.redux.types"; +import {Reducer} from "redux"; + +const initialState: PricesReduxReducerState = { + current: {} +} + +const pricesReduxReducer: Reducer = (state = initialState, action) => { + switch (action.type) { + case EPricesReduxActions.SET_CURRENT_ONCHAIN_PRICE: { + const { tickerSymbol, onchainPrice } = action.payload; + + return { + ...state, + current: { + ...state.current, + [tickerSymbol]: { + ...(state.current[tickerSymbol] || {}), + onchain: onchainPrice + } + } + } + } + case EPricesReduxActions.SET_CURRENT_OFFCHAIN_PRICE: { + const { tickerSymbol, offchainPrice } = action.payload; + + return { + ...state, + current: { + ...state.current, + [tickerSymbol]: { + ...(state.current[tickerSymbol] || {}), + offchain: offchainPrice + } + } + } + } + default: { + return initialState; + } + } +} + +export default pricesReduxReducer; \ No newline at end of file diff --git a/src/redux/prices/prices.redux.types.ts b/src/redux/prices/prices.redux.types.ts new file mode 100644 index 0000000..90729a1 --- /dev/null +++ b/src/redux/prices/prices.redux.types.ts @@ -0,0 +1,29 @@ +import {ReduxAction} from "../redux.types"; + +export enum EPricesReduxActions { + SET_CURRENT_ONCHAIN_PRICE = "SET_CURRENT_ONCHAIN_PRICE", + SET_CURRENT_OFFCHAIN_PRICE = "SET_CURRENT_OFFCHAIN_PRICE", +} + +export type SetCurrentOnchainPriceAction = ReduxAction + +export type SetCurrentOffchainPriceAction = ReduxAction + +export type PricesReduxActions = + SetCurrentOnchainPriceAction | + SetCurrentOffchainPriceAction; + +export type PricesReduxReducerState = { + current: { + [tickerSymbol: string]: { + onchain?: number; + offchain?: number; + } + } +} \ No newline at end of file diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts new file mode 100644 index 0000000..0930952 --- /dev/null +++ b/src/redux/reducer.ts @@ -0,0 +1,10 @@ +import { combineReducers } from "redux"; +import TickersReducer from "./tickers/tickers.redux.reducer"; +import PricesReduxReducer from "./prices/prices.redux.reducer"; + +const rootReducer = combineReducers({ + tickers: TickersReducer, + prices: PricesReduxReducer +}); + +export default rootReducer; \ No newline at end of file diff --git a/src/redux/redux.types.ts b/src/redux/redux.types.ts new file mode 100644 index 0000000..b9f08d0 --- /dev/null +++ b/src/redux/redux.types.ts @@ -0,0 +1,8 @@ +import { Action } from "redux"; +import rootReducer from "./reducer"; + +export type RootState = ReturnType; + +export interface ReduxAction> extends Action { + payload: Payload +} \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts new file mode 100644 index 0000000..60e8fd7 --- /dev/null +++ b/src/redux/store.ts @@ -0,0 +1,8 @@ +import { legacy_createStore as createStore } from "redux"; +import rootReducer from "./reducer"; + +const store = createStore( + rootReducer +); + +export default store; \ No newline at end of file diff --git a/src/redux/tickers/tickers.redux.actions.ts b/src/redux/tickers/tickers.redux.actions.ts new file mode 100644 index 0000000..d43e494 --- /dev/null +++ b/src/redux/tickers/tickers.redux.actions.ts @@ -0,0 +1,11 @@ +import { AddTickerAction, ETickerReduxActions } from "./tickers.redux.types"; + +export function addTicker(tickerSymbol: string, chainlinkFeedAddress: string): AddTickerAction { + return { + type: ETickerReduxActions.ADD_TICKER, + payload: { + tickerSymbol, + chainlinkFeed: chainlinkFeedAddress + } + }; +} \ No newline at end of file diff --git a/src/redux/tickers/tickers.redux.reducer.ts b/src/redux/tickers/tickers.redux.reducer.ts new file mode 100644 index 0000000..da142d8 --- /dev/null +++ b/src/redux/tickers/tickers.redux.reducer.ts @@ -0,0 +1,37 @@ +import { Reducer } from "redux"; +import { ETickerReduxActions, TickerReduxActions, TickerReduxReducerState } from "./tickers.redux.types"; + +const initialState: TickerReduxReducerState = { + symbols: [], + chainlinkFeed: {} +}; + +const tickersReduxReducer: Reducer = (state = initialState, action) => { + switch (action.type) { + case ETickerReduxActions.ADD_TICKER: { + const { tickerSymbol, chainlinkFeed } = action.payload; + + // Can be optimized, but sufficient for current number of supported tickers + let newSymbolArray: string[]; + if (state.symbols.includes(tickerSymbol)) { + newSymbolArray = state.symbols; + } else { + newSymbolArray = [...state.symbols, tickerSymbol]; + } + + return { + ...state, + symbols: newSymbolArray, + chainlinkFeed: { + ...state.chainlinkFeed, + [tickerSymbol]: chainlinkFeed + } + }; + } + default: { + return state; + } + } +}; + +export default tickersReduxReducer; \ No newline at end of file diff --git a/src/redux/tickers/tickers.redux.types.ts b/src/redux/tickers/tickers.redux.types.ts new file mode 100644 index 0000000..26a1e9d --- /dev/null +++ b/src/redux/tickers/tickers.redux.types.ts @@ -0,0 +1,20 @@ +import { ReduxAction } from "../redux.types"; + +export enum ETickerReduxActions { + ADD_TICKER = "ADD_TICKER" +} + +export type AddTickerAction = ReduxAction + +export type TickerReduxActions = + AddTickerAction; + +export type TickerReduxReducerState = { + symbols: string[], + chainlinkFeed: { + [symbol: string]: string + } +} \ No newline at end of file diff --git a/src/services/coingecko/coingecko.service.ts b/src/services/coingecko/coingecko.service.ts new file mode 100644 index 0000000..17e5364 --- /dev/null +++ b/src/services/coingecko/coingecko.service.ts @@ -0,0 +1,35 @@ +import RestService from "../rest.service"; +import { CONFIG } from "../../config"; +import { EAuthenticationType } from "../../types/auth.types"; +import { arrayToString } from "../../utils/common.util"; +import { CoinGeckoFiatCurrencies } from "../../constants/coingecko"; +import { CoinGeckoSimplePriceResponse } from "./coingecko.service.types"; + +class CoinGeckoService extends RestService { + constructor() { + super({ + baseUrl: CONFIG.COINGECKO_BASE_URL, + authConfig: { + type: EAuthenticationType.QUERY_PARAM, + paramName: CONFIG.COINGECKO_API_KEY_QUERY_NAME, + paramValue: CONFIG.COINGECKO_API_KEY + } + }); + } + + public async getPrices(ids: string[]): Promise { + const response = await this.get({ + url: "simple/price", + config: { + params: { + ids: arrayToString(ids), + vs_currencies: CoinGeckoFiatCurrencies.USD + } + } + }); + + return response.data; + } +} + +export default CoinGeckoService; \ No newline at end of file diff --git a/src/services/coingecko/coingecko.service.types.ts b/src/services/coingecko/coingecko.service.types.ts new file mode 100644 index 0000000..ff30efc --- /dev/null +++ b/src/services/coingecko/coingecko.service.types.ts @@ -0,0 +1,6 @@ +import { CoinGeckoFiatCurrencies } from "../../constants/coingecko"; +import { DynamicObject } from "../../types/util.types"; + +export type CoinGeckoSimplePriceResponse = { + [ticker: string]: DynamicObject +} \ No newline at end of file diff --git a/src/services/cron.service.ts b/src/services/cron.service.ts new file mode 100644 index 0000000..571952d --- /dev/null +++ b/src/services/cron.service.ts @@ -0,0 +1,31 @@ +import cron from "node-cron"; + +class CronService { + private _nextJobID: number; + private _jobs: Map; + + constructor() { + this._nextJobID = 0; + this._jobs = new Map(); + } + + public scheduleRecurringJob(job: () => any, minutes: number = 1, runImmediately: boolean = false): number { + const task = cron.schedule(`*/${minutes} * * * *`, () => { + job(); + }, { runOnInit: runImmediately }); + + const jobId = this._nextJobID; + this._jobs.set(jobId, task); + this._nextJobID += 1; + + return jobId; + } + + public cancelJob(jobId: number) { + const task = this._jobs.get(jobId); + + task.stop(); + } +} + +export default new CronService(); \ No newline at end of file diff --git a/src/services/prices/prices.service.ts b/src/services/prices/prices.service.ts new file mode 100644 index 0000000..0ec61cb --- /dev/null +++ b/src/services/prices/prices.service.ts @@ -0,0 +1,6 @@ +class PricesService { + + public static updateOffchainPrices() { + + } +} \ No newline at end of file diff --git a/src/services/rest.service.ts b/src/services/rest.service.ts new file mode 100644 index 0000000..4909e09 --- /dev/null +++ b/src/services/rest.service.ts @@ -0,0 +1,41 @@ +import { AxiosInstance } from "axios"; +import { ApiRequestParams, AuthenticationData } from "../types/auth.types"; +import ApiClient from "../utils/api.util"; +import { DynamicObject } from "../types/util.types"; + +interface RestConfig { + baseUrl?: string; + authConfig?: AuthenticationData; + headers?: DynamicObject; +} + +class RestService { + protected rest: AxiosInstance; + protected baseUrl: string; + + constructor(config?: RestConfig) { + const { authConfig, baseUrl, headers } = config; + + // Setting the base url + this.baseUrl = baseUrl || ""; + this.rest = ApiClient(baseUrl, authConfig, headers); + } + + protected async get(params: Omit) { + return this.rest.get(params.url, params.config); + } + + protected async post(params: ApiRequestParams) { + return this.rest.post(params.url, params.data, params.config); + } + + protected async put(params: ApiRequestParams) { + return this.rest.put(params.url, params.data, params.config); + } + + protected async delete(params: Omit) { + return this.rest.delete(params.url, params.config); + } +} + +export default RestService; diff --git a/src/services/tickers/tickers.service.ts b/src/services/tickers/tickers.service.ts new file mode 100644 index 0000000..c498d6e --- /dev/null +++ b/src/services/tickers/tickers.service.ts @@ -0,0 +1,51 @@ +import Web3Service from "../web3.service"; +import { CONFIG } from "../../config"; +import { TypedEventLog } from "../../contracts/common"; +import { TickerRegistryData } from "./tickers.service.types"; + +class TickersService { + private _registryContract; + + constructor() { + this._registryContract = + Web3Service.getTickerUSDFeedRegistryContract(CONFIG.TICKER_USD_FEED_REGISTRY); + } + + public async listenForTickerUpdates(onTickerUpdate: (tickerData: TickerRegistryData) => void) { + const startBlockNumber = await Web3Service.provider.getBlockNumber(); + const eventFilter = this._registryContract.filters.TickerFeedUpdated(); + + await this._registryContract.on(eventFilter, (data) => { + if (data.log.blockNumber <= startBlockNumber) { + return; + } + + const parsedEvent = this.parseTickerFeedUpdatedEvent(data); + onTickerUpdate(parsedEvent); + }); + } + + public async getAllTickers(): Promise> { + const eventFilter = this._registryContract.filters.TickerFeedUpdated(); + const results = await this._registryContract.queryFilter(eventFilter, CONFIG.CONTRACTS_DEPLOYMENT_BLOCK); + + const tickerMap = new Map(); + results.forEach((result) => { + const data = this.parseTickerFeedUpdatedEvent(result); + tickerMap.set(data.tickerSymbol, data); + }); + + return tickerMap; + } + + private parseTickerFeedUpdatedEvent(event: TypedEventLog): TickerRegistryData { + const args = event.args as [string, string]; + + return { + tickerSymbol: args[0], + chainlinkFeedAddress: args[1] + }; + } +} + +export default TickersService; \ No newline at end of file diff --git a/src/services/tickers/tickers.service.types.ts b/src/services/tickers/tickers.service.types.ts new file mode 100644 index 0000000..bfa79b7 --- /dev/null +++ b/src/services/tickers/tickers.service.types.ts @@ -0,0 +1,4 @@ +export type TickerRegistryData = { + tickerSymbol: string, + chainlinkFeedAddress: string +} \ No newline at end of file diff --git a/src/services/web3.service.ts b/src/services/web3.service.ts new file mode 100644 index 0000000..4606a65 --- /dev/null +++ b/src/services/web3.service.ts @@ -0,0 +1,31 @@ +import { ethers, JsonRpcProvider, Wallet } from "ethers"; +import { TickerUSDFeedRegistryAbi__factory } from "../contracts"; + +import { CONFIG } from "../config"; + +class Web3Service { + public provider: JsonRpcProvider; + public signer: Wallet; + + constructor(providerUrl: string, signerPrivateKey: string) { + this.provider = new ethers.JsonRpcProvider(providerUrl); + this.signer = new ethers.Wallet(signerPrivateKey, this.provider); + } + + public getTickerUSDFeedRegistryContract(address: string, isMutatingState?: boolean) { + const contract = TickerUSDFeedRegistryAbi__factory.connect(address); + + if (isMutatingState) { + return contract.connect(this.signer); + } else { + return contract.connect(this.provider); + } + } +} + +// We'll create a singleton for now while it's single network client +// Once multi-network support is needed, we'll have it modular enough +export default new Web3Service( + CONFIG.ALCHEMY_URL, + CONFIG.PRIVATE_KEY +); \ No newline at end of file diff --git a/src/types/auth.types.ts b/src/types/auth.types.ts new file mode 100644 index 0000000..251b5fc --- /dev/null +++ b/src/types/auth.types.ts @@ -0,0 +1,24 @@ +import { AxiosRequestConfig } from "axios"; + +export enum EAuthenticationType { + QUERY_PARAM = "QUERY_PARAM" +} + +type AuthenticationType = { + type: EAuthenticationType; +} + +export type QueryParamAuthentication = AuthenticationType & { + type: EAuthenticationType.QUERY_PARAM; + paramName: string; + paramValue: string; +} + +export interface ApiRequestParams { + url: string; + config?: AxiosRequestConfig; + data?: any; +} + +export type AuthenticationData = + QueryParamAuthentication \ No newline at end of file diff --git a/src/utils/api.util.ts b/src/utils/api.util.ts new file mode 100644 index 0000000..7c96098 --- /dev/null +++ b/src/utils/api.util.ts @@ -0,0 +1,53 @@ +import axios, { AxiosRequestConfig } from "axios"; +import { EAuthenticationType, AuthenticationData } from "../types/auth.types"; +import { DynamicObject } from "../types/util.types"; +import { AuthenticationError, AuthorizationError } from "./errors.util"; + +function ApiClient(baseUrl: string, auth?: AuthenticationData, additionalHeaders?: DynamicObject) { + const headers: DynamicObject = { ...additionalHeaders }; + const additionalParams: AxiosRequestConfig["params"] = {}; + + if (auth) { + if (auth.type === EAuthenticationType.QUERY_PARAM) { + additionalParams[auth.paramName] = auth.paramValue; + } + } + + const client = axios.create({ + baseURL: baseUrl, + headers + }); + + client.interceptors.request.use((config) => { + config.params = { + ...(config.params || {}), + ...additionalParams + }; + + return config; + }); + + // Init the interceptors + client.interceptors.response.use( + function onResponse(response) { + return response; + }, + function onError(error) { + if (error.response && error.response.status) { + const errorCode = error.response.status; + + if (errorCode === 401) { + return Promise.reject(new AuthenticationError()); + } else if (errorCode === 403) { + return Promise.reject(new AuthorizationError()); + } + } else { + return Promise.reject(error); + } + } + ); + + return client; +} + +export default ApiClient; diff --git a/src/utils/common.util.ts b/src/utils/common.util.ts new file mode 100644 index 0000000..2889e1d --- /dev/null +++ b/src/utils/common.util.ts @@ -0,0 +1,9 @@ +export function arrayToString(array: any[]) { + return array.reduce((accumulator, currentValue, currentIndex) => { + if (currentIndex != 0) { + return accumulator + "," + currentValue.toString(); + } else { + return currentValue.toString(); + } + }); +} \ No newline at end of file diff --git a/src/utils/errors.util.ts b/src/utils/errors.util.ts new file mode 100644 index 0000000..0296856 --- /dev/null +++ b/src/utils/errors.util.ts @@ -0,0 +1,3 @@ +export class AuthenticationError extends Error {} + +export class AuthorizationError extends Error {} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cd5dd6f..31ce278 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -83,6 +88,18 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -124,6 +141,14 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@typechain/ethers-v6@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz#42fe214a19a8b687086c93189b301e2b878797ea" + integrity sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -186,6 +211,11 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/node-cron@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.11.tgz#70b7131f65038ae63cfe841354c8aba363632344" + integrity sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg== + "@types/node@*": version "20.10.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.4.tgz#b246fd84d55d5b1b71bf51f964bd514409347198" @@ -193,6 +223,16 @@ dependencies: undici-types "~5.26.4" +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + +"@types/prettier@^2.1.1": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + "@types/qs@*": version "6.9.10" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.10.tgz#0af26845b5067e1c9a622658a51f60a3934d51e8" @@ -343,6 +383,11 @@ acorn@^8.4.1, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -358,6 +403,13 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -383,6 +435,16 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^4.0.1, array-back@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -393,6 +455,20 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -473,7 +549,16 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -chalk@^4.0.0: +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -496,6 +581,13 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -503,11 +595,43 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-line-args@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^6.1.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== + dependencies: + array-back "^4.0.2" + chalk "^2.4.2" + table-layout "^1.0.2" + typical "^5.2.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -564,13 +688,18 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@^4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -585,6 +714,11 @@ define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -634,6 +768,11 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -734,6 +873,19 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +ethers@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.9.0.tgz#a4534bdcdfde306aee94ef32f3d5c70d7e33fcb9" + integrity sha512-pmfNyQzc2mseLe91FnT2vmNaTt8dDzhxZ/xItAV7uGsF4dI4ek2ufMu3rAkgQETL/TIs0GS5A+U05g9QyWnv3Q== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.5.0" + express@^4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -831,6 +983,13 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -853,6 +1012,20 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== +follow-redirects@^1.15.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -863,6 +1036,15 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -902,6 +1084,18 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -940,6 +1134,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + graphemer@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" @@ -1072,6 +1271,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -1094,6 +1298,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -1116,11 +1327,21 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -1166,7 +1387,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -1178,13 +1399,18 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1210,6 +1436,13 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +node-cron@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.3.tgz#c4bc7173dd96d96c50bdb51122c64415458caff2" + integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A== + dependencies: + uuid "8.3.2" + nodemon@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.2.tgz#222dd0de79fc7b7b3eedba422d2b9e5fc678621e" @@ -1335,6 +1568,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@^2.3.1: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -1343,6 +1581,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" @@ -1397,6 +1640,16 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + +redux@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.0.tgz#29572e29a439e094ff8fec46883fc45053f6736d" + integrity sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -1520,6 +1773,11 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +string-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== + strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -1532,7 +1790,7 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@^5.5.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -1546,6 +1804,16 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +table-layout@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -1575,6 +1843,21 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== +ts-command-line-args@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0" + integrity sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-usage "^6.1.0" + string-format "^2.0.0" + +ts-essentials@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + ts-node@^10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" @@ -1594,6 +1877,11 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -1614,11 +1902,37 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typechain@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" + integrity sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q== + dependencies: + "@types/prettier" "^2.1.1" + debug "^4.3.1" + fs-extra "^7.0.0" + glob "7.1.7" + js-sha3 "^0.8.0" + lodash "^4.17.15" + mkdirp "^1.0.4" + prettier "^2.3.1" + ts-command-line-args "^2.2.0" + ts-essentials "^7.0.1" + typescript@^5.3.3: version "5.3.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" @@ -1629,6 +1943,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -1646,6 +1965,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -1663,11 +1987,24 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wordwrapjs@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.2.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"