diff --git a/components/chain-list/all-chains-item.tsx b/components/chain-list/all-chains-item.tsx index 06c7c0f..8a64eee 100644 --- a/components/chain-list/all-chains-item.tsx +++ b/components/chain-list/all-chains-item.tsx @@ -1,20 +1,16 @@ import { ChainItemType } from "../../types"; +import { Dispatch, FunctionComponent, SetStateAction } from "react"; + +import { ChainImage } from "./chain-image"; +import { Flex1 } from "../../styles/flex-1"; import { - ChangeEvent, - Dispatch, - FunctionComponent, - SetStateAction, - useEffect, - useState, -} from "react"; -import { + ChainCheckBox, ChainImageContainer, ChainInfoContainer, ChainItemContainer, -} from "./chain-list"; -import { ChainImage } from "./chain-image"; -import { Flex1 } from "../../styles/flex-1"; -import { ChainCheckBox, ChainName, WalletAddress } from "./chain-item"; + ChainName, + WalletAddress, +} from "./chain-item"; import color from "../../styles/color"; import styled from "styled-components"; diff --git a/components/chain-list/chain-item.tsx b/components/chain-list/chain-item.tsx index 00a63a3..c1eb37f 100644 --- a/components/chain-list/chain-item.tsx +++ b/components/chain-list/chain-item.tsx @@ -1,17 +1,5 @@ -import { ChainItemType } from "../../types"; -import { - ChangeEvent, - Dispatch, - FunctionComponent, - SetStateAction, - useEffect, - useState, -} from "react"; -import { - ChainImageContainer, - ChainInfoContainer, - ChainItemContainer, -} from "./chain-list"; +import { ChainItemType, WidthHeightProps } from "../../types"; +import { FunctionComponent, useEffect, useState } from "react"; import color from "../../styles/color"; import { Flex1 } from "../../styles/flex-1"; @@ -22,26 +10,31 @@ interface Props { chainItem: ChainItemType; checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void; checkedItems: Set; + disabled?: boolean; } export const ChainItem: FunctionComponent = (props) => { - const { chainItem, checkedItemHandler, checkedItems } = props; - const [checked, setChecked] = useState(false); + const { chainItem, checkedItemHandler, checkedItems, disabled } = props; + const [checked, setChecked] = useState(disabled); const checkHandler = () => { - setChecked(!checked); - checkedItemHandler(chainItem, !checked); + if (!disabled) { + setChecked(!checked); + checkedItemHandler(chainItem, !checked); + } }; useEffect(() => { - setChecked(checkedItems.has(chainItem)); + if (!disabled) { + setChecked(checkedItems.has(chainItem)); + } }, [checkedItems]); return ( @@ -63,6 +56,41 @@ export const ChainItem: FunctionComponent = (props) => { ); }; +export const ChainItemContainer = styled.div<{ + isLoading: boolean; + checked?: boolean; + disabled?: boolean; +}>` + display: flex; + flex-direction: row; + align-items: center; + + gap: 1rem; + + padding: 1.5rem; + + cursor: pointer; + + opacity: ${(props) => (props.disabled ? "0.3" : "1")}; + + &:hover { + background: ${(props) => (props.isLoading ? null : color.grey["600"])}; + } +`; + +export const ChainImageContainer = styled.div` + width: ${(props) => props.width}; + height: ${(props) => props.height}; + + position: relative; +`; + +export const ChainInfoContainer = styled.div` + display: flex; + flex-direction: column; + gap: 0.5rem; +`; + export const ChainName = styled.div` font-weight: 600; font-size: 0.8rem; diff --git a/components/chain-list/chain-list.tsx b/components/chain-list/chain-list.tsx index ab7ba36..b8bc785 100644 --- a/components/chain-list/chain-list.tsx +++ b/components/chain-list/chain-list.tsx @@ -1,5 +1,5 @@ import { Dispatch, FunctionComponent, SetStateAction, useEffect } from "react"; -import { ChainItemType, WidthHeightProps } from "../../types"; +import { ChainItemType } from "../../types"; import color from "../../styles/color"; import styled from "styled-components"; import { ChainItem } from "./chain-item"; @@ -8,6 +8,7 @@ interface Props { allChecked: boolean; setAllChecked: Dispatch>; chainList: ChainItemType[]; + disabledChainList: ChainItemType[]; checkedItems: Set; setCheckedItems: Dispatch>>; } @@ -17,6 +18,7 @@ export const ChainList: FunctionComponent = (props) => { allChecked, setAllChecked, chainList, + disabledChainList, checkedItems, setCheckedItems, } = props; @@ -59,6 +61,15 @@ export const ChainList: FunctionComponent = (props) => { checkedItems={checkedItems} /> ))} + {disabledChainList.map((chainItem) => ( + + ))} ); }; @@ -72,35 +83,3 @@ export const ChainContainer = styled.div` background-color: ${(props) => props.color}; `; - -export const ChainItemContainer = styled.div<{ - isLoading: boolean; - checked?: boolean; -}>` - display: flex; - flex-direction: row; - align-items: center; - - gap: 1rem; - - padding: 1.5rem; - - cursor: pointer; - - &:hover { - background: ${(props) => (props.isLoading ? null : color.grey["700"])}; - } -`; - -export const ChainImageContainer = styled.div` - width: ${(props) => props.width}; - height: ${(props) => props.height}; - - position: relative; -`; - -export const ChainInfoContainer = styled.div` - display: flex; - flex-direction: column; - gap: 0.5rem; -`; diff --git a/components/connect-wallet-modal/wallet-item.tsx b/components/connect-wallet-modal/wallet-item.tsx index 6416cc9..07cf8db 100644 --- a/components/connect-wallet-modal/wallet-item.tsx +++ b/components/connect-wallet-modal/wallet-item.tsx @@ -10,12 +10,13 @@ import { WalletType, } from "../../constants/wallet"; import { getKeplrFromWindow, KeplrWallet } from "../../wallets"; -import { loginWithTwitter } from "../../repository"; +import { loginWithTwitter } from "../../queries"; interface Props { wallet: WalletType; } +// Todo: Wallet 관련된 부분을 Context로 빼는 부분 export const WalletItem: FunctionComponent = (props: Props) => { const { wallet } = props; diff --git a/constants/icns.ts b/constants/icns.ts index 7be921c..7ec0dc4 100644 --- a/constants/icns.ts +++ b/constants/icns.ts @@ -3,6 +3,7 @@ export const MainChainId = "osmo-test-4"; export const RPC_URL = "https://rpc.testnet.osmosis.zone"; export const REST_URL = "https://lcd.testnet.osmosis.zone"; +// TODO: .evn에 없으면 디폴트값 설정 export const REGISTRAR_ADDRESS = "osmo1npn97g7hsgqlp70rw8nhd7c7vyvkukv9x0n25sn4fk5mgcjlz4gq9zlgf3"; export const RESOLVER_ADDRESS = diff --git a/repository/icns.ts b/messages/icns.ts similarity index 57% rename from repository/icns.ts rename to messages/icns.ts index 658451f..0ceba80 100644 --- a/repository/icns.ts +++ b/messages/icns.ts @@ -1,51 +1,8 @@ -import { request } from "../utils/url"; -import { - REGISTRAR_ADDRESS, - RESOLVER_ADDRESS, - REST_URL, -} from "../constants/icns"; -import { Buffer } from "buffer/"; -import { - AddressesQueryResponse, - CosmwasmExecuteMessageResult, - NameByTwitterIdQueryResponse, - QueryError, -} from "../types"; +import { CosmwasmExecuteMessageResult } from "../types"; import { makeCosmwasmExecMsg } from "../wallets"; +import { REGISTRAR_ADDRESS, RESOLVER_ADDRESS } from "../constants/icns"; import { ContractFee } from "../constants/wallet"; -const getCosmwasmQueryUrl = (contractAddress: string, queryMsg: string) => - `${REST_URL}/cosmwasm/wasm/v1/contract/${contractAddress}/smart/${queryMsg}`; - -export const queryRegisteredTwitterId = async ( - twitterId: string, -): Promise => { - const msg = { - name_by_twitter_id: { twitter_id: twitterId }, - }; - return request( - getCosmwasmQueryUrl( - REGISTRAR_ADDRESS, - Buffer.from(JSON.stringify(msg)).toString("base64"), - ), - ); -}; - -export const queryAddressesFromTwitterName = async ( - twitterUsername: string, -): Promise => { - const msg = { - addresses: { name: twitterUsername }, - }; - - return request( - getCosmwasmQueryUrl( - RESOLVER_ADDRESS, - Buffer.from(JSON.stringify(msg)).toString("base64"), - ), - ); -}; - export const makeClaimMessage = ( senderAddress: string, twitterUserName: string, diff --git a/messages/index.ts b/messages/index.ts new file mode 100644 index 0000000..053b642 --- /dev/null +++ b/messages/index.ts @@ -0,0 +1 @@ +export * from "./icns"; diff --git a/pages/complete/index.tsx b/pages/complete/index.tsx index 6ebbc7f..59c99ad 100644 --- a/pages/complete/index.tsx +++ b/pages/complete/index.tsx @@ -32,6 +32,8 @@ export default function CompletePage() { const result = await txTracer.traceTx(Buffer.from(txHash, "hex")); console.log(result); + + // Todo rsult => 확인 후에 확인 }; return ( diff --git a/pages/verification/index.tsx b/pages/verification/index.tsx index 1ac11c3..9ebdad7 100644 --- a/pages/verification/index.tsx +++ b/pages/verification/index.tsx @@ -2,8 +2,12 @@ import { useEffect, useState } from "react"; // Types -import { ChainItemType, TwitterProfileType } from "../../types"; -import { checkTwitterAuthQueryParameter, request } from "../../utils/url"; +import { + ChainItemType, + RegisteredAddresses, + TwitterProfileType, +} from "../../types"; +import { checkTwitterAuthQueryParameter } from "../../utils/url"; // Styles import styled from "styled-components"; @@ -20,7 +24,6 @@ import { useRouter } from "next/router"; import { getKeplrFromWindow, KeplrWallet, - makeCosmwasmExecMsg, sendMsgs, simulateMsgs, } from "../../wallets"; @@ -29,26 +32,20 @@ import { ChainIdHelper } from "@keplr-wallet/cosmos"; import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg"; import { AllChainsItem } from "../../components/chain-list/all-chains-item"; import { SearchInput } from "../../components/search-input"; -import { - MainChainId, - REGISTRAR_ADDRESS, - RESOLVER_ADDRESS, - REST_URL, -} from "../../constants/icns"; +import { MainChainId, RESOLVER_ADDRESS, REST_URL } from "../../constants/icns"; import { fetchTwitterInfo, - makeClaimMessage, - makeSetRecordMessage, queryAddressesFromTwitterName, queryRegisteredTwitterId, verifyTwitterAccount, -} from "../../repository"; +} from "../../queries"; import { ErrorHandler } from "../../utils/error"; import { KEPLR_NOT_FOUND_ERROR, TWITTER_LOGIN_ERROR, } from "../../constants/error-message"; +import { makeClaimMessage, makeSetRecordMessage } from "../../messages"; export default function VerificationPage() { const router = useRouter(); @@ -57,13 +54,19 @@ export default function VerificationPage() { const [isLoading, setIsLoading] = useState(true); const [wallet, setWallet] = useState(); - const [allChains, setAllChains] = useState(); + const [chainList, setChainList] = useState([]); - const [registeredAddressList, setRegisteredAddressList] = useState( + const [disabledChainList, setDisabledChainList] = useState( [], ); - const [checkedItems, setCheckedItems] = useState(new Set()); + const [registeredChainList, setRegisteredChainList] = useState< + RegisteredAddresses[] + >([]); + + const [allChains, setAllChains] = useState(); const [allChecked, setAllChecked] = useState(false); + const [checkedItems, setCheckedItems] = useState(new Set()); + const [searchValue, setSearchValue] = useState(""); useEffect(() => { @@ -80,7 +83,7 @@ export default function VerificationPage() { // Fetch Twitter Profile const twitterInfo = await fetchTwitterInfo(state, code); - // check registered + // contract check registered const registeredQueryResponse = await queryRegisteredTwitterId( twitterInfo.id, ); @@ -95,11 +98,7 @@ export default function VerificationPage() { registeredQueryResponse.data.name, ); - setRegisteredAddressList( - addressesQueryResponse.data.addresses.map( - (address) => address.address, - ), - ); + setRegisteredChainList(addressesQueryResponse.data.addresses); } } catch (error) { if (error instanceof Error && error.message === TWITTER_LOGIN_ERROR) { @@ -117,89 +116,96 @@ export default function VerificationPage() { }, []); useEffect(() => { - // After Wallet Initialize - if (wallet) { - fetchChainList(); - } - }, [wallet]); + const disabledChainList = chainList.filter((chain) => { + for (const registeredChain of registeredChainList) { + if ( + chain.prefix === registeredChain.bech32_prefix && + chain.address === registeredChain.address + ) { + return true; + } + } - useEffect(() => { - // To check registered chain - const filteredChainList = chainList.filter((chain) => { - return registeredAddressList.includes(chain.address); + return false; }); - setCheckedItems(new Set(filteredChainList)); - }, [registeredAddressList]); + const filteredChainList = chainList.filter( + (chain) => !disabledChainList.includes(chain), + ); + + setAllChains({ + chainId: "all chains", + chainName: "all chains", + prefix: `all chains(${filteredChainList.length})`, + address: filteredChainList.map((chain) => chain.chainName).join(", "), + chainImageUrl: AllChainsIcon, + }); + + setChainList(filteredChainList); + setDisabledChainList(disabledChainList); + }, [registeredChainList]); const initWallet = async () => { const keplr = await getKeplrFromWindow(); if (keplr) { const keplrWallet = new KeplrWallet(keplr); + + await fetchChainList(keplrWallet); setWallet(keplrWallet); } else { ErrorHandler(KEPLR_NOT_FOUND_ERROR); } }; - const fetchChainList = async () => { - if (wallet) { - const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map( - (c) => c.chainId, - ); - const chainKeys = await Promise.all( - chainIds.map((chainId) => wallet.getKey(chainId)), - ); + const fetchChainList = async (wallet: KeplrWallet) => { + const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map( + (c) => c.chainId, + ); + const chainKeys = await Promise.all( + chainIds.map((chainId) => wallet.getKey(chainId)), + ); - const chainInfos = (await wallet.getChainInfosWithoutEndpoints()).map( - (chainInfo) => { - return { - chainId: chainInfo.chainId, - chainName: chainInfo.chainName, - prefix: chainInfo.bech32Config.bech32PrefixAccAddr, - chainImageUrl: `https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/${ - ChainIdHelper.parse(chainInfo.chainId).identifier - }/chain.png`, - }; - }, - ); + const chainInfos = (await wallet.getChainInfosWithoutEndpoints()).map( + (chainInfo) => { + return { + chainId: chainInfo.chainId, + chainName: chainInfo.chainName, + prefix: chainInfo.bech32Config.bech32PrefixAccAddr, + chainImageUrl: `https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/${ + ChainIdHelper.parse(chainInfo.chainId).identifier + }/chain.png`, + }; + }, + ); - const chainArray = []; - for (let i = 0; i < chainKeys.length; i++) { - chainArray.push({ - address: chainKeys[i].bech32Address, - ...chainInfos[i], - }); - } - - // remove duplicated item - const filteredChainList = chainArray.filter((nextChain, index, self) => { - return ( - index === - self.findIndex((prevChain) => { - const isDuplicated = prevChain.prefix === nextChain.prefix; - - if (isDuplicated && prevChain.chainName !== nextChain.chainName) { - console.log( - `${nextChain.chainName} has been deleted due to a duplicate name with ${prevChain.chainName}`, - ); - } - - return isDuplicated; - }) - ); + const chainArray = []; + for (let i = 0; i < chainKeys.length; i++) { + chainArray.push({ + address: chainKeys[i].bech32Address, + ...chainInfos[i], }); - - setAllChains({ - chainId: "all chains", - prefix: `all chains(${filteredChainList.length})`, - address: chainInfos.map((chainInfo) => chainInfo.chainName).join(", "), - chainImageUrl: AllChainsIcon, - }); - - setChainList(filteredChainList); } + + // remove duplicated item + const filteredChainList = chainArray.filter((nextChain, index, self) => { + return ( + index === + self.findIndex((prevChain) => { + const isDuplicated = prevChain.prefix === nextChain.prefix; + + if (isDuplicated && prevChain.chainName !== nextChain.chainName) { + console.log( + `${nextChain.chainName} has been deleted due to a duplicate name with ${prevChain.chainName}`, + ); + } + + return isDuplicated; + }) + ); + }); + + setChainList(filteredChainList); }; const checkAdr36 = async () => { @@ -224,7 +230,6 @@ export default function VerificationPage() { const { state, code } = checkTwitterAuthQueryParameter( window.location.search, ); - const twitterInfo = await fetchTwitterInfo(state, code); const adr36Infos = await checkAdr36(); @@ -336,6 +341,12 @@ export default function VerificationPage() { chain.address.includes(searchValue) || chain.prefix.includes(searchValue), )} + disabledChainList={disabledChainList.filter( + (chain) => + chain.chainId.includes(searchValue) || + chain.address.includes(searchValue) || + chain.prefix.includes(searchValue), + )} checkedItems={checkedItems} setCheckedItems={setCheckedItems} /> diff --git a/queries/icns.ts b/queries/icns.ts new file mode 100644 index 0000000..f1b0ae3 --- /dev/null +++ b/queries/icns.ts @@ -0,0 +1,44 @@ +import { request } from "../utils/url"; +import { + REGISTRAR_ADDRESS, + RESOLVER_ADDRESS, + REST_URL, +} from "../constants/icns"; +import { Buffer } from "buffer/"; +import { + AddressesQueryResponse, + NameByTwitterIdQueryResponse, + QueryError, +} from "../types"; + +const getCosmwasmQueryUrl = (contractAddress: string, queryMsg: string) => + `${REST_URL}/cosmwasm/wasm/v1/contract/${contractAddress}/smart/${queryMsg}`; + +export const queryRegisteredTwitterId = async ( + twitterId: string, +): Promise => { + const msg = { + name_by_twitter_id: { twitter_id: twitterId }, + }; + return request( + getCosmwasmQueryUrl( + REGISTRAR_ADDRESS, + Buffer.from(JSON.stringify(msg)).toString("base64"), + ), + ); +}; + +export const queryAddressesFromTwitterName = async ( + twitterUsername: string, +): Promise => { + const msg = { + addresses: { name: twitterUsername }, + }; + + return request( + getCosmwasmQueryUrl( + RESOLVER_ADDRESS, + Buffer.from(JSON.stringify(msg)).toString("base64"), + ), + ); +}; diff --git a/repository/index.ts b/queries/index.ts similarity index 100% rename from repository/index.ts rename to queries/index.ts diff --git a/repository/twitter.ts b/queries/twitter.ts similarity index 88% rename from repository/twitter.ts rename to queries/twitter.ts index 163726a..903fe11 100644 --- a/repository/twitter.ts +++ b/queries/twitter.ts @@ -6,9 +6,9 @@ import { import { request } from "../utils/url"; export const loginWithTwitter = async () => { - const { authUrl }: TwitterAuthUrlResponse = await ( - await fetch("/api/twitter-auth-url") - ).json(); + const { authUrl } = await request( + "/api/twitter-auth-url", + ); window.location.href = authUrl; }; diff --git a/types/chain-item-type.ts b/types/chain-item-type.ts index 0e1d315..315b88a 100644 --- a/types/chain-item-type.ts +++ b/types/chain-item-type.ts @@ -1,5 +1,6 @@ export interface ChainItemType { chainId: string; + chainName: string; prefix: string; chainImageUrl: string; address: string;