forked from LaconicNetwork/icns-frontend
Merge branch 'main' of https://github.com/interchain-name/icns-frontend into main
This commit is contained in:
commit
d14eead3ba
65
components/chain-list/all-chains-item.tsx
Normal file
65
components/chain-list/all-chains-item.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { ChainItemType } from "../../types";
|
||||
import {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
FunctionComponent,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
ChainImageContainer,
|
||||
ChainInfoContainer,
|
||||
ChainItemContainer,
|
||||
} from "./chain-list";
|
||||
import { ChainImage } from "./chain-image";
|
||||
import { Flex1 } from "../../styles/flex-1";
|
||||
import { ChainCheckBox, ChainName, WalletAddress } from "./chain-item";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
|
||||
interface Props {
|
||||
allChecked: boolean;
|
||||
setAllChecked: Dispatch<SetStateAction<boolean>>;
|
||||
chainItem: ChainItemType;
|
||||
}
|
||||
|
||||
export const AllChainsItem: FunctionComponent<Props> = (props) => {
|
||||
const { allChecked, setAllChecked, chainItem } = props;
|
||||
|
||||
const checkHandler = () => {
|
||||
setAllChecked(!allChecked);
|
||||
};
|
||||
|
||||
return (
|
||||
<AllChainsContainer>
|
||||
<ChainItemContainer
|
||||
key={chainItem.prefix}
|
||||
isLoading={false}
|
||||
onClick={checkHandler}
|
||||
>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<ChainImage
|
||||
src={chainItem.chainImageUrl}
|
||||
fill={true}
|
||||
alt={`${chainItem.prefix} chain image`}
|
||||
/>
|
||||
</ChainImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<ChainName>{`.${chainItem.prefix}`}</ChainName>
|
||||
<WalletAddress>{chainItem.address}</WalletAddress>
|
||||
</ChainInfoContainer>
|
||||
|
||||
<Flex1 />
|
||||
|
||||
<ChainCheckBox checked={allChecked} readOnly />
|
||||
</ChainItemContainer>
|
||||
</AllChainsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const AllChainsContainer = styled.div`
|
||||
width: 100%;
|
||||
|
||||
background-color: ${color.grey["800"]};
|
||||
`;
|
@ -1,5 +1,12 @@
|
||||
import { ChainItemType } from "../../types";
|
||||
import { ChangeEvent, FunctionComponent, useState } from "react";
|
||||
import {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
FunctionComponent,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
ChainImageContainer,
|
||||
ChainInfoContainer,
|
||||
@ -14,18 +21,29 @@ import { ChainImage } from "./chain-image";
|
||||
interface Props {
|
||||
chainItem: ChainItemType;
|
||||
checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void;
|
||||
checkedItems: Set<unknown>;
|
||||
}
|
||||
|
||||
export const ChainItem: FunctionComponent<Props> = (props) => {
|
||||
const { chainItem, checkedItemHandler } = props;
|
||||
const { chainItem, checkedItemHandler, checkedItems } = props;
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
const checkHandler = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const checkHandler = () => {
|
||||
setChecked(!checked);
|
||||
checkedItemHandler(chainItem, event.target.checked);
|
||||
checkedItemHandler(chainItem, !checked);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setChecked(checkedItems.has(chainItem));
|
||||
}, [checkedItems]);
|
||||
|
||||
return (
|
||||
<ChainItemContainer key={chainItem.prefix} isLoading={false}>
|
||||
<ChainItemContainer
|
||||
key={chainItem.prefix}
|
||||
isLoading={false}
|
||||
checked={checked}
|
||||
onClick={checkHandler}
|
||||
>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<ChainImage
|
||||
src={chainItem.chainImageUrl}
|
||||
@ -40,31 +58,36 @@ export const ChainItem: FunctionComponent<Props> = (props) => {
|
||||
|
||||
<Flex1 />
|
||||
|
||||
<ChainCheckBox
|
||||
checked={checked}
|
||||
onChange={(event) => checkHandler(event)}
|
||||
/>
|
||||
<ChainCheckBox checked={checked} readOnly />
|
||||
</ChainItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const ChainName = styled.div`
|
||||
export const ChainName = styled.div`
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
|
||||
color: ${color.grey["100"]};
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
const WalletAddress = styled.div`
|
||||
export const WalletAddress = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
|
||||
max-height: 2rem;
|
||||
max-width: 27rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
const ChainCheckBox = styled.input.attrs({ type: "checkbox" })`
|
||||
export const ChainCheckBox = styled.input.attrs({ type: "checkbox" })`
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
`;
|
||||
|
@ -1,17 +1,25 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import { Dispatch, FunctionComponent, SetStateAction, useEffect } from "react";
|
||||
import { ChainItemType, WidthHeightProps } from "../../types";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
import { ChainItem } from "./chain-item";
|
||||
|
||||
interface Props {
|
||||
allChecked: boolean;
|
||||
setAllChecked: Dispatch<SetStateAction<boolean>>;
|
||||
chainList: ChainItemType[];
|
||||
checkedItems: any;
|
||||
setCheckedItems: any;
|
||||
checkedItems: Set<unknown>;
|
||||
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
|
||||
}
|
||||
|
||||
export const ChainList: FunctionComponent<Props> = (props) => {
|
||||
const { chainList, checkedItems, setCheckedItems } = props;
|
||||
const {
|
||||
allChecked,
|
||||
setAllChecked,
|
||||
chainList,
|
||||
checkedItems,
|
||||
setCheckedItems,
|
||||
} = props;
|
||||
|
||||
const checkedItemHandler = (chainItem: ChainItemType, isChecked: boolean) => {
|
||||
const tempSet = new Set(checkedItems);
|
||||
@ -25,6 +33,22 @@ export const ChainList: FunctionComponent<Props> = (props) => {
|
||||
setCheckedItems(tempSet);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (allChecked) {
|
||||
setCheckedItems(new Set(chainList));
|
||||
} else if (chainList.length === checkedItems.size) {
|
||||
setCheckedItems(new Set());
|
||||
}
|
||||
}, [allChecked]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chainList.length === checkedItems.size && checkedItems.size !== 0) {
|
||||
setAllChecked(true);
|
||||
} else {
|
||||
setAllChecked(false);
|
||||
}
|
||||
}, [checkedItems]);
|
||||
|
||||
return (
|
||||
<ChainContainer color={color.grey["800"]}>
|
||||
{chainList.map((chainItem) => (
|
||||
@ -32,6 +56,7 @@ export const ChainList: FunctionComponent<Props> = (props) => {
|
||||
key={chainItem.address}
|
||||
chainItem={chainItem}
|
||||
checkedItemHandler={checkedItemHandler}
|
||||
checkedItems={checkedItems}
|
||||
/>
|
||||
))}
|
||||
</ChainContainer>
|
||||
@ -48,7 +73,10 @@ export const ChainContainer = styled.div`
|
||||
background-color: ${(props) => props.color};
|
||||
`;
|
||||
|
||||
export const ChainItemContainer = styled.div<{ isLoading: boolean }>`
|
||||
export const ChainItemContainer = styled.div<{
|
||||
isLoading: boolean;
|
||||
checked?: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
@ -17,6 +17,7 @@ export const ConnectWalletModal: FunctionComponent<Props> = (props) => {
|
||||
<ReactModal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={onCloseModal}
|
||||
ariaHideApp={false}
|
||||
style={{
|
||||
overlay: { background: "#181818b3" },
|
||||
content: {
|
||||
|
@ -4,9 +4,13 @@ import color from "../../styles/color";
|
||||
import { Flex1 } from "../../styles/flex-1";
|
||||
import styled from "styled-components";
|
||||
import Image from "next/image";
|
||||
import { SELECTED_WALLET_KEY, WalletType } from "../../constants/wallet";
|
||||
import {
|
||||
SELECTED_WALLET_KEY,
|
||||
WALLET_INSTALL_URL,
|
||||
WalletType,
|
||||
} from "../../constants/wallet";
|
||||
import { getKeplrFromWindow, KeplrWallet } from "../../wallets";
|
||||
import { loginWithTwitter } from "../../constants/twitter";
|
||||
import { loginWithTwitter } from "../../repository";
|
||||
|
||||
interface Props {
|
||||
wallet: WalletType;
|
||||
@ -32,8 +36,7 @@ export const WalletItem: FunctionComponent<Props> = (props: Props) => {
|
||||
const keplr = await getKeplrFromWindow();
|
||||
|
||||
if (keplr === undefined) {
|
||||
window.location.href =
|
||||
"https://chrome.google.com/webstore/detail/keplr/dmkamcknogkgcdfhhbddcghachkejeap";
|
||||
window.location.href = WALLET_INSTALL_URL;
|
||||
}
|
||||
|
||||
if (keplr) {
|
||||
|
83
components/search-input/index.tsx
Normal file
83
components/search-input/index.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import {
|
||||
Dispatch,
|
||||
FunctionComponent,
|
||||
SetStateAction,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
import SearchIcon from "../../public/images/svg/search-icon.svg";
|
||||
|
||||
interface Props {
|
||||
searchValue: string;
|
||||
setSearchValue: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const SearchInput: FunctionComponent<Props> = (props) => {
|
||||
const { searchValue, setSearchValue } = props;
|
||||
|
||||
return (
|
||||
<SearchContainer>
|
||||
<SearchIconContainer>
|
||||
<Image src={SearchIcon} fill={true} alt="search icon" />
|
||||
</SearchIconContainer>
|
||||
|
||||
<SearchText
|
||||
type="text"
|
||||
placeholder="search"
|
||||
value={searchValue}
|
||||
onChange={(event) => {
|
||||
setSearchValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</SearchContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
|
||||
min-width: 10rem;
|
||||
height: 2rem;
|
||||
|
||||
padding: 0.5rem 1.3rem;
|
||||
|
||||
border-radius: 3rem;
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
const SearchIconContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
width: 1.3rem;
|
||||
height: 1.3rem;
|
||||
`;
|
||||
|
||||
const SearchText = styled.input`
|
||||
border: none;
|
||||
|
||||
color: ${color.white};
|
||||
background-color: ${color.grey["700"]};
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2rem;
|
||||
|
||||
::placeholder,
|
||||
::-webkit-input-placeholder {
|
||||
color: ${color.grey["400"]};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
160
config.ts
160
config.ts
@ -1,160 +0,0 @@
|
||||
import { ChainItemType } from "./types";
|
||||
|
||||
export const AccountInfos: ChainItemType[] = [
|
||||
{
|
||||
prefix: "cosmos",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/cosmoshub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "osmo",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/osmosis/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "agoric",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/agoric/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "akash",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/akashnet/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "axelar",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/axelar-dojo/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "bostrom",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bostrom/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "persistence",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/core/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "cro",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/crypto-org-chain-mainnet/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "emoney",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/emoney/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "evmos",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/evmos_9001/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "gravity",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/gravity-bridge/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "ixo",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/impacthub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "star",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/iov-mainnet-ibc/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "iaa",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/irishub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "juno",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/juno/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "kava",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/kava_2222/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "regen",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/regen/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "secret",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/secret/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "sent",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/sentinelhub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "certik",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/shentu-2.2/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "sif",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/sifchain/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "somm",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/sommelier/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "stars",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/stargaze/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "stride",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/stride/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "tgrade",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/tgrade-mainnet/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "umee",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/umee/chain.png",
|
||||
},
|
||||
];
|
9
constants/icns.ts
Normal file
9
constants/icns.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const MainChainId = "osmo-test-4";
|
||||
|
||||
export const RPC_URL = "https://rpc.testnet.osmosis.zone";
|
||||
export const REST_URL = "https://lcd.testnet.osmosis.zone";
|
||||
|
||||
export const REGISTRAR_ADDRESS =
|
||||
"osmo1npn97g7hsgqlp70rw8nhd7c7vyvkukv9x0n25sn4fk5mgcjlz4gq9zlgf3";
|
||||
export const RESOLVER_ADDRESS =
|
||||
"osmo1002awr7frr9wk44lc3vfzt0d2w6vz5z03ql6fszjsjy8vdcvk0sskruz3c";
|
@ -1,5 +1,3 @@
|
||||
import { TwitterAuthUrlResponse } from "../types";
|
||||
|
||||
export const twitterOAuthBaseUrl = "https://twitter.com/i/oauth2/authorize";
|
||||
|
||||
export const twitterOAuthScopes = [
|
||||
@ -9,11 +7,3 @@ export const twitterOAuthScopes = [
|
||||
];
|
||||
|
||||
export const twitterApiBaseUrl = "https://api.twitter.com/2";
|
||||
|
||||
export const loginWithTwitter = async () => {
|
||||
const { authUrl }: TwitterAuthUrlResponse = await (
|
||||
await fetch("/api/twitter-auth-url")
|
||||
).json();
|
||||
|
||||
window.location.href = authUrl;
|
||||
};
|
||||
|
@ -3,8 +3,9 @@ import { StaticImageData } from "next/image";
|
||||
import KeplrIcon from "../public/images/svg/keplr-icon.svg";
|
||||
import CosmostationIcon from "../public/images/svg/cosmostation-icon.svg";
|
||||
|
||||
export const WALLET_INSTALL_URL =
|
||||
"https://chrome.google.com/webstore/detail/keplr/dmkamcknogkgcdfhhbddcghachkejeap";
|
||||
export const SELECTED_WALLET_KEY = "SELECTED_WALLET_KEY";
|
||||
export const MainChainId = "osmo-test-4";
|
||||
|
||||
export type WalletName = "Keplr" | "Cosmostation";
|
||||
export interface WalletType {
|
||||
@ -25,3 +26,8 @@ export const WalletList: WalletType[] = [
|
||||
isReady: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const ContractFee = {
|
||||
denom: "uosmo",
|
||||
amount: "500000",
|
||||
};
|
||||
|
@ -15,6 +15,10 @@ const nextConfig = {
|
||||
protocol: "https",
|
||||
hostname: "raw.githubusercontent.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "abs.twimg.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -8,8 +8,32 @@ import color from "../../styles/color";
|
||||
|
||||
import AlertCircleOutlineIcon from "../../public/images/svg/alert-circle-outline.svg";
|
||||
import TwitterIcon from "../../public/images/svg/twitter-icon.svg";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { TendermintTxTracer } from "@keplr-wallet/cosmos";
|
||||
|
||||
export default function CompletePage() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const { txHash } = router.query;
|
||||
|
||||
if (txHash) {
|
||||
traceTX(txHash as string);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const traceTX = async (txHash: string) => {
|
||||
const txTracer = new TendermintTxTracer(
|
||||
"https://rpc.testnet.osmosis.zone",
|
||||
"/websocket",
|
||||
);
|
||||
|
||||
const result = await txTracer.traceTx(Buffer.from(txHash, "hex"));
|
||||
|
||||
console.log(result);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Logo />
|
||||
|
@ -5,7 +5,9 @@ import { useEffect, useState } from "react";
|
||||
import {
|
||||
ChainItemType,
|
||||
IcnsVerificationResponse,
|
||||
RegisteredAddresses,
|
||||
TwitterAuthInfoResponse,
|
||||
TwitterProfileType,
|
||||
} from "../../types";
|
||||
import { request } from "../../utils/url";
|
||||
|
||||
@ -21,57 +23,137 @@ import { PrimaryButton } from "../../components/primary-button";
|
||||
import { TwitterProfile } from "../../components/twitter-profile";
|
||||
import { ChainList } from "../../components/chain-list";
|
||||
import { useRouter } from "next/router";
|
||||
import { MainChainId } from "../../constants/wallet";
|
||||
import { getKeplrFromWindow, KeplrWallet } from "../../wallets";
|
||||
import { ContractFee } from "../../constants/wallet";
|
||||
import {
|
||||
getKeplrFromWindow,
|
||||
KeplrWallet,
|
||||
makeCosmwasmExecMsg,
|
||||
sendMsgs,
|
||||
simulateMsgs,
|
||||
} from "../../wallets";
|
||||
import { ChainIdHelper } from "@keplr-wallet/cosmos";
|
||||
import ErrorBoundary from "../../components/error-boundary";
|
||||
|
||||
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 {
|
||||
fetchTwitterInfo,
|
||||
queryAddressesFromTwitterName,
|
||||
queryRegisteredTwitterId,
|
||||
} from "../../repository";
|
||||
|
||||
export default function VerificationPage() {
|
||||
const router = useRouter();
|
||||
const [twitterAuthInfo, setTwitterAuthInfo] =
|
||||
useState<TwitterAuthInfoResponse | null>();
|
||||
const [twitterAuthInfo, setTwitterAuthInfo] = useState<TwitterProfileType>();
|
||||
|
||||
const [chainList, setChainList] = useState<ChainItemType[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [wallet, setWallet] = useState<KeplrWallet>();
|
||||
const [allChains, setAllChains] = useState<ChainItemType>();
|
||||
const [chainList, setChainList] = useState<ChainItemType[]>([]);
|
||||
const [registeredAddressList, setRegisteredAddressList] = useState<string[]>(
|
||||
[],
|
||||
);
|
||||
const [checkedItems, setCheckedItems] = useState(new Set());
|
||||
const [allChecked, setAllChecked] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const handleVerification = async () => {
|
||||
if (window.location.search) {
|
||||
if (window.location.search.match("error")) {
|
||||
await router.push("/");
|
||||
}
|
||||
|
||||
await fetchTwitterInfo();
|
||||
|
||||
await fetchChainList();
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleVerification();
|
||||
}, []);
|
||||
|
||||
const fetchTwitterInfo = async () => {
|
||||
const fetchUrlQueryParameter = (): { state: string; code: string } => {
|
||||
// Twitter state, auth code check
|
||||
const [, state, code] =
|
||||
window.location.search.match(
|
||||
/^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/,
|
||||
) || [];
|
||||
|
||||
const newTwitterAuthInfo = await request<TwitterAuthInfoResponse>(
|
||||
`/api/twitter-auth-info?state=${state}&code=${code}`,
|
||||
);
|
||||
|
||||
setTwitterAuthInfo(newTwitterAuthInfo);
|
||||
return {
|
||||
state,
|
||||
code,
|
||||
};
|
||||
};
|
||||
|
||||
const fetchChainList = async () => {
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
if (window.location.search) {
|
||||
// Twitter Login Error Check
|
||||
if (window.location.search.match("error")) {
|
||||
await router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
const { state, code } = fetchUrlQueryParameter();
|
||||
|
||||
try {
|
||||
// Initialize Wallet
|
||||
await initWallet();
|
||||
|
||||
// Fetch Twitter Profile
|
||||
const twitterInfo = await fetchTwitterInfo(state, code);
|
||||
|
||||
const registeredQueryResponse = await queryRegisteredTwitterId(
|
||||
twitterInfo.id,
|
||||
);
|
||||
|
||||
setTwitterAuthInfo({
|
||||
...twitterInfo,
|
||||
isRegistered: "data" in registeredQueryResponse,
|
||||
});
|
||||
|
||||
if ("data" in registeredQueryResponse) {
|
||||
const addressesQueryResponse = await queryAddressesFromTwitterName(
|
||||
registeredQueryResponse.data.name,
|
||||
);
|
||||
|
||||
setRegisteredAddressList(
|
||||
addressesQueryResponse.data.addresses.map(
|
||||
(address) => address.address,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
}, []);
|
||||
|
||||
const initWallet = async () => {
|
||||
const keplr = await getKeplrFromWindow();
|
||||
|
||||
if (keplr) {
|
||||
const wallet = new KeplrWallet(keplr);
|
||||
const keplrWallet = new KeplrWallet(keplr);
|
||||
setWallet(keplrWallet);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// After Wallet Initialize
|
||||
if (wallet) {
|
||||
fetchChainList();
|
||||
}
|
||||
}, [wallet]);
|
||||
|
||||
useEffect(() => {
|
||||
const filteredChainList = chainList.filter((chain) => {
|
||||
return registeredAddressList.includes(chain.address);
|
||||
});
|
||||
|
||||
setCheckedItems(new Set(filteredChainList));
|
||||
}, [registeredAddressList]);
|
||||
|
||||
const fetchChainList = async () => {
|
||||
if (wallet) {
|
||||
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
|
||||
(c) => c.chainId,
|
||||
);
|
||||
@ -82,6 +164,8 @@ export default function VerificationPage() {
|
||||
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
|
||||
@ -90,7 +174,7 @@ export default function VerificationPage() {
|
||||
},
|
||||
);
|
||||
|
||||
const chainArray: ChainItemType[] = [];
|
||||
const chainArray = [];
|
||||
for (let i = 0; i < chainKeys.length; i++) {
|
||||
chainArray.push({
|
||||
address: chainKeys[i].bech32Address,
|
||||
@ -99,23 +183,39 @@ export default function VerificationPage() {
|
||||
}
|
||||
|
||||
// remove duplicated item
|
||||
// const filteredChainList = chainArray.filter((chain, index, self) => {
|
||||
// return index === self.findIndex((t) => chain.prefix === t.prefix);
|
||||
// });
|
||||
const filteredChainList = chainArray.filter((nextChain, index, self) => {
|
||||
return (
|
||||
index ===
|
||||
self.findIndex((prevChain) => {
|
||||
const isDuplicated = prevChain.prefix === nextChain.prefix;
|
||||
|
||||
setChainList(chainArray);
|
||||
if (isDuplicated && prevChain.chainName !== nextChain.chainName) {
|
||||
console.log(
|
||||
`${nextChain.chainName} has been deleted due to a duplicate name with ${prevChain.chainName}`,
|
||||
);
|
||||
}
|
||||
|
||||
return isDuplicated;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
setAllChains({
|
||||
chainId: "all chains",
|
||||
prefix: `all chains(${filteredChainList.length})`,
|
||||
address: chainInfos.map((chainInfo) => chainInfo.chainName).join(", "),
|
||||
chainImageUrl: AllChainsIcon,
|
||||
});
|
||||
|
||||
setChainList(filteredChainList);
|
||||
}
|
||||
};
|
||||
|
||||
const verifyTwitterAccount = async () => {
|
||||
try {
|
||||
const keplr = await getKeplrFromWindow();
|
||||
|
||||
if (twitterAuthInfo && keplr) {
|
||||
const wallet = new KeplrWallet(keplr);
|
||||
const verifyTwitterAccount = async (accessToken: string) => {
|
||||
if (wallet) {
|
||||
const key = await wallet.getKey(MainChainId);
|
||||
|
||||
const icnsVerificationList = (
|
||||
return (
|
||||
await request<IcnsVerificationResponse>("/api/icns-verification", {
|
||||
method: "post",
|
||||
headers: {
|
||||
@ -123,24 +223,140 @@ export default function VerificationPage() {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
claimer: key.bech32Address,
|
||||
authToken: twitterAuthInfo.accessToken,
|
||||
authToken: accessToken,
|
||||
}),
|
||||
})
|
||||
).verificationList;
|
||||
|
||||
console.log(icnsVerificationList);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
};
|
||||
|
||||
const checkAdr36 = async () => {
|
||||
if (twitterAuthInfo && wallet) {
|
||||
const key = await wallet.getKey(MainChainId);
|
||||
|
||||
const chainIds = Array.from(checkedItems).map((chain) => {
|
||||
return (chain as ChainItemType).chainId;
|
||||
});
|
||||
|
||||
return await wallet.signICNSAdr36(
|
||||
MainChainId,
|
||||
RESOLVER_ADDRESS,
|
||||
key.bech32Address,
|
||||
twitterAuthInfo.username,
|
||||
chainIds,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickRegistration = async () => {
|
||||
await verifyTwitterAccount();
|
||||
const { state, code } = fetchUrlQueryParameter();
|
||||
const twitterInfo = await fetchTwitterInfo(state, code);
|
||||
|
||||
const adr36Infos = await checkAdr36();
|
||||
|
||||
const icnsVerificationList = await verifyTwitterAccount(
|
||||
twitterInfo.accessToken,
|
||||
);
|
||||
|
||||
if (wallet && icnsVerificationList && adr36Infos) {
|
||||
const key = await wallet.getKey(MainChainId);
|
||||
|
||||
const registerMsg = makeCosmwasmExecMsg(
|
||||
key.bech32Address,
|
||||
REGISTRAR_ADDRESS,
|
||||
{
|
||||
claim: {
|
||||
name: twitterInfo.username,
|
||||
verifying_msg:
|
||||
icnsVerificationList[0].status === "fulfilled"
|
||||
? icnsVerificationList[0].value.data.verifying_msg
|
||||
: "",
|
||||
verifications: icnsVerificationList.map((verification) => {
|
||||
if (verification.status === "fulfilled") {
|
||||
return {
|
||||
public_key: verification.value.data.public_key,
|
||||
signature: verification.value.data.signature,
|
||||
};
|
||||
}
|
||||
}),
|
||||
},
|
||||
},
|
||||
[ContractFee],
|
||||
);
|
||||
|
||||
const addressMsgs = adr36Infos.map((adr36Info) => {
|
||||
return makeCosmwasmExecMsg(
|
||||
key.bech32Address,
|
||||
RESOLVER_ADDRESS,
|
||||
{
|
||||
set_record: {
|
||||
name: twitterInfo.username,
|
||||
bech32_prefix: adr36Info.bech32Prefix,
|
||||
adr36_info: {
|
||||
signer_bech32_address: adr36Info.bech32Address,
|
||||
address_hash: adr36Info.addressHash,
|
||||
pub_key: Buffer.from(adr36Info.pubKey).toString("base64"),
|
||||
signature: Buffer.from(adr36Info.signature).toString("base64"),
|
||||
signature_salt: adr36Info.signatureSalt.toString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
const aminoMsgs = twitterAuthInfo?.isRegistered
|
||||
? []
|
||||
: [registerMsg.amino];
|
||||
const protoMsgs = twitterAuthInfo?.isRegistered
|
||||
? []
|
||||
: [registerMsg.proto];
|
||||
|
||||
for (const addressMsg of addressMsgs) {
|
||||
aminoMsgs.push(addressMsg.amino);
|
||||
protoMsgs.push(addressMsg.proto);
|
||||
}
|
||||
|
||||
console.log(aminoMsgs);
|
||||
|
||||
const chainInfo = {
|
||||
chainId: MainChainId,
|
||||
rest: REST_URL,
|
||||
};
|
||||
|
||||
const simulated = await simulateMsgs(
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
},
|
||||
);
|
||||
|
||||
const txHash = await sendMsgs(
|
||||
wallet,
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
amino: aminoMsgs,
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
gas: Math.floor(simulated.gasUsed * 1.5).toString(),
|
||||
},
|
||||
);
|
||||
|
||||
router.push({
|
||||
pathname: "complete",
|
||||
query: { txHash: Buffer.from(txHash).toString("hex") },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Container>
|
||||
<Logo />
|
||||
|
||||
@ -153,11 +369,29 @@ export default function VerificationPage() {
|
||||
|
||||
<ChainListTitleContainer>
|
||||
<ChainListTitle>Chain List</ChainListTitle>
|
||||
<SearchContainer>Search</SearchContainer>
|
||||
<SearchInput
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
/>
|
||||
</ChainListTitleContainer>
|
||||
|
||||
{allChains && !searchValue ? (
|
||||
<AllChainsItem
|
||||
allChecked={allChecked}
|
||||
setAllChecked={setAllChecked}
|
||||
chainItem={allChains}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<ChainList
|
||||
chainList={chainList}
|
||||
allChecked={allChecked}
|
||||
setAllChecked={setAllChecked}
|
||||
chainList={chainList.filter(
|
||||
(chain) =>
|
||||
chain.chainId.includes(searchValue) ||
|
||||
chain.address.includes(searchValue) ||
|
||||
chain.prefix.includes(searchValue),
|
||||
)}
|
||||
checkedItems={checkedItems}
|
||||
setCheckedItems={setCheckedItems}
|
||||
/>
|
||||
@ -174,7 +408,6 @@ export default function VerificationPage() {
|
||||
)}
|
||||
</MainContainer>
|
||||
</Container>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@ -226,15 +459,3 @@ const ChainListTitle = styled.div`
|
||||
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
const SearchContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 3rem;
|
||||
|
||||
min-width: 10rem;
|
||||
height: 2rem;
|
||||
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
33
public/images/svg/all-chains-icon.svg
Normal file
33
public/images/svg/all-chains-icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 646 KiB |
3
public/images/svg/search-icon.svg
Normal file
3
public/images/svg/search-icon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.08358 3.5C5.9967 3.5 3.5 5.93947 3.5 8.95559C3.5 11.9717 5.9967 14.4112 9.08358 14.4112C10.1857 14.4112 11.2056 14.0953 12.0704 13.5587L16.1041 17.5L17.5 16.1361L13.5176 12.255C14.2335 11.3374 14.6672 10.2004 14.6672 8.95559C14.6672 5.93947 12.1705 3.5 9.08358 3.5ZM9.08358 4.78367C11.4481 4.78367 13.3534 6.64524 13.3534 8.95559C13.3534 11.2659 11.4481 13.1275 9.08358 13.1275C6.71902 13.1275 4.81378 11.2659 4.81378 8.95559C4.81378 6.64524 6.71902 4.78367 9.08358 4.78367Z" fill="#5B5B5B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 608 B |
43
repository/icns.ts
Normal file
43
repository/icns.ts
Normal file
@ -0,0 +1,43 @@
|
||||
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<NameByTwitterIdQueryResponse | QueryError> => {
|
||||
const msg = {
|
||||
name_by_twitter_id: { twitter_id: twitterId },
|
||||
};
|
||||
return request<NameByTwitterIdQueryResponse>(
|
||||
getCosmwasmQueryUrl(
|
||||
REGISTRAR_ADDRESS,
|
||||
Buffer.from(JSON.stringify(msg)).toString("base64"),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const queryAddressesFromTwitterName = async (
|
||||
twitterUsername: string,
|
||||
): Promise<AddressesQueryResponse> => {
|
||||
const msg = {
|
||||
addresses: { name: twitterUsername },
|
||||
};
|
||||
return request<any>(
|
||||
getCosmwasmQueryUrl(
|
||||
RESOLVER_ADDRESS,
|
||||
Buffer.from(JSON.stringify(msg)).toString("base64"),
|
||||
),
|
||||
);
|
||||
};
|
2
repository/index.ts
Normal file
2
repository/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./twitter";
|
||||
export * from "./icns";
|
21
repository/twitter.ts
Normal file
21
repository/twitter.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { TwitterAuthInfoResponse, TwitterAuthUrlResponse } from "../types";
|
||||
import { request } from "../utils/url";
|
||||
|
||||
export const loginWithTwitter = async () => {
|
||||
const { authUrl }: TwitterAuthUrlResponse = await (
|
||||
await fetch("/api/twitter-auth-url")
|
||||
).json();
|
||||
|
||||
window.location.href = authUrl;
|
||||
};
|
||||
|
||||
export const fetchTwitterInfo = async (
|
||||
state: string,
|
||||
code: string,
|
||||
): Promise<TwitterAuthInfoResponse> => {
|
||||
const newTwitterAuthInfo = await request<TwitterAuthInfoResponse>(
|
||||
`/api/twitter-auth-info?state=${state}&code=${code}`,
|
||||
);
|
||||
|
||||
return newTwitterAuthInfo;
|
||||
};
|
@ -37,3 +37,25 @@ export interface IcnsVerificationResponse {
|
||||
}
|
||||
)[];
|
||||
}
|
||||
|
||||
export interface NameByTwitterIdQueryResponse {
|
||||
data: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddressesQueryResponse {
|
||||
data: {
|
||||
addresses: RegisteredAddresses[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface RegisteredAddresses {
|
||||
address: string;
|
||||
bech32_prefix: string;
|
||||
}
|
||||
|
||||
export interface QueryError {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
export interface ChainItemType {
|
||||
chainId: string;
|
||||
prefix: string;
|
||||
chainImageUrl: string;
|
||||
address: string;
|
@ -1,4 +1,5 @@
|
||||
export * from "./width-height-props";
|
||||
export * from "./api-response";
|
||||
export * from "./account-info";
|
||||
export * from "./chain-item-type";
|
||||
export * from "./msg";
|
||||
export * from "./twitter";
|
||||
|
5
types/twitter.ts
Normal file
5
types/twitter.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { TwitterAuthInfoResponse } from "./api-response";
|
||||
|
||||
export interface TwitterProfileType extends TwitterAuthInfoResponse {
|
||||
isRegistered: boolean;
|
||||
}
|
Loading…
Reference in New Issue
Block a user