diff --git a/spaceward/src/components/ConnectWallet.tsx b/spaceward/src/components/ConnectWallet.tsx index 2e597376d..d2bc85618 100644 --- a/spaceward/src/components/ConnectWallet.tsx +++ b/spaceward/src/components/ConnectWallet.tsx @@ -34,6 +34,7 @@ export function ConnectWallet() { const { balance } = useAsset("award"); const wardAmount = BigInt(balance?.amount || "0"); const ward = bigintToFixed(wardAmount, { + ceil: true, decimals: 18, display: 2, }); diff --git a/spaceward/src/components/ui/icons-crypto.tsx b/spaceward/src/components/ui/icons-crypto.tsx index fd8dde019..6c7c222d1 100644 --- a/spaceward/src/components/ui/icons-crypto.tsx +++ b/spaceward/src/components/ui/icons-crypto.tsx @@ -292,9 +292,9 @@ export const NetworkIcons: Record< gradientUnits="userSpaceOnUse" gradientTransform="translate(22.1185 3.26671) scale(7.79125 7.93393)" > - - - + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/spaceward/src/features/actions/util.ts b/spaceward/src/features/actions/util.ts index 6842b72e9..9122ea29c 100644 --- a/spaceward/src/features/actions/util.ts +++ b/spaceward/src/features/actions/util.ts @@ -269,17 +269,16 @@ export const handleEth = async ({ throw new Error("walletconnect not initialized"); } - return await w - .respondSessionRequest({ - topic: walletConnectTopic, - response: { - jsonrpc: "2.0", - id: walletConnectRequestId, - result: res.hash, - }, - }) - // fixme - .then(() => true); + await w.respondSessionRequest({ + topic: walletConnectTopic, + response: { + jsonrpc: "2.0", + id: walletConnectRequestId, + result: res.hash, + }, + }); + + return true; } return provider.waitForTransaction(res.hash).then(() => { @@ -401,6 +400,7 @@ export const handleCosmos = async ({ } const client = await StargateClient.connect(rpc); + const res = await client.broadcastTx( cosmos.tx.v1beta1.TxRaw.encode(txRaw).finish(), ); @@ -409,5 +409,9 @@ export const handleCosmos = async ({ throw new Error("broadcast failed"); } + queryClient.invalidateQueries({ + queryKey: getBalanceQueryKey("cosmos", chainName, "").slice(0, -1), + }); + return true; }; diff --git a/spaceward/src/features/assets/hooks.ts b/spaceward/src/features/assets/hooks.ts index 93fc8cd38..cff9dcba0 100644 --- a/spaceward/src/features/assets/hooks.ts +++ b/spaceward/src/features/assets/hooks.ts @@ -1,81 +1,21 @@ import { walletContext } from "@cosmos-kit/react-lite"; -import type { ExtendedHttpEndpoint, WalletManager } from "@cosmos-kit/core"; import { AddressType } from "@wardenprotocol/wardenjs/codegen/warden/warden/v1beta3/key"; -import { cosmos } from "@wardenprotocol/wardenjs"; import { useQueries, useQuery } from "@tanstack/react-query"; -import { useContext, useEffect, useMemo, useState } from "react"; -import { COSMOS_CHAINS } from "@/config/tokens"; +import { useContext, useMemo } from "react"; import { useQueryHooks } from "@/hooks/useClient"; import { balancesQueryCosmos, balancesQueryEth, fiatPricesQuery, + queryCosmosClients, } from "./queries"; -import type { CosmosQueryClient, PriceMapSlinky } from "./types"; +import type { PriceMapSlinky } from "./types"; const DERIVE_ADDRESSES = [ AddressType.ADDRESS_TYPE_ETHEREUM, AddressType.ADDRESS_TYPE_OSMOSIS, ]; -const queryCosmosClients = (walletManager: WalletManager) => { - const rpcClients: Record = {}; - const rpcRetry: Record = {}; - - return { - queryKey: ["cosmos", "rpcClients"], - queryFn: async () => { - const clients: [CosmosQueryClient, string][] = []; - - for (let i = 0; i < COSMOS_CHAINS.length; i++) { - const { chainName, rpc } = COSMOS_CHAINS[i]; - let client = rpcClients[chainName]; - - if (client) { - // todo implement client health check - clients.push([client, chainName]); - continue; - } - - let endpoint: ExtendedHttpEndpoint | string; - - if (rpc) { - const retry = rpcRetry[chainName] ?? 0; - endpoint = rpc[retry % rpc.length]; - rpcRetry[chainName] = retry + 1; - } else { - const repo = walletManager.getWalletRepo(chainName); - repo.activate(); - - try { - endpoint = await repo.getRpcEndpoint(); - } catch (e) { - console.error(e); - endpoint = `https://rpc.cosmos.directory/${chainName}`; - } - } - - try { - const client = - await cosmos.ClientFactory.createRPCQueryClient({ - rpcEndpoint: - typeof endpoint === "string" - ? endpoint - : endpoint.url, - }); - - clients.push([client, chainName]); - } catch (e) { - console.error(e); - continue; - } - } - - return clients; - }, - } as const; -}; - export const useAssetQueries = (spaceId?: string | null) => { const { walletManager } = useContext(walletContext); const { isReady, useKeysBySpaceId, slinky } = useQueryHooks(); diff --git a/spaceward/src/features/assets/queries.ts b/spaceward/src/features/assets/queries.ts index c2457b550..958afb33a 100644 --- a/spaceward/src/features/assets/queries.ts +++ b/spaceward/src/features/assets/queries.ts @@ -1,12 +1,15 @@ -import { assets } from "chain-registry"; +import { assets, chains } from "chain-registry"; import { ethers } from "ethers"; import { fromBech32, toBech32 } from "@cosmjs/encoding"; +import type { ExtendedHttpEndpoint, WalletManager } from "@cosmos-kit/core"; +import { cosmos } from "@wardenprotocol/wardenjs"; import { AddressType } from "@wardenprotocol/wardenjs/codegen/warden/warden/v1beta3/key"; import { QueryKeyResponse } from "@wardenprotocol/wardenjs/codegen/warden/warden/v1beta3/query"; import erc20Abi from "@/contracts/eip155/erc20Abi"; import multicallAbi from "@/contracts/eip155/multicall3Abi"; import aggregatorV3InterfaceABI from "@/contracts/eip155/priceFeedAbi"; import { + COSMOS_CHAINS, COSMOS_PRICES, EIP_155_NATIVE_PRICE_FEEDS, ENABLED_ETH_CHAINS, @@ -20,6 +23,7 @@ import { getAbiItem, getCosmosChain, getInterface } from "./util"; type ChainName = Parameters[0]; const assetsByChain: Record = {}; +const chainByName: Record = {}; const getChainAssets = (chainName: string) => { if (chainName in assetsByChain) { @@ -624,3 +628,99 @@ export const fiatPricesQuery = (enabled: boolean) => { }), }; }; + +const rpcClients: Record = {}; +const rpcRetry: Record = {}; + +const checkHealth = async ( + client: CosmosQueryClient | undefined, + chainName: string, +) => { + if (!client) { + return false; + } + + let chain = chainByName[chainName]; + + if (!chain) { + chain = chains.find((x) => x.chain_name === chainName); + + if (!chain) { + console.warn("chain not found", { chainName }); + return false; + } + + chainByName[chainName] = chain; + } + + const header = ( + await client.cosmos.base.tendermint.v1beta1.getLatestBlock({}) + ).block?.header; + + const isChainIdValid = header?.chainId === chain.chain_id; + // todo check against block height and block time + return isChainIdValid; +}; + +export const queryCosmosClients = (walletManager: WalletManager) => { + return { + queryKey: ["cosmos", "rpcClients"], + queryFn: async () => { + const clients: [CosmosQueryClient, string][] = []; + + for (let i = 0; i < COSMOS_CHAINS.length; i++) { + const { chainName, rpc } = COSMOS_CHAINS[i]; + const retries = (rpc?.length ?? 0) + 1; + + for (let i = 0; i < retries; i++) { + let client = rpcClients[chainName]; + const retry = (rpcRetry[chainName] ?? 0) % (retries + 1); + rpcRetry[chainName] = retry + 1; + + if (!client) { + let endpoint: ExtendedHttpEndpoint | string; + + if (!rpc?.[retry]) { + const repo = walletManager.getWalletRepo(chainName); + repo.activate(); + + try { + endpoint = await repo.getRpcEndpoint(); + } catch (e) { + console.error(e); + endpoint = `https://rpc.cosmos.directory/${chainName}`; + } + } else { + endpoint = rpc[retry]; + } + + try { + client = + await cosmos.ClientFactory.createRPCQueryClient( + { + rpcEndpoint: + typeof endpoint === "string" + ? endpoint + : endpoint.url, + }, + ); + } catch (e) { + console.error(e); + continue; + } + } + + if (await checkHealth(client, chainName)) { + rpcClients[chainName] = client; + clients.push([client!, chainName]); + break; + } else if (rpcClients[chainName]) { + delete rpcClients[chainName]; + } + } + } + + return clients; + }, + } as const; +}; diff --git a/spaceward/src/features/staking/StakingHeading.tsx b/spaceward/src/features/staking/StakingHeading.tsx index dbdf73798..1443cb12a 100644 --- a/spaceward/src/features/staking/StakingHeading.tsx +++ b/spaceward/src/features/staking/StakingHeading.tsx @@ -27,6 +27,7 @@ export default function StakingHeading(props: HeadingProps) { {bigintToFixed(props.availableWard ?? BigInt(0), { + ceil: true, decimals: 18, format: true, display: 2