Wallets integration (#3)

* keplr/metamask integration initial commit

* chains settings and type definitions. notifications prototype

* fix: dom nested buttons

* address copied toast

* react-toastify colors

* wallet store and initial queries setup. zustand and react query dependencies added

* _app code cleanup

* remove obsolete WalletContext

* unused import

* walletStore initial commit

* leftover component reference removed

* fix: react hydration mismatch wallet component

* metamask conditional click handler

* connect modal minor tweaks and wallet installation urls added
This commit is contained in:
Gustavo Mauricio 2022-09-14 12:28:18 +01:00 committed by GitHub
parent 121549d41d
commit 28c53b1e59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1936 additions and 32 deletions

View File

@ -1,5 +1,12 @@
import React, { Fragment } from "react";
import React, { Fragment, useState } from "react";
import Image from "next/image";
import { Dialog, Transition } from "@headlessui/react";
import { toast } from "react-toastify";
import { getInjectiveAddress } from "utils/address";
import { getExperimentalChainConfigBasedOnChainId } from "utils/experimental-chains";
import { ChainId } from "types";
import useWalletStore from "stores/useWalletStore";
type Props = {
isOpen: boolean;
@ -7,6 +14,69 @@ type Props = {
};
const ConnectModal = ({ isOpen, onClose }: Props) => {
const [isLoading, setIsLoading] = useState(false);
const actions = useWalletStore((state) => state.actions);
const metamaskInstalled = useWalletStore((state) => state.metamaskInstalled);
const isKeplrInstalled = typeof window !== "undefined" && window.keplr;
const handleConnectSuccess = () => {
onClose();
// defering update on loading state to avoid updating before close animation is finished
setTimeout(() => {
setIsLoading(false);
}, 500);
};
const handleConnectKeplr = async () => {
if (!window.keplr) {
toast.error("You need Keplr extension installed");
return;
}
setIsLoading(true);
try {
const chainData = getExperimentalChainConfigBasedOnChainId(
ChainId.Mainnet
);
await window.keplr.experimentalSuggestChain(chainData);
const key = await window.keplr.getKey(ChainId.Mainnet);
actions.setAddress(key.bech32Address);
handleConnectSuccess();
} catch (e) {
// TODO: handle exception
console.log(e);
setIsLoading(false);
}
};
const handleConnectMetamask = async () => {
if (!metamaskInstalled) {
toast.error("You need Metamask extension installed");
return;
}
setIsLoading(true);
try {
// TODO: missing type definitions
const addresses = await (window.ethereum as any).request({
method: "eth_requestAccounts",
});
const [address] = addresses;
actions.setAddress(getInjectiveAddress(address));
handleConnectSuccess();
} catch (e) {
// TODO: handle exception
console.log(e);
setIsLoading(false);
}
};
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onClose}>
@ -36,18 +106,93 @@ const ConnectModal = ({ isOpen, onClose }: Props) => {
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
className="text-lg font-medium leading-6 text-gray-900 mb-6"
>
Connect your wallet
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
Obcaecati suscipit alias itaque dolores, accusamus eius rem
reiciendis optio in ducimus? Nulla hic, ut cupiditate totam
culpa sed ratione dignissimos sunt.
</p>
</div>
{isLoading ? (
<div role="status" className="text-center">
<svg
className="inline w-10 h-10 text-gray-200 animate-spin fill-orange-500"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
) : (
<div className="flex flex-col gap-3 mt-2">
<button
className="flex items-center p-4 bg-black/90 rounded-xl hover:bg-black/70"
onClick={handleConnectMetamask}
>
<Image
src="/wallets/metamask.webp"
height={30}
width={30}
alt="metamask"
/>
<div className="ml-4 text-left">
<div className="flex items-end">
<p>Metamask</p>
{!metamaskInstalled && (
<a
className="ml-3 text-sm text-blue-600"
onClick={(e) => {
window.open("https://metamask.io/", "_blank");
e.stopPropagation();
}}
>
Install
</a>
)}
</div>
<p className="text-sm text-gray-400">
Connect using Metamask
</p>
</div>
</button>
<button
className="flex items-center p-4 bg-black/90 rounded-xl hover:bg-black/70"
onClick={handleConnectKeplr}
>
<Image
src="/wallets/keplr.png"
height={30}
width={30}
alt="keplr"
/>
<div className="ml-4 text-left">
<div className="flex items-end">
<p>Keplr</p>
{!isKeplrInstalled && (
<a
className="ml-3 text-sm text-blue-600"
onClick={(e) => {
window.open("https://www.keplr.app/", "_blank");
e.stopPropagation();
}}
>
Install
</a>
)}
</div>
<p className="text-sm text-gray-400">
Connect using Keplr
</p>
</div>
</button>
</div>
)}
</Dialog.Panel>
</Transition.Child>
</div>

View File

@ -1,9 +1,9 @@
import React, { useState } from "react";
import React from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import SearchInput from "components/SearchInput";
import ConnectModal from "./ConnectModal";
import Wallet from "./Wallet";
const mockedAccounts = [
{
@ -37,8 +37,6 @@ const NavLink = ({ href, children }: { href: string; children: string }) => {
};
const Navigation = () => {
const [showConnectModal, setShowConnectModal] = useState(false);
return (
<div>
{/* Main navigation bar */}
@ -55,12 +53,7 @@ const Navigation = () => {
<NavLink href="/portfolio">Portfolio</NavLink>
<NavLink href="/council">Council</NavLink>
</div>
<button
className="rounded-3xl bg-green-500 py-2 px-3 font-semibold"
onClick={() => setShowConnectModal(true)}
>
Connect Wallet
</button>
<Wallet />
</div>
{/* Sub navigation bar */}
<div className="flex justify-between px-6 py-3 text-sm text-white/40">
@ -80,10 +73,6 @@ const Navigation = () => {
<div>Risk Gauge</div>
</div>
</div>
<ConnectModal
isOpen={showConnectModal}
onClose={() => setShowConnectModal(false)}
/>
</div>
);
};

104
components/Wallet.tsx Normal file
View File

@ -0,0 +1,104 @@
import React, { useEffect, useState } from "react";
import { Popover } from "@headlessui/react";
import { toast } from "react-toastify";
import Image from "next/image";
import { formatWalletAddress } from "utils/formatters";
import ConnectModal from "./ConnectModal";
import useWalletStore from "stores/useWalletStore";
import useInjectiveBalance from "hooks/useInjectiveBalance";
const WalletPopover = ({ children }: { children: React.ReactNode }) => {
const address = useWalletStore((state) => state.address);
const actions = useWalletStore((state) => state.actions);
const { data } = useInjectiveBalance();
return (
<Popover className="relative">
<Popover.Button className="rounded-3xl bg-green-500 py-2 px-2 text-sm font-semibold w-[200px] overflow-hidden text-ellipsis">
{children}
</Popover.Button>
<Popover.Panel className="absolute z-10 right-0 pt-2">
<div className="bg-white rounded-2xl p-6 text-gray-900">
<div className="flex justify-between items-center mb-4">
<div className="flex items-center">
<Image src="/injective.svg" alt="token" width={24} height={24} />
<p className="ml-2">
INJ{" "}
<span className="text-lg font-semibold ml-1">
{data?.toFixed(2)}
</span>
</p>
</div>
<button
className="text-white text-sm bg-[#524bb1] rounded-3xl hover:bg-[#6962cc] cursor-pointer px-5 py-1.5 "
onClick={() => actions.setAddress("")}
>
Disconnect
</button>
</div>
<p className="mb-6 text-sm">{address}</p>
<button
className="flex items-center text-slate-500 hover:text-slate-700 text-sm"
onClick={() => {
navigator.clipboard.writeText(address).then(() => {
toast.success("Address copied to your clipboard");
});
}}
>
<svg
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-5 h-5 mr-1"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5A3.375 3.375 0 006.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0015 2.25h-1.5a2.251 2.251 0 00-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 00-9-9z"
/>
</svg>
Copy Address
</button>
</div>
</Popover.Panel>
</Popover>
);
};
const Wallet = () => {
const [showConnectModal, setShowConnectModal] = useState(false);
const [hasHydrated, setHasHydrated] = useState<boolean>(false);
const address = useWalletStore((state) => state.address);
// avoid server-client hydration mismatch
useEffect(() => {
setHasHydrated(true);
}, []);
return (
<>
{hasHydrated && address ? (
<WalletPopover>{formatWalletAddress(address)}</WalletPopover>
) : (
<button
className="rounded-3xl bg-green-500 py-2 px-2 text-sm font-semibold w-[200px]"
onClick={() => setShowConnectModal(true)}
>
Connect Wallet
</button>
)}
<ConnectModal
isOpen={showConnectModal}
onClose={() => setShowConnectModal(false)}
/>
</>
);
};
export default Wallet;

20
hooks/useAllBalances.tsx Normal file
View File

@ -0,0 +1,20 @@
import { useQuery } from "@tanstack/react-query";
import useWalletStore from "stores/useWalletStore";
const useAllBalances = () => {
const address = useWalletStore((state) => state.address);
return useQuery(
["allBalances"],
() =>
fetch(
`https://lcd.injective.network/cosmos/bank/v1beta1/balances/${address}`
).then((res) => res.json()),
{
enabled: !!address,
}
);
};
export default useAllBalances;

View File

@ -0,0 +1,37 @@
import { useQuery } from "@tanstack/react-query";
import BigNumber from "bignumber.js";
import useWalletStore from "stores/useWalletStore";
type Result = {
balance: {
amount: number;
denom: string;
};
};
const useAllBalances = () => {
const address = useWalletStore((state) => state.address);
const result = useQuery<Result>(
["injectiveBalance"],
async () =>
fetch(
`https://lcd.injective.network/cosmos/bank/v1beta1/balances/${address}/by_denom?denom=inj`
).then((res) => res.json()),
{
enabled: !!address,
}
);
return {
...result,
data:
result?.data &&
BigNumber(result.data.balance.amount)
.div(10 ** 18)
.toNumber(),
};
};
export default useAllBalances;

View File

@ -10,12 +10,21 @@
},
"dependencies": {
"@headlessui/react": "^1.7.0",
"@keplr-wallet/cosmos": "^0.10.24",
"@metamask/detect-provider": "^1.2.0",
"@sentry/nextjs": "^7.12.1",
"@tanstack/react-query": "^4.3.4",
"bech32": "^2.0.0",
"bignumber.js": "^9.1.0",
"ethereumjs-util": "^7.1.5",
"next": "12.2.5",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"react-toastify": "^9.0.8",
"zustand": "^4.1.1"
},
"devDependencies": {
"@keplr-wallet/types": "^0.10.24",
"@types/node": "18.7.14",
"@types/react": "18.0.18",
"@types/react-dom": "18.0.6",

View File

@ -1,10 +1,37 @@
import type { AppProps } from "next/app";
import Head from "next/head";
import { ToastContainer, Zoom } from "react-toastify";
import "react-toastify/dist/ReactToastify.min.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import detectEthereumProvider from "@metamask/detect-provider";
import "../styles/globals.css";
import Layout from "components/Layout";
import { useEffect } from "react";
import useWalletStore from "stores/useWalletStore";
async function isMetamaskInstalled(): Promise<boolean> {
const provider = await detectEthereumProvider();
return !!provider;
}
const queryClient = new QueryClient();
function MyApp({ Component, pageProps }: AppProps) {
const actions = useWalletStore((state) => state.actions);
// init store
useEffect(() => {
const verifyMetamask = async () => {
actions.setMetamaskInstalledStatus(await isMetamaskInstalled());
};
console.log("HERE");
verifyMetamask();
}, [actions]);
return (
<>
<Head>
@ -12,9 +39,21 @@ function MyApp({ Component, pageProps }: AppProps) {
{/* <meta name="description" content="Generated by create next app" /> */}
<link rel="icon" href="/favicon.svg" />
</Head>
<Layout>
<Component {...pageProps} />
</Layout>
<QueryClientProvider client={queryClient}>
<Layout>
<Component {...pageProps} />
</Layout>
<ToastContainer
autoClose={1500}
closeButton={false}
position="bottom-right"
hideProgressBar
icon={false}
newestOnTop
theme="colored"
transition={Zoom}
/>
</QueryClientProvider>
</>
);
}

34
public/injective.svg Normal file
View File

@ -0,0 +1,34 @@
<svg viewBox="100 100 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M148.497 169.135C150.981 166.013 153.613 163.009 156.245 160.005C156.363 159.856 156.63 159.826 156.748 159.677C156.985 159.38 157.37 159.201 157.606 158.903L157.843 158.606C159.678 156.91 161.63 155.064 163.881 153.456C171.845 147.41 180.11 142.817 188.825 139.795C216.778 129.981 247.894 136.029 272.295 159.065C306.366 191.002 303.315 242.451 276.117 276.647C241.748 327.625 182.684 398.748 264.463 462.46C279.167 473.916 290.075 483.361 336.392 496.746C306.1 502.326 278.012 500.59 246.748 492.605C224.634 480.123 189.866 453.397 178.037 417.3C160.159 362.562 209.513 280.732 233.365 249.216C266.113 205.599 213.124 158.382 174.112 211.095C153.72 238.566 118.044 316.303 130.442 373.965C137.691 406.664 147.353 430.499 185.663 463.241C178.559 459.049 171.66 454.294 164.968 448.974C75.957 366.06 86.2838 237.859 148.497 169.135Z"
fill="url(#paint0_linear)"
/>
<path
d="M451.503 430.865C449.019 433.987 446.387 436.991 443.755 439.995C443.637 440.144 443.37 440.174 443.252 440.323C443.015 440.62 442.63 440.799 442.394 441.097L442.157 441.394C440.322 443.09 438.37 444.936 436.119 446.544C428.155 452.59 419.89 457.183 411.175 460.205C383.222 470.019 352.106 463.971 327.705 440.935C293.634 408.998 296.685 357.549 323.883 323.353C358.252 272.375 417.316 201.252 335.537 137.54C320.833 126.084 309.925 116.639 263.608 103.254C293.9 97.6736 321.988 99.4095 353.251 107.395C375.366 119.877 410.134 146.603 421.963 182.7C439.841 237.438 390.487 319.268 366.635 350.784C333.887 394.401 386.876 441.618 425.888 388.905C446.28 361.434 481.956 283.697 469.558 226.035C462.309 193.336 452.647 169.501 414.337 136.759C421.441 140.951 428.34 145.706 435.032 151.026C524.043 233.94 513.716 362.141 451.503 430.865Z"
fill="url(#paint1_linear)"
/>
<defs>
<linearGradient
id="paint0_linear"
x1="100"
y1="300"
x2="500"
y2="300"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#0082FA" />
<stop offset="1" stop-color="#00F2FE" />
</linearGradient>
<linearGradient
id="paint1_linear"
x1="100"
y1="300"
x2="500"
y2="300"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#0082FA" />
<stop offset="1" stop-color="#00F2FE" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/wallets/keplr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

52
stores/useWalletStore.tsx Normal file
View File

@ -0,0 +1,52 @@
import create from "zustand";
import { persist } from "zustand/middleware";
import { Wallet } from "types";
const dummyStorageApi = {
getItem: () => null,
removeItem: () => undefined,
setItem: () => undefined,
};
interface WalletStore {
address: string;
injectiveAddress: string;
addresses: string[];
metamaskInstalled: boolean;
wallet: Wallet;
actions: {
setAddress: (address: string) => void;
setMetamaskInstalledStatus: (value: boolean) => void;
};
}
const useWalletStore = create<WalletStore>()(
persist(
(set, get) => ({
address: "",
injectiveAddress: "",
addresses: [],
metamaskInstalled: false,
wallet: Wallet.Metamask,
actions: {
setAddress: (address: string) => set(() => ({ address })),
setMetamaskInstalledStatus: (value: boolean) =>
set(() => ({ metamaskInstalled: value })),
},
}),
{
name: "wallet",
partialize: (state) =>
Object.fromEntries(
Object.entries(state).filter(
([key]) => !["metamaskInstalled", "actions"].includes(key)
)
),
// getStorage: () =>
// typeof window !== "undefined" ? localStorage : dummyStorageApi,
}
)
);
export default useWalletStore;

View File

@ -28,3 +28,13 @@ a {
background: black;
}
}
/* react-toastify */
/* https://fkhadra.github.io/react-toastify/how-to-style#override-css-variables */
.Toastify__toast {
@apply text-sm;
}
.Toastify__toast-theme--colored.Toastify__toast--success {
@apply bg-green-500;
}

53
types/index.ts Normal file
View File

@ -0,0 +1,53 @@
// ENUMS
export enum EthereumChainId {
Mainnet = 1,
Ropsten = 3,
Rinkeby = 4,
Goerli = 5,
Kovan = 42,
Injective = 888,
Ganache = 1337,
HardHat = 31337,
}
export enum ChainId {
Mainnet = "injective-1",
Testnet = "injective-888",
Devnet = "injective-777",
}
export enum Wallet {
Metamask = "metamask",
Ledger = "ledger",
LedgerLegacy = "ledger-legacy",
Trezor = "trezor",
Keplr = "keplr",
Torus = "torus",
WalletConnect = "wallet-connect",
}
// COSMOS
export enum CosmosChainId {
Injective = "injective-1",
Cosmoshub = "cosmoshub-4",
Juno = "juno-1",
Osmosis = "osmosis-1",
Terra = "columbus-5",
TerraUST = "columbus-5",
Chihuahua = "chihuahua-1",
Axelar = "axelar-dojo-1",
Evmos = "evmos_9001-2",
Persistence = "core-1",
Secret = "secret-4",
Stride = "stride-1",
}
export enum TestnetCosmosChainId {
Injective = "injective-888",
Cosmoshub = "cosmoshub-testnet",
}
export enum DevnetCosmosChainId {
Injective = "injective-777",
Injective1 = "injective-777",
}

5
types/keplr.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import type { Window as KeplrWindow } from "@keplr-wallet/types";
declare global {
interface Window extends KeplrWindow {}
}

18
utils/address.ts Normal file
View File

@ -0,0 +1,18 @@
import { bech32 } from "bech32";
import { Address } from "ethereumjs-util";
export const getInjectiveAddress = (address: string): string => {
const addressBuffer = Address.fromString(address.toString()).toBuffer();
return bech32.encode("inj", bech32.toWords(addressBuffer));
};
export const getAddressFromInjectiveAddress = (address: string): string => {
if (address.startsWith("0x")) {
return address;
}
return `0x${Buffer.from(
bech32.fromWords(bech32.decode(address).words)
).toString("hex")}`;
};

View File

@ -0,0 +1,349 @@
import { Bech32Address } from "@keplr-wallet/cosmos";
import {
ChainId,
CosmosChainId,
DevnetCosmosChainId,
TestnetCosmosChainId,
} from "types";
export const getEndpointsFromChainId = (
chainId: TestnetCosmosChainId | CosmosChainId | ChainId | DevnetCosmosChainId
): { rpc: string; rest: string } => {
switch (chainId) {
case CosmosChainId.Cosmoshub:
return {
rpc: "https://tm.cosmos.injective.network",
rest: "https://lcd.cosmos.injective.network",
};
case CosmosChainId.Osmosis:
return {
rpc: "https://tm.osmosis.injective.network",
rest: "https://lcd.osmosis.injective.network",
};
case CosmosChainId.Injective:
return {
rpc: "https://tm.injective.network",
rest: "https://lcd.injective.network",
};
case CosmosChainId.Juno:
return {
rpc: "https://tm.juno.injective.network",
rest: "https://lcd.juno.injective.network",
};
case CosmosChainId.Terra:
return {
rpc: "https://tm.terra.injective.network",
rest: "https://lcd.terra.injective.network",
};
case CosmosChainId.TerraUST:
return {
rpc: "https://tm.terra.injective.network",
rest: "https://lcd.terra.injective.network",
};
case TestnetCosmosChainId.Cosmoshub:
return {
rpc: "https://testnet.tm.cosmos.injective.dev",
rest: "https://testnet.lcd.cosmos.injective.dev",
};
case TestnetCosmosChainId.Injective:
return {
rpc: "https://testnet.tm.injective.dev",
rest: "https://testnet.lcd.injective.dev",
};
case DevnetCosmosChainId.Injective:
return {
rpc: "https://devnet.tm.injective.dev",
rest: "https://devnet.lcd.injective.dev",
};
case CosmosChainId.Chihuahua:
return {
rpc: "https://rpc.chihuahua.wtf",
rest: "https://api.chihuahua.wtf",
};
case CosmosChainId.Axelar:
return {
rpc: "https://tm.axelar.injective.network",
rest: "https://lcd.axelar.injective.network",
};
case CosmosChainId.Evmos:
return {
rpc: "https://tm.evmos.injective.network",
rest: "https://lcd.evmos.injective.network",
};
case CosmosChainId.Persistence:
return {
rpc: "https://tm.persistence.injective.network",
rest: "https://lcd.persistence.injective.network",
};
case CosmosChainId.Secret:
return {
rpc: "https://tm.secret.injective.network",
rest: "https://lcd.secret.injective.network",
};
case CosmosChainId.Stride:
return {
rpc: "https://tm.stride.injective.network",
rest: "https://lcd.stride.injective.network",
};
default:
throw new Error(`Endpoints for ${chainId} not found`);
}
};
export const experimentalChainsConfig = {
[TestnetCosmosChainId.Cosmoshub]: {
...getEndpointsFromChainId(TestnetCosmosChainId.Cosmoshub),
rpcConfig: undefined,
restConfig: undefined,
chainId: "cosmoshub-testnet",
chainName: "Cosmos Testnet",
stakeCurrency: {
coinDenom: "UPHOTON",
coinMinimalDenom: "uphoton",
coinDecimals: 6,
coinGeckoId: "cosmos",
},
walletUrl: "https://wallet.keplr.app/#/cosmoshub/stake",
walletUrlForStaking: "https://wallet.keplr.app/#/cosmoshub/stake",
bip44: {
coinType: 118,
},
bech32Config: Bech32Address.defaultBech32Config("cosmos"),
currencies: [
{
coinDenom: "UPHOTON",
coinMinimalDenom: "uphoton",
coinDecimals: 6,
coinGeckoId: "cosmos",
},
],
feeCurrencies: [
{
coinDenom: "UPHOTON",
coinMinimalDenom: "uphoton",
coinDecimals: 6,
coinGeckoId: "cosmos",
},
],
coinType: 118,
features: ["ibc-transfer"],
},
[TestnetCosmosChainId.Injective]: {
...getEndpointsFromChainId(TestnetCosmosChainId.Injective),
rpcConfig: undefined,
restConfig: undefined,
chainId: "injective-888",
chainName: "Injective Testnet",
stakeCurrency: {
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
walletUrl: "https://hub.injective.dev/",
walletUrlForStaking: "https://hub.injective.dev/",
bip44: {
coinType: 60,
},
bech32Config: Bech32Address.defaultBech32Config("inj"),
currencies: [
{
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
],
feeCurrencies: [
{
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
],
gasPriceStep: {
low: 5000000000,
average: 25000000000,
high: 40000000000,
},
coinType: 60,
features: ["ibc-transfer", "ibc-go", "eth-address-gen", "eth-key-sign"],
},
[DevnetCosmosChainId.Injective]: {
...getEndpointsFromChainId(DevnetCosmosChainId.Injective),
rpcConfig: undefined,
restConfig: undefined,
chainId: "injective-777",
chainName: "Injective - Devnet",
stakeCurrency: {
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
walletUrl: "https://hub.injective.dev/",
walletUrlForStaking: "https://hub.injective.dev/",
bip44: {
coinType: 60,
},
bech32Config: Bech32Address.defaultBech32Config("inj"),
currencies: [
{
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
],
feeCurrencies: [
{
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
],
gasPriceStep: {
low: 5000000000,
average: 25000000000,
high: 40000000000,
},
coinType: 60,
features: ["ibc-transfer", "ibc-go", "eth-address-gen", "eth-key-sign"],
},
[CosmosChainId.Injective]: {
...getEndpointsFromChainId(CosmosChainId.Injective),
rpcConfig: undefined,
restConfig: undefined,
chainId: "injective-1",
chainName: "Injective - Beta",
stakeCurrency: {
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
walletUrl: "https://hub.injective.network/",
walletUrlForStaking: "https://hub.injective.network/",
bip44: {
coinType: 60,
},
bech32Config: Bech32Address.defaultBech32Config("inj"),
currencies: [
{
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
],
feeCurrencies: [
{
coinDenom: "INJ",
coinMinimalDenom: "inj",
coinDecimals: 18,
coinGeckoId: "injective-protocol",
},
],
gasPriceStep: {
low: 5000000000,
average: 25000000000,
high: 40000000000,
},
features: ["ibc-transfer", "ibc-go", "eth-address-gen", "eth-key-sign"],
beta: true,
},
[CosmosChainId.Terra]: {
...getEndpointsFromChainId(CosmosChainId.Terra),
rpcConfig: undefined,
restConfig: undefined,
chainId: "columbus-5",
chainName: "Terra",
stakeCurrency: {
coinDenom: "LUNA",
coinMinimalDenom: "uluna",
coinDecimals: 6,
coinGeckoId: "terra-luna",
},
walletUrl: "https://station.terra.money/wallet",
walletUrlForStaking: "https://station.terra.money/wallet",
bip44: {
coinType: 118,
},
bech32Config: Bech32Address.defaultBech32Config("terra"),
currencies: [
{
coinDenom: "LUNA",
coinMinimalDenom: "uluna",
coinDecimals: 6,
coinGeckoId: "terra-luna",
},
{
coinDenom: "UST",
coinMinimalDenom: "uusd",
coinGeckoId: "terrausd",
coinDecimals: 6,
},
],
feeCurrencies: [
{
coinDenom: "LUNA",
coinMinimalDenom: "uluna",
coinGeckoId: "terra-luna",
coinDecimals: 6,
},
{
coinDenom: "UST",
coinMinimalDenom: "uusd",
coinGeckoId: "terrausd",
coinDecimals: 6,
},
],
coinType: 118,
gasPriceStep: {
low: 0.01,
average: 0.3,
high: 0.04,
},
features: ["ibc-transfer"],
},
[CosmosChainId.Chihuahua]: {
...getEndpointsFromChainId(CosmosChainId.Chihuahua),
chainId: "chihuahua-1",
chainName: "Chihuahua",
stakeCurrency: {
coinDenom: "HUAHUA",
coinMinimalDenom: "uhuahua",
coinDecimals: 6,
},
bip44: {
coinType: 118,
},
bech32Config: Bech32Address.defaultBech32Config("chihuahua"),
currencies: [
{
coinDenom: "HUAHUA",
coinMinimalDenom: "uhuahua",
coinDecimals: 6,
},
],
feeCurrencies: [
{
coinDenom: "HUAHUA",
coinMinimalDenom: "uhuahua",
coinDecimals: 6,
},
],
gasPriceStep: {
low: 0.025,
average: 0.03,
high: 0.035,
},
features: ["ibc-transfer", "ibc-go"],
},
} as Record<string, any>;
export const getExperimentalChainConfigBasedOnChainId = (
chainId: string
): any | undefined => experimentalChainsConfig[chainId];

13
utils/formatters.ts Normal file
View File

@ -0,0 +1,13 @@
export const formatWalletAddress = (
address: string,
substrLength = 6
): string => {
if (address.length <= 10) {
return address;
}
return `${address.slice(0, substrLength)}...${address.slice(
address.length - substrLength,
address.length
)}`;
};

1035
yarn.lock

File diff suppressed because it is too large Load Diff