From 136a14e8e5bf13b5c5a374389f7d4adb88916a03 Mon Sep 17 00:00:00 2001 From: ismellike Date: Tue, 20 Feb 2024 20:31:59 -0600 Subject: [PATCH] Detect native denoms fully staked in daos (#1714) --- packages/config/eslint/index.js | 6 ++ packages/state/recoil/selectors/chain.ts | 27 ++++++++- packages/state/recoil/selectors/indexer.ts | 1 + packages/state/recoil/selectors/wallet.ts | 70 ++++++++++++++++++++++ 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/packages/config/eslint/index.js b/packages/config/eslint/index.js index f1f3198417..ceff9d67dd 100644 --- a/packages/config/eslint/index.js +++ b/packages/config/eslint/index.js @@ -24,6 +24,12 @@ const eslintConfig = { 'no-unused-vars': ['off'], 'react/jsx-sort-props': ['warn', { reservedFirst: ['key'] }], 'tailwindcss/classnames-order': ['warn'], + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], eqeqeq: ['error'], }, overrides: [ diff --git a/packages/state/recoil/selectors/chain.ts b/packages/state/recoil/selectors/chain.ts index 9064899592..e57586e915 100644 --- a/packages/state/recoil/selectors/chain.ts +++ b/packages/state/recoil/selectors/chain.ts @@ -3,6 +3,7 @@ import { fromBase64, toHex } from '@cosmjs/encoding' import { Coin, IndexedTx, StargateClient } from '@cosmjs/stargate' import uniq from 'lodash.uniq' import { + noWait, selector, selectorFamily, waitForAll, @@ -78,6 +79,7 @@ import { queryValidatorIndexerSelector, } from './indexer' import { genericTokenSelector } from './token' +import { walletTokenDaoStakedDenomsSelector } from './wallet' export const stargateClientForChainSelector = selectorFamily< StargateClient, @@ -342,15 +344,36 @@ export const nativeBalancesSelector = selectorFamily< const balances = [ ...get(justNativeBalancesSelector({ address, chainId })), ] - // Add native denom if not present. const nativeToken = getNativeTokenForChainId(chainId) - if (!balances.some(({ denom }) => denom === nativeToken.denomOrAddress)) { + const stakedDenoms = + get( + noWait( + walletTokenDaoStakedDenomsSelector({ + walletAddress: address, + chainId, + }) + ) + ).valueMaybe() || [] + + const uniqueDenoms = new Set(balances.map(({ denom }) => denom)) + + // Add native denom if not present. + if (!uniqueDenoms.has(nativeToken.denomOrAddress)) { balances.push({ amount: '0', denom: nativeToken.denomOrAddress, }) + uniqueDenoms.add(nativeToken.denomOrAddress) } + // Add denoms staked to DAOs if not present. + stakedDenoms.forEach((denom) => { + if (!uniqueDenoms.has(denom)) { + balances.push({ amount: '0', denom }) + uniqueDenoms.add(denom) + } + }) + const tokenLoadables = get( waitForAny( balances.map(({ denom }) => diff --git a/packages/state/recoil/selectors/indexer.ts b/packages/state/recoil/selectors/indexer.ts index f845a8dd41..9effccb5c6 100644 --- a/packages/state/recoil/selectors/indexer.ts +++ b/packages/state/recoil/selectors/indexer.ts @@ -124,6 +124,7 @@ export const queryValidatorIndexerSelector = selectorFamily< }) ), }) + export const queryWalletIndexerSelector = selectorFamily< any, Omit & { diff --git a/packages/state/recoil/selectors/wallet.ts b/packages/state/recoil/selectors/wallet.ts index f4a95b1a5b..7b073379e4 100644 --- a/packages/state/recoil/selectors/wallet.ts +++ b/packages/state/recoil/selectors/wallet.ts @@ -1,8 +1,12 @@ import { selectorFamily, waitForAll } from 'recoil' import { GenericTokenBalance, TokenType, WithChainId } from '@dao-dao/types' +import { DAO_VOTING_TOKEN_STAKED_CONTRACT_NAMES } from '@dao-dao/utils' import { refreshWalletBalancesIdAtom } from '../atoms' +import { isContractSelector } from './contract' +import { votingModuleSelector } from './contracts/DaoCore.v2' +import * as DaoVotingTokenStaked from './contracts/DaoVotingTokenStaked' import { queryWalletIndexerSelector } from './indexer' import { genericTokenSelector } from './token' @@ -50,3 +54,69 @@ export const walletCw20BalancesSelector = selectorFamily< })) }, }) + +export const walletTokenDaoStakedDenomsSelector = selectorFamily< + readonly string[], + WithChainId<{ walletAddress: string }> +>({ + key: 'walletTokenDaoStakedDenoms', + get: + ({ walletAddress, chainId }) => + ({ get }) => { + // Get the DAOs that the wallet is a member of + const daos = get( + queryWalletIndexerSelector({ + chainId, + walletAddress, + formula: 'daos/memberOf', + }) + ) + if (!daos || !Array.isArray(daos) || daos.length === 0) { + return [] + } + + // Get the token staked voting modules for each DAO + const votingModules = get( + waitForAll( + daos.map(({ dao: contractAddress }) => + votingModuleSelector({ + contractAddress, + chainId, + params: [], + }) + ) + ) + ).filter((contractAddress) => + get( + isContractSelector({ + contractAddress, + chainId, + names: DAO_VOTING_TOKEN_STAKED_CONTRACT_NAMES, + }) + ) + ) + + if (votingModules.length === 0) { + return [] + } + + // Get a list of denoms from the voting modules + const denoms = get( + waitForAll( + votingModules.map((contractAddress) => + DaoVotingTokenStaked.denomSelector({ + contractAddress, + chainId, + params: [], + }) + ) + ) + ) + + // Create a Set from the denoms to ensure uniqueness + const uniqueDenoms = new Set(denoms.map(({ denom }) => denom)) + + // Convert the Set back into an array to return + return [...uniqueDenoms] + }, +})