This commit is contained in:
delivan 2022-12-15 00:41:57 +09:00
commit d14eead3ba
22 changed files with 724 additions and 296 deletions

View 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"]};
`;

View File

@ -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;
`;

View File

@ -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;

View File

@ -17,6 +17,7 @@ export const ConnectWalletModal: FunctionComponent<Props> = (props) => {
<ReactModal
isOpen={isModalOpen}
onRequestClose={onCloseModal}
ariaHideApp={false}
style={{
overlay: { background: "#181818b3" },
content: {

View File

@ -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) {

View 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
View File

@ -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
View 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";

View File

@ -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;
};

View File

@ -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",
};

View File

@ -15,6 +15,10 @@ const nextConfig = {
protocol: "https",
hostname: "raw.githubusercontent.com",
},
{
protocol: "https",
hostname: "abs.twimg.com",
},
],
},
};

View File

@ -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 />

View File

@ -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"]};
`;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 646 KiB

View 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
View 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
View File

@ -0,0 +1,2 @@
export * from "./twitter";
export * from "./icns";

21
repository/twitter.ts Normal file
View 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;
};

View File

@ -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;
}

View File

@ -1,4 +1,5 @@
export interface ChainItemType {
chainId: string;
prefix: string;
chainImageUrl: string;
address: string;

View File

@ -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
View File

@ -0,0 +1,5 @@
import { TwitterAuthInfoResponse } from "./api-response";
export interface TwitterProfileType extends TwitterAuthInfoResponse {
isRegistered: boolean;
}