From 107e33bdfee6bc0ea6515b69069fef331bc706a7 Mon Sep 17 00:00:00 2001 From: jahabeebs Date: Tue, 2 Jul 2024 18:51:03 -0500 Subject: [PATCH] new global safe model --- src/hooks/useSafeData.ts | 28 +++++++++++++++++++++---- src/model/globalSafeModel.ts | 39 +++++++++++++++++++++++++++++++++++ src/model/index.ts | 3 +++ src/services/safes.ts | 25 +++++++++++++++++++++- src/utils/gebManager/index.ts | 39 ++++++++++++++++++++++++++++++----- src/utils/helper.ts | 20 +++++++++++++----- src/utils/interfaces.ts | 9 ++++++++ 7 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 src/model/globalSafeModel.ts diff --git a/src/hooks/useSafeData.ts b/src/hooks/useSafeData.ts index edfa74f4..a284fbe6 100644 --- a/src/hooks/useSafeData.ts +++ b/src/hooks/useSafeData.ts @@ -12,9 +12,23 @@ export default function useSafeData() { const geb = useGeb() const tokensData = geb?.tokenList const { account } = useActiveWeb3React() - const { safeModel: safeActions } = useStoreActions((state) => state) + const { safeModel: safeActions, globalSafeModel: globalSafeActions } = useStoreActions((state) => state) + const previousAccount = usePrevious(account) + const fetchGlobalSafes = useCallback(() => { + if (geb && tokensData) { + try { + globalSafeActions.fetchGlobalSafes({ + geb, + tokensData, + }) + } catch (error) { + console.debug('Failed to fetch user safes', error) + } + } + }, [geb, tokensData, globalSafeActions]) + const fetchUserSafes = useCallback(() => { if (account && geb && tokensData) { try { @@ -32,14 +46,20 @@ export default function useSafeData() { // Fetch safes initially and on account or geb change useEffect(() => { fetchUserSafes() + fetchGlobalSafes() const interval = setInterval(fetchUserSafes, 60000) - return () => clearInterval(interval) - }, [fetchUserSafes]) + const globalInterval = setInterval(fetchGlobalSafes, 60000) + return () => { + clearInterval(interval) + clearInterval(globalInterval) + } + }, [fetchUserSafes, fetchGlobalSafes]) // Handles account changes useEffect(() => { if (account && previousAccount !== account) { fetchUserSafes() + fetchGlobalSafes() } - }, [account, previousAccount, fetchUserSafes]) + }, [account, previousAccount, fetchUserSafes, fetchGlobalSafes]) } diff --git a/src/model/globalSafeModel.ts b/src/model/globalSafeModel.ts new file mode 100644 index 00000000..695d4b2d --- /dev/null +++ b/src/model/globalSafeModel.ts @@ -0,0 +1,39 @@ +import { action, Action, thunk, Thunk } from 'easy-peasy' +import { StoreModel } from '~/model' +import { fetchGlobalSafes } from '~/services/safes' +import { timeout, ILiquidationData, ISafe, IFetchGlobalSafesPayload } from '~/utils' + +export interface GlobalSafeModel { + list: Array + liquidationData: ILiquidationData | null + fetchGlobalSafes: Thunk + setList: Action> + setLiquidationData: Action +} + +const globalSafeModel: GlobalSafeModel = { + list: [], + liquidationData: null, + fetchGlobalSafes: thunk(async (actions, payload) => { + let fetched + try { + fetched = await fetchGlobalSafes(payload) + } catch (e) { + console.debug('Failed to fetch global safes', e) + } + if (fetched) { + actions.setList(fetched.globalSafes) + actions.setLiquidationData(fetched.liquidationData) + await timeout(200) + return fetched + } + }), + setList: action((state, payload) => { + state.list = payload + }), + setLiquidationData: action((state, payload) => { + state.liquidationData = payload + }), +} + +export default globalSafeModel diff --git a/src/model/index.ts b/src/model/index.ts index 959573ec..8ddf1734 100755 --- a/src/model/index.ts +++ b/src/model/index.ts @@ -2,6 +2,7 @@ import settingsModel, { SettingsModel } from './settingsModel' import popupsModel, { PopupsModel } from './popupsModel' import connectWalletModel, { ConnectWalletModel } from './connectWalletModel' import safeModel, { SafeModel } from './safeModel' +import globalSafeModel, { GlobalSafeModel } from './globalSafeModel' import transactionsModel, { TransactionsModel } from './transactionsModel' import multicallModel, { MulticallModel } from './multicallModel' import auctionModel, { AuctionModel } from './auctionModel' @@ -16,6 +17,7 @@ export interface StoreModel { popupsModel: PopupsModel connectWalletModel: ConnectWalletModel safeModel: SafeModel + globalSafeModel: GlobalSafeModel transactionsModel: TransactionsModel multicallModel: MulticallModel auctionModel: AuctionModel @@ -31,6 +33,7 @@ const model: StoreModel = { popupsModel, connectWalletModel, safeModel, + globalSafeModel, transactionsModel, multicallModel, auctionModel, diff --git a/src/services/safes.ts b/src/services/safes.ts index 7c75889d..1c2a12e3 100644 --- a/src/services/safes.ts +++ b/src/services/safes.ts @@ -1,4 +1,4 @@ -import { formatUserSafe, IFetchSafesPayload, IUserSafeList } from '~/utils' +import { formatUserSafe, IFetchGlobalSafesPayload, IFetchSafesPayload, IUserSafeList } from '~/utils' import gebManager from '~/utils/gebManager' export const fetchUserSafes = async (config: IFetchSafesPayload) => { @@ -42,3 +42,26 @@ export const fetchUserSafesRaw = async (config: IFetchSafesPayload) => { return response } + +export const fetchGlobalSafes = async (config: IFetchGlobalSafesPayload) => { + let ownerAddressesResponse = await gebManager.getGlobalSafesRpc() + if (!ownerAddressesResponse) return + + let safesResponse = await gebManager.fetchSafesForOwners(config, ownerAddressesResponse.ownerAddresses) + if (!safesResponse) return + + const liquidationData = { + collateralLiquidationData: safesResponse.collateralLiquidationData, + currentRedemptionPrice: safesResponse.systemState.currentRedemptionPrice.value, + currentRedemptionRate: safesResponse.systemState.currentRedemptionRate.annualizedRate, + globalDebt: safesResponse.systemState.globalDebt, + globalDebtCeiling: safesResponse.systemState.globalDebtCeiling, + perSafeDebtCeiling: safesResponse.systemState.perSafeDebtCeiling, + } + + const globalSafes = formatUserSafe(safesResponse.safes, liquidationData, config.tokensData) + return { + globalSafes, + liquidationData, + } +} diff --git a/src/utils/gebManager/index.ts b/src/utils/gebManager/index.ts index 766c1bd8..8666b1a0 100644 --- a/src/utils/gebManager/index.ts +++ b/src/utils/gebManager/index.ts @@ -1,8 +1,7 @@ +import axios from 'axios' import { BigNumber } from 'ethers' -import { Geb, utils } from '@opendollar/sdk' -import { ILiquidationResponse, IUserSafeList } from '../interfaces' - -import { TokenLiquidationData, fetchLiquidationData } from '@opendollar/sdk/lib/virtual/virtualLiquidationData' +import { fetchLiquidationData, Geb, TokenLiquidationData, utils } from '@opendollar/sdk' +import { ILiquidationResponse, IUserSafeList, IOwnerAddressesResponse } from '../interfaces' import { fetchUserSafes } from '@opendollar/sdk/lib/virtual/virtualUserSafes.js' import { TokenData } from '@opendollar/sdk/lib/contracts/addreses' @@ -14,6 +13,11 @@ interface UserListConfig { safeId_not?: null } +interface GlobalSafesConfig { + geb: Geb + tokensData: { [key: string]: TokenData } +} + // returns LiquidationData const getLiquidationDataRpc = async ( geb: Geb, @@ -98,6 +102,7 @@ const getUserSafesRpc = async (config: UserListConfig): Promise = safeHandler: safe.addy, safeId: safe.id.toString(), collateralType: safe.collateralType, + ownerAddress: config.address, })) return { @@ -111,12 +116,36 @@ const getUserSafesRpc = async (config: UserListConfig): Promise = } } +const getGlobalSafesRpc = async (): Promise => { + const response = await axios.get('https://bot.opendollar.com/api/vaults') + const ownerAddresses: string[] = Array.from(new Set(response.data.details.map((safe: any) => safe.owner))) + return { ownerAddresses } +} + +const fetchSafesForOwners = async (config: GlobalSafesConfig, ownerAddresses: string[]): Promise => { + const allSafes: any[] = [] + const safePromises = ownerAddresses.map((address) => getUserSafesRpc({ ...config, address })) + + const results = await Promise.all(safePromises) + results.forEach((result) => { + allSafes.push(...result.safes) + }) + + const liquidationData = await getLiquidationDataRpc(config.geb, config.tokensData) + return { + safes: allSafes, + erc20Balances: [], + ...liquidationData, + } +} + const gebManager = { getUserSafesRpc, + getGlobalSafesRpc, + fetchSafesForOwners, getLiquidationDataRpc, } -// Helper functions export const parseWad = (val: BigNumber) => utils.wadToFixed(val).toString() export const parseRay = (val: BigNumber) => utils.rayToFixed(val).toString() export const parseRad = (val: BigNumber) => utils.radToFixed(val).toString() diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 34e4cbfb..950692f7 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -127,6 +127,13 @@ export const toFixedString = (value: string, type: keyof typeof floatsTypes = 'W } } +const getBytes32String = (collateralType: string, tokensData: { [key: string]: TokenData }): string | null => { + const token = Object.values(tokensData).find( + (token) => token.symbol === collateralType || token.bytes32String === collateralType + ) + return token ? token.bytes32String : null +} + export const formatUserSafe = ( safes: Array, liquidationData: ILiquidationData, @@ -141,18 +148,19 @@ export const formatUserSafe = ( const { currentRedemptionPrice, currentRedemptionRate, collateralLiquidationData } = liquidationData return safes - .filter((s) => s.collateralType in collateralBytes32) .map((s) => { - const token = collateralBytes32[s.collateralType] + const bytes32String = getBytes32String(s.collateralType, tokensData) + if (!bytes32String || !(bytes32String in collateralBytes32)) return null + + const token = collateralBytes32[bytes32String] const accumulatedRate = collateralLiquidationData[token]?.accumulatedRate const currentPrice = collateralLiquidationData[token]?.currentPrice + const availableDebt = returnAvailableDebt(currentPrice?.safetyPrice, '0', s.collateral, s.debt) const liquidationCRatio = collateralLiquidationData[token]?.liquidationCRatio const safetyCRatio = collateralLiquidationData[token]?.safetyCRatio const liquidationPenalty = collateralLiquidationData[token]?.liquidationPenalty const totalAnnualizedStabilityFee = collateralLiquidationData[token]?.totalAnnualizedStabilityFee - const availableDebt = returnAvailableDebt(currentPrice?.safetyPrice, '0', s.collateral, s.debt) - const totalDebt = returnTotalValue(returnTotalDebt(s.debt, accumulatedRate) as string, '0').toString() const liquidationPrice = getLiquidationPrice( @@ -171,12 +179,13 @@ export const formatUserSafe = ( return { id: s.safeId, + ownerAddress: s.ownerAddress, safeHandler: s.safeHandler, date: s.createdAt, riskState: ratioChecker(Number(collateralRatio), Number(liquidationCRatio), Number(safetyCRatio)), collateral: s.collateral, collateralType: s.collateralType, - collateralName: collateralBytes32[s.collateralType], + collateralName: collateralBytes32[bytes32String], debt: s.debt, totalDebt, availableDebt, @@ -192,6 +201,7 @@ export const formatUserSafe = ( currentRedemptionRate: currentRedemptionRate || '0', } as ISafe }) + .filter((s): s is ISafe => s !== null) .sort((a, b) => Number(b.riskState) - Number(a.riskState) || Number(b.debt) - Number(a.debt)) } diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index c44ac3c3..98ae62fc 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -16,6 +16,10 @@ export interface DynamicObject { [key: string]: any } +export interface IOwnerAddressesResponse { + ownerAddresses: string[] +} + interface IColors { primary: string secondary: string @@ -366,6 +370,11 @@ export interface IFetchSafesPayload { tokensData: { [key: string]: TokenData } } +export interface IFetchGlobalSafesPayload { + geb: Geb + tokensData: { [key: string]: TokenData } +} + export interface IFetchSafeById extends IFetchSafesPayload { safeId: string }