From ac285756c00e0c841f34761695e7a3a535fdf687 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Wed, 6 Mar 2024 20:12:17 +0900 Subject: [PATCH 01/72] WIP... --- .../src/components/modal/internal/context.tsx | 1 + .../extension/src/components/modal/modal.tsx | 37 ++++- .../extension/src/components/modal/types.ts | 2 + packages/extension/src/env.d.ts | 2 + .../extension/src/layouts/header/header.tsx | 12 +- .../extension/src/pages/main/available.tsx | 31 +++- .../src/pages/main/token-detail/hook.ts | 28 ++++ .../src/pages/main/token-detail/index.ts | 1 + .../src/pages/main/token-detail/modal.tsx | 148 ++++++++++++++++++ .../main/token-detail/msg-items/index.tsx | 20 +++ .../main/token-detail/msg-items/send.tsx | 114 ++++++++++++++ .../src/pages/main/token-detail/types.ts | 25 +++ packages/extension/webpack.config.js | 1 + 13 files changed, 407 insertions(+), 15 deletions(-) create mode 100644 packages/extension/src/pages/main/token-detail/hook.ts create mode 100644 packages/extension/src/pages/main/token-detail/index.ts create mode 100644 packages/extension/src/pages/main/token-detail/modal.tsx create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/index.tsx create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/send.tsx create mode 100644 packages/extension/src/pages/main/token-detail/types.ts diff --git a/packages/extension/src/components/modal/internal/context.tsx b/packages/extension/src/components/modal/internal/context.tsx index 28eeb7a56b..22c23926ac 100644 --- a/packages/extension/src/components/modal/internal/context.tsx +++ b/packages/extension/src/components/modal/internal/context.tsx @@ -45,6 +45,7 @@ export const ModalRootProvider: FunctionComponent = ({ children }) => { root.style.left = "0"; root.style.right = "0"; root.style.zIndex = "9999999"; + root.style.pointerEvents = "none"; rootElementMap.set(id, root); diff --git a/packages/extension/src/components/modal/modal.tsx b/packages/extension/src/components/modal/modal.tsx index 38eb1f8db3..4b5576637d 100644 --- a/packages/extension/src/components/modal/modal.tsx +++ b/packages/extension/src/components/modal/modal.tsx @@ -23,6 +23,7 @@ export const Modal: FunctionComponent = ({ maxHeight, onCloseTransitionEnd, forceNotUseSimplebar, + disableBackdrop, children, }) => { const modalRoot = useModalRoot(isOpen); @@ -87,6 +88,7 @@ export const Modal: FunctionComponent = ({ } }} forceNotUseSimplebar={forceNotUseSimplebar} + disableBackdrop={disableBackdrop} > {children} @@ -104,6 +106,7 @@ const ModalChild: FunctionComponent<{ onCloseTransitionEnd: () => void; forceNotUseSimplebar?: boolean; + disableBackdrop?: boolean; }> = ({ children, align, @@ -112,6 +115,7 @@ const ModalChild: FunctionComponent<{ close, onCloseTransitionEnd, forceNotUseSimplebar, + disableBackdrop, }) => { const transition = useSpringValue(0, { config: defaultSpringConfig, @@ -180,14 +184,33 @@ const ModalChild: FunctionComponent<{ }; })(), - backgroundColor: transition.to((t) => - Color("#000000") - .alpha(t * 0.55) - .string() - ), + // root의 pointer events가 "none"으로 설정된다. + // 하지만 backdrop 자체가 모든 화면을 채우기 때문에 여기서 pointer events를 로직에 따라 대체할 수 있다. + // disableBackdrop 옵션에 따라서 선택한다. + pointerEvents: disableBackdrop ? "none" : "auto", + + backgroundColor: disableBackdrop + ? "rgba(0,0,0,0)" + : transition.to((t) => + Color("#000000") + .alpha(t * 0.55) + .string() + ), }} onClick={(e) => { e.preventDefault(); + + if (disableBackdrop) { + if ( + innerContainerRef.current && + innerContainerRef.current !== e.target && + innerContainerRef.current.contains(e.target as Node) + ) { + e.stopPropagation(); + } + return; + } + e.stopPropagation(); if ( @@ -213,6 +236,10 @@ const ModalChild: FunctionComponent<{ overflow: "auto", + // root의 pointer events가 "none"으로 설정된다. + // 하지만 당연히 컨텐츠 자체는 pointer events를 받아야 하므로 여기서 "auto"로 설정한다. + pointerEvents: "auto", + position: "absolute", left: 0, right: 0, diff --git a/packages/extension/src/components/modal/types.ts b/packages/extension/src/components/modal/types.ts index ee59d20d19..ac057fb539 100644 --- a/packages/extension/src/components/modal/types.ts +++ b/packages/extension/src/components/modal/types.ts @@ -11,4 +11,6 @@ export interface ModalProps { maxHeight?: string; forceNotUseSimplebar?: boolean; + + disableBackdrop?: boolean; } diff --git a/packages/extension/src/env.d.ts b/packages/extension/src/env.d.ts index 378262ecf2..28fd58f45d 100644 --- a/packages/extension/src/env.d.ts +++ b/packages/extension/src/env.d.ts @@ -2,5 +2,7 @@ declare namespace NodeJS { interface ProcessEnv { /** node environment */ NODE_ENV: "production" | "development" | undefined; + + KEPLR_EXT_TX_HISTORY_BASE_URL: string; } } diff --git a/packages/extension/src/layouts/header/header.tsx b/packages/extension/src/layouts/header/header.tsx index 386ed197ce..86aca6bad2 100644 --- a/packages/extension/src/layouts/header/header.tsx +++ b/packages/extension/src/layouts/header/header.tsx @@ -19,11 +19,13 @@ const pxToRem = (px: number) => { }; const bottomButtonPaddingRem = 0.75; +export const HeaderHeight = "3.75rem"; + const Styles = { Container: styled.div``, HeaderContainer: styled.div` - height: 3.75rem; + height: ${HeaderHeight}; background: ${(props) => props.theme.mode === "light" @@ -51,7 +53,7 @@ const Styles = { `, HeaderTitle: styled.div` - height: 3.75rem; + height: ${HeaderHeight}; position: absolute; top: 0; @@ -68,7 +70,7 @@ const Styles = { : ColorPalette["white"]}; `, HeaderLeft: styled.div` - height: 3.75rem; + height: ${HeaderHeight}; position: absolute; top: 0; @@ -82,7 +84,7 @@ const Styles = { `, HeaderRight: styled.div` - height: 3.75rem; + height: ${HeaderHeight}; position: absolute; top: 0; @@ -112,7 +114,7 @@ const Styles = { return css``; }} - padding-top: 3.75rem; + padding-top: ${HeaderHeight}; padding-bottom: ${({ bottomPadding }) => bottomPadding}; ${({ diff --git a/packages/extension/src/pages/main/available.tsx b/packages/extension/src/pages/main/available.tsx index a3269ca9fc..162b45b4f5 100644 --- a/packages/extension/src/pages/main/available.tsx +++ b/packages/extension/src/pages/main/available.tsx @@ -17,7 +17,6 @@ import { Styles, TextButton } from "../../components/button-text"; import { Box } from "../../components/box"; import { Modal } from "../../components/modal"; import { ChainIdHelper } from "@keplr-wallet/cosmos"; -import { useNavigate } from "react-router"; import { Gutter } from "../../components/gutter"; import { EmptyView } from "../../components/empty-view"; import { Subtitle3 } from "../../components/typography"; @@ -28,6 +27,7 @@ import { ColorPalette } from "../../styles"; import { FormattedMessage, useIntl } from "react-intl"; import styled, { useTheme } from "styled-components"; import { DenomHelper } from "@keplr-wallet/common"; +import { TokenDetailModal } from "./token-detail"; const zeroDec = new Dec(0); @@ -57,7 +57,6 @@ export const AvailableTabView: FunctionComponent<{ }> = observer(({ search, isNotReady, onClickGetStarted }) => { const { hugeQueriesStore, chainStore, accountStore, uiConfigStore } = useStore(); - const navigate = useNavigate(); const intl = useIntl(); const theme = useTheme(); @@ -166,6 +165,10 @@ export const AvailableTabView: FunctionComponent<{ const isShowNotFound = allBalancesSearchFiltered.length === 0 && trimSearch.length > 0; + const [tokenDetailModal, setTokenDetailModal] = useState< + { chainId: string; coinMinimalDenom: string } | undefined + >(undefined); + return ( {isNotReady ? ( @@ -236,9 +239,11 @@ export const AvailableTabView: FunctionComponent<{ viewToken={viewToken} key={`${viewToken.chainInfo.chainId}-${viewToken.token.currency.coinMinimalDenom}`} onClick={() => - navigate( - `/send?chainId=${viewToken.chainInfo.chainId}&coinMinimalDenom=${viewToken.token.currency.coinMinimalDenom}` - ) + setTokenDetailModal({ + chainId: viewToken.chainInfo.chainId, + coinMinimalDenom: + viewToken.token.currency.coinMinimalDenom, + }) } copyAddress={(() => { // For only native tokens, show copy address button @@ -342,6 +347,22 @@ export const AvailableTabView: FunctionComponent<{ > setIsFoundTokenModalOpen(false)} /> + + setTokenDetailModal(undefined)} + maxHeight="100vh" + disableBackdrop={true} + > + {tokenDetailModal ? ( + setTokenDetailModal(undefined)} + chainId={tokenDetailModal.chainId} + coinMinimalDenom={tokenDetailModal.coinMinimalDenom} + /> + ) : null} + ); }); diff --git a/packages/extension/src/pages/main/token-detail/hook.ts b/packages/extension/src/pages/main/token-detail/hook.ts new file mode 100644 index 0000000000..ffc096c2a1 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/hook.ts @@ -0,0 +1,28 @@ +import { useEffect, useState } from "react"; +import { simpleFetch } from "@keplr-wallet/simple-fetch"; + +export const usePaginatedCursorQuery = ( + baseURL: string, + uriFn: (page: number) => string +): { + pages: { + response: R | undefined; + }[]; +} => { + const [response, setResponse] = useState(undefined); + + useEffect(() => { + simpleFetch(baseURL, uriFn(1)).then((r) => { + // TODO: 오류처리 + setResponse(r.data); + }); + }, []); + + return { + pages: [ + { + response, + }, + ], + }; +}; diff --git a/packages/extension/src/pages/main/token-detail/index.ts b/packages/extension/src/pages/main/token-detail/index.ts new file mode 100644 index 0000000000..031608e25f --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/index.ts @@ -0,0 +1 @@ +export * from "./modal"; diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx new file mode 100644 index 0000000000..e6ec2db411 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -0,0 +1,148 @@ +import React, { FunctionComponent } from "react"; +import { observer } from "mobx-react-lite"; +import styled from "styled-components"; +import { HeaderHeight } from "../../../layouts/header"; +import { Box } from "../../../components/box"; +import { XAxis, YAxis } from "../../../components/axis"; +import { useStore } from "../../../stores"; +import { Body1, Subtitle3 } from "../../../components/typography"; +import { ColorPalette } from "../../../styles"; +import { Gutter } from "../../../components/gutter"; +import { usePaginatedCursorQuery } from "./hook"; +import { ResMsgsHistory } from "./types"; +import { Stack } from "../../../components/stack"; +import { MsgItemRender } from "./msg-items"; + +const Styles = { + Container: styled.div` + height: calc(100vh - ${HeaderHeight}); + + background-color: ${ColorPalette["gray-700"]}; + + border-top-style: solid; + border-top-width: 1px; + border-top-color: ${ColorPalette["gray-500"]}; + + display: flex; + flex-direction: column; + `, + Balance: styled.div` + font-weight: 500; + font-size: 1.75rem; + line-height: 2.125rem; + `, +}; + +export const TokenDetailModal: FunctionComponent<{ + close: () => void; + chainId: string; + coinMinimalDenom: string; +}> = observer(({ close, chainId, coinMinimalDenom }) => { + const { chainStore, accountStore, queriesStore, priceStore } = useStore(); + + const chainInfo = chainStore.getChain(chainId); + const currency = chainInfo.forceFindCurrency(coinMinimalDenom); + + const balance = queriesStore + .get(chainId) + .queryBalances.getQueryBech32Address( + accountStore.getAccount(chainId).bech32Address + ) + .getBalance(currency); + + const msgHistory = usePaginatedCursorQuery( + process.env["KEPLR_EXT_TX_HISTORY_BASE_URL"], + () => { + return `/history/msgs/${chainInfo.chainIdentifier}/${ + accountStore.getAccount(chainId).bech32Address + }?relations=send,ibc-receive&denoms=${encodeURIComponent( + currency.coinMinimalDenom + )}&vsCurrencies=${priceStore.defaultVsCurrency}`; + } + ); + + return ( + + { + e.preventDefault(); + + close(); + }} + alignY="center" + > + +
+ + {(() => { + let denom = currency.coinDenom; + if ("originCurrency" in currency && currency.originCurrency) { + denom = currency.originCurrency.coinDenom; + } + + return `${denom} on ${chainInfo.chainName}`; + })()} + +
+ + + + +
TODO: Address chip
+ + + + + {(() => { + if (!balance) { + return `0 ${currency.coinDenom}`; + } + + return balance.balance + .maxDecimals(6) + .inequalitySymbol(true) + .shrink(true) + .hideIBCMetadata(true) + .toString(); + })()} + + + + {(() => { + if (!balance) { + return "-"; + } + const price = priceStore.calculatePrice(balance.balance); + if (price) { + return price.toString(); + } + return "-"; + })()} + + + + + + + {msgHistory.pages[0].response + ? msgHistory.pages[0].response.msgs.map((msg) => { + return ( + + ); + }) + : null} + + + + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx new file mode 100644 index 0000000000..85411f1427 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx @@ -0,0 +1,20 @@ +import React, { FunctionComponent } from "react"; +import { ResMsg } from "../types"; +import { MsgRelationSend } from "./send"; + +export const MsgItemRender: FunctionComponent<{ + msg: ResMsg; + prices?: Record | undefined>; + targetDenom: string; +}> = ({ msg, prices, targetDenom }) => { + switch (msg.relation) { + case "send": { + return ( + + ); + } + } + + // TODO: 임시적인 alternative? + return null; +}; diff --git a/packages/extension/src/pages/main/token-detail/msg-items/send.tsx b/packages/extension/src/pages/main/token-detail/msg-items/send.tsx new file mode 100644 index 0000000000..7bf4e21884 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/send.tsx @@ -0,0 +1,114 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { ResMsg } from "../types"; +import { Box } from "../../../../components/box"; +import { ColorPalette } from "../../../../styles"; +import { XAxis, YAxis } from "../../../../components/axis"; +import { Body3, Subtitle3 } from "../../../../components/typography"; +import { Gutter } from "../../../../components/gutter"; +import { Bech32Address } from "@keplr-wallet/cosmos"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty, Dec, PricePretty } from "@keplr-wallet/unit"; + +export const MsgRelationSend: FunctionComponent<{ + msg: ResMsg; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore, priceStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const sendAmountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const amounts = (msg.msg as any)["amount"] as { + denom: string; + amount: string; + }[]; + + const amt = amounts.find((amt) => amt.denom === targetDenom); + if (!amt) { + return new CoinPretty(currency, "0"); + } + return new CoinPretty(currency, amt.amount); + }, [chainInfo, msg.msg, targetDenom]); + + // mobx와 useMemo의 조합 문제로... 값 몇개를 밖으로 뺀다. + const foundCurrency = chainInfo.findCurrency(targetDenom); + const defaultVsCurrency = priceStore.defaultVsCurrency; + const sendAmountPricePretty = useMemo(() => { + if (foundCurrency && foundCurrency.coinGeckoId) { + const price = prices?.[foundCurrency.coinGeckoId]; + if (price != null && price[defaultVsCurrency] != null) { + const dec = sendAmountPretty.toDec(); + const priceDec = new Dec(price[defaultVsCurrency]!.toString()); + const fiatCurrency = priceStore.getFiatCurrency(defaultVsCurrency); + if (fiatCurrency) { + return new PricePretty(fiatCurrency, dec.mul(priceDec)); + } + } + } + return; + }, [defaultVsCurrency, foundCurrency, priceStore, prices, sendAmountPretty]); + + const toAddress = (() => { + try { + return Bech32Address.shortenAddress((msg.msg as any)["to_address"], 22); + } catch (e) { + console.log(e); + return "Unknown"; + } + })(); + + return ( + + +
logo
+
+ + + Send + + {toAddress} + + +
+ + + {sendAmountPretty + .maxDecimals(2) + .shrink(true) + .hideIBCMetadata(true) + .inequalitySymbol(true) + .toString()} + + {sendAmountPricePretty ? ( + + + + {sendAmountPricePretty.toString()} + + + ) : null} + + +
+
+ + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/types.ts b/packages/extension/src/pages/main/token-detail/types.ts new file mode 100644 index 0000000000..582f6b7833 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/types.ts @@ -0,0 +1,25 @@ +export interface ResMsgsHistory { + msgs: { + msg: ResMsg; + prices?: Record | undefined>; + }[]; +} + +export interface ResMsg { + txHash: string; + code: number; + + height: number; + time: number; + chainId: string; + chainIdentifier: string; + + relation: string; + msgIndex: number; + msg: unknown; + eventStartIndex: number; + eventEndIndex: number; + + search: string; + denoms?: string[]; +} diff --git a/packages/extension/webpack.config.js b/packages/extension/webpack.config.js index 5556dc0841..acc08b0d61 100644 --- a/packages/extension/webpack.config.js +++ b/packages/extension/webpack.config.js @@ -174,6 +174,7 @@ module.exports = { KEPLR_EXT_CHAIN_REGISTRY_URL: "", KEPLR_EXT_GOOGLE_MEASUREMENT_ID: "", KEPLR_EXT_GOOGLE_API_KEY_FOR_MEASUREMENT: "", + KEPLR_EXT_TX_HISTORY_BASE_URL: "", WC_PROJECT_ID: "", }), new ForkTsCheckerWebpackPlugin(), From ce623df088bcd435293040e57da84f83fc0f7764 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 11 Mar 2024 16:33:41 +0900 Subject: [PATCH 02/72] WIP --- .../src/pages/main/token-detail/constants.ts | 8 + .../src/pages/main/token-detail/modal.tsx | 153 +++++++++--------- .../main/token-detail/msg-items/base.tsx | 105 ++++++++++++ .../main/token-detail/msg-items/delegate.tsx | 74 +++++++++ .../main/token-detail/msg-items/index.tsx | 44 ++++- .../msg-items/merged-claim-rewards.tsx | 53 ++++++ .../main/token-detail/msg-items/receive.tsx | 52 ++++++ .../main/token-detail/msg-items/send.tsx | 88 ++-------- .../token-detail/msg-items/undelegate.tsx | 74 +++++++++ .../src/pages/main/token-detail/types.ts | 8 +- 10 files changed, 507 insertions(+), 152 deletions(-) create mode 100644 packages/extension/src/pages/main/token-detail/constants.ts create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/base.tsx create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/receive.tsx create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts new file mode 100644 index 0000000000..bf6cb05226 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -0,0 +1,8 @@ +export const Relations = [ + "send", + "receive", + "delegate", + "undelegate", + "redelegate", + "custom/merged-claim-rewards", +]; diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index e6ec2db411..2f38550d0b 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -12,6 +12,8 @@ import { usePaginatedCursorQuery } from "./hook"; import { ResMsgsHistory } from "./types"; import { Stack } from "../../../components/stack"; import { MsgItemRender } from "./msg-items"; +import SimpleBar from "simplebar-react"; +import { Relations } from "./constants"; const Styles = { Container: styled.div` @@ -55,7 +57,7 @@ export const TokenDetailModal: FunctionComponent<{ () => { return `/history/msgs/${chainInfo.chainIdentifier}/${ accountStore.getAccount(chainId).bech32Address - }?relations=send,ibc-receive&denoms=${encodeURIComponent( + }?relations=${Relations.join(",")}&denoms=${encodeURIComponent( currency.coinMinimalDenom )}&vsCurrencies=${priceStore.defaultVsCurrency}`; } @@ -63,86 +65,93 @@ export const TokenDetailModal: FunctionComponent<{ return ( - { - e.preventDefault(); - - close(); + - -
- - {(() => { - let denom = currency.coinDenom; - if ("originCurrency" in currency && currency.originCurrency) { - denom = currency.originCurrency.coinDenom; - } - - return `${denom} on ${chainInfo.chainName}`; - })()} - -
- - - - -
TODO: Address chip
+ { + e.preventDefault(); - - - - {(() => { - if (!balance) { - return `0 ${currency.coinDenom}`; - } + +
+ + {(() => { + let denom = currency.coinDenom; + if ("originCurrency" in currency && currency.originCurrency) { + denom = currency.originCurrency.coinDenom; + } + + return `${denom} on ${chainInfo.chainName}`; + })()} + +
+ + - return balance.balance - .maxDecimals(6) - .inequalitySymbol(true) - .shrink(true) - .hideIBCMetadata(true) - .toString(); - })()} - - - {(() => { - if (!balance) { +
TODO: Address chip
+ + + + + {(() => { + if (!balance) { + return `0 ${currency.coinDenom}`; + } + + return balance.balance + .maxDecimals(6) + .inequalitySymbol(true) + .shrink(true) + .hideIBCMetadata(true) + .toString(); + })()} + + + + {(() => { + if (!balance) { + return "-"; + } + const price = priceStore.calculatePrice(balance.balance); + if (price) { + return price.toString(); + } return "-"; - } - const price = priceStore.calculatePrice(balance.balance); - if (price) { - return price.toString(); - } - return "-"; - })()} - - + })()} +
+ - - - - {msgHistory.pages[0].response - ? msgHistory.pages[0].response.msgs.map((msg) => { - return ( - - ); - }) - : null} - - + + + + {msgHistory.pages[0].response + ? msgHistory.pages[0].response.msgs.map((msg) => { + return ( + + ); + }) + : null} + + + ); }); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx new file mode 100644 index 0000000000..5055c9f89c --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx @@ -0,0 +1,105 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { Box } from "../../../../components/box"; +import { ColorPalette } from "../../../../styles"; +import { XAxis, YAxis } from "../../../../components/axis"; +import { Body3, Subtitle3 } from "../../../../components/typography"; +import { Gutter } from "../../../../components/gutter"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty, Dec, PricePretty } from "@keplr-wallet/unit"; + +export const MsgItemBase: FunctionComponent<{ + chainId: string; + title: string; + paragraph?: string; + amount: CoinPretty; + prices: Record | undefined>; + targetDenom: string; +}> = observer(({ chainId, title, paragraph, amount, prices, targetDenom }) => { + const { chainStore, priceStore } = useStore(); + + const chainInfo = chainStore.getChain(chainId); + + // mobx와 useMemo의 조합 문제로... 값 몇개를 밖으로 뺀다. + const foundCurrency = chainInfo.findCurrency(targetDenom); + const defaultVsCurrency = priceStore.defaultVsCurrency; + const sendAmountPricePretty = useMemo(() => { + if (foundCurrency && foundCurrency.coinGeckoId) { + const price = prices[foundCurrency.coinGeckoId]; + if (price != null && price[defaultVsCurrency] != null) { + const dec = amount.toDec(); + const priceDec = new Dec(price[defaultVsCurrency]!.toString()); + const fiatCurrency = priceStore.getFiatCurrency(defaultVsCurrency); + if (fiatCurrency) { + return new PricePretty(fiatCurrency, dec.mul(priceDec)); + } + } + } + return; + }, [defaultVsCurrency, foundCurrency, priceStore, prices, amount]); + + return ( + + +
logo
+
+ + + {title} + {paragraph ? ( + + + {paragraph} + + ) : null} + + +
+ + + {amount + .maxDecimals(2) + .shrink(true) + .hideIBCMetadata(true) + .inequalitySymbol(true) + .toString()} + + {sendAmountPricePretty ? ( + + + + {sendAmountPricePretty.toString()} + + + ) : null} + + +
+
+ + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx b/packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx new file mode 100644 index 0000000000..0e1143a55d --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx @@ -0,0 +1,74 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; +import { Staking } from "@keplr-wallet/stores"; + +export const MsgRelationDelegate: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore, queriesStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const amountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const amount = (msg.msg as any)["amount"] as { + denom: string; + amount: string; + }; + + if (amount.denom !== targetDenom) { + return new CoinPretty(currency, "0"); + } + return new CoinPretty(currency, amount.amount); + }, [chainInfo, msg.msg, targetDenom]); + + const validatorAddress: string = useMemo(() => { + return (msg.msg as any)["validator_address"]; + }, [msg.msg]); + + const queryBonded = queriesStore + .get(chainInfo.chainId) + .cosmos.queryValidators.getQueryStatus(Staking.BondStatus.Bonded); + const queryUnbonding = queriesStore + .get(chainInfo.chainId) + .cosmos.queryValidators.getQueryStatus(Staking.BondStatus.Unbonding); + const queryUnbonded = queriesStore + .get(chainInfo.chainId) + .cosmos.queryValidators.getQueryStatus(Staking.BondStatus.Unbonded); + + const moniker: string | undefined = (() => { + if (!validatorAddress) { + return undefined; + } + const bonded = queryBonded.getValidator(validatorAddress); + if (bonded?.description.moniker) { + return bonded.description.moniker; + } + const unbonding = queryUnbonding.getValidator(validatorAddress); + if (unbonding?.description.moniker) { + return unbonding.description.moniker; + } + const unbonded = queryUnbonded.getValidator(validatorAddress); + if (unbonded?.description.moniker) { + return unbonded.description.moniker; + } + })(); + + return ( + + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx index 85411f1427..1471a2b3f7 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx @@ -1,9 +1,13 @@ import React, { FunctionComponent } from "react"; -import { ResMsg } from "../types"; +import { MsgHistory } from "../types"; import { MsgRelationSend } from "./send"; +import { MsgRelationReceive } from "./receive"; +import { MsgRelationDelegate } from "./delegate"; +import { MsgRelationUndelegate } from "./undelegate"; +import { MsgRelationMergedClaimRewards } from "./merged-claim-rewards"; export const MsgItemRender: FunctionComponent<{ - msg: ResMsg; + msg: MsgHistory; prices?: Record | undefined>; targetDenom: string; }> = ({ msg, prices, targetDenom }) => { @@ -13,6 +17,42 @@ export const MsgItemRender: FunctionComponent<{ ); } + case "receive": { + return ( + + ); + } + case "delegate": { + return ( + + ); + } + case "undelegate": { + return ( + + ); + } + case "custom/merged-claim-rewards": { + return ( + + ); + } } // TODO: 임시적인 alternative? diff --git a/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx b/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx new file mode 100644 index 0000000000..ee11233d6a --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx @@ -0,0 +1,53 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; + +export const MsgRelationMergedClaimRewards: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const amountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const rewards = msg.meta["rewards"]; + if ( + rewards && + Array.isArray(rewards) && + rewards.length > 0 && + typeof rewards[0] === "string" + ) { + for (const coinStr of rewards) { + const split = (coinStr as string).split( + /^([0-9]+)(\s)*([a-zA-Z][a-zA-Z0-9/-]*)$/ + ); + + if (split.length === 5) { + const denom = split[3]; + if (denom === targetDenom) { + return new CoinPretty(currency, split[1]); + } + } + } + } + + return new CoinPretty(currency, "0"); + }, [chainInfo, msg.meta, targetDenom]); + + return ( + + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/receive.tsx b/packages/extension/src/pages/main/token-detail/msg-items/receive.tsx new file mode 100644 index 0000000000..de8ab7a104 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/receive.tsx @@ -0,0 +1,52 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { Bech32Address } from "@keplr-wallet/cosmos"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; + +export const MsgRelationReceive: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const sendAmountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const amounts = (msg.msg as any)["amount"] as { + denom: string; + amount: string; + }[]; + + const amt = amounts.find((amt) => amt.denom === targetDenom); + if (!amt) { + return new CoinPretty(currency, "0"); + } + return new CoinPretty(currency, amt.amount); + }, [chainInfo, msg.msg, targetDenom]); + + const fromAddress = (() => { + try { + return Bech32Address.shortenAddress((msg.msg as any)["from_address"], 22); + } catch (e) { + console.log(e); + return "Unknown"; + } + })(); + + return ( + + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/send.tsx b/packages/extension/src/pages/main/token-detail/msg-items/send.tsx index 7bf4e21884..83a4072410 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/send.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/send.tsx @@ -1,21 +1,17 @@ import React, { FunctionComponent, useMemo } from "react"; -import { ResMsg } from "../types"; -import { Box } from "../../../../components/box"; -import { ColorPalette } from "../../../../styles"; -import { XAxis, YAxis } from "../../../../components/axis"; -import { Body3, Subtitle3 } from "../../../../components/typography"; -import { Gutter } from "../../../../components/gutter"; +import { MsgHistory } from "../types"; import { Bech32Address } from "@keplr-wallet/cosmos"; import { observer } from "mobx-react-lite"; import { useStore } from "../../../../stores"; -import { CoinPretty, Dec, PricePretty } from "@keplr-wallet/unit"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; export const MsgRelationSend: FunctionComponent<{ - msg: ResMsg; + msg: MsgHistory; prices?: Record | undefined>; targetDenom: string; }> = observer(({ msg, prices, targetDenom }) => { - const { chainStore, priceStore } = useStore(); + const { chainStore } = useStore(); const chainInfo = chainStore.getChain(msg.chainId); @@ -34,24 +30,6 @@ export const MsgRelationSend: FunctionComponent<{ return new CoinPretty(currency, amt.amount); }, [chainInfo, msg.msg, targetDenom]); - // mobx와 useMemo의 조합 문제로... 값 몇개를 밖으로 뺀다. - const foundCurrency = chainInfo.findCurrency(targetDenom); - const defaultVsCurrency = priceStore.defaultVsCurrency; - const sendAmountPricePretty = useMemo(() => { - if (foundCurrency && foundCurrency.coinGeckoId) { - const price = prices?.[foundCurrency.coinGeckoId]; - if (price != null && price[defaultVsCurrency] != null) { - const dec = sendAmountPretty.toDec(); - const priceDec = new Dec(price[defaultVsCurrency]!.toString()); - const fiatCurrency = priceStore.getFiatCurrency(defaultVsCurrency); - if (fiatCurrency) { - return new PricePretty(fiatCurrency, dec.mul(priceDec)); - } - } - } - return; - }, [defaultVsCurrency, foundCurrency, priceStore, prices, sendAmountPretty]); - const toAddress = (() => { try { return Bech32Address.shortenAddress((msg.msg as any)["to_address"], 22); @@ -62,53 +40,13 @@ export const MsgRelationSend: FunctionComponent<{ })(); return ( - - -
logo
-
- - - Send - - {toAddress} - - -
- - - {sendAmountPretty - .maxDecimals(2) - .shrink(true) - .hideIBCMetadata(true) - .inequalitySymbol(true) - .toString()} - - {sendAmountPricePretty ? ( - - - - {sendAmountPricePretty.toString()} - - - ) : null} - - -
-
- + ); }); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx b/packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx new file mode 100644 index 0000000000..7c584df936 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx @@ -0,0 +1,74 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; +import { Staking } from "@keplr-wallet/stores"; + +export const MsgRelationUndelegate: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore, queriesStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const amountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const amount = (msg.msg as any)["amount"] as { + denom: string; + amount: string; + }; + + if (amount.denom !== targetDenom) { + return new CoinPretty(currency, "0"); + } + return new CoinPretty(currency, amount.amount); + }, [chainInfo, msg.msg, targetDenom]); + + const validatorAddress: string = useMemo(() => { + return (msg.msg as any)["validator_address"]; + }, [msg.msg]); + + const queryBonded = queriesStore + .get(chainInfo.chainId) + .cosmos.queryValidators.getQueryStatus(Staking.BondStatus.Bonded); + const queryUnbonding = queriesStore + .get(chainInfo.chainId) + .cosmos.queryValidators.getQueryStatus(Staking.BondStatus.Unbonding); + const queryUnbonded = queriesStore + .get(chainInfo.chainId) + .cosmos.queryValidators.getQueryStatus(Staking.BondStatus.Unbonded); + + const moniker: string | undefined = (() => { + if (!validatorAddress) { + return undefined; + } + const bonded = queryBonded.getValidator(validatorAddress); + if (bonded?.description.moniker) { + return bonded.description.moniker; + } + const unbonding = queryUnbonding.getValidator(validatorAddress); + if (unbonding?.description.moniker) { + return unbonding.description.moniker; + } + const unbonded = queryUnbonded.getValidator(validatorAddress); + if (unbonded?.description.moniker) { + return unbonded.description.moniker; + } + })(); + + return ( + + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/types.ts b/packages/extension/src/pages/main/token-detail/types.ts index 582f6b7833..8a5a41cede 100644 --- a/packages/extension/src/pages/main/token-detail/types.ts +++ b/packages/extension/src/pages/main/token-detail/types.ts @@ -1,16 +1,17 @@ export interface ResMsgsHistory { msgs: { - msg: ResMsg; + msg: MsgHistory; prices?: Record | undefined>; }[]; + nextCursor: string; } -export interface ResMsg { +export interface MsgHistory { txHash: string; code: number; height: number; - time: number; + time: Date; chainId: string; chainIdentifier: string; @@ -22,4 +23,5 @@ export interface ResMsg { search: string; denoms?: string[]; + meta: Record; } From bd2072134ca3d41e5c5c85b352c902f9ed4feeb6 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 11 Mar 2024 20:42:29 +0900 Subject: [PATCH 03/72] scroll to fetch --- .../src/pages/main/token-detail/constants.ts | 2 + .../src/pages/main/token-detail/hook.ts | 79 ++++++++++++++++--- .../src/pages/main/token-detail/modal.tsx | 68 +++++++++++++++- 3 files changed, 133 insertions(+), 16 deletions(-) diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts index bf6cb05226..5c0769b867 100644 --- a/packages/extension/src/pages/main/token-detail/constants.ts +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -6,3 +6,5 @@ export const Relations = [ "redelegate", "custom/merged-claim-rewards", ]; + +export const PaginationLimit = 5; diff --git a/packages/extension/src/pages/main/token-detail/hook.ts b/packages/extension/src/pages/main/token-detail/hook.ts index ffc096c2a1..c29483d62e 100644 --- a/packages/extension/src/pages/main/token-detail/hook.ts +++ b/packages/extension/src/pages/main/token-detail/hook.ts @@ -1,28 +1,83 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; export const usePaginatedCursorQuery = ( baseURL: string, - uriFn: (page: number) => string + initialUriFn: () => string, + nextCursorQueryString: (page: number, prev: R) => Record, + isEndedFn: (prev: R) => boolean ): { + isFetching: boolean; pages: { response: R | undefined; }[]; + next: () => void; } => { - const [response, setResponse] = useState(undefined); + const [responses, setResponses] = useState(undefined); + const baseURLRef = useRef(baseURL); + baseURLRef.current = baseURL; + const initialUriFnRef = useRef(initialUriFn); + initialUriFnRef.current = initialUriFn; + const nextCursorQueryStringRef = useRef(nextCursorQueryString); + nextCursorQueryStringRef.current = nextCursorQueryString; + const isEndedFnRef = useRef(isEndedFn); + isEndedFnRef.current = isEndedFn; + + // 어차피 바로 useEffect에 의해서 fetch되기 때문에 true로 시작... + const [isFetching, setIsFetching] = useState(true); useEffect(() => { - simpleFetch(baseURL, uriFn(1)).then((r) => { - // TODO: 오류처리 - setResponse(r.data); - }); + simpleFetch(baseURLRef.current, initialUriFnRef.current()) + .then((r) => { + // TODO: 오류처리 + setResponses([r.data]); + }) + .finally(() => { + setIsFetching(false); + }); }, []); + const next = useCallback(() => { + if ( + isFetching || + !responses || + responses.length === 0 || + isEndedFnRef.current(responses[responses.length - 1]) + ) { + return; + } + + const nextPage = responses.length + 1; + let uri = initialUriFnRef.current(); + const qs = nextCursorQueryStringRef.current( + nextPage, + responses[responses.length - 1] + ); + const params = new URLSearchParams(qs); + if (uri.length === 0 || uri === "/") { + uri = `?${params.toString()}`; + } else { + uri += `&${params.toString()}`; + } + + setIsFetching(true); + simpleFetch(baseURLRef.current, uri) + .then((r) => { + // TODO: 오류처리 + setResponses((res) => { + const newRes = !res ? [] : [...res]; + newRes.push(r.data); + return newRes; + }); + }) + .finally(() => { + setIsFetching(false); + }); + }, [isFetching, responses]); + return { - pages: [ - { - response, - }, - ], + isFetching, + pages: !responses ? [] : responses.map((r) => ({ response: r })), + next, }; }; diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index 2f38550d0b..a7fc02146e 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent } from "react"; +import React, { FunctionComponent, useRef } from "react"; import { observer } from "mobx-react-lite"; import styled from "styled-components"; import { HeaderHeight } from "../../../layouts/header"; @@ -13,7 +13,8 @@ import { ResMsgsHistory } from "./types"; import { Stack } from "../../../components/stack"; import { MsgItemRender } from "./msg-items"; import SimpleBar from "simplebar-react"; -import { Relations } from "./constants"; +import { PaginationLimit, Relations } from "./constants"; +import SimpleBarCore from "simplebar-core"; const Styles = { Container: styled.div` @@ -59,13 +60,44 @@ export const TokenDetailModal: FunctionComponent<{ accountStore.getAccount(chainId).bech32Address }?relations=${Relations.join(",")}&denoms=${encodeURIComponent( currency.coinMinimalDenom - )}&vsCurrencies=${priceStore.defaultVsCurrency}`; + )}&vsCurrencies=${priceStore.defaultVsCurrency}&limit=${PaginationLimit}`; + }, + (_, prev) => { + return { + cursor: prev.nextCursor, + }; + }, + (res) => { + if (!res.nextCursor) { + return true; + } + return false; } ); + const simpleBarRef = useRef(null); + // scroll to refresh + const onScroll = () => { + const el = simpleBarRef.current?.getContentElement(); + const scrollEl = simpleBarRef.current?.getScrollElement(); + if (el && scrollEl) { + const rect = el.getBoundingClientRect(); + const scrollRect = scrollEl.getBoundingClientRect(); + + const remainingBottomY = + rect.y + rect.height - scrollRect.y - scrollRect.height; + + if (remainingBottomY < scrollRect.height / 10) { + msgHistory.next(); + } + } + }; + return ( - {msgHistory.pages[0].response + {(() => { + if ( + msgHistory.pages.length === 0 || + !msgHistory.pages[0].response + ) { + return null; + } + + const allMsgs: ResMsgsHistory["msgs"][0][] = []; + for (const page of msgHistory.pages) { + if (page.response) { + for (const msg of page.response.msgs) { + allMsgs.push(msg); + } + } + } + + return allMsgs.map((msg) => { + return ( + + ); + }); + })()} + {msgHistory.pages.length > 0 && msgHistory.pages[0].response ? msgHistory.pages[0].response.msgs.map((msg) => { return ( Date: Mon, 11 Mar 2024 21:22:24 +0900 Subject: [PATCH 04/72] Fix a mistake --- .../extension/src/pages/main/token-detail/modal.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index a7fc02146e..c8c39b099b 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -197,18 +197,6 @@ export const TokenDetailModal: FunctionComponent<{ ); }); })()} - {msgHistory.pages.length > 0 && msgHistory.pages[0].response - ? msgHistory.pages[0].response.msgs.map((msg) => { - return ( - - ); - }) - : null} From 5a8674e96c91e0fe83b052a74b16ff08810b20dc Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 11 Mar 2024 22:37:40 +0900 Subject: [PATCH 05/72] WIP --- .../extension/src/components/modal/modal.tsx | 39 +- .../extension/src/components/modal/types.ts | 3 +- .../extension/src/pages/main/available.tsx | 23 +- .../src/pages/main/token-detail/constants.ts | 3 +- .../src/pages/main/token-detail/modal.tsx | 371 ++++++++++++++---- .../main/token-detail/msg-items/base.tsx | 2 + 6 files changed, 334 insertions(+), 107 deletions(-) diff --git a/packages/extension/src/components/modal/modal.tsx b/packages/extension/src/components/modal/modal.tsx index 4b5576637d..b5f9d39e8e 100644 --- a/packages/extension/src/components/modal/modal.tsx +++ b/packages/extension/src/components/modal/modal.tsx @@ -23,6 +23,7 @@ export const Modal: FunctionComponent = ({ maxHeight, onCloseTransitionEnd, forceNotUseSimplebar, + forceNotOverflowAuto, disableBackdrop, children, }) => { @@ -65,6 +66,10 @@ export const Modal: FunctionComponent = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const transition = useSpringValue(isOpen ? 1 : 0, { + config: defaultSpringConfig, + }); + if (!rootElement) { return null; } @@ -76,6 +81,7 @@ export const Modal: FunctionComponent = ({ return ReactDOM.createPortal(
= ({ } }} forceNotUseSimplebar={forceNotUseSimplebar} + forceNotOverflowAuto={forceNotOverflowAuto} disableBackdrop={disableBackdrop} > {children} @@ -98,29 +105,30 @@ export const Modal: FunctionComponent = ({ }; const ModalChild: FunctionComponent<{ + transition: ReturnType>; + isOpen: boolean; close: () => void; - align: "center" | "bottom" | "left"; + align: "center" | "bottom" | "left" | "right"; maxHeight?: string; onCloseTransitionEnd: () => void; forceNotUseSimplebar?: boolean; + forceNotOverflowAuto?: boolean; disableBackdrop?: boolean; }> = ({ children, + transition, align, maxHeight, isOpen, close, onCloseTransitionEnd, forceNotUseSimplebar, + forceNotOverflowAuto, disableBackdrop, }) => { - const transition = useSpringValue(0, { - config: defaultSpringConfig, - }); - const onCloseTransitionEndRef = useRef(onCloseTransitionEnd); onCloseTransitionEndRef.current = onCloseTransitionEnd; useLayoutEffect(() => { @@ -140,7 +148,7 @@ const ModalChild: FunctionComponent<{ style: React.ComponentProps["style"], children: any ) => { - if (align === "left" || forceNotUseSimplebar) { + if (align === "left" || align === "right" || forceNotUseSimplebar) { // align left는 사실 sidebar로만 쓰이는데... // SimpleBar를 사용하면 height를 결정하기 힘든 문제가 있어서 대충 처리한다 return ( @@ -172,7 +180,7 @@ const ModalChild: FunctionComponent<{ right: 0, ...(() => { - if (align === "left") { + if (align === "left" || align === "right") { return; } @@ -232,9 +240,11 @@ const ModalChild: FunctionComponent<{ // 화면을 다 가릴수는 없게 만든다. // align이 left일때는 (사실은 sidebar에서만 left align이 사용됨) // 그냥 냅두고 알아서 처리하게 한다. - maxHeight: align !== "left" ? maxHeight || "85vh" : undefined, + maxHeight: !(align === "left" || align === "right") + ? maxHeight || "85vh" + : undefined, - overflow: "auto", + overflow: forceNotOverflowAuto ? undefined : "auto", // root의 pointer events가 "none"으로 설정된다. // 하지만 당연히 컨텐츠 자체는 pointer events를 받아야 하므로 여기서 "auto"로 설정한다. @@ -256,6 +266,17 @@ const ModalChild: FunctionComponent<{ }; } + if (align === "right") { + return { + top: 0, + bottom: 0, + + transform: transition.to( + (t) => `translateX(${(1 - t) * 100}%)` + ), + }; + } + return { bottom: align === "center" diff --git a/packages/extension/src/components/modal/types.ts b/packages/extension/src/components/modal/types.ts index ac057fb539..a081068fa7 100644 --- a/packages/extension/src/components/modal/types.ts +++ b/packages/extension/src/components/modal/types.ts @@ -6,11 +6,12 @@ export interface ModalProps { // XXX: 세로로 애니메이션할 때는 height를 결정지을 수 없다. // 그러므로 center, bottom일 때는 screen height에 대해서 상대적으로 설정할 수 없다. - align: "center" | "bottom" | "left"; + align: "center" | "bottom" | "left" | "right"; maxHeight?: string; forceNotUseSimplebar?: boolean; + forceNotOverflowAuto?: boolean; disableBackdrop?: boolean; } diff --git a/packages/extension/src/pages/main/available.tsx b/packages/extension/src/pages/main/available.tsx index 162b45b4f5..373cc71def 100644 --- a/packages/extension/src/pages/main/available.tsx +++ b/packages/extension/src/pages/main/available.tsx @@ -168,6 +168,8 @@ export const AvailableTabView: FunctionComponent<{ const [tokenDetailModal, setTokenDetailModal] = useState< { chainId: string; coinMinimalDenom: string } | undefined >(undefined); + // modal의 close transition을 유지하기 위해서는 tokenDetailModal != null 같은 방식으로만 open/close를 처리하면 안된다... + const [isTokenDetailModalOpen, setIsTokenDetailModalOpen] = useState(false); return ( @@ -238,13 +240,14 @@ export const AvailableTabView: FunctionComponent<{ + onClick={() => { setTokenDetailModal({ chainId: viewToken.chainInfo.chainId, coinMinimalDenom: viewToken.token.currency.coinMinimalDenom, - }) - } + }); + setIsTokenDetailModalOpen(true); + }} copyAddress={(() => { // For only native tokens, show copy address button if ( @@ -349,15 +352,17 @@ export const AvailableTabView: FunctionComponent<{ setTokenDetailModal(undefined)} - maxHeight="100vh" - disableBackdrop={true} + isOpen={tokenDetailModal != null && isTokenDetailModalOpen} + align="right" + close={() => setIsTokenDetailModalOpen(false)} + onCloseTransitionEnd={() => { + setTokenDetailModal(undefined); + }} + forceNotOverflowAuto={true} > {tokenDetailModal ? ( setTokenDetailModal(undefined)} + close={() => setIsTokenDetailModalOpen(false)} chainId={tokenDetailModal.chainId} coinMinimalDenom={tokenDetailModal.coinMinimalDenom} /> diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts index 5c0769b867..1a024cff11 100644 --- a/packages/extension/src/pages/main/token-detail/constants.ts +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -7,4 +7,5 @@ export const Relations = [ "custom/merged-claim-rewards", ]; -export const PaginationLimit = 5; +// TODO: 지금은 scroll to fetch 테스트 용으로 값이 좀 작은거고 나중에는 20으로 올려야함. +export const PaginationLimit = 8; diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index c8c39b099b..b9421ebef8 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -1,11 +1,10 @@ import React, { FunctionComponent, useRef } from "react"; import { observer } from "mobx-react-lite"; import styled from "styled-components"; -import { HeaderHeight } from "../../../layouts/header"; import { Box } from "../../../components/box"; import { XAxis, YAxis } from "../../../components/axis"; import { useStore } from "../../../stores"; -import { Body1, Subtitle3 } from "../../../components/typography"; +import { Body1, Body3, Subtitle3 } from "../../../components/typography"; import { ColorPalette } from "../../../styles"; import { Gutter } from "../../../components/gutter"; import { usePaginatedCursorQuery } from "./hook"; @@ -15,20 +14,26 @@ import { MsgItemRender } from "./msg-items"; import SimpleBar from "simplebar-react"; import { PaginationLimit, Relations } from "./constants"; import SimpleBarCore from "simplebar-core"; +import { HeaderHeight } from "../../../layouts/header"; const Styles = { Container: styled.div` - height: calc(100vh - ${HeaderHeight}); + height: 100vh; background-color: ${ColorPalette["gray-700"]}; - border-top-style: solid; - border-top-width: 1px; - border-top-color: ${ColorPalette["gray-500"]}; + display: flex; + flex-direction: column; + `, + Header: styled.div` + height: ${HeaderHeight}; display: flex; flex-direction: column; `, + Body: styled.div` + height: calc(100vh - ${HeaderHeight}); + `, Balance: styled.div` font-weight: 500; font-size: 1.75rem; @@ -53,6 +58,109 @@ export const TokenDetailModal: FunctionComponent<{ ) .getBalance(currency); + const buttons: { + icon: React.ReactElement; + text: string; + onClick: () => void; + }[] = [ + { + icon: ( + + + + + ), + text: "Buy", + onClick: () => { + // TODO: noop yet + }, + }, + { + icon: ( + + + + + ), + text: "Receive", + onClick: () => { + // TODO: noop yet + }, + }, + { + icon: ( + + + + + ), + text: "Swap", + onClick: () => { + // TODO: noop yet + }, + }, + { + icon: ( + + + + + ), + text: "Send", + onClick: () => { + // TODO: noop yet + }, + }, + ]; + const msgHistory = usePaginatedCursorQuery( process.env["KEPLR_EXT_TX_HISTORY_BASE_URL"], () => { @@ -95,24 +203,41 @@ export const TokenDetailModal: FunctionComponent<{ return ( - + { - e.preventDefault(); - - close(); + style={{ + flex: 1, }} alignY="center" + paddingX="1.25rem" > + { + e.preventDefault(); + + close(); + }} + > + + + +
{(() => { @@ -125,81 +250,153 @@ export const TokenDetailModal: FunctionComponent<{ })()}
+ {/* 뒤로가기 버튼과 좌우를 맞추기 위해서 존재... */} + - - -
TODO: Address chip
- - - - - {(() => { - if (!balance) { - return `0 ${currency.coinDenom}`; - } - - return balance.balance - .maxDecimals(6) - .inequalitySymbol(true) - .shrink(true) - .hideIBCMetadata(true) - .toString(); - })()} - + + + - - {(() => { - if (!balance) { - return "-"; - } - const price = priceStore.calculatePrice(balance.balance); - if (price) { - return price.toString(); - } - return "-"; - })()} - - - - - - - {(() => { - if ( - msgHistory.pages.length === 0 || - !msgHistory.pages[0].response - ) { - return null; - } - - const allMsgs: ResMsgsHistory["msgs"][0][] = []; - for (const page of msgHistory.pages) { - if (page.response) { - for (const msg of page.response.msgs) { - allMsgs.push(msg); - } +
TODO: Address chip
+ + + + {(() => { + if (!balance) { + return `0 ${currency.coinDenom}`; } - } - return allMsgs.map((msg) => { + return balance.balance + .maxDecimals(6) + .inequalitySymbol(true) + .shrink(true) + .hideIBCMetadata(true) + .toString(); + })()} + + + + {(() => { + if (!balance) { + return "-"; + } + const price = priceStore.calculatePrice(balance.balance); + if (price) { + return price.toString(); + } + return "-"; + })()} + + + + + + + {buttons.map((obj, i) => { return ( - + + + { + e.preventDefault(); + + obj.onClick(); + }} + > + + {obj.icon} + + + + + {obj.text} + + + + + + {i === buttons.length - 1 ? ( + + ) : null} + ); - }); - })()} -
-
- + })} + + + + + + + + + + + + Staked Balance + + TODO + + + + + + + + + {(() => { + if ( + msgHistory.pages.length === 0 || + !msgHistory.pages[0].response + ) { + return null; + } + + const allMsgs: ResMsgsHistory["msgs"][0][] = []; + for (const page of msgHistory.pages) { + if (page.response) { + for (const msg of page.response.msgs) { + allMsgs.push(msg); + } + } + } + + return allMsgs.map((msg) => { + return ( + + ); + }); + })()} + + + + ); }); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx index 5055c9f89c..fe522f33e3 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx @@ -44,6 +44,8 @@ export const MsgItemBase: FunctionComponent<{ borderRadius="0.375rem" paddingX="1rem" paddingY="0.875rem" + minHeight="4rem" + alignY="center" >
logo
From dbd852525b1cf58b0fdaf91d15a359ab37c92845 Mon Sep 17 00:00:00 2001 From: HeesungB Date: Wed, 13 Mar 2024 14:06:02 +0900 Subject: [PATCH 06/72] Fix typo `delegate` to `stake` --- packages/extension/src/languages/en.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/extension/src/languages/en.json b/packages/extension/src/languages/en.json index 92c54b1756..e9e719faa6 100644 --- a/packages/extension/src/languages/en.json +++ b/packages/extension/src/languages/en.json @@ -418,12 +418,12 @@ "page.sign.components.messages.custom.title": "Custom", "page.sign.components.messages.claim-rewards.title": "Claim Rewards", "page.sign.components.messages.claim-rewards.paragraph": "Claim staking reward from {validator}", - "page.sign.components.messages.delegate.title": "Delegate", - "page.sign.components.messages.delegate.paragraph": "Delegate {amount} to {validator}", + "page.sign.components.messages.delegate.title": "Stake", + "page.sign.components.messages.delegate.paragraph": "Stake {amount} to {validator}", "page.sign.components.messages.execute-wasm-contract.title": "Execute Wasm Contract", "page.sign.components.messages.execute-wasm-contract.paragraph": "Execute contract {address} by sending {sent}", - "page.sign.components.messages.redelegate.title": "Redelegate", - "page.sign.components.messages.redelegate.paragraph": "Redelegate {coin} from {from} to {to}", + "page.sign.components.messages.redelegate.title": "Switch Validator", + "page.sign.components.messages.redelegate.paragraph": "Switch Validator from {from} to {to} for {coin}", "page.sign.components.messages.send.title": "Send", "page.sign.components.messages.send.paragraph": "{address} will receive {amount}", "page.sign.components.messages.transfer.title": "IBC Transfer", @@ -434,8 +434,8 @@ "page.sign.components.messages.pay-packet-fee.paragraph": "Spend {total} to pay fees for relaying IBC packet to {channelId}", "page.sign.components.messages.pay-packet-fee.close-button": "Close", "page.sign.components.messages.pay-packet-fee.open-button": "Details", - "page.sign.components.messages.undelegate.title": "Undelegate", - "page.sign.components.messages.undelegate.paragraph": "Undelegate {coin} from {from} Asset will be liquid after unbonding period", + "page.sign.components.messages.undelegate.title": "Unstake", + "page.sign.components.messages.undelegate.paragraph": "Unstake {coin} from {from} Asset will be liquid after unbonding period", "page.sign.components.messages.vote.title": "Vote", "page.sign.components.messages.vote.empty": "Empty", "page.sign.components.messages.vote.yes": "Yes", @@ -503,7 +503,7 @@ "page.main.available.get-started-button": "Get Started", "page.main.available.new-token-found": "{numFoundToken} new token(s) found", "page.main.staked.staked-balance-title": "Staked Balance", - "page.main.staked.staked-balance-tooltip": "Tokens that are delegated to validators and earning rewards.", + "page.main.staked.staked-balance-tooltip": "Tokens that are staked to validators and earning rewards.", "page.main.staked.unstaking-balance-title": "Unstaking Balance", "page.main.staked.unstaking-balance-tooltip": "Tokens that are under the unbonding period.", "page.main.staked.empty-view-title": "Ready to Start Staking?", From e626e294d0814da6adaf58225ac3ed335f45caa5 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Tue, 12 Mar 2024 16:51:24 +0900 Subject: [PATCH 07/72] Some fixes --- .../extension/src/pages/main/available.tsx | 80 ++++++++--- .../src/pages/main/token-detail/hook.ts | 14 +- .../src/pages/main/token-detail/messages.tsx | 105 ++++++++++++++ .../src/pages/main/token-detail/modal.tsx | 128 ++++++++++-------- .../pages/main/token-detail/token-info.tsx | 42 ++++++ .../src/pages/main/token-detail/types.ts | 2 +- 6 files changed, 290 insertions(+), 81 deletions(-) create mode 100644 packages/extension/src/pages/main/token-detail/messages.tsx create mode 100644 packages/extension/src/pages/main/token-detail/token-info.tsx diff --git a/packages/extension/src/pages/main/available.tsx b/packages/extension/src/pages/main/available.tsx index 373cc71def..308da2a01c 100644 --- a/packages/extension/src/pages/main/available.tsx +++ b/packages/extension/src/pages/main/available.tsx @@ -28,6 +28,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import styled, { useTheme } from "styled-components"; import { DenomHelper } from "@keplr-wallet/common"; import { TokenDetailModal } from "./token-detail"; +import { useSearchParams } from "react-router-dom"; const zeroDec = new Dec(0); @@ -165,11 +166,23 @@ export const AvailableTabView: FunctionComponent<{ const isShowNotFound = allBalancesSearchFiltered.length === 0 && trimSearch.length > 0; - const [tokenDetailModal, setTokenDetailModal] = useState< - { chainId: string; coinMinimalDenom: string } | undefined - >(undefined); - // modal의 close transition을 유지하기 위해서는 tokenDetailModal != null 같은 방식으로만 open/close를 처리하면 안된다... - const [isTokenDetailModalOpen, setIsTokenDetailModalOpen] = useState(false); + const [searchParams, setSearchParams] = useSearchParams(); + + const tokenDetailInfo: { + chainId: string | null; + coinMinimalDenom: string | null; + isTokenDetailModalOpen: boolean | null; + } = (() => { + return { + chainId: searchParams.get("tokenChainId"), + coinMinimalDenom: searchParams.get("tokenCoinMinimalDenom"), + // modal의 close transition을 유지하기 위해서는 위의 두 field가 존재하는지 만으로 판단하면 안된다... + // close transition이 끝난후에 위의 두 값을 지워줘야한다. + // close가 될 것인지는 밑의 값으로 판단한다. + isTokenDetailModalOpen: + searchParams.get("isTokenDetailModalOpen") === "true", + }; + })(); return ( @@ -241,12 +254,19 @@ export const AvailableTabView: FunctionComponent<{ viewToken={viewToken} key={`${viewToken.chainInfo.chainId}-${viewToken.token.currency.coinMinimalDenom}`} onClick={() => { - setTokenDetailModal({ - chainId: viewToken.chainInfo.chainId, - coinMinimalDenom: - viewToken.token.currency.coinMinimalDenom, + setSearchParams((prev) => { + prev.set( + "tokenChainId", + viewToken.chainInfo.chainId + ); + prev.set( + "tokenCoinMinimalDenom", + viewToken.token.currency.coinMinimalDenom + ); + prev.set("isTokenDetailModalOpen", "true"); + + return prev; }); - setIsTokenDetailModalOpen(true); }} copyAddress={(() => { // For only native tokens, show copy address button @@ -352,19 +372,45 @@ export const AvailableTabView: FunctionComponent<{ 0 && + tokenDetailInfo.coinMinimalDenom != null && + tokenDetailInfo.coinMinimalDenom.length > 0 && + tokenDetailInfo.isTokenDetailModalOpen === true + } align="right" - close={() => setIsTokenDetailModalOpen(false)} + close={() => { + setSearchParams((prev) => { + prev.delete("isTokenDetailModalOpen"); + + return prev; + }); + }} onCloseTransitionEnd={() => { - setTokenDetailModal(undefined); + setSearchParams((prev) => { + prev.delete("tokenChainId"); + prev.delete("tokenCoinMinimalDenom"); + + return prev; + }); }} forceNotOverflowAuto={true} > - {tokenDetailModal ? ( + {tokenDetailInfo.chainId != null && + tokenDetailInfo.chainId.length > 0 && + tokenDetailInfo.coinMinimalDenom != null && + tokenDetailInfo.coinMinimalDenom.length > 0 ? ( setIsTokenDetailModalOpen(false)} - chainId={tokenDetailModal.chainId} - coinMinimalDenom={tokenDetailModal.coinMinimalDenom} + close={() => { + setSearchParams((prev) => { + prev.delete("isTokenDetailModalOpen"); + + return prev; + }); + }} + chainId={tokenDetailInfo.chainId} + coinMinimalDenom={tokenDetailInfo.coinMinimalDenom} /> ) : null} diff --git a/packages/extension/src/pages/main/token-detail/hook.ts b/packages/extension/src/pages/main/token-detail/hook.ts index c29483d62e..f2906b9f86 100644 --- a/packages/extension/src/pages/main/token-detail/hook.ts +++ b/packages/extension/src/pages/main/token-detail/hook.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; export const usePaginatedCursorQuery = ( @@ -75,9 +75,11 @@ export const usePaginatedCursorQuery = ( }); }, [isFetching, responses]); - return { - isFetching, - pages: !responses ? [] : responses.map((r) => ({ response: r })), - next, - }; + return useMemo(() => { + return { + isFetching, + pages: !responses ? [] : responses.map((r) => ({ response: r })), + next, + }; + }, [isFetching, next, responses]); }; diff --git a/packages/extension/src/pages/main/token-detail/messages.tsx b/packages/extension/src/pages/main/token-detail/messages.tsx new file mode 100644 index 0000000000..1f2f5fc550 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/messages.tsx @@ -0,0 +1,105 @@ +import React, { FunctionComponent } from "react"; +import { usePaginatedCursorQuery } from "./hook"; +import { ResMsgsHistory } from "./types"; +import { Stack } from "../../../components/stack"; +import { MsgItemRender } from "./msg-items"; +import { Box } from "../../../components/box"; +import { Subtitle4 } from "../../../components/typography"; +import { ColorPalette } from "../../../styles"; +import { FormattedDate } from "react-intl"; + +export const RenderMessages: FunctionComponent<{ + msgHistory: ReturnType>; + targetDenom: string; +}> = ({ msgHistory, targetDenom }) => { + const msgsPerDaily: { + year: number; + month: number; + day: number; + msgs: ResMsgsHistory["msgs"]; + }[] = (() => { + if (msgHistory.pages.length === 0) { + return []; + } + + const res: { + year: number; + month: number; + day: number; + msgs: ResMsgsHistory["msgs"]; + }[] = []; + + // prop 자체로부터 이미 내림차순된 채로 온다고 가정하고 작성한다. + for (const page of msgHistory.pages) { + if (page.response) { + for (const msg of page.response.msgs) { + if (res.length === 0) { + const time = new Date(msg.msg.time); + res.push({ + year: time.getFullYear(), + month: time.getMonth(), + day: time.getDate(), + msgs: [msg], + }); + } else { + const last = res[res.length - 1]; + const time = new Date(msg.msg.time); + if ( + last.year === time.getFullYear() && + last.month === time.getMonth() && + last.day === time.getDate() + ) { + last.msgs.push(msg); + } else { + res.push({ + year: time.getFullYear(), + month: time.getMonth(), + day: time.getDate(), + msgs: [msg], + }); + } + } + } + } + } + + return res; + })(); + + return ( + + {msgsPerDaily.map((msgs, i) => { + return ( + + + + + + + + {msgs.msgs.map((msg) => { + return ( + + ); + })} + + + ); + })} + + ); +}; diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index b9421ebef8..5c6ecd5a74 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -9,12 +9,13 @@ import { ColorPalette } from "../../../styles"; import { Gutter } from "../../../components/gutter"; import { usePaginatedCursorQuery } from "./hook"; import { ResMsgsHistory } from "./types"; -import { Stack } from "../../../components/stack"; -import { MsgItemRender } from "./msg-items"; import SimpleBar from "simplebar-react"; import { PaginationLimit, Relations } from "./constants"; import SimpleBarCore from "simplebar-core"; import { HeaderHeight } from "../../../layouts/header"; +import { useNavigate } from "react-router"; +import { TokenInfos } from "./token-info"; +import { RenderMessages } from "./messages"; const Styles = { Container: styled.div` @@ -58,6 +59,8 @@ export const TokenDetailModal: FunctionComponent<{ ) .getBalance(currency); + const navigate = useNavigate(); + const buttons: { icon: React.ReactElement; text: string; @@ -156,7 +159,9 @@ export const TokenDetailModal: FunctionComponent<{ ), text: "Send", onClick: () => { - // TODO: noop yet + navigate( + `/send?chainId=${chainId}&coinMinimalDenom=${coinMinimalDenom}` + ); }, }, ]; @@ -338,63 +343,72 @@ export const TokenDetailModal: FunctionComponent<{
- - - - - - - - Staked Balance - - TODO - - - - + {chainInfo.stakeCurrency && + chainInfo.stakeCurrency.coinMinimalDenom === currency.coinMinimalDenom + ? (() => { + const queryDelegation = queriesStore + .get(chainId) + .cosmos.queryDelegations.getQueryBech32Address( + accountStore.getAccount(chainId).bech32Address + ); - - - - {(() => { - if ( - msgHistory.pages.length === 0 || - !msgHistory.pages[0].response - ) { - return null; - } + return ( + + + + + + + + + + Staked Balance + + + + {queryDelegation.total + ? queryDelegation.total + .maxDecimals(6) + .shrink(true) + .inequalitySymbol(true) + .trim(true) + .toString() + : "-"} + + + + + + + ); + })() + : null} - const allMsgs: ResMsgsHistory["msgs"][0][] = []; - for (const page of msgHistory.pages) { - if (page.response) { - for (const msg of page.response.msgs) { - allMsgs.push(msg); - } - } - } + + - return allMsgs.map((msg) => { - return ( - - ); - }); - })()} - - + + diff --git a/packages/extension/src/pages/main/token-detail/token-info.tsx b/packages/extension/src/pages/main/token-detail/token-info.tsx new file mode 100644 index 0000000000..7e464d4500 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/token-info.tsx @@ -0,0 +1,42 @@ +import React, { FunctionComponent } from "react"; +import { Box } from "../../../components/box"; +import { ColorPalette } from "../../../styles"; +import { Subtitle3, Subtitle4 } from "../../../components/typography"; +import { Stack } from "../../../components/stack"; +import { XAxis } from "../../../components/axis"; + +export const TokenInfos: FunctionComponent<{ + title: string; + infos: { + title: string; + text: string; + }[]; +}> = ({ title, infos }) => { + return ( + + + {title} + + + {infos.map((info, i) => { + return ( + + + + {info.title} + +
+ {info.text} + + + ); + })} + + + ); +}; diff --git a/packages/extension/src/pages/main/token-detail/types.ts b/packages/extension/src/pages/main/token-detail/types.ts index 8a5a41cede..b22c6e317b 100644 --- a/packages/extension/src/pages/main/token-detail/types.ts +++ b/packages/extension/src/pages/main/token-detail/types.ts @@ -11,7 +11,7 @@ export interface MsgHistory { code: number; height: number; - time: Date; + time: string; chainId: string; chainIdentifier: string; From a2497ba002203fa1bcecc959224b0177299239a0 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 18 Mar 2024 21:00:37 +0900 Subject: [PATCH 08/72] =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/token-detail/msg-items/base.tsx | 169 +++++++++--------- .../main/token-detail/msg-items/delegate.tsx | 24 +++ .../main/token-detail/msg-items/logo.tsx | 27 +++ .../msg-items/merged-claim-rewards.tsx | 16 ++ .../main/token-detail/msg-items/receive.tsx | 22 +++ .../main/token-detail/msg-items/send.tsx | 22 +++ .../token-detail/msg-items/undelegate.tsx | 25 +++ 7 files changed, 223 insertions(+), 82 deletions(-) create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/logo.tsx diff --git a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx index fe522f33e3..dcf9996c52 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx @@ -9,99 +9,104 @@ import { useStore } from "../../../../stores"; import { CoinPretty, Dec, PricePretty } from "@keplr-wallet/unit"; export const MsgItemBase: FunctionComponent<{ + logo: React.ReactElement; chainId: string; title: string; paragraph?: string; amount: CoinPretty; prices: Record | undefined>; targetDenom: string; -}> = observer(({ chainId, title, paragraph, amount, prices, targetDenom }) => { - const { chainStore, priceStore } = useStore(); +}> = observer( + ({ logo, chainId, title, paragraph, amount, prices, targetDenom }) => { + const { chainStore, priceStore } = useStore(); - const chainInfo = chainStore.getChain(chainId); + const chainInfo = chainStore.getChain(chainId); - // mobx와 useMemo의 조합 문제로... 값 몇개를 밖으로 뺀다. - const foundCurrency = chainInfo.findCurrency(targetDenom); - const defaultVsCurrency = priceStore.defaultVsCurrency; - const sendAmountPricePretty = useMemo(() => { - if (foundCurrency && foundCurrency.coinGeckoId) { - const price = prices[foundCurrency.coinGeckoId]; - if (price != null && price[defaultVsCurrency] != null) { - const dec = amount.toDec(); - const priceDec = new Dec(price[defaultVsCurrency]!.toString()); - const fiatCurrency = priceStore.getFiatCurrency(defaultVsCurrency); - if (fiatCurrency) { - return new PricePretty(fiatCurrency, dec.mul(priceDec)); + // mobx와 useMemo의 조합 문제로... 값 몇개를 밖으로 뺀다. + const foundCurrency = chainInfo.findCurrency(targetDenom); + const defaultVsCurrency = priceStore.defaultVsCurrency; + const sendAmountPricePretty = useMemo(() => { + if (foundCurrency && foundCurrency.coinGeckoId) { + const price = prices[foundCurrency.coinGeckoId]; + if (price != null && price[defaultVsCurrency] != null) { + const dec = amount.toDec(); + const priceDec = new Dec(price[defaultVsCurrency]!.toString()); + const fiatCurrency = priceStore.getFiatCurrency(defaultVsCurrency); + if (fiatCurrency) { + return new PricePretty(fiatCurrency, dec.mul(priceDec)); + } } } - } - return; - }, [defaultVsCurrency, foundCurrency, priceStore, prices, amount]); + return; + }, [defaultVsCurrency, foundCurrency, priceStore, prices, amount]); - return ( - - -
logo
-
- - - {title} - {paragraph ? ( - - - {paragraph} - - ) : null} - + return ( + + + + {logo} + +
+ + + {title} + {paragraph ? ( + + + {paragraph} + + ) : null} + -
- - - {amount - .maxDecimals(2) - .shrink(true) - .hideIBCMetadata(true) - .inequalitySymbol(true) - .toString()} - - {sendAmountPricePretty ? ( - - - - {sendAmountPricePretty.toString()} - - - ) : null} - - -
-
- - ); -}); + /> + + + {amount + .maxDecimals(2) + .shrink(true) + .hideIBCMetadata(true) + .inequalitySymbol(true) + .toString()} + + {sendAmountPricePretty ? ( + + + + {sendAmountPricePretty.toString()} + + + ) : null} + + +
+
+
+ ); + } +); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx b/packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx index 0e1143a55d..4533d8b296 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/delegate.tsx @@ -5,6 +5,8 @@ import { useStore } from "../../../../stores"; import { CoinPretty } from "@keplr-wallet/unit"; import { MsgItemBase } from "./base"; import { Staking } from "@keplr-wallet/stores"; +import { ItemLogo } from "./logo"; +import { ColorPalette } from "../../../../styles"; export const MsgRelationDelegate: FunctionComponent<{ msg: MsgHistory; @@ -63,6 +65,28 @@ export const MsgRelationDelegate: FunctionComponent<{ return ( + + + + } + /> + } chainId={msg.chainId} title="Stake" paragraph={moniker} diff --git a/packages/extension/src/pages/main/token-detail/msg-items/logo.tsx b/packages/extension/src/pages/main/token-detail/msg-items/logo.tsx new file mode 100644 index 0000000000..0dc23825a3 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/logo.tsx @@ -0,0 +1,27 @@ +import React, { FunctionComponent } from "react"; +import { Box } from "../../../../components/box"; +import { ColorPalette } from "../../../../styles"; + +export const ItemLogo: FunctionComponent<{ + center: React.ReactElement; + backgroundColor?: string; +}> = ({ center, backgroundColor }) => { + return ( + +
+ {center} +
+
+ ); +}; diff --git a/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx b/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx index ee11233d6a..c4ce85a5f1 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite"; import { useStore } from "../../../../stores"; import { CoinPretty } from "@keplr-wallet/unit"; import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; export const MsgRelationMergedClaimRewards: FunctionComponent<{ msg: MsgHistory; @@ -43,6 +44,21 @@ export const MsgRelationMergedClaimRewards: FunctionComponent<{ return ( + TODO + + } + /> + } chainId={msg.chainId} title="Claim Reward" amount={amountPretty} diff --git a/packages/extension/src/pages/main/token-detail/msg-items/receive.tsx b/packages/extension/src/pages/main/token-detail/msg-items/receive.tsx index de8ab7a104..e5907b4668 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/receive.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/receive.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react-lite"; import { useStore } from "../../../../stores"; import { CoinPretty } from "@keplr-wallet/unit"; import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; export const MsgRelationReceive: FunctionComponent<{ msg: MsgHistory; @@ -41,6 +42,27 @@ export const MsgRelationReceive: FunctionComponent<{ return ( + + + } + /> + } chainId={msg.chainId} title="Receive" paragraph={fromAddress} diff --git a/packages/extension/src/pages/main/token-detail/msg-items/send.tsx b/packages/extension/src/pages/main/token-detail/msg-items/send.tsx index 83a4072410..4a9bdba763 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/send.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/send.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react-lite"; import { useStore } from "../../../../stores"; import { CoinPretty } from "@keplr-wallet/unit"; import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; export const MsgRelationSend: FunctionComponent<{ msg: MsgHistory; @@ -41,6 +42,27 @@ export const MsgRelationSend: FunctionComponent<{ return ( + + + } + /> + } chainId={msg.chainId} title="Send" paragraph={toAddress} diff --git a/packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx b/packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx index 7c584df936..a8db8c9555 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/undelegate.tsx @@ -5,6 +5,8 @@ import { useStore } from "../../../../stores"; import { CoinPretty } from "@keplr-wallet/unit"; import { MsgItemBase } from "./base"; import { Staking } from "@keplr-wallet/stores"; +import { ColorPalette } from "../../../../styles"; +import { ItemLogo } from "./logo"; export const MsgRelationUndelegate: FunctionComponent<{ msg: MsgHistory; @@ -63,6 +65,29 @@ export const MsgRelationUndelegate: FunctionComponent<{ return ( + + + + } + /> + } chainId={msg.chainId} title="Unstake" paragraph={moniker} From 0f3768e27e96c4160e97bbd9cba0a01caadbcc33 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 18 Mar 2024 21:36:37 +0900 Subject: [PATCH 09/72] =?UTF-8?q?"ibc-send"=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/main/token-detail/constants.ts | 1 + .../main/token-detail/msg-items/ibc-send.tsx | 131 ++++++++++++++++++ .../main/token-detail/msg-items/index.tsx | 10 ++ .../main/token-detail/msg-items/logo.tsx | 19 ++- .../src/pages/main/token-detail/types.ts | 27 ++++ 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts index 1a024cff11..e98268ab2c 100644 --- a/packages/extension/src/pages/main/token-detail/constants.ts +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -1,6 +1,7 @@ export const Relations = [ "send", "receive", + "ibc-send", "delegate", "undelegate", "redelegate", diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx new file mode 100644 index 0000000000..3567b0d9a1 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx @@ -0,0 +1,131 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { Bech32Address } from "@keplr-wallet/cosmos"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; +import { Buffer } from "buffer/"; +import { ChainInfo } from "@keplr-wallet/types"; +import { ChainImageFallback } from "../../../../components/image"; + +export const MsgRelationIBCSend: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const sendAmountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const token = (msg.msg as any)["token"] as { + denom: string; + amount: string; + }; + + if (token.denom !== targetDenom) { + return new CoinPretty(currency, "0"); + } + return new CoinPretty(currency, token.amount); + }, [chainInfo, msg.msg, targetDenom]); + + const toAddress = (() => { + if (!msg.ibcTracking) { + return "Unknown"; + } + + try { + let res = Bech32Address.shortenAddress((msg.msg as any)["receiver"], 22); + const packetData = Buffer.from( + msg.ibcTracking.originPacket, + "base64" + ).toString(); + const parsed = JSON.parse(packetData); + let obj: any = (() => { + if (!parsed.memo) { + return undefined; + } + + typeof parsed.memo === "string" ? JSON.parse(parsed.memo) : parsed.memo; + })(); + + while (obj) { + if (obj.receiver) { + res = Bech32Address.shortenAddress(obj.receiver, 22); + } + obj = typeof obj.next === "string" ? JSON.parse(obj.next) : obj.next; + } + + return res; + } catch (e) { + console.log(e); + return "Unknown"; + } + })(); + + const destinationChain: ChainInfo | undefined = (() => { + if (!msg.ibcTracking) { + return undefined; + } + + try { + let res: ChainInfo | undefined = undefined; + for (const path of msg.ibcTracking.paths) { + if (!path.clientChainId) { + return undefined; + } + if (!chainStore.hasChain(path.clientChainId)) { + return undefined; + } + + res = chainStore.getChain(path.clientChainId); + } + + return res; + } catch (e) { + console.log(e); + return undefined; + } + })(); + + return ( + + + + } + deco={ + destinationChain ? ( + + ) : undefined + } + /> + } + chainId={msg.chainId} + title="IBC Send" + paragraph={toAddress} + amount={sendAmountPretty} + prices={prices || {}} + targetDenom={targetDenom} + /> + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx index 1471a2b3f7..b6410012de 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx @@ -5,6 +5,7 @@ import { MsgRelationReceive } from "./receive"; import { MsgRelationDelegate } from "./delegate"; import { MsgRelationUndelegate } from "./undelegate"; import { MsgRelationMergedClaimRewards } from "./merged-claim-rewards"; +import { MsgRelationIBCSend } from "./ibc-send"; export const MsgItemRender: FunctionComponent<{ msg: MsgHistory; @@ -26,6 +27,15 @@ export const MsgItemRender: FunctionComponent<{ /> ); } + case "ibc-send": { + return ( + + ); + } case "delegate": { return ( = ({ center, backgroundColor }) => { +}> = ({ center, deco, backgroundColor }) => { return ( {center}
+ {deco ? ( +
+ {deco} +
+ ) : null}
); }; diff --git a/packages/extension/src/pages/main/token-detail/types.ts b/packages/extension/src/pages/main/token-detail/types.ts index b22c6e317b..82f25233a3 100644 --- a/packages/extension/src/pages/main/token-detail/types.ts +++ b/packages/extension/src/pages/main/token-detail/types.ts @@ -24,4 +24,31 @@ export interface MsgHistory { search: string; denoms?: string[]; meta: Record; + + ibcTracking?: { + chainId: string; + chainIdentifier: string; + txHeight: number; + originPortId: string; + originChannelId: string; + originSequence: number; + paths: { + status: "pending" | "success" | "refunded" | "failed" | "unknown-result"; + chainId?: string; + chainIdentifier?: string; + portId: string; + channelId: string; + sequence?: number; + + counterpartyChannelId?: string; + counterpartyPortId?: string; + clientChainId?: string; + clientChainIdentifier?: string; + + clientFetched: boolean; + }[]; + + // base64 encoded + originPacket: string; + }; } From cd84cf88c71c6c20afcd37b7565349db849f88e7 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 18 Mar 2024 22:31:13 +0900 Subject: [PATCH 10/72] =?UTF-8?q?IBC=20send=20refunded=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/common/src/coin/index.ts | 32 ++++++ packages/common/src/index.ts | 1 + .../src/pages/main/token-detail/constants.ts | 1 + .../msg-items/ibc-send-refunded.tsx | 97 +++++++++++++++++++ .../main/token-detail/msg-items/index.tsx | 10 ++ .../msg-items/merged-claim-rewards.tsx | 13 +-- 6 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 packages/common/src/coin/index.ts create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx diff --git a/packages/common/src/coin/index.ts b/packages/common/src/coin/index.ts new file mode 100644 index 0000000000..ce23a3e4b1 --- /dev/null +++ b/packages/common/src/coin/index.ts @@ -0,0 +1,32 @@ +export function isValidCoinStr(str: string): boolean { + const split = str.split(/^([0-9]+)(\s)*([a-zA-Z][a-zA-Z0-9/-]*)$/); + + if (split.length === 5) { + if ( + split[1].length > 0 && + split[3].length > 0 && + !Number.isNaN(parseInt(split[1])) + ) { + return true; + } + } + + return false; +} + +export function parseCoinStr(str: string): { + denom: string; + amount: string; +} { + const split = str.split(/^([0-9]+)(\s)*([a-zA-Z][a-zA-Z0-9/-]*)$/); + + if (split.length === 5) { + const denom = split[3]; + return { + denom, + amount: split[1], + }; + } + + throw new Error(`Invalid coin string: ${str}`); +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index a3be8dd258..f1d3c18ef7 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -7,3 +7,4 @@ export * from "./json"; export * from "./icns"; export * from "./retry"; export * from "./sleep"; +export * from "./coin"; diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts index e98268ab2c..f5fe888fbb 100644 --- a/packages/extension/src/pages/main/token-detail/constants.ts +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -2,6 +2,7 @@ export const Relations = [ "send", "receive", "ibc-send", + "ibc-send-refunded", "delegate", "undelegate", "redelegate", diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx new file mode 100644 index 0000000000..6400c9b017 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx @@ -0,0 +1,97 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; +import { ChainInfo } from "@keplr-wallet/types"; +import { ChainImageFallback } from "../../../../components/image"; +import { isValidCoinStr, parseCoinStr } from "@keplr-wallet/common"; + +export const MsgRelationIBCSendRefunded: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const sendAmountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const receives = msg.meta["receives"] as string[]; + for (const receive of receives) { + if (isValidCoinStr(receive)) { + const coin = parseCoinStr(receive); + if (coin.denom === targetDenom) { + return new CoinPretty(currency, coin.amount); + } + } + } + + return new CoinPretty(currency, "0"); + }, [chainInfo, msg.meta, targetDenom]); + + const destinationChain: ChainInfo | undefined = (() => { + if (!msg.ibcTracking) { + return undefined; + } + + try { + let res: ChainInfo | undefined = undefined; + for (const path of msg.ibcTracking.paths) { + if (!path.clientChainId) { + return undefined; + } + if (!chainStore.hasChain(path.clientChainId)) { + return undefined; + } + + res = chainStore.getChain(path.clientChainId); + } + + return res; + } catch (e) { + console.log(e); + return undefined; + } + })(); + + return ( + + + + } + deco={ + destinationChain ? ( + + ) : undefined + } + /> + } + chainId={msg.chainId} + title="Send Reverted" + amount={sendAmountPretty} + prices={prices || {}} + targetDenom={targetDenom} + /> + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx index b6410012de..771dc703df 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx @@ -6,6 +6,7 @@ import { MsgRelationDelegate } from "./delegate"; import { MsgRelationUndelegate } from "./undelegate"; import { MsgRelationMergedClaimRewards } from "./merged-claim-rewards"; import { MsgRelationIBCSend } from "./ibc-send"; +import { MsgRelationIBCSendRefunded } from "./ibc-send-refunded"; export const MsgItemRender: FunctionComponent<{ msg: MsgHistory; @@ -36,6 +37,15 @@ export const MsgItemRender: FunctionComponent<{ /> ); } + case "ibc-send-refunded": { + return ( + + ); + } case "delegate": { return ( Date: Tue, 19 Mar 2024 13:42:44 +0900 Subject: [PATCH 11/72] =?UTF-8?q?claim=20rewards=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../msg-items/merged-claim-rewards.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx b/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx index 77a5abdfb9..aad0a0f40f 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/merged-claim-rewards.tsx @@ -6,6 +6,7 @@ import { CoinPretty } from "@keplr-wallet/unit"; import { MsgItemBase } from "./base"; import { ItemLogo } from "./logo"; import { isValidCoinStr, parseCoinStr } from "@keplr-wallet/common"; +import { ColorPalette } from "../../../../styles"; export const MsgRelationMergedClaimRewards: FunctionComponent<{ msg: MsgHistory; @@ -43,15 +44,22 @@ export const MsgRelationMergedClaimRewards: FunctionComponent<{ - TODO + } /> From d94f9a54671bd6ba3e9c214e302784d37d9c627c Mon Sep 17 00:00:00 2001 From: Thunnini Date: Tue, 19 Mar 2024 18:12:26 +0900 Subject: [PATCH 12/72] =?UTF-8?q?"ibc-send-receive"=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/main/token-detail/constants.ts | 1 + .../msg-items/ibc-send-receive.tsx | 116 ++++++++++++++++++ .../main/token-detail/msg-items/ibc-send.tsx | 2 +- .../main/token-detail/msg-items/index.tsx | 10 ++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts index f5fe888fbb..5a2c3e65ce 100644 --- a/packages/extension/src/pages/main/token-detail/constants.ts +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -2,6 +2,7 @@ export const Relations = [ "send", "receive", "ibc-send", + "ibc-send-receive", "ibc-send-refunded", "delegate", "undelegate", diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx new file mode 100644 index 0000000000..b4e125dec2 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx @@ -0,0 +1,116 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; +import { ChainInfo } from "@keplr-wallet/types"; +import { ChainImageFallback } from "../../../../components/image"; +import { isValidCoinStr, parseCoinStr } from "@keplr-wallet/common"; +import { Bech32Address } from "@keplr-wallet/cosmos"; +import { Buffer } from "buffer/"; + +export const MsgRelationIBCSendReceive: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const sendAmountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const receives = msg.meta["receives"] as string[]; + for (const receive of receives) { + if (isValidCoinStr(receive)) { + const coin = parseCoinStr(receive); + if (coin.denom === targetDenom) { + return new CoinPretty(currency, coin.amount); + } + } + } + + return new CoinPretty(currency, "0"); + }, [chainInfo, msg.meta, targetDenom]); + + const fromAddress = (() => { + if (!msg.ibcTracking) { + return "Unknown"; + } + + try { + const packet = JSON.parse( + Buffer.from(msg.ibcTracking.originPacket, "base64").toString() + ); + + return Bech32Address.shortenAddress(packet["sender"], 22); + } catch (e) { + console.log(e); + return "Unknown"; + } + })(); + + const sourceChain: ChainInfo | undefined = (() => { + if (!msg.ibcTracking) { + return undefined; + } + + try { + if (msg.ibcTracking.paths.length > 0) { + const path = msg.ibcTracking.paths[0]; + if (!path.chainId) { + return undefined; + } + if (!chainStore.hasChain(path.chainId)) { + return undefined; + } + return chainStore.getChain(path.chainId); + } + + return undefined; + } catch (e) { + console.log(e); + return undefined; + } + })(); + + return ( + + + + } + deco={ + sourceChain ? ( + + ) : undefined + } + /> + } + chainId={msg.chainId} + title="Send Receive" + paragraph={fromAddress} + amount={sendAmountPretty} + prices={prices || {}} + targetDenom={targetDenom} + /> + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx index 3567b0d9a1..46a46d6afd 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx @@ -121,7 +121,7 @@ export const MsgRelationIBCSend: FunctionComponent<{ /> } chainId={msg.chainId} - title="IBC Send" + title="Send" paragraph={toAddress} amount={sendAmountPretty} prices={prices || {}} diff --git a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx index 771dc703df..2976f1abac 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx @@ -7,6 +7,7 @@ import { MsgRelationUndelegate } from "./undelegate"; import { MsgRelationMergedClaimRewards } from "./merged-claim-rewards"; import { MsgRelationIBCSend } from "./ibc-send"; import { MsgRelationIBCSendRefunded } from "./ibc-send-refunded"; +import { MsgRelationIBCSendReceive } from "./ibc-send-receive"; export const MsgItemRender: FunctionComponent<{ msg: MsgHistory; @@ -37,6 +38,15 @@ export const MsgItemRender: FunctionComponent<{ /> ); } + case "ibc-send-receive": { + return ( + + ); + } case "ibc-send-refunded": { return ( Date: Tue, 19 Mar 2024 18:55:55 +0900 Subject: [PATCH 13/72] =?UTF-8?q?"vote"=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/main/token-detail/constants.ts | 1 + .../main/token-detail/msg-items/base.tsx | 33 +++-- .../main/token-detail/msg-items/index.tsx | 6 + .../main/token-detail/msg-items/vote.tsx | 121 ++++++++++++++++++ 4 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/vote.tsx diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts index 5a2c3e65ce..476ad1becc 100644 --- a/packages/extension/src/pages/main/token-detail/constants.ts +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -7,6 +7,7 @@ export const Relations = [ "delegate", "undelegate", "redelegate", + "vote", "custom/merged-claim-rewards", ]; diff --git a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx index dcf9996c52..ec73677723 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx @@ -13,11 +13,21 @@ export const MsgItemBase: FunctionComponent<{ chainId: string; title: string; paragraph?: string; - amount: CoinPretty; + amount: CoinPretty | string; + overrideAmountColor?: string; prices: Record | undefined>; targetDenom: string; }> = observer( - ({ logo, chainId, title, paragraph, amount, prices, targetDenom }) => { + ({ + logo, + chainId, + title, + paragraph, + amount, + overrideAmountColor, + prices, + targetDenom, + }) => { const { chainStore, priceStore } = useStore(); const chainInfo = chainStore.getChain(chainId); @@ -26,6 +36,10 @@ export const MsgItemBase: FunctionComponent<{ const foundCurrency = chainInfo.findCurrency(targetDenom); const defaultVsCurrency = priceStore.defaultVsCurrency; const sendAmountPricePretty = useMemo(() => { + if (typeof amount === "string") { + return undefined; + } + if (foundCurrency && foundCurrency.coinGeckoId) { const price = prices[foundCurrency.coinGeckoId]; if (price != null && price[defaultVsCurrency] != null) { @@ -80,14 +94,17 @@ export const MsgItemBase: FunctionComponent<{ color={ColorPalette["white"]} style={{ whiteSpace: "nowrap", + color: overrideAmountColor, }} > - {amount - .maxDecimals(2) - .shrink(true) - .hideIBCMetadata(true) - .inequalitySymbol(true) - .toString()} + {typeof amount === "string" + ? amount + : amount + .maxDecimals(2) + .shrink(true) + .hideIBCMetadata(true) + .inequalitySymbol(true) + .toString()} {sendAmountPricePretty ? ( diff --git a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx index 2976f1abac..ef71f1cdef 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx @@ -8,6 +8,7 @@ import { MsgRelationMergedClaimRewards } from "./merged-claim-rewards"; import { MsgRelationIBCSend } from "./ibc-send"; import { MsgRelationIBCSendRefunded } from "./ibc-send-refunded"; import { MsgRelationIBCSendReceive } from "./ibc-send-receive"; +import { MsgRelationVote } from "./vote"; export const MsgItemRender: FunctionComponent<{ msg: MsgHistory; @@ -74,6 +75,11 @@ export const MsgItemRender: FunctionComponent<{ /> ); } + case "vote": { + return ( + + ); + } case "custom/merged-claim-rewards": { return ( | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const proposal: { + proposalId: string; + title: string; + } = useMemo(() => { + return { + proposalId: (msg.msg as any)["proposal_id"], + title: "TODO", + }; + }, [msg.msg]); + + const voteText: { + text: string; + color: string; + } = useMemo(() => { + switch ((msg.msg as any)["option"]) { + case "VOTE_OPTION_YES": + return { + text: "Yes", + color: ColorPalette["gray-10"], + }; + case "VOTE_OPTION_NO": + return { + text: "No", + color: ColorPalette["gray-10"], + }; + case "VOTE_OPTION_NO_WITH_VETO": + return { + text: "NWV", + color: ColorPalette["yellow-400"], + }; + case "VOTE_OPTION_ABSTAIN": + return { + text: "Abstain", + color: ColorPalette["gray-10"], + }; + default: + return { + text: "Unknown", + color: ColorPalette["gray-10"], + }; + } + }, [msg.msg]); + + return ( + + + + + + + + + + + + + + } + /> + } + chainId={msg.chainId} + title="Vote" + paragraph={`#${proposal.proposalId} ${proposal.title}`} + amount={voteText.text} + overrideAmountColor={voteText.color} + prices={prices || {}} + targetDenom={targetDenom} + /> + ); +}); From a07636b08e61563066bcdcb1c9223c802e700f48 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Tue, 19 Mar 2024 19:56:57 +0900 Subject: [PATCH 14/72] Minor fix --- .../extension/src/pages/main/token-detail/msg-items/base.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx index ec73677723..e773f306dd 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/base.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/base.tsx @@ -63,7 +63,7 @@ export const MsgItemBase: FunctionComponent<{ minHeight="4rem" alignY="center" > - + {logo} From 27dc282340475226f602ac409d0feaca8fdee3df Mon Sep 17 00:00:00 2001 From: Thunnini Date: Fri, 22 Mar 2024 17:00:44 +0900 Subject: [PATCH 15/72] Very minor change --- .../pages/main/token-detail/msg-items/ibc-send-receive.tsx | 2 +- .../pages/main/token-detail/msg-items/ibc-send-refunded.tsx | 5 ++++- .../src/pages/main/token-detail/msg-items/ibc-send.tsx | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx index b4e125dec2..c07603ae4d 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-receive.tsx @@ -100,7 +100,7 @@ export const MsgRelationIBCSendReceive: FunctionComponent<{ } deco={ sourceChain ? ( - + ) : undefined } /> diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx index 6400c9b017..1897d044c4 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send-refunded.tsx @@ -82,7 +82,10 @@ export const MsgRelationIBCSendRefunded: FunctionComponent<{ } deco={ destinationChain ? ( - + ) : undefined } /> diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx index 46a46d6afd..df2180d204 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-send.tsx @@ -115,7 +115,10 @@ export const MsgRelationIBCSend: FunctionComponent<{ } deco={ destinationChain ? ( - + ) : undefined } /> From 1dae0e8b1429750c08cf5fdd589bb2ebf6cdbfbf Mon Sep 17 00:00:00 2001 From: delivan Date: Tue, 26 Mar 2024 23:43:52 +0900 Subject: [PATCH 16/72] Enhance `BuyCryptoModal` for selected token --- .../components/buy-crypto-modal/index.tsx | 119 ++++++++++-------- .../src/pages/main/token-detail/modal.tsx | 20 ++- 2 files changed, 85 insertions(+), 54 deletions(-) diff --git a/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx b/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx index c47cc3357d..3395fee824 100644 --- a/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx +++ b/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx @@ -9,7 +9,7 @@ import { } from "../../../../config.ui"; import { Box } from "../../../../components/box"; import { useStore } from "../../../../stores"; -import { ChainInfo } from "@keplr-wallet/types"; +import { ChainInfo, AppCurrency } from "@keplr-wallet/types"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; import { FormattedMessage } from "react-intl"; @@ -67,7 +67,11 @@ const Styles = { export const BuyCryptoModal: FunctionComponent<{ close: () => void; -}> = observer(({ close }) => { + selectedTokenInfo?: { + chainId: string; + currency: AppCurrency; + }; +}> = observer(({ close, selectedTokenInfo }) => { const theme = useTheme(); const { accountStore, chainStore } = useStore(); const [fiatOnRampServiceInfos, setFiatOnRampServiceInfos] = useState( @@ -85,38 +89,68 @@ export const BuyCryptoModal: FunctionComponent<{ }, []); const buySupportServiceInfos = fiatOnRampServiceInfos.map((serviceInfo) => { - const buySupportChainIds = Object.keys( - serviceInfo.buySupportCoinDenomsByChainId - ); - - const buySupportDefaultChainInfo = (() => { - if ( - buySupportChainIds.length > 0 && - chainStore.hasChain(buySupportChainIds[0]) - ) { - return chainStore.getChain(buySupportChainIds[0]); - } - })(); - + const buySupportCoinDenoms = [ + ...new Set( + selectedTokenInfo + ? Object.entries(serviceInfo.buySupportCoinDenomsByChainId) + .filter( + ([chainId, coinDenoms]) => + chainId === selectedTokenInfo.chainId && + coinDenoms?.some((coinDenom) => + coinDenom === "USDC" + ? selectedTokenInfo.currency.coinDenom.includes("USDC") + : coinDenom === selectedTokenInfo.currency.coinDenom + ) + ) + .map(([_, coinDenoms]) => coinDenoms) + .flat() + : Object.values(serviceInfo.buySupportCoinDenomsByChainId).flat() + ), + ]; const buySupportChainAccounts = (() => { const res: { chainInfo: ChainInfo; bech32Address: string; }[] = []; - for (const chainId of buySupportChainIds) { + for (const chainId of Object.keys( + serviceInfo.buySupportCoinDenomsByChainId + )) { if (chainStore.hasChain(chainId)) { - res.push({ - chainInfo: chainStore.getChain(chainId), - bech32Address: accountStore.getAccount(chainId).bech32Address, - }); + if ( + !selectedTokenInfo || + (selectedTokenInfo && selectedTokenInfo.chainId === chainId) + ) { + res.push({ + chainInfo: chainStore.getChain(chainId), + bech32Address: accountStore.getAccount(chainId).bech32Address, + }); + } } } return res; })(); + const selectedCoinDenom = selectedTokenInfo + ? buySupportCoinDenoms.find((coinDenom) => + coinDenom === "USDC" + ? selectedTokenInfo.currency.coinDenom.includes("USDC") + : coinDenom === selectedTokenInfo.currency.coinDenom + ) + : undefined; + const selectedChainName = selectedTokenInfo + ? buySupportChainAccounts.find( + (chainAccount) => + chainAccount.chainInfo.chainId === selectedTokenInfo.chainId + )?.chainInfo.chainName + : undefined; + const buyUrlParams = (() => { + if (buySupportCoinDenoms.length === 0) { + return undefined; + } + switch (serviceInfo.serviceId) { case "moonpay": return { @@ -136,12 +170,7 @@ export const BuyCryptoModal: FunctionComponent<{ ) ) ), - ...(buySupportDefaultChainInfo && { - defaultCurrencyCode: ( - buySupportDefaultChainInfo.stakeCurrency || - buySupportDefaultChainInfo.currencies[0] - ).coinDenom.toLowerCase(), - }), + defaultCurrencyCode: selectedCoinDenom ?? buySupportCoinDenoms[0], }; case "transak": return { @@ -163,21 +192,8 @@ export const BuyCryptoModal: FunctionComponent<{ ), }) ), - cryptoCurrencyList: buySupportChainAccounts - .map( - (chainAccount) => - ( - chainAccount.chainInfo.stakeCurrency || - chainAccount.chainInfo.currencies[0] - ).coinDenom - ) - .join(","), - ...(buySupportDefaultChainInfo && { - defaultCryptoCurrency: ( - buySupportDefaultChainInfo.stakeCurrency || - buySupportDefaultChainInfo.currencies[0] - ).coinDenom, - }), + cryptoCurrencyList: buySupportCoinDenoms, + defaultCryptoCurrency: selectedCoinDenom ?? buySupportCoinDenoms[0], }; case "kado": return { @@ -186,18 +202,11 @@ export const BuyCryptoModal: FunctionComponent<{ networkList: buySupportChainAccounts.map((chainAccount) => chainAccount.chainInfo.chainName.toUpperCase() ), - cryptoList: [ - ...new Set( - Object.values(serviceInfo.buySupportCoinDenomsByChainId).flat() - ), - ], - ...(buySupportDefaultChainInfo && { - onRevCurrency: - serviceInfo.buySupportCoinDenomsByChainId[ - buySupportDefaultChainInfo.chainId - ]?.[0] ?? "USDC", - network: buySupportDefaultChainInfo.chainName.toUpperCase(), - }), + cryptoList: buySupportCoinDenoms, + onRevCurrency: selectedCoinDenom ?? buySupportCoinDenoms[0], + network: + selectedChainName ?? + buySupportChainAccounts[0].chainInfo.chainName.toUpperCase(), }; default: return; @@ -248,6 +257,10 @@ const ServiceItem: FunctionComponent<{ }> = ({ serviceInfo, close }) => { const { analyticsStore } = useStore(); + if (serviceInfo.buyUrl === undefined) { + return null; + } + return ( { diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index 5c6ecd5a74..7d9a9bb450 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -16,6 +16,8 @@ import { HeaderHeight } from "../../../layouts/header"; import { useNavigate } from "react-router"; import { TokenInfos } from "./token-info"; import { RenderMessages } from "./messages"; +import { Modal } from "../../../components/modal"; +import { BuyCryptoModal } from "../components"; const Styles = { Container: styled.div` @@ -52,6 +54,8 @@ export const TokenDetailModal: FunctionComponent<{ const chainInfo = chainStore.getChain(chainId); const currency = chainInfo.forceFindCurrency(coinMinimalDenom); + const [isOpenBuy, setIsOpenBuy] = React.useState(false); + const balance = queriesStore .get(chainId) .queryBalances.getQueryBech32Address( @@ -87,7 +91,7 @@ export const TokenDetailModal: FunctionComponent<{ ), text: "Buy", onClick: () => { - // TODO: noop yet + setIsOpenBuy(true); }, }, { @@ -411,6 +415,20 @@ export const TokenDetailModal: FunctionComponent<{ /> + + setIsOpenBuy(false)} + > + setIsOpenBuy(false)} + selectedTokenInfo={{ + chainId, + currency, + }} + /> + ); }); From 0d10e8e70a29e9c51a0446ccd09a0310300185d1 Mon Sep 17 00:00:00 2001 From: delivan Date: Wed, 27 Mar 2024 00:06:42 +0900 Subject: [PATCH 17/72] Hide buy button when a token is not supported on every on-ramp providers --- packages/extension/src/hooks/use-buy.ts | 164 +++++++++++++++++ .../components/buy-crypto-modal/index.tsx | 166 +----------------- packages/extension/src/pages/main/index.tsx | 8 +- .../src/pages/main/token-detail/modal.tsx | 15 +- 4 files changed, 186 insertions(+), 167 deletions(-) create mode 100644 packages/extension/src/hooks/use-buy.ts diff --git a/packages/extension/src/hooks/use-buy.ts b/packages/extension/src/hooks/use-buy.ts new file mode 100644 index 0000000000..af50fa6411 --- /dev/null +++ b/packages/extension/src/hooks/use-buy.ts @@ -0,0 +1,164 @@ +import { useEffect, useState } from "react"; +import { useStore } from "../stores"; +import { FiatOnRampServiceInfo } from "../config.ui"; + +import { ChainInfo, AppCurrency } from "@keplr-wallet/types"; +import { simpleFetch } from "@keplr-wallet/simple-fetch"; + +export const useBuy = (selectedTokenInfo?: { + chainId: string; + currency: AppCurrency; +}) => { + const { accountStore, chainStore } = useStore(); + const [fiatOnRampServiceInfos, setFiatOnRampServiceInfos] = useState< + FiatOnRampServiceInfo[] + >([]); + + useEffect(() => { + (async () => { + const response = await simpleFetch<{ list: FiatOnRampServiceInfo[] }>( + "https://raw.githubusercontent.com/chainapsis/keplr-fiat-on-off-ramp-registry/main/fiat-on-off-ramp-list.json" + ); + + setFiatOnRampServiceInfos(response.data.list); + })(); + }, []); + + const buySupportServiceInfos = fiatOnRampServiceInfos.map((serviceInfo) => { + const buySupportCoinDenoms = [ + ...new Set( + selectedTokenInfo + ? Object.entries(serviceInfo.buySupportCoinDenomsByChainId) + .filter( + ([chainId, coinDenoms]) => + chainId === selectedTokenInfo.chainId && + coinDenoms?.some((coinDenom) => + coinDenom === "USDC" + ? selectedTokenInfo.currency.coinDenom.includes("USDC") + : coinDenom === selectedTokenInfo.currency.coinDenom + ) + ) + .map(([_, coinDenoms]) => coinDenoms) + .flat() + : Object.values(serviceInfo.buySupportCoinDenomsByChainId).flat() + ), + ]; + const buySupportChainAccounts = (() => { + const res: { + chainInfo: ChainInfo; + bech32Address: string; + }[] = []; + + for (const chainId of Object.keys( + serviceInfo.buySupportCoinDenomsByChainId + )) { + if (chainStore.hasChain(chainId)) { + if ( + !selectedTokenInfo || + (selectedTokenInfo && selectedTokenInfo.chainId === chainId) + ) { + res.push({ + chainInfo: chainStore.getChain(chainId), + bech32Address: accountStore.getAccount(chainId).bech32Address, + }); + } + } + } + + return res; + })(); + + const selectedCoinDenom = selectedTokenInfo + ? buySupportCoinDenoms.find((coinDenom) => + coinDenom === "USDC" + ? selectedTokenInfo.currency.coinDenom.includes("USDC") + : coinDenom === selectedTokenInfo.currency.coinDenom + ) + : undefined; + const selectedChainName = selectedTokenInfo + ? buySupportChainAccounts.find( + (chainAccount) => + chainAccount.chainInfo.chainId === selectedTokenInfo.chainId + )?.chainInfo.chainName + : undefined; + + const buyUrlParams = (() => { + if (buySupportCoinDenoms.length === 0) { + return undefined; + } + + switch (serviceInfo.serviceId) { + case "moonpay": + return { + apiKey: + process.env["KEPLR_EXT_MOONPAY_API_KEY"] ?? serviceInfo.apiKey, + showWalletAddressForm: "true", + walletAddresses: encodeURIComponent( + JSON.stringify( + buySupportChainAccounts.reduce( + (acc, cur) => ({ + ...acc, + [( + cur.chainInfo.stakeCurrency || cur.chainInfo.currencies[0] + ).coinDenom.toLowerCase()]: cur.bech32Address, + }), + {} + ) + ) + ), + defaultCurrencyCode: selectedCoinDenom ?? buySupportCoinDenoms[0], + }; + case "transak": + return { + apiKey: + process.env["KEPLR_EXT_TRANSAK_API_KEY"] ?? serviceInfo.apiKey, + hideMenu: "true", + walletAddressesData: encodeURIComponent( + JSON.stringify({ + coins: buySupportChainAccounts.reduce( + (acc, cur) => ({ + ...acc, + [( + cur.chainInfo.stakeCurrency || cur.chainInfo.currencies[0] + ).coinDenom]: { + address: cur.bech32Address, + }, + }), + {} + ), + }) + ), + cryptoCurrencyList: buySupportCoinDenoms, + defaultCryptoCurrency: selectedCoinDenom ?? buySupportCoinDenoms[0], + }; + case "kado": + return { + apiKey: process.env["KEPLR_EXT_KADO_API_KEY"] ?? serviceInfo.apiKey, + product: "BUY", + networkList: buySupportChainAccounts.map((chainAccount) => + chainAccount.chainInfo.chainName.toUpperCase() + ), + cryptoList: buySupportCoinDenoms, + onRevCurrency: selectedCoinDenom ?? buySupportCoinDenoms[0], + network: + selectedChainName ?? + buySupportChainAccounts[0].chainInfo.chainName.toUpperCase(), + }; + default: + return; + } + })(); + const buyUrl = buyUrlParams + ? `${serviceInfo.buyOrigin}?${Object.entries(buyUrlParams) + .map((paramKeyValue) => paramKeyValue.join("=")) + .join("&")}` + : undefined; + + return { + ...serviceInfo, + buyUrl, + }; + }); + + return buySupportServiceInfos; +}; diff --git a/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx b/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx index 3395fee824..4ef97aca82 100644 --- a/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx +++ b/packages/extension/src/pages/main/components/buy-crypto-modal/index.tsx @@ -1,16 +1,11 @@ -import React, { FunctionComponent, useEffect, useState } from "react"; +import React, { FunctionComponent } from "react"; import { observer } from "mobx-react-lite"; import styled, { useTheme } from "styled-components"; import { ColorPalette } from "../../../../styles"; import { Subtitle1 } from "../../../../components/typography"; -import { - FiatOnRampServiceInfo, - FiatOnRampServiceInfos, -} from "../../../../config.ui"; +import { FiatOnRampServiceInfo } from "../../../../config.ui"; import { Box } from "../../../../components/box"; import { useStore } from "../../../../stores"; -import { ChainInfo, AppCurrency } from "@keplr-wallet/types"; -import { simpleFetch } from "@keplr-wallet/simple-fetch"; import { FormattedMessage } from "react-intl"; const Styles = { @@ -67,162 +62,9 @@ const Styles = { export const BuyCryptoModal: FunctionComponent<{ close: () => void; - selectedTokenInfo?: { - chainId: string; - currency: AppCurrency; - }; -}> = observer(({ close, selectedTokenInfo }) => { + buySupportServiceInfos: (FiatOnRampServiceInfo & { buyUrl?: string })[]; +}> = observer(({ close, buySupportServiceInfos }) => { const theme = useTheme(); - const { accountStore, chainStore } = useStore(); - const [fiatOnRampServiceInfos, setFiatOnRampServiceInfos] = useState( - FiatOnRampServiceInfos - ); - - useEffect(() => { - (async () => { - const response = await simpleFetch<{ list: FiatOnRampServiceInfo[] }>( - "https://raw.githubusercontent.com/chainapsis/keplr-fiat-on-off-ramp-registry/main/fiat-on-off-ramp-list.json" - ); - - setFiatOnRampServiceInfos(response.data.list); - })(); - }, []); - - const buySupportServiceInfos = fiatOnRampServiceInfos.map((serviceInfo) => { - const buySupportCoinDenoms = [ - ...new Set( - selectedTokenInfo - ? Object.entries(serviceInfo.buySupportCoinDenomsByChainId) - .filter( - ([chainId, coinDenoms]) => - chainId === selectedTokenInfo.chainId && - coinDenoms?.some((coinDenom) => - coinDenom === "USDC" - ? selectedTokenInfo.currency.coinDenom.includes("USDC") - : coinDenom === selectedTokenInfo.currency.coinDenom - ) - ) - .map(([_, coinDenoms]) => coinDenoms) - .flat() - : Object.values(serviceInfo.buySupportCoinDenomsByChainId).flat() - ), - ]; - const buySupportChainAccounts = (() => { - const res: { - chainInfo: ChainInfo; - bech32Address: string; - }[] = []; - - for (const chainId of Object.keys( - serviceInfo.buySupportCoinDenomsByChainId - )) { - if (chainStore.hasChain(chainId)) { - if ( - !selectedTokenInfo || - (selectedTokenInfo && selectedTokenInfo.chainId === chainId) - ) { - res.push({ - chainInfo: chainStore.getChain(chainId), - bech32Address: accountStore.getAccount(chainId).bech32Address, - }); - } - } - } - - return res; - })(); - - const selectedCoinDenom = selectedTokenInfo - ? buySupportCoinDenoms.find((coinDenom) => - coinDenom === "USDC" - ? selectedTokenInfo.currency.coinDenom.includes("USDC") - : coinDenom === selectedTokenInfo.currency.coinDenom - ) - : undefined; - const selectedChainName = selectedTokenInfo - ? buySupportChainAccounts.find( - (chainAccount) => - chainAccount.chainInfo.chainId === selectedTokenInfo.chainId - )?.chainInfo.chainName - : undefined; - - const buyUrlParams = (() => { - if (buySupportCoinDenoms.length === 0) { - return undefined; - } - - switch (serviceInfo.serviceId) { - case "moonpay": - return { - apiKey: - process.env["KEPLR_EXT_MOONPAY_API_KEY"] ?? serviceInfo.apiKey, - showWalletAddressForm: "true", - walletAddresses: encodeURIComponent( - JSON.stringify( - buySupportChainAccounts.reduce( - (acc, cur) => ({ - ...acc, - [( - cur.chainInfo.stakeCurrency || cur.chainInfo.currencies[0] - ).coinDenom.toLowerCase()]: cur.bech32Address, - }), - {} - ) - ) - ), - defaultCurrencyCode: selectedCoinDenom ?? buySupportCoinDenoms[0], - }; - case "transak": - return { - apiKey: - process.env["KEPLR_EXT_TRANSAK_API_KEY"] ?? serviceInfo.apiKey, - hideMenu: "true", - walletAddressesData: encodeURIComponent( - JSON.stringify({ - coins: buySupportChainAccounts.reduce( - (acc, cur) => ({ - ...acc, - [( - cur.chainInfo.stakeCurrency || cur.chainInfo.currencies[0] - ).coinDenom]: { - address: cur.bech32Address, - }, - }), - {} - ), - }) - ), - cryptoCurrencyList: buySupportCoinDenoms, - defaultCryptoCurrency: selectedCoinDenom ?? buySupportCoinDenoms[0], - }; - case "kado": - return { - apiKey: process.env["KEPLR_EXT_KADO_API_KEY"] ?? serviceInfo.apiKey, - product: "BUY", - networkList: buySupportChainAccounts.map((chainAccount) => - chainAccount.chainInfo.chainName.toUpperCase() - ), - cryptoList: buySupportCoinDenoms, - onRevCurrency: selectedCoinDenom ?? buySupportCoinDenoms[0], - network: - selectedChainName ?? - buySupportChainAccounts[0].chainInfo.chainName.toUpperCase(), - }; - default: - return; - } - })(); - const buyUrl = buyUrlParams - ? `${serviceInfo.buyOrigin}?${Object.entries(buyUrlParams) - .map((paramKeyValue) => paramKeyValue.join("=")) - .join("&")}` - : undefined; - - return { - ...serviceInfo, - buyUrl, - }; - }); return ( diff --git a/packages/extension/src/pages/main/index.tsx b/packages/extension/src/pages/main/index.tsx index 75dd3ff672..1f2183b513 100644 --- a/packages/extension/src/pages/main/index.tsx +++ b/packages/extension/src/pages/main/index.tsx @@ -51,6 +51,7 @@ import { LogAnalyticsEventMsg, } from "@keplr-wallet/background"; import { BACKGROUND_PORT } from "@keplr-wallet/router"; +import { useBuy } from "../../hooks/use-buy"; export interface ViewToken { token: CoinPretty; @@ -247,6 +248,8 @@ export const MainPage: FunctionComponent<{ const [isOpenDepositModal, setIsOpenDepositModal] = React.useState(false); const [isOpenBuy, setIsOpenBuy] = React.useState(false); + const buySupportServiceInfos = useBuy(); + const searchRef = useRef(null); const [search, setSearch] = useState(""); const [isEnteredSearch, setIsEnteredSearch] = useState(false); @@ -590,7 +593,10 @@ export const MainPage: FunctionComponent<{ align="bottom" close={() => setIsOpenBuy(false)} > - setIsOpenBuy(false)} /> + setIsOpenBuy(false)} + buySupportServiceInfos={buySupportServiceInfos} + /> ); diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index 7d9a9bb450..ff2445bf01 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -18,6 +18,7 @@ import { TokenInfos } from "./token-info"; import { RenderMessages } from "./messages"; import { Modal } from "../../../components/modal"; import { BuyCryptoModal } from "../components"; +import { useBuy } from "../../../hooks/use-buy"; const Styles = { Container: styled.div` @@ -56,6 +57,11 @@ export const TokenDetailModal: FunctionComponent<{ const [isOpenBuy, setIsOpenBuy] = React.useState(false); + const buySupportServiceInfos = useBuy({ chainId, currency }); + const isSomeBuySupport = buySupportServiceInfos.some( + (serviceInfo) => !!serviceInfo.buyUrl + ); + const balance = queriesStore .get(chainId) .queryBalances.getQueryBech32Address( @@ -314,6 +320,10 @@ export const TokenDetailModal: FunctionComponent<{ {buttons.map((obj, i) => { + if (obj.text === "Buy" && !isSomeBuySupport) { + return null; + } + return ( @@ -423,10 +433,7 @@ export const TokenDetailModal: FunctionComponent<{ > setIsOpenBuy(false)} - selectedTokenInfo={{ - chainId, - currency, - }} + buySupportServiceInfos={buySupportServiceInfos} /> From 99e8fea9577ef3fabf98e7f7dc0ef3e9b2edb2dc Mon Sep 17 00:00:00 2001 From: Thunnini Date: Wed, 27 Mar 2024 14:39:20 +0900 Subject: [PATCH 18/72] =?UTF-8?q?ibc=20swap=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/main/token-detail/constants.ts | 2 + .../msg-items/ibc-swap-receive.tsx | 103 +++++++++++++++++ .../main/token-detail/msg-items/ibc-swap.tsx | 107 ++++++++++++++++++ .../main/token-detail/msg-items/index.tsx | 20 ++++ 4 files changed, 232 insertions(+) create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/ibc-swap-receive.tsx create mode 100644 packages/extension/src/pages/main/token-detail/msg-items/ibc-swap.tsx diff --git a/packages/extension/src/pages/main/token-detail/constants.ts b/packages/extension/src/pages/main/token-detail/constants.ts index 476ad1becc..12c7da6740 100644 --- a/packages/extension/src/pages/main/token-detail/constants.ts +++ b/packages/extension/src/pages/main/token-detail/constants.ts @@ -9,6 +9,8 @@ export const Relations = [ "redelegate", "vote", "custom/merged-claim-rewards", + "ibc-swap-skip-osmosis", + "ibc-swap-skip-osmosis-receive", ]; // TODO: 지금은 scroll to fetch 테스트 용으로 값이 좀 작은거고 나중에는 20으로 올려야함. diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-swap-receive.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-swap-receive.tsx new file mode 100644 index 0000000000..2d1c4df9ea --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-swap-receive.tsx @@ -0,0 +1,103 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; +import { ChainInfo } from "@keplr-wallet/types"; +import { ChainImageFallback } from "../../../../components/image"; +import { isValidCoinStr, parseCoinStr } from "@keplr-wallet/common"; + +export const MsgRelationIBCSwapReceive: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const sendAmountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const receives = msg.meta["receives"]; + if ( + receives && + Array.isArray(receives) && + receives.length > 0 && + typeof receives[0] === "string" + ) { + for (const coinStr of receives) { + if (isValidCoinStr(coinStr as string)) { + const coin = parseCoinStr(coinStr as string); + if (coin.denom === targetDenom) { + return new CoinPretty(currency, coin.amount); + } + } + } + } + + return new CoinPretty(currency, "0"); + }, [chainInfo, msg.meta, targetDenom]); + + const sourceChain: ChainInfo | undefined = (() => { + if (!msg.ibcTracking) { + return undefined; + } + + try { + if (msg.ibcTracking.paths.length > 0) { + const path = msg.ibcTracking.paths[0]; + if (!path.chainId) { + return undefined; + } + if (!chainStore.hasChain(path.chainId)) { + return undefined; + } + return chainStore.getChain(path.chainId); + } + + return undefined; + } catch (e) { + console.log(e); + return undefined; + } + })(); + + return ( + + + + } + deco={ + sourceChain ? ( + + ) : undefined + } + /> + } + chainId={msg.chainId} + title="Swap Completed" + amount={sendAmountPretty} + prices={prices || {}} + targetDenom={targetDenom} + /> + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/ibc-swap.tsx b/packages/extension/src/pages/main/token-detail/msg-items/ibc-swap.tsx new file mode 100644 index 0000000000..200108d35b --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/msg-items/ibc-swap.tsx @@ -0,0 +1,107 @@ +import React, { FunctionComponent, useMemo } from "react"; +import { MsgHistory } from "../types"; +import { observer } from "mobx-react-lite"; +import { useStore } from "../../../../stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import { MsgItemBase } from "./base"; +import { ItemLogo } from "./logo"; +import { ChainInfo } from "@keplr-wallet/types"; +import { ChainImageFallback } from "../../../../components/image"; +import { isValidCoinStr, parseCoinStr } from "@keplr-wallet/common"; + +export const MsgRelationIBCSwap: FunctionComponent<{ + msg: MsgHistory; + prices?: Record | undefined>; + targetDenom: string; +}> = observer(({ msg, prices, targetDenom }) => { + const { chainStore } = useStore(); + + const chainInfo = chainStore.getChain(msg.chainId); + + const sendAmountPretty = useMemo(() => { + const currency = chainInfo.forceFindCurrency(targetDenom); + + const from = msg.meta["from"]; + if ( + from && + Array.isArray(from) && + from.length > 0 && + typeof from[0] === "string" + ) { + for (const coinStr of from) { + if (isValidCoinStr(coinStr as string)) { + const coin = parseCoinStr(coinStr as string); + if (coin.denom === targetDenom) { + return new CoinPretty(currency, coin.amount); + } + } + } + } + + return new CoinPretty(currency, "0"); + }, [chainInfo, msg.meta, targetDenom]); + + const destinationChain: ChainInfo | undefined = (() => { + if (!msg.ibcTracking) { + return undefined; + } + + try { + let res: ChainInfo | undefined = undefined; + for (const path of msg.ibcTracking.paths) { + if (!path.clientChainId) { + return undefined; + } + if (!chainStore.hasChain(path.clientChainId)) { + return undefined; + } + + res = chainStore.getChain(path.clientChainId); + } + + return res; + } catch (e) { + console.log(e); + return undefined; + } + })(); + + return ( + + + + } + deco={ + destinationChain ? ( + + ) : undefined + } + /> + } + chainId={msg.chainId} + title="Swap" + amount={sendAmountPretty} + prices={prices || {}} + targetDenom={targetDenom} + /> + ); +}); diff --git a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx index ef71f1cdef..33735ec6f7 100644 --- a/packages/extension/src/pages/main/token-detail/msg-items/index.tsx +++ b/packages/extension/src/pages/main/token-detail/msg-items/index.tsx @@ -9,6 +9,8 @@ import { MsgRelationIBCSend } from "./ibc-send"; import { MsgRelationIBCSendRefunded } from "./ibc-send-refunded"; import { MsgRelationIBCSendReceive } from "./ibc-send-receive"; import { MsgRelationVote } from "./vote"; +import { MsgRelationIBCSwap } from "./ibc-swap"; +import { MsgRelationIBCSwapReceive } from "./ibc-swap-receive"; export const MsgItemRender: FunctionComponent<{ msg: MsgHistory; @@ -57,6 +59,24 @@ export const MsgItemRender: FunctionComponent<{ /> ); } + case "ibc-swap-skip-osmosis": { + return ( + + ); + } + case "ibc-swap-skip-osmosis-receive": { + return ( + + ); + } case "delegate": { return ( Date: Wed, 27 Mar 2024 15:05:08 +0900 Subject: [PATCH 19/72] =?UTF-8?q?=EA=B0=80=EA=B2=A9=EC=9D=84=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/main/token-detail/modal.tsx | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index ff2445bf01..55faa01791 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -19,6 +19,7 @@ import { RenderMessages } from "./messages"; import { Modal } from "../../../components/modal"; import { BuyCryptoModal } from "../components"; import { useBuy } from "../../../hooks/use-buy"; +import { CoinPretty, DecUtils } from "@keplr-wallet/unit"; const Styles = { Container: styled.div` @@ -407,16 +408,39 @@ export const TokenDetailModal: FunctionComponent<{ })() : null} - - + {(() => { + const price = (() => { + if (!currency.coinGeckoId) { + return; + } + + return priceStore.calculatePrice( + new CoinPretty( + currency, + DecUtils.getTenExponentN(currency.coinDecimals) + ) + ); + })(); + + if (!price) { + return null; + } + + return ( + + + + + ); + })()} Date: Wed, 27 Mar 2024 17:17:16 +0900 Subject: [PATCH 20/72] WIP --- .../pages/main/token-detail/circle-button.tsx | 67 +++++++++++++++ .../src/pages/main/token-detail/modal.tsx | 82 ++++++++----------- 2 files changed, 100 insertions(+), 49 deletions(-) create mode 100644 packages/extension/src/pages/main/token-detail/circle-button.tsx diff --git a/packages/extension/src/pages/main/token-detail/circle-button.tsx b/packages/extension/src/pages/main/token-detail/circle-button.tsx new file mode 100644 index 0000000000..074bd4de9f --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/circle-button.tsx @@ -0,0 +1,67 @@ +import React, { FunctionComponent } from "react"; +import { Box } from "../../../components/box"; +import { YAxis } from "../../../components/axis"; +import { Gutter } from "../../../components/gutter"; +import { Body3 } from "../../../components/typography"; +import { ColorPalette } from "../../../styles"; + +export const CircleButton: FunctionComponent<{ + onClick: () => void; + icon: React.ReactElement; + text: string; + disabled?: boolean; +}> = ({ onClick, icon, text, disabled }) => { + const [_isHover, setIsHover] = React.useState(false); + const isHover = disabled ? false : _isHover; + + return ( + { + e.preventDefault(); + + if (disabled) { + return; + } + + onClick(); + }} + onHoverStateChange={(hovered) => { + setIsHover(hovered); + }} + > + + + {icon} + + + + + + {text} + + + + + + ); +}; diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index 55faa01791..d116df8e07 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -20,6 +20,7 @@ import { Modal } from "../../../components/modal"; import { BuyCryptoModal } from "../components"; import { useBuy } from "../../../hooks/use-buy"; import { CoinPretty, DecUtils } from "@keplr-wallet/unit"; +import { CircleButton } from "./circle-button"; const Styles = { Container: styled.div` @@ -81,18 +82,17 @@ export const TokenDetailModal: FunctionComponent<{ icon: ( - ), @@ -105,18 +105,17 @@ export const TokenDetailModal: FunctionComponent<{ icon: ( - ), @@ -129,18 +128,17 @@ export const TokenDetailModal: FunctionComponent<{ icon: ( - ), @@ -153,18 +151,17 @@ export const TokenDetailModal: FunctionComponent<{ icon: ( - ), @@ -321,33 +318,20 @@ export const TokenDetailModal: FunctionComponent<{ {buttons.map((obj, i) => { + let disabled = false; if (obj.text === "Buy" && !isSomeBuySupport) { - return null; + disabled = true; } return ( - { - e.preventDefault(); - - obj.onClick(); - }} - > - - {obj.icon} - - - - - {obj.text} - - - - - + {i === buttons.length - 1 ? ( ) : null} From 2a99e8a96a7d92bce08f103b382fd434bda349f0 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Wed, 27 Mar 2024 20:30:12 +0900 Subject: [PATCH 21/72] WIP --- .../main/token-detail/address-chip/index.tsx | 67 +++++++++++++++++ .../src/pages/main/token-detail/modal.tsx | 25 ++++++- .../main/token-detail/receive-modal/index.tsx | 75 +++++++++++++++++++ 3 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 packages/extension/src/pages/main/token-detail/address-chip/index.tsx create mode 100644 packages/extension/src/pages/main/token-detail/receive-modal/index.tsx diff --git a/packages/extension/src/pages/main/token-detail/address-chip/index.tsx b/packages/extension/src/pages/main/token-detail/address-chip/index.tsx new file mode 100644 index 0000000000..db9ba83768 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/address-chip/index.tsx @@ -0,0 +1,67 @@ +import React, { FunctionComponent } from "react"; +import { observer } from "mobx-react-lite"; +import { Box } from "../../../../components/box"; +import { ColorPalette } from "../../../../styles"; +import { Body3 } from "../../../../components/typography"; +import { useStore } from "../../../../stores"; +import { XAxis } from "../../../../components/axis"; +import { Bech32Address } from "@keplr-wallet/cosmos"; + +export const AddressChip: FunctionComponent<{ + chainId: string; +}> = observer(({ chainId }) => { + const { accountStore } = useStore(); + + const account = accountStore.getAccount(chainId); + + return ( + + + + {Bech32Address.shortenAddress(account.bech32Address, 22)} + + + + ); +}); + +export const QRCodeChip: FunctionComponent<{ + onClick: () => void; +}> = ({ onClick }) => { + return ( + + + + + + + + ); +}; diff --git a/packages/extension/src/pages/main/token-detail/modal.tsx b/packages/extension/src/pages/main/token-detail/modal.tsx index d116df8e07..a69da7963f 100644 --- a/packages/extension/src/pages/main/token-detail/modal.tsx +++ b/packages/extension/src/pages/main/token-detail/modal.tsx @@ -21,6 +21,8 @@ import { BuyCryptoModal } from "../components"; import { useBuy } from "../../../hooks/use-buy"; import { CoinPretty, DecUtils } from "@keplr-wallet/unit"; import { CircleButton } from "./circle-button"; +import { AddressChip, QRCodeChip } from "./address-chip"; +import { ReceiveModal } from "./receive-modal"; const Styles = { Container: styled.div` @@ -57,6 +59,7 @@ export const TokenDetailModal: FunctionComponent<{ const chainInfo = chainStore.getChain(chainId); const currency = chainInfo.forceFindCurrency(coinMinimalDenom); + const [isReceiveOpen, setIsReceiveOpen] = React.useState(false); const [isOpenBuy, setIsOpenBuy] = React.useState(false); const buySupportServiceInfos = useBuy({ chainId, currency }); @@ -121,7 +124,7 @@ export const TokenDetailModal: FunctionComponent<{ ), text: "Receive", onClick: () => { - // TODO: noop yet + setIsReceiveOpen(true); }, }, { @@ -278,7 +281,17 @@ export const TokenDetailModal: FunctionComponent<{ }} > -
TODO: Address chip
+ + + + + { + setIsReceiveOpen(true); + }} + /> + + + + setIsReceiveOpen(false)} + > + setIsReceiveOpen(false)} /> + ); }); diff --git a/packages/extension/src/pages/main/token-detail/receive-modal/index.tsx b/packages/extension/src/pages/main/token-detail/receive-modal/index.tsx new file mode 100644 index 0000000000..95b23d7486 --- /dev/null +++ b/packages/extension/src/pages/main/token-detail/receive-modal/index.tsx @@ -0,0 +1,75 @@ +import React, { FunctionComponent } from "react"; +import { observer } from "mobx-react-lite"; +import { Box } from "../../../../components/box"; +import { ColorPalette } from "../../../../styles"; +import { useTheme } from "styled-components"; +import { Gutter } from "../../../../components/gutter"; +import { H4, Subtitle3 } from "../../../../components/typography"; +import { XAxis } from "../../../../components/axis"; +import { useStore } from "../../../../stores"; +import { ChainImageFallback } from "../../../../components/image"; +import { QRCodeSVG } from "qrcode.react"; +import { AddressChip } from "../address-chip"; +import { Button } from "../../../../components/button"; + +export const ReceiveModal: FunctionComponent<{ + chainId: string; + close: () => void; +}> = observer(({ chainId, close }) => { + const { chainStore, accountStore } = useStore(); + + const theme = useTheme(); + + const chainInfo = chainStore.getChain(chainId); + const account = accountStore.getAccount(chainId); + + return ( + + + +

Copy Address

+ + + + + {chainInfo.chainName} + + + + + + + + + +
+ + +