diff --git a/app.ts b/app.ts index 225f53f74..0d70d9ef3 100644 --- a/app.ts +++ b/app.ts @@ -1,5 +1,4 @@ -import { loadRestRoutesBeethoven } from './modules/beethoven/loadRestRoutes'; -import { loadRestRoutesBalancer } from './modules/balancer/loadRestRoutes'; +import { loadRestRoutes } from './modules/common/loadRestRoutes'; import { env } from './app/env'; import createExpressApp from 'express'; import { corsMiddleware } from './app/middleware/corsMiddleware'; @@ -12,10 +11,8 @@ import { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginUsageReporting, } from 'apollo-server-core'; -import { ApolloServerPlugin } from 'apollo-server-plugin-base'; -import { beethovenSchema } from './graphql_schema_generated_beethoven'; -import { balancerSchema } from './graphql_schema_generated_balancer'; -import { balancerResolvers, beethovenResolvers } from './app/gql/resolvers'; +import { schema } from './graphql_schema_generated'; +import { resolvers } from './app/gql/resolvers'; import helmet from 'helmet'; import GraphQLJSON from 'graphql-type-json'; import * as Sentry from '@sentry/node'; @@ -76,11 +73,7 @@ async function startServer() { app.use(contextMiddleware); app.use(sessionMiddleware); - if (env.PROTOCOL === 'beethoven') { - loadRestRoutesBeethoven(app); - } else if (env.PROTOCOL === 'balancer') { - loadRestRoutesBalancer(app); - } + loadRestRoutes(app); const httpServer = http.createServer(app); @@ -103,9 +96,9 @@ async function startServer() { const server = new ApolloServer({ resolvers: { JSON: GraphQLJSON, - ...(env.PROTOCOL === 'beethoven' ? beethovenResolvers : balancerResolvers), + ...resolvers, }, - typeDefs: env.PROTOCOL === 'beethoven' ? beethovenSchema : balancerSchema, + typeDefs: schema, introspection: true, plugins, context: ({ req }) => req.context, diff --git a/app/gql/resolvers.ts b/app/gql/resolvers.ts index 29e80c1a4..b6794c924 100644 --- a/app/gql/resolvers.ts +++ b/app/gql/resolvers.ts @@ -2,9 +2,5 @@ import { loadFilesSync } from '@graphql-tools/load-files'; import path from 'path'; import { mergeResolvers } from '@graphql-tools/merge'; -const balancerResolversArray = loadFilesSync(path.join(__dirname, '../../modules/!(beethoven)/**/*.resolvers.*')); - -const beethovenResolversArray = loadFilesSync(path.join(__dirname, '../../modules/!(balancer)/**/*.resolvers.*')); - -export const balancerResolvers = mergeResolvers(balancerResolversArray); -export const beethovenResolvers = mergeResolvers(beethovenResolversArray); +const resolversArray = loadFilesSync(path.join(__dirname, '../../modules/**/*.resolvers.*')); +export const resolvers = mergeResolvers(resolversArray); diff --git a/codegen.yml b/codegen.yml index 958b0e043..57c4ff13b 100644 --- a/codegen.yml +++ b/codegen.yml @@ -137,31 +137,13 @@ generates: schema: ${USER_SNAPSHOT_SUBGRAPH} plugins: - schema-ast - graphql_schema_generated_balancer.ts: - schema: - - ./modules/**/*.gql - - '!./modules/beethoven/beets.gql' - - '!./modules/**/*.beets.gql' - plugins: - - add: - content: | - import { gql } from 'apollo-server-express'; - export const balancerSchema = gql` - #\n# THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n# - - schema-ast - - add: - placement: 'append' - content: '`;' - graphql_schema_generated_beethoven.ts: - schema: - - ./modules/**/*.gql - - '!./modules/balancer/balancer.gql' - - '!./modules/**/*.balancer.gql' + graphql_schema_generated.ts: + schema: ./modules/**/*.gql plugins: - add: content: | import { gql } from 'apollo-server-express'; - export const beethovenSchema = gql` + export const schema = gql` #\n# THIS FILE IS AUTOGENERATED — DO NOT EDIT IT\n# - schema-ast - add: diff --git a/modules/balancer/loadRestRoutes.ts b/modules/balancer/loadRestRoutes.ts deleted file mode 100644 index aad0efca7..000000000 --- a/modules/balancer/loadRestRoutes.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Express } from 'express'; - -export function loadRestRoutesBalancer(app: Express) { - app.use('/health', (req, res) => res.sendStatus(200)); -} diff --git a/modules/coingecko/coingecko.service.ts b/modules/coingecko/coingecko.service.ts index d638b9ff7..c3da74188 100644 --- a/modules/coingecko/coingecko.service.ts +++ b/modules/coingecko/coingecko.service.ts @@ -72,8 +72,10 @@ interface CoinId { that happen. */ -const tokensPerInterval = env.COINGECKO_API_KEY ? ((env.DEPLOYMENT_ENV as DeploymentEnv) === 'main' ? 10 : 5) : 3; -const requestRateLimiter = new RateLimiter({ tokensPerInterval, interval: 'minute' }); +const tokensPerMinute = env.COINGECKO_API_KEY ? 10 : 3; +const requestRateLimiter = new RateLimiter({ tokensPerInterval: tokensPerMinute, interval: 'minute' }); +//max 10 addresses per request because of URI size limit, pro is max 180 because of URI limit +const addressChunkSize = env.COINGECKO_API_KEY ? 180 : 20; export class CoingeckoService { private readonly baseUrl: string; @@ -104,10 +106,10 @@ export class CoingeckoService { * Rate limit for the CoinGecko API is 10 calls each second per IP address. */ public async getTokenPrices(addresses: string[]): Promise { - //max 180 addresses per request because of URI size limit - const addressesPerRequest = 180; + const addressesPerRequest = addressChunkSize; try { - if (addresses.length / addressesPerRequest > 10) throw new Error('Too many requests for rate limit.'); + // if (addresses.length / addressesPerRequest > tokensPerMinute) + // throw new Error('Too many requests for rate limit.'); const tokenDefinitions = await tokenService.getTokenDefinitions([networkContext.chain]); const mapped = addresses.map((address) => this.getMappedTokenDetails(address, tokenDefinitions)); diff --git a/modules/beethoven/loadRestRoutes.ts b/modules/common/loadRestRoutes.ts similarity index 52% rename from modules/beethoven/loadRestRoutes.ts rename to modules/common/loadRestRoutes.ts index 999c068ba..fb0f89769 100644 --- a/modules/beethoven/loadRestRoutes.ts +++ b/modules/common/loadRestRoutes.ts @@ -2,7 +2,7 @@ import { Express } from 'express'; import { beetsGetCirculatingSupply } from '../beets/lib/beets'; import { tokenService } from '../token/token.service'; -export function loadRestRoutesBeethoven(app: Express) { +export function loadRestRoutes(app: Express) { app.use('/health', (req, res) => res.sendStatus(200)); app.use('/circulating_supply', (req, res) => { beetsGetCirculatingSupply().then((result) => { @@ -10,11 +10,11 @@ export function loadRestRoutesBeethoven(app: Express) { }); }); - app.use('/late-quartet', async (req, res) => { - const tokenPrices = await tokenService.getTokenPrices(); + // app.use('/late-quartet', async (req, res) => { + // const tokenPrices = await tokenService.getTokenPrices(); - res.send({ - bptPrice: tokenService.getPriceForToken(tokenPrices, '0xf3a602d30dcb723a74a0198313a7551feaca7dac'), - }); - }); + // res.send({ + // bptPrice: tokenService.getPriceForToken(tokenPrices, '0xf3a602d30dcb723a74a0198313a7551feaca7dac'), + // }); + // }); } diff --git a/modules/pool/abi/FxPool.json b/modules/pool/abi/FxPool.json new file mode 100644 index 000000000..75eb2ed77 --- /dev/null +++ b/modules/pool/abi/FxPool.json @@ -0,0 +1,553 @@ +[ + { + "inputs": [ + { "internalType": "address[]", "name": "_assetsToRegister", "type": "address[]" }, + { "internalType": "contract IVault", "name": "vault", "type": "address" }, + { "internalType": "uint256", "name": "_protocolPercentFee", "type": "uint256" }, + { "internalType": "string", "name": "_name", "type": "string" }, + { "internalType": "string", "name": "_symbol", "type": "string" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "numeraire", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "reserve", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "weight", "type": "uint256" } + ], + "name": "AssetIncluded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "derivative", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "numeraire", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "reserve", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "assimilator", "type": "address" } + ], + "name": "AssimilatorIncluded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "newCollector", "type": "address" }], + "name": "ChangeCollectorAddress", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "bool", "name": "isEmergency", "type": "bool" }], + "name": "EmergencyAlarm", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "bytes32", "name": "poolId", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "lptAmountBurned", "type": "uint256" }, + { "indexed": false, "internalType": "uint256[]", "name": "amountsWithdrawn", "type": "uint256[]" } + ], + "name": "EmergencyWithdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "feesCollected", "type": "uint256" }], + "name": "FeesAccrued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "recipient", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "feesCollected", "type": "uint256" } + ], + "name": "FeesCollected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "bytes32", "name": "poolId", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "lptAmountBurned", "type": "uint256" }, + { "indexed": false, "internalType": "uint256[]", "name": "amountsWithdrawn", "type": "uint256[]" } + ], + "name": "OnExitPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "bytes32", "name": "poolId", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "lptAmountMinted", "type": "uint256" }, + { "indexed": false, "internalType": "uint256[]", "name": "amountsDeposited", "type": "uint256[]" } + ], + "name": "OnJoinPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "alpha", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "beta", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "delta", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "epsilon", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "lambda", "type": "uint256" } + ], + "name": "ParametersSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "account", "type": "address" }], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "updater", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "newProtocolPercentage", "type": "uint256" } + ], + "name": "ProtocolFeeShareUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "trader", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "origin", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "target", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "originAmount", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "targetAmount", "type": "uint256" } + ], + "name": "Trade", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "address", "name": "account", "type": "address" }], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_derivative", "type": "address" }], + "name": "assimilator", + "outputs": [{ "internalType": "address", "name": "assimilator_", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "collectorAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "curve", + "outputs": [ + { "internalType": "int128", "name": "alpha", "type": "int128" }, + { "internalType": "int128", "name": "beta", "type": "int128" }, + { "internalType": "int128", "name": "delta", "type": "int128" }, + { "internalType": "int128", "name": "epsilon", "type": "int128" }, + { "internalType": "int128", "name": "lambda", "type": "int128" }, + { "internalType": "uint256", "name": "cap", "type": "uint256" }, + { "internalType": "contract IVault", "name": "vault", "type": "address" }, + { "internalType": "address", "name": "fxPoolAddress", "type": "address" }, + { "internalType": "bytes32", "name": "poolId", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "derivatives", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "emergency", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPoolId", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVault", + "outputs": [{ "internalType": "contract IVault", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "_assets", "type": "address[]" }, + { "internalType": "uint256[]", "name": "_assetWeights", "type": "uint256[]" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { "internalType": "uint256", "name": "total_", "type": "uint256" }, + { "internalType": "uint256[]", "name": "individual_", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "numeraires", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "poolId", "type": "bytes32" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "bytes", "name": "userData", "type": "bytes" } + ], + "name": "onExitPool", + "outputs": [ + { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" }, + { "internalType": "uint256[]", "name": "dueProtocolFeeAmounts", "type": "uint256[]" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "poolId", "type": "bytes32" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256[]", "name": "", "type": "uint256[]" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "bytes", "name": "userData", "type": "bytes" } + ], + "name": "onJoinPool", + "outputs": [ + { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, + { "internalType": "uint256[]", "name": "dueProtocolFeeAmounts", "type": "uint256[]" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "enum IVault.SwapKind", "name": "kind", "type": "uint8" }, + { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, + { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "bytes32", "name": "poolId", "type": "bytes32" }, + { "internalType": "uint256", "name": "lastChangeBlock", "type": "uint256" }, + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "bytes", "name": "userData", "type": "bytes" } + ], + "internalType": "struct IPoolSwapStructs.SwapRequest", + "name": "swapRequest", + "type": "tuple" + }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "onSwap", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "protocolPercentFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "reserves", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_cap", "type": "uint256" }], + "name": "setCap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_collectorAddress", "type": "address" }], + "name": "setCollectorAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bool", "name": "_emergency", "type": "bool" }], + "name": "setEmergency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_alpha", "type": "uint256" }, + { "internalType": "uint256", "name": "_beta", "type": "uint256" }, + { "internalType": "uint256", "name": "_feeAtHalt", "type": "uint256" }, + { "internalType": "uint256", "name": "_epsilon", "type": "uint256" }, + { "internalType": "uint256", "name": "_lambda", "type": "uint256" } + ], + "name": "setParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [], "name": "setPaused", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [{ "internalType": "uint256", "name": "_protocolPercentFee", "type": "uint256" }], + "name": "setProtocolPercentFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalUnclaimedFeesInNumeraire", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "totalDepositNumeraire", "type": "uint256" }], + "name": "viewDeposit", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "viewParameters", + "outputs": [ + { "internalType": "uint256", "name": "alpha_", "type": "uint256" }, + { "internalType": "uint256", "name": "beta_", "type": "uint256" }, + { "internalType": "uint256", "name": "delta_", "type": "uint256" }, + { "internalType": "uint256", "name": "epsilon_", "type": "uint256" }, + { "internalType": "uint256", "name": "lambda_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_curvesToBurn", "type": "uint256" }], + "name": "viewWithdraw", + "outputs": [{ "internalType": "uint256[]", "name": "", "type": "uint256[]" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/modules/pool/lib/pool-creator.service.ts b/modules/pool/lib/pool-creator.service.ts index fb22dc4fb..2b632c652 100644 --- a/modules/pool/lib/pool-creator.service.ts +++ b/modules/pool/lib/pool-creator.service.ts @@ -15,8 +15,12 @@ export class PoolCreatorService { return networkContext.services.balancerSubgraphService; } + private get chain() { + return networkContext.chain + } + public async syncAllPoolsFromSubgraph(blockNumber: number): Promise { - const existingPools = await prisma.prismaPool.findMany({ where: { chain: networkContext.chain } }); + const existingPools = await prisma.prismaPool.findMany({ where: { chain: this.chain } }); const subgraphPools = await this.balancerSubgraphService.getAllPools({}, false); const sortedSubgraphPools = this.sortSubgraphPools(subgraphPools); @@ -39,11 +43,11 @@ export class PoolCreatorService { } public async syncNewPoolsFromSubgraph(blockNumber: number): Promise { - const existingPools = await prisma.prismaPool.findMany({ where: { chain: networkContext.chain } }); + const existingPools = await prisma.prismaPool.findMany({ where: { chain: this.chain } }); const latest = await prisma.prismaPool.findFirst({ orderBy: { createTime: 'desc' }, select: { createTime: true }, - where: { chain: networkContext.chain }, + where: { chain: this.chain }, }); const subgraphPools = await this.balancerSubgraphService.getAllPools( @@ -93,7 +97,7 @@ export class PoolCreatorService { if (nestedPool) { await prisma.prismaPoolToken.update({ - where: { id_chain: { id: token.id, chain: networkContext.chain } }, + where: { id_chain: { id: token.id, chain: this.chain } }, data: { nestedPoolId: nestedPool.id }, }); } @@ -106,12 +110,12 @@ export class PoolCreatorService { let operations: any[] = []; const pools = await prisma.prismaPool.findMany({ ...prismaPoolWithExpandedNesting, - where: { chain: networkContext.chain }, + where: { chain: this.chain }, }); //clear any existing await prisma.prismaPoolExpandedTokens.updateMany({ - where: { chain: networkContext.chain }, + where: { chain: this.chain }, data: { nestedPoolId: null }, }); @@ -141,7 +145,7 @@ export class PoolCreatorService { tokenAddress_poolId_chain: { tokenAddress: token.address, poolId: pool.id, - chain: networkContext.chain, + chain: this.chain, }, }, data: { nestedPoolId: token.nestedPoolId }, @@ -159,7 +163,7 @@ export class PoolCreatorService { const allNestedTypePools = await prisma.prismaPool.findMany({ where: { - chain: networkContext.chain, + chain: this.chain, type: { in: [PrismaPoolType.LINEAR, PrismaPoolType.PHANTOM_STABLE] }, }, select: { id: true, address: true }, @@ -173,14 +177,14 @@ export class PoolCreatorService { symbol: token.symbol, name: token.name, decimals: token.decimals, - chain: networkContext.chain, + chain: this.chain, })), { address: pool.address, symbol: pool.symbol || '', name: pool.name || '', decimals: 18, - chain: networkContext.chain, + chain: this.chain, }, ], }); @@ -188,7 +192,7 @@ export class PoolCreatorService { await prisma.prismaPool.create({ data: { id: pool.id, - chain: networkContext.chain, + chain: this.chain, createTime: pool.createTime, address: pool.address, symbol: pool.symbol || '', @@ -286,7 +290,7 @@ export class PoolCreatorService { await prisma.prismaPoolTokenDynamicData.createMany({ data: poolTokens.map((token) => ({ id: token.id, - chain: networkContext.chain, + chain: this.chain, poolTokenId: token.id, blockNumber, priceRate: token.priceRate || '1.0', @@ -303,7 +307,7 @@ export class PoolCreatorService { public async createAllTokensRelationshipForPool(poolId: string): Promise { const pool = await prisma.prismaPool.findUnique({ ...prismaPoolWithExpandedNesting, - where: { id_chain: { id: poolId, chain: networkContext.chain } }, + where: { id_chain: { id: poolId, chain: this.chain } }, }); if (!pool) { @@ -330,7 +334,7 @@ export class PoolCreatorService { skipDuplicates: true, data: allTokens.map((token) => ({ poolId, - chain: networkContext.chain, + chain: this.chain, tokenAddress: token.address, nestedPoolId: token.nestedPoolId || null, })), @@ -350,7 +354,7 @@ export class PoolCreatorService { const token = poolTokens[i]; await prisma.prismaPoolToken.update({ - where: { id_chain: { id: token.id, chain: networkContext.chain } }, + where: { id_chain: { id: token.id, chain: this.chain } }, data: { index: token.index || subgraphPool.tokensList.findIndex((address) => address === token.address), }, diff --git a/modules/pool/lib/pool-gql-loader.service.ts b/modules/pool/lib/pool-gql-loader.service.ts index 4d965269e..1e90acefc 100644 --- a/modules/pool/lib/pool-gql-loader.service.ts +++ b/modules/pool/lib/pool-gql-loader.service.ts @@ -11,7 +11,6 @@ import { import { GqlBalancePoolAprItem, GqlBalancePoolAprSubItem, - GqlChain, GqlPoolDynamicData, GqlPoolFeaturedPoolGroup, GqlPoolInvestConfig, @@ -27,6 +26,7 @@ import { GqlPoolTokenExpanded, GqlPoolTokenUnion, GqlPoolUnion, + GqlPoolUserBalance, GqlPoolWithdrawConfig, GqlPoolWithdrawOption, QueryPoolGetPoolsArgs, @@ -34,11 +34,13 @@ import { import { isSameAddress } from '@balancer-labs/sdk'; import _ from 'lodash'; import { prisma } from '../../../prisma/prisma-client'; -import { Chain, Prisma, PrismaPoolAprType } from '@prisma/client'; +import { Chain, Prisma, PrismaPoolAprType, PrismaUserStakedBalance, PrismaUserWalletBalance } from '@prisma/client'; import { isWeightedPoolV2 } from './pool-utils'; import { oldBnum } from '../../big-number/old-big-number'; import { networkContext } from '../../network/network-context.service'; import { fixedNumber } from '../../view-helpers/fixed-number'; +import { parseUnits } from 'ethers/lib/utils'; +import { formatFixed } from '@ethersproject/bignumber'; export class PoolGqlLoaderService { public async getPool(id: string, chain: Chain): Promise { @@ -59,6 +61,39 @@ export class PoolGqlLoaderService { } public async getPools(args: QueryPoolGetPoolsArgs): Promise { + // only include wallet and staked balances if the query requests it + // this makes sure that we don't load ALL user balances when we don't filter on userAddress + if (args.where?.userAddress) { + const pools = await prisma.prismaPool.findMany({ + ...this.mapQueryArgsToPoolQuery(args), + include: { + ...prismaPoolMinimal.include, + userWalletBalances: { + where: { + userAddress: { + equals: args.where?.userAddress, + mode: 'insensitive' as const, + }, + balanceNum: { gt: 0 }, + }, + }, + userStakedBalances: { + where: { + userAddress: { + equals: args.where?.userAddress, + mode: 'insensitive' as const, + }, + balanceNum: { gt: 0 }, + }, + }, + }, + }); + + return pools.map((pool) => + this.mapToMinimalGqlPool(pool, pool.userWalletBalances, pool.userStakedBalances), + ); + } + const pools = await prisma.prismaPool.findMany({ ...this.mapQueryArgsToPoolQuery(args), include: prismaPoolMinimal.include, @@ -77,7 +112,11 @@ export class PoolGqlLoaderService { return pools.map((pool) => this.mapPoolToGqlPool(pool)) as GqlPoolLinear[]; } - public mapToMinimalGqlPool(pool: PrismaPoolMinimal): GqlPoolMinimal { + public mapToMinimalGqlPool( + pool: PrismaPoolMinimal, + userWalletbalances: PrismaUserWalletBalance[] = [], + userStakedBalances: PrismaUserStakedBalance[] = [], + ): GqlPoolMinimal { return { ...pool, decimals: 18, @@ -85,6 +124,7 @@ export class PoolGqlLoaderService { allTokens: this.mapAllTokens(pool), displayTokens: this.mapDisplayTokens(pool), staking: this.getStakingData(pool), + userBalance: this.getUserBalance(userWalletbalances, userStakedBalances), }; } @@ -357,9 +397,7 @@ export class PoolGqlLoaderService { investConfig: this.getPoolInvestConfig(pool), withdrawConfig: this.getPoolWithdrawConfig(pool), nestingType: this.getPoolNestingType(pool), - tokens: pool.tokens - .filter((token) => token.address !== pool.address) - .map((token) => this.mapPoolTokenToGqlUnion(token)), + tokens: pool.tokens.map((token) => this.mapPoolTokenToGqlUnion(token)), allTokens: this.mapAllTokens(pool), displayTokens: this.mapDisplayTokens(pool), }; @@ -572,6 +610,20 @@ export class PoolGqlLoaderService { }; } + private getUserBalance( + userWalletBalances: PrismaUserWalletBalance[], + userStakedBalances: PrismaUserStakedBalance[], + ): GqlPoolUserBalance { + const stakedNum = parseUnits(userWalletBalances.at(0)?.balance || '0', 18); + const walletNum = parseUnits(userStakedBalances.at(0)?.balance || '0', 18); + + return { + walletBalance: userWalletBalances.at(0)?.balance || '0', + stakedBalance: userStakedBalances.at(0)?.balance || '0', + totalBalance: formatFixed(stakedNum.add(walletNum), 18), + }; + } + private getPoolDynamicData(pool: PrismaPoolMinimal): GqlPoolDynamicData { const { fees24h, diff --git a/modules/pool/lib/pool-on-chain-data.service.ts b/modules/pool/lib/pool-on-chain-data.service.ts index 66c658053..72bef5972 100644 --- a/modules/pool/lib/pool-on-chain-data.service.ts +++ b/modules/pool/lib/pool-on-chain-data.service.ts @@ -1,61 +1,14 @@ -// TODO: let's make it simpler, it's already refactored in the SDK -import ElementPoolAbi from '../abi/ConvergentCurvePool.json'; -import LinearPoolAbi from '../abi/LinearPool.json'; -import LiquidityBootstrappingPoolAbi from '../abi/LiquidityBootstrappingPool.json'; -import ComposableStablePoolAbi from '../abi/ComposableStablePool.json'; -import GyroEV2Abi from '../abi/GyroEV2.json'; -import VaultAbi from '../abi/Vault.json'; -import aTokenRateProvider from '../abi/StaticATokenRateProvider.json'; -import WeightedPoolAbi from '../abi/WeightedPool.json'; -import StablePoolAbi from '../abi/StablePool.json'; -import MetaStablePoolAbi from '../abi/MetaStablePool.json'; -import StablePhantomPoolAbi from '../abi/StablePhantomPool.json'; -import { BigNumber } from 'ethers'; import { formatFixed } from '@ethersproject/bignumber'; -import { PrismaPoolType } from '@prisma/client'; +import { Chain, PrismaPoolType } from '@prisma/client'; import { isSameAddress } from '@balancer-labs/sdk'; import { prisma } from '../../../prisma/prisma-client'; -import { isComposableStablePool, isGyroEV2, isGyroPool, isStablePool, isWeightedPoolV2 } from './pool-utils'; +import { isComposableStablePool, isStablePool } from './pool-utils'; import { TokenService } from '../../token/token.service'; -import { networkContext } from '../../network/network-context.service'; import { prismaBulkExecuteOperations } from '../../../prisma/prisma-util'; -import { Multicaller3 } from '../../web3/multicaller3'; -import { formatBytes32String } from '@ethersproject/strings'; -import { keccak256 } from '@ethersproject/solidity'; -import { defaultAbiCoder } from '@ethersproject/abi'; -import GyroConfigAbi from '../abi/GyroConfig.json'; - -interface PoolStatusResult { - [key: string]: { - isPaused: boolean; - inRecoveryMode: boolean; - }; -} - -interface MulticallPoolStateExecuteResult { - inRecoveryMode: boolean; - pausedState: { - paused: boolean; - }; -} -interface MulticallExecuteResult { - amp?: string[] | undefined; - swapFee: string | undefined; - totalSupply: string | undefined; - weights?: string[] | undefined; - targets?: string[] | undefined; - poolTokens: { - tokens: string[]; - balances: string[]; - }; - wrappedTokenRate?: BigNumber | undefined; - rate?: BigNumber | undefined; - swapEnabled?: boolean | undefined; - protocolFeePercentageCache?: number | undefined; - tokenRates?: BigNumber[] | undefined; - metaPriceRateCache?: [BigNumber, BigNumber, BigNumber][] | undefined; - gyroProtocolFee?: BigNumber | undefined; -} +import { fetchOnChainPoolState } from './pool-onchain-state'; +import { fetchOnChainPoolData } from './pool-onchain-data'; +import { fetchOnChainGyroFees } from './pool-onchain-gyro-fee'; +import { networkContext } from '../../network/network-context.service'; const SUPPORTED_POOL_TYPES: PrismaPoolType[] = [ 'WEIGHTED', @@ -72,17 +25,25 @@ const SUPPORTED_POOL_TYPES: PrismaPoolType[] = [ ]; export class PoolOnChainDataService { - gyroDefaultFee = 0; - gyroPoolTypeFee = 0; constructor(private readonly tokenService: TokenService) {} + private get options() { + return { + chain: networkContext.chain, + vaultAddress: networkContext.data.balancer.vault, + yieldProtocolFeePercentage: networkContext.data.balancer.yieldProtocolFeePercentage, + gyroConfig: networkContext.data.gyro?.config, + composableStableFactories: networkContext.data.balancer.composableStablePoolFactories, + }; + } + public async updateOnChainStatus(poolIds: string[]): Promise { if (poolIds.length === 0) return; const filteredPools = await prisma.prismaPool.findMany({ where: { id: { in: poolIds }, - chain: networkContext.chain, + chain: this.options.chain, type: { in: SUPPORTED_POOL_TYPES }, }, include: { @@ -90,40 +51,18 @@ export class PoolOnChainDataService { }, }); - const multicall = new Multicaller3(ComposableStablePoolAbi); - - filteredPools.forEach((pool) => { - if (!(pool.type === 'ELEMENT')) { - multicall.call(`${pool.id}.pausedState`, pool.address, 'getPausedState'); - } - if ( - pool.type !== 'LIQUIDITY_BOOTSTRAPPING' && // exclude all LBP - pool.type !== 'META_STABLE' && // exclude meta stable - pool.type !== 'ELEMENT' // exclude element - ) { - multicall.call(`${pool.id}.inRecoveryMode`, pool.address, 'inRecoveryMode'); - } - }); - - const multicallResult = (await multicall.execute()) as Record; - - const poolStatusResults: PoolStatusResult = {}; + const state = await fetchOnChainPoolState(filteredPools, 1024); const operations = []; for (const pool of filteredPools) { - if (pool.dynamicData) { - let isPaused = false; - let isInRecoveryMode = false; + if (!state[pool.id]) continue; // Some pool types are filtered out in the state fetching function - if (multicallResult[pool.id] && multicallResult[pool.id].inRecoveryMode) { - isInRecoveryMode = multicallResult[pool.id].inRecoveryMode; - } - if (multicallResult[pool.id] && multicallResult[pool.id].pausedState) { - isPaused = multicallResult[pool.id].pausedState.paused; - } + const { isPaused, isInRecoveryMode } = state[pool.id]; + const data = pool.dynamicData; + if (data && (data.isPaused !== isPaused || data.isInRecoveryMode !== isInRecoveryMode)) { operations.push( prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, + where: { id_chain: { id: pool.id, chain: this.options.chain } }, data: { isPaused, isInRecoveryMode, @@ -143,7 +82,7 @@ export class PoolOnChainDataService { const filteredPools = await prisma.prismaPool.findMany({ where: { id: { in: poolIds }, - chain: networkContext.chain, + chain: this.options.chain, type: { in: SUPPORTED_POOL_TYPES }, }, include: { @@ -155,195 +94,51 @@ export class PoolOnChainDataService { }, }); - const tokenPrices = await this.tokenService.getTokenPrices(); + const gyroPools = filteredPools.filter((pool) => pool.type.includes('GYRO')); + const poolsWithComposableStableType = filteredPools.map((pool) => ({ + ...pool, + type: (isComposableStablePool(pool) ? 'COMPOSABLE_STABLE' : pool.type) as + | PrismaPoolType + | 'COMPOSABLE_STABLE', + })); - const abis: any = Object.values( - // Remove duplicate entries using their names - Object.fromEntries( - [ - ...ElementPoolAbi, - ...LinearPoolAbi, - ...LiquidityBootstrappingPoolAbi, - ...ComposableStablePoolAbi, - ...GyroEV2Abi, - ...GyroConfigAbi, - ...VaultAbi, - ...aTokenRateProvider, - ...WeightedPoolAbi, - ...StablePoolAbi, - ...StablePhantomPoolAbi, - ...MetaStablePoolAbi, - ...ComposableStablePoolAbi, - //...WeightedPoolV2Abi, - ].map((row) => [row.name, row]), - ), + const tokenPrices = await this.tokenService.getTokenPrices(); + const onchainResults = await fetchOnChainPoolData( + poolsWithComposableStableType, + this.options.vaultAddress, + 1024, ); + const gyroFees = await (this.options.gyroConfig + ? fetchOnChainGyroFees(gyroPools, this.options.gyroConfig, 1024) + : Promise.resolve({} as { [address: string]: string })); - const multiPool = new Multicaller3(abis); - - // Adding Gyro default fee calls - const feeKey = formatBytes32String('PROTOCOL_SWAP_FEE_PERC'); - if (networkContext.data.gyro?.config) { - const eclpKey = keccak256( - ['bytes'], - [ - defaultAbiCoder.encode( - ['bytes32', 'bytes32'], - [feeKey, formatBytes32String('ECLP')] - ), - ] - ); - multiPool.call('gyro.default', networkContext.data.gyro.config, 'getUint', [feeKey]); - multiPool.call('gyro.eclp', networkContext.data.gyro.config, 'getUint', [eclpKey]); - } - - filteredPools.forEach((pool) => { - if (!SUPPORTED_POOL_TYPES.includes(pool.type || '')) { - console.error(`Unknown pool type: ${pool.type} ${pool.id}`); - return; - } - - // get per pool yield protocol fee (type 2) - if ( - networkContext.data.balancer.factoriesWithpoolSpecificProtocolFeePercentagesProvider?.includes( - pool.factory || '', - ) - ) { - multiPool.call( - `${pool.id}.protocolYieldFeePercentageCache`, - pool.address, - 'getProtocolFeePercentageCache', - [2], - ); - } - - multiPool.call( - `${pool.id}.poolTokens`, - networkContext.data.balancer.vault, - 'getPoolTokens', - [pool.id], - false, - ); - - if (pool.type === 'WEIGHTED' || pool.type === 'LIQUIDITY_BOOTSTRAPPING' || pool.type === 'INVESTMENT') { - multiPool.call(`${pool.id}.weights`, pool.address, 'getNormalizedWeights'); - multiPool.call(`${pool.id}.swapFee`, pool.address, 'getSwapFeePercentage'); - } else if (isStablePool(pool.type)) { - // MetaStable & StablePhantom is the same as Stable for multicall purposes - multiPool.call(`${pool.id}.amp`, pool.address, 'getAmplificationParameter'); - multiPool.call(`${pool.id}.swapFee`, pool.address, 'getSwapFeePercentage'); - } else if (pool.type === 'ELEMENT') { - multiPool.call(`${pool.id}.swapFee`, pool.address, 'percentFee'); - } else if (pool.type === 'LINEAR') { - multiPool.call(`${pool.id}.swapFee`, pool.address, 'getSwapFeePercentage'); - multiPool.call(`${pool.id}.targets`, pool.address, 'getTargets'); - multiPool.call(`${pool.id}.rate`, pool.address, 'getRate'); - multiPool.call(`${pool.id}.wrappedTokenRate`, pool.address, 'getWrappedTokenRate'); - } else if (isGyroPool(pool)) { - multiPool.call(`${pool.id}.swapFee`, pool.address, 'getSwapFeePercentage'); - multiPool.call(`${pool.id}.rate`, pool.address, 'getRate'); - - // Get fee from Gyro config pool - if (networkContext.data.gyro?.config) { - const poolFeeKey = keccak256( - ['bytes'], - [ - defaultAbiCoder.encode( - ['bytes32', 'uint256'], - [feeKey, pool.address] - ), - ] - ); - multiPool.call(`${pool.id}.gyroProtocolFee`, networkContext.data.gyro.config, 'getUint', [poolFeeKey]); - } - } - - if (pool.type === 'LIQUIDITY_BOOTSTRAPPING' || pool.type === 'INVESTMENT') { - multiPool.call(`${pool.id}.swapEnabled`, pool.address, 'getSwapEnabled'); - } - - if (pool.type === 'META_STABLE') { - multiPool.call(`${pool.id}.rate`, pool.address, 'getRate'); - - const tokenAddresses = pool.tokens.map((token) => token.address); - - tokenAddresses.forEach((token, i) => { - multiPool.call(`${pool.id}.metaPriceRateCache[${i}]`, pool.address, 'getPriceRateCache', [token]); - }); - } - - if (isComposableStablePool(pool) || isWeightedPoolV2(pool)) { - multiPool.call(`${pool.id}.totalSupply`, pool.address, 'getActualSupply'); - } else if (pool.type === 'LINEAR' || pool.type === 'PHANTOM_STABLE') { - // the old phantom stable and linear pool does not have this and expose the actual supply as virtualSupply - multiPool.call(`${pool.id}.totalSupply`, pool.address, 'getVirtualSupply'); - } else { - //default to totalSupply for any other pool type - multiPool.call(`${pool.id}.totalSupply`, pool.address, 'totalSupply'); - } - - if (pool.type === 'PHANTOM_STABLE') { - //we retrieve token rates for phantom stable and composable stable pools - const tokenAddresses = pool.tokens.map((token) => token.address); - - tokenAddresses.forEach((token, i) => { - multiPool.call(`${pool.id}.tokenRates[${i}]`, pool.address, 'getTokenRate', [token]); - }); - } - - // gyro pool returns uint[] for rates - if (isGyroEV2(pool)) { - multiPool.call(`${pool.id}.tokenRates`, pool.address, 'getTokenRates'); - } - }); - - let poolsOnChainData = {} as Record; - let gyroDefaults = { - default: 0, - eclp: 0, - } - - try { - const { gyro, ..._poolsOnChainData } = (await multiPool.execute()); - poolsOnChainData = _poolsOnChainData; - gyroDefaults = gyro; - } catch (err: any) { - console.error(err); - throw `Issue with multicall execution. ${err}`; - } - - const poolsOnChainDataArray = Object.entries(poolsOnChainData); - - for (let index = 0; index < poolsOnChainDataArray.length; index++) { - const [poolId, onchainData] = poolsOnChainDataArray[index]; - const pool = filteredPools.find((pool) => pool.id === poolId)!; - const { poolTokens } = onchainData; + const operations = []; + for (const pool of filteredPools) { + const onchainData = onchainResults[pool.id]; + const { amp, poolTokens } = onchainData; try { if (isStablePool(pool.type)) { - if (!onchainData.amp) { - console.log('onchain data', onchainData); - console.error(`Stable Pool Missing Amp: ${poolId}`); + if (!amp) { + console.error(`Stable Pool Missing Amp: ${pool.id}`); continue; } - // Need to scale amp by precision to match expected Subgraph scale - // amp is stored with 3 decimals of precision - const amp = formatFixed(onchainData.amp[0], 3); - //only update if amp has changed if (!pool.stableDynamicData || pool.stableDynamicData.amp !== amp) { - await prisma.prismaPoolStableDynamicData.upsert({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, - create: { id: pool.id, chain: networkContext.chain, poolId: pool.id, amp, blockNumber }, - update: { amp, blockNumber }, - }); + operations.push( + prisma.prismaPoolStableDynamicData.upsert({ + where: { id_chain: { id: pool.id, chain: this.options.chain } }, + create: { id: pool.id, chain: this.options.chain, poolId: pool.id, amp, blockNumber }, + update: { amp, blockNumber }, + }), + ); } } if (pool.type === 'LINEAR') { if (!onchainData.targets) { - console.error(`Linear Pool Missing Targets: ${poolId}`); + console.error(`Linear Pool Missing Targets: ${pool.id}`); continue; } else { const lowerTarget = formatFixed(onchainData.targets[0], 18); @@ -354,44 +149,34 @@ export class PoolOnChainDataService { pool.linearDynamicData.lowerTarget !== lowerTarget || pool.linearDynamicData.upperTarget !== upperTarget ) { - await prisma.prismaPoolLinearDynamicData.upsert({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, - create: { - id: pool.id, - chain: networkContext.chain, - poolId: pool.id, - upperTarget, - lowerTarget, - blockNumber, - }, - update: { upperTarget, lowerTarget, blockNumber }, - }); + operations.push( + prisma.prismaPoolLinearDynamicData.upsert({ + where: { id_chain: { id: pool.id, chain: this.options.chain } }, + create: { + id: pool.id, + chain: this.options.chain, + poolId: pool.id, + upperTarget, + lowerTarget, + blockNumber, + }, + update: { upperTarget, lowerTarget, blockNumber }, + }), + ); } } } - const swapFee = formatFixed(onchainData.swapFee || '0', 18); - const totalShares = formatFixed(onchainData.totalSupply || '0', 18); + const { swapFee, totalShares } = onchainData; const swapEnabled = typeof onchainData.swapEnabled !== 'undefined' ? onchainData.swapEnabled : pool.dynamicData?.swapEnabled; - let yieldProtocolFeePercentage = - typeof onchainData.protocolFeePercentageCache !== 'undefined' - ? formatFixed(onchainData.protocolFeePercentageCache, 18) - : `${networkContext.data.balancer.yieldProtocolFeePercentage}`; - - // Setting Gyro protocol fee - if (onchainData.gyroProtocolFee) { - yieldProtocolFeePercentage = formatFixed(onchainData.gyroProtocolFee, 18); - } else if (pool.type.includes('GYRO')) { - if (pool.type === 'GYROE' && gyroDefaults.eclp) { - yieldProtocolFeePercentage = formatFixed(gyroDefaults.eclp, 18) || '0'; - } else { - yieldProtocolFeePercentage = formatFixed(gyroDefaults.default, 18) || '0'; - } - } + const yieldProtocolFeePercentage = + gyroFees[pool.id] || + onchainData.protocolYieldFeePercentageCache || + String(this.options.yieldProtocolFeePercentage); if ( pool.dynamicData && @@ -400,17 +185,19 @@ export class PoolOnChainDataService { pool.dynamicData.swapEnabled !== swapEnabled || pool.dynamicData.protocolYieldFee !== yieldProtocolFeePercentage) ) { - await prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, - data: { - swapFee, - totalShares, - totalSharesNum: parseFloat(totalShares), - swapEnabled: typeof swapEnabled !== 'undefined' ? swapEnabled : true, - protocolYieldFee: yieldProtocolFeePercentage, - blockNumber, - }, - }); + operations.push( + prisma.prismaPoolDynamicData.update({ + where: { id_chain: { id: pool.id, chain: this.options.chain } }, + data: { + swapFee, + totalShares, + totalSharesNum: parseFloat(totalShares), + swapEnabled: typeof swapEnabled !== 'undefined' ? swapEnabled : true, + protocolYieldFee: yieldProtocolFeePercentage, + blockNumber, + }, + }), + ); } for (let i = 0; i < poolTokens.tokens.length; i++) { @@ -418,34 +205,29 @@ export class PoolOnChainDataService { const poolToken = pool.tokens.find((token) => isSameAddress(token.address, tokenAddress)); if (!poolToken) { - throw `Pool Missing Expected Token: ${poolId} ${tokenAddress}`; + throw `Pool Missing Expected Token: ${pool.id} ${tokenAddress}`; } if (poolToken.index !== i) { throw `Pooltoken index mismatch! "poolToken.index": ${poolToken.index} vs "i": ${i} on pool ${pool.id}`; } - const balance = formatFixed(poolTokens.balances[i], poolToken.token.decimals); - const weight = onchainData.weights ? formatFixed(onchainData.weights[i], 18) : null; + const balance = poolTokens.balances[i]; + const weight = onchainData.weights ? onchainData.weights[i] : null; // set token price rate for various rate types // top level token rates, e.g. LSTs in pools - let priceRate = onchainData.tokenRates ? formatFixed(onchainData.tokenRates[i], 18) : '1.0'; - - // metastable pools - if (onchainData.metaPriceRateCache && onchainData.metaPriceRateCache[i][0].gt('0')) { - priceRate = formatFixed(onchainData.metaPriceRateCache[i][0], 18); - } + let priceRate = poolTokens.rates[i] ?? '1.0'; // bpt price rate if (onchainData.rate && isSameAddress(poolToken.address, pool.address)) { - priceRate = formatFixed(onchainData.rate, 18); + priceRate = onchainData.rate; } // linear wrapped token rate if (onchainData.wrappedTokenRate && pool.linearData?.wrappedIndex === poolToken.index) { - priceRate = formatFixed(onchainData.wrappedTokenRate, 18); + priceRate = onchainData.wrappedTokenRate; } if ( @@ -454,39 +236,43 @@ export class PoolOnChainDataService { poolToken.dynamicData.priceRate !== priceRate || poolToken.dynamicData.weight !== weight ) { - await prisma.prismaPoolTokenDynamicData.upsert({ - where: { id_chain: { id: poolToken.id, chain: networkContext.chain } }, - create: { - id: poolToken.id, - chain: networkContext.chain, - poolTokenId: poolToken.id, - blockNumber, - priceRate, - weight, - balance, - balanceUSD: - poolToken.address === pool.address - ? 0 - : this.tokenService.getPriceForToken(tokenPrices, poolToken.address) * - parseFloat(balance), - }, - update: { - blockNumber, - priceRate, - weight, - balance, - balanceUSD: - poolToken.address === pool.address - ? 0 - : this.tokenService.getPriceForToken(tokenPrices, poolToken.address) * - parseFloat(balance), - }, - }); + operations.push( + prisma.prismaPoolTokenDynamicData.upsert({ + where: { id_chain: { id: poolToken.id, chain: this.options.chain } }, + create: { + id: poolToken.id, + chain: this.options.chain, + poolTokenId: poolToken.id, + blockNumber, + priceRate, + weight, + balance, + balanceUSD: + poolToken.address === pool.address + ? 0 + : this.tokenService.getPriceForToken(tokenPrices, poolToken.address) * + parseFloat(balance), + }, + update: { + blockNumber, + priceRate, + weight, + balance, + balanceUSD: + poolToken.address === pool.address + ? 0 + : this.tokenService.getPriceForToken(tokenPrices, poolToken.address) * + parseFloat(balance), + }, + }), + ); } } } catch (e) { console.log('error syncing on chain data', e); } } + + await prismaBulkExecuteOperations(operations, false); } } diff --git a/modules/pool/lib/pool-onchain-data.ts b/modules/pool/lib/pool-onchain-data.ts new file mode 100644 index 000000000..ca333d132 --- /dev/null +++ b/modules/pool/lib/pool-onchain-data.ts @@ -0,0 +1,224 @@ +import { formatEther, formatUnits } from 'ethers/lib/utils'; +import { Multicaller3 } from '../../web3/multicaller3'; +import { PrismaPoolType } from '@prisma/client'; +import { BigNumber, formatFixed } from '@ethersproject/bignumber'; +import ElementPoolAbi from '../abi/ConvergentCurvePool.json'; +import LinearPoolAbi from '../abi/LinearPool.json'; +import LiquidityBootstrappingPoolAbi from '../abi/LiquidityBootstrappingPool.json'; +import ComposableStablePoolAbi from '../abi/ComposableStablePool.json'; +import GyroEV2Abi from '../abi/GyroEV2.json'; +import VaultAbi from '../abi/Vault.json'; +import aTokenRateProvider from '../abi/StaticATokenRateProvider.json'; +import WeightedPoolAbi from '../abi/WeightedPool.json'; +import StablePoolAbi from '../abi/StablePool.json'; +import MetaStablePoolAbi from '../abi/MetaStablePool.json'; +import StablePhantomPoolAbi from '../abi/StablePhantomPool.json'; +import FxPoolAbi from '../abi/FxPool.json'; +import { JsonFragment } from '@ethersproject/abi'; + +interface PoolInput { + id: string; + address: string; + type: PrismaPoolType | 'COMPOSABLE_STABLE'; + tokens: { + address: string; + token: { + decimals: number; + }; + }[]; + version: number; +} + +interface OnchainData { + poolTokens: [string[], BigNumber[]]; + totalSupply: BigNumber; + swapFee: BigNumber; + swapEnabled?: boolean; + protocolYieldFeePercentageCache?: BigNumber; + rate?: BigNumber; + weights?: BigNumber[]; + targets?: [BigNumber, BigNumber]; + wrappedTokenRate?: BigNumber; + amp?: [BigNumber, boolean, BigNumber]; + tokenRates?: [BigNumber, BigNumber]; + tokenRate?: BigNumber[]; + metaPriceRateCache?: [BigNumber, BigNumber, BigNumber][]; +} + +const abi: JsonFragment[] = Object.values( + // Remove duplicate entries using their names + Object.fromEntries( + [ + ...ElementPoolAbi, + ...LinearPoolAbi, + ...LiquidityBootstrappingPoolAbi, + ...ComposableStablePoolAbi, + ...GyroEV2Abi, + ...VaultAbi, + ...aTokenRateProvider, + ...WeightedPoolAbi, + ...StablePoolAbi, + ...StablePhantomPoolAbi, + ...MetaStablePoolAbi, + ...ComposableStablePoolAbi, + ...FxPoolAbi, + //...WeightedPoolV2Abi, + ].map((row) => [row.name, row]), + ), +); + +const getSwapFeeFn = (type: string) => { + if (type === 'ELEMENT') { + return 'percentFee'; + } else if (type === 'FX') { + return 'protocolPercentFee'; + } else { + return 'getSwapFeePercentage'; + } +}; + +const getTotalSupplyFn = (type: PoolInput['type'], version: number) => { + if (['LINEAR', 'PHANTOM_STABLE'].includes(type)) { + return 'getVirtualSupply'; + } else if ( + type === 'COMPOSABLE_STABLE' || + (type === 'WEIGHTED' && version > 1) || + (type === 'GYROE' && version > 1) || + (type === 'UNKNOWN' && version > 1) + ) { + return 'getActualSupply'; + } else { + return 'totalSupply'; + } +}; + +const addDefaultCallsToMulticaller = ( + { id, address, type, version }: PoolInput, + vaultAddress: string, + multicaller: Multicaller3, +) => { + multicaller.call(`${id}.poolTokens`, vaultAddress, 'getPoolTokens', [id]); + multicaller.call(`${id}.totalSupply`, address, getTotalSupplyFn(type, version)); + multicaller.call(`${id}.swapFee`, address, getSwapFeeFn(type)); + multicaller.call(`${id}.rate`, address, 'getRate'); + multicaller.call(`${id}.protocolYieldFeePercentageCache`, address, 'getProtocolFeePercentageCache', [2]); +}; + +const weightedCalls = ({ id, address }: PoolInput, multicaller: Multicaller3) => { + multicaller.call(`${id}.weights`, address, 'getNormalizedWeights'); +}; + +const lbpAndInvestmentCalls = ({ id, address }: PoolInput, multicaller: Multicaller3) => { + multicaller.call(`${id}.weights`, address, 'getNormalizedWeights'); + multicaller.call(`${id}.swapEnabled`, address, 'getSwapEnabled'); +}; + +const linearCalls = ({ id, address }: PoolInput, multicaller: Multicaller3) => { + multicaller.call(`${id}.targets`, address, 'getTargets'); + multicaller.call(`${id}.wrappedTokenRate`, address, 'getWrappedTokenRate'); +}; + +const stableCalls = ({ id, address, tokens }: PoolInput, multicaller: Multicaller3) => { + multicaller.call(`${id}.amp`, address, 'getAmplificationParameter'); + + tokens.forEach(({ address: tokenAddress }, i) => { + multicaller.call(`${id}.tokenRate[${i}]`, address, 'getTokenRate', [tokenAddress]); + }); +}; + +const metaStableCalls = ({ id, address, tokens }: PoolInput, multicaller: Multicaller3) => { + multicaller.call(`${id}.amp`, address, 'getAmplificationParameter'); + + tokens.forEach(({ address: tokenAddress }, i) => { + multicaller.call(`${id}.metaPriceRateCache[${i}]`, address, 'getPriceRateCache', [tokenAddress]); + }); +}; + +const gyroECalls = ({ id, address }: PoolInput, multicaller: Multicaller3) => { + multicaller.call(`${id}.tokenRates`, address, 'getTokenRates'); +}; + +const addPoolTypeSpecificCallsToMulticaller = (type: PoolInput['type'], version = 1) => { + const do_nothing = () => ({}); + switch (type) { + case 'WEIGHTED': + return weightedCalls; + case 'LIQUIDITY_BOOTSTRAPPING': + case 'INVESTMENT': + return lbpAndInvestmentCalls; + case 'STABLE': + case 'PHANTOM_STABLE': + case 'COMPOSABLE_STABLE': + return stableCalls; + case 'META_STABLE': + return metaStableCalls; + case 'GYROE': + if (version === 2) { + return gyroECalls; + } else { + return do_nothing; + } + case 'LINEAR': + return linearCalls; + default: + return do_nothing; + } +}; + +const parse = (result: OnchainData, decimalsLookup: { [address: string]: number }) => ({ + amp: result.amp ? formatFixed(result.amp[0], String(result.amp[2]).length - 1) : undefined, + swapFee: formatEther(result.swapFee ?? '0'), + totalShares: formatEther(result.totalSupply || '0'), + weights: result.weights?.map(formatEther), + targets: result.targets?.map(String), + poolTokens: result.poolTokens + ? { + tokens: result.poolTokens[0].map((token) => token.toLowerCase()), + balances: result.poolTokens[1].map((balance, i) => + formatUnits(balance, decimalsLookup[result.poolTokens[0][i].toLowerCase()]), + ), + rates: result.poolTokens[0].map((_, i) => + result.tokenRate && result.tokenRate[i] + ? formatEther(result.tokenRate[i]) + : result.tokenRates && result.tokenRates[i] + ? formatEther(result.tokenRates[i]) + : result.metaPriceRateCache && result.metaPriceRateCache[i][0].gt(0) + ? formatEther(result.metaPriceRateCache[i][0]) + : undefined, + ), + } + : { tokens: [], balances: [], rates: [] }, + wrappedTokenRate: result.wrappedTokenRate ? formatEther(result.wrappedTokenRate) : '1.0', + rate: result.rate ? formatEther(result.rate) : '1.0', + swapEnabled: result.swapEnabled, + protocolYieldFeePercentageCache: result.protocolYieldFeePercentageCache + ? formatEther(result.protocolYieldFeePercentageCache) + : undefined, +}); + +export const fetchOnChainPoolData = async (pools: PoolInput[], vaultAddress: string, batchSize = 1024) => { + if (pools.length === 0) { + return {}; + } + + const multicaller = new Multicaller3(abi, batchSize); + + pools.forEach((pool) => { + addDefaultCallsToMulticaller(pool, vaultAddress, multicaller); + addPoolTypeSpecificCallsToMulticaller(pool.type, pool.version)(pool, multicaller); + }); + + const results = (await multicaller.execute()) as { + [id: string]: OnchainData; + }; + + const decimalsLookup = Object.fromEntries( + pools.flatMap((pool) => pool.tokens.map(({ address, token }) => [address, token.decimals])), + ); + + const parsed = Object.fromEntries( + Object.entries(results).map(([key, result]) => [key, parse(result, decimalsLookup)]), + ); + + return parsed; +}; diff --git a/modules/pool/lib/pool-onchain-gyro-fee.ts b/modules/pool/lib/pool-onchain-gyro-fee.ts new file mode 100644 index 000000000..aef4b3c3d --- /dev/null +++ b/modules/pool/lib/pool-onchain-gyro-fee.ts @@ -0,0 +1,98 @@ +import { Multicaller3 } from '../../web3/multicaller3'; +import { PrismaPoolType } from '@prisma/client'; +import abi from '../abi/GyroConfig.json'; +import { defaultAbiCoder } from '@ethersproject/abi'; +import { formatBytes32String } from '@ethersproject/strings'; +import { keccak256 } from '@ethersproject/solidity'; +import { formatEther } from 'ethers/lib/utils'; + +interface PoolInput { + id: string; + address: string; + type: PrismaPoolType; + version?: number; +} + +interface OnchainGyroFees { + eclpFee?: string; + twoClpFee?: string; + threeClpFee?: string; + defaultFee?: string; + pools?: { + [id: string]: { + poolFee?: string; + }; + }; +} + +export const fetchOnChainGyroFees = async (pools: PoolInput[], gyroConfigAddress?: string, batchSize = 1024) => { + if (pools.length === 0 || !gyroConfigAddress) { + return {}; + } + + const multicaller = new Multicaller3(abi, batchSize); + + const feeKey = formatBytes32String('PROTOCOL_SWAP_FEE_PERC'); + + const eclpKey = keccak256( + ['bytes'], + [defaultAbiCoder.encode(['bytes32', 'bytes32'], [feeKey, formatBytes32String('ECLP')])], + ); + + const twoClpKey = keccak256( + ['bytes'], + [defaultAbiCoder.encode(['bytes32', 'bytes32'], [feeKey, formatBytes32String('2CLP')])], + ); + + const threeClpKey = keccak256( + ['bytes'], + [defaultAbiCoder.encode(['bytes32', 'bytes32'], [feeKey, formatBytes32String('3CLP')])], + ); + + multicaller.call('defaultFee', gyroConfigAddress, 'getUint', [feeKey]); + multicaller.call('eclpFee', gyroConfigAddress, 'getUint', [eclpKey]); + multicaller.call('twoClpFee', gyroConfigAddress, 'getUint', [twoClpKey]); + multicaller.call('threeClpFee', gyroConfigAddress, 'getUint', [threeClpKey]); + + let poolTypeLookup: { [id: string]: PrismaPoolType } = {}; + pools.forEach(({ id, type, address }) => { + if (type.includes('GYRO')) { + const poolFeeKey = keccak256( + ['bytes'], + [defaultAbiCoder.encode(['bytes32', 'uint256'], [feeKey, address])], + ); + + multicaller.call(`pools.${id}.poolFee`, gyroConfigAddress, 'getUint', [poolFeeKey]); + + poolTypeLookup[id] = type; + } + }); + + const results = (await multicaller.execute()) as OnchainGyroFees; + const defaultFee = results.defaultFee ?? '0'; + const eclpFee = results.eclpFee ?? '0'; + const twoClpFee = results.twoClpFee ?? '0'; + const threeClpFee = results.threeClpFee ?? '0'; + + let parsed: { [address: string]: string } = {}; + if (results.pools) { + parsed = Object.fromEntries( + Object.entries(results.pools).map(([id, { poolFee }]) => [ + id, + formatEther( + poolFee + ? poolFee + : poolTypeLookup[id] == 'GYROE' + ? eclpFee + : poolTypeLookup[id] == 'GYRO' + ? twoClpFee + : poolTypeLookup[id] == 'GYRO3' + ? threeClpFee + : defaultFee, + ), + ]), + ); + } + + return parsed; +}; diff --git a/modules/pool/lib/pool-onchain-state.ts b/modules/pool/lib/pool-onchain-state.ts new file mode 100644 index 000000000..a11928d01 --- /dev/null +++ b/modules/pool/lib/pool-onchain-state.ts @@ -0,0 +1,52 @@ +import { Multicaller3 } from '../../web3/multicaller3'; +import { PrismaPoolType } from '@prisma/client'; +import abi from '../abi/WeightedPoolV2.json'; + +interface PoolInput { + id: string; + address: string; + type: PrismaPoolType; + version?: number; +} + +interface OnchainState { + pausedState?: { + paused: boolean; + }; + inRecoveryMode?: boolean; +} + +const parse = (result: OnchainState) => ({ + isPaused: result.pausedState?.paused ? result.pausedState.paused : false, + isInRecoveryMode: result.inRecoveryMode ? result.inRecoveryMode : false, +}); + +export const fetchOnChainPoolState = async (pools: PoolInput[], batchSize = 1024) => { + if (pools.length === 0) { + return {}; + } + + const multicaller = new Multicaller3(abi, batchSize); + + pools.forEach(({ id, type, address }) => { + // filter certain pool types that don't have pausedState or recovery mode + if (type !== 'ELEMENT') { + multicaller.call(`${id}.pausedState`, address, 'getPausedState'); + } + if ( + type !== 'LIQUIDITY_BOOTSTRAPPING' && // exclude all LBP + type !== 'META_STABLE' && // exclude meta stable + type !== 'ELEMENT' // exclude element + ) { + multicaller.call(`${id}.inRecoveryMode`, address, 'inRecoveryMode'); + } + }); + + const results = (await multicaller.execute()) as { + [id: string]: OnchainState; + }; + + const parsed = Object.fromEntries(Object.entries(results).map(([key, result]) => [key, parse(result)])); + + return parsed; +}; diff --git a/modules/pool/lib/pool-snapshot.service.ts b/modules/pool/lib/pool-snapshot.service.ts index ac54bacc0..8a7e0866d 100644 --- a/modules/pool/lib/pool-snapshot.service.ts +++ b/modules/pool/lib/pool-snapshot.service.ts @@ -33,14 +33,14 @@ export class PoolSnapshotService { const timestamp = this.getTimestampForRange(range); return prisma.prismaPoolSnapshot.findMany({ - where: { poolId, timestamp: { gte: timestamp }, chain: chain }, + where: { poolId, timestamp: { gte: timestamp }, chain }, orderBy: { timestamp: 'asc' }, }); } - public async getSnapshotForPool(poolId: string, timestamp: number) { + public async getSnapshotForPool(poolId: string, timestamp: number, chain: Chain) { return prisma.prismaPoolSnapshot.findUnique({ - where: { id_chain: { id: `${poolId}-${timestamp}`, chain: this.chain } }, + where: { id_chain: { id: `${poolId}-${timestamp}`, chain } }, }); } diff --git a/modules/pool/lib/pool-usd-data.service.ts b/modules/pool/lib/pool-usd-data.service.ts index aafffbf3c..9191a273c 100644 --- a/modules/pool/lib/pool-usd-data.service.ts +++ b/modules/pool/lib/pool-usd-data.service.ts @@ -18,6 +18,10 @@ export class PoolUsdDataService { return networkContext.services.balancerSubgraphService; } + private get chain() { + return networkContext.chain; + } + /** * Liquidity is dependent on token prices, so the values here are constantly in flux. * When updating, the easiest is to update all pools at once. @@ -26,7 +30,7 @@ export class PoolUsdDataService { minShares: number = 0.00000000001, maxShares: number = Number.MAX_SAFE_INTEGER, ) { - const tokenPrices = await this.tokenService.getTokenPrices(); + const tokenPrices = await this.tokenService.getTokenPrices(this.chain); const pools = await prisma.prismaPool.findMany({ include: { dynamicData: true, tokens: { include: { dynamicData: true } } }, where: { @@ -40,7 +44,7 @@ export class PoolUsdDataService { }, ], }, - chain: networkContext.chain, + chain: this.chain, }, }); @@ -66,7 +70,7 @@ export class PoolUsdDataService { tokenId: item.id, poolId: pool.id, poolName: pool.name, - chain: networkContext.chain, + chain: pool.chain, }, }, ); @@ -74,7 +78,7 @@ export class PoolUsdDataService { } updates.push( prisma.prismaPoolTokenDynamicData.update({ - where: { id_chain: { id: item.id, chain: networkContext.chain } }, + where: { id_chain: { id: item.id, chain: pool.chain } }, data: { balanceUSD: item.balanceUSD }, }), ); @@ -86,7 +90,7 @@ export class PoolUsdDataService { tags: { poolId: pool.id, poolName: pool.name, - chain: networkContext.chain, + chain: pool.chain, }, }, ); @@ -95,7 +99,7 @@ export class PoolUsdDataService { updates.push( prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, + where: { id_chain: { id: pool.id, chain: pool.chain } }, data: { totalLiquidity }, }), ); @@ -136,7 +140,7 @@ export class PoolUsdDataService { updates.push( prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, + where: { id_chain: { id: pool.id, chain: this.chain } }, data: { totalLiquidity24hAgo: totalLiquidity, totalShares24hAgo: pool.totalShares }, }), ); @@ -153,7 +157,7 @@ export class PoolUsdDataService { const yesterday = moment().subtract(1, 'day').unix(); const twoDaysAgo = moment().subtract(2, 'day').unix(); const pools = await prisma.prismaPool.findMany({ - where: poolIds ? { id: { in: poolIds }, chain: networkContext.chain } : { chain: networkContext.chain }, + where: poolIds ? { id: { in: poolIds }, chain: this.chain } : { chain: this.chain }, include: { swaps: { where: { timestamp: { gte: twoDaysAgo } } }, dynamicData: true, @@ -182,7 +186,7 @@ export class PoolUsdDataService { ) { operations.push( prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, + where: { id_chain: { id: pool.id, chain: pool.chain } }, data: { volume24h, fees24h, volume48h, fees48h }, }), ); @@ -198,7 +202,7 @@ export class PoolUsdDataService { */ public async updateYieldCaptureForAllPools() { const pools = await prisma.prismaPool.findMany({ - where: { chain: networkContext.chain }, + where: { chain: this.chain }, include: { dynamicData: true, aprItems: true, @@ -252,7 +256,7 @@ export class PoolUsdDataService { operations.push( prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, + where: { id_chain: { id: pool.id, chain: pool.chain } }, data: { yieldCapture24h, yieldCapture48h }, }), ); @@ -272,7 +276,7 @@ export class PoolUsdDataService { const stakedUsers = await prisma.prismaUserStakedBalance.groupBy({ by: ['poolId'], _count: { userAddress: true }, - where: { chain: networkContext.chain, balanceNum: { gt: 0 } }, + where: { chain: this.chain, balanceNum: { gt: 0 } }, }); for (const pool of subgraphPools) { @@ -280,7 +284,7 @@ export class PoolUsdDataService { updates.push( prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, + where: { id_chain: { id: pool.id, chain: this.chain } }, data: { lifetimeVolume: parseFloat(pool.totalSwapVolume), lifetimeSwapFees: parseFloat(pool.totalSwapFee), @@ -291,7 +295,7 @@ export class PoolUsdDataService { ); const snapshots = await prisma.prismaPoolSnapshot.findMany({ - where: { poolId: pool.id, chain: networkContext.chain }, + where: { poolId: pool.id, chain: this.chain }, }); if (snapshots.length > 0) { @@ -306,7 +310,7 @@ export class PoolUsdDataService { updates.push( prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: pool.id, chain: networkContext.chain } }, + where: { id_chain: { id: pool.id, chain: this.chain } }, data: { sharePriceAth: sharePriceAth.sharePrice, sharePriceAthTimestamp: sharePriceAth.timestamp, diff --git a/modules/pool/pool.gql b/modules/pool/pool.gql index 219dea792..f6d231cf3 100644 --- a/modules/pool/pool.gql +++ b/modules/pool/pool.gql @@ -75,6 +75,13 @@ type GqlPoolMinimal { staking: GqlPoolStaking type: GqlPoolMinimalType! version: Int! + userBalance: GqlPoolUserBalance +} + +type GqlPoolUserBalance { + totalBalance: AmountHumanReadable! + walletBalance: AmountHumanReadable! + stakedBalance: AmountHumanReadable! } enum GqlPoolMinimalType { diff --git a/modules/pool/pool.prisma b/modules/pool/pool.prisma index 16767b4df..9244c46b5 100644 --- a/modules/pool/pool.prisma +++ b/modules/pool/pool.prisma @@ -423,7 +423,7 @@ model PrismaPoolStakingGauge { chain Chain gaugeAddress String - votingGauge PrismaVotingGauge? + votingGauge PrismaVotingGauge[] rewards PrismaPoolStakingGaugeReward[] status PrismaPoolStakingGaugeStatus @default(ACTIVE) version Int @default(1) diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index 6f7816f4f..468a8f201 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -125,11 +125,7 @@ export class PoolService { const featuredPoolGroups = await this.poolGqlLoaderService.getFeaturedPoolGroups(); - this.cache.put( - `${FEATURED_POOL_GROUPS_CACHE_KEY}:${this.chainId}`, - featuredPoolGroups, - 60 * 5 * 1000, - ); + this.cache.put(`${FEATURED_POOL_GROUPS_CACHE_KEY}:${this.chainId}`, featuredPoolGroups, 60 * 5 * 1000); return featuredPoolGroups; } @@ -359,13 +355,21 @@ export class PoolService { public async syncPoolVersionForAllPools() { const subgraphPools = await this.balancerSubgraphService.getAllPools({}, false); + for (const subgraphPool of subgraphPools) { - await prisma.prismaPool.update({ - where: { id_chain: { chain: this.chain, id: subgraphPool.id } }, - data: { - version: subgraphPool.poolTypeVersion ? subgraphPool.poolTypeVersion : 1, - }, - }); + try { + await prisma.prismaPool.update({ + where: { id_chain: { chain: this.chain, id: subgraphPool.id } }, + data: { + version: subgraphPool.poolTypeVersion ? subgraphPool.poolTypeVersion : 1, + }, + }); + } catch (e: any) { + // Some pools are filtered from the DB, like test pools, + // so we just ignore them without breaking the loop + const error = e.meta ? e.meta.cause : e; + console.error(error, 'Network', networkContext.chain, 'Pool ID: ', subgraphPool.id); + } } } @@ -490,7 +494,7 @@ export class PoolService { if (gauge && gauge.votingGauge) await prisma.prismaVotingGauge.deleteMany({ - where: { chain: this.chain, id: gauge.votingGauge.id }, + where: { chain: this.chain, id: { in: gauge.votingGauge.map((gauge) => gauge.id) } }, }); await prisma.prismaPoolStakingGauge.deleteMany({ diff --git a/modules/protocol/protocol.gql b/modules/protocol/protocol.gql index 0a63a8532..c9af32d44 100644 --- a/modules/protocol/protocol.gql +++ b/modules/protocol/protocol.gql @@ -1,6 +1,6 @@ extend type Query { - protocolMetricsChain: GqlProtocolMetricsChain! - protocolMetricsAggregated(chainIds: [String!]!): GqlProtocolMetricsAggregated! + protocolMetricsChain(chain: GqlChain): GqlProtocolMetricsChain! + protocolMetricsAggregated(chains: [GqlChain!]): GqlProtocolMetricsAggregated! latestSyncedBlocks: GqlLatestSyncedBlocks! } diff --git a/modules/protocol/protocol.resolvers.ts b/modules/protocol/protocol.resolvers.ts index 829681c29..09fa59460 100644 --- a/modules/protocol/protocol.resolvers.ts +++ b/modules/protocol/protocol.resolvers.ts @@ -1,14 +1,27 @@ import { GqlLatestSyncedBlocks, Resolvers } from '../../schema'; import { protocolService } from './protocol.service'; import { networkContext } from '../network/network-context.service'; +import { headerChain } from '../context/header-chain'; const protocolResolvers: Resolvers = { Query: { - protocolMetricsChain: async (parent, args, context) => { - return protocolService.getMetrics(networkContext.chainId); + protocolMetricsChain: async (parent, { chain }, context) => { + const currentChain = headerChain(); + if (!chain && currentChain) { + chain = currentChain; + } else if (!chain) { + throw new Error('poolGetPool error: Provide "chain" param'); + } + return protocolService.getMetrics(chain); }, - protocolMetricsAggregated: async (parent, { chainIds }, context) => { - return protocolService.getAggregatedMetrics(chainIds); + protocolMetricsAggregated: async (parent, { chains }, context) => { + const currentChain = headerChain(); + if (!chains && currentChain) { + chains = [currentChain]; + } else if (!chains) { + throw new Error('tokenGetTokens error: Provide "chains" param'); + } + return protocolService.getAggregatedMetrics(chains); }, latestSyncedBlocks: async (): Promise => { return protocolService.getLatestSyncedBlocks(); @@ -16,7 +29,7 @@ const protocolResolvers: Resolvers = { }, Mutation: { protocolCacheMetrics: async (): Promise => { - await protocolService.cacheProtocolMetrics(networkContext.chainId, networkContext.chain); + await protocolService.cacheProtocolMetrics(networkContext.chain); return 'success'; }, }, diff --git a/modules/protocol/protocol.service.ts b/modules/protocol/protocol.service.ts index d64085b84..45af70a3f 100644 --- a/modules/protocol/protocol.service.ts +++ b/modules/protocol/protocol.service.ts @@ -1,11 +1,10 @@ import moment from 'moment-timezone'; import { prisma } from '../../prisma/prisma-client'; -import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import { Cache } from 'memory-cache'; import { Chain, PrismaLastBlockSyncedCategory, PrismaUserBalanceType } from '@prisma/client'; import _ from 'lodash'; import { networkContext } from '../network/network-context.service'; -import { AllNetworkConfigs } from '../network/network-config'; +import { AllNetworkConfigsKeyedOnChain } from '../network/network-config'; import { GqlProtocolMetricsAggregated, GqlProtocolMetricsChain } from '../../schema'; import { GraphQLClient } from 'graphql-request'; import { getSdk } from '../subgraphs/balancer-subgraph/generated/balancer-subgraph-types'; @@ -24,12 +23,12 @@ export class ProtocolService { constructor() {} - public async getAggregatedMetrics(chainIds: string[]): Promise { + public async getAggregatedMetrics(chains: Chain[]): Promise { const chainMetrics: GqlProtocolMetricsChain[] = []; - for (const chainId of chainIds) { + for (const chain of chains) { // this should resolve quickly if all chains are cached, possible to get slammed by an unlucky query though - const metrics = await this.getMetrics(chainId); + const metrics = await this.getMetrics(chain); chainMetrics.push(metrics); } @@ -60,22 +59,22 @@ export class ProtocolService { }; } - public async getMetrics(chainId: string): Promise { - const cached = this.cache.get(`${PROTOCOL_METRICS_CACHE_KEY}:${chainId}`); + public async getMetrics(chain: Chain): Promise { + const cached = this.cache.get(`${PROTOCOL_METRICS_CACHE_KEY}:${chain}`); if (cached) { return cached; } - return this.cacheProtocolMetrics(chainId, AllNetworkConfigs[chainId].data.chain.prismaId); + return this.cacheProtocolMetrics(chain); } - public async cacheProtocolMetrics(chainId: string, chain: Chain): Promise { + public async cacheProtocolMetrics(chain: Chain): Promise { const oneDayAgo = moment().subtract(24, 'hours').unix(); const startOfDay = moment().startOf('day').unix(); const sevenDayRange = moment().startOf('day').subtract(7, 'days').unix(); - const client = new GraphQLClient(AllNetworkConfigs[chainId].data.subgraphs.balancer); + const client = new GraphQLClient(AllNetworkConfigsKeyedOnChain[chain].data.subgraphs.balancer); const subgraphClient = getSdk(client); const { balancers } = await subgraphClient.BalancerProtocolData({}); @@ -125,10 +124,10 @@ export class ProtocolService { }, }); - const balancerV1Tvl = await this.getBalancerV1Tvl(chainId); + const balancerV1Tvl = await this.getBalancerV1Tvl(`${AllNetworkConfigsKeyedOnChain[chain].data.chain.id}`); const protocolData = { - chainId, + chainId: `${AllNetworkConfigsKeyedOnChain[chain].data.chain.id}`, totalLiquidity: `${totalLiquidity + balancerV1Tvl}`, totalSwapFee, totalSwapVolume, @@ -141,7 +140,7 @@ export class ProtocolService { numLiquidityProviders: `${holdersQueryResponse._sum.holdersCount || '0'}`, }; - this.cache.put(`${PROTOCOL_METRICS_CACHE_KEY}:${chainId}`, protocolData, 60 * 30 * 1000); + this.cache.put(`${PROTOCOL_METRICS_CACHE_KEY}:${chain}`, protocolData, 60 * 30 * 1000); return protocolData; } diff --git a/modules/user/lib/user-snapshot.service.ts b/modules/user/lib/user-snapshot.service.ts index d886305d8..b13eeac55 100644 --- a/modules/user/lib/user-snapshot.service.ts +++ b/modules/user/lib/user-snapshot.service.ts @@ -231,7 +231,7 @@ export class UserSnapshotService { where: { id_chain: { id: latestStoredUserPoolSnapshot.poolId, - chain: networkContext.chain, + chain: latestStoredUserPoolSnapshot.chain, }, }, include: { @@ -246,8 +246,9 @@ export class UserSnapshotService { if (totalBalance > 0) { //enrich with poolsnapshot data and save const poolSnapshot = await this.poolSnapshotService.getSnapshotForPool( - latestStoredUserPoolSnapshot.poolId, + pool.id, userSubgraphSnapshot.timestamp, + pool.chain, ); /* @@ -268,7 +269,7 @@ export class UserSnapshotService { operations.push( prisma.prismaUserPoolBalanceSnapshot.upsert({ where: { - id_chain: { id: userPoolBalanceSnapshotData.id, chain: networkContext.chain }, + id_chain: { id: userPoolBalanceSnapshotData.id, chain: userPoolBalanceSnapshotData.chain }, }, create: userPoolBalanceSnapshotData, update: userPoolBalanceSnapshotData, @@ -281,7 +282,7 @@ export class UserSnapshotService { id: `${pool.id}-${userSubgraphSnapshot.user.id.toLowerCase()}-${ userSubgraphSnapshot.timestamp }`, - chain: networkContext.chain, + chain: pool.chain, timestamp: userSubgraphSnapshot.timestamp, userAddress: userSubgraphSnapshot.user.id.toLowerCase(), poolId: pool.id, @@ -298,7 +299,7 @@ export class UserSnapshotService { operations.push( prisma.prismaUserPoolBalanceSnapshot.upsert({ where: { - id_chain: { id: userPoolBalanceSnapshotData.id, chain: networkContext.chain }, + id_chain: { id: userPoolBalanceSnapshotData.id, chain: userPoolBalanceSnapshotData.chain }, }, create: userPoolBalanceSnapshotData, update: userPoolBalanceSnapshotData, diff --git a/modules/vebal/debug-voting-list.spec.ts b/modules/vebal/debug-voting-list.spec.ts index 21874ed6e..b131011dc 100644 --- a/modules/vebal/debug-voting-list.spec.ts +++ b/modules/vebal/debug-voting-list.spec.ts @@ -292,7 +292,9 @@ it('Uses streamer-v1-map to find gauges (that use streamer instead of recipient) const savedGauges = await repository.saveVotingGauges(fetchedVotingGauges); expect(savedGauges).toMatchInlineSnapshot(` - [ + { + "saveErrors": [], + "votingGaugesWithStakingGaugeId": [ { "addedTimestamp": 1657479716, "gaugeAddress": "0xcf5938ca6d9f19c73010c7493e19c02acfa8d24d", @@ -315,6 +317,7 @@ it('Uses streamer-v1-map to find gauges (that use streamer instead of recipient) "relativeWeightCap": undefined, "stakingGaugeId": "0xfaad21203a7856889cb6eb644ab6864e7253107a", }, - ] + ], + } `); }, 1000_000); diff --git a/modules/vebal/vebal-voting-list.service.ts b/modules/vebal/vebal-voting-list.service.ts index 3f78e2061..db30f16b9 100644 --- a/modules/vebal/vebal-voting-list.service.ts +++ b/modules/vebal/vebal-voting-list.service.ts @@ -100,9 +100,13 @@ export class VeBalVotingListService { } public async getValidVotingGauges() { + // A gauge should be included in the voting list when: + // - it is alive (not killed) + // - it is killed and has valid votes (the users should be able to reallocate votes) const gaugesWithStaking = await prisma.prismaVotingGauge.findMany({ where: { stakingGaugeId: { not: null }, + OR: [{ status: 'ACTIVE' }, { relativeWeight: { not: '0' } }], }, select: { id: true, @@ -137,13 +141,12 @@ export class VeBalVotingListService { const syncErrors: Error[] = []; for (const addressChunk of chunks) { - const { votingGauges, errors } = await this.fetchVotingGauges(addressChunk); + const { filteredGauges, errors } = await this.fetchVotingGauges(addressChunk); syncErrors.push(...errors); - /* We avoid saving gauges in specialVotingGaugeAddresses because they require special handling */ - const cleanVotingGauges = votingGauges.filter( + const cleanVotingGauges = filteredGauges.filter( (gauge) => !specialVotingGaugeAddresses.includes(gauge.gaugeAddress), ); @@ -164,30 +167,31 @@ export class VeBalVotingListService { const votingGauges = this.votingGauges.updateOnchainGaugesWithSubgraphData(onchainGauges, subgraphGauges); - try { - this.throwIfMissingVotingGaugeData(votingGauges); - } catch (error) { - errors.push(error as Error); - } + const gaugesWithMissingData = this.returnGaugesWithMissingData(votingGauges); - return { votingGauges, errors }; + const filteredGauges = votingGauges.filter( + (gauge) => !gaugesWithMissingData.map((gauge) => gauge.gaugeAddress).includes(gauge.gaugeAddress), + ); + + if (gaugesWithMissingData.length > 0) { + const errorMessage = + 'Detected active voting gauge/s with votes (relative weight > 0) that are not in subgraph: ' + + JSON.stringify(gaugesWithMissingData); + console.error(errorMessage); + errors.push(new Error(errorMessage)); + } + return { filteredGauges, errors }; } - throwIfMissingVotingGaugeData(votingGauges: VotingGauge[]) { + returnGaugesWithMissingData(votingGauges: VotingGauge[]) { const gaugesWithMissingData = votingGauges .filter((gauge) => !veGauges.includes(gauge.gaugeAddress)) .filter((gauge) => !gauge.isInSubgraph) - .filter(this.votingGauges.isValidForVotingList) + .filter((gauge) => gauge.relativeWeight > 0) // Ignore old Vebal gauge address .filter((gauge) => gauge.gaugeAddress !== oldVeBalAddress); - if (gaugesWithMissingData.length > 0) { - const errorMessage = - 'Detected active voting gauge/s with votes (relative weight > 0) that are not in subgraph: ' + - JSON.stringify(gaugesWithMissingData); - console.error(errorMessage); - throw new Error(errorMessage); - } + return gaugesWithMissingData; } } diff --git a/modules/vebal/vebal.prisma b/modules/vebal/vebal.prisma index c4b1b3a0f..df28431b5 100644 --- a/modules/vebal/vebal.prisma +++ b/modules/vebal/vebal.prisma @@ -27,7 +27,6 @@ enum PrismaVotingGaugeStatus { model PrismaVotingGauge { @@id([id, chain]) - @@unique([stakingGaugeId, chain]) id String chain Chain diff --git a/modules/vebal/voting-gauges.repository.spec.ts b/modules/vebal/voting-gauges.repository.spec.ts index 941705780..7f35f11d5 100644 --- a/modules/vebal/voting-gauges.repository.spec.ts +++ b/modules/vebal/voting-gauges.repository.spec.ts @@ -116,19 +116,14 @@ it('successfully saves onchain gauges', async () => { describe('When staking gauge is not found ', () => { beforeEach(() => prismaMock.prismaPoolStakingGauge.findFirst.mockResolvedValue(null)); - it('throws when gauge is valid for voting (not killed)', async () => { + it('has errors when gauge is valid for voting (not killed)', async () => { const repository = new VotingGaugesRepository(prismaMock); const votingGauge = aVotingGauge({ network: Chain.MAINNET, isKilled: false }); - let error: Error = EmptyError; - try { - await repository.saveVotingGauges([votingGauge]); - } catch (e) { - error = e as Error; - } + const { votingGaugesWithStakingGaugeId, saveErrors } = await repository.saveVotingGauges([votingGauge]); - expect(error.message).toContain('VotingGauge not found in PrismaPoolStakingGauge:'); + expect(saveErrors.length).toBe(1); }); it('does not throw when gauge is valid for voting (killed with no votes)', async () => { diff --git a/modules/vebal/voting-gauges.repository.ts b/modules/vebal/voting-gauges.repository.ts index 78d16a8c7..347932c17 100644 --- a/modules/vebal/voting-gauges.repository.ts +++ b/modules/vebal/voting-gauges.repository.ts @@ -128,7 +128,7 @@ export class VotingGaugesRepository { await this.saveVotingGauge(gauge); return gauge; } catch (error) { - saveErrors.push(error as Error); + saveErrors.push(new Error(`Failed to save voting gauge ${gauge.gaugeAddress} with error ${error}`)); return gauge; } }), @@ -138,7 +138,6 @@ export class VotingGaugesRepository { } async saveVotingGauge(gauge: VotingGauge) { - if (!this.isValidForVotingList(gauge)) return; try { const upsertFields = { id: gauge.gaugeAddress, diff --git a/package.json b/package.json index d44899b0e..5c7bd2869 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "node-cron": "^3.0.0", "prisma": "^4.1.1", "safe-compare": "^1.1.4", - "stellate": "^1.15.2", + "stellate": "2.7.1", "ts-dotenv": "^0.8.1", "uuid": "^8.3.2", "yarn": "^1.22.19" diff --git a/prisma/migrations/20231204142011_remove_constraint_votinggauge/migration.sql b/prisma/migrations/20231204142011_remove_constraint_votinggauge/migration.sql new file mode 100644 index 000000000..4b0b35734 --- /dev/null +++ b/prisma/migrations/20231204142011_remove_constraint_votinggauge/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "PrismaVotingGauge_stakingGaugeId_chain_key"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9ec3e38d1..79f305c65 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -469,7 +469,7 @@ model PrismaPoolStakingGauge { chain Chain gaugeAddress String - votingGauge PrismaVotingGauge? + votingGauge PrismaVotingGauge[] rewards PrismaPoolStakingGaugeReward[] status PrismaPoolStakingGaugeStatus @default(ACTIVE) version Int @default(1) @@ -846,7 +846,6 @@ enum PrismaVotingGaugeStatus { model PrismaVotingGauge { @@id([id, chain]) - @@unique([stakingGaugeId, chain]) id String chain Chain diff --git a/stellate-beets-canary.ts b/stellate-beets-canary.ts new file mode 100644 index 000000000..1a21f625a --- /dev/null +++ b/stellate-beets-canary.ts @@ -0,0 +1,137 @@ +// import { Config } from 'stellate'; + +// const config: Config = { +// config: { +// originUrl: 'https://backend-v3-canary-origin.beets-ftm-node.com/graphql', +// schema: 'https://backend-v3-canary.beets-ftm-node.com/graphql', +// name: 'backend-v3-canary', +// devPortal: { +// enabled: true, +// auth: false, +// description: 'IMPORTANT: To stay up to date with changes of our API, please join https://t.me/beetsapi', +// readme: 'Learn how to use the API ### Query pools for a certain or multiple chains and apply sorting ```graphiql { poolGetPools(where: {chainIn: [FANTOM, OPTIMISM]}, orderBy: totalLiquidity, orderDirection: desc) { id name } } ``` ### Query user balances for one or more chains ```graphiql { userGetPoolBalances( address: "0x4fbe899d37fb7514adf2f41b0630e018ec275a0c" chains: [FANTOM] ) { poolId stakedBalance walletBalance } } ``` Check out the Graphql Schema for endless possibilities.', +// urls: { +// logo: 'https://beethoven-assets.s3.eu-central-1.amazonaws.com/logo-full%402x.png', +// favicon: 'https://assets.coingecko.com/coins/images/19158/large/beets-icon-large.png?1634545465', +// support: 'https://discord.gg/kbPnYJjvwZ', +// website: 'https://beets.fi', +// }, +// }, +// queryDepthLimit: 10, +// scopes: { +// AUTHENTICATED: 'header:accountaddress', +// CHAIN: 'header:chainid', +// AUTHENTICATED_CHAIN: 'header:accountaddress|header:chainid', +// }, +// rootTypeNames: { +// query: 'Query', +// mutation: 'Mutation', +// }, +// rules: [ +// { +// types: ['Query'], +// maxAge: 15, +// swr: 30, +// description: 'Cache everything (default)', +// scope: 'CHAIN', +// }, +// { +// types: { +// Query: [ +// 'userGetSwaps', +// 'userGetStaking', +// 'userGetPoolBalances', +// 'userGetFbeetsBalance', +// 'userGetPoolJoinExits', +// ], +// }, +// maxAge: 10, +// swr: 15, +// scope: 'AUTHENTICATED_CHAIN', +// description: 'Time critical user queries', +// }, +// { +// types: { +// Query: ['latestSyncedBlocks'], +// }, +// maxAge: 2, +// swr: 10, +// description: 'Time critical block data', +// scope: 'CHAIN', +// }, +// { +// types: { +// Query: [ +// 'protocolMetricsChain', +// 'protocolMetricsAggregated', +// 'tokenGetProtocolTokenPrice', +// 'beetsGetFbeetsRatio', +// 'blocksGetBlocksPerSecond', +// 'blocksGetBlocksPerDay', +// 'blocksGetAverageBlockTime', +// 'tokenGetTokens', +// 'poolGetFeaturedPoolGroups', +// 'contentGetNewsItems', +// ], +// }, +// maxAge: 60, +// swr: 120, +// description: 'Mostly static, cache for a long time', +// scope: 'CHAIN', +// }, +// ], +// rateLimits: (req) => { +// if ( +// req.headers['stellate-api-token'] && +// req.headers['stellate-api-token'] === +// 'stl8_bcebb2b60910a55e58a82c8e83825034dc763e294582447118fab0a6a1225ebb' +// ) { +// return [ +// { +// name: 'Specific API Token based limits', +// state: 'dryRun', +// group: req.headers['stellate-api-token'], +// limit: { +// type: 'RequestCount', +// budget: 20, +// window: '1m', +// }, +// }, +// ]; +// } + +// if (req.headers['stellate-api-token']) { +// return [ +// { +// name: 'General API Token based limits', +// state: 'dryRun', +// group: req.headers['stellate-api-token'], +// limit: { +// type: 'RequestCount', +// budget: 10, +// window: '1m', +// }, +// }, +// ]; +// } + +// const xForwardedFor = Array.isArray(req.headers['x-forwarded-for']) +// ? req.headers['x-forwarded-for'][0] +// : req.headers['x-forwarded-for']; +// return [ +// { +// name: 'IP based limits', +// state: 'dryRun', +// group: xForwardedFor ? xForwardedFor.split(',')[0] : req.ip, +// limit: { +// type: 'RequestCount', +// budget: 5, +// window: '1m', +// }, +// }, +// ]; +// }, +// }, +// }; + +// export default config; diff --git a/stellate-fantom.ts b/stellate-fantom.ts deleted file mode 100644 index b747d333f..000000000 --- a/stellate-fantom.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Config } from 'stellate'; - -const config: Config = { - config: { - schema: 'https://backend-v2.beets-ftm-node.com/graphql', - queryDepthLimit: 10, - scopes: { - AUTHENTICATED: 'header:accountaddress', - }, - rootTypeNames: { - query: 'Query', - mutation: 'Mutation', - }, - rules: [ - { - types: ['Query'], - maxAge: 15, - swr: 30, - description: 'Cache everything (default)', - }, - { - types: { - Query: [ - 'userGetSwaps', - 'userGetStaking', - 'userGetPoolBalances', - 'userGetFbeetsBalance', - 'userGetPoolJoinExits', - 'poolGetUserSwapVolume', - ], - }, - maxAge: 10, - swr: 15, - scope: 'AUTHENTICATED', - description: 'Time critical user queries', - }, - { - types: { - Query: ['latestSyncedBlocks', 'blocksGetAverageBlockTime'], - }, - maxAge: 2, - swr: 5, - description: 'Time critical block data', - }, - ], - name: 'beetx-backend-v2', - originUrl: 'https://backend-v2-cdn.beets-ftm-node.com/graphql', - }, -}; - -export default config; diff --git a/stellate-optimism.ts b/stellate-optimism.ts deleted file mode 100644 index 63e035f98..000000000 --- a/stellate-optimism.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Config } from 'stellate'; - -const config: Config = { - config: { - schema: 'https://backend-optimism-v2.beets-ftm-node.com/graphql', - queryDepthLimit: 10, - scopes: { - AUTHENTICATED: 'header:accountaddress', - }, - rootTypeNames: { - query: 'Query', - mutation: 'Mutation', - }, - rules: [ - { - types: ['Query'], - maxAge: 15, - swr: 30, - description: 'Cache everything (default)', - }, - { - types: { - Query: [ - 'userGetSwaps', - 'userGetStaking', - 'userGetPoolBalances', - 'userGetFbeetsBalance', - 'userGetPoolJoinExits', - 'poolGetUserSwapVolume', - ], - }, - maxAge: 10, - swr: 15, - scope: 'AUTHENTICATED', - description: 'Time critical user queries', - }, - { - types: { - Query: ['latestSyncedBlocks', 'blocksGetAverageBlockTime'], - }, - maxAge: 2, - swr: 5, - description: 'Time critical block data', - }, - ], - name: 'beetx-backend-v2-optimism', - originUrl: 'https://backend-optimism-v2-cdn.beets-ftm-node.com/graphql', - }, -}; - -export default config; diff --git a/tsconfig.json b/tsconfig.json index c4157392a..817776afd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,8 @@ }, "exclude": [ "node_modules", - "debug" + "debug", + "**/*.spec.ts", + "**/*.test.ts" ] } diff --git a/worker/job-handlers.ts b/worker/job-handlers.ts index 8225f9ee7..4da33e683 100644 --- a/worker/job-handlers.ts +++ b/worker/job-handlers.ts @@ -173,7 +173,7 @@ export function configureWorkerRoutes(app: Express) { await runIfNotAlreadyRunning( job.name, chainId, - () => protocolService.cacheProtocolMetrics(networkContext.chainId, networkContext.chain), + () => protocolService.cacheProtocolMetrics(networkContext.chain), res, next, ); diff --git a/yarn.lock b/yarn.lock index ccf0f16fa..1c3313ada 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2478,111 +2478,221 @@ ts-node "^9" tslib "^2" +"@esbuild/android-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" + integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA== + "@esbuild/android-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz#fa6f0cc7105367cb79cc0a8bf32bf50cb1673e45" integrity sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw== +"@esbuild/android-arm@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" + integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A== + "@esbuild/android-arm@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.11.tgz#ae84a410696c9f549a15be94eaececb860bacacb" integrity sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q== +"@esbuild/android-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" + integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww== + "@esbuild/android-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.11.tgz#0e58360bbc789ad0d68174d32ba20e678c2a16b6" integrity sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw== +"@esbuild/darwin-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" + integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg== + "@esbuild/darwin-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz#fcdcd2ef76ca656540208afdd84f284072f0d1f9" integrity sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w== +"@esbuild/darwin-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" + integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw== + "@esbuild/darwin-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz#c5ac602ec0504a8ff81e876bc8a9811e94d69d37" integrity sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw== +"@esbuild/freebsd-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" + integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ== + "@esbuild/freebsd-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz#7012fb06ee3e6e0d5560664a65f3fefbcc46db2e" integrity sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A== +"@esbuild/freebsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" + integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ== + "@esbuild/freebsd-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz#c5de1199f70e1f97d5c8fca51afa9bf9a2af5969" integrity sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q== +"@esbuild/linux-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" + integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg== + "@esbuild/linux-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz#2a6d3a74e0b8b5f294e22b4515b29f76ebd42660" integrity sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog== +"@esbuild/linux-arm@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" + integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA== + "@esbuild/linux-arm@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz#5175bd61b793b436e4aece6328aa0d9be07751e1" integrity sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg== +"@esbuild/linux-ia32@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" + integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ== + "@esbuild/linux-ia32@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz#20ee6cfd65a398875f321a485e7b2278e5f6f67b" integrity sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw== +"@esbuild/linux-loong64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" + integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ== + "@esbuild/linux-loong64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz#8e7b251dede75083bf44508dab5edce3f49d052b" integrity sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw== +"@esbuild/linux-mips64el@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" + integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A== + "@esbuild/linux-mips64el@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz#a3125eb48538ac4932a9d05089b157f94e443165" integrity sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg== +"@esbuild/linux-ppc64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" + integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg== + "@esbuild/linux-ppc64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz#842abadb7a0995bd539adee2be4d681b68279499" integrity sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ== +"@esbuild/linux-riscv64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" + integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA== + "@esbuild/linux-riscv64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz#7ce6e6cee1c72d5b4d2f4f8b6fcccf4a9bea0e28" integrity sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w== +"@esbuild/linux-s390x@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" + integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q== + "@esbuild/linux-s390x@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz#98fbc794363d02ded07d300df2e535650b297b96" integrity sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg== +"@esbuild/linux-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" + integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw== + "@esbuild/linux-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz#f8458ec8cf74c8274e4cacd00744d8446cac52eb" integrity sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA== +"@esbuild/netbsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" + integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q== + "@esbuild/netbsd-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz#a7b2f991b8293748a7be42eac1c4325faf0c7cca" integrity sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q== +"@esbuild/openbsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" + integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g== + "@esbuild/openbsd-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz#3e50923de84c54008f834221130fd23646072b2f" integrity sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ== +"@esbuild/sunos-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" + integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg== + "@esbuild/sunos-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz#ae47a550b0cd395de03606ecfba03cc96c7c19e2" integrity sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng== +"@esbuild/win32-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" + integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag== + "@esbuild/win32-arm64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz#05d364582b7862d7fbf4698ef43644f7346dcfcc" integrity sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg== +"@esbuild/win32-ia32@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" + integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw== + "@esbuild/win32-ia32@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz#a3372095a4a1939da672156a3c104f8ce85ee616" integrity sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg== +"@esbuild/win32-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" + integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA== + "@esbuild/win32-x64@0.18.11": version "0.18.11" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz#6526c7e1b40d5b9f0a222c6b767c22f6fb97aa57" @@ -4527,9 +4637,9 @@ graphql "^14.5.3" "@types/http-proxy@^1.17.8": - version "1.17.9" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" - integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== + version "1.17.14" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" + integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== dependencies: "@types/node" "*" @@ -4566,9 +4676,9 @@ integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== "@types/js-yaml@^3.12.1": - version "3.12.7" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.7.tgz#330c5d97a3500e9c903210d6e49f02964af04a0e" - integrity sha512-S6+8JAYTE1qdsc9HMVsfY7+SgSuUU/Tp6TYTmITW0PZxiyIMvol3Gy//y69Wkhs0ti4py5qgR3uZH6uz/DNzJQ== + version "3.12.10" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.10.tgz#4d80d0c7dfc570eb4f0be280cb2d67789f977ba5" + integrity sha512-/Mtaq/wf+HxXpvhzFYzrzCqNRcA958sW++7JOFC8nPrZcvfi/TrzOaaGbvt27ltJB2NQbHVAg5a1wUCsyMH7NA== "@types/js-yaml@^4.0.0": version "4.0.4" @@ -4622,16 +4732,23 @@ resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.0.tgz#f946cefb5c05c64f460090f6be97bd50460c8898" integrity sha512-RNBIyVwa/1v2r8/SqK8tadH2sJlFRAo5Ghac/cOcCv4Kp94m0I03UmAh9WVhCqS9ZdB84dF3x47p9aTw8E4c4A== -"@types/node@*", "@types/node@^16.11.7": - version "16.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" - integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== +"@types/node@*": + version "20.9.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.2.tgz#002815c8e87fe0c9369121c78b52e800fadc0ac6" + integrity sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg== + dependencies: + undici-types "~5.26.4" "@types/node@^10.1.0": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== +"@types/node@^16.11.7": + version "16.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" + integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -5747,9 +5864,9 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" - integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -6468,10 +6585,33 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@0.8.57: - version "0.8.57" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.57.tgz#a42d02bc2b57c70bcd0ef897fe244766bb6dd926" - integrity sha512-j02SFrUwFTRUqiY0Kjplwjm1psuzO1d6AjaXKuOR9hrY0HuPsT6sV42B6myW34h1q4CRy+Y3g4RU/cGJeI/nNA== +esbuild@^0.17.7: + version "0.17.19" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955" + integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw== + optionalDependencies: + "@esbuild/android-arm" "0.17.19" + "@esbuild/android-arm64" "0.17.19" + "@esbuild/android-x64" "0.17.19" + "@esbuild/darwin-arm64" "0.17.19" + "@esbuild/darwin-x64" "0.17.19" + "@esbuild/freebsd-arm64" "0.17.19" + "@esbuild/freebsd-x64" "0.17.19" + "@esbuild/linux-arm" "0.17.19" + "@esbuild/linux-arm64" "0.17.19" + "@esbuild/linux-ia32" "0.17.19" + "@esbuild/linux-loong64" "0.17.19" + "@esbuild/linux-mips64el" "0.17.19" + "@esbuild/linux-ppc64" "0.17.19" + "@esbuild/linux-riscv64" "0.17.19" + "@esbuild/linux-s390x" "0.17.19" + "@esbuild/linux-x64" "0.17.19" + "@esbuild/netbsd-x64" "0.17.19" + "@esbuild/openbsd-x64" "0.17.19" + "@esbuild/sunos-x64" "0.17.19" + "@esbuild/win32-arm64" "0.17.19" + "@esbuild/win32-ia32" "0.17.19" + "@esbuild/win32-x64" "0.17.19" esbuild@^0.18.10: version "0.18.11" @@ -6659,7 +6799,7 @@ execa@^4.1.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^5.0.0, execa@^5.1.1: +execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -6892,9 +7032,9 @@ find-up@^4.0.0, find-up@^4.1.0: path-exists "^4.0.0" follow-redirects@^1.0.0: - version "1.15.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" - integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== follow-redirects@^1.14.4: version "1.14.5" @@ -7736,7 +7876,7 @@ is-date-object@^1.0.1: is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^1.0.0: version "1.0.0" @@ -7976,7 +8116,7 @@ isarray@~1.0.0: isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isobject@^3.0.1: version "3.0.1" @@ -8899,6 +9039,11 @@ lru_map@^0.3.3: resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== +lz-string@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + magic-string@^0.30.0: version "0.30.1" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.1.tgz#ce5cd4b0a81a5d032bd69aab4522299b2166284d" @@ -9761,9 +9906,11 @@ queue-microtask@^1.2.2: integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== random-words@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/random-words/-/random-words-1.2.0.tgz#94d0cc8061965efe955d60b80ad93392a7edf2f5" - integrity sha512-YP2bXrT19pxtBh22DK9CLcWsmBjUBAGzw3JWJycTNbXm1+0aS6PrKuAJ9aLT0GGaPlPp9LExfJIMVkzhrDZE6g== + version "1.3.0" + resolved "https://registry.yarnpkg.com/random-words/-/random-words-1.3.0.tgz#00715efb8dd787d244f963c994367707c1e95676" + integrity sha512-brwCGe+DN9DqZrAQVNj1Tct1Lody6GrYL/7uei5wfjeQdacFyFd2h/51LNlOoBMzIKMS9xohuL4+wlF/z1g/xg== + dependencies: + seedrandom "^3.0.5" randombytes@^2.1.0: version "2.1.0" @@ -9936,7 +10083,7 @@ require-main-filename@^2.0.0: requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== resolve-cwd@^3.0.0: version "3.0.0" @@ -10111,6 +10258,11 @@ secp256k1@^4.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -10232,12 +10384,7 @@ signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== -signal-exit@^3.0.3: - version "3.0.6" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" - integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== - -signal-exit@^3.0.7: +signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -10272,11 +10419,6 @@ slice-ansi@0.0.4: resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= -slugify@^1.4.7: - version "1.6.5" - resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.5.tgz#c8f5c072bf2135b80703589b39a3d41451fbe8c8" - integrity sha512-8mo9bslnBO3tr5PEVFzMPIWwWnipGS0xVbYf65zxDqfNwmzYn1LpiKNrR6DlClusuvo+hDHd1zKpmfAe83NQSQ== - snake-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" @@ -10348,7 +10490,7 @@ sponge-case@^1.0.1: sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== ssh-remote-port-forward@^1.0.4: version "1.0.4" @@ -10396,21 +10538,20 @@ std-env@^3.3.3: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.3.tgz#a54f06eb245fdcfef53d56f3c0251f1d5c3d01fe" integrity sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg== -stellate@^1.15.2: - version "1.15.2" - resolved "https://registry.yarnpkg.com/stellate/-/stellate-1.15.2.tgz#0fce013cbf02fb217faa50e4df424ae6a4ab78d2" - integrity sha512-0i5lnzmNnAoEgCqzbt93hYVfBBOC/DwFhFFxcvjHJM3rTA7zfFcoBfJ2kicXy/Z2JDHXv7wzWe0E6uLa0SL0CA== +stellate@2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/stellate/-/stellate-2.7.1.tgz#c83a689dbb096c9df7ea77685986272c149a1883" + integrity sha512-vtRL1w3oDtLpMX1vENKAkrlf3U/mLEzGo5DaujfBvgOV5hm3qar1J3XdKPNFhauK7UWd9czOCTQVucDgmNJSbA== dependencies: "@atomist/yaml-updater" "^1.0.2" "@sindresorhus/slugify" "^1.1.0" - esbuild "0.8.57" - execa "^5.1.1" + esbuild "^0.17.7" express "^4.17.1" http-proxy-middleware "^2.0.1" is-ci "^3.0.1" + lz-string "1.5.0" random-words "^1.1.1" - slugify "^1.4.7" - zod "^3.15.1" + zod "^3.21.4" strict-event-emitter@^0.2.0, strict-event-emitter@^0.2.4: version "0.2.5" @@ -10932,6 +11073,11 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^4.9.3: version "4.10.0" resolved "https://registry.yarnpkg.com/undici/-/undici-4.10.0.tgz#f2684de9cbe2ab0a85a477ce0ec59b739be4438d" @@ -11435,7 +11581,7 @@ zip-stream@^4.1.0: compress-commons "^4.1.0" readable-stream "^3.6.0" -zod@^3.15.1: - version "3.18.0" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc" - integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA== +zod@^3.21.4: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==