diff --git a/src/constants/raw/pinto-base.js b/src/constants/raw/pinto-base.js index cd83a28..ad41dbc 100644 --- a/src/constants/raw/pinto-base.js +++ b/src/constants/raw/pinto-base.js @@ -22,12 +22,14 @@ const contracts = { PINTOCBBTC: ['0x3e11226fe3d85142B734ABCe6e58918d5828d1b4', 18, wellAbi], PINTOWSOL: ['0x3e11444c7650234c748D743D8d374fcE2eE5E6C9', 18, wellAbi], PINTOUSDC: ['0x3e1133aC082716DDC3114bbEFEeD8B1731eA9cb1', 18, wellAbi], + PINTOWSTETH: ['0x3e1155245FF9a6a019Bc35827e801c6ED2CE91b9', 18, wellAbi], SPINTO: ['0x00b174d66ada7d63789087f50a9b9e0e48446dc1', 18, wrappedDepositAbi], WETH: ['0x4200000000000000000000000000000000000006', 18, erc20Abi], CBETH: ['0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22', 18, erc20Abi], CBBTC: ['0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', 8, erc20Abi], WSOL: ['0x1C61629598e4a901136a81BC138E5828dc150d67', 9, erc20Abi], USDC: ['0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', 6, erc20Abi], + WSTETH: ['0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452', 18, erc20Abi], CP2: ['0xBA510C289fD067EBbA41335afa11F0591940d6fe', null, wellFunctionAbi], STABLE2: ['0xBA51055a97b40d7f41f3F64b57469b5D45B67c87', null, wellFunctionAbi], SOW_V0: ['0xbb0a41927895F8ca2b4ECCc659ba158735fCF28B', null, sowBlueprintV0Abi], diff --git a/src/repository/postgres/seeders/20251110193438-whitelist-wsteth.js b/src/repository/postgres/seeders/20251110193438-whitelist-wsteth.js new file mode 100644 index 0000000..5d45925 --- /dev/null +++ b/src/repository/postgres/seeders/20251110193438-whitelist-wsteth.js @@ -0,0 +1,97 @@ +'use strict'; + +const db = require('../models'); +const { C } = require('../../../constants/runtime-constants'); +const AlchemyUtil = require('../../../datasources/alchemy'); +const PromiseUtil = require('../../../utils/async/promise'); +const Contracts = require('../../../datasources/contracts/contracts'); +const EnvUtil = require('../../../utils/env'); +const { TOKEN_TABLE } = require('../../../constants/tables'); + +// This was copied from silo-tokens-base.js and modified to include only pintowsteth. + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + if (!EnvUtil.isChainEnabled('base')) { + console.log(`Skipping seeder: chain 'base' is not enabled.`); + return; + } + const c = C('base'); + const tokens = [c.PINTOWSTETH]; + + // Add base tokens + await AlchemyUtil.ready(c.CHAIN); + const beanstalk = Contracts.getBeanstalk(c); + + // Gets tokens that have already been populated + const existingTokens = await db.sequelize.models.Token.findAll({ + where: { + chain: c.CHAIN, + address: { + [Sequelize.Op.in]: tokens + } + }, + attributes: ['address'] + }); + + // Add new tokens only + const newTokens = tokens.filter((token) => !existingTokens.some((t) => t.address === token)); + if (newTokens.length > 0) { + const rows = []; + for (const token of newTokens) { + const erc20 = Contracts.get(token, c); + const [name, symbol, supply, decimals] = await Promise.all([ + erc20.name(), + erc20.symbol(), + erc20.totalSupply(), + (async () => Number(await erc20.decimals()))() + ]); + const [bdv, stalkEarnedPerSeason, stemTip, totalDeposited, totalDepositedBdv] = await Promise.all( + [ + PromiseUtil.defaultOnReject(1n)(beanstalk.bdv(token, BigInt(10 ** decimals))), + (async () => { + const tokenSettings = await beanstalk.tokenSettings(token); + return tokenSettings.stalkEarnedPerSeason; + })(), + beanstalk.stemTipForToken(token), + beanstalk.getTotalDeposited(token), + beanstalk.getTotalDepositedBdv(token) + // If any revert, they return null instead + ].map(PromiseUtil.defaultOnReject(null)) + ); + rows.push({ + address: token, + chain: c.CHAIN, + name, + symbol, + supply, + decimals, + isWhitelisted: true, + bdv, + stalkEarnedPerSeason, + stemTip, + totalDeposited, + totalDepositedBdv, + createdAt: new Date(), + updatedAt: new Date() + }); + } + + await queryInterface.bulkInsert(TOKEN_TABLE.env, rows); + } + }, + + async down(queryInterface, Sequelize) { + if (EnvUtil.isChainEnabled('base')) { + const c = C('base'); + const tokens = [c.PINTOWSTETH]; + // Delete pinto tokens + await queryInterface.bulkDelete(TOKEN_TABLE.env, { + address: { + [Sequelize.Op.in]: tokens + } + }); + } + } +}; diff --git a/src/repository/subgraph/basin-subgraph.js b/src/repository/subgraph/basin-subgraph.js index 0285893..e45f08e 100644 --- a/src/repository/subgraph/basin-subgraph.js +++ b/src/repository/subgraph/basin-subgraph.js @@ -15,9 +15,7 @@ class BasinSubgraphRepository { } }`, `block: {number: ${blockNumber}}`, - // The exchange subgraph needs to update to indiate isBeanstalk or wasBeanstalk (for dewhitelisted) - '', - // 'isBeanstalk: true', + 'isBeanstalk: true', { field: 'symbol', lastValue: ' ', diff --git a/src/service/exchange-service.js b/src/service/exchange-service.js index 70ad104..793b0ba 100644 --- a/src/service/exchange-service.js +++ b/src/service/exchange-service.js @@ -23,16 +23,6 @@ class ExchangeService { BasinSubgraphRepository.getAllWells(block.number), BasinSubgraphRepository.getAllTrades(block.timestamp - ONE_DAY, block.timestamp) ]); - // The exchange subgraph needs to update to indiate isBeanstalk or wasBeanstalk (for dewhitelisted) - // Until then allWells must manually filter out pools - if (C().PROJECT === 'pinto') { - const allWellAddresses = Object.keys(allWells); - for (const wellAddress of allWellAddresses) { - if (![C().PINTOWETH, C().PINTOCBETH, C().PINTOCBBTC, C().PINTOUSDC, C().PINTOWSOL].includes(wellAddress)) { - delete allWells[wellAddress]; - } - } - } const allPriceEvents = ExchangeService.priceEventsByWell(allWells, allTrades); // For each well in the subgraph, construct a formatted response diff --git a/src/service/price-service.js b/src/service/price-service.js index e9e8b3a..4ed5e2e 100644 --- a/src/service/price-service.js +++ b/src/service/price-service.js @@ -46,7 +46,7 @@ class PriceService { static #getPriceFunction(token) { if (token === C().BEAN) { return PriceService.getBeanPrice; - } else if ([C().WETH, C().CBETH, C().CBBTC, C().WSOL, C().USDC].includes(token)) { + } else if ([C().WETH, C().CBETH, C().CBBTC, C().WSOL, C().USDC, C().WSTETH].includes(token)) { return (options) => PriceService.getUsdOracleTokenPrice(token, options); } return () => ({ diff --git a/src/service/tractor/blueprints/blueprint-constants.js b/src/service/tractor/blueprints/blueprint-constants.js index 115156b..ade8bb3 100644 --- a/src/service/tractor/blueprints/blueprint-constants.js +++ b/src/service/tractor/blueprints/blueprint-constants.js @@ -8,7 +8,8 @@ class BlueprintConstants { [C().PINTOCBETH]: 2, [C().PINTOCBBTC]: 3, [C().PINTOUSDC]: 4, - [C().PINTOWSOL]: 5 + [C().PINTOWSOL]: 5, + [C().PINTOWSTETH]: 6 }; } diff --git a/test/service/exchange-service.test.js b/test/service/exchange-service.test.js index 3713cec..104fb95 100644 --- a/test/service/exchange-service.test.js +++ b/test/service/exchange-service.test.js @@ -13,8 +13,6 @@ const ERC20Info = require('../../src/datasources/erc20-info'); const BasinSubgraphRepository = require('../../src/repository/subgraph/basin-subgraph'); const TradeDto = require('../../src/repository/dto/TradeDto'); -const testTimestamp = 1715020584; - describe('ExchangeService', () => { beforeEach(() => { mockBeanstalkConstants(); @@ -25,12 +23,12 @@ describe('ExchangeService', () => { it('should return all Basin tickers in the expected format', async () => { const wellsResponse = require('../mock-responses/subgraph/basin/wells.json'); - jest.spyOn(mockBasinSG, 'request').mockResolvedValueOnce(wellsResponse); + jest.spyOn(mockBasinSG, 'request').mockResolvedValue(wellsResponse); // In practice these 2 values are not necessary since the subsequent getWellPriceRange is also mocked. - jest.spyOn(BasinSubgraphRepository, 'getAllTrades').mockResolvedValueOnce(undefined); + jest.spyOn(BasinSubgraphRepository, 'getAllTrades').mockResolvedValue(undefined); jest.spyOn(ExchangeService, 'priceEventsByWell').mockReturnValueOnce(undefined); - jest.spyOn(LiquidityUtil, 'calcWellLiquidityUSD').mockResolvedValueOnce(27491579.59267346); - jest.spyOn(LiquidityUtil, 'calcDepth').mockResolvedValueOnce({ + jest.spyOn(LiquidityUtil, 'calcWellLiquidityUSD').mockResolvedValue(27491579.59267346); + jest.spyOn(LiquidityUtil, 'calcDepth').mockResolvedValue({ buy: { float: [135736.220357, 52.83352694098683] }, @@ -81,9 +79,7 @@ describe('ExchangeService', () => { }); test('Returns swap history', async () => { - jest - .spyOn(mockBasinSG, 'request') - .mockResolvedValueOnce(require('../mock-responses/subgraph/basin/swapHistory.json')); + jest.spyOn(mockBasinSG, 'request').mockResolvedValue(require('../mock-responses/subgraph/basin/swapHistory.json')); const options = { ticker_id: `${BEAN}_${WETH}`, diff --git a/test/util/mock-constants.js b/test/util/mock-constants.js index 4aeaab1..4bfa05e 100644 --- a/test/util/mock-constants.js +++ b/test/util/mock-constants.js @@ -46,12 +46,19 @@ function mockPintoERC20s() { symbol: 'U-PINTOWSOLCP2w', decimals: 18 }, + { + token: C().PINTOWSTETH, + name: 'PINTO:WSTETH Constant Product 2 Upgradeable Well', + symbol: 'U-PINTOWSTETHC2w', + decimals: 18 + }, { token: C().PINTOUSDC, name: 'PINTO:USDC Stable 2 Upgradeable Well', symbol: 'U-PINTOUSDCS2w', decimals: 18 }, { token: C().WETH, name: 'Wrapped Ether', symbol: 'WETH', decimals: 18 }, { token: C().CBETH, name: 'Coinbase Wrapped Staked ETH', symbol: 'cbETH', decimals: 18 }, { token: C().CBBTC, name: 'Coinbase Wrapped BTC', symbol: 'cbBTC', decimals: 8 }, { token: C().WSOL, name: 'Wrapped SOL', symbol: 'SOL', decimals: 9 }, - { token: C().USDC, name: 'USD Coin', symbol: 'USDC', decimals: 6 } + { token: C().USDC, name: 'USD Coin', symbol: 'USDC', decimals: 6 }, + { token: C().WSTETH, name: 'Wrapped Staked ETH', symbol: 'WSTETH', decimals: 18 } ]; jest.spyOn(ERC20Info, 'getTokenInfo').mockImplementation((token) => {