├── .ebextensions └── cloudwatch.config ├── .env.default ├── .gitignore ├── README.md ├── app.js ├── bin └── www.js ├── constants ├── AppConstants.js ├── CacheSettings.js ├── CoinAddressCoingeckoIdMap.js ├── CoinAddressReplacementMap.js ├── PoolAddressInternalIdMap.js ├── PoolMetadata.js ├── Web3.js ├── abis │ ├── AggregatorStablePrice.json │ ├── address_getter.json │ ├── aggregator.json │ ├── crv-circsupply-util.json │ ├── crypto-registry.json │ ├── crypto_pool.json │ ├── distributor.json │ ├── erc20.json │ ├── erc20_mkr.json │ ├── example_gauge.json │ ├── example_gauge_2.json │ ├── factory-crvusd │ │ ├── pool.json │ │ └── registry.json │ ├── factory-crypto-registry.json │ ├── factory-crypto-swap.json │ ├── factory-crypto │ │ └── factory-crypto-pool-2.json │ ├── factory-stableswap-ng │ │ ├── pool.json │ │ └── registry.json │ ├── factory-tricrypto │ │ └── registry.json │ ├── factory-twocrypto │ │ ├── pool.json │ │ └── registry.json │ ├── factory-v2-registry.json │ ├── factory-v2 │ │ ├── MetaBTC.json │ │ ├── MetaBTCBalances.json │ │ ├── MetaUSD.json │ │ ├── MetaUSDBalances.json │ │ ├── Plain2Balances.json │ │ ├── Plain2Basic.json │ │ ├── Plain2ETH.json │ │ ├── Plain2Optimized.json │ │ ├── Plain3Balances.json │ │ ├── Plain3Basic.json │ │ ├── Plain3ETH.json │ │ ├── Plain3Optimized.json │ │ ├── Plain4Balances.json │ │ ├── Plain4Basic.json │ │ ├── Plain4ETH.json │ │ └── Plain4Optimized.json │ ├── factory_crypto_swap.json │ ├── factory_registry.json │ ├── factory_swap.json │ ├── factory_tricrypto_swap.json │ ├── fantom │ │ └── factory-eywa │ │ │ ├── pool.json │ │ │ └── registry.json │ ├── gauge-factory-sidechain.json │ ├── gauge-registry-sidechain.json │ ├── gauge-registry.json │ ├── gauge-sidechain-v2.json │ ├── gauge_controller.json │ ├── lending │ │ ├── amm.json │ │ ├── controller.json │ │ └── oneway │ │ │ ├── registry.json │ │ │ └── vault.json │ ├── meta-registry.json │ ├── multicall.json │ ├── multicall2.json │ ├── napier-pool.json │ ├── napier-quoter.json │ ├── pools │ │ ├── 2pool.json │ │ └── aave.json │ ├── registry-v1.1.json │ ├── registry.json │ ├── sidechain-gauge.json │ ├── sidechain-root-gauge.json │ ├── synthetix-exchange-rates.json │ ├── synthetix-token.json │ ├── temple-lp-token.json │ ├── tripool_swap.json │ └── yc-token.json ├── coins │ ├── coins.js │ ├── index.js │ └── init-validation.js ├── configs │ ├── configs.js │ ├── index.js │ └── init-validation.js ├── pools-zaps │ ├── arbitrum.json │ ├── avalanche.json │ ├── ethereum.json │ ├── fantom.json │ ├── harmony.json │ ├── index.js │ ├── mantle.json │ ├── optimism.json │ ├── polygon.json │ ├── x-layer.json │ └── xdai.json ├── pools │ ├── index.js │ ├── init-validation.js │ └── pools.js └── reference-assets.json ├── jsconfig.json ├── middleware └── access-control.js ├── package-lock.json ├── package.json ├── public ├── css │ └── swaggerui.css └── static │ └── inflation.json ├── routes ├── _setup.js ├── root.js └── v1 │ ├── _redirects.json │ ├── getAllGauges.js │ ├── getAllPoolsVolume │ └── [blockchainId].js │ ├── getBaseApys │ └── [blockchainId].js │ ├── getCrvCircSupply.js │ ├── getCrvusdTotalSupply.js │ ├── getCrvusdTotalSupplyNumber.js │ ├── getETHprice.js │ ├── getFactoGauges │ └── [blockchainId].js │ ├── getFactoGaugesCrvRewards │ └── [blockchainId].js │ ├── getFactoryAPYs │ ├── [blockchainId] │ │ ├── [version].js │ │ └── custom-sidechains │ │ │ ├── _aurora.js │ │ │ ├── _bsc.js │ │ │ ├── _celo.js │ │ │ ├── _kava.js │ │ │ ├── _mantle.js │ │ │ ├── _moonbeam.js │ │ │ ├── _x-layer.js │ │ │ ├── _zkevm.js │ │ │ └── _zksync.js │ ├── _ethereum.js │ └── _sidechains.js │ ├── getFactoryCryptoPools │ └── [blockchainId].js │ ├── getFactoryPools.js │ ├── getFactoryTVL.js │ ├── getFactoryV2Pools │ └── [blockchainId].js │ ├── getGas.js │ ├── getHiddenPools.js │ ├── getLendingVaults │ ├── [lendingBlockchainId] │ │ └── [lendingRegistryId].js │ └── all │ │ └── [lendingBlockchainId].js │ ├── getMainPoolsAPYs │ └── [blockchainId].js │ ├── getMainPoolsGaugeRewards.js │ ├── getMainRegistryPools.js │ ├── getMainRegistryPoolsAndLpTokens.js │ ├── getPlatforms.js │ ├── getPointsCampaigns.js │ ├── getPoolList │ └── [blockchainId].js │ ├── getPools │ ├── [blockchainId] │ │ └── [registryId].js │ ├── _augmentedCoinsUtils.js │ ├── _utils.js │ ├── all │ │ └── [blockchainId].js │ ├── big │ │ └── [blockchainId].js │ ├── empty │ │ └── [blockchainId].js │ └── small │ │ └── [blockchainId].js │ ├── getRegistryAddress.js │ ├── getScrvusdTotalSupplyNumber.js │ ├── getScrvusdTotalSupplyResult.js │ ├── getSubgraphData │ ├── [blockchainId].js │ └── _fallback-data │ │ ├── getSubgraphData-arbitrum.json │ │ ├── getSubgraphData-aurora.json │ │ ├── getSubgraphData-avalanche.json │ │ ├── getSubgraphData-celo.json │ │ ├── getSubgraphData-ethereum.json │ │ ├── getSubgraphData-fantom.json │ │ ├── getSubgraphData-harmony.json │ │ ├── getSubgraphData-moonbeam.json │ │ ├── getSubgraphData-optimism.json │ │ ├── getSubgraphData-polygon.json │ │ └── getSubgraphData-xdai.json │ ├── getTokens │ └── all │ │ └── [blockchainId].js │ ├── getVolumes │ ├── [blockchainId].js │ └── ethereum │ │ └── crvusd-amms.js │ └── getWeeklyFees.js └── utils ├── Array.js ├── Async.js ├── Calls.js ├── Date.js ├── Function.js ├── Function.test.js ├── Graphql ├── _fallback-data │ └── crvusd-amms.json └── index.js ├── Number.js ├── Number.test.js ├── Pagination.js ├── Request.js ├── String.js ├── Web3 ├── index.js ├── parsing.js └── web3.js ├── api.js ├── data ├── abis │ ├── index.js │ └── json │ │ ├── 2pool │ │ └── swap.json │ │ ├── 3pool │ │ └── swap.json │ │ ├── ERC20.json │ │ ├── aRewards.json │ │ ├── aave │ │ └── swap.json │ │ ├── adapter.json │ │ ├── alusd │ │ ├── deposit.json │ │ ├── rewards.json │ │ └── swap.json │ │ ├── ankreth │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── atricrypto │ │ ├── swap.json │ │ ├── token.json │ │ └── zap.json │ │ ├── balancer.json │ │ ├── bbtc │ │ ├── deposit.json │ │ └── swap.json │ │ ├── busd │ │ ├── deposit.json │ │ └── swap.json │ │ ├── busdv2 │ │ ├── deposit.json │ │ └── swap.json │ │ ├── cERC20.json │ │ ├── compound │ │ ├── deposit.json │ │ ├── migration.json │ │ ├── oldSwap.json │ │ └── swap.json │ │ ├── cross_asset_swaps.json │ │ ├── dusd │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── eurs │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── fee_distro.json │ │ ├── frax │ │ ├── deposit.json │ │ ├── rewards.json │ │ └── swap.json │ │ ├── fusdt │ │ ├── deposit.json │ │ └── swap.json │ │ ├── gaugecontroller.json │ │ ├── gusd │ │ ├── deposit.json │ │ └── swap.json │ │ ├── hbtc │ │ └── swap.json │ │ ├── husd │ │ ├── deposit.json │ │ └── swap.json │ │ ├── ib │ │ └── swap.json │ │ ├── idle │ │ └── swap.json │ │ ├── idleERC20.json │ │ ├── iearn │ │ ├── apr.json │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── link │ │ └── swap.json │ │ ├── linkusd │ │ ├── deposit.json │ │ └── swap.json │ │ ├── liquiditygauge.json │ │ ├── liquiditygauge_v2.json │ │ ├── liquiditygauge_v3.json │ │ ├── lusd │ │ ├── deposit.json │ │ ├── rewards.json │ │ └── swap.json │ │ ├── minter.json │ │ ├── multicall.json │ │ ├── musd │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── obtc │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── paave │ │ └── rewards.json │ │ ├── pax │ │ ├── deposit.json │ │ └── swap.json │ │ ├── pbtc │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── registry.json │ │ ├── ren │ │ ├── adapter.json │ │ └── swap.json │ │ ├── reth │ │ └── swap.json │ │ ├── reward-contracts │ │ ├── v1.json │ │ ├── v2.json │ │ ├── v4.json │ │ └── vrewarddata.json │ │ ├── rsv │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── saave │ │ └── swap.json │ │ ├── sbtc │ │ ├── adapter.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── sbtcRewards.json │ │ ├── seth │ │ └── swap.json │ │ ├── steth │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── susd │ │ └── swap.json │ │ ├── susdv2 │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── synthERC20.json │ │ ├── synthetixExchanger.json │ │ ├── system_status_snx.json │ │ ├── tbtc │ │ ├── deposit.json │ │ ├── sCurveRewards.json │ │ └── swap.json │ │ ├── tricrypto │ │ ├── swap.json │ │ └── token.json │ │ ├── tusd │ │ ├── deposit.json │ │ └── swap.json │ │ ├── usdk │ │ ├── deposit.json │ │ └── swap.json │ │ ├── usdn │ │ ├── deposit.json │ │ └── swap.json │ │ ├── usdn_rewards.json │ │ ├── usdp │ │ ├── deposit.json │ │ └── swap.json │ │ ├── usdt │ │ ├── deposit.json │ │ └── swap.json │ │ ├── ust │ │ ├── deposit.json │ │ └── swap.json │ │ ├── votingescrow.json │ │ └── yERC20.json ├── assets-prices.js ├── curve-lending-vaults-data.js ├── curve-platform-registries.js ├── curve-pools-data.js ├── curve-prices.js ├── getCrvusdPrice.js ├── getDaiAPYs.js ├── getETHLSTAPYs.js ├── getEywaTokenPrices.js ├── getFactoryV2GaugeRewards.js ├── getFactoryV2SidechainGaugeRewards.js ├── getNapierTokenPrices.js ├── getSynthetixTokenPrices.js ├── getTempleTokenPrices.js ├── getYcTokenPrices.js ├── prices.curve.fi │ ├── chains.js │ ├── gauges.js │ └── pools-metadata.js ├── tokens-data.js └── tokens-prices.js ├── getters.js ├── helpers.js └── swr.js /.ebextensions/cloudwatch.config: -------------------------------------------------------------------------------- 1 | files: 2 | "/opt/aws/amazon-cloudwatch-agent/bin/config.json": 3 | mode: "000600" 4 | owner: root 5 | group: root 6 | content: | 7 | { 8 | "agent": { 9 | "metrics_collection_interval": 60, 10 | "run_as_user": "root" 11 | }, 12 | "metrics": { 13 | "namespace": "System/Linux", 14 | "append_dimensions": { 15 | "AutoScalingGroupName": "${aws:AutoScalingGroupName}" 16 | }, 17 | "metrics_collected": { 18 | "mem": { 19 | "measurement": [ 20 | "mem_used_percent" 21 | ] 22 | } 23 | } 24 | } 25 | } 26 | container_commands: 27 | start_cloudwatch_agent: 28 | command: /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a append-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json 29 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | NODE_ENV= 2 | DEV_REDIS_HOST= 3 | PROD_REDIS_HOST= 4 | BLOCKNATIVE_API_KEY= 5 | DRPC_API_KEY= 6 | LLAMANODES_API_KEY= 7 | ALCHEMI_API_KEY= 8 | API_KEY_AVALANCHE= 9 | CLOUDFRONT_SECRET= 10 | PORT= 11 | THEGRAPH_API_KEY= 12 | ALCHEMY_SUBGRAPH_API_KEY= 13 | GITHUB_API_UA= 14 | GITHUB_FINE_GRAINED_PERSONAL_ACCESS_TOKEN= 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .DS_Store 4 | 5 | # Elastic Beanstalk Files 6 | .elasticbeanstalk/* 7 | !.elasticbeanstalk/*.cfg.yml 8 | !.elasticbeanstalk/*.global.yml 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | 4 | 5 | OpenAPI specification: 6 | 7 | Status: 8 | 9 | # Data 10 | 11 | ## Ethereum LST APYs 12 | 13 | When a Curve pool contains an LST, the API includes its staking APY into the pool's base APY. 14 | This is the list of ETH LSTs currently supported by the API: https://github.com/curvefi/curve-api-metadata/blob/main/ethereum-lst-defillama.json 15 | If an ETH LST is missing from this list, feel free to add it: [info on how to do it](https://github.com/curvefi/curve-api-metadata/tree/main?tab=readme-ov-file#files) 16 | 17 | # Development 18 | 19 | ## Deployment 20 | 21 | When a PR is merged into `main`, the new app is automatically deployed (it takes roughly 15 minutes). 22 | 23 | # Technical setup 24 | 25 | - Environment variables: 26 | - Dev env variables are injected by dotenv-safe, using `.env.default` as template, and using values from `.env` 27 | - Prod env variables are injected by ElasticBeanstalk env variables 28 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import routeSetup from '#root/routes/_setup.js'; 3 | import accessControl from '#root/middleware/access-control.js'; 4 | 5 | const app = express(); 6 | 7 | app.use(accessControl); 8 | await routeSetup(app); 9 | app.use(express.static('public')); 10 | 11 | export default app; 12 | -------------------------------------------------------------------------------- /bin/www.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import app from '#root/app.js'; 4 | import { IS_DEV } from '#root/constants/AppConstants.js'; 5 | import cluster from 'cluster'; 6 | import Debug from 'debug'; 7 | import http from 'http'; 8 | import os from 'os'; 9 | 10 | const debug = Debug('nodejs-example-express-elasticache:server'); 11 | const cpuCount = os.cpus().length; 12 | const workers = {}; 13 | 14 | function spawn() { 15 | const worker = cluster.fork(); 16 | workers[worker.pid] = worker; 17 | return worker; 18 | } 19 | 20 | // Get port from environment and store in Express. 21 | const port = normalizePort(process.env.PORT || '3000'); 22 | app.set('port', port); 23 | 24 | if (cluster.isPrimary) { 25 | const workerCount = IS_DEV ? 1 : cpuCount; 26 | for (let i = 0; i < workerCount; i++) { 27 | spawn(); 28 | } 29 | 30 | // If a worker dies, log it to the console and start another worker. 31 | cluster.on('exit', function(worker, code, signal) { 32 | console.log('Worker ' + worker.process.pid + ' died.'); 33 | cluster.fork(); 34 | }); 35 | 36 | // Log when a worker starts listening 37 | cluster.on('listening', function(worker, address) { 38 | console.log('Worker started with PID ' + worker.process.pid + '.'); 39 | }); 40 | 41 | } else { 42 | let server = http.createServer(app); 43 | 44 | function onError(error) { 45 | if (error.syscall !== 'listen') { 46 | throw error; 47 | } 48 | 49 | const bind = typeof port === 'string' 50 | ? 'Pipe ' + port 51 | : 'Port ' + port; 52 | 53 | // handle specific listen errors with friendly messages 54 | switch (error.code) { 55 | case 'EACCES': 56 | console.error(bind + ' requires elevated privileges'); 57 | process.exit(1); 58 | break; 59 | case 'EADDRINUSE': 60 | console.error(bind + ' is already in use'); 61 | process.exit(1); 62 | break; 63 | default: 64 | throw error; 65 | } 66 | } 67 | 68 | function onListening() { 69 | const addr = server.address(); 70 | const bind = typeof addr === 'string' 71 | ? 'pipe ' + addr 72 | : 'port ' + addr.port; 73 | debug('Listening on ' + bind); 74 | } 75 | 76 | server.listen(port); 77 | server.on('error', onError); 78 | server.on('listening', onListening); 79 | } 80 | 81 | // Normalize a port into a number, string, or false. 82 | function normalizePort(val) { 83 | const port = parseInt(val, 10); 84 | 85 | if (isNaN(port)) { 86 | // named pipe 87 | return val; 88 | } 89 | 90 | if (port >= 0) { 91 | // port number 92 | return port; 93 | } 94 | 95 | return false; 96 | } 97 | 98 | process.on('SIGINT', () => { 99 | Object.values(workers).forEach((worker) => { 100 | worker.disconnect(); 101 | }); 102 | 103 | process.exit(0); 104 | }); 105 | -------------------------------------------------------------------------------- /constants/AppConstants.js: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv-safer'; 2 | 3 | // Ensure all required env variables are defined 4 | config({ 5 | example: './.env.default', 6 | }); 7 | 8 | const IS_DEV = process.env.NODE_ENV !== 'production'; 9 | 10 | const BASE_API_DOMAIN = IS_DEV ? 'http://localhost:3000' : 'https://api.curve.finance'; 11 | 12 | const REWARD_TOKENS_REPLACE_MAP = { 13 | }; 14 | 15 | /** 16 | * Flip to true to force using fallback data for thegraph (fallback data ideally 17 | * needs to be updated ahead of time if issues are predictable, e.g. planned maintenance). 18 | * Fallback data will already be used automatically if any thegraph request is failing, 19 | * so using that flip shouldn't be necessary at all. 20 | */ 21 | const USE_FALLBACK_THEGRAPH_DATA = false; 22 | 23 | // Flip to true in order to easily populate thegraph fallback data: each 24 | // place that's querying thegraph will dump its output, prefixed with 25 | // "FALLBACK_THEGRAPH_DATA_POPULATE_MODE" and the filename where to store 26 | // this data, in order to update that filename easily. 27 | const FALLBACK_THEGRAPH_DATA_POPULATE_MODE = false; 28 | 29 | const SMALL_POOLS_USDTOTAL_THRESHOLD = 10000; 30 | 31 | export { 32 | IS_DEV, 33 | REWARD_TOKENS_REPLACE_MAP, 34 | BASE_API_DOMAIN, 35 | USE_FALLBACK_THEGRAPH_DATA, 36 | FALLBACK_THEGRAPH_DATA_POPULATE_MODE, 37 | SMALL_POOLS_USDTOTAL_THRESHOLD, 38 | }; 39 | -------------------------------------------------------------------------------- /constants/CacheSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global cache settings: 3 | * - `minTimeToStale` is the number of milliseconds until a cache entry is considered 4 | * stale, and is then refreshed 5 | * - `maxTimeToLive` is the number of milliseconds until a cache entry is considered 6 | * expired (by opposition to *stale*): if considered expired, it will not be returned, 7 | * it will be refreshed instead and will only return after this fresh invocation 8 | * 9 | * Our node-side swr utility bases its behavior on both those props. They can both be 10 | * overridden in individual swr calls (in practice, `minTimeToStale` is most overridden 11 | * the most often). 12 | * Redis only uses `maxTimeToLive` as its `expiry` prop. 13 | * 14 | * `maxTimeToLive` is set to a very long period to prevent outtages at all costs. With 15 | * monitoring in place, any broken script or stale data would get caught rapidly, but 16 | * we never want a situation where the fix would take too long to ship, and the cache 17 | * entries would get deleted, and nothing could be served anymore by the api: so an 18 | * absurdly high value is used to minimize the risk of downtime, while still allowing 19 | * unused cache entries to be deleted in a reasonable timeframe. 20 | */ 21 | 22 | const CACHE_SETTINGS = { 23 | minTimeToStale: 30 * 1000, // 30s 24 | maxTimeToLive: 14 * 24 * 60 * 60 * 1000, // 14d 25 | serialize: JSON.stringify, // serialize product object to string 26 | deserialize: JSON.parse, // deserialize cached product string to object 27 | }; 28 | 29 | export default CACHE_SETTINGS; 30 | -------------------------------------------------------------------------------- /constants/CoinAddressReplacementMap.js: -------------------------------------------------------------------------------- 1 | const CoinAddressReplacementMap = { 2 | kava: { 3 | '0xc86c7c0efbd6a49b35e8714c5f59d99de09a225b': '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', // wKAVA -> KAVA 4 | }, 5 | }; 6 | 7 | export default CoinAddressReplacementMap; 8 | -------------------------------------------------------------------------------- /constants/Web3.js: -------------------------------------------------------------------------------- 1 | import BN from 'bignumber.js'; 2 | 3 | const CHAIN_ID = 1; 4 | const DECIMALS_WEI = 1e18; 5 | const DECIMALS_GWEI = 1e9; 6 | const MAX_UINT256 = BN(2).pow(256).minus(1); 7 | 8 | const RPC_URL = `https://direct.drpc.org/ogrpc?network=ethereum&dkey=${process.env.DRPC_API_KEY}`; 9 | const RPC_BACKUP_URL = `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMI_API_KEY}`; 10 | 11 | const RPC_URL_BSC = 'https://bsc-dataseed.binance.org/'; 12 | 13 | const BASE_URL_EXPLORER_ADDRESS = 'https://etherscan.io/address/'; 14 | 15 | export { 16 | CHAIN_ID, 17 | DECIMALS_WEI, 18 | DECIMALS_GWEI, 19 | RPC_URL, 20 | RPC_BACKUP_URL, 21 | RPC_URL_BSC, 22 | BASE_URL_EXPLORER_ADDRESS, 23 | MAX_UINT256, 24 | }; 25 | -------------------------------------------------------------------------------- /constants/abis/address_getter.json: -------------------------------------------------------------------------------- 1 | [{"name":"NewAddressIdentifier","inputs":[{"type":"uint256","name":"id","indexed":true},{"type":"address","name":"addr","indexed":false},{"type":"string","name":"description","indexed":false}],"anonymous":false,"type":"event"},{"name":"AddressModified","inputs":[{"type":"uint256","name":"id","indexed":true},{"type":"address","name":"new_address","indexed":false},{"type":"uint256","name":"version","indexed":false}],"anonymous":false,"type":"event"},{"name":"CommitNewAdmin","inputs":[{"type":"uint256","name":"deadline","indexed":true},{"type":"address","name":"admin","indexed":true}],"anonymous":false,"type":"event"},{"name":"NewAdmin","inputs":[{"type":"address","name":"admin","indexed":true}],"anonymous":false,"type":"event"},{"outputs":[],"inputs":[{"type":"address","name":"_admin"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"get_registry","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1061},{"name":"max_id","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1258},{"name":"get_address","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"_id"}],"stateMutability":"view","type":"function","gas":1308},{"name":"add_new_id","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_address"},{"type":"string","name":"_description"}],"stateMutability":"nonpayable","type":"function","gas":291275},{"name":"set_address","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"uint256","name":"_id"},{"type":"address","name":"_address"}],"stateMutability":"nonpayable","type":"function","gas":182430},{"name":"unset_address","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"uint256","name":"_id"}],"stateMutability":"nonpayable","type":"function","gas":101348},{"name":"commit_transfer_ownership","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"address","name":"_new_admin"}],"stateMutability":"nonpayable","type":"function","gas":74048},{"name":"apply_transfer_ownership","outputs":[{"type":"bool","name":""}],"inputs":[],"stateMutability":"nonpayable","type":"function","gas":60125},{"name":"revert_transfer_ownership","outputs":[{"type":"bool","name":""}],"inputs":[],"stateMutability":"nonpayable","type":"function","gas":21400},{"name":"admin","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1331},{"name":"transfer_ownership_deadline","outputs":[{"type":"uint256","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1361},{"name":"future_admin","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1391},{"name":"get_id_info","outputs":[{"type":"address","name":"addr"},{"type":"bool","name":"is_active"},{"type":"uint256","name":"version"},{"type":"uint256","name":"last_modified"},{"type":"string","name":"description"}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":12168}] 2 | -------------------------------------------------------------------------------- /constants/abis/aggregator.json: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"int256","name":"current","type":"int256"},{"indexed":true,"internalType":"uint256","name":"roundId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"AnswerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"roundId","type":"uint256"},{"indexed":true,"internalType":"address","name":"startedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"startedAt","type":"uint256"}],"name":"NewRound","type":"event"},{"inputs":[{"internalType":"uint256","name":"roundId","type":"uint256"}],"name":"getAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"roundId","type":"uint256"}],"name":"getTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRound","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /constants/abis/crv-circsupply-util.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "stateMutability": "nonpayable", 4 | "type": "constructor", 5 | "inputs": [], 6 | "outputs": [] 7 | }, 8 | { 9 | "stateMutability": "nonpayable", 10 | "type": "function", 11 | "name": "add_contract", 12 | "inputs": [ 13 | { 14 | "name": "_contract", 15 | "type": "address" 16 | } 17 | ], 18 | "outputs": [] 19 | }, 20 | { 21 | "stateMutability": "view", 22 | "type": "function", 23 | "name": "circulating_supply", 24 | "inputs": [], 25 | "outputs": [ 26 | { 27 | "name": "", 28 | "type": "uint256" 29 | } 30 | ] 31 | }, 32 | { 33 | "stateMutability": "nonpayable", 34 | "type": "function", 35 | "name": "set_admin", 36 | "inputs": [ 37 | { 38 | "name": "_new_admin", 39 | "type": "address" 40 | } 41 | ], 42 | "outputs": [] 43 | }, 44 | { 45 | "stateMutability": "view", 46 | "type": "function", 47 | "name": "admin", 48 | "inputs": [], 49 | "outputs": [ 50 | { 51 | "name": "", 52 | "type": "address" 53 | } 54 | ] 55 | }, 56 | { 57 | "stateMutability": "view", 58 | "type": "function", 59 | "name": "CRV", 60 | "inputs": [], 61 | "outputs": [ 62 | { 63 | "name": "", 64 | "type": "address" 65 | } 66 | ] 67 | }, 68 | { 69 | "stateMutability": "view", 70 | "type": "function", 71 | "name": "contracts", 72 | "inputs": [ 73 | { 74 | "name": "arg0", 75 | "type": "uint256" 76 | } 77 | ], 78 | "outputs": [ 79 | { 80 | "name": "", 81 | "type": "address" 82 | } 83 | ] 84 | }, 85 | { 86 | "stateMutability": "view", 87 | "type": "function", 88 | "name": "num_contracts", 89 | "inputs": [], 90 | "outputs": [ 91 | { 92 | "name": "", 93 | "type": "uint256" 94 | } 95 | ] 96 | }, 97 | { 98 | "stateMutability": "view", 99 | "type": "function", 100 | "name": "cached_contracts", 101 | "inputs": [ 102 | { 103 | "name": "arg0", 104 | "type": "uint256" 105 | } 106 | ], 107 | "outputs": [ 108 | { 109 | "name": "", 110 | "type": "address" 111 | } 112 | ] 113 | } 114 | ] -------------------------------------------------------------------------------- /constants/abis/erc20.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] 2 | -------------------------------------------------------------------------------- /constants/abis/multicall.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"name":"timestamp","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"components":[{"name":"target","type":"address"},{"name":"callData","type":"bytes"}],"name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"name":"blockNumber","type":"uint256"},{"name":"returnData","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getLastBlockHash","outputs":[{"name":"blockHash","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"name":"difficulty","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentBlockGasLimit","outputs":[{"name":"gaslimit","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"name":"coinbase","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"name":"blockHash","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /constants/abis/multicall2.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall2.Call[]","name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes[]","name":"returnData","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall2.Call[]","name":"calls","type":"tuple[]"}],"name":"blockAndAggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall2.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBlockNumber","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"internalType":"address","name":"coinbase","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"internalType":"uint256","name":"difficulty","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockGasLimit","outputs":[{"internalType":"uint256","name":"gaslimit","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"requireSuccess","type":"bool"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall2.Call[]","name":"calls","type":"tuple[]"}],"name":"tryAggregate","outputs":[{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall2.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"requireSuccess","type":"bool"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall2.Call[]","name":"calls","type":"tuple[]"}],"name":"tryBlockAndAggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall2.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"}] 2 | -------------------------------------------------------------------------------- /constants/abis/sidechain-root-gauge.json: -------------------------------------------------------------------------------- 1 | [{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_crv_token","type":"address"},{"name":"_gauge_controller","type":"address"},{"name":"_minter","type":"address"}],"outputs":[]},{"stateMutability":"payable","type":"fallback"},{"stateMutability":"nonpayable","type":"function","name":"transmit_emissions","inputs":[],"outputs":[]},{"stateMutability":"view","type":"function","name":"integrate_fraction","inputs":[{"name":"_user","type":"address"}],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"nonpayable","type":"function","name":"user_checkpoint","inputs":[{"name":"_user","type":"address"}],"outputs":[{"name":"","type":"bool"}]},{"stateMutability":"nonpayable","type":"function","name":"set_killed","inputs":[{"name":"_is_killed","type":"bool"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"update_bridger","inputs":[],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"initialize","inputs":[{"name":"_bridger","type":"address"},{"name":"_chain_id","type":"uint256"}],"outputs":[]},{"stateMutability":"view","type":"function","name":"chain_id","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"bridger","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"factory","inputs":[],"outputs":[{"name":"","type":"address"}]},{"stateMutability":"view","type":"function","name":"inflation_params","inputs":[],"outputs":[{"name":"","type":"tuple","components":[{"name":"rate","type":"uint256"},{"name":"finish_time","type":"uint256"}]}]},{"stateMutability":"view","type":"function","name":"last_period","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"total_emissions","inputs":[],"outputs":[{"name":"","type":"uint256"}]},{"stateMutability":"view","type":"function","name":"is_killed","inputs":[],"outputs":[{"name":"","type":"bool"}]}] 2 | -------------------------------------------------------------------------------- /constants/coins/index.js: -------------------------------------------------------------------------------- 1 | import coins from '#root/constants/coins/coins.js'; 2 | import { arrayToHashmap } from '#root/utils/Array.js'; 3 | import REFERENCE_ASSETS from '../reference-assets.json' assert { type: 'json' }; 4 | import validateCoinConfigs from './init-validation.js'; 5 | 6 | const FIAT_ASSET_TYPES = [ 7 | REFERENCE_ASSETS.USD, 8 | REFERENCE_ASSETS.EUR, 9 | ]; 10 | 11 | const defaultCoinTemplate = { 12 | id: undefined, 13 | coingeckoId: undefined, 14 | contractKey: undefined, 15 | type: undefined, 16 | symbol: undefined, 17 | wrappedSymbol: undefined, 18 | wrapperSymbol: undefined, 19 | decimals: undefined, 20 | address: undefined, 21 | isSynth: false, 22 | isLpToken: false, 23 | wrappedCoinType: null, 24 | }; 25 | 26 | class Coin { 27 | constructor(props) { 28 | Object.assign(this, defaultCoinTemplate, props); 29 | } 30 | 31 | isWrappedCoin() { 32 | return this.wrappedCoinType !== null; 33 | } 34 | 35 | isFiat() { 36 | return FIAT_ASSET_TYPES.includes(this.type); 37 | } 38 | } 39 | 40 | const augmentedCoins = arrayToHashmap(Array.from(Object.entries(coins)).map(([id, props]) => [id, new Coin(props)])); 41 | validateCoinConfigs(augmentedCoins); 42 | 43 | const coinsProxy = new Proxy(augmentedCoins, { 44 | get(target, name) { 45 | if ( 46 | typeof name === 'string' && 47 | !(name in target) && 48 | name !== 'inspect' && 49 | name !== 'state' && 50 | name !== 'render' && 51 | !name.startsWith('_') // Ignore "private" props 52 | ) { 53 | throw new Error(`Property "${name}" not found on object "coins"`); 54 | } 55 | 56 | return target[name]; 57 | }, 58 | }); 59 | 60 | export default coinsProxy; 61 | const _Coin = Coin; 62 | export { _Coin as Coin }; 63 | 64 | export function getCoinByAddress(address) { 65 | const lcAddress = address.toLowerCase(); 66 | const coinFromAddress = Array.from(Object.values(augmentedCoins)).find((coin) => { 67 | if (!(coin instanceof Coin)) return false; // Necessary because of exports overriding the coins object 68 | return lcAddress === coin.address.toLowerCase(); 69 | }); 70 | 71 | if (!coinFromAddress) throw new Error(`Couldn’t find coin for address ${address}`); 72 | 73 | return coinFromAddress; 74 | } 75 | -------------------------------------------------------------------------------- /constants/coins/init-validation.js: -------------------------------------------------------------------------------- 1 | import { flattenArray } from '#root/utils/Array.js'; 2 | import { IS_DEV } from '#root/constants/AppConstants.js'; 3 | 4 | const checks = [{ 5 | description: 'Coins which aren’t lp tokens (`isLpToken = false`) must have a `coingeckoId` prop defined', 6 | failsIfFn: (coin) => (!coin.isLpToken && typeof coin.coingeckoId === 'undefined'), 7 | }, { 8 | description: '`decimals` must indicate the number of decimals of a coin (e.g. 18 not 1e18)', 9 | failsIfFn: (coin) => (coin?.decimals > 30), 10 | }]; 11 | 12 | const validatePoolConfigs = (coins) => { 13 | if (!IS_DEV) return; 14 | 15 | const errors = flattenArray(Array.from(Object.values(coins)).map((coin) => ( 16 | checks 17 | .filter(({ failsIfFn }) => failsIfFn(coin)) 18 | .map(({ description }) => `${description} [coin: ${coin.id}]`) 19 | ))); 20 | 21 | if (errors.length > 0) { 22 | throw new Error(`Error${errors.length > 1 ? 's' : ''} found in coins config:\n\n${errors.join('\n')}\n`); 23 | } 24 | }; 25 | 26 | export default validatePoolConfigs; 27 | -------------------------------------------------------------------------------- /constants/configs/index.js: -------------------------------------------------------------------------------- 1 | import configs from './configs.js'; 2 | import validateConfigs from './init-validation.js'; 3 | 4 | validateConfigs(configs); 5 | 6 | const getConfigByRpcUrl = (rpcUrl) => ( 7 | Array.from(Object.entries(configs)).find(([, config]) => config.rpcUrl === rpcUrl) 8 | ); 9 | 10 | export default configs; 11 | export { getConfigByRpcUrl }; 12 | -------------------------------------------------------------------------------- /constants/configs/init-validation.js: -------------------------------------------------------------------------------- 1 | import { flattenArray } from '#root/utils/Array.js'; 2 | import { IS_DEV } from '#root/constants/AppConstants.js'; 3 | 4 | const checks = [{ 5 | description: 'Sidechain config must have a `shortId` property', 6 | failsIfFn: (id, config) => (id !== 'ethereum' && !config.shortId), 7 | }, { 8 | description: '`gaugeRegistryAddress2` and `gaugeRootRegistry2` must be either both defined or undefined: if one exists, the other must too', 9 | failsIfFn: (id, config) => (typeof config.gaugeRegistryAddress2 !== typeof config.gaugeRootRegistry2), 10 | }]; 11 | 12 | const validateConfigs = (configs) => { 13 | if (!IS_DEV) return; 14 | 15 | const errors = flattenArray(Array.from(Object.entries(configs)).map(([id, config]) => ( 16 | checks 17 | .filter(({ failsIfFn }) => failsIfFn(id, config)) 18 | .map(({ description }) => `${description} [config: ${id}]`) 19 | ))); 20 | 21 | if (errors.length > 0) { 22 | throw new Error(`Error${errors.length > 1 ? 's' : ''} found in configs config:\n\n${errors.join('\n')}\n`); 23 | } 24 | }; 25 | 26 | export default validateConfigs; 27 | -------------------------------------------------------------------------------- /constants/pools-zaps/arbitrum.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": { 3 | "v1metausd": "0x7544fe3d184b6b55d6b36c3fca1157ee0ba30287", 4 | "metausd": "0x7544fe3d184b6b55d6b36c3fca1157ee0ba30287", 5 | "metausdbalances": "0x7544fe3d184b6b55d6b36c3fca1157ee0ba30287", 6 | "metausd-factory-v2-41": "0x58ac91f5be7dc0c35b24b96b19bac55fbb8e705e", 7 | "metausdbalances-factory-v2-41": "0x58ac91f5be7dc0c35b24b96b19bac55fbb8e705e", 8 | "v1metabtc": "0x803a2b40c5a9bb2b86dd630b274fa2a9202874c2", 9 | "metabtc": "0x803a2b40c5a9bb2b86dd630b274fa2a9202874c2", 10 | "metabtcbalances": "0x803a2b40c5a9bb2b86dd630b274fa2a9202874c2" 11 | }, 12 | "pools": { 13 | "0x960ea3e3c7fb317332d990873d354e18d7645590": "0xf97c707024ef0dd3e77a0824555a46b622bfb500", 14 | "0xa827a652ead76c6b0b3d19dba05452e06e25c27e": "0x25e2e8d104bc1a70492e2be32da7c1f8367f9d2c" 15 | } 16 | } -------------------------------------------------------------------------------- /constants/pools-zaps/avalanche.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": { 3 | "v1metausd": "0x001e3ba199b4ff4b5b6e97acd96dafc0e2e4156e", 4 | "metausd": "0x001e3ba199b4ff4b5b6e97acd96dafc0e2e4156e", 5 | "metausdbalances": "0x001e3ba199b4ff4b5b6e97acd96dafc0e2e4156e", 6 | "metabtc": "0xEeB3DDBcc4174e0b3fd1C13aD462b95D11Ef42C3", 7 | "metabtcbalances": "0xEeB3DDBcc4174e0b3fd1C13aD462b95D11Ef42C3" 8 | }, 9 | "pools": { 10 | "0xb755b949c126c04e0348dd881a5cf55d424742b2": "0x58e57ca18b7a47112b877e31929798cd3d703b0f", 11 | "0x204f0620e7e7f07b780535711884835977679bba": "0x9f2Fa7709B30c75047980a0d70A106728f0Ef2db" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /constants/pools-zaps/fantom.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": { 3 | "v1metausd": "0x78d51eb71a62c081550efcc0a9f9ea94b2ef081c", 4 | "metausd": "0x78d51eb71a62c081550efcc0a9f9ea94b2ef081c", 5 | "metausdbalances": "0x78d51eb71a62c081550efcc0a9f9ea94b2ef081c", 6 | "metausd-geist": "0x247aeb220e87f24c40c9f86b65d6bd5d3c987b55", 7 | "metausdbalances-geist": "0x247aeb220e87f24c40c9f86b65d6bd5d3c987b55", 8 | "v1metabtc": "0x001e3ba199b4ff4b5b6e97acd96dafc0e2e4156e", 9 | "metabtc": "0x001e3ba199b4ff4b5b6e97acd96dafc0e2e4156e", 10 | "metabtcbalances": "0x001e3ba199b4ff4b5b6e97acd96dafc0e2e4156e" 11 | }, 12 | "pools": { 13 | "0x92d5ebf3593a92888c25c0abef126583d4b5312e": "0xa42bd395f183726d1a8774cfa795771f8acfd777" 14 | } 15 | } -------------------------------------------------------------------------------- /constants/pools-zaps/harmony.json: -------------------------------------------------------------------------------- 1 | { 2 | "pools": { 3 | "0x0e3dc2bcbfea84072a0c794b7653d3db364154e0": "0x76147c0c989670d106b57763a24410a2a22e335e" 4 | } 5 | } -------------------------------------------------------------------------------- /constants/pools-zaps/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * There is no onchain registry for zaps, so this is a hardcoded registry. 3 | * Note: JSON files need ALL addresses to be lowercase in order to match properly. 4 | */ 5 | 6 | import arbitrum from './arbitrum.json' assert { type: 'json' }; 7 | import avalanche from './avalanche.json' assert { type: 'json' }; 8 | import ethereum from './ethereum.json' assert { type: 'json' }; 9 | import fantom from './fantom.json' assert { type: 'json' }; 10 | import harmony from './harmony.json' assert { type: 'json' }; 11 | import optimism from './optimism.json' assert { type: 'json' }; 12 | import polygon from './polygon.json' assert { type: 'json' }; 13 | import xdai from './xdai.json' assert { type: 'json' }; 14 | 15 | export default { 16 | arbitrum, 17 | avalanche, 18 | ethereum, 19 | fantom, 20 | harmony, 21 | optimism, 22 | polygon, 23 | xdai, 24 | }; 25 | -------------------------------------------------------------------------------- /constants/pools-zaps/mantle.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": {} 3 | } 4 | -------------------------------------------------------------------------------- /constants/pools-zaps/optimism.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": { 3 | "v1metausd": "0x167e42a1c7ab4be03764a2222aac57f5f6754411", 4 | "metausd": "0x167e42a1c7ab4be03764a2222aac57f5f6754411", 5 | "metausdbalances": "0x167e42a1c7ab4be03764a2222aac57f5f6754411", 6 | "metausd-factory-v2-16": "0x4244eb811d6e0ef302326675207a95113db4e1f8", 7 | "metausdbalances-factory-v2-16": "0x4244eb811d6e0ef302326675207a95113db4e1f8" 8 | } 9 | } -------------------------------------------------------------------------------- /constants/pools-zaps/polygon.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": { 3 | "v1metausd": "0x5ab5c56b9db92ba45a0b46a207286cd83c15c939", 4 | "metausd": "0x5ab5c56b9db92ba45a0b46a207286cd83c15c939", 5 | "metausdbalances": "0x5ab5c56b9db92ba45a0b46a207286cd83c15c939", 6 | "v1metabtc": "0xe2e6dc1708337a6e59f227921db08f21e3394723", 7 | "metabtc": "0xe2e6dc1708337a6e59f227921db08f21e3394723", 8 | "metabtcbalances": "0xe2e6dc1708337a6e59f227921db08f21e3394723", 9 | "metacrypto": "0x3d8eadb739d1ef95dd53d718e4810721837c69c1" 10 | }, 11 | "pools": { 12 | "0x751b1e21756bdbc307cbcc5085c042a0e9aaef36": "0x3fcd5de6a9fc8a99995c406c77dda3ed7e406f81", 13 | "0x92577943c7ac4accb35288ab2cc84d75fec330af": "0x3fa8ebd5d16445b42e0b6a54678718c94ea99abc", 14 | "0x92215849c439e1f8612b6646060b4e3e5ef822cc": "0x1d8b86e3d88cdb2d34688e87e72f388cb541b7c8", 15 | "0xb446bf7b8d6d4276d0c75ec0e3ee8dd7fe15783a": "0x225fb4176f0e20cdb66b4a3df70ca3063281e855", 16 | "0x9b3d675fdbe6a0935e8b7d1941bc6f78253549b7": "0x4df7ef55e99a56851187822d96b4e17d98a47ded" 17 | } 18 | } -------------------------------------------------------------------------------- /constants/pools-zaps/x-layer.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": { 3 | "stablenp-todo-zap-address-is-correct-but-implementation-id-not": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /constants/pools-zaps/xdai.json: -------------------------------------------------------------------------------- 1 | { 2 | "implementations": { 3 | "v1metausd": "0x87c067fac25f123554a0e76596bf28cfa37fd5e9", 4 | "metausd": "0x87c067fac25f123554a0e76596bf28cfa37fd5e9", 5 | "metausdbalances": "0x87c067fac25f123554a0e76596bf28cfa37fd5e9" 6 | }, 7 | "pools": { 8 | "0x85ba9dfb4a3e4541420fc75be02e2b42042d7e46": "0xdf6eb52c4a9d7d5964b918c50d47a643fd7d3d4c", 9 | "0x5633e00994896d0f472926050ecb32e38bef3e65": "0xF182926A64C0A19234E7E1FCDfE772aA7A1CA351", 10 | "0x056c6c5e684cec248635ed86033378cc444459b0": "0xe3fff29d4dc930ebb787fecd49ee5963dadf60b6" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /constants/pools/init-validation.js: -------------------------------------------------------------------------------- 1 | import { flattenArray } from '#root/utils/Array.js'; 2 | import { IS_DEV } from '../AppConstants.js'; 3 | 4 | const checks = [{ 5 | description: 'Meta pools must have a deposit zap', 6 | failsIfFn: (pool) => (pool.isMetaPool && pool.addresses.deposit === null), 7 | }, { 8 | description: 'Prop riskLevel must be defined if isRiskier = true', 9 | failsIfFn: (pool) => (pool.isRiskier && typeof pool.riskLevel === 'undefined'), 10 | }]; 11 | 12 | const validatePoolConfigs = (pools) => { 13 | if (!IS_DEV) return; 14 | 15 | const errors = flattenArray(pools.map((pool) => ( 16 | checks 17 | .filter(({ failsIfFn }) => failsIfFn(pool)) 18 | .map(({ description }) => `${description} [pool: ${pool.id}]`) 19 | ))); 20 | 21 | if (errors.length > 0) { 22 | throw new Error(`Error${errors.length > 1 ? 's' : ''} found in pools config:\n\n${errors.join('\n')}\n`); 23 | } 24 | }; 25 | 26 | export default validatePoolConfigs; 27 | -------------------------------------------------------------------------------- /constants/reference-assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "USD": "usd", 3 | "EUR": "eur", 4 | "BTC": "btc", 5 | "ETH": "eth", 6 | "LINK": "link", 7 | "SPELL": "spell", 8 | "T": "t", 9 | "MATIC": "matic", 10 | "FTM": "ftm", 11 | "AVAX": "avax", 12 | "SNX": "snx", 13 | "BNB": "bnb" 14 | } 15 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "#root/*": [ 6 | "./*" 7 | ] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /middleware/access-control.js: -------------------------------------------------------------------------------- 1 | import { IS_DEV } from "#root/constants/AppConstants.js"; 2 | 3 | export default (req, res, next) => { 4 | if (IS_DEV) next(); 5 | else if (req.header('X-Cloudfront-Secret') === process.env.CLOUDFRONT_SECRET) next(); 6 | else res.status(400).send('Direct access not allowed'); 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curve-api", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "test": "jest ./utils/*.test.js", 8 | "test:Number": "jest ./utils/Number.test.js", 9 | "test:Function": "jest ./utils/Function.test.js" 10 | }, 11 | "type": "module", 12 | "imports": { 13 | "#root/*.js": "./*.js", 14 | "#root/*.json": "./*.json" 15 | }, 16 | "dependencies": { 17 | "@curvefi/stablecoin-api": "^1.5.7", 18 | "@octokit/rest": "^21.1.0", 19 | "bignumber.js": "^9.1.2", 20 | "body-parser": "^1.20.2", 21 | "core-js-pure": "^3.36.1", 22 | "date-fns": "^3.3.1", 23 | "debug": "~2.6.9", 24 | "dotenv-safer": "1.0.0", 25 | "exponential-backoff": "^3.1.1", 26 | "express": "~4.18.2", 27 | "form-urlencoded": "^6.1.4", 28 | "graphql-request": "^6.1.0", 29 | "ioredis": "^5.3.2", 30 | "lodash.groupby": "^4.6.0", 31 | "lodash.partition": "^4.6.0", 32 | "lodash.uniq": "^4.5.0", 33 | "lodash.uniqby": "^4.7.0", 34 | "memoizee": "^0.4.15", 35 | "stale-while-revalidate-cache": "^3.2.1", 36 | "swagger-jsdoc": "^6.2.8", 37 | "swagger-ui-express": "^5.0.0", 38 | "web3": "^1.3.4" 39 | }, 40 | "devDependencies": { 41 | "jest": "^29.7.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/css/swaggerui.css: -------------------------------------------------------------------------------- 1 | .topbar, 2 | .curl-command, 3 | .response-col_links, 4 | /* .opblock-tag, */ 5 | .try-out__btn, 6 | .copy-to-clipboard { 7 | display: none !important; 8 | } 9 | 10 | .opblock-description { 11 | white-space: pre-line; 12 | } 13 | 14 | .swagger-ui .markdown code, 15 | .swagger-ui .renderedMarkdown code { 16 | color: inherit; 17 | padding: 2px; 18 | font-weight: inherit; 19 | font-size: 0.9em; 20 | } 21 | 22 | .renderedMarkdown p:not(:first-child) { 23 | margin-top: 0; 24 | } 25 | 26 | .renderedMarkdown p:not(:last-child) { 27 | margin-bottom: 0; 28 | } 29 | 30 | .opblock-summary-path__deprecated { 31 | text-decoration: none !important; 32 | } 33 | 34 | .opblock-summary-path__deprecated::after { 35 | content: "(deprecated)"; 36 | margin-left: 5px; 37 | font-weight: normal; 38 | } 39 | 40 | .opblock-description a { 41 | text-decoration: underline !important; 42 | color: #3b4151 !important; 43 | } 44 | 45 | .swagger-ui ul { 46 | margin: 0; 47 | margin-top: -5px; 48 | padding-left: 20px; 49 | font-size: 14px; 50 | line-height: 1.5em; 51 | white-space: normal; 52 | } 53 | -------------------------------------------------------------------------------- /routes/root.js: -------------------------------------------------------------------------------- 1 | import { BASE_API_DOMAIN } from "#root/constants/AppConstants.js"; 2 | 3 | const MAX_AGE = 30 * 24 * 60 * 60; 4 | 5 | export default (_, res) => { 6 | res.setHeader('Cache-Control', `max-age=${MAX_AGE}, s-maxage=${MAX_AGE}, stale-while-revalidate`); 7 | res.status(200).send(`API docs: ${BASE_API_DOMAIN}/v1/documentation`); 8 | }; 9 | -------------------------------------------------------------------------------- /routes/v1/getAllPoolsVolume/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getAllPoolsVolume/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Volumes and APYs 7 | * description: Returns total 24h volume for a chain. 8 | * parameters: 9 | * - $ref: '#/components/parameters/blockchainId' 10 | * responses: 11 | * 200: 12 | * description: 13 | */ 14 | 15 | import { NotFoundError, fn } from '#root/utils/api.js'; 16 | import getSubgraphDataFn from '#root/routes/v1/getSubgraphData/[blockchainId].js'; 17 | import getFactoryAPYsFn from '#root/routes/v1/getFactoryAPYs/[blockchainId]/[version].js'; 18 | 19 | export default fn(async ({ blockchainId }) => { 20 | try { 21 | const { totalVolume, cryptoShare } = await getSubgraphDataFn.straightCall({ blockchainId }); 22 | return { 23 | totalVolume, 24 | cryptoShare 25 | }; 26 | } catch (err) { 27 | // Fallback for chains without subgraph available; inaccurate because misses facto-crypto 28 | // this won't be necessary anymore once we've moved to curve-prices for all chains 29 | if (err instanceof NotFoundError) { 30 | const dataStable = await getFactoryAPYsFn.straightCall({ blockchainId, version: 'stable' }); 31 | const dataCrypto = await getFactoryAPYsFn.straightCall({ blockchainId, version: 'crypto' }); 32 | const totalVolume = dataStable.totalVolume + dataCrypto.totalVolume; 33 | 34 | return { 35 | totalVolume, 36 | cryptoShare: dataCrypto.totalVolume / totalVolume * 100, 37 | }; 38 | } 39 | 40 | throw err; 41 | } 42 | }, { 43 | maxAge: 10 * 60, // 10m 44 | cacheKey: ({ blockchainId }) => `getAllPoolsVolume-${blockchainId}`, 45 | }); 46 | -------------------------------------------------------------------------------- /routes/v1/getCrvCircSupply.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getCrvCircSupply: 4 | * get: 5 | * tags: 6 | * - crvUSD 7 | * description: Returns the circulating supply of crvUSD 8 | * responses: 9 | * 200: 10 | * description: 11 | */ 12 | 13 | import { fn } from '#root/utils/api.js'; 14 | import { multiCall } from '#root/utils/Calls.js'; 15 | import { trunc } from '#root/utils/Number.js'; 16 | import CRV_CIRCSUPPLY_UTIL_ABI from '#root/constants/abis/crv-circsupply-util.json' assert { type: 'json' }; 17 | 18 | const CRV_CIRCSUPPLY_UTIL_CONTRACT_ADDRESS = '0x14139EB676342b6bC8E41E0d419969f23A49881e'; 19 | 20 | export default fn(async () => { 21 | const [circSupply] = await multiCall([{ 22 | address: CRV_CIRCSUPPLY_UTIL_CONTRACT_ADDRESS, 23 | abi: CRV_CIRCSUPPLY_UTIL_ABI, 24 | methodName: 'circulating_supply', 25 | }]); 26 | 27 | return { 28 | crvCirculatingSupply: trunc(circSupply / 1e18), 29 | }; 30 | }, { 31 | maxAge: 60 * 60, // 1h 32 | cacheKey: 'getCrvCircSupply', 33 | }); 34 | -------------------------------------------------------------------------------- /routes/v1/getCrvusdTotalSupply.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getCrvusdTotalSupply: 4 | * get: 5 | * tags: 6 | * - crvUSD 7 | * description: Returns the total supply of crvUSD 8 | * responses: 9 | * 200: 10 | * description: 11 | */ 12 | 13 | import { fn } from '#root/utils/api.js'; 14 | import crvusd from "@curvefi/stablecoin-api"; 15 | import configs from '#root/constants/configs/index.js' 16 | 17 | export default fn(async () => { 18 | await crvusd.default.init('JsonRpc', { url: configs.ethereum.rpcUrl, privateKey: '' }, { gasPrice: 0, maxFeePerGas: 0, maxPriorityFeePerGas: 0, chainId: 1 }); 19 | 20 | const crvusdTotalSupply = await crvusd.default.totalSupply(); 21 | 22 | return { 23 | crvusdTotalSupply: crvusdTotalSupply.total, 24 | }; 25 | }, { 26 | maxAge: 5 * 60, // 5m 27 | cacheKey: 'getCrvusdTotalSupply', 28 | }); 29 | -------------------------------------------------------------------------------- /routes/v1/getCrvusdTotalSupplyNumber.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getCrvusdTotalSupplyNumber: 4 | * get: 5 | * tags: 6 | * - crvUSD 7 | * description: Returns the total supply of crvUSD as a number 8 | * responses: 9 | * 200: 10 | * description: 11 | */ 12 | 13 | import { fn } from '#root/utils/api.js'; 14 | import crvusd from "@curvefi/stablecoin-api"; 15 | import configs from '#root/constants/configs/index.js' 16 | 17 | export default fn(async () => { 18 | await crvusd.default.init('JsonRpc', { url: configs.ethereum.rpcUrl, privateKey: '' }, { gasPrice: 0, maxFeePerGas: 0, maxPriorityFeePerGas: 0, chainId: 1 }); 19 | 20 | const crvusdTotalSupply = await crvusd.default.totalSupply(); 21 | 22 | return Number(crvusdTotalSupply.total); 23 | }, { 24 | maxAge: 5 * 60, // 5m 25 | cacheKey: 'getCrvusdTotalSupplyNumber', 26 | returnFlatData: true, 27 | appendGeneratedTime: false, 28 | }); 29 | -------------------------------------------------------------------------------- /routes/v1/getETHprice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getETHprice: 4 | * get: 5 | * deprecated: true 6 | * tags: 7 | * - Deprecated 8 | * description: Returns the current Ethereum USD price 9 | * responses: 10 | * 200: 11 | * description: 12 | */ 13 | 14 | import Web3 from 'web3'; 15 | import * as WEB3_CONSTANTS from '#root/constants/Web3.js' 16 | import { fn } from '#root/utils/api.js'; 17 | import aggregatorInterfaceABI from '#root/constants/abis/aggregator.json' assert { type: 'json' }; 18 | 19 | const web3 = new Web3(WEB3_CONSTANTS.RPC_URL); 20 | const chainlinkETHUSDaddress = '0xF79D6aFBb6dA890132F9D7c355e3015f15F3406F'; 21 | 22 | export default fn(async () => { 23 | const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 1000, { data: 'chainlink' })); 24 | const data = await Promise.race([ 25 | (await fetch('https://api.coinpaprika.com/v1/tickers/eth-ethereum')).json(), 26 | (await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd')).json(), 27 | timeoutPromise, 28 | ]); 29 | 30 | let price; 31 | 32 | if (data.quotes) { 33 | price = data.quotes.USD.price; 34 | } else if (data.ethereum) { 35 | price = data.ethereum.usd; 36 | } else { 37 | const ETHUSDpricefeed = new web3.eth.Contract(aggregatorInterfaceABI, chainlinkETHUSDaddress); 38 | price = await ETHUSDpricefeed.methods.latestAnswer().call() / 1e8; 39 | } 40 | return { price }; 41 | }, { 42 | maxAge: 20, 43 | cacheKey: 'getETHprice', 44 | }); 45 | -------------------------------------------------------------------------------- /routes/v1/getFactoGaugesCrvRewards/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getFactoGaugesCrvRewards/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Volumes and APYs 7 | * description: Returns unboosted CRV APRs for sidechain facto gauges 8 | * parameters: 9 | * - $ref: '#/components/parameters/blockchainId' 10 | * responses: 11 | * 200: 12 | * description: 13 | */ 14 | 15 | import { fn } from '#root/utils/api.js'; 16 | import getAssetsPrices from '#root/utils/data/assets-prices.js'; 17 | import getFactoGaugesFn from '#root/routes/v1/getFactoGauges/[blockchainId].js'; 18 | import { lc } from '#root/utils/String.js'; 19 | import getAllCurvePoolsData from '#root/utils/data/curve-pools-data.js'; 20 | import getAllCurveLendingVaultsData from '#root/utils/data/curve-lending-vaults-data.js'; 21 | 22 | const NON_STANDARD_OUTDATED_GAUGES = [ 23 | 'celo-0x4969e38b8d37fc42a1897295Ea6d7D0b55944497', 24 | ].map(lc); 25 | 26 | export default fn(async ({ blockchainId }) => { 27 | const { gauges } = await getFactoGaugesFn.straightCall({ blockchainId }); 28 | const [poolsData, lendingVaultsData] = await Promise.all([ 29 | getAllCurvePoolsData([blockchainId]), 30 | getAllCurveLendingVaultsData([blockchainId]), 31 | ]); 32 | 33 | const sideChainGauges = gauges.filter(({ 34 | side_chain: isSideChain, 35 | name, 36 | is_killed: isKilled, 37 | gauge, 38 | }) => ( 39 | isSideChain && 40 | // name.startsWith(`${blockchainId}-`) && 41 | !isKilled && 42 | !NON_STANDARD_OUTDATED_GAUGES.includes(`${blockchainId}-${lc(gauge)}`) 43 | )); 44 | 45 | if (sideChainGauges.length === 0) { 46 | return { sideChainGaugesApys: [] }; 47 | } 48 | 49 | const { 'curve-dao-token': crvPrice } = await getAssetsPrices(['curve-dao-token']); 50 | const sideChainGaugesApys = sideChainGauges.map(({ 51 | swap, 52 | name, 53 | gauge_data: { 54 | inflation_rate: rate, // This already takes gauge_relative_weight into account in side facto gauges 55 | totalSupply, 56 | }, 57 | areCrvRewardsStuckInBridge, 58 | }) => { 59 | const lcAddress = swap.toLowerCase(); 60 | // Not all pools have an lpTokenAddress 61 | const pool = poolsData.find(({ address }) => ( 62 | address.toLowerCase() === lcAddress 63 | )); 64 | const lendingVault = lendingVaultsData.find(({ address }) => ( 65 | address.toLowerCase() === lcAddress 66 | )); 67 | if (!pool && !lendingVault) throw new Error(`Can't find pool or lending vault data for address "${lcAddress}"`); 68 | const isPool = !!pool; 69 | 70 | const lpTokenUsdValue = ( 71 | isPool ? 72 | (pool.usdTotal / (pool.totalSupply / 1e18)) : 73 | lendingVault.vaultShares.pricePerShare 74 | ); 75 | const gaugeUsdValue = totalSupply / 1e18 * lpTokenUsdValue; 76 | 77 | const apy = ( 78 | areCrvRewardsStuckInBridge ? 0 : 79 | ((rate / 1e18) * (86400 * 365) / gaugeUsdValue * 0.4 * crvPrice * 100) 80 | ); 81 | 82 | return { 83 | address: lcAddress, 84 | name, 85 | apy, 86 | areCrvRewardsStuckInBridge, 87 | }; 88 | }); 89 | 90 | return { sideChainGaugesApys }; 91 | }, { 92 | maxAge: 5 * 60, 93 | cacheKey: ({ blockchainId }) => `getFactoGaugesCrvRewards-${blockchainId}`, 94 | }); 95 | -------------------------------------------------------------------------------- /routes/v1/getFactoryAPYs/[blockchainId]/[version].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getFactoryAPYs/{blockchainId}/{version}: 4 | * get: 5 | * tags: 6 | * - Volumes and APYs 7 | * description: | 8 | * Returns *inaccurate* volume and base APY data for Curve pools on chains that aren’t indexed by either the [Curve Prices API](https://prices.curve.fi/feeds-docs) or the [Curve subgraphs](https://github.com/curvefi/volume-subgraphs). 9 | * Data returned by this endpoint is necessarily inaccurate due to the manual chain-walking involved, coupled with the limitations of RPC endpoints available for this subset of chains. Using this endpoint should be considered an imperfect last resort. 10 | * If the chain for which you want to retrieve volume and base APY data is available through either [`/getVolumes/{blockchainId}`](#/default/get_getVolumes__blockchainId_) or [`getSubgraphData/[blockchainId]`](#/default/get_getSubgraphData__blockchainId_), please use these. 11 | * parameters: 12 | * - in: path 13 | * name: blockchainId 14 | * required: true 15 | * schema: 16 | * type: string 17 | * enum: [bsc, kava, zkevm, zksync, x-layer, mantle] 18 | * - in: path 19 | * name: version 20 | * required: true 21 | * schema: 22 | * type: string 23 | * enum: [stable, crypto] 24 | * responses: 25 | * 200: 26 | * description: 27 | */ 28 | 29 | // Note: Ethereum is not documented because this endpoint shouldn’t be used anymore for Ethereum at all 30 | 31 | import { fn } from '#root/utils/api.js'; 32 | import getEthereumFactoryAPYs from '#root/routes/v1/getFactoryAPYs/_ethereum.js'; 33 | import getSidechainFactoryAPYs from '#root/routes/v1/getFactoryAPYs/_sidechains.js'; 34 | 35 | const paramSanitizers = { 36 | version: ({ blockchainId, version }) => ({ 37 | isValid: ( 38 | (blockchainId !== 'ethereum') ? 39 | ['stable', 'crypto'].includes(version) : 40 | ['crypto', '1', '2'].includes(version) 41 | ), 42 | }), 43 | }; 44 | 45 | // Note: keep the openapi parameter definition up to date with this array 46 | const SIDECHAINS_WITH_CUSTOM_SUPPORT = [ 47 | 'bsc', 48 | 'kava', 49 | 'aurora', 50 | 'celo', 51 | 'moonbeam', 52 | 'zkevm', 53 | 'zksync', 54 | 'x-layer', 55 | 'mantle', 56 | ]; 57 | 58 | export default fn(async ({ blockchainId, version }) => { 59 | if (blockchainId === 'ethereum') { 60 | return getEthereumFactoryAPYs({ version }); 61 | } else if (SIDECHAINS_WITH_CUSTOM_SUPPORT.includes(blockchainId)) { 62 | return (await import(`./custom-sidechains/_${blockchainId}.js`)).default({ version }); 63 | } else { 64 | return getSidechainFactoryAPYs({ blockchainId, version }); 65 | } 66 | }, { 67 | maxAge: 10 * 60, 68 | cacheKey: ({ blockchainId, version }) => `getFactoryAPYs-${blockchainId}-${version}`, 69 | paramSanitizers, 70 | }); 71 | 72 | export { paramSanitizers }; 73 | -------------------------------------------------------------------------------- /routes/v1/getFactoryAPYs/_sidechains.js: -------------------------------------------------------------------------------- 1 | import { sum } from '#root/utils/Array.js'; 2 | import getSubgraphDataFn from '#root/routes/v1/getSubgraphData/[blockchainId].js'; 3 | import getPoolsFn from '#root/routes/v1/getPools/[blockchainId]/[registryId].js'; 4 | 5 | export default async ({ blockchainId, version }) => { 6 | const { poolData } = ( 7 | version === 'stable' ? 8 | await getPoolsFn.straightCall({ blockchainId, registryId: 'factory' }) : 9 | await getPoolsFn.straightCall({ blockchainId, registryId: 'factory-crypto' }) 10 | ); 11 | 12 | const poolDetails = []; 13 | 14 | const { poolList: poolsStats } = await getSubgraphDataFn.straightCall({ blockchainId }); 15 | poolData.forEach((pool, index) => { 16 | const lcSwapAddress = pool.address.toLowerCase(); 17 | const poolStats = poolsStats.find(({ address }) => address.toLowerCase() === lcSwapAddress); 18 | 19 | if (!poolStats) { 20 | const errorMessage = `Couldn't find pool address ${pool.address} in subgraph stats data`; 21 | console.error(errorMessage) 22 | 23 | poolDetails.push({ 24 | index, 25 | poolAddress: pool.address, 26 | poolSymbol: pool.symbol, 27 | apyFormatted: undefined, 28 | apy: undefined, 29 | apyWeekly: undefined, 30 | virtualPrice: undefined, 31 | volume: undefined, 32 | }); 33 | } else { 34 | poolDetails.push({ 35 | index, 36 | poolAddress: pool.address, 37 | poolSymbol: pool.symbol, 38 | apyFormatted: `${poolStats.latestDailyApy.toFixed(2)}%`, 39 | apy: poolStats.latestDailyApy, 40 | apyWeekly: poolStats.latestWeeklyApy, 41 | virtualPrice: poolStats.virtualPrice, 42 | volume: version === 'stable' ? poolStats.rawVolume : poolStats.volumeUSD, // Crypto pools historically have usd volume there, keeping this inconsistency to avoid breakage 43 | }); 44 | } 45 | }); 46 | 47 | const totalVolume = sum(poolDetails.map(({ volume }) => volume)); 48 | 49 | poolDetails.sort((a, b) => (a.index > b.index) ? 1 : ((b.index > a.index) ? -1 : 0)) 50 | 51 | return { 52 | poolDetails, 53 | totalVolume, 54 | totalVolumeUsd: totalVolume, // Alias for legacy endpoints 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /routes/v1/getFactoryCryptoPools/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getFactoryCryptoPools/{blockchainId}: 4 | * get: 5 | * deprecated: true 6 | * tags: 7 | * - Deprecated 8 | * description: | 9 | * Deprecated: please use `getPools/{blockchainId}/factory-crypto` instead 10 | * parameters: 11 | * - $ref: '#/components/parameters/blockchainId' 12 | * responses: 13 | * 200: 14 | * description: 15 | */ 16 | 17 | import { fn } from '#root/utils/api.js'; 18 | import getPoolsFn from '#root/routes/v1/getPools/[blockchainId]/[registryId].js'; 19 | 20 | export default fn(async ({ blockchainId }) => ( 21 | getPoolsFn.straightCall({ blockchainId, registryId: 'factory-crypto' }) 22 | ), { 23 | maxAgeCDN: 5 * 60, // Don't cache in redis since it's just a pass-through endpoint 24 | cacheKeyCDN: ({ blockchainId }) => `getFactoryCryptoPools-${blockchainId}`, 25 | }); 26 | -------------------------------------------------------------------------------- /routes/v1/getFactoryTVL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getFactoryTVL: 4 | * get: 5 | * deprecated: true 6 | * tags: 7 | * - Deprecated 8 | * description: | 9 | * Deprecated: please use `getPools/ethereum/factory` instead 10 | * responses: 11 | * 200: 12 | * description: 13 | */ 14 | 15 | import getPoolsFn from '#root/routes/v1/getPools/[blockchainId]/[registryId].js'; 16 | import { fn } from '#root/utils/api.js'; 17 | 18 | export default fn(async () => { 19 | let res = await getPoolsFn.straightCall({ blockchainId: 'ethereum', registryId: 'factory' }) 20 | const factoryBalances = res.tvl 21 | return { factoryBalances }; 22 | }, { 23 | maxAgeCDN: 5 * 60, // Don't cache in redis since it's just a pass-through endpoint 24 | cacheKeyCDN: 'getFactoryTVL', 25 | }); 26 | -------------------------------------------------------------------------------- /routes/v1/getFactoryV2Pools/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getFactoryV2Pools/{blockchainId}: 4 | * get: 5 | * deprecated: true 6 | * tags: 7 | * - Deprecated 8 | * description: | 9 | * Deprecated: please use `getPools/{blockchainId}/factory` instead 10 | * parameters: 11 | * - $ref: '#/components/parameters/blockchainId' 12 | * responses: 13 | * 200: 14 | * description: 15 | */ 16 | 17 | import { fn } from '#root/utils/api.js'; 18 | import getPoolsFn from '#root/routes/v1/getPools/[blockchainId]/[registryId].js'; 19 | 20 | export default fn(async ({ blockchainId }) => ( 21 | getPoolsFn.straightCall({ blockchainId, registryId: 'factory' }) 22 | ), { 23 | maxAgeCDN: 5 * 60, // Don't cache in redis since it's just a pass-through endpoint 24 | cacheKeyCDN: ({ blockchainId }) => `getFactoryV2Pools-${blockchainId}`, 25 | }); 26 | -------------------------------------------------------------------------------- /routes/v1/getGas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getGas: 4 | * get: 5 | * tags: 6 | * - Misc 7 | * description: Returns Ethereum gas prices (in gwei) (uses Blocknative API) 8 | * responses: 9 | * 200: 10 | * description: 11 | */ 12 | 13 | import { fn } from '#root/utils/api.js'; 14 | 15 | const blocknativeApiFetchOptions = { 16 | method: 'GET', 17 | headers: { Authorization: process.env.BLOCKNATIVE_API_KEY }, 18 | }; 19 | 20 | const getBlocknativeData = async () => { 21 | const { blockPrices: [{ baseFeePerGas, estimatedPrices }] } = await (await fetch('https://api.blocknative.com/gasprices/blockprices?confidenceLevels=99&confidenceLevels=90&confidenceLevels=80&confidenceLevels=60', blocknativeApiFetchOptions)).json(); 22 | 23 | return [ 24 | baseFeePerGas, 25 | estimatedPrices.find(({ confidence }) => confidence === 99), 26 | estimatedPrices.find(({ confidence }) => confidence === 90), 27 | estimatedPrices.find(({ confidence }) => confidence === 80), 28 | estimatedPrices.find(({ confidence }) => confidence === 60), 29 | ]; 30 | }; 31 | 32 | const CACHE_PROPS = { 33 | maxAge: 30, 34 | cacheKey: 'getGas', 35 | }; 36 | 37 | export default fn(async () => { 38 | const [ 39 | baseFee, 40 | fastestGasInfo, 41 | fastGasInfo, 42 | standardGasInfo, 43 | slowGasInfo, 44 | ] = await getBlocknativeData(); 45 | 46 | const eip1559Gas = { 47 | base: baseFee * 1e9, 48 | prio: [ 49 | fastestGasInfo, 50 | fastGasInfo, 51 | standardGasInfo, 52 | slowGasInfo, 53 | ].map(({ maxPriorityFeePerGas }) => maxPriorityFeePerGas * 1e9), 54 | max: [ 55 | fastestGasInfo, 56 | fastGasInfo, 57 | standardGasInfo, 58 | slowGasInfo, 59 | ].map(({ maxFeePerGas }) => maxFeePerGas * 1e9), 60 | }; 61 | 62 | return { 63 | gas: { 64 | rapid: fastestGasInfo.price * 1e9, 65 | fast: fastGasInfo.price * 1e9, 66 | standard: standardGasInfo.price * 1e9, 67 | slow: slowGasInfo.price * 1e9, 68 | }, 69 | eip1559Gas, 70 | }; 71 | }, CACHE_PROPS); 72 | -------------------------------------------------------------------------------- /routes/v1/getLendingVaults/all/[lendingBlockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getLendingVaults/all/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Lending 7 | * description: | 8 | * Returns all lending vaults, in all registries, on a specific chain. 9 | * parameters: 10 | * - $ref: '#/components/parameters/lendingBlockchainId' 11 | * responses: 12 | * 200: 13 | * description: 14 | * /getLendingVaults/all: 15 | * get: 16 | * tags: 17 | * - Lending 18 | * description: | 19 | * Returns all lending vaults, in all registries, on all chains. 20 | * responses: 21 | * 200: 22 | * description: 23 | */ 24 | 25 | /** 26 | * This endpoint, along with all bulk getLendingVaults endpoints, is only cached at the CDN level: 27 | * it uses the `maxAgeCDN` prop only. 28 | * 29 | * This approach allows to take advantage of: 30 | * 1. Redis caching of all combinations pools(blockchainId, registryId): these are already 31 | * cached and available, so this is very fast, the server only assembles them 32 | * 2. CDN caching: Cloudfront makes this assembled, large amount of data, available 33 | * close to all API consumers 34 | * 35 | * This has two advantages: 36 | * 1. Redis isn't bloated with large amounts of data that are already stored in it 37 | * in their unassembled form 38 | * 2. The server doesn't need to do that assembling too often, CDN caching makes sure of that 39 | */ 40 | 41 | import getAllCurveLendingVaultsData from '#root/utils/data/curve-lending-vaults-data.js'; 42 | import { allLendingBlockchainIds, fn } from '#root/utils/api.js'; 43 | import { sum } from '#root/utils/Array.js'; 44 | 45 | export default fn(async ({ lendingBlockchainId }) => { 46 | const blockchainIds = ( 47 | lendingBlockchainId === 'all' ? 48 | allLendingBlockchainIds : 49 | [lendingBlockchainId] 50 | ); 51 | 52 | const lendingVaultData = await getAllCurveLendingVaultsData(blockchainIds, false); 53 | 54 | return { 55 | lendingVaultData, 56 | tvl: sum(lendingVaultData.map(({ usdTotal }) => usdTotal)), 57 | }; 58 | }, { 59 | maxAgeCDN: 5 * 60, 60 | cacheKeyCDN: ({ lendingBlockchainId }) => `getAllLendingVaults-${lendingBlockchainId}`, 61 | paramSanitizers: { 62 | // Override default lendingBlockchainId sanitizer for this endpoint 63 | lendingBlockchainId: ({ lendingBlockchainId }) => ({ 64 | isValid: allLendingBlockchainIds.includes(lendingBlockchainId), 65 | defaultValue: 'all', 66 | }), 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /routes/v1/getMainPoolsAPYs/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getMainPoolsAPYs/{blockchainId}: 4 | * get: 5 | * deprecated: true 6 | * tags: 7 | * - Deprecated 8 | * description: | 9 | * Returns *inaccurate* volume and base APY data for `main` registry pools on chains that aren’t indexed by either the [Curve Prices API](https://prices.curve.finance/feeds-docs) or the [Curve subgraphs](https://github.com/curvefi/volume-subgraphs). 10 | * Data returned by this endpoint is necessarily inaccurate due to the manual chain-walking involved, coupled with the limitations of RPC endpoints available for this subset of chains. Using this endpoint should be considered an imperfect last resort. 11 | * If the chain for which you want to retrieve volume and base APY data is available through either [`/getVolumes/{blockchainId}`](#/default/get_getVolumes__blockchainId_) or [`getSubgraphData/[blockchainId]`](#/default/get_getSubgraphData__blockchainId_), please use these. 12 | * 13 | * Note: At the moment, all chains with a `main` registry have support in one of the endpoints mentioned above. This endpoint will not return data for any chain anymore. 14 | * parameters: 15 | * - in: path 16 | * name: blockchainId 17 | * required: true 18 | * schema: 19 | * type: string 20 | * enum: [] 21 | * responses: 22 | * 200: 23 | * description: 24 | */ 25 | 26 | import { NotFoundError, fn } from '#root/utils/api.js'; 27 | 28 | // Note: keep the openapi parameter definition up to date with this array 29 | const SIDECHAINS_WITH_CUSTOM_SUPPORT = [ 30 | ]; 31 | 32 | export default fn(async ({ blockchainId }) => { 33 | if (SIDECHAINS_WITH_CUSTOM_SUPPORT.includes(blockchainId)) { 34 | return (await import(`./custom-sidechains/_${blockchainId}.js`)).default(); 35 | } else { 36 | throw new NotFoundError(`This chain has a getSubgraphData endpoint available, please use "/api/getSubgraphData/${blockchainId}"`); 37 | } 38 | }, { 39 | maxAge: 5 * 60, 40 | cacheKey: ({ blockchainId }) => `getMainPoolsAPYs-${blockchainId}`, 41 | returnFlatData: true, // In order to match the legacy raw-stats format exactly 42 | }); 43 | -------------------------------------------------------------------------------- /routes/v1/getMainRegistryPools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getMainRegistryPools: 4 | * get: 5 | * deprecated: true 6 | * tags: 7 | * - Deprecated 8 | * description: | 9 | * Returns Ethereum main registry pools addresses as an array 10 | * Deprecated: please use `getPools/ethereum/main` instead 11 | * responses: 12 | * 200: 13 | * description: 14 | */ 15 | 16 | /** 17 | * Note: this method is exposed as an API endpoint, but is mostly meant as an internal utility 18 | * that can retrieve data for all chains, not just Ethereum. 19 | */ 20 | 21 | import Web3 from 'web3'; 22 | import configs from '#root/constants/configs/index.js' 23 | import { ZERO_ADDRESS } from '#root/utils/Web3/index.js'; 24 | import { fn } from '#root/utils/api.js'; 25 | import { getRegistry } from '#root/utils/getters.js'; 26 | import registryAbi from '#root/constants/abis/factory_registry.json' assert { type: 'json' }; 27 | import multicallAbi from '#root/constants/abis/multicall.json' assert { type: 'json' }; 28 | 29 | export default fn(async ({ blockchainId }) => { 30 | const config = configs[blockchainId]; 31 | if (config.hasNoMainRegistry) return { poolList: [] }; 32 | 33 | const { rpcUrl, multicall2Address } = config; 34 | const web3 = new Web3(rpcUrl); 35 | 36 | const registryAddress = await getRegistry({ blockchainId }); 37 | if (registryAddress === ZERO_ADDRESS) return { poolList: [] }; 38 | 39 | const registry = new web3.eth.Contract(registryAbi, registryAddress); 40 | const poolCount = await registry.methods.pool_count().call(); 41 | const multicall = new web3.eth.Contract(multicallAbi, multicall2Address); 42 | 43 | // get pool addresses 44 | let calls = []; 45 | for (let i = 0; i < poolCount; i += 1) { 46 | calls.push([registryAddress, registry.methods.pool_list(i).encodeABI()]); 47 | } 48 | let aggcalls = await multicall.methods.aggregate(calls).call(); 49 | const poolList = aggcalls[1].map((hex) => web3.eth.abi.decodeParameter('address', hex)); 50 | 51 | return { poolList }; 52 | }, { 53 | maxAge: 3600, // 1 hour 54 | cacheKey: ({ blockchainId }) => `getMainRegistryPools-${blockchainId}`, 55 | }); 56 | -------------------------------------------------------------------------------- /routes/v1/getPlatforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getPlatforms: 4 | * get: 5 | * tags: 6 | * - Misc 7 | * description: | 8 | * Returns platforms (also known as `blockchainId` in other API endpoints) that Curve is deployed on, and which pool registries are available on each platform. 9 | * Useful to then query e.g. [`/api/getPools/{blockchainId}/{registryId}`](#/default/get_getPools__blockchainId___registryId_) 10 | * responses: 11 | * 200: 12 | * description: 13 | */ 14 | 15 | import configs from '#root/constants/configs/index.js' 16 | import { arrayToHashmap } from '#root/utils/Array.js'; 17 | import { fn } from '#root/utils/api.js'; 18 | import getPlatformRegistries from '#root/utils/data/curve-platform-registries.js'; 19 | 20 | const allBlockchainIds = Array.from(Object.keys(configs)); 21 | 22 | export default fn(async () => ({ 23 | platforms: arrayToHashmap(await Promise.all(allBlockchainIds.map(async (blockchainId) => [ 24 | blockchainId, 25 | (await getPlatformRegistries(blockchainId)).registryIds, 26 | ]))), 27 | platformToChainIdMap: arrayToHashmap(allBlockchainIds.map((platformId) => [ 28 | platformId, 29 | configs[platformId].chainId, 30 | ])), 31 | }), { 32 | maxAge: 60 * 60, // 1h 33 | cacheKey: 'getPlatforms', 34 | }); 35 | -------------------------------------------------------------------------------- /routes/v1/getPointsCampaigns.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getPointsCampaigns: 4 | * get: 5 | * tags: 6 | * - Misc 7 | * description: | 8 | * Returns points campaigns (see https://github.com/curvefi/curve-frontend/tree/main/packages/external-rewards/src) 9 | * responses: 10 | * 200: 11 | * description: 12 | */ 13 | 14 | import memoize from 'memoizee'; 15 | import { allBlockchainIds, fn } from '#root/utils/api.js'; 16 | import { Octokit } from '@octokit/rest'; 17 | import { sequentialPromiseMap } from '#root/utils/Async.js'; 18 | import { getNowTimestamp } from '#root/utils/Date.js'; 19 | import { removeNulls } from '#root/utils/Array.js'; 20 | 21 | const octokit = new Octokit({ 22 | auth: process.env.GITHUB_FINE_GRAINED_PERSONAL_ACCESS_TOKEN, 23 | userAgent: process.env.GITHUB_API_UA, 24 | }); 25 | 26 | const getCampaigns = memoize(async () => { 27 | const filePaths = await octokit.rest.repos.getContent({ 28 | owner: 'curvefi', 29 | repo: 'curve-frontend', 30 | path: '/packages/external-rewards/src/campaigns', 31 | }).then(({ data }) => data.map(({ path }) => path).filter((path) => path.endsWith('.json'))); 32 | 33 | const nowTs = getNowTimestamp(); 34 | const campaigns = removeNulls(await sequentialPromiseMap(filePaths, async (filePath) => ( 35 | octokit.rest.repos.getContent({ 36 | owner: 'curvefi', 37 | repo: 'curve-frontend', 38 | path: filePath, 39 | }).then(({ data: { content } }) => { 40 | const jsonFile = Buffer.from(content, 'base64').toString(); 41 | const campaignConfig = JSON.parse(jsonFile); 42 | 43 | const relevantCampaignPools = (campaignConfig.pools ?? []).filter(({ 44 | tags, 45 | network, 46 | action, 47 | campaignStart, 48 | campaignEnd, 49 | }) => ( 50 | tags.includes('points') && 51 | allBlockchainIds.includes(network) && 52 | (action === 'lp' || action === 'supply') && 53 | Number(campaignStart) < nowTs && 54 | nowTs < Number(campaignEnd) 55 | )); 56 | 57 | if (relevantCampaignPools.length === 0) return null; 58 | 59 | return { 60 | campaignName: campaignConfig.campaignName, 61 | platform: campaignConfig.platform, 62 | platformImageId: campaignConfig.platformImageId, 63 | dashboardLink: campaignConfig.dashboardLink, 64 | pools: relevantCampaignPools, 65 | }; 66 | }) 67 | ))); 68 | 69 | return campaigns; 70 | }, { 71 | maxAge: 10 * 60 * 1000, 72 | promise: true, 73 | }); 74 | 75 | export default fn(async () => ({ 76 | pointCampaigns: await getCampaigns(), 77 | }), { 78 | maxAge: 60 * 60, // 1h 79 | cacheKey: 'getPointsCampaigns', 80 | }); 81 | -------------------------------------------------------------------------------- /routes/v1/getPoolList/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getPoolList/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Pools 7 | * description: | 8 | * Returns addresses of all pools, in all registries, on a specific chain. 9 | * 10 | * Note: For backward compatibility, in this endpoint the "factory" registry is renamed to "stable-factory" 11 | * parameters: 12 | * - $ref: '#/components/parameters/blockchainId' 13 | * responses: 14 | * 200: 15 | * description: 16 | */ 17 | 18 | // Note: this endpoint's code is old, it works but can be made cleaner using `multiCall` from `utils/Calls.js` 19 | 20 | import Web3 from 'web3'; 21 | import { NotFoundError, fn } from '#root/utils/api.js'; 22 | import REGISTRY_ABI from '#root/constants/abis/registry.json' assert { type: 'json' }; 23 | import configs from '#root/constants/configs/index.js' 24 | import getPlatformRegistries from '#root/utils/data/curve-platform-registries.js'; 25 | import { sequentialPromiseFlatMap } from '#root/utils/Async.js'; 26 | import { uniqBy } from '#root/utils/Array.js'; 27 | import { multiCall } from '#root/utils/Calls.js'; 28 | 29 | const getPoolList = fn(async ({ blockchainId }) => { 30 | const config = configs[blockchainId]; 31 | 32 | if (typeof config === 'undefined') { 33 | throw new NotFoundError(`No factory data for blockchainId "${blockchainId}"`); 34 | } 35 | 36 | const web3 = new Web3(config.rpcUrl); 37 | const networkSettingsParam = ( 38 | typeof config.multicall2Address !== 'undefined' ? 39 | { networkSettings: { web3, multicall2Address: config.multicall2Address } } : 40 | undefined 41 | ); 42 | 43 | const { 44 | registryIds, 45 | registryAddresses, 46 | } = await getPlatformRegistries(blockchainId); 47 | 48 | const poolList = await sequentialPromiseFlatMap(registryIds, async (registryId, i) => { 49 | const registryAddress = registryAddresses[i]; 50 | 51 | const poolCount = Number((await multiCall([{ 52 | address: registryAddress, 53 | abi: REGISTRY_ABI, 54 | methodName: 'pool_count', 55 | ...networkSettingsParam, 56 | }]))[0]); 57 | if (poolCount === 0) return []; 58 | 59 | const unfilteredPoolIds = Array(poolCount).fill(0).map((_, i) => i); 60 | 61 | const unfilteredPoolAddresses = await multiCall(unfilteredPoolIds.map((id) => ({ 62 | address: registryAddress, 63 | abi: REGISTRY_ABI, 64 | methodName: 'pool_list', 65 | params: [id], 66 | ...networkSettingsParam, 67 | }))); 68 | 69 | return unfilteredPoolAddresses.map((address) => ({ 70 | type: (registryId === 'factory' ? 'stable-factory' : registryId), 71 | address, 72 | })); 73 | }); 74 | 75 | return { 76 | poolList: uniqBy(poolList, 'address'), 77 | }; 78 | }, { 79 | maxAge: 60, 80 | cacheKey: ({ blockchainId }) => `getPoolList-${blockchainId}`, 81 | }); 82 | 83 | export default getPoolList; 84 | -------------------------------------------------------------------------------- /routes/v1/getPools/all/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getPools/all/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Pools 7 | * description: | 8 | * Returns all pools, in all registries, on a specific chain. 9 | * parameters: 10 | * - $ref: '#/components/parameters/blockchainId' 11 | * responses: 12 | * 200: 13 | * description: 14 | * /getPools/all: 15 | * get: 16 | * tags: 17 | * - Pools 18 | * description: | 19 | * Returns all pools, in all registries, on all chains. 20 | * responses: 21 | * 200: 22 | * description: 23 | */ 24 | 25 | /** 26 | * This endpoint, along with all bulk getPools endpoints, is only cached at the CDN level: 27 | * it uses the `maxAgeCDN` prop only. 28 | * 29 | * This approach allows to take advantage of: 30 | * 1. Redis caching of all combinations pools(blockchainId, registryId): these are already 31 | * cached and available, so this is very fast, the server only assembles them 32 | * 2. CDN caching: Cloudfront makes this assembled, large amount of data, available 33 | * close to all API consumers 34 | * 35 | * This has two advantages: 36 | * 1. Redis isn't bloated with large amounts of data that are already stored in it 37 | * in their unassembled form 38 | * 2. The server doesn't need to do that assembling too often, CDN caching makes sure of that 39 | */ 40 | 41 | import configs from '#root/constants/configs/index.js'; 42 | import getAllCurvePoolsData from '#root/utils/data/curve-pools-data.js'; 43 | import { fn } from '#root/utils/api.js'; 44 | import { sum } from '#root/utils/Array.js'; 45 | 46 | const allBlockchainIds = Array.from(Object.keys(configs)); 47 | 48 | export default fn(async ({ blockchainId }) => { 49 | const blockchainIds = ( 50 | blockchainId === 'all' ? 51 | allBlockchainIds : 52 | [blockchainId] 53 | ); 54 | 55 | const poolData = await getAllCurvePoolsData(blockchainIds, false); 56 | 57 | return { 58 | poolData, 59 | tvl: sum(poolData.map(({ usdTotalExcludingBasePool }) => usdTotalExcludingBasePool)), 60 | }; 61 | }, { 62 | maxAgeCDN: 5 * 60, 63 | cacheKeyCDN: ({ blockchainId }) => `getAllPools-${blockchainId}`, 64 | paramSanitizers: { 65 | // Override default blockchainId sanitizer for this endpoint 66 | blockchainId: ({ blockchainId }) => ({ 67 | isValid: allBlockchainIds.includes(blockchainId), 68 | defaultValue: 'all', 69 | }), 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /routes/v1/getPools/big/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getPools/big/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Pools 7 | * description: | 8 | * Returns all pools whose TVL is ≥$10k, in all registries, on a specific chain. 9 | * parameters: 10 | * - $ref: '#/components/parameters/blockchainId' 11 | * responses: 12 | * 200: 13 | * description: 14 | * /getPools/big: 15 | * get: 16 | * tags: 17 | * - Pools 18 | * description: | 19 | * Returns all pools whose TVL is ≥$10k, in all registries, on all chains. 20 | * responses: 21 | * 200: 22 | * description: 23 | */ 24 | 25 | /** 26 | * This endpoint, along with all bulk getPools endpoints, is only cached at the CDN level: 27 | * it uses the `maxAgeCDN` prop only. 28 | * 29 | * This approach allows to take advantage of: 30 | * 1. Redis caching of all combinations pools(blockchainId, registryId): these are already 31 | * cached and available, so this is very fast, the server only assembles them 32 | * 2. CDN caching: Cloudfront makes this assembled, large amount of data, available 33 | * close to all API consumers 34 | * 35 | * This has two advantages: 36 | * 1. Redis isn't bloated with large amounts of data that are already stored in it 37 | * in their unassembled form 38 | * 2. The server doesn't need to do that assembling too often, CDN caching makes sure of that 39 | */ 40 | 41 | import configs from '#root/constants/configs/index.js'; 42 | import getAllCurvePoolsData from '#root/utils/data/curve-pools-data.js'; 43 | import { fn } from '#root/utils/api.js'; 44 | import { SMALL_POOLS_USDTOTAL_THRESHOLD } from '#root/constants/AppConstants.js'; 45 | import { sum } from '#root/utils/Array.js'; 46 | 47 | const allBlockchainIds = Array.from(Object.keys(configs)); 48 | 49 | export default fn(async ({ blockchainId }) => { 50 | const blockchainIds = ( 51 | blockchainId === 'all' ? 52 | allBlockchainIds : 53 | [blockchainId] 54 | ); 55 | 56 | const poolData = ( 57 | (await getAllCurvePoolsData(blockchainIds, false)) 58 | .filter(({ usdTotal }) => usdTotal >= SMALL_POOLS_USDTOTAL_THRESHOLD) 59 | ); 60 | 61 | return { 62 | poolData, 63 | tvl: sum(poolData.map(({ usdTotalExcludingBasePool }) => usdTotalExcludingBasePool)), 64 | }; 65 | }, { 66 | maxAgeCDN: 5 * 60, 67 | cacheKeyCDN: ({ blockchainId }) => `getAllBigPools-${blockchainId}`, 68 | paramSanitizers: { 69 | // Override default blockchainId sanitizer for this endpoint 70 | blockchainId: ({ blockchainId }) => ({ 71 | isValid: allBlockchainIds.includes(blockchainId), 72 | defaultValue: 'all', 73 | }), 74 | }, 75 | }); 76 | -------------------------------------------------------------------------------- /routes/v1/getPools/empty/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getPools/empty/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Pools 7 | * description: | 8 | * Returns all pools whose TVL is $0, in all registries, on a specific chain. 9 | * parameters: 10 | * - $ref: '#/components/parameters/blockchainId' 11 | * responses: 12 | * 200: 13 | * description: 14 | * /getPools/empty: 15 | * get: 16 | * tags: 17 | * - Pools 18 | * description: | 19 | * Returns all pools whose TVL is $0, in all registries, on all chains. 20 | * responses: 21 | * 200: 22 | * description: 23 | */ 24 | 25 | /** 26 | * This endpoint, along with all bulk getPools endpoints, is only cached at the CDN level: 27 | * it uses the `maxAgeCDN` prop only. 28 | * 29 | * This approach allows to take advantage of: 30 | * 1. Redis caching of all combinations pools(blockchainId, registryId): these are already 31 | * cached and available, so this is very fast, the server only assembles them 32 | * 2. CDN caching: Cloudfront makes this assembled, large amount of data, available 33 | * close to all API consumers 34 | * 35 | * This has two advantages: 36 | * 1. Redis isn't bloated with large amounts of data that are already stored in it 37 | * in their unassembled form 38 | * 2. The server doesn't need to do that assembling too often, CDN caching makes sure of that 39 | */ 40 | 41 | import configs from '#root/constants/configs/index.js'; 42 | import getAllCurvePoolsData from '#root/utils/data/curve-pools-data.js'; 43 | import { fn } from '#root/utils/api.js'; 44 | import { sum } from '#root/utils/Array.js'; 45 | 46 | const allBlockchainIds = Array.from(Object.keys(configs)); 47 | 48 | export default fn(async ({ blockchainId }) => { 49 | const blockchainIds = ( 50 | blockchainId === 'all' ? 51 | allBlockchainIds : 52 | [blockchainId] 53 | ); 54 | 55 | const poolData = ( 56 | (await getAllCurvePoolsData(blockchainIds, false)) 57 | .filter(({ usdTotal }) => (usdTotal === 0)) 58 | ); 59 | 60 | return { 61 | poolData, 62 | tvl: sum(poolData.map(({ usdTotalExcludingBasePool }) => usdTotalExcludingBasePool)), 63 | }; 64 | }, { 65 | maxAgeCDN: 5 * 60, 66 | cacheKeyCDN: ({ blockchainId }) => `getAllEmptyPools-${blockchainId}`, 67 | paramSanitizers: { 68 | // Override default blockchainId sanitizer for this endpoint 69 | blockchainId: ({ blockchainId }) => ({ 70 | isValid: allBlockchainIds.includes(blockchainId), 71 | defaultValue: 'all', 72 | }), 73 | }, 74 | }); 75 | -------------------------------------------------------------------------------- /routes/v1/getPools/small/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getPools/small/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Pools 7 | * description: | 8 | * Returns all pools whose TVL is <$10k, in all registries, on a specific chain. 9 | * parameters: 10 | * - $ref: '#/components/parameters/blockchainId' 11 | * responses: 12 | * 200: 13 | * description: 14 | * /getPools/small: 15 | * get: 16 | * tags: 17 | * - Pools 18 | * description: | 19 | * Returns all pools whose TVL is <$10k, in all registries, on all chains. 20 | * responses: 21 | * 200: 22 | * description: 23 | */ 24 | 25 | /** 26 | * This endpoint, along with all bulk getPools endpoints, is only cached at the CDN level: 27 | * it uses the `maxAgeCDN` prop only. 28 | * 29 | * This approach allows to take advantage of: 30 | * 1. Redis caching of all combinations pools(blockchainId, registryId): these are already 31 | * cached and available, so this is very fast, the server only assembles them 32 | * 2. CDN caching: Cloudfront makes this assembled, large amount of data, available 33 | * close to all API consumers 34 | * 35 | * This has two advantages: 36 | * 1. Redis isn't bloated with large amounts of data that are already stored in it 37 | * in their unassembled form 38 | * 2. The server doesn't need to do that assembling too often, CDN caching makes sure of that 39 | */ 40 | 41 | import configs from '#root/constants/configs/index.js'; 42 | import getAllCurvePoolsData from '#root/utils/data/curve-pools-data.js'; 43 | import { fn } from '#root/utils/api.js'; 44 | import { SMALL_POOLS_USDTOTAL_THRESHOLD } from '#root/constants/AppConstants.js'; 45 | import { sum } from '#root/utils/Array.js'; 46 | 47 | const allBlockchainIds = Array.from(Object.keys(configs)); 48 | 49 | export default fn(async ({ blockchainId }) => { 50 | const blockchainIds = ( 51 | blockchainId === 'all' ? 52 | allBlockchainIds : 53 | [blockchainId] 54 | ); 55 | 56 | const poolData = ( 57 | (await getAllCurvePoolsData(blockchainIds, false)) 58 | .filter(({ usdTotal }) => (usdTotal > 0 && usdTotal < SMALL_POOLS_USDTOTAL_THRESHOLD)) 59 | ); 60 | 61 | return { 62 | poolData, 63 | tvl: sum(poolData.map(({ usdTotalExcludingBasePool }) => usdTotalExcludingBasePool)), 64 | }; 65 | }, { 66 | maxAgeCDN: 5 * 60, 67 | cacheKeyCDN: ({ blockchainId }) => `getAllSmallPools-${blockchainId}`, 68 | paramSanitizers: { 69 | // Override default blockchainId sanitizer for this endpoint 70 | blockchainId: ({ blockchainId }) => ({ 71 | isValid: allBlockchainIds.includes(blockchainId), 72 | defaultValue: 'all', 73 | }), 74 | }, 75 | }); 76 | -------------------------------------------------------------------------------- /routes/v1/getRegistryAddress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getRegistryAddress: 4 | * get: 5 | * tags: 6 | * - Misc 7 | * description: | 8 | * Returns address of the Ethereum registry 9 | * See 10 | * responses: 11 | * 200: 12 | * description: 13 | */ 14 | 15 | import { fn } from '#root/utils/api.js'; 16 | import { getRegistry } from '#root/utils/getters.js'; 17 | 18 | export default fn(async () => { 19 | let registryAddress = await getRegistry(); 20 | return { registryAddress }; 21 | }, { 22 | maxAge: 3600, // 1 hour 23 | cacheKey: 'getRegistryAddress', 24 | }); 25 | -------------------------------------------------------------------------------- /routes/v1/getScrvusdTotalSupplyNumber.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getScrvusdTotalSupplyNumber: 4 | * get: 5 | * tags: 6 | * - crvUSD 7 | * description: Returns the total supply of scrvUSD as a number 8 | * responses: 9 | * 200: 10 | * description: 11 | */ 12 | 13 | import { fn } from '#root/utils/api.js'; 14 | import { multiCall } from '#root/utils/Calls.js'; 15 | import erc20Abi from '#root/constants/abis/erc20.json' assert { type: 'json' }; 16 | import { uintToBN } from '#root/utils/Web3/parsing.js'; 17 | 18 | export default fn(async () => { 19 | const [scrvusdTotalSupplyRaw] = await multiCall([{ 20 | address: '0x0655977FEb2f289A4aB78af67BAB0d17aAb84367', 21 | abi: erc20Abi, 22 | methodName: 'totalSupply', 23 | }]); 24 | const scrvusdTotalSupply = uintToBN(scrvusdTotalSupplyRaw, 18); 25 | 26 | return scrvusdTotalSupply.toNumber(); 27 | }, { 28 | maxAge: 5 * 60, // 5m 29 | cacheKey: 'getScrvusdTotalSupplyNumber', 30 | returnFlatData: true, 31 | appendGeneratedTime: false, 32 | }); 33 | -------------------------------------------------------------------------------- /routes/v1/getScrvusdTotalSupplyResult.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getScrvusdTotalSupplyResult: 4 | * get: 5 | * tags: 6 | * - crvUSD 7 | * description: Returns the total supply of scrvUSD as a JSON object 8 | * responses: 9 | * 200: 10 | * description: 11 | */ 12 | 13 | import { fn } from '#root/utils/api.js'; 14 | import getScrvusdTotalSupplyNumberFn from '#root/routes/v1/getScrvusdTotalSupplyNumber.js'; 15 | 16 | export default fn(async () => { 17 | const scrvusdTotalSupply = await getScrvusdTotalSupplyNumberFn.straightCall(); 18 | 19 | return { result: scrvusdTotalSupply }; 20 | }, { 21 | maxAge: 5 * 60, // 5m 22 | cacheKey: 'getScrvusdTotalSupplyResult', 23 | returnFlatData: true, 24 | appendGeneratedTime: false, 25 | }); 26 | -------------------------------------------------------------------------------- /routes/v1/getSubgraphData/_fallback-data/getSubgraphData-aurora.json: -------------------------------------------------------------------------------- 1 | { 2 | "poolList": [ 3 | { 4 | "type": "main", 5 | "address": "0xbF7E49483881C76487b0989CD7d9A8239B20CA41", 6 | "rawVolume": 225.9144214413364, 7 | "volumeUSD": 225.86965471401564, 8 | "latestDailyApy": 0.11574770053734973, 9 | "latestWeeklyApy": 0.34071576371919043, 10 | "virtualPrice": 1028646503619680600 11 | } 12 | ], 13 | "totalVolume": 225.86965471401564, 14 | "cryptoVolume": 0, 15 | "cryptoShare": 0, 16 | "subgraphHasErrors": false 17 | } 18 | -------------------------------------------------------------------------------- /routes/v1/getSubgraphData/_fallback-data/getSubgraphData-harmony.json: -------------------------------------------------------------------------------- 1 | { 2 | "poolList": [], 3 | "totalVolume": 0, 4 | "cryptoVolume": 0, 5 | "cryptoShare": null, 6 | "subgraphHasErrors": false 7 | } 8 | -------------------------------------------------------------------------------- /routes/v1/getTokens/all/[blockchainId].js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getTokens/all/{blockchainId}: 4 | * get: 5 | * tags: 6 | * - Tokens 7 | * description: | 8 | * Returns all tokens that can be found in Curve pools, on a specific chain. 9 | * Pools need at least $10k TVL for tokens to make this list. 10 | * 11 | * Note that tokens’ `usdPrice` is very simply the usd price of that token reported 12 | * by the largest Curve pool on that chain, at that one point in time. There is no effort 13 | * made to average or smooth out those prices, they should be used for presentation purposes only. 14 | * parameters: 15 | * - $ref: '#/components/parameters/blockchainId' 16 | * responses: 17 | * 200: 18 | * description: | 19 | * 20 | */ 21 | 22 | /** 23 | * This endpoint, along with all bulk getPools endpoints, is only cached at the CDN level: 24 | * it uses the `maxAgeCDN` prop only. 25 | * 26 | * This approach allows to take advantage of: 27 | * 1. Redis caching of all combinations pools(blockchainId, registryId): these are already 28 | * cached and available, so this is very fast, the server only assembles them 29 | * 2. CDN caching: Cloudfront makes this assembled, large amount of data, available 30 | * close to all API consumers 31 | * 32 | * This has two advantages: 33 | * 1. Redis isn't bloated with large amounts of data that are already stored in it 34 | * in their unassembled form 35 | * 2. The server doesn't need to do that assembling too often, CDN caching makes sure of that 36 | */ 37 | 38 | import getAllCurvePoolsData from '#root/utils/data/curve-pools-data.js'; 39 | import { fn } from '#root/utils/api.js'; 40 | import { flattenArray, uniqBy } from '#root/utils/Array.js'; 41 | import { lc } from '#root/utils/String.js'; 42 | 43 | export default fn(async ({ blockchainId }) => { 44 | const poolData = await getAllCurvePoolsData([blockchainId], false); 45 | const coins = ( 46 | uniqBy(flattenArray( 47 | poolData.filter(({ usdTotal }) => usdTotal > 10000) 48 | .sort(({ usdTotal: usdTotalA }, { usdTotal: usdTotalB }) => ( 49 | usdTotalA > usdTotalB ? -1 : 50 | usdTotalA < usdTotalB ? 1 : 0 51 | )) 52 | .map(({ coins }) => coins) 53 | ), ({ address, blockchainId }) => `${lc(address)}-${blockchainId}`) 54 | .map(({ 55 | address, 56 | decimals, 57 | symbol, 58 | name, 59 | usdPrice, 60 | }) => ({ 61 | address, 62 | decimals: Number(decimals), 63 | symbol, 64 | name, 65 | usdPrice, 66 | })) 67 | ); 68 | 69 | return { 70 | tokens: coins, 71 | }; 72 | }, { 73 | maxAgeCDN: 5 * 60, 74 | cacheKeyCDN: ({ blockchainId }) => `getAllTokens-${blockchainId}`, 75 | }); 76 | -------------------------------------------------------------------------------- /routes/v1/getVolumes/ethereum/crvusd-amms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getVolumes/ethereum/crvusd-amms: 4 | * get: 5 | * tags: 6 | * - Volumes and APYs 7 | * - crvUSD 8 | * description: | 9 | * Returns last daily volume for each [crvUSD AMM](https://docs.curve.fi/crvUSD/amm/) 10 | * responses: 11 | * 200: 12 | * description: 13 | */ 14 | 15 | import { fn } from '#root/utils/api.js'; 16 | import { sum } from '#root/utils/Array.js'; 17 | import { sequentialPromiseMap } from '#root/utils/Async.js'; 18 | import { getNowTimestamp } from '#root/utils/Date.js'; 19 | import { fetchPages } from '#root/utils/Pagination.js'; 20 | 21 | export default fn(async () => { 22 | const crvusdMarkets = await fetchPages('https://prices.curve.finance/v1/crvusd/markets/ethereum', { 23 | fetch_on_chain: false, 24 | per_page: 100, 25 | }); 26 | 27 | const amms = crvusdMarkets.map(({ llamma }) => llamma); 28 | const timestampNow = getNowTimestamp(); 29 | const timestampDayAgo = timestampNow - 86400; 30 | 31 | const volumeData = await sequentialPromiseMap(amms, async (amm) => { 32 | const { data } = await (await fetch(`https://prices.curve.finance/v1/crvusd/llamma_ohlc/ethereum/${amm}?agg_number=1&agg_units=day&start=${timestampDayAgo}&end=${timestampNow}`)).json(); 33 | 34 | return { 35 | address: amm, 36 | volumeUSD: data[0]?.volume ?? 0, 37 | }; 38 | }); 39 | 40 | return { 41 | amms: volumeData, 42 | totalVolume: sum(volumeData.map(({ volumeUSD }) => volumeUSD)), 43 | }; 44 | }, { 45 | maxAge: 60 * 60, 46 | cacheKey: 'getVolumes/ethereum/crvusd-amms', 47 | }); 48 | -------------------------------------------------------------------------------- /routes/v1/getWeeklyFees.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * /getWeeklyFees: 4 | * get: 5 | * tags: 6 | * - Misc 7 | * description: Returns weekly fees 8 | * responses: 9 | * 200: 10 | * description: 11 | */ 12 | 13 | import Web3 from 'web3'; 14 | import * as WEB3_CONSTANTS from '#root/constants/Web3.js'; 15 | import { fn } from '#root/utils/api.js'; 16 | import { get3crvFeeDistributor, getCrvusdFeeDistributor } from '#root/utils/getters.js'; 17 | import { getThursdayUTCTimestamp } from '#root/utils/helpers.js'; 18 | import { multiCall } from '#root/utils/Calls.js'; 19 | import distributorAbi from '#root/constants/abis/distributor.json' assert { type: 'json' }; 20 | import tripoolSwapAbi from '#root/constants/abis/tripool_swap.json' assert { type: 'json' }; 21 | import { getNowTimestamp } from '#root/utils/Date.js'; 22 | import { uintToBN } from '#root/utils/Web3/index.js'; 23 | import { arrayOfIncrements, arrayToHashmap, flattenArray, sumBN } from '#root/utils/Array.js'; 24 | import groupBy from 'lodash.groupby'; 25 | 26 | export default fn(async () => { 27 | const feeDistributors = [{ 28 | address: await get3crvFeeDistributor(), 29 | feeToken: '3crv', 30 | }, { 31 | address: await getCrvusdFeeDistributor(), 32 | feeToken: 'crvUSD', 33 | }]; 34 | 35 | const distribStartTimes = (await multiCall(feeDistributors.map(({ address }) => ({ 36 | address, 37 | abi: distributorAbi, 38 | methodName: 'start_time', 39 | })))).map((ts) => Number(ts)); 40 | 41 | const distribStartTs = Math.min(...distribStartTimes); 42 | const nowTs = getNowTimestamp(); 43 | const pastThursdayTs = await getThursdayUTCTimestamp(); 44 | 45 | const weeksElapsed = Math.ceil((nowTs - distribStartTs) / (60 * 60 * 24 * 7)); 46 | 47 | // Note: we use current vprice even for past distributions, not perfectly accurate 48 | // but less rpc-intensive than querying vprice for all those individual past blocks 49 | const [tricrvVirtualPriceRaw] = await multiCall([{ 50 | address: '0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7', 51 | abi: tripoolSwapAbi, 52 | methodName: 'get_virtual_price', 53 | }]); 54 | const tricrvVirtualPrice = uintToBN(tricrvVirtualPriceRaw, 18); 55 | 56 | const tokensPerWeekRaw = await multiCall(flattenArray(arrayOfIncrements(weeksElapsed).map((weekId) => { 57 | const thursdayTs = pastThursdayTs - (weekId * 86400 * 7); 58 | 59 | return feeDistributors.map(({ address, feeToken }) => ({ 60 | address, 61 | abi: distributorAbi, 62 | methodName: 'tokens_per_week', 63 | params: [thursdayTs], 64 | metaData: { thursdayTs, feeToken }, 65 | })); 66 | }))); 67 | 68 | const tokensPerWeek = arrayToHashmap(Object.entries(groupBy(tokensPerWeekRaw, 'metaData.thursdayTs')).map(([thursdayTs, weekFees]) => { 69 | const weekTotalFee = sumBN(weekFees.map(({ data, metaData }) => ( 70 | metaData.feeToken === '3crv' ? 71 | uintToBN(data, 18).times(tricrvVirtualPrice) : 72 | uintToBN(data, 18) 73 | ))); 74 | 75 | // Uses legacy-compatible object shape (naming isn't ideal) 76 | return [thursdayTs, { 77 | date: new Date(thursdayTs * 1000).toDateString(), 78 | ts: thursdayTs * 1000, 79 | rawFees: weekTotalFee.dp(2).toNumber(), 80 | }]; 81 | })); 82 | 83 | return { 84 | weeklyFeesTable: Object.values(tokensPerWeek).sort(({ ts: tsA }, { ts: tsB }) => ( 85 | tsA > tsB ? -1 : 86 | tsA < tsB ? 1 : 0 87 | )), 88 | totalFees: { 89 | fees: sumBN(Object.values(tokensPerWeek).map(({ rawFees }) => rawFees)).toNumber(), 90 | }, 91 | }; 92 | }, { 93 | maxAge: 15 * 60, // 15 min 94 | cacheKey: 'getWeeklyFees', 95 | }); 96 | -------------------------------------------------------------------------------- /utils/Array.js: -------------------------------------------------------------------------------- 1 | import uniqBy from 'lodash.uniqby'; 2 | import BN from 'bignumber.js'; 3 | 4 | const uniq = (array) => Array.from(new Set(array).values()); 5 | 6 | const flattenArray = (arrays) => [].concat(...arrays); 7 | 8 | const flatMap = (arr, fn) => flattenArray(arr.map(fn)); 9 | 10 | const getArrayChunks = (array, maxItemsPerChunk) => { 11 | const res = []; 12 | 13 | for (let i = 0; array.length > i * maxItemsPerChunk; i += 1) { 14 | res.push( 15 | array.slice(i * maxItemsPerChunk, (i * maxItemsPerChunk) + maxItemsPerChunk) 16 | ); 17 | } 18 | 19 | return res; 20 | }; 21 | 22 | // [['a', '1'], ['b', 2], …] -> { a: 1, b: 2, … } 23 | const arrayToHashmap = (array) => ( 24 | Object.assign({}, ...array.map(([key, val]) => ({ [key]: val }))) 25 | ); 26 | 27 | const sumBy = (arr, fn) => ( 28 | arr.reduce((total, item) => total + parseFloat(fn(item)), 0) 29 | ); 30 | 31 | const sumByBN = (arr, fn) => ( 32 | arr.reduce((total, item) => total.plus(fn(item) || 0), BN(0)) 33 | ); 34 | 35 | const sumBN = (arr) => ( 36 | arr.reduce((total, numberBN) => ( 37 | total.plus(numberBN || 0) 38 | ), BN(0)) 39 | ); 40 | 41 | const sum = (arr) => ( 42 | arr.reduce((total, number) => ( 43 | total + (number || 0) 44 | ), 0) 45 | ); 46 | 47 | // arrayOfIncrements(3) -> [0, 1, 2] 48 | const arrayOfIncrements = (count) => [...Array(Number(count)).keys()]; 49 | 50 | class CaseInsensitiveMap extends Map { 51 | set(key, value) { 52 | if (typeof key === 'string') { 53 | key = key.toLowerCase(); 54 | } 55 | return super.set(key, value); 56 | } 57 | 58 | get(key) { 59 | if (typeof key === 'string') { 60 | key = key.toLowerCase(); 61 | } 62 | 63 | return super.get(key); 64 | } 65 | 66 | has(key) { 67 | if (typeof key === 'string') { 68 | key = key.toLowerCase(); 69 | } 70 | 71 | return super.has(key); 72 | } 73 | } 74 | 75 | const removeNulls = (arr) => arr.filter((o) => o !== null); 76 | 77 | export { 78 | uniq, 79 | uniqBy, 80 | flattenArray, 81 | flatMap, 82 | getArrayChunks, 83 | arrayToHashmap, 84 | sumBy, 85 | sumByBN, 86 | sumBN, 87 | sum, 88 | arrayOfIncrements, 89 | CaseInsensitiveMap, 90 | removeNulls, 91 | }; 92 | -------------------------------------------------------------------------------- /utils/Async.js: -------------------------------------------------------------------------------- 1 | import { flattenArray, getArrayChunks } from '#root/utils/Array.js'; 2 | 3 | const sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration)); 4 | 5 | const sleepUntil = async (conditionFn, checkInterval = 200) => { 6 | if (typeof conditionFn !== 'function') { 7 | throw new Error('sleepUntil expects a function as first argument'); 8 | } 9 | 10 | while (!conditionFn()) { 11 | await sleep(checkInterval); 12 | } 13 | }; 14 | 15 | const sequentialPromiseMap = async (array, asyncFn, chunkSize) => { 16 | const results = []; 17 | let i = 0; 18 | 19 | const chunked = chunkSize ? getArrayChunks(array, chunkSize) : array; 20 | 21 | while (i < chunked.length) { 22 | const res = await asyncFn(chunked[i], i); // eslint-disable-line no-await-in-loop 23 | results.push(res); 24 | 25 | i += 1; 26 | } 27 | 28 | return chunkSize ? flattenArray(results) : results; 29 | }; 30 | 31 | const sequentialPromiseFlatMap = async (array, asyncFn, chunkSize) => ( 32 | flattenArray(await sequentialPromiseMap(array, asyncFn, chunkSize)) 33 | ); 34 | 35 | const sequentialPromiseReduce = async (array, asyncFn) => { 36 | const results = []; 37 | let i = 0; 38 | 39 | while (i < array.length) { 40 | const res = await asyncFn(array[i], i, results); // eslint-disable-line no-await-in-loop 41 | results.push(res); 42 | 43 | i += 1; 44 | } 45 | 46 | return results; 47 | }; 48 | 49 | const runConcurrentlyAtMost = async (asyncFns, atMost) => { 50 | const runningPromises = new Set(); 51 | const values = []; 52 | 53 | for (const asyncFn of asyncFns) { 54 | while (runningPromises.size >= atMost) { 55 | await Promise.race(runningPromises); 56 | await sleep(0); // Politely let Promise.race yield to the rest of its code first 57 | } 58 | 59 | const promise = asyncFn() 60 | .then((value) => { 61 | values.push(value); 62 | runningPromises.delete(promise); 63 | }); 64 | 65 | runningPromises.add(promise); 66 | } 67 | 68 | // runningPromises is a dynamic iterable, so make sure Promise.all only resolves 69 | // once all dynamically-added promises have 70 | while (runningPromises.size) { 71 | await Promise.all(runningPromises); // Wait for all requests to finish 72 | } 73 | 74 | return values; 75 | }; 76 | 77 | export { 78 | sequentialPromiseMap, 79 | sequentialPromiseFlatMap, 80 | sequentialPromiseReduce, 81 | sleepUntil, 82 | sleep, 83 | runConcurrentlyAtMost, 84 | }; 85 | -------------------------------------------------------------------------------- /utils/Function.js: -------------------------------------------------------------------------------- 1 | // Matches the expected params of a function with exactly one object parameter 2 | // E.g. async ({ blockchainId, version }) => {} 3 | const FUNCTION_OBJECT_PARAM_REGEX = /^(?:async )?\({([a-zA-Z0-9,?\s]+)}\)/; 4 | 5 | // Matches functions starting with `() =>` or `async () =>` 6 | const FUNCTION_NO_PARAM_REGEX = /^(?:async )?\(\) =>/; 7 | 8 | /** 9 | * Returns an array of param names for a function that should either have 10 | * no params at all, or a single param of type object. 11 | * E.g. the function `async ({ blockchainId, version }) => {}` 12 | * would return `['blockchainId', 'version']` 13 | * See test file for examples of valid/invalid params. 14 | */ 15 | const getFunctionParamObjectKeys = (fn) => { 16 | const fnString = fn.toString(); 17 | const hasNoParams = FUNCTION_NO_PARAM_REGEX.test(fnString); 18 | if (hasNoParams) return []; 19 | 20 | const matches = FUNCTION_OBJECT_PARAM_REGEX.exec(fnString); 21 | if (matches === null) { 22 | throw new Error('API function does not have the expected parameter shape (it expects a signature with a single object)'); 23 | } 24 | 25 | const paramString = matches[1]; 26 | const params = paramString.match(/[a-zA-Z0-9]+/g); 27 | return params; 28 | }; 29 | 30 | export { 31 | getFunctionParamObjectKeys, 32 | }; 33 | -------------------------------------------------------------------------------- /utils/Function.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | // NOTE: for testing purposes, transform the imported file to commonjs format for Jest compatibility 4 | const { getFunctionParamObjectKeys } = require('./Function.js'); 5 | 6 | describe('Function utils', () => { 7 | test('getFunctionParamObjectKeys()', () => { 8 | const functionWithoutParams = () => { }; 9 | const functionWithExpectedShape1 = async ({ blockchainId, version }) => { }; 10 | const functionWithExpectedShape2 = async ({ 11 | blockchainId, 12 | version, 13 | }) => { }; 14 | const functionWithUnexpectedShape1 = async ({ blockchainId, version } = {}) => { }; 15 | const functionWithUnexpectedShape2 = async ({ blockchainId } = {}) => { }; 16 | 17 | expect(getFunctionParamObjectKeys(functionWithoutParams)).toEqual([]); 18 | expect(getFunctionParamObjectKeys(functionWithExpectedShape1)).toEqual(['blockchainId', 'version']); 19 | expect(getFunctionParamObjectKeys(functionWithExpectedShape2)).toEqual(['blockchainId', 'version']); 20 | expect(() => getFunctionParamObjectKeys(functionWithUnexpectedShape1)).toThrow('API function does not have the expected parameter shape'); 21 | expect(() => getFunctionParamObjectKeys(functionWithUnexpectedShape2)).toThrow('API function does not have the expected parameter shape'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /utils/Graphql/_fallback-data/crvusd-amms.json: -------------------------------------------------------------------------------- 1 | { 2 | "amms": [ 3 | { 4 | "id": "0x136e783846ef68c8bd00a3369f787df8d683a696", 5 | "volumeSnapshots": [ 6 | { 7 | "swapVolumeUSD": "0", 8 | "timestamp": "1699049639" 9 | } 10 | ] 11 | }, 12 | { 13 | "id": "0x1681195c176239ac5e72d9aebacf5b2492e0c4ee", 14 | "volumeSnapshots": [ 15 | { 16 | "swapVolumeUSD": "1396.885567672216437411657842756729", 17 | "timestamp": "1699207211" 18 | } 19 | ] 20 | }, 21 | { 22 | "id": "0x37417b2238aa52d0dd2d6252d989e728e8f706e4", 23 | "volumeSnapshots": [ 24 | { 25 | "swapVolumeUSD": "0", 26 | "timestamp": "1699217567" 27 | } 28 | ] 29 | }, 30 | { 31 | "id": "0xe0438eb3703bf871e31ce639bd351109c88666ea", 32 | "volumeSnapshots": [ 33 | { 34 | "swapVolumeUSD": "42329.26015253124127545573033088", 35 | "timestamp": "1699207235" 36 | } 37 | ] 38 | }, 39 | { 40 | "id": "0xf9bd9da2427a50908c4c6d1599d8e62837c2bcb0", 41 | "volumeSnapshots": [ 42 | { 43 | "swapVolumeUSD": "8047.1470819062095393620061180449", 44 | "timestamp": "1699209515" 45 | } 46 | ] 47 | }, 48 | { 49 | "id": "0xfa96ad0a9e64261db86950e2da362f5572c5c6fd", 50 | "volumeSnapshots": [ 51 | { 52 | "swapVolumeUSD": "0", 53 | "timestamp": "1699216679" 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /utils/Graphql/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Graphql wrapper that queries graphql as we normally would, 3 | * or falls back to simply returning fallback data if the 4 | * USE_FALLBACK_THEGRAPH_DATA flag is set to true. 5 | * 6 | * Signature: `request(endpoint, queryString, variables, fallbackDataFileName)` 7 | * If the third arg is omitted, will behave like it does w/o the wrapper. 8 | * 9 | * Use the flag FALLBACK_THEGRAPH_DATA_POPULATE_MODE to easily update 10 | * fallback data files’ contents by copy/pasting the latest json output. 11 | */ 12 | 13 | import { request, batchRequests, gql } from 'graphql-request'; 14 | import { 15 | USE_FALLBACK_THEGRAPH_DATA, 16 | FALLBACK_THEGRAPH_DATA_POPULATE_MODE, 17 | } from '#root/constants/AppConstants.js' 18 | 19 | const getFallbackData = async (fallbackDataFileName) => ( 20 | (await import(`./_fallback-data/${fallbackDataFileName}.json`, { assert: { type: 'json' } })).default 21 | ); 22 | 23 | const wrappedRequest = async (endpoint, queryString, variables, fallbackDataFileName) => { 24 | let data; 25 | 26 | if (USE_FALLBACK_THEGRAPH_DATA && typeof fallbackDataFileName !== 'undefined') { 27 | return getFallbackData(fallbackDataFileName); 28 | } 29 | 30 | try { 31 | data = await request(endpoint, gql`${queryString}`, variables); 32 | } catch (err) { 33 | if (typeof fallbackDataFileName !== 'undefined') { 34 | console.log(`CAUGHT AND HANDLED GRAPHQL ERROR: There was an error querying the following graphql endpoint: "${endpoint}". Fallback data was returned instead of fresh data. The caught error is logged below ↓`); 35 | console.log(err); 36 | 37 | data = await getFallbackData(fallbackDataFileName); 38 | } else { 39 | throw err; 40 | } 41 | } 42 | 43 | if (FALLBACK_THEGRAPH_DATA_POPULATE_MODE) { 44 | console.log(`FALLBACK_THEGRAPH_DATA_POPULATE_MODE for /fallback-data/${fallbackDataFileName}.json`, JSON.stringify(data)); 45 | } 46 | 47 | return data; 48 | }; 49 | 50 | const wrappedRequests = async (endpoint, queryStringAndVarArray, fallbackDataFileName) => { 51 | let data; 52 | 53 | if (USE_FALLBACK_THEGRAPH_DATA && typeof fallbackDataFileName !== 'undefined') { 54 | return getFallbackData(fallbackDataFileName); 55 | } 56 | 57 | console.log('queryStringAndVarArray', queryStringAndVarArray) 58 | console.log('queryStringAndVarArray.gql', queryStringAndVarArray.map(({ 59 | document, 60 | variables, 61 | }) => ({ 62 | document: gql`${document}`, 63 | variables, 64 | }))) 65 | 66 | try { 67 | data = await batchRequests(endpoint, queryStringAndVarArray.map(({ 68 | query, 69 | variables, 70 | }) => ({ 71 | query: gql`${query}`, 72 | variables, 73 | })), fallbackDataFileName); 74 | } catch (err) { 75 | if (typeof fallbackDataFileName !== 'undefined') { 76 | console.log(`CAUGHT AND HANDLED GRAPHQL ERROR: There was an error querying the following graphql endpoint (batch query): "${endpoint}". Fallback data was returned instead of fresh data. The caught error is logged below ↓`); 77 | console.log(err); 78 | 79 | data = await getFallbackData(fallbackDataFileName); 80 | } else { 81 | throw err; 82 | } 83 | } 84 | 85 | if (FALLBACK_THEGRAPH_DATA_POPULATE_MODE) { 86 | console.log(`FALLBACK_THEGRAPH_DATA_POPULATE_MODE for /fallback-data/${fallbackDataFileName}.json`, JSON.stringify(data)); 87 | } 88 | 89 | return data; 90 | }; 91 | 92 | export { 93 | wrappedRequest as request, 94 | wrappedRequests as requests, 95 | }; 96 | -------------------------------------------------------------------------------- /utils/Number.test.js: -------------------------------------------------------------------------------- 1 | import BN from 'bignumber.js'; 2 | import { 3 | truncWithoutZeroDecimals, 4 | formatLargeNumber, 5 | addTtlRandomness, 6 | } from './Number.js'; 7 | 8 | describe('Number utils', () => { 9 | test('truncWithoutZeroDecimals()', () => { 10 | expect(truncWithoutZeroDecimals(BN(2.02), 2)).toBe('2.02'); 11 | expect(truncWithoutZeroDecimals(BN(2.02), 1)).toBe('2'); 12 | expect(truncWithoutZeroDecimals(BN(2.02), 8)).toBe('2.02'); 13 | expect(truncWithoutZeroDecimals(BN(2.02), 0)).toBe('2'); 14 | expect(truncWithoutZeroDecimals(BN(2.00), 0)).toBe('2'); 15 | expect(truncWithoutZeroDecimals(BN(2.00))).toBe('2'); 16 | }); 17 | 18 | test('formatLargeNumber()', () => { 19 | expect(formatLargeNumber(BN(1200000))).toBe('1.2m'); 20 | expect(formatLargeNumber(BN(120000))).toBe('120k'); 21 | expect(formatLargeNumber(BN(1200))).toBe('1,200'); 22 | }); 23 | 24 | test('formatLargeNumber()', () => { 25 | expect(formatLargeNumber(BN(1200000))).toBe('1.2m'); 26 | expect(formatLargeNumber(BN(120000))).toBe('120k'); 27 | expect(formatLargeNumber(BN(1.23456))).toBe('1.23'); 28 | expect(formatLargeNumber(BN(0.23456))).toBe('0.23'); 29 | expect(formatLargeNumber(BN(0.00456))).toBe('0.0046'); 30 | expect(formatLargeNumber(BN(0.0005))).toBe('0.0005'); 31 | expect(formatLargeNumber(BN(0.1005))).toBe('0.1'); 32 | expect(formatLargeNumber(BN(0.000067))).toBe('0.000067'); 33 | expect(formatLargeNumber(BN(3509.4303209777585), 0)).toBe('3,509'); 34 | expect(formatLargeNumber(BN(3510), 0)).toBe('3,510'); 35 | }); 36 | 37 | test('addTtlRandomness()', () => { 38 | for (let i = 0; i < 50; i++) { 39 | const r10 = addTtlRandomness(10); 40 | const r30 = addTtlRandomness(30); 41 | const r60 = addTtlRandomness(60); 42 | const r120 = addTtlRandomness(120); 43 | 44 | expect(r10).toBe(10); 45 | expect(r30).toBeGreaterThanOrEqual(20); 46 | expect(r30).toBeLessThanOrEqual(60); 47 | expect(r60).toBeGreaterThanOrEqual(40); 48 | expect(r60).toBeLessThanOrEqual(120); 49 | expect(r120).toBeGreaterThanOrEqual(90); 50 | expect(r120).toBeLessThanOrEqual(210); 51 | } 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /utils/Pagination.js: -------------------------------------------------------------------------------- 1 | import Request from '#root/utils/Request.js'; 2 | 3 | /** 4 | * Fetch paginated endpoints, returns an array of the pages' results. 5 | * A paginated endpoint is expected to have (names are optionally configurable) 6 | * properties `page`, `per_page` and `count` (retrieved page number; items per page; total items). 7 | * It must also have an array that holds results data (defaults to `data`). 8 | * 9 | * Example response of a compatible paginated endpoint: 10 | * ``` 11 | * { 12 | * "page": 1, 13 | * "per_page": 100, 14 | * "count": 6, 15 | * "data": [...items], 16 | * } 17 | * ``` 18 | * 19 | * Params: 20 | * - `url`: path without query parameters 21 | * - `data`: data that'll be url-encoded and passed as query parameters 22 | */ 23 | const DEFAULT_PAGINATION_PROPS = { 24 | PAGE: 'page', 25 | PER_PAGE: 'per_page', 26 | COUNT: 'count', 27 | DATA: 'data', 28 | }; 29 | 30 | const fetchPages = async (url, data = {}, paginationPropsOverride = {}) => { 31 | const paginationProps = { ...DEFAULT_PAGINATION_PROPS, ...paginationPropsOverride }; 32 | const fetchedPages = []; 33 | 34 | let page = 1; 35 | let count; 36 | 37 | do { 38 | const dataWithPaginationParams = { 39 | ...data, 40 | [paginationProps.PAGE]: page, 41 | [paginationProps.PER_PAGE]: data[paginationProps.PER_PAGE], 42 | } 43 | const result = await (await Request.get(url, dataWithPaginationParams)).json(); 44 | 45 | fetchedPages.push(...result[paginationProps.DATA]); 46 | page = result[paginationProps.PAGE] + 1; 47 | count = result[paginationProps.COUNT]; 48 | } while ((page - 1) * data[paginationProps.PER_PAGE] < count); 49 | 50 | return fetchedPages; 51 | }; 52 | 53 | export { 54 | fetchPages, 55 | }; 56 | -------------------------------------------------------------------------------- /utils/Request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface to send requests – thin wrapper around Fetch 3 | * 4 | * send, get and post return a Promise that resolves with a Response object 5 | * from which can be retrieved Body's contents. They all receive the same params: 6 | * 7 | * @param {string} url 8 | * @param {object} [data] - any piece of data to send alongside the request 9 | * @param {object} [customSettings] - any additional options to pass to Fetch 10 | */ 11 | 12 | import formUrlEncoded from 'form-urlencoded'; 13 | 14 | class Request { 15 | static send(url, data = {}, customSettings = {}) { 16 | const defaultSettings = { method: 'GET' }; 17 | const settings = Object.assign(defaultSettings, customSettings); 18 | 19 | switch (settings.method) { 20 | // Attach data as query string params for GET and HEAD requests 21 | case 'GET': 22 | case 'HEAD': { 23 | const queryStringData = formUrlEncoded(data); 24 | if (queryStringData) url += `?${queryStringData}`; // eslint-disable-line no-param-reassign 25 | break; 26 | } 27 | 28 | default: 29 | settings.body = JSON.stringify(data); 30 | settings.headers = { 31 | 'Content-Type': 'application/json', 32 | }; 33 | break; 34 | } 35 | 36 | // Throws if response status code outside of range 200-299 37 | return fetch(url, settings) 38 | .then((response) => { 39 | if (!response.ok) throw response; 40 | return response; 41 | }); 42 | } 43 | 44 | static get(url, data, customSettings = {}) { 45 | return Request.send(url, data, Object.assign(customSettings, { method: 'GET' })); 46 | } 47 | 48 | static post(url, data, customSettings = {}) { 49 | return Request.send(url, data, Object.assign(customSettings, { method: 'POST' })); 50 | } 51 | 52 | static uploadFile(url, data, customSettings = {}) { 53 | const settings = Object.assign({ method: 'POST' }, customSettings); 54 | 55 | const formData = new FormData(); 56 | Array.from(Object.entries(data)).forEach(([key, value]) => { 57 | formData.append(key, value); 58 | }); 59 | 60 | settings.body = formData; 61 | 62 | return fetch(url, settings) 63 | .then((response) => { 64 | if (!response.ok) throw response; // Throws if response status code outside of range 200-299 65 | return response; 66 | }); 67 | } 68 | } 69 | 70 | export default Request; 71 | -------------------------------------------------------------------------------- /utils/String.js: -------------------------------------------------------------------------------- 1 | const lc = (string) => string?.toLowerCase(); 2 | 3 | const maxChars = (string, max) => string.length <= max ? string : `${string.slice(0, (max - 1))}…`; 4 | 5 | export { 6 | lc, 7 | maxChars, 8 | }; 9 | -------------------------------------------------------------------------------- /utils/Web3/index.js: -------------------------------------------------------------------------------- 1 | export { default as web3, MulticallContract, multiCall, getEncodedCalls, getDecodedData, canAutomaticallyChangeNetwork, changeNetwork, ZERO_ADDRESS } from './web3.js'; 2 | export * from './parsing.js'; 3 | -------------------------------------------------------------------------------- /utils/Web3/parsing.js: -------------------------------------------------------------------------------- 1 | import BN from 'bignumber.js'; 2 | 3 | const uintToBN = (uint256, decimals) => ( 4 | typeof uint256 !== 'undefined' ? 5 | BN(uint256).div(10 ** decimals) : 6 | undefined 7 | ); 8 | 9 | const numberToUint = (number, decimals) => { 10 | if (typeof number === 'undefined' || typeof decimals === 'undefined') { 11 | throw new Error('Missing some mandatory parameters for numberToUint(number, decimals)'); 12 | } 13 | 14 | return BN(number).times(10 ** decimals).toFixed(); 15 | }; 16 | 17 | export { 18 | uintToBN, 19 | numberToUint, 20 | }; 21 | -------------------------------------------------------------------------------- /utils/data/abis/json/ERC20.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/aRewards.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"constant":true,"inputs":[],"name":"adai","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"claim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_claimer","type":"address"}],"name":"claimable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"seize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"yfi","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/alusd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":1650},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"bool","name":"_is_deposit"}],"stateMutability":"view","type":"function","gas":2717}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/atricrypto/zap.json: -------------------------------------------------------------------------------- 1 | [{"inputs": [{"name": "_pool", "type": "address"}, {"name": "_base_pool", "type": "address"}], "outputs": [], "stateMutability": "nonpayable", "type": "constructor", "name": "constructor"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_min_mint_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "add_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 22519, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}, {"name": "_min_dy", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "exchange_underlying", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 29236, "inputs": [{"name": "_amount", "type": "uint256"}, {"name": "_min_amounts", "type": "uint256[5]"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 14489, "inputs": [{"name": "_token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}, {"name": "_min_amount", "type": "uint256"}, {"name": "_receiver", "type": "address"}], "name": "remove_liquidity_one_coin", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"gas": 8391, "inputs": [{"name": "i", "type": "uint256"}, {"name": "j", "type": "uint256"}, {"name": "_dx", "type": "uint256"}], "name": "get_dy_underlying", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 4305, "inputs": [{"name": "_amounts", "type": "uint256[5]"}, {"name": "_is_deposit", "type": "bool"}], "name": "calc_token_amount", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 6199, "inputs": [{"name": "token_amount", "type": "uint256"}, {"name": "i", "type": "uint256"}], "name": "calc_withdraw_one_coin", "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"gas": 1407, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1437, "inputs": [{"name": "arg0", "type": "uint256"}], "name": "underlying_coins", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1358, "inputs": [], "name": "pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1388, "inputs": [], "name": "base_pool", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"gas": 1418, "inputs": [], "name": "token", "outputs": [{"name": "", "type": "address"}], "stateMutability": "view", "type": "function"}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/bbtc/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":31141},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32919},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14467},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":38200},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3147},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4414},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/busdv2/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":1650},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"bool","name":"_is_deposit"}],"stateMutability":"view","type":"function","gas":2717}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/compound/migration.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputs":[], 4 | "inputs":[ 5 | { 6 | "type":"address", 7 | "name":"_old" 8 | }, 9 | { 10 | "type":"address", 11 | "name":"_old_token" 12 | }, 13 | { 14 | "type":"address", 15 | "name":"_new" 16 | }, 17 | { 18 | "type":"address", 19 | "name":"_new_token" 20 | }, 21 | { 22 | "type":"address[2]", 23 | "name":"_coins" 24 | } 25 | ], 26 | "constant":false, 27 | "payable":false, 28 | "type":"constructor" 29 | }, 30 | { 31 | "name":"migrate", 32 | "outputs":[], 33 | "inputs":[], 34 | "constant":false, 35 | "payable":false, 36 | "type":"function", 37 | "gas":106812 38 | }, 39 | { 40 | "name":"coins", 41 | "outputs":[ 42 | { 43 | "type":"address", 44 | "name":"out" 45 | } 46 | ], 47 | "inputs":[ 48 | { 49 | "type":"int128", 50 | "name":"arg0" 51 | } 52 | ], 53 | "constant":true, 54 | "payable":false, 55 | "type":"function", 56 | "gas":1380 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /utils/data/abis/json/dusd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/frax/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":1650},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"bool","name":"_is_deposit"}],"stateMutability":"view","type":"function","gas":2717}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/fusdt/deposit.json: -------------------------------------------------------------------------------- 1 | [{"stateMutability":"nonpayable","type":"constructor","inputs":[{"name":"_pool","type":"address"},{"name":"_base_pool","type":"address"}],"outputs":[]},{"stateMutability":"nonpayable","type":"function","name":"add_liquidity","inputs":[{"name":"_amounts","type":"uint256[3]"},{"name":"_min_mint_amount","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}],"gas":25605},{"stateMutability":"nonpayable","type":"function","name":"remove_liquidity","inputs":[{"name":"_amount","type":"uint256"},{"name":"_min_amounts","type":"uint256[3]"}],"outputs":[{"name":"","type":"uint256[3]"}],"gas":26803},{"stateMutability":"nonpayable","type":"function","name":"remove_liquidity_one_coin","inputs":[{"name":"_token_amount","type":"uint256"},{"name":"i","type":"int128"},{"name":"_min_amount","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}],"gas":14494},{"stateMutability":"nonpayable","type":"function","name":"remove_liquidity_imbalance","inputs":[{"name":"_amounts","type":"uint256[3]"},{"name":"_max_burn_amount","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}],"gas":32024},{"stateMutability":"view","type":"function","name":"calc_withdraw_one_coin","inputs":[{"name":"_token_amount","type":"uint256"},{"name":"i","type":"int128"}],"outputs":[{"name":"","type":"uint256"}],"gas":3174},{"stateMutability":"view","type":"function","name":"calc_token_amount","inputs":[{"name":"_amounts","type":"uint256[3]"},{"name":"_is_deposit","type":"bool"}],"outputs":[{"name":"","type":"uint256"}],"gas":3998},{"stateMutability":"view","type":"function","name":"pool","inputs":[],"outputs":[{"name":"","type":"address"}],"gas":1268},{"stateMutability":"view","type":"function","name":"base_pool","inputs":[],"outputs":[{"name":"","type":"address"}],"gas":1298},{"stateMutability":"view","type":"function","name":"coins","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"address"}],"gas":1437},{"stateMutability":"view","type":"function","name":"base_coins","inputs":[{"name":"arg0","type":"uint256"}],"outputs":[{"name":"","type":"address"}],"gas":1467}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/gusd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/husd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/iearn/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address[4]","name":"_coins"},{"type":"address[4]","name":"_underlying_coins"},{"type":"address","name":"_curve"},{"type":"address","name":"_token"}],"constant":false,"payable":false,"type":"constructor"},{"name":"add_liquidity","outputs":[],"inputs":[{"type":"uint256[4]","name":"uamounts"},{"type":"uint256","name":"min_mint_amount"}],"constant":false,"payable":false,"type":"function","gas":164560},{"name":"remove_liquidity","outputs":[],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_uamounts"}],"constant":false,"payable":false,"type":"function","gas":99521},{"name":"remove_liquidity_imbalance","outputs":[],"inputs":[{"type":"uint256[4]","name":"uamounts"},{"type":"uint256","name":"max_burn_amount"}],"constant":false,"payable":false,"type":"function","gas":120148},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"constant":true,"payable":false,"type":"function","gas":3881601},{"name":"remove_liquidity_one_coin","outputs":[],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"min_uamount"}],"constant":false,"payable":false,"type":"function"},{"name":"remove_liquidity_one_coin","outputs":[],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"min_uamount"},{"type":"bool","name":"donate_dust"}],"constant":false,"payable":false,"type":"function"},{"name":"withdraw_donated_dust","outputs":[],"inputs":[],"constant":false,"payable":false,"type":"function","gas":63973},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"int128","name":"arg0"}],"constant":true,"payable":false,"type":"function","gas":1680},{"name":"underlying_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"int128","name":"arg0"}],"constant":true,"payable":false,"type":"function","gas":1710},{"name":"curve","outputs":[{"type":"address","name":""}],"inputs":[],"constant":true,"payable":false,"type":"function","gas":1541},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"constant":true,"payable":false,"type":"function","gas":1571}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/linkusd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/lusd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":1650},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"bool","name":"_is_deposit"}],"stateMutability":"view","type":"function","gas":2717}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/minter.json: -------------------------------------------------------------------------------- 1 | [{"name":"Minted","inputs":[{"type":"address","name":"recipient","indexed":true},{"type":"address","name":"gauge","indexed":false},{"type":"uint256","name":"minted","indexed":false}],"anonymous":false,"type":"event"},{"outputs":[],"inputs":[{"type":"address","name":"_token"},{"type":"address","name":"_controller"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"mint","outputs":[],"inputs":[{"type":"address","name":"gauge_addr"}],"stateMutability":"nonpayable","type":"function","gas":100038},{"name":"mint_many","outputs":[],"inputs":[{"type":"address[8]","name":"gauge_addrs"}],"stateMutability":"nonpayable","type":"function","gas":408502},{"name":"mint_for","outputs":[],"inputs":[{"type":"address","name":"gauge_addr"},{"type":"address","name":"_for"}],"stateMutability":"nonpayable","type":"function","gas":101219},{"name":"toggle_approve_mint","outputs":[],"inputs":[{"type":"address","name":"minting_user"}],"stateMutability":"nonpayable","type":"function","gas":36726},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"controller","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1331},{"name":"minted","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"},{"type":"address","name":"arg1"}],"stateMutability":"view","type":"function","gas":1669},{"name":"allowed_to_mint_for","outputs":[{"type":"bool","name":""}],"inputs":[{"type":"address","name":"arg0"},{"type":"address","name":"arg1"}],"stateMutability":"view","type":"function","gas":1699}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/multicall.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"name":"timestamp","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"components":[{"name":"target","type":"address"},{"name":"callData","type":"bytes"}],"name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"name":"blockNumber","type":"uint256"},{"name":"returnData","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getLastBlockHash","outputs":[{"name":"blockHash","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"name":"difficulty","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentBlockGasLimit","outputs":[{"name":"gaslimit","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"name":"coinbase","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"name":"blockHash","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/musd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/obtc/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":31141},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32919},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14467},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":38200},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3147},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4414},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/pbtc/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":31141},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32919},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14467},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":38200},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3147},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4414},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/rsv/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/tbtc/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/tusd/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_deposit_amounts"},{"type":"uint256","name":"_min_mint_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"uint256[4]","name":"_min_amounts"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_burn_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"}],"stateMutability":"nonpayable","type":"function"},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"uint256","name":"_max_burn_amount"},{"type":"address","name":"_receiver"}],"stateMutability":"nonpayable","type":"function"},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":1650},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_pool"},{"type":"uint256[4]","name":"_amounts"},{"type":"bool","name":"_is_deposit"}],"stateMutability":"view","type":"function","gas":2717}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/usdk/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/usdn/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":33623},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32551},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14313},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":37758},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3002},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4325},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/usdn_rewards.json: -------------------------------------------------------------------------------- 1 | [{"name":"CommitOwnership","inputs":[{"type":"address","name":"admin","indexed":false}],"anonymous":false,"type":"event"},{"name":"ApplyOwnership","inputs":[{"type":"address","name":"admin","indexed":false}],"anonymous":false,"type":"event"},{"name":"Deposit","inputs":[{"type":"address","name":"coin","indexed":true},{"type":"uint256","name":"value","indexed":false}],"anonymous":false,"type":"event"},{"name":"ClaimStarted","inputs":[{"type":"address","name":"coin","indexed":true},{"type":"address","name":"account","indexed":true}],"anonymous":false,"type":"event"},{"name":"ClaimReward","inputs":[{"type":"address","name":"to","indexed":true},{"type":"uint256","name":"value","indexed":false}],"anonymous":false,"type":"event"},{"name":"Withdrawal","inputs":[{"type":"address","name":"coin","indexed":false},{"type":"uint256","name":"value","indexed":false}],"anonymous":false,"type":"event"},{"outputs":[],"inputs":[{"type":"address","name":"_gauge"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"claimable_tokens","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"_coin"},{"type":"address","name":"_account"}],"stateMutability":"view","type":"function","gas":14622},{"name":"deposit","outputs":[],"inputs":[{"type":"address","name":"_coin"},{"type":"uint256","name":"_value"}],"stateMutability":"nonpayable","type":"function","gas":131578},{"name":"claim","outputs":[],"inputs":[{"type":"address","name":"_coin"}],"stateMutability":"nonpayable","type":"function","gas":182119},{"name":"emergency_withdrawal","outputs":[],"inputs":[{"type":"address","name":"_coin"}],"stateMutability":"nonpayable","type":"function","gas":61532},{"name":"commit_transfer_ownership","outputs":[],"inputs":[{"type":"address","name":"addr"}],"stateMutability":"nonpayable","type":"function","gas":37744},{"name":"apply_transfer_ownership","outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"function","gas":38600},{"name":"owner","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"future_owner","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"gauge","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1331},{"name":"last_fraction","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"},{"type":"address","name":"arg1"}],"stateMutability":"view","type":"function","gas":1791},{"name":"last_integrate_inv","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"},{"type":"address","name":"arg1"}],"stateMutability":"view","type":"function","gas":1821},{"name":"integrated_deposit","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"}],"stateMutability":"view","type":"function","gas":1636},{"name":"commited_deposit_for","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"},{"type":"address","name":"arg1"}],"stateMutability":"view","type":"function","gas":1881},{"name":"current_working_supply","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"address","name":"arg0"}],"stateMutability":"view","type":"function","gas":1696}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/usdp/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":34073},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32919},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14467},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":38200},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3147},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4414},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/abis/json/ust/deposit.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[{"type":"address","name":"_pool"},{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"constructor"},{"name":"add_liquidity","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"min_mint_amount"}],"stateMutability":"nonpayable","type":"function","gas":34073},{"name":"remove_liquidity","outputs":[{"type":"uint256[4]","name":""}],"inputs":[{"type":"uint256","name":"_amount"},{"type":"uint256[4]","name":"min_amounts"}],"stateMutability":"nonpayable","type":"function","gas":32919},{"name":"remove_liquidity_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"},{"type":"uint256","name":"_min_amount"}],"stateMutability":"nonpayable","type":"function","gas":14467},{"name":"remove_liquidity_imbalance","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"uint256","name":"max_burn_amount"}],"stateMutability":"nonpayable","type":"function","gas":38200},{"name":"calc_withdraw_one_coin","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256","name":"_token_amount"},{"type":"int128","name":"i"}],"stateMutability":"view","type":"function","gas":3147},{"name":"calc_token_amount","outputs":[{"type":"uint256","name":""}],"inputs":[{"type":"uint256[4]","name":"amounts"},{"type":"bool","name":"is_deposit"}],"stateMutability":"view","type":"function","gas":4414},{"name":"pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1241},{"name":"token","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1271},{"name":"base_pool","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function","gas":1301},{"name":"coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1440},{"name":"base_coins","outputs":[{"type":"address","name":""}],"inputs":[{"type":"uint256","name":"arg0"}],"stateMutability":"view","type":"function","gas":1470}] 2 | -------------------------------------------------------------------------------- /utils/data/assets-prices.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import Request from '#root/utils/Request.js'; 3 | import { arrayToHashmap } from '#root/utils/Array.js'; 4 | 5 | const getAssetsPrices = memoize(async (assetCoingeckoIds) => { 6 | if (assetCoingeckoIds.length === 0) return {}; 7 | 8 | // https://defillama.com/docs/api 9 | return Request.get(`https://coins.llama.fi/prices/current/${assetCoingeckoIds.map((id) => `coingecko:${id}`).join(',')}?aa`) 10 | .then((response) => response.json()) 11 | .then(({ coins: prices }) => arrayToHashmap(assetCoingeckoIds.map((id) => [ 12 | id, 13 | id === 'dollar' ? 1 : prices[`coingecko:${id}`]?.price, 14 | ]))); 15 | }, { 16 | promise: true, 17 | maxAge: 2 * 60 * 1000, // 2 min 18 | primitive: true, 19 | }); 20 | 21 | export default getAssetsPrices; 22 | -------------------------------------------------------------------------------- /utils/data/curve-lending-vaults-data.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import configs from '#root/constants/configs/index.js' 3 | import { flattenArray } from '#root/utils/Array.js'; 4 | import { sequentialPromiseFlatMap } from '#root/utils/Async.js'; 5 | import getLendingVaultsFn from '#root/routes/v1/getLendingVaults/[lendingBlockchainId]/[lendingRegistryId].js'; 6 | 7 | const attachBlockchainId = (blockchainId, vaultData) => ({ 8 | ...vaultData, 9 | blockchainId, 10 | }); 11 | 12 | const attachRegistryId = (registryId, vaultData) => ({ 13 | ...vaultData, 14 | registryId, 15 | }); 16 | 17 | const getAllCurveLendingVaultsData = memoize(async (blockchainIds, preventQueryingFactoData = true) => ( 18 | flattenArray(await sequentialPromiseFlatMap(blockchainIds, async (lendingBlockchainId) => { 19 | const config = configs[lendingBlockchainId]; 20 | const platformRegistries = Object.keys(config.lendingVaultRegistries ?? []); 21 | 22 | return Promise.all(platformRegistries.map((lendingRegistryId) => ( 23 | (getLendingVaultsFn.straightCall({ lendingBlockchainId, lendingRegistryId, preventQueryingFactoData })) 24 | .then((res) => res.lendingVaultData.map((vaultData) => attachBlockchainId(lendingBlockchainId, vaultData)).map((vaultData) => attachRegistryId(lendingRegistryId, vaultData))) 25 | ))) 26 | })) 27 | ), { 28 | promise: true, 29 | maxAge: 60 * 1000, // 60s 30 | length: 2, 31 | }); 32 | 33 | export default getAllCurveLendingVaultsData; 34 | -------------------------------------------------------------------------------- /utils/data/curve-platform-registries.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import memoize from 'memoizee'; 3 | import configs from '#root/constants/configs/index.js'; 4 | import ADDRESS_GETTER_ABI from '#root/constants/abis/address_getter.json' assert { type: 'json' }; 5 | import { multiCall } from '#root/utils/Calls.js'; 6 | import { ZERO_ADDRESS } from '#root/utils/Web3/index.js'; 7 | 8 | const getMainRegistryAddress = async (blockchainId) => { 9 | const { rpcUrl, multicall2Address } = configs[blockchainId]; 10 | const web3 = new Web3(rpcUrl); 11 | 12 | return (await multiCall([{ 13 | address: '0x0000000022d53366457f9d5e68ec105046fc4383', 14 | abi: ADDRESS_GETTER_ABI, 15 | methodName: 'get_address', 16 | params: [0], 17 | networkSettings: { web3, multicall2Address }, 18 | }]))[0]; 19 | }; 20 | 21 | const getPlatformRegistries = memoize(async (blockchainId) => { 22 | const config = configs[blockchainId]; 23 | if (typeof config === 'undefined') { 24 | throw new Error(`No config data for blockchainId "${blockchainId}"`); 25 | } 26 | 27 | const { 28 | getFactoryRegistryAddress, 29 | getCryptoRegistryAddress, 30 | getFactoryCryptoRegistryAddress, 31 | getFactoryCrvusdRegistryAddress, 32 | getFactoryTwocryptoRegistryAddress, 33 | getFactoryTricryptoRegistryAddress, 34 | getFactoryEywaRegistryAddress, 35 | getFactoryStableswapNgRegistryAddress, 36 | hasNoMainRegistry, 37 | } = config; 38 | 39 | const registryIds = [ 40 | (!hasNoMainRegistry ? 'main' : null), 41 | (typeof getFactoryRegistryAddress === 'function' ? 'factory' : null), 42 | (typeof getCryptoRegistryAddress === 'function' ? 'crypto' : null), 43 | (typeof getFactoryCryptoRegistryAddress === 'function' ? 'factory-crypto' : null), 44 | (typeof getFactoryCrvusdRegistryAddress === 'function' ? 'factory-crvusd' : null), 45 | (typeof getFactoryTwocryptoRegistryAddress === 'function' ? 'factory-twocrypto' : null), 46 | (typeof getFactoryTricryptoRegistryAddress === 'function' ? 'factory-tricrypto' : null), 47 | (typeof getFactoryEywaRegistryAddress === 'function' ? 'factory-eywa' : null), 48 | (typeof getFactoryStableswapNgRegistryAddress === 'function' ? 'factory-stable-ng' : null), 49 | ]; 50 | 51 | const registryAddresses = [ 52 | (!hasNoMainRegistry ? (await getMainRegistryAddress(blockchainId)) : null), 53 | (typeof getFactoryRegistryAddress === 'function' ? (await getFactoryRegistryAddress()) : null), 54 | (typeof getCryptoRegistryAddress === 'function' ? (await getCryptoRegistryAddress()) : null), 55 | (typeof getFactoryCryptoRegistryAddress === 'function' ? (await getFactoryCryptoRegistryAddress()) : null), 56 | (typeof getFactoryCrvusdRegistryAddress === 'function' ? (await getFactoryCrvusdRegistryAddress()) : null), 57 | (typeof getFactoryTwocryptoRegistryAddress === 'function' ? (await getFactoryTwocryptoRegistryAddress()) : null), 58 | (typeof getFactoryTricryptoRegistryAddress === 'function' ? (await getFactoryTricryptoRegistryAddress()) : null), 59 | (typeof getFactoryEywaRegistryAddress === 'function' ? (await getFactoryEywaRegistryAddress()) : null), 60 | (typeof getFactoryStableswapNgRegistryAddress === 'function' ? (await getFactoryStableswapNgRegistryAddress()) : null), 61 | ]; 62 | 63 | return { 64 | registryIds: registryIds.filter((o, i) => o !== null && registryAddresses[i] !== ZERO_ADDRESS), 65 | registryAddresses: registryAddresses.filter((o) => o !== null && o !== ZERO_ADDRESS), 66 | }; 67 | }, { 68 | promise: true, 69 | }); 70 | 71 | export default getPlatformRegistries; 72 | -------------------------------------------------------------------------------- /utils/data/curve-pools-data.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import { flattenArray } from '#root/utils/Array.js'; 3 | import { sequentialPromiseFlatMap } from '#root/utils/Async.js'; 4 | import getPoolsFn from '#root/routes/v1/getPools/[blockchainId]/[registryId].js'; 5 | import getPlatformRegistries from '#root/utils/data/curve-platform-registries.js'; 6 | 7 | const attachBlockchainId = (blockchainId, poolData) => ({ 8 | ...poolData, 9 | blockchainId, 10 | }); 11 | 12 | const attachRegistryId = (registryId, poolData) => ({ 13 | ...poolData, 14 | registryId, 15 | }); 16 | 17 | const attachFactoryTag = (poolData) => ({ 18 | ...poolData, 19 | factory: true, 20 | }); 21 | 22 | const getAllCurvePoolsData = memoize(async (blockchainIds, preventQueryingFactoData = true) => ( 23 | flattenArray(await sequentialPromiseFlatMap(blockchainIds, async (blockchainId) => ( 24 | Promise.all((await getPlatformRegistries(blockchainId)).registryIds.map((registryId) => ( 25 | (getPoolsFn.straightCall({ blockchainId, registryId, preventQueryingFactoData })) 26 | .then((res) => res.poolData.map((poolData) => attachBlockchainId(blockchainId, poolData)).map((poolData) => attachRegistryId(registryId, poolData)).map((poolData) => ( 27 | registryId.startsWith('factory') ? 28 | attachFactoryTag(poolData) : 29 | poolData 30 | ))) 31 | ))) 32 | ))) 33 | ), { 34 | promise: true, 35 | maxAge: 60 * 1000, // 60s 36 | length: 2, 37 | }); 38 | 39 | export default getAllCurvePoolsData; 40 | -------------------------------------------------------------------------------- /utils/data/curve-prices.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import Request from '#root/utils/Request.js'; 3 | import { arrayToHashmap } from '#root/utils/Array.js'; 4 | import { lc } from '#root/utils/String.js'; 5 | import { getNowTimestamp } from '#root/utils/Date.js'; 6 | 7 | const IGNORED_TOKEN_ADDRESSES = { 8 | ethereum: [ 9 | '0x691c25C461DaFC47792b6E4d674FBB637bca1C6F', // Spam 10 | '0x3D5a15A9d8EA1D76A32cD70eea882968992d8D95', // Spam 11 | '0xe80c0cd204d654cebe8dd64a4857cab6be8345a3', // JPEG usd calculation is being fixed in curve-js, to remove soon 12 | '0x73a052500105205d34Daf004eAb301916DA8190f', // This usd calculation is wrong in curve-js due to broken pool 13 | ].map(lc), 14 | }; 15 | 16 | // Coins in these pools won't have their usd price calculated using internal prices 17 | const IGNORED_POOL_ADDRESSES = { 18 | ethereum: [ 19 | '0x808dB6E464279C6A77a1164E0b34d64Bd6fB526E', // Broken pool 20 | '0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51', // Broken pool 21 | ].map(lc), 22 | }; 23 | 24 | const getCurvePrices = memoize(async (blockchainId) => { 25 | if (typeof blockchainId === 'undefined') throw new Error('Missing blockchainId param'); 26 | const { data } = await (await Request.get(`https://prices.curve.finance/v1/usd_price/${blockchainId}`)).json(); 27 | 28 | return arrayToHashmap( 29 | data 30 | // Only use fresh prices (last updated in the past 24h, i.e. had a trade in the past 24h) 31 | .filter(({ last_updated }) => ( 32 | // Append 'Z' because this is a UTC datetime string 33 | (Date.parse(`${last_updated}Z`) / 1000) > (getNowTimestamp() - (86400)) 34 | )) 35 | .map(({ 36 | address, 37 | usd_price, 38 | }) => [ 39 | lc(address), 40 | usd_price, 41 | ]) 42 | .filter(([lcAddress]) => !(IGNORED_TOKEN_ADDRESSES[blockchainId] || []).includes(lcAddress)) 43 | ); 44 | }, { 45 | promise: true, 46 | maxAge: 5 * 60 * 1000, // 5 min 47 | primitive: true, 48 | // The preFetch option makes this in-memory cache behave kind of like a stale-while-revalidate strategy, 49 | // except it refreshes the value *ahead of its expiration time*, not *once it's stale*. Iow it keeps the 50 | // value fresh and minimizes the chances of having to wait for a fresh value. 51 | preFetch: true, 52 | }); 53 | 54 | export default getCurvePrices; 55 | export { 56 | IGNORED_TOKEN_ADDRESSES, 57 | IGNORED_POOL_ADDRESSES, 58 | }; 59 | -------------------------------------------------------------------------------- /utils/data/getCrvusdPrice.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import Web3 from 'web3'; 3 | import configs from '#root/constants/configs/index.js'; 4 | import { lc } from '#root/utils/String.js'; 5 | import { multiCall } from '#root/utils/Calls.js'; 6 | import AGGREGATOR_STABLE_PRICE_ABI from '#root/constants/abis/AggregatorStablePrice.json' assert { type: 'json' }; 7 | 8 | const CRVUSD_ADDRESSES = { 9 | ethereum: lc('0xf939e0a03fb07f59a73314e73794be0e57ac1b4e'), 10 | base: lc('0x417ac0e078398c154edfadd9ef675d30be60af93'), 11 | arbitrum: lc('0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5'), 12 | xdai: lc('0xaBEf652195F98A91E490f047A5006B71c85f058d'), 13 | optimism: lc('0xc52d7f23a2e460248db6ee192cb23dd12bddcbf6'), 14 | polygon: lc('0xc4ce1d6f5d98d65ee25cf85e9f2e9dcfee6cb5d6'), 15 | fraxtal: lc('0xB102f7Efa0d5dE071A8D37B3548e1C7CB148Caf3'), 16 | sonic: lc('0x7fff4c4a827c84e32c5e175052834111b2ccd270'), 17 | }; 18 | 19 | const AGGREGATOR_STABLE_PRICE_ADDRESS = '0x18672b1b0c623a30089A280Ed9256379fb0E4E62'; 20 | 21 | const { rpcUrl, multicall2Address } = configs.ethereum; 22 | const web3 = new Web3(rpcUrl); 23 | 24 | const getCrvusdPrice = memoize(async () => { 25 | const [crvusdPrice] = await multiCall([{ 26 | address: AGGREGATOR_STABLE_PRICE_ADDRESS, 27 | abi: AGGREGATOR_STABLE_PRICE_ABI, 28 | methodName: 'price', 29 | networkSettings: { web3, multicall2Address }, 30 | }]); 31 | 32 | return (crvusdPrice / 1e18); 33 | }, { 34 | promise: true, 35 | maxAge: 1 * 60 * 1000, // 1 min 36 | }); 37 | 38 | const getCrvusdPriceForBlockchainId = async (blockchainId) => { 39 | const isCrvusdDeployedThere = Object.hasOwn(CRVUSD_ADDRESSES, blockchainId); 40 | if (!isCrvusdDeployedThere) return {}; 41 | 42 | const crvusdPrice = await getCrvusdPrice(); 43 | 44 | return { 45 | [CRVUSD_ADDRESSES[blockchainId]]: crvusdPrice, 46 | }; 47 | }; 48 | 49 | export default getCrvusdPriceForBlockchainId; 50 | export { CRVUSD_ADDRESSES }; 51 | -------------------------------------------------------------------------------- /utils/data/getDaiAPYs.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import Web3 from 'web3'; 3 | import configs from '#root/constants/configs/index.js'; 4 | import { multiCall } from '../Calls.js'; 5 | import { uintToBN } from '../Web3/index.js'; 6 | import { lc } from '../String.js'; 7 | 8 | const MAKER_POT_ADDRESS = '0x197e90f9fad81970ba7976f33cbd77088e5d7cf7'; 9 | const MAKER_POT_ABI_SUBSET = [{ "constant": true, "inputs": [], "name": "dsr", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }]; 10 | 11 | const { rpcUrl, multicall2Address } = configs.ethereum; 12 | const web3 = new Web3(rpcUrl); 13 | 14 | const getDaiAPYs = memoize(async () => { 15 | const dsr = await multiCall([{ 16 | address: MAKER_POT_ADDRESS, 17 | abi: MAKER_POT_ABI_SUBSET, 18 | methodName: 'dsr', 19 | networkSettings: { web3, multicall2Address }, 20 | }]); 21 | 22 | const rateDaily = uintToBN(dsr, 27).minus(1).times(86400); 23 | const apy = rateDaily.plus(1).pow(365 / 1).minus(1); 24 | return [{ 25 | address: lc('0x83f20f44975d03b1b09e64809b757c47f942beea'), 26 | apy: apy.dp(4).toNumber(), 27 | }]; 28 | }, { 29 | promise: true, 30 | maxAge: 60 * 60 * 1000, // 60 min 31 | preFetch: true, 32 | }); 33 | 34 | export default getDaiAPYs; 35 | -------------------------------------------------------------------------------- /utils/data/getETHLSTAPYs.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import { flattenArray } from '#root/utils/Array.js'; 3 | import { lc } from '#root/utils/String.js'; 4 | 5 | const FALLBACK_RETURN_VALUE = {}; 6 | 7 | // Returns an array of `[lstAddress, blockchainId, stakingApy]` 8 | const getETHLSTAPYs = memoize(async () => { 9 | const [ 10 | { status, data }, 11 | LST_METADATA, 12 | ] = await Promise.all([ 13 | (await fetch('https://yields.llama.fi/pools')).json(), 14 | (await fetch('https://raw.githubusercontent.com/curvefi/curve-api-metadata/main/ethereum-lst-defillama.json')).json(), 15 | ]); 16 | if (status !== 'success') return FALLBACK_RETURN_VALUE; 17 | 18 | const map = flattenArray(LST_METADATA.map(({ defillamaProps, lstAddresses }) => { 19 | const stakingApy = (data.find(({ 20 | chain, 21 | project, 22 | symbol, 23 | }) => ( 24 | chain === 'Ethereum' && 25 | project === defillamaProps.project && 26 | symbol === defillamaProps.symbol 27 | ))?.apyMean30d / 100) || 0; // Default to 0 if not found 28 | 29 | return lstAddresses.map(({ address, blockchainId }) => ({ 30 | lstAddress: lc(address), 31 | blockchainId, 32 | stakingApy, 33 | })); 34 | })); 35 | 36 | return map; 37 | }, { 38 | promise: true, 39 | maxAge: 30 * 60 * 1000, // 30 min 40 | preFetch: true, 41 | }); 42 | 43 | export default getETHLSTAPYs; 44 | -------------------------------------------------------------------------------- /utils/data/getEywaTokenPrices.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import { arrayToHashmap } from '#root/utils/Array.js'; 3 | import groupBy from 'lodash.groupby'; 4 | import Request from '#root/utils/Request.js'; 5 | import { FANTOM_FACTO_STABLE_NG_EYWA_POOL_IDS, SONIC_FACTO_STABLE_NG_EYWA_POOL_IDS } from '#root/constants/PoolMetadata.js'; 6 | import configs from '#root/constants/configs/configs.js'; 7 | 8 | const REWARD_TOKEN_ADDRESSES = [ 9 | '0x8D9241935453120825C4a95446e351FbC338527D', 10 | ]; 11 | 12 | // Eywa API isn’t reliable, this keeps a copy last prices for when live prices aren’t available 13 | const LAST_PRICES_CACHE = new Map(); 14 | 15 | const getEywaTokenPrice = memoize((address, blockchainId) => ( 16 | Request.get(`https://api.crosscurve.fi/prices/${address}/${configs[blockchainId].chainId}`) 17 | .then((res) => res.json()) 18 | .then((str) => Number(str)) 19 | // Commented out because curve-prices not available for sonic yet, and LAST_PRICES_CACHE takes care of the same job 20 | // .catch(() => ( 21 | // // Fallback to curve-prices when eywa api is down 22 | // Request.get(`https://prices.curve.finance/v1/usd_price/${blockchainId}/${address}`) 23 | // .then((res) => res.json()) 24 | // .then(({ data: { usd_price } }) => usd_price) 25 | // )) 26 | .then((usdPrice) => { 27 | LAST_PRICES_CACHE.set(address, usdPrice); 28 | return usdPrice; 29 | }) 30 | .catch(() => LAST_PRICES_CACHE.get(address)) // Fallback to last known value 31 | ), { 32 | promise: true, 33 | maxAge: 60 * 1000, 34 | }); 35 | 36 | const getEywaTokenPrices = memoize(async ( 37 | allCoinAddresses, 38 | registryId, 39 | blockchainId 40 | ) => { 41 | const filteredCoinAddresses = allCoinAddresses.filter(({ poolId }) => ( 42 | registryId === 'factory-eywa' ? true : 43 | blockchainId === 'fantom' ? FANTOM_FACTO_STABLE_NG_EYWA_POOL_IDS.includes(poolId) : 44 | blockchainId === 'sonic' ? SONIC_FACTO_STABLE_NG_EYWA_POOL_IDS.includes(poolId) : 45 | false 46 | )); 47 | const firstTokenOfEachEywaPool = Array.from(Object.values(groupBy(filteredCoinAddresses, 'poolId'))).map(([{ address }]) => address); 48 | const allTokenAddresses = [ 49 | ...firstTokenOfEachEywaPool, 50 | ...REWARD_TOKEN_ADDRESSES, 51 | ]; 52 | const prices = await Promise.all(allTokenAddresses.map((address) => getEywaTokenPrice(address, blockchainId))) 53 | 54 | const EywaTokensPrices = arrayToHashmap(allTokenAddresses.map((address, i) => [address.toLowerCase(), prices[i]])); 55 | 56 | return EywaTokensPrices; 57 | }, { 58 | promise: true, 59 | maxAge: 15 * 60 * 1000, 60 | preFetch: true, 61 | }); 62 | 63 | export default getEywaTokenPrices; 64 | -------------------------------------------------------------------------------- /utils/data/getSynthetixTokenPrices.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This util retrieves synth prices directly from Synthetix's 3 | * ExchangeRates contract 4 | */ 5 | 6 | import memoize from 'memoizee'; 7 | import { arrayToHashmap } from '#root/utils/Array.js'; 8 | import { multiCall } from '#root/utils/Calls.js'; 9 | import { lc } from '#root/utils/String.js'; 10 | import SYNTHETIX_TOKEN_ABI from '#root/constants/abis/synthetix-token.json' assert { type: 'json' }; 11 | import SYNTHETIX_EXCHANGE_RATES_ABI from '#root/constants/abis/synthetix-exchange-rates.json' assert { type: 'json' }; 12 | import { uintToBN } from '../Web3/index.js'; 13 | 14 | const synths = [ 15 | '0x97fe22e7341a0cd8db6f6c021a24dc8f4dad855f', // sGBP 16 | '0xd71ecff9342a5ced620049e616c5035f1db98620', // sEUR 17 | '0x269895a3df4d73b077fc823dd6da1b95f72aaf9b', // sKRW 18 | '0xf6b1c627e95bfc3c1b4c9b825a032ff0fbf3e07d', // sJPY 19 | '0xf48e200eaf9906362bb1442fca31e0835773b8b4', // sAUD 20 | '0x0f83287ff768d1c1e17a42f44d644d7f22e8ee1d', // sCHF 21 | ]; 22 | 23 | const getSynthetixTokenPrices = memoize(async (networkSettingsParam) => { 24 | const currencyKeys = await multiCall(synths.map((address) => ({ 25 | address, 26 | abi: SYNTHETIX_TOKEN_ABI, 27 | methodName: 'currencyKey', 28 | ...networkSettingsParam, 29 | }))); 30 | 31 | const currencyRates = await multiCall(currencyKeys.map((currencyKey) => ({ 32 | address: '0x648280dD2db772CD018A0CEC72fab5bF8B7683AB', 33 | abi: SYNTHETIX_EXCHANGE_RATES_ABI, 34 | methodName: 'rateForCurrency', 35 | params: [currencyKey], 36 | ...networkSettingsParam, 37 | }))); 38 | 39 | const ycTokensPrices = arrayToHashmap(currencyRates.map((rawRate, i) => { 40 | const address = lc(synths[i]); 41 | const price = uintToBN(rawRate, 18).toNumber(); // This assumes 1 sUSD = $1 42 | 43 | return [address, price]; 44 | })); 45 | 46 | return ycTokensPrices; 47 | }, { 48 | promise: true, 49 | maxAge: 3 * 60 * 1000, 50 | preFetch: true, 51 | }); 52 | 53 | export default getSynthetixTokenPrices; 54 | -------------------------------------------------------------------------------- /utils/data/getTempleTokenPrices.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import { arrayToHashmap, flattenArray } from '#root/utils/Array.js'; 3 | import { multiCall } from '#root/utils/Calls.js'; 4 | import TEMPLE_LP_TOKEN_ABI from '#root/constants/abis/temple-lp-token.json' assert { type: 'json' }; 5 | import getTokensPrices from '#root/utils/data/tokens-prices.js'; 6 | 7 | // Add more if we're missing prices from more 8 | const TEMPLE_LP_TOKENS = { 9 | ethereum: [ 10 | '0x6021444f1706f15465bEe85463BCc7d7cC17Fc03', // FRAX-TEMPLE LP 11 | ], 12 | }; 13 | 14 | const getTempleTokenPrices = memoize(async ( 15 | networkSettingsParam, 16 | blockchainId, 17 | coinAddressesAndPricesMapFallback 18 | ) => { 19 | const templePoolDatas = await multiCall(flattenArray(TEMPLE_LP_TOKENS[blockchainId].map((address) => [{ 20 | address, 21 | abi: TEMPLE_LP_TOKEN_ABI, 22 | methodName: 'totalSupply', 23 | metaData: { address, type: 'totalSupply' }, 24 | networkSettingsParam, 25 | }, { 26 | address, 27 | abi: TEMPLE_LP_TOKEN_ABI, 28 | methodName: 'getReserves', 29 | metaData: { address, type: 'reserves' }, 30 | networkSettingsParam, 31 | }, { 32 | address, 33 | abi: TEMPLE_LP_TOKEN_ABI, 34 | methodName: 'token0', 35 | metaData: { address, type: 'token0' }, 36 | networkSettingsParam, 37 | }, { 38 | address, 39 | abi: TEMPLE_LP_TOKEN_ABI, 40 | methodName: 'token1', 41 | metaData: { address, type: 'token1' }, 42 | networkSettingsParam, 43 | }]))); 44 | 45 | const underlyingAddressesAndMetadata = templePoolDatas.filter(({ metaData: { type } }) => ( 46 | type === 'token0' || 47 | type === 'token1' 48 | )); 49 | const underlyingPrices = await getTokensPrices(underlyingAddressesAndMetadata.map(({ data }) => data.toLowerCase())); 50 | 51 | const groupedData = templePoolDatas.reduce((accu, { data, metaData: { type, address } }) => { 52 | const lcAddress = address.toLowerCase(); 53 | 54 | return { 55 | ...accu, 56 | [lcAddress]: { 57 | ...(accu[lcAddress] || {}), 58 | [type]: data, 59 | }, 60 | }; 61 | }, {}); 62 | 63 | const templeLpTokensPrices = arrayToHashmap(Array.from(Object.entries(groupedData)).map(([ 64 | address, 65 | data, 66 | ]) => { 67 | const token0Balance = data.reserves._reserve0 / 1e18; 68 | const token1Balance = data.reserves._reserve1 / 1e18; 69 | const token0Price = ( 70 | underlyingPrices[data.token0.toLowerCase()] || 71 | coinAddressesAndPricesMapFallback[data.token0.toLowerCase()] 72 | ); 73 | const token1Price = ( 74 | underlyingPrices[data.token1.toLowerCase()] || 75 | coinAddressesAndPricesMapFallback[data.token1.toLowerCase()] 76 | ); 77 | const totalSupply = data.totalSupply / 1e18; 78 | const price = ((token0Balance * token0Price) + (token1Balance * token1Price)) / totalSupply; 79 | 80 | return [address, price]; 81 | })); 82 | 83 | return templeLpTokensPrices; 84 | }, { 85 | promise: true, 86 | maxAge: 15 * 60 * 1000, 87 | preFetch: true, 88 | }); 89 | 90 | export default getTempleTokenPrices; 91 | -------------------------------------------------------------------------------- /utils/data/getYcTokenPrices.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import { arrayToHashmap, flattenArray } from '#root/utils/Array.js'; 3 | import { multiCall } from '#root/utils/Calls.js'; 4 | import ERC20ABI from '#root/constants/abis/erc20.json' assert { type: 'json' }; 5 | import YC_TOKEN_ABI from '#root/constants/abis/yc-token.json' assert { type: 'json' }; 6 | import getTokensPrices from '#root/utils/data/tokens-prices.js'; 7 | 8 | // Add more if we're missing prices from more 9 | const YC_TOKENS = { 10 | ethereum: [ 11 | '0x8e595470ed749b85c6f7669de83eae304c2ec68f', // cyDAI 12 | '0x76eb2fe28b36b3ee97f3adae0c69606eedb2a37c', // cyUSDC 13 | '0x48759f220ed983db51fa7a8c0d2aab8f3ce4166a', // cyUSDT 14 | ], 15 | fantom: [ 16 | '0x04c762a5df2fa02fe868f25359e0c259fb811cfe', // iDAI 17 | '0x328a7b4d538a2b3942653a9983fda3c12c571141', // iUSDC 18 | '0x70fac71debfd67394d1278d98a29dea79dc6e57a', // iUSDT 19 | ], 20 | }; 21 | 22 | const getYcTokenPrices = memoize(async ( 23 | networkSettingsParam, 24 | blockchainId, 25 | coinAddressesAndPricesMapFallback 26 | ) => { 27 | const ycTokensDataPart1 = await multiCall(flattenArray(YC_TOKENS[blockchainId].map((address) => [{ 28 | address, 29 | abi: YC_TOKEN_ABI, 30 | methodName: 'underlying', 31 | metaData: { type: 'underlyingAddress', address }, 32 | ...networkSettingsParam, 33 | }, { 34 | address, 35 | abi: YC_TOKEN_ABI, 36 | methodName: 'exchangeRateStored', 37 | metaData: { type: 'exchangeRateStored', address }, 38 | ...networkSettingsParam, 39 | }]))); 40 | 41 | const underlyingAddressesAndMetadata = ycTokensDataPart1.filter(({ metaData: { type } }) => type === 'underlyingAddress'); 42 | const underlyingPrices = await getTokensPrices(underlyingAddressesAndMetadata.map(({ data }) => data.toLowerCase())); 43 | 44 | const ycTokensDataPart2 = await multiCall(underlyingAddressesAndMetadata.map(({ 45 | data: underlyingAddress, 46 | metaData: { address }, 47 | }) => ({ 48 | address: underlyingAddress, 49 | abi: ERC20ABI, 50 | methodName: 'decimals', 51 | metaData: { type: 'underlyingDecimals', address }, 52 | ...networkSettingsParam, 53 | }))); 54 | 55 | const ycTokensData = [ 56 | ...ycTokensDataPart1, 57 | ...ycTokensDataPart2, 58 | ]; 59 | 60 | const groupedData = ycTokensData.reduce((accu, { data, metaData: { type, address } }) => { 61 | const lcAddress = address.toLowerCase(); 62 | 63 | return { 64 | ...accu, 65 | [lcAddress]: { 66 | ...(accu[lcAddress] || {}), 67 | [type]: data, 68 | }, 69 | }; 70 | }, {}); 71 | 72 | const ycTokensPrices = arrayToHashmap(Array.from(Object.entries(groupedData)).map(([ 73 | address, 74 | data, 75 | ]) => { 76 | const underlyingPrice = ( 77 | underlyingPrices[data.underlyingAddress.toLowerCase()] || 78 | coinAddressesAndPricesMapFallback[data.underlyingAddress.toLowerCase()] 79 | ); 80 | const exchangeRateStored = data.exchangeRateStored / (10 ** (18 - 8 + Number(data.underlyingDecimals))); 81 | const price = underlyingPrice * exchangeRateStored; 82 | 83 | return [address, price]; 84 | })); 85 | 86 | return ycTokensPrices; 87 | }, { 88 | promise: true, 89 | maxAge: 15 * 60 * 1000, 90 | preFetch: true, 91 | }); 92 | 93 | export default getYcTokenPrices; 94 | -------------------------------------------------------------------------------- /utils/data/prices.curve.fi/chains.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | 3 | // Note: keep the openapi description of `routes/v1/getVolumes/[blockchainId].js` up to date when editing this array 4 | const PRICES_CURVE_FI_AVAILABLE_CHAIN_IDS = [ 5 | 'ethereum', 6 | 'polygon', 7 | 'arbitrum', 8 | 'base', 9 | 'optimism', 10 | 'fantom', 11 | 'xdai', 12 | 'fraxtal', 13 | 'sonic', 14 | 'hyperliquid', 15 | ]; 16 | 17 | const getPricesCurveFiChainsBlockchainId = memoize(async (blockchainId) => { 18 | if (!PRICES_CURVE_FI_AVAILABLE_CHAIN_IDS.includes(blockchainId)) { 19 | return []; 20 | } 21 | 22 | const { data } = await (await fetch(`https://prices.curve.finance/v1/chains/${blockchainId}`)).json(); 23 | return data; 24 | }, { 25 | promise: true, 26 | maxAge: 5 * 60 * 1000, // That endpoint is cached 5 minutes, no point in querying it more often 27 | }); 28 | 29 | export default getPricesCurveFiChainsBlockchainId; 30 | export { PRICES_CURVE_FI_AVAILABLE_CHAIN_IDS }; 31 | -------------------------------------------------------------------------------- /utils/data/prices.curve.fi/gauges.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | 3 | const getExternalGaugeListAddresses = memoize(async () => { 4 | const { gauges } = await (await fetch('https://prices.curve.finance/v1/dao/gauges/overview')).json(); 5 | return gauges.map(({ address, effective_address }) => effective_address ?? address); 6 | }, { 7 | promise: true, 8 | maxAge: 30 * 60 * 1000, // That endpoint is cached 30 minutes, no point in querying it more often given its current use-case 9 | }); 10 | 11 | export default getExternalGaugeListAddresses; 12 | -------------------------------------------------------------------------------- /utils/data/prices.curve.fi/pools-metadata.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Retrieves pool data from prices.curve.finance and caches it. 3 | * 4 | * This utility uses two layers of caching to reduce load on prices.curve.finance and time spent 5 | * networking as much as possible: 6 | * 1. An in-memory cache (using memoizee) for instant storing+retrieving 7 | * 2. A Redis cache to keep cached entities readily available across restarts/redeployments 8 | * of this server 9 | * 10 | * This is coupled with a very large MAX_AGE_SEC value, because metadata never really changes. 11 | */ 12 | 13 | import memoize from 'memoizee'; 14 | import { PRICES_CURVE_FI_AVAILABLE_CHAIN_IDS } from '#root/utils/data/prices.curve.fi/chains.js'; 15 | import { lc } from '#root/utils/String.js'; 16 | import swr from '#root/utils/swr.js'; 17 | import { backOff } from 'exponential-backoff'; 18 | import { IS_DEV } from '#root/constants/AppConstants.js'; 19 | 20 | const MAX_AGE_SEC = 86400; // 24 hours 21 | 22 | const getPricesCurveFiPoolsMetadataBlockchainId = memoize(async (address, blockchainId) => { 23 | if (!PRICES_CURVE_FI_AVAILABLE_CHAIN_IDS.includes(blockchainId)) { 24 | return undefined; 25 | } 26 | 27 | const lcAddress = lc(address); 28 | 29 | const metaData = (await swr( 30 | `getPricesCurveFiPoolsMetadataBlockchainId-${blockchainId}-${lcAddress}`, 31 | async () => backOff(async () => { 32 | return (await fetch(`https://prices.curve.finance/v1/pools/${blockchainId}/${lcAddress}/metadata`)).json(); 33 | }, { 34 | numOfAttempts: 1, 35 | retry: (e, attemptNumber) => { 36 | if (IS_DEV) console.log(`prices.curve.finance retrying!`, { attemptNumber, blockchainId, lcAddress }); 37 | return true; 38 | }, 39 | }).catch(() => { 40 | console.log(`prices.curve.finance failed, returning undefined!`, { blockchainId, lcAddress }); 41 | return undefined; 42 | }), 43 | { minTimeToStale: MAX_AGE_SEC * 1000 } // See CacheSettings.js 44 | )).value; 45 | 46 | return metaData; 47 | }, { 48 | promise: true, 49 | maxAge: MAX_AGE_SEC * 1000, 50 | }); 51 | 52 | const getPoolAssetTypesFromExternalStore = async (address, blockchainId) => { 53 | const metaData = await getPricesCurveFiPoolsMetadataBlockchainId(address, blockchainId); 54 | return metaData?.asset_types; 55 | }; 56 | 57 | const getPoolCreationTsAndBlockFromExternalStore = async (address, blockchainId) => { 58 | const metaData = await getPricesCurveFiPoolsMetadataBlockchainId(address, blockchainId); 59 | if (!metaData || metaData.deployment_date === null) return null; 60 | 61 | return { 62 | // Append 'Z' because this is a UTC datetime string 63 | creationTs: (Date.parse(`${metaData.deployment_date}Z`) / 1000), 64 | creationBlockNumber: metaData.deployment_block, 65 | }; 66 | }; 67 | 68 | export default getPricesCurveFiPoolsMetadataBlockchainId; 69 | export { 70 | getPoolAssetTypesFromExternalStore, 71 | getPoolCreationTsAndBlockFromExternalStore, 72 | }; 73 | -------------------------------------------------------------------------------- /utils/data/tokens-data.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import configs from '#root/constants/configs/index.js' 3 | import { multiCall } from '#root/utils/Calls.js'; 4 | import ERC20ABI from '#root/constants/abis/erc20.json' assert { type: 'json' }; 5 | import { flattenArray, arrayToHashmap } from '#root/utils/Array.js'; 6 | import { lc } from '#root/utils/String.js'; 7 | 8 | const cache = new Map(); 9 | 10 | const fetchTokensData = async (tokenAddresses, blockchainId = 'ethereum') => { 11 | const config = configs[blockchainId]; 12 | const { 13 | rpcUrl, 14 | multicall2Address, 15 | } = config; 16 | 17 | const web3 = new Web3(rpcUrl); 18 | const networkSettingsParam = ( 19 | typeof multicall2Address !== 'undefined' ? 20 | { networkSettings: { web3, multicall2Address } } : 21 | undefined 22 | ); 23 | 24 | const tokenData = await multiCall(flattenArray(tokenAddresses.map((address) => [{ 25 | address, 26 | abi: ERC20ABI, 27 | methodName: 'symbol', 28 | metaData: { 29 | address, 30 | type: 'symbol', 31 | }, 32 | ...networkSettingsParam, 33 | }, { 34 | address, 35 | abi: ERC20ABI, 36 | methodName: 'decimals', 37 | metaData: { 38 | address, 39 | type: 'decimals', 40 | }, 41 | ...networkSettingsParam, 42 | }]))); 43 | 44 | const mergedTokenData = tokenData.reduce((accu, { 45 | data, 46 | metaData: { type, address }, 47 | }) => { 48 | const key = address; 49 | const tokenInfo = accu[key]; 50 | 51 | accu[key] = { 52 | ...tokenInfo, 53 | [type]: ( 54 | type === 'decimals' ? 55 | Number(data) : 56 | data 57 | ), 58 | }; 59 | 60 | return accu; 61 | }, {}); 62 | 63 | // Save to cache 64 | Array.from(Object.entries(mergedTokenData)).forEach(([address, data]) => { 65 | cache.set(`${blockchainId}-${address}`, { 66 | ...data, 67 | address, // Attach address to the coin's data 68 | blockchainId, // Attach blockchainId to the coin's data 69 | }); 70 | }); 71 | }; 72 | 73 | const getTokensData = async (tokenAddresses, blockchainId = 'ethereum') => { 74 | if (tokenAddresses.length === 0) return {}; 75 | 76 | const lcTokenAddresses = tokenAddresses.map(lc); 77 | 78 | /** 79 | * This caching strategy will only return cached results if *all* requested addresses 80 | * are already cached. This is ok because by far the lowest hanging fruit to avoid 81 | * unecessary onchain requests is to prevent the exact same requests from being run 82 | * multiple times. 83 | */ 84 | const areAllTokenDataCached = lcTokenAddresses.every((address) => cache.has(`${blockchainId}-${address}`)); 85 | if (!areAllTokenDataCached) await fetchTokensData(lcTokenAddresses, blockchainId); 86 | 87 | return arrayToHashmap(lcTokenAddresses.map((address) => [ 88 | address, 89 | cache.get(`${blockchainId}-${address}`), 90 | ])); 91 | }; 92 | 93 | export default getTokensData; 94 | -------------------------------------------------------------------------------- /utils/data/tokens-prices.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import { backOff } from 'exponential-backoff'; 3 | import Request from '#root/utils/Request.js'; 4 | import { arrayToHashmap } from '#root/utils/Array.js'; 5 | import { sequentialPromiseMap } from '#root/utils/Async.js'; 6 | import getAssetsPrices from '#root/utils/data/assets-prices.js'; 7 | import getCrvusdPriceForBlockchainId from '#root/utils/data/getCrvusdPrice.js'; 8 | 9 | const MAX_ADDRESSES_PER_COINGECKO_REQUEST = 30; 10 | const NATIVE_ETH_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; 11 | const KP3R_ADDRESS_ON_ETHEREUM = '0x1ceb5cb57c4d4e2b2433641b95dd330a33185a44'; 12 | const RKP3R_ADDRESS_ON_ETHEREUM = '0xedb67ee1b171c4ec66e6c10ec43edbba20fae8e9'; 13 | 14 | const getTokensPrices = memoize(async (addresses, platform = 'ethereum') => { 15 | const attachRkp3rPrice = platform === 'ethereum'; 16 | 17 | // eslint-disable-next-line no-param-reassign 18 | if (attachRkp3rPrice) addresses = addresses.concat(KP3R_ADDRESS_ON_ETHEREUM); 19 | 20 | // https://defillama.com/docs/api 21 | const pricesChunks = await sequentialPromiseMap(addresses, (addressesChunk) => ( 22 | backOff(() => Request.get(`https://coins.llama.fi/prices/current/${addressesChunk.map((a) => `${platform}:${a}`).join(',')}`), { 23 | retry: (e, attemptNumber) => { 24 | console.log(`defillama retrying!`, { attemptNumber, addressesChunk }); 25 | return true; 26 | }, 27 | }) 28 | .then((response) => response.json()) 29 | .then(({ coins: prices }) => arrayToHashmap(Array.from(Object.entries(prices)).map(([platformAndAddress, { price: usdPrice }]) => [ 30 | platformAndAddress.split(':')[1].toLowerCase(), 31 | usdPrice, 32 | ]))) 33 | ), MAX_ADDRESSES_PER_COINGECKO_REQUEST); 34 | 35 | const crvUsdPrice = await getCrvusdPriceForBlockchainId(platform); 36 | const mergedPrices = Object.assign({}, ...pricesChunks, crvUsdPrice); 37 | 38 | const attachNativeEthPrice = addresses.some((address) => address.toLowerCase() === NATIVE_ETH_ADDRESS); 39 | if (attachNativeEthPrice) { 40 | const coingeckoId = platform; 41 | mergedPrices[NATIVE_ETH_ADDRESS] = (await getAssetsPrices([coingeckoId]))[coingeckoId]; 42 | } 43 | 44 | if (attachRkp3rPrice) { 45 | // Estimation: rkp3r is a kp3r option redeemable for 50% asset price 46 | mergedPrices[RKP3R_ADDRESS_ON_ETHEREUM] = mergedPrices[KP3R_ADDRESS_ON_ETHEREUM] * 0.1; 47 | } 48 | 49 | return mergedPrices; 50 | }, { 51 | promise: true, 52 | maxAge: 2 * 60 * 1000, // 2 min 53 | primitive: true, 54 | }); 55 | 56 | export default getTokensPrices; 57 | -------------------------------------------------------------------------------- /utils/getters.js: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import Web3 from 'web3'; 3 | import configs from '#root/constants/configs/index.js' 4 | import addressGetterAbi from '#root/constants/abis/address_getter.json' assert { type: 'json' }; 5 | const addressGetter = '0x0000000022d53366457f9d5e68ec105046fc4383' 6 | const multiCall = '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441' 7 | 8 | const feeDistributor3crv = '0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc' 9 | const feeDistributorCrvusd = '0xD16d5eC345Dd86Fb63C6a9C43c517210F1027914' 10 | 11 | const getRegistry = memoize(async ({ blockchainId } = {}) => { 12 | if (typeof blockchainId === 'undefined') blockchainId = 'ethereum'; 13 | 14 | const web3 = new Web3(configs[blockchainId].rpcUrl); 15 | const contract = new web3.eth.Contract(addressGetterAbi, addressGetter); 16 | return contract.methods.get_registry().call(); 17 | }, { 18 | promise: true, 19 | maxAge: 10 * 60 * 1000, // 10 min 20 | normalizer: ([{ blockchainId } = {}]) => blockchainId, 21 | }); 22 | 23 | const getMultiCall = async () => { 24 | return multiCall 25 | } 26 | 27 | const getFactoryRegistry = async () => '0x0959158b6040D32d04c301A72CBFD6b39E21c9AE'; // old factory 28 | 29 | const get3crvFeeDistributor = async () => feeDistributor3crv; 30 | const getCrvusdFeeDistributor = async () => feeDistributorCrvusd; 31 | 32 | export { 33 | getFactoryRegistry, 34 | get3crvFeeDistributor, 35 | getCrvusdFeeDistributor, 36 | getMultiCall, 37 | getRegistry 38 | }; 39 | -------------------------------------------------------------------------------- /utils/helpers.js: -------------------------------------------------------------------------------- 1 | const getThursdayUTCTimestamp = async () => { 2 | let ts = new Date().getTime(); 3 | let week = 604800; 4 | return Math.floor((ts / 1000) / week) * week 5 | }; 6 | 7 | export { 8 | getThursdayUTCTimestamp 9 | }; 10 | -------------------------------------------------------------------------------- /utils/swr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a Promise-wrapped instance of a stale-while-revalidate cache helper that 3 | * takes care of serving fresh data, caching it, and revalidating as needed, using 4 | * Redis as cache storage. 5 | */ 6 | 7 | import { createStaleWhileRevalidateCache, EmitterEvents } from 'stale-while-revalidate-cache'; 8 | import { Redis } from 'ioredis'; 9 | import CACHE_SETTINGS from '#root/constants/CacheSettings.js'; 10 | import { IS_DEV } from '#root/constants/AppConstants.js'; 11 | import { NotFoundError, ParamError } from '#root/utils/api.js'; 12 | 13 | const cacheNode = IS_DEV ? process.env.DEV_REDIS_HOST : process.env.PROD_REDIS_HOST 14 | console.log('Using Redis node:', cacheNode); 15 | 16 | const redis = new Redis(cacheNode); // To update prod redis server 17 | 18 | const storage = { 19 | async getItem(key) { 20 | return redis.get(key); 21 | }, 22 | async setItem(key, value) { 23 | await redis.set(key, value, 'EX', (CACHE_SETTINGS.maxTimeToLive / 1000)); 24 | }, 25 | async removeItem(key) { 26 | await redis.del(key); 27 | }, 28 | }; 29 | 30 | const swr = createStaleWhileRevalidateCache({ 31 | storage, 32 | ...CACHE_SETTINGS, 33 | }); 34 | 35 | swr.onAny((event, payload) => { 36 | switch (event) { 37 | case EmitterEvents.cacheGetFailed: 38 | console.log('Error: cacheGetFailed', payload); 39 | break 40 | 41 | case EmitterEvents.cacheSetFailed: 42 | console.log('Error: cacheSetFailed', payload); 43 | break 44 | 45 | case EmitterEvents.revalidateFailed: 46 | if (IS_DEV) { 47 | console.log(JSON.stringify(payload)); 48 | console.log('Error: revalidateFailed'); 49 | } else if (!(payload.error instanceof ParamError) && !(payload.error instanceof NotFoundError)) { 50 | console.log('Error: revalidateFailed', payload.cacheKey); 51 | } 52 | break; 53 | } 54 | }); 55 | 56 | 57 | export default swr; 58 | --------------------------------------------------------------------------------