Skip to content

Commit

Permalink
fix: ensure IBC tokens are referenced correctly (#440)
Browse files Browse the repository at this point in the history
* feat: use default token list for TokenPicker

* feat: simplify asset list creation, add all at start

* fix: make dev tokens consistent with regular chain-registry tokens

* fix: reference IBC denoms correctly in TokenPicker, URLs, and pages

* fix: ensure denom amount reading can match denom aliases

    - this helps the IBC denom aliases be found

* fix: add environment dev tokens more consistently with their usage

* feat: add extra IBC information to IBC context tokens

* fix: protect against mis-matched pool reserve tokens

* fix: bridging to local chain requires non-IBC reference on other chain

* fix: resolution of bridging IBC denom should not require a lookup

* fix: matching of tokens in Position table cards

* fix: use RPC instead of LCD for endpoints with IBC denoms in path

* fix: ensure token matching works with IBC tokens

* fix: reference IBC denoms correctly in page links

* refactor: simplify token path part usage

* fix: ensure indexer token paths work with IBC tokens

* fix: IBC tokens were sometimes double-encoded

* fix: use IBC token.address to reference all IBC denoms
  • Loading branch information
dib542 authored Sep 26, 2023
1 parent 5fe9fc1 commit d6f7a67
Show file tree
Hide file tree
Showing 22 changed files with 275 additions and 194 deletions.
11 changes: 6 additions & 5 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ PUBLIC_URL=https://app.dev.duality.xyz

REACT_APP__IS_MAINNET=testnet

# Add prices for development tokens
REACT_APP__DEV_TOKEN_DENOMS=["sdk.coin:token", "sdk.coin:stake"]
REACT_APP__CHAIN_FEE_TOKENS=[{"denom":"sdk.coin:token"}]
# Add development tokens
REACT_APP__DEV_ASSET_MAP={"tokenA":"ATOM/cosmoshub","tokenB":"USDC/ethereum","tokenC":"ETH/ethereum","tokenD":"OSMO/osmosis","tokenE":"JUNO/juno","tokenF":"STRD/stride","tokenG":"STARS/stargaze","tokenH":"CRE/crescent","tokenI":"HUAHUA/chihuahua"}

# Override chain name
# Override chain settings
REACT_APP__CHAIN_ID=duality-devnet
REACT_APP__CHAIN_NAME=Duality Devnet
REACT_APP__CHAIN_FEE_TOKENS=[{"denom":"stake"}]

# An example of using bridged IBC tokens as fee tokens in the consumer chain:
# note: IBC denom trace "transfer/channel-1/stake" is
# IBC denom hash "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9"
#
# REACT_APP__CHAIN={"chain_name":"duality"}
# REACT_APP__CHAIN_ASSETS={"chain_name":"duality","assets":[{"description":"Provider token","address":"stake","base":"ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9","name":"Provider Stake","display":"stk","symbol":"STK","denom_units":[{"denom":"ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9","exponent":0,"aliases":["stake"]},{"denom":"stk","exponent":6,"aliases":["provider-stake","STAKE"]}],"logo_URIs":{"svg":"https://raw.githubusercontent.com/cosmos/chain-registry/master/cosmoshub/images/atom.svg"}}]}
# REACT_APP__CHAIN_FEE_TOKENS=[{"denom":"ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9"}]
# REACT_APP__PROVIDER_CHAIN={"chain_name":"local-provider","staking":{"staking_tokens":[{"denom":"stk"}]},"apis":{"rpc":[{"address":"http://localhost:26657"}],"rest":[{"address":"http://localhost:1317"}]},"status":"live","network_type":"testnet","pretty_name":"Provider Local","chain_id":"provider","bech32_prefix":"cosmos","slip44":118,"logo_URIs":{"svg":"https://raw.githubusercontent.com/cosmos/chain-registry/master/cosmoshub/images/atom.svg"}}
# REACT_APP__PROVIDER_ASSETS={"chain_name":"local-provider","assets":[{"description":"Provider token","address":"stake","denom_units":[{"denom":"stake","exponent":0},{"denom":"stk","exponent":6}],"base":"stake","name":"Provider Stake","display":"stk","symbol":"STK","logo_URIs":{"svg":"https://raw.githubusercontent.com/cosmos/chain-registry/master/cosmoshub/images/atom.svg"}}]}
2 changes: 1 addition & 1 deletion src/components/TokenInputGroup/TokenInputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface InputGroupProps {
variant?: 'success' | 'error' | false;
onTokenChanged?: (token?: Token) => void;
onValueChanged?: (value: string) => void;
tokenList: Array<Token>;
tokenList?: Array<Token>;
className?: string;
exclusion?: Token;
value?: string;
Expand Down
13 changes: 9 additions & 4 deletions src/components/TokenPicker/TokenPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import BigNumber from 'bignumber.js';

import { useFilteredTokenList } from './hooks';
import { useDualityTokens } from '../../lib/web3/hooks/useTokens';
import useTokens, {
useDualityTokens,
useTokensWithIbcInfo,
} from '../../lib/web3/hooks/useTokens';
import { Token } from '../../lib/web3/utils/tokens';
import useUserTokens from '../../lib/web3/hooks/useUserTokens';
import { useBankBalanceDisplayAmount } from '../../lib/web3/hooks/useUserBankBalances';
Expand All @@ -29,7 +32,7 @@ interface TokenPickerProps {
onChange: (newToken: Token | undefined) => void;
exclusion?: Token | undefined;
value: Token | undefined;
tokenList: Array<Token>;
tokenList?: Array<Token>;
disabled?: boolean;
showChain?: boolean;
}
Expand Down Expand Up @@ -81,7 +84,7 @@ export default function TokenPicker({
value,
onChange,
exclusion,
tokenList,
tokenList: givenTokenList,
disabled = false,
showChain = true,
}: TokenPickerProps) {
Expand All @@ -90,6 +93,8 @@ export default function TokenPicker({
const [selectedIndex, setSelectedIndex] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const bodyRef = useRef<HTMLUListElement>(null);
const defaultTokenList = useTokensWithIbcInfo(useTokens());
const tokenList = givenTokenList || defaultTokenList;
const userList = useUserTokens();
const [assetMode, setAssetMode] = useState<AssetModeType>(
userList.length ? 'User' : 'Duality'
Expand Down Expand Up @@ -257,7 +262,7 @@ export default function TokenPicker({
filteredList.map(({ chain, symbol, token }, index) => {
return token ? (
<TokenPickerItem
key={`${token.base}:${token.chain.chain_name}`}
key={`${token.address}:${token.chain.chain_name}`}
token={token}
chain={chain}
symbol={symbol}
Expand Down
6 changes: 2 additions & 4 deletions src/components/cards/AssetsTableCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Dialog from '../Dialog/Dialog';

import TableCard, { TableCardProps } from '../../components/cards/TableCard';
import useTokens, {
matchTokens,
useTokensWithIbcInfo,
} from '../../lib/web3/hooks/useTokens';
import BridgeCard from './BridgeCard';
Expand Down Expand Up @@ -115,10 +116,7 @@ export default function AssetsTableCard({
{filteredList.length > 0 ? (
filteredList.map(({ chain, symbol, token }) => {
const foundUserAsset = allUserBankAssets.find((userToken) => {
return (
userToken.token.address === token.address &&
userToken.token.chain.chain_id === token.chain.chain_id
);
return matchTokens(userToken.token, token);
});
return foundUserAsset ? (
<AssetRow
Expand Down
32 changes: 16 additions & 16 deletions src/components/cards/BridgeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { coin } from '@cosmjs/stargate';
import TokenPicker from '../TokenPicker/TokenPicker';
import NumberInput from '../inputs/NumberInput/NumberInput';

import useTokens from '../../lib/web3/hooks/useTokens';
import useBridge from '../../pages/Bridge/useBridge';
import { useWeb3 } from '../../lib/web3/useWeb3';
import { useUserBankValues } from '../../lib/web3/hooks/useUserBankValues';
Expand All @@ -28,6 +27,7 @@ import {
Token,
getBaseDenomAmount,
getDisplayDenomAmount,
getIbcDenom,
} from '../../lib/web3/utils/tokens';

import './BridgeCard.scss';
Expand All @@ -50,7 +50,6 @@ export default function BridgeCard({
inputRef?: React.RefObject<HTMLInputElement>;
onSuccess?: () => void;
}) {
const tokenList = useTokens();
const [token, setToken] = useState<Token | undefined>(from || to);
const [value, setValue] = useState('');

Expand Down Expand Up @@ -110,9 +109,15 @@ export default function BridgeCard({
if (!amount || !Number(amount)) {
throw new Error('Invalid Token Amount');
}
// find the base non-IBC denom to match the base amount being sent
const tokenDenom = from.base;
if (!tokenDenom) {
throw new Error('Source denom not found');
}

try {
await sendRequest({
token: coin(amount, from.address),
token: coin(amount, tokenDenom),
timeout_timestamp: timeoutTimestamp,
sender: chainAddressFrom,
receiver: chainAddressTo,
Expand All @@ -136,26 +141,22 @@ export default function BridgeCard({
if (!to.chain.chain_id) {
throw new Error('Destination Chain not found');
}
const amount = getBaseDenomAmount(to, value);
if (!amount || !Number(amount)) {
const baseAmount = getBaseDenomAmount(to, value);
if (!baseAmount || !Number(baseAmount)) {
throw new Error('Invalid Token Amount');
}
if (!ibcTransferInfo.channel.counterparty) {
throw new Error('No egress connection information found');
}
// find the denom unit asked for
const denomUnit = to.denom_units.find(
(unit) =>
unit.denom === to.address ||
unit.aliases?.find((alias) => alias === to.address)
// find the base IBC denom to match the base amount being sent
const tokenBaseDenom = getIbcDenom(
to.base,
ibcTransferInfo.channel.channel_id,
ibcTransferInfo.channel.port_id
);
// use the IBC version of the denom unit if found
const tokenDenom =
denomUnit?.aliases?.find((alias) => alias.startsWith('ibc/')) ??
to.address;
try {
await sendRequest({
token: coin(amount, tokenDenom),
token: coin(baseAmount, tokenBaseDenom),
timeout_timestamp: timeoutTimestamp,
sender: chainAddressFrom,
receiver: chainAddressTo,
Expand Down Expand Up @@ -308,7 +309,6 @@ export default function BridgeCard({
value={token}
exclusion={token}
onChange={setToken}
tokenList={tokenList}
showChain={false}
disabled
/>
Expand Down
3 changes: 2 additions & 1 deletion src/components/cards/PoolStakesTableCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import IncentivesCard from './IncentivesCard';

import { tickIndexToPrice } from '../../lib/web3/utils/ticks';
import { guessInvertedOrder } from '../../lib/web3/utils/pairs';
import { matchTokens } from '../../lib/web3/hooks/useTokens';

import './PoolsTableCard.scss';
import { useStake } from '../../pages/MyLiquidity/useStaking';
Expand Down Expand Up @@ -424,7 +425,7 @@ function StakingRow({
};
checkbox?: ReactElement;
}) {
const tokensInverted = tokenA.address !== userPosition.token0.address;
const tokensInverted = !matchTokens(tokenA, userPosition.token0);

const {
tokenAContext,
Expand Down
43 changes: 23 additions & 20 deletions src/components/cards/PoolsTableCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import TableCard, { TableCardProps } from './TableCard';

import { useSimplePrice } from '../../lib/tokenPrices';
import { useFilteredTokenList } from '../../components/TokenPicker/hooks';
import useTokens from '../../lib/web3/hooks/useTokens';
import useTokens, {
getTokenPathPart,
matchTokenByAddress,
useTokensWithIbcInfo,
} from '../../lib/web3/hooks/useTokens';

import { formatAmount } from '../../lib/utils/number';
import { Token, getDisplayDenomAmount } from '../../lib/web3/utils/tokens';
Expand Down Expand Up @@ -41,28 +45,24 @@ export default function PoolsTableCard<T extends string | number>({
}: Omit<TableCardProps<T>, 'children'> & PoolsTableCardOptions) {
const [searchValue, setSearchValue] = useState<string>('');

const tokenList = useTokens();
const tokenList = useTokensWithIbcInfo(useTokens());
const { data: tokenPairs } = useTokenPairs();
const allPairsList = useMemo<Array<[string, Token, Token]>>(() => {
const tokenListByAddress = tokenList.reduce<{ [address: string]: Token }>(
(acc, token) => {
if (token.address) {
acc[token.address] = token;
}
return acc;
},
{}
);
return tokenPairs
? tokenPairs
.map<[string, Token, Token]>(([token0, token1]) => {
// find the tokens that match our known pair token addresses
.map(([token0, token1]) => {
return [
getPairID(token0, token1),
tokenListByAddress[token0],
tokenListByAddress[token1],
tokenList.find(matchTokenByAddress(token0)),
tokenList.find(matchTokenByAddress(token1)),
];
})
.filter(([, token0, token1]) => token0 && token1)
// remove pairs with unfound tokens
.filter<[string, Token, Token]>(
(tokenPair): tokenPair is [string, Token, Token] =>
tokenPair.every(Boolean)
)
: [];
}, [tokenList, tokenPairs]);

Expand Down Expand Up @@ -239,7 +239,7 @@ export function MyPoolsTableCard<T extends string | number>({
}: Omit<TableCardProps<T>, 'children'> & PoolsTableCardOptions) {
const [searchValue, setSearchValue] = useState<string>('');

const tokenList = useTokens();
const tokenList = useTokensWithIbcInfo(useTokens());

const userPositionsShareValues = useUserPositionsShareValues();

Expand All @@ -264,8 +264,8 @@ export function MyPoolsTableCard<T extends string | number>({
const { token0: token0Address, token1: token1Address } =
userPosition.deposit.pairID;
const pairID = getPairID(token0Address, token1Address);
const token0 = tokenList.find((token) => token.address === token0Address);
const token1 = tokenList.find((token) => token.address === token1Address);
const token0 = tokenList.find(matchTokenByAddress(token0Address));
const token1 = tokenList.find(matchTokenByAddress(token1Address));
if (pairID && token0 && token1) {
map[pairID] = map[pairID] || { token0, token1, userPositions: [] };
map[pairID].userPositions.push(userPosition);
Expand Down Expand Up @@ -373,8 +373,11 @@ const defaultActions: Actions = {
manage: {
title: 'Manage',
className: 'button-light m-0',
action: ({ navigate, token0, token1 }) =>
navigate(`/pools/${token0.symbol}/${token1.symbol}/edit`),
action: ({ navigate, token0, token1 }) => {
return navigate(
`/pools/${getTokenPathPart(token0)}/${getTokenPathPart(token1)}/edit`
);
},
},
};

Expand Down
16 changes: 13 additions & 3 deletions src/components/stats/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
TimeSeriesPage,
TimeSeriesRow,
TokenValuePair,
getIndexerTokenPathPart,
getLastDataChanges,
getLastDataValues,
} from './utils';
Expand Down Expand Up @@ -108,7 +109,10 @@ function useStatTokenValue(

// TVL
function getStatTvlPath(tokenA: Token, tokenB: Token) {
return `stats/tvl/${tokenA.address}/${tokenB.address}`;
return `stats/tvl/${[
getIndexerTokenPathPart(tokenA),
getIndexerTokenPathPart(tokenB),
].join('/')}`;
}
function getStatTvlValues([amountA, amountB]: number[]): number[] {
return [amountA, amountB];
Expand All @@ -123,7 +127,10 @@ export function useStatComposition(tokenA: Token, tokenB: Token) {

// volume and fees
function getStatVolumePath(tokenA: Token, tokenB: Token) {
return `stats/volume/${tokenA.address}/${tokenB.address}`;
return `stats/volume/${[
getIndexerTokenPathPart(tokenA),
getIndexerTokenPathPart(tokenB),
].join('/')}`;
}
function getStatVolumeValues([amountA, amountB]: number[]): number[] {
return [amountA, amountB];
Expand All @@ -145,7 +152,10 @@ export function useStatFee(tokenA: Token, tokenB: Token) {

// volatility
function getStatVolatilityPath(tokenA: Token, tokenB: Token) {
return `stats/volatility/${tokenA.address}/${tokenB.address}`;
return `stats/volatility/${[
getIndexerTokenPathPart(tokenA),
getIndexerTokenPathPart(tokenB),
].join('/')}`;
}
function getStatVolatilityValues([value]: number[]): number[] {
return [value];
Expand Down
6 changes: 6 additions & 0 deletions src/components/stats/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { formatCurrency, formatPercentage } from '../../lib/utils/number';
import { Token } from '../../lib/web3/utils/tokens';

// format a URL path part to reference a token on the indexer
export function getIndexerTokenPathPart(token: Token) {
return encodeURIComponent(token.address);
}

export type TimeSeriesRow = [unixTime: number, values: number[]];
export type TimeSeriesPage = {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/tokenPrices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ const { REACT_APP__DEV_TOKEN_DENOMS } = process.env;

const baseAPI = 'https://api.coingecko.com/api/v3';

const devTokens = JSON.parse(REACT_APP__DEV_TOKEN_DENOMS || '[]') as string[];
// identify dev tokens using a specific dev chain name
function isDevToken(token?: Token) {
return !!token && devTokens.includes(token.base);
return !!token && token.chain.chain_name === '___dev___';
}

class FetchError extends Error {
Expand Down
Loading

0 comments on commit d6f7a67

Please sign in to comment.