From 711ab0f37b9ba2df8837f0cd1bb9285d98412be4 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:35:14 +0200 Subject: [PATCH 01/13] Add assets to ChainInfo and ChainSelect --- components/chainSelect/ChainSelect.tsx | 36 +++++++++++++++++++------- types/index.ts | 1 + 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/components/chainSelect/ChainSelect.tsx b/components/chainSelect/ChainSelect.tsx index 91dce56..138a6b7 100644 --- a/components/chainSelect/ChainSelect.tsx +++ b/components/chainSelect/ChainSelect.tsx @@ -62,6 +62,7 @@ const ChainSelect = () => { const [tempDisplayDenomExponent, setDisplayDenomExponent] = useState( state.chain.displayDenomExponent, ); + const [tempAssets, setAssets] = useState(state.chain.assets); const [tempGasPrice, setGasPrice] = useState(state.chain.gasPrice); const [tempChainName, setChainName] = useState(state.chain.chainDisplayName); const [tempRegistryName, setRegistryName] = useState(state.chain.registryName); @@ -110,6 +111,7 @@ const ChainSelect = () => { setDenom(state.chain.denom); setDisplayDenom(state.chain.displayDenom); setDisplayDenomExponent(state.chain.displayDenomExponent); + setAssets(state.chain.assets); setGasPrice(state.chain.gasPrice); setChainName(state.chain.chainDisplayName); setExplorerLink(state.chain.explorerLink); @@ -133,25 +135,31 @@ const ChainSelect = () => { try { const chainData = await getChainFromRegistry(chainOption.path); - const assets = await getAssetsFromRegistry(chainOption.path); - const firstAsset = assets[0]; + const registryAssets = await getAssetsFromRegistry(chainOption.path); + const firstAsset = registryAssets[0]; const nodeAddress = await getNodeFromArray(chainData.apis.rpc); const explorerLink = getExplorerFromArray(chainData.explorers); - const denom = firstAsset.base || ""; - const displayDenom = firstAsset.symbol || ""; + const firstAssetDenom = firstAsset.base; + const displayDenom = firstAsset.symbol; + + const assets = registryAssets.flatMap(({ denom_units }) => + denom_units.map(({ denom, exponent }) => ({ denom, exponent })), + ); const displayUnit = firstAsset.denom_units.find((u) => u.denom == firstAsset.display); const displayDenomExponent = displayUnit?.exponent ?? 6; - const feeToken = chainData.fees.fee_tokens.find((token) => token.denom == denom) ?? { denom }; + const feeToken = chainData.fees.fee_tokens.find( + (token) => token.denom == firstAssetDenom, + ) ?? { denom: firstAssetDenom }; const gasPrice = feeToken.average_gas_price ?? feeToken.low_gas_price ?? feeToken.high_gas_price ?? feeToken.fixed_min_gas_price ?? 0.03; - const formattedGasPrice = firstAsset ? `${gasPrice}${denom}` : ""; + const formattedGasPrice = firstAsset ? `${gasPrice}${firstAssetDenom}` : ""; // change app state dispatch({ @@ -163,10 +171,11 @@ const ChainSelect = () => { chainDisplayName: chainData.pretty_name, nodeAddress, explorerLink, - denom, + denom: firstAssetDenom, displayDenom, displayDenomExponent, gasPrice: formattedGasPrice, + assets, }, }); @@ -253,6 +262,7 @@ const ChainSelect = () => { denom: tempDenom, displayDenom: tempDisplayDenom, displayDenomExponent: tempDisplayDenomExponent, + assets: tempAssets, gasPrice: tempGasPrice, chainId: tempChainId, chainDisplayName: tempChainName, @@ -367,14 +377,22 @@ const ChainSelect = () => { } label="Denom Exponent" /> + ) => + setAssets(JSON.parse(e.target.value)) + } + label="Assets" + /> + +
) => setGasPrice(e.target.value)} label="Gas Price" /> -
-
Date: Thu, 22 Jun 2023 09:35:34 +0200 Subject: [PATCH 02/13] Serialize array params like assets --- context/AppContext.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/context/AppContext.tsx b/context/AppContext.tsx index f58e2ca..689b5ab 100644 --- a/context/AppContext.tsx +++ b/context/AppContext.tsx @@ -1,7 +1,6 @@ -import React, { useEffect, createContext, useContext, useReducer } from "react"; - -import { AppReducer, ChangeChainAction, initialState } from "./AppReducer"; +import { createContext, useContext, useEffect, useReducer } from "react"; import { ChainInfo } from "../types"; +import { AppReducer, ChangeChainAction, initialState } from "./AppReducer"; export interface AppContextType { chain: ChainInfo; @@ -23,6 +22,7 @@ function getChainInfoFromUrl(): ChainInfo { decodeURIComponent(params.get("displayDenomExponent") || ""), 10, ), + assets: JSON.parse(decodeURIComponent(params.get("assets") || "{}")), gasPrice: decodeURIComponent(params.get("gasPrice") || ""), chainId: decodeURIComponent(params.get("chainId") || ""), chainDisplayName: decodeURIComponent(params.get("chainDisplayName") || ""), @@ -37,11 +37,13 @@ function getChainInfoFromUrl(): ChainInfo { function setChainInfoParams(chainInfo: ChainInfo) { const params = new URLSearchParams(); - const keys = Object.keys(chainInfo) as Array; - - keys.forEach((value: keyof ChainInfo) => { - params.set(value, encodeURIComponent(chainInfo[value] || "")); - }); + for (const [key, value] of Object.entries(chainInfo)) { + if (Array.isArray(value)) { + params.set(key, encodeURIComponent(JSON.stringify(value))); + } else { + params.set(key, encodeURIComponent(value ?? "")); + } + } window.history.replaceState({}, "", `${location.pathname}?${params}`); } From ae66458d39a639f148435f6380bb4ec9ec1d0f2c Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:36:04 +0200 Subject: [PATCH 03/13] Add select and custom denom in MsgSendForm --- .../CreateTxForm/MsgForm/MsgSendForm.tsx | 153 ++++++++++++++---- 1 file changed, 121 insertions(+), 32 deletions(-) diff --git a/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx index dda14ff..875461b 100644 --- a/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx +++ b/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx @@ -5,10 +5,39 @@ import { useEffect, useState } from "react"; import { MsgGetter } from ".."; import { useAppContext } from "../../../../context/AppContext"; import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers"; +import { ChainInfo } from "../../../../types"; import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg"; import Input from "../../../inputs/Input"; +import Select from "../../../inputs/Select"; import StackableContainer from "../../../layout/StackableContainer"; +const getDenomOptions = (assets: ChainInfo["assets"]) => { + const customDenomOption = { label: "Custom (enter denom below)", value: "custom" }; + if (!assets?.length) { + return [customDenomOption]; + } + + const filteredAssets = assets.filter(({ denom: denomToFilter }) => { + if (denomToFilter.startsWith("u")) { + const foundMacroDenom = assets.find( + ({ denom }) => denom.toLowerCase() === denomToFilter.slice(1).toLowerCase(), + ); + if (foundMacroDenom) { + return false; + } + } + + return true; + }); + + const denomOptions = filteredAssets.map(({ denom }) => ({ + label: denom.toUpperCase(), + value: denom, + })); + + return [...denomOptions, customDenomOption]; +}; + interface MsgSendFormProps { readonly fromAddress: string; readonly setMsgGetter: (msgGetter: MsgGetter) => void; @@ -19,60 +48,85 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) const { state } = useAppContext(); assert(state.chain.addressPrefix, "addressPrefix missing"); + const denomOptions = getDenomOptions(state.chain.assets); + const [toAddress, setToAddress] = useState(""); + const [selectedDenom, setSelectedDenom] = useState(denomOptions[0]); + const [customDenom, setCustomDenom] = useState(""); const [amount, setAmount] = useState("0"); const [toAddressError, setToAddressError] = useState(""); + const [customDenomError, setCustomDenomError] = useState(""); const [amountError, setAmountError] = useState(""); useEffect(() => { - try { - assert(state.chain.denom, "denom missing"); + assert(state.chain.denom, "denom missing"); - setToAddressError(""); - setAmountError(""); + setToAddressError(""); + setCustomDenomError(""); + setAmountError(""); - const isMsgValid = (): boolean => { - assert(state.chain.addressPrefix, "addressPrefix missing"); + const isMsgValid = (): boolean => { + assert(state.chain.addressPrefix, "addressPrefix missing"); - const addressErrorMsg = checkAddress(toAddress, state.chain.addressPrefix); - if (addressErrorMsg) { - setToAddressError( - `Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`, - ); - return false; - } + const addressErrorMsg = checkAddress(toAddress, state.chain.addressPrefix); + if (addressErrorMsg) { + setToAddressError(`Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`); + return false; + } - if (!amount || Number(amount) <= 0) { - setAmountError("Amount must be greater than 0"); - return false; - } + if (selectedDenom.value === "custom" && !customDenom) { + setCustomDenomError("Custom denom must be set because of selection above"); + return false; + } - return true; - }; + if (!amount || Number(amount) <= 0) { + setAmountError("Amount must be greater than 0"); + return false; + } - const amountInAtomics = amount - ? Decimal.fromUserInput(amount, Number(state.chain.displayDenomExponent)).atomics - : "0"; + if (selectedDenom.value === "custom" && !Number.isInteger(amount)) { + setAmountError("Amount cannot be decimal for custom denom"); + return false; + } - const msgValue = MsgCodecs[MsgTypeUrls.Send].fromPartial({ - fromAddress, - toAddress, - amount: [{ amount: amountInAtomics, denom: state.chain.denom }], - }); + return true; + }; - const msg: MsgSendEncodeObject = { typeUrl: MsgTypeUrls.Send, value: msgValue }; + const denom = ( + selectedDenom.value === "custom" ? customDenom : selectedDenom.value + ).toLowerCase(); + const exponent = + state.chain.assets?.find(({ denom: currentDenom }) => currentDenom.toLowerCase() === denom) + ?.exponent ?? 0; - setMsgGetter({ isMsgValid, msg }); - } catch {} + const amountInAtomics = (() => { + try { + return Decimal.fromUserInput(amount, exponent).atomics; + } catch { + return "0"; + } + })(); + + const msgValue = MsgCodecs[MsgTypeUrls.Send].fromPartial({ + fromAddress, + toAddress, + amount: [{ denom, amount: amountInAtomics }], + }); + + const msg: MsgSendEncodeObject = { typeUrl: MsgTypeUrls.Send, value: msgValue }; + + setMsgGetter({ isMsgValid, msg }); }, [ amount, + customDenom, fromAddress, + selectedDenom.value, setMsgGetter, state.chain.addressPrefix, + state.chain.assets, state.chain.chainId, state.chain.denom, - state.chain.displayDenomExponent, toAddress, ]); @@ -92,10 +146,36 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`} />
+
+ + setCustomDenom(target.value)} + placeholder="Select Custom denom above" + disabled={selectedDenom.value !== "custom"} + error={customDenomError} + /> +
setAmount(target.value)} @@ -106,6 +186,15 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) .form-item { margin-top: 1.5em; } + .form-item label { + font-style: italic; + font-size: 12px; + } + .form-select { + display: flex; + flex-direction: column; + gap: 0.8em; + } button.remove { background: rgba(255, 255, 255, 0.2); width: 30px; From a1cb013dd3643d7506e5b45cb81560e24f776f7f Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:36:21 +0200 Subject: [PATCH 04/13] Tweak printableCoin to check assets --- lib/displayHelpers.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/displayHelpers.ts b/lib/displayHelpers.ts index 34466b0..e7db53d 100644 --- a/lib/displayHelpers.ts +++ b/lib/displayHelpers.ts @@ -37,18 +37,23 @@ const thinSpace = "\u202F"; const printableCoin = (coin: Coin, chainInfo: ChainInfo) => { if (!coin.amount || !coin.denom) return ""; - // The display denom from configuration - if (coin.denom === chainInfo.denom) { - const exponent = Number(chainInfo.displayDenomExponent); - const value = Decimal.fromAtomics(coin.amount ?? "0", exponent).toString(); - const ticker = chainInfo.displayDenom; - return value + thinSpace + ticker; - } + // Check if denom is in assets + const foundAsset = chainInfo.assets?.find( + (asset) => asset.denom.toLowerCase() === coin.denom.toLowerCase(), + ); - // Auto-convert leading "u"s - if (coin.denom.startsWith("u")) { - const value = Decimal.fromAtomics(coin.amount ?? "0", 6).toString(); - const ticker = coin.denom.slice(1).toUpperCase(); + // Check if denom starting with "u" has a macrodenom in assets + const foundMacrodenom = coin.denom.startsWith("u") + ? chainInfo.assets?.find( + (asset) => asset.denom.toLowerCase() === coin.denom.slice(1).toLowerCase(), + ) + : undefined; + + const assetToPrint = foundMacrodenom ?? foundAsset; + + if (assetToPrint) { + const value = Decimal.fromAtomics(coin.amount ?? "0", assetToPrint.exponent).toString(); + const ticker = assetToPrint.denom.toUpperCase(); return value + thinSpace + ticker; } @@ -57,12 +62,12 @@ const printableCoin = (coin: Coin, chainInfo: ChainInfo) => { const value = coin.amount; const hash = coin.denom.slice(4); const ellidedHash = ellideMiddle(hash, 11); - const ticker = `ibc/${ellidedHash}`; + const ticker = `ibc/${ellidedHash}`.toUpperCase(); return value + thinSpace + ticker; } // Fallback to plain coin display - return coin.amount + thinSpace + coin.denom; + return coin.amount + thinSpace + coin.denom.toUpperCase(); }; const printableCoins = (coins: readonly Coin[], chainInfo: ChainInfo) => From 06aa02bca9f11307dc8501aa6e8ccc5618ec2317 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:39:49 +0200 Subject: [PATCH 05/13] Make custom denom placeholder more helpful --- components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx index 875461b..1bf9166 100644 --- a/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx +++ b/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx @@ -167,7 +167,9 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) name="custom-denom" value={customDenom} onChange={({ target }) => setCustomDenom(target.value)} - placeholder="Select Custom denom above" + placeholder={ + selectedDenom.value === "custom" ? "Enter custom denom" : "Select Custom denom above" + } disabled={selectedDenom.value !== "custom"} error={customDenomError} /> From eca93d23cd6732f85fec175531093da84cf40216 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 23 Jun 2023 23:00:42 +0200 Subject: [PATCH 06/13] Fix registry type --- components/chainSelect/chainregistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/chainSelect/chainregistry.ts b/components/chainSelect/chainregistry.ts index 1714f4c..a8f068d 100644 --- a/components/chainSelect/chainregistry.ts +++ b/components/chainSelect/chainregistry.ts @@ -46,7 +46,7 @@ export interface RegistryChainResponse { export interface RegistryAssetDenomUnit { denom: string; exponent: number; - aliases: string[]; + aliases?: string[]; } /** From 9cab79269edca70465e1c9e3aacb30c59c5e2c62 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 23 Jun 2023 23:01:01 +0200 Subject: [PATCH 07/13] Add assets to ChainInfo --- components/chainSelect/ChainSelect.tsx | 7 +------ types/index.ts | 3 ++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/components/chainSelect/ChainSelect.tsx b/components/chainSelect/ChainSelect.tsx index 138a6b7..d9aa6ba 100644 --- a/components/chainSelect/ChainSelect.tsx +++ b/components/chainSelect/ChainSelect.tsx @@ -142,11 +142,6 @@ const ChainSelect = () => { const explorerLink = getExplorerFromArray(chainData.explorers); const firstAssetDenom = firstAsset.base; const displayDenom = firstAsset.symbol; - - const assets = registryAssets.flatMap(({ denom_units }) => - denom_units.map(({ denom, exponent }) => ({ denom, exponent })), - ); - const displayUnit = firstAsset.denom_units.find((u) => u.denom == firstAsset.display); const displayDenomExponent = displayUnit?.exponent ?? 6; @@ -175,7 +170,7 @@ const ChainSelect = () => { displayDenom, displayDenomExponent, gasPrice: formattedGasPrice, - assets, + assets: registryAssets, }, }); diff --git a/types/index.ts b/types/index.ts index 75b0804..b01fb4d 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,5 +1,6 @@ import { StdFee } from "@cosmjs/amino"; import { EncodeObject } from "@cosmjs/proto-signing"; +import { RegistryAsset } from "../components/chainSelect/chainregistry"; declare global { interface Window { @@ -51,7 +52,7 @@ export interface ChainInfo { denom?: string; displayDenom?: string; displayDenomExponent?: number; - assets?: readonly { readonly denom: string; readonly exponent: number }[]; + assets?: readonly RegistryAsset[]; gasPrice?: string; chainId?: string; chainDisplayName?: string; From 312bfb5ba25ac63e507b65de7500632fc4ea4eb8 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 23 Jun 2023 23:02:19 +0200 Subject: [PATCH 08/13] Fix printableCoin for microdenoms --- lib/displayHelpers.ts | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/lib/displayHelpers.ts b/lib/displayHelpers.ts index e7db53d..a635ead 100644 --- a/lib/displayHelpers.ts +++ b/lib/displayHelpers.ts @@ -37,26 +37,6 @@ const thinSpace = "\u202F"; const printableCoin = (coin: Coin, chainInfo: ChainInfo) => { if (!coin.amount || !coin.denom) return ""; - // Check if denom is in assets - const foundAsset = chainInfo.assets?.find( - (asset) => asset.denom.toLowerCase() === coin.denom.toLowerCase(), - ); - - // Check if denom starting with "u" has a macrodenom in assets - const foundMacrodenom = coin.denom.startsWith("u") - ? chainInfo.assets?.find( - (asset) => asset.denom.toLowerCase() === coin.denom.slice(1).toLowerCase(), - ) - : undefined; - - const assetToPrint = foundMacrodenom ?? foundAsset; - - if (assetToPrint) { - const value = Decimal.fromAtomics(coin.amount ?? "0", assetToPrint.exponent).toString(); - const ticker = assetToPrint.denom.toUpperCase(); - return value + thinSpace + ticker; - } - // Ellide IBC tokens if (coin.denom.startsWith("ibc/")) { const value = coin.amount; @@ -66,6 +46,19 @@ const printableCoin = (coin: Coin, chainInfo: ChainInfo) => { return value + thinSpace + ticker; } + const foundAsset = chainInfo.assets?.find( + (asset) => coin.denom === asset.symbol || coin.denom === asset.base, + ); + const foundExponent = foundAsset?.denom_units.find( + (unit) => unit.denom === foundAsset.symbol.toLowerCase(), + )?.exponent; + + if (foundExponent) { + const value = Decimal.fromAtomics(coin.amount, foundExponent).toString(); + const ticker = foundAsset.symbol; + return value + thinSpace + ticker; + } + // Fallback to plain coin display return coin.amount + thinSpace + coin.denom.toUpperCase(); }; From 55604efe0e5fce9554e9a95dac8cb916faac10e4 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 23 Jun 2023 23:02:35 +0200 Subject: [PATCH 09/13] Make printableCoin tests pass --- lib/displayHelpers.spec.ts | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/displayHelpers.spec.ts b/lib/displayHelpers.spec.ts index 385c1b0..25e00e9 100644 --- a/lib/displayHelpers.spec.ts +++ b/lib/displayHelpers.spec.ts @@ -12,13 +12,35 @@ const testChainInfo: ChainInfo = { denom: "ujunox", displayDenom: "JUNOX", displayDenomExponent: 6, + assets: [{ + description: "The native token of JUNO Chain", + denom_units: [ + { + denom: "ujunox", + exponent: 0 + }, + { + denom: "junox", + exponent: 6 + } + ], + base: "ujunox", + name: "Juno Testnet", + display: "junox", + symbol: "JUNOX", + logo_URIs: { + png: "https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.png", + svg: "https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.svg" + }, + coingecko_id: "juno-network" + }], gasPrice: "0.04ujunox", }; const emptyCoin: Coin = { amount: "", denom: "" }; const coinInChain: Coin = { amount: "1000", denom: "ujunox" }; const coinNotInChainLeading: Coin = { amount: "20000", denom: "utest" }; -const coinNotInChainNotLeading: Coin = { amount: "300000", denom: "test" }; +const coinNotInChainNotLeading: Coin = { amount: "300000", denom: "TEST" }; const ibcCoin: Coin = { amount: "4000000", denom: "ibc/c4cff46fd6de35ca4cf4ce031e643c8fdc9ba4b99ae598e9b0ed98fe3a2319f9", @@ -34,17 +56,17 @@ describe("printableCoin", () => { }); it("works with coin not in ChainInfo with leading 'u'", () => { - expect(printableCoin(coinNotInChainLeading, testChainInfo)).toEqual(`0.02${thinSpace}TEST`); + expect(printableCoin(coinNotInChainLeading, testChainInfo)).toEqual(`20000${thinSpace}UTEST`); }); it("works with coin not in ChainInfo without leading 'u'", () => { expect(printableCoin(coinNotInChainNotLeading, testChainInfo)).toEqual( - `300000${thinSpace}test`, + `300000${thinSpace}TEST`, ); }); it("works with IBC coin", () => { - expect(printableCoin(ibcCoin, testChainInfo)).toEqual(`4000000${thinSpace}ibc/c4cff…319f9`); + expect(printableCoin(ibcCoin, testChainInfo)).toEqual(`4000000${thinSpace}IBC/C4CFF…319F9`); }); }); @@ -67,7 +89,7 @@ describe("printableCoins", () => { ]; expect(printableCoins(coins, testChainInfo)).toEqual( - `0.001${thinSpace}JUNOX, 0.02${thinSpace}TEST, 300000${thinSpace}test, 4000000 ibc/c4cff…319f9`, + `0.001${thinSpace}JUNOX, 20000${thinSpace}UTEST, 300000${thinSpace}TEST, 4000000 IBC/C4CFF…319F9`, ); }); }); From 996b65ae08c4407cd7d89fa1fc1332fc7ca54a19 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 23 Jun 2023 23:03:14 +0200 Subject: [PATCH 10/13] Fix amountInAtomics. Simplify getDenomOptions --- .../CreateTxForm/MsgForm/MsgSendForm.tsx | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx index 1bf9166..6b78740 100644 --- a/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx +++ b/components/forms/CreateTxForm/MsgForm/MsgSendForm.tsx @@ -11,31 +11,14 @@ import Input from "../../../inputs/Input"; import Select from "../../../inputs/Select"; import StackableContainer from "../../../layout/StackableContainer"; +const customDenomOption = { label: "Custom (enter denom below)", value: "custom" } as const; + const getDenomOptions = (assets: ChainInfo["assets"]) => { - const customDenomOption = { label: "Custom (enter denom below)", value: "custom" }; if (!assets?.length) { return [customDenomOption]; } - const filteredAssets = assets.filter(({ denom: denomToFilter }) => { - if (denomToFilter.startsWith("u")) { - const foundMacroDenom = assets.find( - ({ denom }) => denom.toLowerCase() === denomToFilter.slice(1).toLowerCase(), - ); - if (foundMacroDenom) { - return false; - } - } - - return true; - }); - - const denomOptions = filteredAssets.map(({ denom }) => ({ - label: denom.toUpperCase(), - value: denom, - })); - - return [...denomOptions, customDenomOption]; + return [...assets.map((asset) => ({ label: asset.symbol, value: asset })), customDenomOption]; }; interface MsgSendFormProps { @@ -75,7 +58,7 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) return false; } - if (selectedDenom.value === "custom" && !customDenom) { + if (selectedDenom.value === customDenomOption.value && !customDenom) { setCustomDenomError("Custom denom must be set because of selection above"); return false; } @@ -85,7 +68,7 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) return false; } - if (selectedDenom.value === "custom" && !Number.isInteger(amount)) { + if (selectedDenom.value === customDenomOption.value && !Number.isInteger(Number(amount))) { setAmountError("Amount cannot be decimal for custom denom"); return false; } @@ -93,15 +76,20 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) return true; }; - const denom = ( - selectedDenom.value === "custom" ? customDenom : selectedDenom.value - ).toLowerCase(); - const exponent = - state.chain.assets?.find(({ denom: currentDenom }) => currentDenom.toLowerCase() === denom) - ?.exponent ?? 0; + const denom = + selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol; const amountInAtomics = (() => { try { + if (selectedDenom.value === customDenomOption.value) { + return Decimal.fromUserInput(amount, 0).atomics; + } + + const foundAsset = state.chain.assets?.find((asset) => asset.symbol === denom); + const exponent = + foundAsset?.denom_units.find((unit) => unit.denom === foundAsset.symbol.toLowerCase()) + ?.exponent ?? 0; + return Decimal.fromUserInput(amount, exponent).atomics; } catch { return "0"; @@ -155,7 +143,7 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) value={selectedDenom} onChange={(option: (typeof denomOptions)[number]) => { setSelectedDenom(option); - if (option.value !== "custom") { + if (option.value !== customDenomOption.value) { setCustomDenom(""); } }} @@ -168,9 +156,11 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) value={customDenom} onChange={({ target }) => setCustomDenom(target.value)} placeholder={ - selectedDenom.value === "custom" ? "Enter custom denom" : "Select Custom denom above" + selectedDenom.value === customDenomOption.value + ? "Enter custom denom" + : "Select Custom denom above" } - disabled={selectedDenom.value !== "custom"} + disabled={selectedDenom.value !== customDenomOption.value} error={customDenomError} />
From b8fa36177916d45ee9f92e84def974ab4acfa7b1 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:59:01 +0200 Subject: [PATCH 11/13] Assert min assets --- components/chainSelect/ChainSelect.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/chainSelect/ChainSelect.tsx b/components/chainSelect/ChainSelect.tsx index d9aa6ba..93abf97 100644 --- a/components/chainSelect/ChainSelect.tsx +++ b/components/chainSelect/ChainSelect.tsx @@ -136,6 +136,7 @@ const ChainSelect = () => { try { const chainData = await getChainFromRegistry(chainOption.path); const registryAssets = await getAssetsFromRegistry(chainOption.path); + assert(registryAssets.length >= 1, "No assets found in registry"); const firstAsset = registryAssets[0]; const nodeAddress = await getNodeFromArray(chainData.apis.rpc); From 0f16dc6652461757be3666ba785a6307be6ba52f Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:59:37 +0200 Subject: [PATCH 12/13] Apply uppercase to ibc hash only --- lib/displayHelpers.spec.ts | 4 ++-- lib/displayHelpers.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/displayHelpers.spec.ts b/lib/displayHelpers.spec.ts index 25e00e9..3766e65 100644 --- a/lib/displayHelpers.spec.ts +++ b/lib/displayHelpers.spec.ts @@ -66,7 +66,7 @@ describe("printableCoin", () => { }); it("works with IBC coin", () => { - expect(printableCoin(ibcCoin, testChainInfo)).toEqual(`4000000${thinSpace}IBC/C4CFF…319F9`); + expect(printableCoin(ibcCoin, testChainInfo)).toEqual(`4000000${thinSpace}ibc/C4CFF…319F9`); }); }); @@ -89,7 +89,7 @@ describe("printableCoins", () => { ]; expect(printableCoins(coins, testChainInfo)).toEqual( - `0.001${thinSpace}JUNOX, 20000${thinSpace}UTEST, 300000${thinSpace}TEST, 4000000 IBC/C4CFF…319F9`, + `0.001${thinSpace}JUNOX, 20000${thinSpace}UTEST, 300000${thinSpace}TEST, 4000000 ibc/C4CFF…319F9`, ); }); }); diff --git a/lib/displayHelpers.ts b/lib/displayHelpers.ts index a635ead..100bf61 100644 --- a/lib/displayHelpers.ts +++ b/lib/displayHelpers.ts @@ -42,7 +42,7 @@ const printableCoin = (coin: Coin, chainInfo: ChainInfo) => { const value = coin.amount; const hash = coin.denom.slice(4); const ellidedHash = ellideMiddle(hash, 11); - const ticker = `ibc/${ellidedHash}`.toUpperCase(); + const ticker = `ibc/${ellidedHash.toUpperCase()}`; return value + thinSpace + ticker; } From b6bbcd122947a2dda2976da4205a2ab1fad48f2c Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:59:43 +0200 Subject: [PATCH 13/13] Apply prettier --- lib/displayHelpers.spec.ts | 42 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/displayHelpers.spec.ts b/lib/displayHelpers.spec.ts index 3766e65..9f04922 100644 --- a/lib/displayHelpers.spec.ts +++ b/lib/displayHelpers.spec.ts @@ -12,28 +12,30 @@ const testChainInfo: ChainInfo = { denom: "ujunox", displayDenom: "JUNOX", displayDenomExponent: 6, - assets: [{ - description: "The native token of JUNO Chain", - denom_units: [ - { - denom: "ujunox", - exponent: 0 + assets: [ + { + description: "The native token of JUNO Chain", + denom_units: [ + { + denom: "ujunox", + exponent: 0, + }, + { + denom: "junox", + exponent: 6, + }, + ], + base: "ujunox", + name: "Juno Testnet", + display: "junox", + symbol: "JUNOX", + logo_URIs: { + png: "https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.png", + svg: "https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.svg", }, - { - denom: "junox", - exponent: 6 - } - ], - base: "ujunox", - name: "Juno Testnet", - display: "junox", - symbol: "JUNOX", - logo_URIs: { - png: "https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.png", - svg: "https://raw.githubusercontent.com/cosmos/chain-registry/master/testnets/junotestnet/images/juno.svg" + coingecko_id: "juno-network", }, - coingecko_id: "juno-network" - }], + ], gasPrice: "0.04ujunox", };