diff --git a/.env.dev b/.env.dev index e27f61bb..61df93d5 100644 --- a/.env.dev +++ b/.env.dev @@ -1,12 +1,11 @@ - -REACT_APP_COMETA_API_URL='https://api.testnet.cometa.farm/' -REACT_APP_BENEFICIARY_ADDR='3ONHOKYJGYREOUEP52JP7YMZDR77ZHM7B4KOJ3AMPSYOJTINL5W5YOX2HM' -REACT_APP_FARM_CREATION_FEE='200' -REACT_APP_FARM_FLAT_ALGO_CREATION_FEE='5' +REACT_APP_COMETA_API_URL='https://api.cometa.farm/' +REACT_APP_BENEFICIARY_ADDR='METAFG5UBD74CKQFIIABWMMQXR45J7BAP3KV6BVR3V7LDPNAEKNEVLMBRE' +REACT_APP_FARM_CREATION_FEE='100' +REACT_APP_FARM_FLAT_ALGO_CREATION_FEE='100' REACH_CONNECTOR_MODE=ALGO-browser # TODO: Make it configurable between deployments/builds (e.g. put it in .env.local and set this as actual env variables in deployments) -ALGO_NETWORK=TestNet +ALGO_NETWORK=MainNet ALGO_TOKEN='' @@ -19,5 +18,3 @@ ALGO_INDEXER_PORT=$ALGO_PORT ALGO_INDEXER_SERVER="https://${ALGO_NETWORK}-idx.algonode.cloud" REACT_APP_ALAMMEX_API_KEY='9a1c1fe8-b49a-4d18-b393-4777e557ff74' - -API_CONTRACTS_MAX_COUNT=10 diff --git a/.env.testnet b/.env.testnet new file mode 100644 index 00000000..e27f61bb --- /dev/null +++ b/.env.testnet @@ -0,0 +1,23 @@ + +REACT_APP_COMETA_API_URL='https://api.testnet.cometa.farm/' +REACT_APP_BENEFICIARY_ADDR='3ONHOKYJGYREOUEP52JP7YMZDR77ZHM7B4KOJ3AMPSYOJTINL5W5YOX2HM' +REACT_APP_FARM_CREATION_FEE='200' +REACT_APP_FARM_FLAT_ALGO_CREATION_FEE='5' +REACH_CONNECTOR_MODE=ALGO-browser + +# TODO: Make it configurable between deployments/builds (e.g. put it in .env.local and set this as actual env variables in deployments) +ALGO_NETWORK=TestNet + +ALGO_TOKEN='' + +ALGO_SERVER="https://${ALGO_NETWORK}-api.algonode.cloud" +ALGO_PORT=443 + +ALGO_INDEXER_TOKEN=$ALGO_TOKEN + +ALGO_INDEXER_PORT=$ALGO_PORT +ALGO_INDEXER_SERVER="https://${ALGO_NETWORK}-idx.algonode.cloud" + +REACT_APP_ALAMMEX_API_KEY='9a1c1fe8-b49a-4d18-b393-4777e557ff74' + +API_CONTRACTS_MAX_COUNT=10 diff --git a/package.json b/package.json index 89fb8c37..e09dd992 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "build": "GENERATE_SOURCEMAP=false node scripts/build.js", "prod": "env-cmd -f .env.prod yarn build", "dev": "env-cmd -f .env.dev yarn build", + "testnet": "env-cmd -f .env.testnet yarn build", "test": "node scripts/test.js", "postinstall": "husky install", "lint": "xo src", diff --git a/src/Farm/store.ts b/src/Farm/store.ts index dce747dc..24c8e665 100644 --- a/src/Farm/store.ts +++ b/src/Farm/store.ts @@ -8,18 +8,14 @@ import { ascend, descend, min, Ord, sort, zip, zipWith } from 'ramda'; import { Asset, AssetId, - assetId, buildContractsStore, ContractState, Amount, Priced, - assetLoaded, + allAssetsLoaded, registerAsset, - registerPricedAsset, - $algoUsdPrice, fetchAsset, ALGO_ASSET, - fetchAlgoPriceFx, $pricedAssets, $networkTime, Contract, @@ -27,12 +23,13 @@ import { hasLocalState, Time, $meanRoundDuration, + $pricedAlgo, } from '../common/store'; +import { getPricedLpInfo, getPricedLpInfos, PricedLpInfo } from '../providers/flexApiProvider'; import { nonConcurrent } from '../common/store/utils'; import { AllDefined, Backend } from '../types'; -import { LPTokenInfo, DexProvider, makeDex, PoolInfo } from '../dexes'; +import { LPTokenInfo, DexProvider } from '../dexes'; import { fromSmallestUnits, YEAR } from '../common/lib'; -import { getLpState } from '../providers/apiProvider'; import { calculateAlgoReward, convertAmountToUSD, getPoolState } from './PoolList/Pool/utils'; import { PoolState } from './PoolList/Pool/types'; import { ColumnType } from './PoolList/PoolList'; @@ -42,76 +39,36 @@ const FARM_BACKENDS = { '17.2.5': farmBackend_17_2_5 as Backend, }; -// TODO: this function is a huge costyl -export function detectAssetProvider({ name }: { name: string }): DexProvider { - if (name.includes('TinymanPool2.0')) { - return 'T3'; - } - name = name.toLowerCase(); - if (name.includes('tinyman')) { - return 'T2'; - } - if (name.includes('humble')) { - return 'H2'; - } - if (name.includes('liquidity') || name.includes('pact')) { - return 'PT'; - } - - return 'MOCK'; -} - -export async function getLPTokenInfoBackend(asset: Asset, provider: DexProvider): Promise> { - const lpState = await getLpState(asset.id); +function formatPricedLPInfo(lpInfo: PricedLpInfo, asset: Asset): Priced { return { ...asset, - poolId: lpState.id, - asset1: lpState.asset1_id, - asset2: lpState.asset2_id, + poolId: lpInfo.id, + asset1: lpInfo.asset1_id, + asset2: lpInfo.asset2_id, liquidityAsset: asset.id, - asset1Reserve: BigInt(lpState.asset1_reserve), - asset2Reserve: BigInt(lpState.asset2_reserve), - totalLiquidity: BigInt(lpState.issued_tokens), - poolDex: provider, - dexFeeApr: 0, // TODO - price: lpState.token_price_usd, - priceInAlgo: lpState.token_price, + asset1Reserve: BigInt(lpInfo.asset1_reserve_micros), + asset2Reserve: BigInt(lpInfo.asset2_reserve_micros), + totalLiquidity: BigInt(lpInfo.issued_tokens_micros), + poolDex: lpInfo.dex_provider, + dexFeeApr: lpInfo.swap_fee_apr || 0, // TODO + price: lpInfo.token_price_usd, + priceInAlgo: lpInfo.token_price_algo, }; } -export async function getLPTokenInfo( - asset: Asset, - algoPrice: number | null, - provider?: DexProvider -): Promise> { - if (provider === undefined) { - provider = detectAssetProvider(asset); - } - if (provider === 'PT') { - // Pact pools fix - return await getLPTokenInfoBackend(asset, provider); - } - - if (algoPrice === null) { - algoPrice = await fetchAlgoPriceFx(); - } +export async function getLPTokenInfoBackend(asset: Asset): Promise> { + const lpInfo = await getPricedLpInfo(asset.id); + return formatPricedLPInfo(lpInfo, asset); +} - const dex = makeDex(provider); - const poolInfo = await dex.getPoolByAddress(asset.creator).catch(() => dex.getPoolByAddress(asset.reserve)); - - const firstAsset = await fetchAsset(poolInfo.asset1); - let fstAssetPrice; - if (poolInfo.asset1 === 0) { - fstAssetPrice = algoPrice; - } else { - const algoPool = await dex.getPoolByAssets(firstAsset, ALGO_ASSET); - const priceInAlgo = (await algoPool.getSwap(firstAsset, BigInt(10 ** firstAsset.decimals), 0.01)).price; - fstAssetPrice = algoPrice * priceInAlgo; +export async function getManyLPInfosBackend(): Promise[]> { + const lpInfos = await getPricedLpInfos(); + const processedInfos = []; + for (const lpInfo of lpInfos) { + const asset = await fetchAsset(lpInfo.token_id); + processedInfos.push(formatPricedLPInfo(lpInfo, asset)); } - const asset1Reserve = fromSmallestUnits(firstAsset, poolInfo.asset1Reserve); - const totalLiquidity = fromSmallestUnits(asset, poolInfo.totalLiquidity); - const price = (asset1Reserve / totalLiquidity) * fstAssetPrice * 2; - return { ...asset, ...poolInfo, price, priceInAlgo: price / algoPrice }; + return processedInfos; } const BIG_NUM = BigInt('1000000000000000000'); @@ -176,21 +133,20 @@ type LPTokenStore = Map>; export const $lpTokenInfos = createStore(Map()); -export const getLPTokenInfoFx = createEffect( +export const getLPTokenInfosFx = createEffect( nonConcurrent( - async ({ - asset, - provider, - algoPrice, - }: { - asset: Asset; - provider: DexProvider | undefined; - algoPrice: number | null; - }) => getLPTokenInfo(asset, algoPrice, provider) + async ({ lpTokenIds, algoPrice }: { lpTokenIds: AssetId[]; algoPrice: number | null }) => + await getManyLPInfosBackend() ) ); -$lpTokenInfos.on(getLPTokenInfoFx.done, (state, { params, result }) => state.set(assetId(params.asset), result)); +$lpTokenInfos.on(getLPTokenInfosFx.done, (state, { result }) => { + let newState = state; + for (const lpTokenInfo of result) { + newState = newState.set(lpTokenInfo.id, lpTokenInfo); + } + return newState; +}); const $lpTokenIds = createStore(Set()).on($farmPools, (state, pools) => { const newIds = Set(pools.filter((pool) => pool.state !== null).map((pool) => pool.state!.initial.stakeToken)); @@ -199,26 +155,16 @@ const $lpTokenIds = createStore(Set()).on($farmPools, (state, pools) => // Automatically fetch LP token infos when general info about them gets fetched the first time sample({ - clock: assetLoaded, - source: { lpTokens: $lpTokenIds, algoPrice: $algoUsdPrice }, - filter: ({ lpTokens }, asset) => lpTokens.has(asset.id), - fn: ({ algoPrice }, asset) => ({ asset, algoPrice, provider: undefined }), - target: getLPTokenInfoFx, + clock: allAssetsLoaded, + source: { lpTokens: $lpTokenIds, algoPrice: $pricedAlgo.map((algo) => algo?.price ?? null) }, + fn: ({ lpTokens, algoPrice }) => ({ lpTokenIds: lpTokens.toArray(), algoPrice }), + target: getLPTokenInfosFx, }); $farmPools.watch((farms) => { const farmStates = farms.map((farm) => farm.state).filter((s): s is ContractState<'farm'> => s !== null); for (const s of farmStates) { registerAsset(s.initial.stakeToken); - registerPricedAsset(s.initial.rewardToken); - } -}); - -$stakePools.watch((farms) => { - const farmStates = farms.map((farm) => farm.state).filter((s): s is ContractState<'farm'> => s !== null); - for (const s of farmStates) { - registerPricedAsset(s.initial.stakeToken); - registerPricedAsset(s.initial.rewardToken); } }); @@ -332,7 +278,7 @@ export function createAprs( $pools, $networkTime, $meanRoundDuration, - $algoUsdPrice, + $pricedAlgo, $stakeTokens, $farmRewardTokens, (pools, time, meanRoundDuration, algoPrice, stakingTokens, farmRewardTokens) => @@ -375,7 +321,7 @@ export function createAprs( const algoRewardAPR = totalStakedUSD && algoPrice - ? ((algoRewardPerBlock * algoPrice * blocksInAYear) / totalStakedUSD) * 100 + ? ((algoRewardPerBlock * algoPrice.price * blocksInAYear) / totalStakedUSD) * 100 : 0; const feesAPR = ((stakeTokenInfo as Priced).dexFeeApr ?? 0) * 100; diff --git a/src/Farm/utils.ts b/src/Farm/utils.ts index 95e609de..087096c0 100644 --- a/src/Farm/utils.ts +++ b/src/Farm/utils.ts @@ -101,3 +101,21 @@ export const useWalletPersistedState = ( return usePersistedState(walletAddr ? `${walletAddr}-${key}` : undefined, initialValue); }; + +export function detectAssetProvider({ name }: { name: string }): DexProvider { + if (name.includes('TinymanPool2.0')) { + return 'T3'; + } + name = name.toLowerCase(); + if (name.includes('tinyman')) { + return 'T2'; + } + if (name.includes('humble')) { + return 'H2'; + } + if (name.includes('liquidity') || name.includes('pact')) { + return 'PT'; + } + + return 'MOCK'; +} diff --git a/src/LaaS/store.ts b/src/LaaS/store.ts index 12d0f086..0db2d453 100644 --- a/src/LaaS/store.ts +++ b/src/LaaS/store.ts @@ -1,4 +1,4 @@ -import { buildContractsStore, ContractType, registerPricedAsset } from '../common/store'; +import { buildContractsStore } from '../common/store'; import { backend as laasBackend } from '../cometa-laas-tmp/wrapper'; // TODO shall we support multiple backends? @@ -13,8 +13,8 @@ export const initializeLaasPool = initializeContract; $contractStatesWithCache.watch((states) => states.valueSeq().forEach((s) => { - registerPricedAsset(s.initial.aToken); - registerPricedAsset(s.initial.bToken); + // registerPricedAsset(s.initial.aToken); + // registerPricedAsset(s.initial.bToken); }) ); diff --git a/src/Menu/Menu.tsx b/src/Menu/Menu.tsx index 665ba455..080bce54 100644 --- a/src/Menu/Menu.tsx +++ b/src/Menu/Menu.tsx @@ -4,7 +4,7 @@ import logo from '../imgs/logo.png'; import meta_logo from '../imgs/meta_token.svg'; import algo_logo from '../imgs/algo_token.svg'; import burger from '../imgs/burger.svg'; -import { $algoUsdPrice, $pricedAssets, Asset, Priced } from '../common/store'; +import { $pricedAlgo, $pricedAssets, Asset, Priced } from '../common/store'; import { ConnectWallet } from '../wallet/ConnectWallet'; import { ALGONET, isMobile, META_TOKEN_ID, TESTNET } from '../AppContext'; import { getTokenLink } from '../Farm/PoolList/Pool/utils'; @@ -70,7 +70,7 @@ function BurgerMenu({ ALGOPrice, METAPrice }: { ALGOPrice: number | null; METAPr } export function Menu() { - const ALGOPrice = useUnit($algoUsdPrice); + const ALGOPrice = useUnit($pricedAlgo)?.price ?? null; const METAPrice = useStoreMap($pricedAssets, (as) => as.get(META_TOKEN_ID, null)); const [isBurgerOpen, setIsBurgerOpen] = useState(false); diff --git a/src/Stake/store.ts b/src/Stake/store.ts index 4bbfa647..930509f2 100644 --- a/src/Stake/store.ts +++ b/src/Stake/store.ts @@ -4,7 +4,7 @@ import { backend as distribution_17_0_4 } from 'metalabs-distribution-17_0_4'; import { backend as distribution_17_0_5 } from 'metalabs-distribution-17_0_5'; import { combine, Store } from 'effector'; -import { buildContractsStore, registerPricedAsset, $networkTime, $pricedAssets, Contract } from '../common/store'; +import { buildContractsStore, $networkTime, $pricedAssets, Contract } from '../common/store'; import { $stakePools, $farmStakeTokens, @@ -42,7 +42,7 @@ export const $allStakePools = combine( $contractStatesWithCache.watch((states) => states.valueSeq().forEach((s) => { - registerPricedAsset(s.initial.token); + // registerPricedAsset(s.initial.token); }) ); diff --git a/src/Swap/Swap.tsx b/src/Swap/Swap.tsx index 116a87a0..26523a1f 100644 --- a/src/Swap/Swap.tsx +++ b/src/Swap/Swap.tsx @@ -1,21 +1,10 @@ import React, { useEffect, useRef, useState } from 'react'; import { useUnit } from 'effector-react'; import { Account } from '@reach-sh/stdlib/ALGO'; -import { useModal } from 'react-hooks-use-modal'; -import { func } from 'prop-types'; import { DeflexQuote } from '@deflex/deflex-sdk-js'; import { theme } from '../theme'; import { ALGONET, deflexClient, MAINNET, META_TOKEN_ID, reach, TESTNET } from '../AppContext'; -import { - $account, - $balances, - Amount, - Asset, - AssetId, - fetchAsset, - fetchAssetPriceFx, - refreshAccountInfo, -} from '../common/store'; +import { $account, $balances, Amount, Asset, AssetId, fetchAsset, refreshAccountInfo } from '../common/store'; import { logEvent, LogName } from '../logEvent'; import { PacmanButton } from '../Components/PacmanButton/PacmanButton'; @@ -23,7 +12,6 @@ import { algoexplorerTxLink, fromSmallestUnits, getSmallestUnits, - parseTxs, SentTxError, signAndPostTxnGroups, } from '../common/lib'; diff --git a/src/common/store/assets.ts b/src/common/store/assets.ts index aae6aed2..90e57bcb 100644 --- a/src/common/store/assets.ts +++ b/src/common/store/assets.ts @@ -1,12 +1,9 @@ import { Map } from 'immutable'; import { createEffect, createEvent, createStore, sample, combine, split, Store, restore } from 'effector'; -import { algod, USDT_TOKEN_ID } from '../../AppContext'; -import { getAlgoRateFromVestige } from '../../providers/coinPriceProvider'; -import { pactDex } from '../../dexes'; +import { getAllAssets } from '../../providers/flexApiProvider'; import { $accountInfo } from './account'; import { Asset, AssetId, Amount, Priced } from './types'; -import { nonConcurrent, fetchStore } from './utils'; -import { doEachTick } from './time'; +import { nonConcurrent } from './utils'; // Main event to add the asset, adds it to all of the relevant stores export const registerAsset = createEvent(); @@ -43,108 +40,51 @@ export const ALGO_ASSET: Asset = { decimals: 6, }; -const fetchAssetFx = createEffect( - nonConcurrent(async (id: AssetId): Promise => { - const { params } = await algod.getAssetByID(id).do(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { creator, reserve, decimals } = params; - - const name = params['name'] ?? Buffer.from(params['name-b64'], 'base64').toString(); - const unit_name = params['unit-name'] ?? Buffer.from(params['unit-name-b64'], 'base64').toString(); - - return { - id, - name: name as string, - unitName: unit_name as string, - creator: creator as string, - reserve: reserve as string, - decimals: decimals as number, - } as Asset; - }) -); - -export const assetLoaded = fetchAssetFx.doneData; -export const $assets = createStore(Map().set(0, ALGO_ASSET)).on(assetLoaded, (assets, a) => - assets.set(a.id, a) -); - -const queryAsset = createEvent(); -const assetFoundInStore = createEvent(); -const storeFetchAttempt = sample({ clock: queryAsset, source: $assets, fn: (assets, id) => assets.get(id) || id }); - -split({ - source: storeFetchAttempt, - match: (a: Asset | AssetId) => (typeof a === 'number' ? 'fetch' : 'return'), - cases: { - fetch: fetchAssetFx, - return: assetFoundInStore, - }, -}); - -// Fetch asset info from algod on asset registration -sample({ - clock: registerAsset, - target: queryAsset, -}); - -/** - * Queries the asset by ID and returns without fetching it from algod if it's already in the store. - * - * @param assetId - * @returns Promise with asset - */ -export const fetchAsset = async (id: AssetId): Promise => { - const saved = await fetchStore($assets.map((assets) => assets.get(id, null))); - if (saved) return saved; - return fetchAssetFx(id); -}; +export async function fetchAllAssets(): Promise { + console.log('\nfetching all assets\n'); + const assetDetails = await getAllAssets(); + console.log('assetDetails:', assetDetails); + + return assetDetails.map((asset) => ({ + id: asset.id, + name: asset.name, + unitName: asset.unit_name, + creator: asset.creator, + reserve: asset.reserve, + decimals: asset.decimals, + })); +} -// ================================================================= -// ALGO price fetching -// (token prices are in separate file to avoid circular import to/from dexesProvider) -// ================================================================= -export const fetchAlgoPriceFx = createEffect( - nonConcurrent(async () => { +export const fetchAllAssetsFx = createEffect( + nonConcurrent(async (): Promise => { + console.log('Inside fetchAllAssetsFx'); try { - const rate = await getAlgoRateFromVestige(); - if (!rate) { - throw new Error(`Failed to fetch ALGO price from Vestige`); - } - - return Number(rate.price); + const assets = await fetchAllAssets(); + console.log('Assets fetched:', assets); + return assets; } catch (error) { - console.warn('Failed to get price from Vestige, piggybacking on Pact. Error was:', error); - const ALGO = 0; - const pool = await pactDex.getMostLiquidPool(ALGO, USDT_TOKEN_ID); - - return pool.calculator.primaryAssetPrice; + console.error('Failed to fetch assets:', error); + return []; // Return an empty array in case of failure } }) ); +export const allAssetsLoaded = fetchAllAssetsFx.doneData; -export const $algoUsdPrice = restore(fetchAlgoPriceFx.doneData, null); - -export const fetchAllPricesFx = createEffect(async () => { - //console.log('fetching prices...'); - return fetchAlgoPriceFx() - .then(() => { - // console.log('prices fetched'); - }) - .catch(() => { - console.log('failed to fetch prices :('); - }); -}); - -// Re-fetch prices once in say, 1 minute -void doEachTick(60_000, fetchAllPricesFx); +export const fetchAsset = async (assetId: AssetId): Promise => { + console.log('fetching asset with id:', assetId); + // eslint-disable-next-line effector/no-getState + const assets = $assets.getState(); + const saved = assets.get(assetId, null); + if (saved) return saved; + throw new Error(`Asset with id ${assetId} not found`); +}; -export const $pricedAlgo: Store | null> = combine( - $assets.map((as) => as.get(0, ALGO_ASSET)), - $algoUsdPrice, - (algoAsset, price) => { - if (price !== null) { - return { ...algoAsset, price, priceInAlgo: 1 }; +export const $assets = createStore(Map().set(0, ALGO_ASSET)).on( + allAssetsLoaded, + (assets, newAssets) => { + if (newAssets.length === 0) { + return assets; // Return the current state if no new assets are loaded } - return null; + return newAssets.reduce((acc, asset) => acc.set(asset.id, asset), assets); } ); diff --git a/src/common/store/prices.ts b/src/common/store/prices.ts index d3f348a2..896de9a7 100644 --- a/src/common/store/prices.ts +++ b/src/common/store/prices.ts @@ -1,72 +1,60 @@ import { createStore, createEffect, createEvent, combine, sample, Store } from 'effector'; import { Map } from 'immutable'; -import { getSwapCostSomewhere } from '../../dexes'; -import { META_TOKEN_ID } from '../../AppContext'; -import { SLIPPAGE } from '../../Swap/Swap'; -import { $assets, assetLoaded, ALGO_ASSET, $pricedAlgo, registerAsset } from './assets'; +import { AssetPriceInfo, getAllPrices } from '../../providers/flexApiProvider'; +import { $assets, ALGO_ASSET } from './assets'; import { Asset, AssetId, Priced } from './types'; import { nonConcurrent } from './utils'; - -export const fetchAssetPriceFx = createEffect( - nonConcurrent(async (asset: Asset): Promise => { - if (asset.id === 0) { - return 1; +import { doEachTick } from './time'; + +export const fetchAllPricesFx = createEffect( + nonConcurrent(async (): Promise => { + console.log('Inside fetchAllPricesFx'); + try { + const prices = await getAllPrices(); + console.log('Prices fetched:', prices); + return prices; + } catch (error) { + console.error('Failed to fetch prices:', error); + return []; // Return an empty array in case of failure } - const swapQuote = await getSwapCostSomewhere(asset, ALGO_ASSET, BigInt(10 ** asset.decimals), SLIPPAGE); - return swapQuote.price; }) ); -export const $assetAlgoPrices = createStore(Map()).on( - fetchAssetPriceFx.done, - (prices, { params, result }) => prices.set(params.id, result) +export const $assetPrices = createStore(Map()).on( + fetchAllPricesFx.doneData, + (prices, newPrices) => { + return newPrices.reduce((acc, price) => acc.set(price.asset_id, price), prices); + } ); -// Bool flags needed to not fetch swap prices of LP tokens for example -export const requireAssetPrice = createEvent(); -export const $assetIsPriced = createStore(Map()).on(requireAssetPrice, (as, id) => as.set(id, true)); - -export const registerPricedAsset = createEvent(); -sample({ - clock: registerPricedAsset, - target: [registerAsset, requireAssetPrice], -}); - -registerPricedAsset(META_TOKEN_ID); - -// Automatically fetch necessary assets prices when info about them is getting loaded first time -sample({ - clock: assetLoaded, - source: $assetIsPriced, - filter: (pricedFlags, asset) => pricedFlags.get(asset.id, false), - fn: (_, asset) => asset, - target: fetchAssetPriceFx, -}); - -fetchAssetPriceFx.fail.watch((v) => { - console.log('ASSET PRICE FETCHING FAILED', v); +void doEachTick(60_000, fetchAllPricesFx); + +export const $pricedAssets: Store>> = combine($assets, $assetPrices, (assets, prices) => { + return assets.map((asset) => { + const price = prices.get(asset.id); + if (!price) { + return { + ...asset, + price: 0, + priceInAlgo: 0, + }; + } + return { + ...asset, + price: price.price_usd, + priceInAlgo: price.price_algo, + }; + }); }); -export const $pricedAssets: Store>> = combine( - $pricedAlgo, - $assetAlgoPrices, - $assets, - (pricedAlgo: Priced | null, assetAlgoPrices: Map, assets: Map) => { - if (pricedAlgo === null) { - return Map>(); // Empty map, because cannot price anything without algo price +export const $pricedAlgo: Store | null> = combine( + $assets.map((as) => as.get(0, ALGO_ASSET)), + $assetPrices.map((prices) => prices.get(0)), + (algoAsset, algoPrice) => { + if (algoPrice) { + return { ...algoAsset, price: algoPrice.price_usd, priceInAlgo: 1 }; } - - return assetAlgoPrices - .map((priceInAlgo, assetId) => { - const asset = assets.get(assetId); - if (!asset) { - throw new Error(`impossible: having price ${priceInAlgo} for unfetched asset ${assetId}`); - } - - const price = priceInAlgo * pricedAlgo.price; - return { ...asset, priceInAlgo, price }; - }) - .set(0, pricedAlgo); + return null; } ); diff --git a/src/dexes/index.ts b/src/dexes/index.ts index 3012a9a7..28446a46 100644 --- a/src/dexes/index.ts +++ b/src/dexes/index.ts @@ -1,8 +1,7 @@ import { algod, ALGONET, TESTNET } from '../AppContext'; import { AppId, Asset, AssetId, Amount, fetchAsset, ALGO_ASSET } from '../common/store'; -import { WalletTransactionGroup } from '../types'; -import { Dex, PoolInfo, SwapQuote, MintQuote, DexProvider, DexPool } from './common'; +import { Dex, SwapQuote, DexProvider, DexPool } from './common'; import { PactDex } from './pact'; import { TinymanDex } from './tinyman'; import { HumbleDex } from './humble'; diff --git a/src/index.tsx b/src/index.tsx index 44b55634..81267355 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,14 +1,11 @@ import { createRoot } from 'react-dom/client'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; -import * as Sentry from '@sentry/react'; -import { BrowserTracing } from '@sentry/tracing'; - import { ThemeProvider } from 'styled-components'; import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; import { useEffect, useState } from 'react'; import { useModal, ModalProvider } from 'react-hooks-use-modal'; -import { useStoreMap, useUnit } from 'effector-react'; +import { useStore, useStoreMap, useUnit } from 'effector-react'; import { Flip, ToastContainer } from 'react-toastify'; import ReactGA from 'react-ga'; import GlobalStyle from './common/globalStyles'; @@ -21,7 +18,7 @@ import { Zap } from './Zap'; import { MetaDAO } from './MetaDAO'; import { theme } from './theme'; import { Container, ContentContainer } from './common/styled'; -import { $account, ContractInfo, fetchAllPricesFx } from './common/store'; +import { $account, ContractInfo, fetchAllAssetsFx, fetchAllPricesFx } from './common/store'; import { Stake } from './Stake/Stake'; import './css/index.css'; @@ -34,19 +31,18 @@ import { Footer } from './Menu/Footer'; import { notify } from './Components/Notification'; import { AddFarm } from './Farm/AddFarm'; import { LaaS } from './LaaS/LaaS'; -import { setLaasPoolInfos } from './LaaS/store'; import { AddLaaS } from './LaaS/AddLaaS'; import { Metapunks } from './Metapunks/Metapunks'; -Sentry.init({ - dsn: 'https://65dfff9b40a24539b633789b8cfba771@o1313570.ingest.sentry.io/6563864', - integrations: [new BrowserTracing()], - - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for performance monitoring. - // We recommend adjusting this value in production - tracesSampleRate: 1, -}); +// Sentry.init({ +// dsn: 'https://65dfff9b40a24539b633789b8cfba771@o1313570.ingest.sentry.io/6563864', +// integrations: [new BrowserTracing()], +// +// // Set tracesSampleRate to 1.0 to capture 100% +// // of transactions for performance monitoring. +// // We recommend adjusting this value in production +// tracesSampleRate: 1, +// }); ReactGA.initialize('G-P19KGXJGTP'); // TODO @@ -65,6 +61,7 @@ window.open = (function (open) { const queryClient = new QueryClient(); void fetchAllPricesFx(); +void fetchAllAssetsFx(); const WELCOME_MODAL_KEY = `welcome_cosmaud`; diff --git a/src/providers/apiProvider.ts b/src/providers/apiProvider.ts index 0244d7d1..002ecb92 100644 --- a/src/providers/apiProvider.ts +++ b/src/providers/apiProvider.ts @@ -115,34 +115,6 @@ export async function getContracts( }); } -export type LpState = { - id: number; - token_id: number; - asset1_id: number; - asset2_id: number; - dex_provider: string; - address: string; - - asset1_reserve: number; - asset2_reserve: number; - issued_tokens: number; - - token_price: number; - token_price_usd: number; - swap_fee_apr?: number; -}; - -export async function getLpState(lp_id: number): Promise { - const res = await instance - .post(`/info/lp/state?lp_token_id=${lp_id}`) - .then(({ data }) => data) - .catch((error) => { - console.log('ERR', error); - throw error; - }); - return res; -} - export async function getPoolInfo(asset1: number, asset2: number): Promise { return instance.get(`/pool?asset_1_id=${asset1}&asset_2_id=${asset2}`).then(({ data }) => data); } diff --git a/src/providers/flexApiProvider.ts b/src/providers/flexApiProvider.ts new file mode 100644 index 00000000..74108836 --- /dev/null +++ b/src/providers/flexApiProvider.ts @@ -0,0 +1,106 @@ +import { DexProvider } from '../dexes'; +import { AssetId } from '../common/store'; +import { instance } from './apiProvider'; + +export type AssetDetails = { + id: AssetId; + name: string; + unit_name: string; + creator: string; + reserve: string; + decimals: number; +}; + +export type PricedLpInfo = { + id: number; + token_id: number; + asset1_id: number; + asset2_id: number; + dex_provider: DexProvider; + address: string; + + asset1_reserve_micros: number; + asset2_reserve_micros: number; + issued_tokens_micros: number; + + asset1_reserve: number; + asset2_reserve: number; + issued_tokens: number; + + token_price_algo: number; + token_price_usd: number; + swap_fee_apr?: number; +}; + +export type AssetPriceInfo = { + asset_id: number; + asset_name: string; + price_usd: number; + price_algo: number; + last_update_round: number; + seconds_since_update?: number; +}; + +export async function getPricedLpInfo(lp_token_id: number): Promise { + return await instance + .post(`/lp/state/priced?lp_token_id=${lp_token_id}`) + .then(({ data }) => data) + .catch((error) => { + console.log('ERR', error); + throw error; + }); +} + +export async function getPricedLpInfos(lp_token_ids?: number[]): Promise { + const params: { [key: string]: any } = {}; + if (lp_token_ids) { + params['lp_token_ids'] = lp_token_ids; + } + return await instance + .post(`/lp/states/priced`, params) + .then(({ data }) => data) + .catch((error) => { + console.log('ERR', error); + throw error; + }); +} + +export async function getAsset(asset_id: number): Promise { + return await instance + .post(`/asset?asset_id=${asset_id}`) + .then(({ data }) => data) + .catch((error) => { + console.log('ERR', error); + throw error; + }); +} + +export async function getAllAssets(asset_ids: number[] | null = null): Promise { + try { + const response = await instance.post(`/assets`, { ids: asset_ids }); + return response.data; + } catch (error) { + console.error('Failed to fetch assets from API:', error); + throw error; // Rethrow the error to be handled by the effect + } +} + +export async function getPrice(asset_id: number): Promise { + return await instance + .post(`/asset/price?asset_id=${asset_id}`) + .then(({ data }) => data) + .catch((error) => { + console.log('ERR', error); + throw error; + }); +} + +export async function getAllPrices(asset_ids: number[] | null = null): Promise { + try { + const response = await instance.post(`/assets/price`, { ids: asset_ids }); + return response.data; + } catch (error) { + console.error('Failed to fetch assets from API:', error); + throw error; // Rethrow the error to be handled by the effect + } +}