Skip to content

Commit

Permalink
Keplr wallet intergration
Browse files Browse the repository at this point in the history
  • Loading branch information
HeesungB committed Jan 4, 2023
1 parent 5fa27f1 commit fe4677b
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 84 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
"author": "",
"license": "",
"dependencies": {
"@keplr-wallet/chain-validator": "^0.11.18-rc.3",
"@keplr-wallet/common": "0.11.18-rc.3",
"@keplr-wallet/cosmos": "0.11.18-rc.3",
"@keplr-wallet/types": "0.11.18-rc.3",
"@keplr-wallet/chain-validator": "^0.11.23",
"@keplr-wallet/common": "^0.11.23",
"@keplr-wallet/cosmos": "^0.11.23",
"@keplr-wallet/types": "^0.11.23",
"axios": "^0.27.2",
"image-size": "^1.0.2",
"ws": "^8.11.0",
Expand Down
28 changes: 28 additions & 0 deletions pages/api/chains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";

import path from "path";
import { promises as fs } from "fs";
import { ChainInfo } from "@keplr-wallet/types";

type Data = {
chains: ChainInfo[];
};

export default async function handler(
_: NextApiRequest,
res: NextApiResponse<Data>,
) {
const jsonDirectory = path.join(process.cwd(), "cosmos");

const fetchChains = (await fs.readdir(jsonDirectory)).map((fileName) =>
fs.readFile(`${jsonDirectory}/${fileName}`, "utf8"),
);

const chainInfos: ChainInfo[] = (await Promise.all(fetchChains)).map(
(chainInfo) => JSON.parse(chainInfo),
);

//Return the content of the data file in json format
res.status(200).json({ chains: chainInfos });
}
13 changes: 0 additions & 13 deletions pages/api/hello.ts

This file was deleted.

27 changes: 27 additions & 0 deletions pages/components/chain-item/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FunctionComponent } from "react";
import styled from "styled-components";
import { DisplayChainInfo } from "../../index";

interface Props {
chainItem: DisplayChainInfo;
}

export const ChainItem: FunctionComponent<Props> = (props) => {
const { chainItem } = props;

return (
<ChainItemContainer key={chainItem.chainId}>
<div>Chain Name: {chainItem.chainName}</div>
<div>Display Type: {chainItem.displayType}</div>
</ChainItemContainer>
);
};

export const ChainItemContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
padding: 1.5rem;
cursor: pointer;
`;
89 changes: 59 additions & 30 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,83 @@
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { ChainInfo } from "@keplr-wallet/types";
import { ChainItem } from "./components/chain-item";
import { getKeplrFromWindow, KeplrWallet } from "../wallet";

interface RepositoryTreesResponse {
sha: string;
tree: RepositoryTree[];
interface ChainsResponse {
chains: ChainInfo[];
}

interface RepositoryTree {
path: string;
url: string;
}
type DisplayType = "normal" | "registered";

interface BlobResponse {
content: string;
}
export type DisplayChainInfo = ChainInfo & { displayType: DisplayType };

export default function Home() {
const [isExist, setIsExist] = useState<boolean>();
const [chainInfos, setChainInfos] = useState<DisplayChainInfo[]>([]);

useEffect(() => {
init();
}, []);

const init = async () => {
const urls: string[] = await fetchRepositoryTrees();
try {
const chainIds = await checkKeplr();
await fetchChains(chainIds);
} catch (e) {
console.error(e);
}
};

const checkKeplr = async () => {
const keplr = await getKeplrFromWindow();

const chainBlobs = urls.map((tree) => fetchChainBlob(tree));
if (keplr === undefined) {
setIsExist(false);
return;
// window.location.href =
// "https://chrome.google.com/webstore/detail/keplr/dmkamcknogkgcdfhhbddcghachkejeap";
}

// const test = await Promise.all(chainBlobs);
// console.log(test);
if (keplr) {
setIsExist(true);

const wallet = new KeplrWallet(keplr);
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
(c) => c.chainId,
);

await wallet.init(chainIds);

return chainIds;
}
};

const fetchRepositoryTrees = async () => {
const response: RepositoryTreesResponse = await (
await fetch(
"https://api.github.com/repos/chainapsis/keplr-chain-registry/git/trees/main?recursive=1",
)
const fetchChains = async (chainIds: string[] | undefined) => {
const chainsResponse: ChainsResponse = await (
await fetch("/api/chains")
).json();

return response.tree
.filter((item) => item.path.includes("cosmos/"))
.map((item) => item.url);
};
const registeredChainInfos = chainsResponse.chains;

const fetchChainBlob = async (url: string) => {
const response: BlobResponse = await (await fetch(url)).json();
const displayChainInfo: DisplayChainInfo[] = registeredChainInfos.map(
(chainInfo) => {
if (chainIds?.includes(chainInfo.chainId)) {
return { ...chainInfo, displayType: "registered" };
}

const decoded = JSON.parse(
Buffer.from(response.content, "base64").toString("utf-8"),
return { ...chainInfo, displayType: "normal" };
},
);

return decoded;
setChainInfos(displayChainInfo);
};

return <div>Home</div>;
return (
<div>
{!isExist ? <div>Install Keplr</div> : null}
{chainInfos.map((chainInfo) => (
<ChainItem chainItem={chainInfo} />
))}
</div>
);
}
2 changes: 2 additions & 0 deletions wallet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./types";
export * from "./keplr";
75 changes: 75 additions & 0 deletions wallet/keplr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { BroadcastMode, ChainInfo, Keplr } from "@keplr-wallet/types";
import { Wallet } from "./types";

export const getKeplrFromWindow: () => Promise<
Keplr | undefined
> = async () => {
if (typeof window === "undefined") {
return undefined;
}

if (window.keplr) {
return window.keplr;
}

if (document.readyState === "complete") {
return window.keplr;
}

return new Promise((resolve) => {
const documentStateChange = (event: Event) => {
if (
event.target &&
(event.target as Document).readyState === "complete"
) {
resolve(window.keplr);
document.removeEventListener("readystatechange", documentStateChange);
}
};

document.addEventListener("readystatechange", documentStateChange);
});
};

export class KeplrWallet implements Wallet {
constructor(public readonly keplr: Keplr) {}

broadcastTxSync(chainId: string, tx: Uint8Array): Promise<Uint8Array> {
return this.keplr.sendTx(chainId, tx, "sync" as BroadcastMode);
}

getChainInfosWithoutEndpoints(): Promise<
(Pick<ChainInfo, "chainId" | "chainName" | "bech32Config"> & {
readonly isEthermintLike?: boolean;
})[]
> {
return this.keplr.getChainInfosWithoutEndpoints().then((chainInfos) => {
return chainInfos.map((chainInfo) => {
return {
...chainInfo,
isEthermintLike:
chainInfo.features?.includes("eth-address-gen") ||
chainInfo.features?.includes("eth-key-sign"),
};
});
});
}

getKey(chainId: string): Promise<{
readonly name: string;
readonly pubKey: Uint8Array;
readonly bech32Address: string;
readonly isLedgerNano?: boolean;
}> {
return this.keplr.getKey(chainId).then((key) => {
return {
...key,
isLedgerNano: key.isNanoLedger,
};
});
}

init(chainIds: string[]): Promise<void> {
return this.keplr.enable(chainIds);
}
}
18 changes: 18 additions & 0 deletions wallet/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ChainInfo } from "@keplr-wallet/types";

export interface Wallet {
init(chainIds: string[]): Promise<void>;

getChainInfosWithoutEndpoints(): Promise<
(Pick<ChainInfo, "chainId" | "chainName" | "bech32Config"> & {
readonly isEthermintLike?: boolean;
})[]
>;

getKey(chainId: string): Promise<{
readonly name: string;
readonly pubKey: Uint8Array;
readonly bech32Address: string;
readonly isLedgerNano?: boolean;
}>;
}
5 changes: 5 additions & 0 deletions wallet/window.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Window as KeplrWindow } from "@keplr-wallet/types";

declare global {
interface Window extends KeplrWindow {}
}
Loading

0 comments on commit fe4677b

Please sign in to comment.