2022-12-07 09:17:59 +00:00
|
|
|
// React
|
2022-12-01 08:33:51 +00:00
|
|
|
import { useEffect, useState } from "react";
|
2022-12-07 09:17:59 +00:00
|
|
|
|
2022-12-07 09:27:51 +00:00
|
|
|
// Types
|
2022-12-12 13:45:24 +00:00
|
|
|
import {
|
|
|
|
ChainItemType,
|
|
|
|
IcnsVerificationResponse,
|
|
|
|
TwitterAuthInfoResponse,
|
|
|
|
} from "../../types";
|
2022-12-05 14:16:02 +00:00
|
|
|
import { request } from "../../utils/url";
|
2022-12-01 08:33:51 +00:00
|
|
|
|
2022-12-07 09:17:59 +00:00
|
|
|
// Styles
|
2022-12-07 13:55:22 +00:00
|
|
|
import styled from "styled-components";
|
2022-12-06 14:53:31 +00:00
|
|
|
import color from "../../styles/color";
|
2022-12-01 08:33:51 +00:00
|
|
|
|
2022-12-07 09:17:59 +00:00
|
|
|
// Components
|
|
|
|
import { Logo } from "../../components/logo";
|
2022-12-09 11:59:52 +00:00
|
|
|
import { SkeletonChainList } from "../../components/skeleton";
|
2022-12-07 09:27:51 +00:00
|
|
|
|
2022-12-07 09:17:59 +00:00
|
|
|
import { PrimaryButton } from "../../components/primary-button";
|
2022-12-12 07:10:11 +00:00
|
|
|
import { TwitterProfile } from "../../components/twitter-profile";
|
2022-12-09 11:59:52 +00:00
|
|
|
import { ChainList } from "../../components/chain-list";
|
2022-12-12 13:45:24 +00:00
|
|
|
import { useRouter } from "next/router";
|
|
|
|
import { MainChainId } from "../../constants/wallet";
|
2022-12-13 14:46:32 +00:00
|
|
|
import {
|
|
|
|
getKeplrFromWindow,
|
|
|
|
KeplrWallet,
|
|
|
|
makeCosmwasmExecMsg,
|
|
|
|
simulateMsgs,
|
|
|
|
} from "../../wallets";
|
2022-12-12 13:45:24 +00:00
|
|
|
import { ChainIdHelper } from "@keplr-wallet/cosmos";
|
2022-12-07 09:17:59 +00:00
|
|
|
|
2022-12-13 12:21:16 +00:00
|
|
|
import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg";
|
|
|
|
import { AllChainsItem } from "../../components/chain-list/all-chains-item";
|
2022-12-13 13:10:31 +00:00
|
|
|
import { SearchInput } from "../../components/search-input";
|
2022-12-13 14:46:32 +00:00
|
|
|
import {
|
|
|
|
REGISTRAR_ADDRESS,
|
|
|
|
RESOLVER_ADDRESS,
|
|
|
|
REST_URL,
|
|
|
|
} from "../../constants/icns";
|
2022-12-13 12:21:16 +00:00
|
|
|
|
2022-12-01 08:33:51 +00:00
|
|
|
export default function VerificationPage() {
|
2022-12-12 13:45:24 +00:00
|
|
|
const router = useRouter();
|
2022-12-05 11:48:09 +00:00
|
|
|
const [twitterAuthInfo, setTwitterAuthInfo] =
|
|
|
|
useState<TwitterAuthInfoResponse | null>();
|
2022-12-01 08:33:51 +00:00
|
|
|
|
2022-12-07 09:17:59 +00:00
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
2022-12-13 12:21:16 +00:00
|
|
|
const [wallet, setWallet] = useState<KeplrWallet>();
|
|
|
|
const [allChains, setAllChains] = useState<ChainItemType>();
|
|
|
|
const [chainList, setChainList] = useState<ChainItemType[]>([]);
|
2022-12-12 13:45:24 +00:00
|
|
|
const [checkedItems, setCheckedItems] = useState(new Set());
|
2022-12-13 12:21:16 +00:00
|
|
|
const [allChecked, setAllChecked] = useState(false);
|
2022-12-13 13:10:31 +00:00
|
|
|
const [searchValue, setSearchValue] = useState("");
|
2022-12-12 13:45:24 +00:00
|
|
|
|
2022-12-05 14:16:02 +00:00
|
|
|
useEffect(() => {
|
|
|
|
const handleVerification = async () => {
|
2022-12-07 09:27:51 +00:00
|
|
|
if (window.location.search) {
|
2022-12-12 13:45:24 +00:00
|
|
|
if (window.location.search.match("error")) {
|
|
|
|
await router.push("/");
|
2022-12-13 12:21:16 +00:00
|
|
|
return;
|
2022-12-12 13:45:24 +00:00
|
|
|
}
|
|
|
|
|
2022-12-13 14:46:32 +00:00
|
|
|
const twitterInfo = await fetchTwitterInfo();
|
|
|
|
setTwitterAuthInfo(twitterInfo);
|
2022-12-12 13:45:24 +00:00
|
|
|
|
2022-12-13 12:21:16 +00:00
|
|
|
await initWallet();
|
2022-12-08 14:31:51 +00:00
|
|
|
|
|
|
|
setIsLoading(false);
|
2022-12-07 09:27:51 +00:00
|
|
|
}
|
2022-12-05 14:16:02 +00:00
|
|
|
};
|
2022-12-01 08:33:51 +00:00
|
|
|
|
2022-12-05 14:16:02 +00:00
|
|
|
handleVerification();
|
2022-12-01 08:33:51 +00:00
|
|
|
}, []);
|
|
|
|
|
2022-12-13 14:46:32 +00:00
|
|
|
const fetchTwitterInfo = async (): Promise<TwitterAuthInfoResponse> => {
|
2022-12-12 13:45:24 +00:00
|
|
|
const [, state, code] =
|
|
|
|
window.location.search.match(
|
|
|
|
/^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/,
|
|
|
|
) || [];
|
|
|
|
|
|
|
|
const newTwitterAuthInfo = await request<TwitterAuthInfoResponse>(
|
|
|
|
`/api/twitter-auth-info?state=${state}&code=${code}`,
|
|
|
|
);
|
|
|
|
|
2022-12-13 14:46:32 +00:00
|
|
|
return newTwitterAuthInfo;
|
2022-12-12 13:45:24 +00:00
|
|
|
};
|
|
|
|
|
2022-12-13 12:21:16 +00:00
|
|
|
const initWallet = async () => {
|
2022-12-12 13:45:24 +00:00
|
|
|
const keplr = await getKeplrFromWindow();
|
|
|
|
|
|
|
|
if (keplr) {
|
2022-12-13 12:21:16 +00:00
|
|
|
const keplrWallet = new KeplrWallet(keplr);
|
|
|
|
setWallet(keplrWallet);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (wallet) {
|
|
|
|
fetchAllChains();
|
|
|
|
fetchChainList();
|
|
|
|
}
|
|
|
|
}, [wallet]);
|
|
|
|
|
|
|
|
const fetchAllChains = async () => {
|
|
|
|
if (wallet) {
|
|
|
|
const chainNames = (await wallet.getChainInfosWithoutEndpoints()).map(
|
|
|
|
(chainInfo) => chainInfo.chainName,
|
|
|
|
);
|
|
|
|
|
|
|
|
setAllChains({
|
|
|
|
chainId: "all chains",
|
|
|
|
prefix: `all chains(${chainNames.length})`,
|
|
|
|
address: chainNames.join(", "),
|
|
|
|
chainImageUrl: AllChainsIcon,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const fetchChainList = async () => {
|
|
|
|
if (wallet) {
|
2022-12-12 13:45:24 +00:00
|
|
|
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 {
|
2022-12-13 12:21:16 +00:00
|
|
|
chainId: chainInfo.chainId,
|
2022-12-12 13:45:24 +00:00
|
|
|
prefix: chainInfo.bech32Config.bech32PrefixAccAddr,
|
|
|
|
chainImageUrl: `https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/${
|
|
|
|
ChainIdHelper.parse(chainInfo.chainId).identifier
|
|
|
|
}/chain.png`,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const chainArray: ChainItemType[] = [];
|
|
|
|
for (let i = 0; i < chainKeys.length; i++) {
|
|
|
|
chainArray.push({
|
|
|
|
address: chainKeys[i].bech32Address,
|
|
|
|
...chainInfos[i],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove duplicated item
|
2022-12-13 12:21:16 +00:00
|
|
|
const filteredChainList = chainArray.filter((chain, index, self) => {
|
|
|
|
return index === self.findIndex((t) => chain.prefix === t.prefix);
|
|
|
|
});
|
2022-12-12 13:45:24 +00:00
|
|
|
|
2022-12-13 12:21:16 +00:00
|
|
|
setChainList(filteredChainList);
|
2022-12-12 13:45:24 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-12-13 14:46:32 +00:00
|
|
|
const verifyTwitterAccount = async (accessToken: string) => {
|
|
|
|
if (wallet) {
|
2022-12-12 13:45:24 +00:00
|
|
|
const key = await wallet.getKey(MainChainId);
|
|
|
|
|
|
|
|
const icnsVerificationList = (
|
|
|
|
await request<IcnsVerificationResponse>("/api/icns-verification", {
|
|
|
|
method: "post",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
claimer: key.bech32Address,
|
2022-12-13 14:46:32 +00:00
|
|
|
authToken: accessToken,
|
2022-12-12 13:45:24 +00:00
|
|
|
}),
|
|
|
|
})
|
|
|
|
).verificationList;
|
|
|
|
|
2022-12-13 14:46:32 +00:00
|
|
|
return icnsVerificationList;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const checkAdr36 = async () => {
|
|
|
|
if (twitterAuthInfo && wallet) {
|
|
|
|
const key = await wallet.getKey(MainChainId);
|
|
|
|
|
|
|
|
const chainIds = Array.from(checkedItems).map(
|
|
|
|
(chain) => (chain as ChainItemType).chainId,
|
|
|
|
);
|
|
|
|
|
|
|
|
const adr36Infos = await wallet.signICNSAdr36(
|
|
|
|
MainChainId,
|
|
|
|
RESOLVER_ADDRESS,
|
|
|
|
key.bech32Address,
|
|
|
|
twitterAuthInfo.username,
|
|
|
|
chainIds,
|
|
|
|
);
|
|
|
|
|
|
|
|
return adr36Infos;
|
2022-12-12 13:45:24 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onClickRegistration = async () => {
|
2022-12-13 14:46:32 +00:00
|
|
|
const twitterInfo = await fetchTwitterInfo();
|
|
|
|
|
2022-12-14 05:50:07 +00:00
|
|
|
const adr36Infos = await checkAdr36();
|
|
|
|
|
2022-12-13 14:46:32 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
[
|
|
|
|
{
|
|
|
|
denom: "uosmo",
|
|
|
|
amount: "500000",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
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 = [registerMsg.amino];
|
|
|
|
const protoMsgs = [registerMsg.proto];
|
|
|
|
for (const addressMsg of addressMsgs) {
|
|
|
|
aminoMsgs.push(addressMsg.amino);
|
|
|
|
protoMsgs.push(addressMsg.proto);
|
|
|
|
}
|
|
|
|
|
|
|
|
const chainInfo = {
|
|
|
|
chainId: MainChainId,
|
|
|
|
rest: REST_URL,
|
|
|
|
};
|
|
|
|
|
|
|
|
const simulated = await simulateMsgs(
|
|
|
|
chainInfo,
|
|
|
|
key.bech32Address,
|
|
|
|
{
|
|
|
|
proto: protoMsgs,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
amount: [],
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
console.log("simulated", simulated);
|
|
|
|
}
|
2022-12-12 13:45:24 +00:00
|
|
|
};
|
|
|
|
|
2022-12-01 08:33:51 +00:00
|
|
|
return (
|
2022-12-06 14:53:31 +00:00
|
|
|
<Container>
|
|
|
|
<Logo />
|
2022-12-01 08:33:51 +00:00
|
|
|
|
2022-12-06 14:53:31 +00:00
|
|
|
<MainContainer>
|
2022-12-07 09:17:59 +00:00
|
|
|
{isLoading ? (
|
2022-12-09 11:59:52 +00:00
|
|
|
<SkeletonChainList />
|
2022-12-07 09:17:59 +00:00
|
|
|
) : (
|
|
|
|
<ContentContainer>
|
2022-12-09 11:59:52 +00:00
|
|
|
<TwitterProfile twitterProfileInformation={twitterAuthInfo} />
|
2022-12-07 09:17:59 +00:00
|
|
|
|
|
|
|
<ChainListTitleContainer>
|
|
|
|
<ChainListTitle>Chain List</ChainListTitle>
|
2022-12-13 13:10:31 +00:00
|
|
|
<SearchInput
|
|
|
|
searchValue={searchValue}
|
|
|
|
setSearchValue={setSearchValue}
|
|
|
|
/>
|
2022-12-07 09:17:59 +00:00
|
|
|
</ChainListTitleContainer>
|
|
|
|
|
2022-12-13 13:10:31 +00:00
|
|
|
{allChains && !searchValue ? (
|
2022-12-13 12:21:16 +00:00
|
|
|
<AllChainsItem
|
|
|
|
allChecked={allChecked}
|
|
|
|
setAllChecked={setAllChecked}
|
|
|
|
chainItem={allChains}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
|
2022-12-12 13:45:24 +00:00
|
|
|
<ChainList
|
2022-12-13 12:21:16 +00:00
|
|
|
allChecked={allChecked}
|
|
|
|
setAllChecked={setAllChecked}
|
2022-12-13 13:10:31 +00:00
|
|
|
chainList={chainList.filter(
|
|
|
|
(chain) =>
|
|
|
|
chain.chainId.includes(searchValue) ||
|
|
|
|
chain.address.includes(searchValue) ||
|
|
|
|
chain.prefix.includes(searchValue),
|
|
|
|
)}
|
2022-12-12 13:45:24 +00:00
|
|
|
checkedItems={checkedItems}
|
|
|
|
setCheckedItems={setCheckedItems}
|
|
|
|
/>
|
2022-12-07 09:17:59 +00:00
|
|
|
|
|
|
|
<ButtonContainer>
|
2022-12-12 13:45:24 +00:00
|
|
|
<PrimaryButton
|
|
|
|
disabled={checkedItems.size < 1}
|
|
|
|
onClick={onClickRegistration}
|
|
|
|
>
|
|
|
|
Register
|
|
|
|
</PrimaryButton>
|
2022-12-07 09:17:59 +00:00
|
|
|
</ButtonContainer>
|
|
|
|
</ContentContainer>
|
|
|
|
)}
|
2022-12-06 14:53:31 +00:00
|
|
|
</MainContainer>
|
|
|
|
</Container>
|
2022-12-01 08:33:51 +00:00
|
|
|
);
|
|
|
|
}
|
2022-12-07 13:55:22 +00:00
|
|
|
|
|
|
|
const Container = styled.div`
|
|
|
|
width: 100vw;
|
|
|
|
height: 100vh;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const MainContainer = styled.div`
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
color: white;
|
|
|
|
`;
|
|
|
|
|
2022-12-09 11:59:52 +00:00
|
|
|
export const ContentContainer = styled.div`
|
2022-12-07 13:55:22 +00:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
width: 40rem;
|
|
|
|
|
|
|
|
margin-top: 5rem;
|
|
|
|
`;
|
|
|
|
|
2022-12-09 11:59:52 +00:00
|
|
|
export const ButtonContainer = styled.div`
|
2022-12-07 13:55:22 +00:00
|
|
|
width: 12rem;
|
|
|
|
height: 4rem;
|
|
|
|
|
2022-12-09 11:59:52 +00:00
|
|
|
margin-top: 2rem;
|
2022-12-07 13:55:22 +00:00
|
|
|
`;
|
|
|
|
|
2022-12-09 11:59:52 +00:00
|
|
|
export const ChainListTitleContainer = styled.div`
|
2022-12-07 13:55:22 +00:00
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
margin-top: 2rem;
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const ChainListTitle = styled.div`
|
|
|
|
font-weight: 700;
|
|
|
|
font-size: 1.5rem;
|
|
|
|
line-height: 1.9rem;
|
|
|
|
|
|
|
|
color: ${color.white};
|
|
|
|
`;
|