From 06b5431ec679d75387cf550e33c62f504ee6bd7b Mon Sep 17 00:00:00 2001 From: Jigar Patel <97186961+jigar-arc10@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:22:44 -0400 Subject: [PATCH] Wallet connect & sign arbitrary (#268) --- apps/provider-console/package.json | 7 + .../src/chains/akash-sandbox.ts | 17 + .../src/chains/akash-testnet.ts | 17 + apps/provider-console/src/chains/akash.ts | 378 +++++++++++++++ apps/provider-console/src/chains/index.ts | 6 + .../src/components/layout/Nav.tsx | 7 +- .../components/layout/TransactionModal.tsx | 38 ++ .../src/components/layout/WalletStatus.tsx | 182 +++++++ .../components/shared/FormattedDecimal.tsx | 39 ++ .../components/wallet/ConnectWalletButton.tsx | 37 ++ .../ChainParamProvider/ChainParamProvider.tsx | 37 ++ .../src/context/ChainParamProvider/index.ts | 1 + .../CustomChainProvider.tsx | 63 +++ .../src/context/CustomChainProvider/index.ts | 1 + .../PricingProvider/PricingProvider.tsx | 56 +++ .../src/context/PricingProvider/index.ts | 1 + .../SettingsProviderContext.tsx | 307 ++++++++++++ .../src/context/SettingsProvider/index.ts | 1 + .../context/WalletProvider/WalletProvider.tsx | 325 +++++++++++++ .../src/context/WalletProvider/index.ts | 1 + .../src/hooks/useAllowance.tsx | 89 ++++ apps/provider-console/src/hooks/useDenom.ts | 21 + .../src/hooks/useLocalStorage.ts | 106 ++++ .../src/hooks/usePreviousRoute.ts | 24 + .../src/hooks/useSelectedNetwork.ts | 24 + .../src/hooks/useWalletBalance.ts | 71 +++ apps/provider-console/src/hooks/useWhen.ts | 10 + apps/provider-console/src/pages/_app.tsx | 33 +- apps/provider-console/src/queries/index.ts | 3 + .../src/queries/queryClient.ts | 5 + .../provider-console/src/queries/queryKeys.ts | 49 ++ .../src/queries/useBlocksQuery.ts | 34 ++ .../src/queries/useMarketData.ts | 15 + .../src/queries/useSettings.ts | 38 ++ .../src/store/networkStore.ts | 73 +++ apps/provider-console/src/store/routeStore.ts | 7 + apps/provider-console/src/types/block.ts | 28 ++ apps/provider-console/src/types/coin.ts | 4 + apps/provider-console/src/types/dashboard.ts | 113 +++++ apps/provider-console/src/types/deployment.ts | 335 +++++++++++++ apps/provider-console/src/types/index.ts | 8 +- apps/provider-console/src/types/network.ts | 14 + apps/provider-console/src/types/node.ts | 38 ++ .../provider-console/src/types/transaction.ts | 22 + apps/provider-console/src/types/validator.ts | 33 ++ apps/provider-console/src/utils/apiUtils.ts | 33 ++ apps/provider-console/src/utils/authClient.ts | 41 ++ apps/provider-console/src/utils/constants.ts | 59 +++ .../src/utils/customRegistry.ts | 2 + apps/provider-console/src/utils/dateUtils.ts | 56 +++ apps/provider-console/src/utils/init.ts | 7 + .../provider-console/src/utils/mathHelpers.ts | 63 +++ apps/provider-console/src/utils/priceUtils.ts | 83 ++++ .../provider-console/src/utils/proto/grant.ts | 3 + .../provider-console/src/utils/proto/index.ts | 29 ++ apps/provider-console/src/utils/restClient.ts | 96 ++++ .../provider-console/src/utils/walletUtils.ts | 75 +++ package-lock.json | 452 +++++++++++++++--- 58 files changed, 3652 insertions(+), 65 deletions(-) create mode 100644 apps/provider-console/src/chains/akash-sandbox.ts create mode 100644 apps/provider-console/src/chains/akash-testnet.ts create mode 100644 apps/provider-console/src/chains/akash.ts create mode 100644 apps/provider-console/src/chains/index.ts create mode 100644 apps/provider-console/src/components/layout/TransactionModal.tsx create mode 100644 apps/provider-console/src/components/layout/WalletStatus.tsx create mode 100644 apps/provider-console/src/components/shared/FormattedDecimal.tsx create mode 100644 apps/provider-console/src/components/wallet/ConnectWalletButton.tsx create mode 100644 apps/provider-console/src/context/ChainParamProvider/ChainParamProvider.tsx create mode 100644 apps/provider-console/src/context/ChainParamProvider/index.ts create mode 100644 apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx create mode 100644 apps/provider-console/src/context/CustomChainProvider/index.ts create mode 100644 apps/provider-console/src/context/PricingProvider/PricingProvider.tsx create mode 100644 apps/provider-console/src/context/PricingProvider/index.ts create mode 100644 apps/provider-console/src/context/SettingsProvider/SettingsProviderContext.tsx create mode 100644 apps/provider-console/src/context/SettingsProvider/index.ts create mode 100644 apps/provider-console/src/context/WalletProvider/WalletProvider.tsx create mode 100644 apps/provider-console/src/context/WalletProvider/index.ts create mode 100644 apps/provider-console/src/hooks/useAllowance.tsx create mode 100644 apps/provider-console/src/hooks/useDenom.ts create mode 100644 apps/provider-console/src/hooks/useLocalStorage.ts create mode 100644 apps/provider-console/src/hooks/usePreviousRoute.ts create mode 100644 apps/provider-console/src/hooks/useSelectedNetwork.ts create mode 100644 apps/provider-console/src/hooks/useWalletBalance.ts create mode 100644 apps/provider-console/src/hooks/useWhen.ts create mode 100644 apps/provider-console/src/queries/index.ts create mode 100644 apps/provider-console/src/queries/queryClient.ts create mode 100644 apps/provider-console/src/queries/queryKeys.ts create mode 100644 apps/provider-console/src/queries/useBlocksQuery.ts create mode 100644 apps/provider-console/src/queries/useMarketData.ts create mode 100644 apps/provider-console/src/queries/useSettings.ts create mode 100644 apps/provider-console/src/store/networkStore.ts create mode 100644 apps/provider-console/src/store/routeStore.ts create mode 100644 apps/provider-console/src/types/block.ts create mode 100644 apps/provider-console/src/types/coin.ts create mode 100644 apps/provider-console/src/types/dashboard.ts create mode 100644 apps/provider-console/src/types/deployment.ts create mode 100644 apps/provider-console/src/types/network.ts create mode 100644 apps/provider-console/src/types/node.ts create mode 100644 apps/provider-console/src/types/transaction.ts create mode 100644 apps/provider-console/src/types/validator.ts create mode 100644 apps/provider-console/src/utils/apiUtils.ts create mode 100644 apps/provider-console/src/utils/authClient.ts create mode 100644 apps/provider-console/src/utils/customRegistry.ts create mode 100644 apps/provider-console/src/utils/dateUtils.ts create mode 100644 apps/provider-console/src/utils/init.ts create mode 100644 apps/provider-console/src/utils/mathHelpers.ts create mode 100644 apps/provider-console/src/utils/priceUtils.ts create mode 100644 apps/provider-console/src/utils/proto/grant.ts create mode 100644 apps/provider-console/src/utils/proto/index.ts create mode 100644 apps/provider-console/src/utils/restClient.ts create mode 100644 apps/provider-console/src/utils/walletUtils.ts diff --git a/apps/provider-console/package.json b/apps/provider-console/package.json index 0f9d67ad1..95843bf1d 100644 --- a/apps/provider-console/package.json +++ b/apps/provider-console/package.json @@ -13,12 +13,19 @@ }, "dependencies": { "@akashnetwork/ui": "*", + "@cosmos-kit/cosmostation-extension": "^2.12.2", + "@cosmos-kit/keplr": "^2.12.2", + "@cosmos-kit/leap-extension": "^2.12.2", + "@cosmos-kit/react": "^2.18.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "jotai": "^2.9.0", + "jwt-decode": "^4.0.0", "lucide-react": "^0.395.0", "next": "14.2.4", "react": "^18", "react-dom": "^18", + "react-query": "^3.39.3", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "ts-loader": "^9.5.1" diff --git a/apps/provider-console/src/chains/akash-sandbox.ts b/apps/provider-console/src/chains/akash-sandbox.ts new file mode 100644 index 000000000..f351c4e38 --- /dev/null +++ b/apps/provider-console/src/chains/akash-sandbox.ts @@ -0,0 +1,17 @@ +import { AssetList } from "@chain-registry/types"; + +import { akash, akashAssetList } from "./akash"; + +export const akashSandbox = { + ...akash, + chain_id: "sandbox-01", + network_type: "sandbox", + chain_name: "akash-sandbox", + pretty_name: "Akash-Sandbox", + apis: { + rpc: [{ address: "https://rpc.sandbox-01.aksh.pw", provider: "ovrclk" }], + rest: [{ address: "https://api.sandbox-01.aksh.pw", provider: "ovrclk" }] + } +}; + +export const akashSandboxAssetList: AssetList = { ...akashAssetList, chain_name: "akash-sandbox", assets: [...akashAssetList.assets] }; diff --git a/apps/provider-console/src/chains/akash-testnet.ts b/apps/provider-console/src/chains/akash-testnet.ts new file mode 100644 index 000000000..009d4fef5 --- /dev/null +++ b/apps/provider-console/src/chains/akash-testnet.ts @@ -0,0 +1,17 @@ +import { AssetList } from "@chain-registry/types"; + +import { akash, akashAssetList } from "./akash"; + +export const akashTestnet = { + ...akash, + chain_id: "testnet-02", + network_type: "testnet", + chain_name: "akash-testnet", + pretty_name: "Akash-Testnet", + apis: { + rpc: [{ address: "https://rpc.testnet-02.aksh.pw", provider: "ovrclk" }], + rest: [{ address: "https://api.testnet-02.aksh.pw", provider: "ovrclk" }] + } +}; + +export const akashTestnetAssetList: AssetList = { ...akashAssetList, chain_name: "akash-testnet", assets: [...akashAssetList.assets] }; diff --git a/apps/provider-console/src/chains/akash.ts b/apps/provider-console/src/chains/akash.ts new file mode 100644 index 000000000..a509807cd --- /dev/null +++ b/apps/provider-console/src/chains/akash.ts @@ -0,0 +1,378 @@ +import { AssetList } from "@chain-registry/types"; +import { assets } from "chain-registry"; + +// Obtained from https://raw.githubusercontent.com/cosmos/chain-registry/master/akash/chain.json +export const akash = { + $schema: "../chain.schema.json", + chain_name: "akash", + status: "live", + network_type: "mainnet", + website: "https://akash.network/", + pretty_name: "Akash", + chain_id: "akashnet-2", + bech32_prefix: "akash", + daemon_name: "akash", + node_home: "$HOME/.akash", + slip44: 118, + fees: { + fee_tokens: [ + { + denom: "uakt", + fixed_min_gas_price: 0 + } + ] + }, + staking: { + staking_tokens: [ + { + denom: "uakt" + } + ] + }, + codebase: { + git_repo: "https://github.com/akash-network/node/", + recommended_version: "v0.26.2", + compatible_versions: ["v0.26.1", "v0.26.2"], + binaries: { + "linux/amd64": "https://github.com/akash-network/node/releases/download/v0.26.2/akash_linux_amd64.zip", + "linux/arm64": "https://github.com/akash-network/node/releases/download/v0.26.2/akash_linux_arm64.zip" + }, + genesis: { + genesis_url: "https://raw.githubusercontent.com/akash-network/net/master/mainnet/genesis.json" + }, + versions: [ + { + name: "v0.22.0", + recommended_version: "v0.22.7", + compatible_versions: ["v0.22.7"], + binaries: { + "linux/amd64": "https://github.com/akash-network/node/releases/download/v0.22.7/akash_linux_amd64.zip", + "linux/arm64": "https://github.com/akash-network/node/releases/download/v0.22.7/akash_linux_arm64.zip" + }, + next_version_name: "v0.24.0" + }, + { + name: "v0.24.0", + recommended_version: "v0.24.0", + compatible_versions: ["v0.24.0"], + binaries: { + "linux/amd64": "https://github.com/akash-network/node/releases/download/v0.24.0/akash_linux_amd64.zip", + "linux/arm64": "https://github.com/akash-network/node/releases/download/v0.24.0/akash_linux_arm64.zip" + }, + next_version_name: "v0.26.0" + }, + { + name: "v0.26.0", + recommended_version: "v0.26.2", + compatible_versions: ["v0.26.1", "v0.26.2"], + proposal: 231, + height: 12992204, + binaries: { + "linux/amd64": "https://github.com/akash-network/node/releases/download/v0.26.2/akash_linux_amd64.zip", + "linux/arm64": "https://github.com/akash-network/node/releases/download/v0.26.2/akash_linux_arm64.zip" + }, + next_version_name: "" + } + ] + }, + logo_URIs: { + png: "https://raw.githubusercontent.com/cosmos/chain-registry/master/akash/images/akt.png", + svg: "https://raw.githubusercontent.com/cosmos/chain-registry/master/akash/images/akt.svg" + }, + description: "Akash is open-source Supercloud that lets users buy and sell computing resources securely and efficiently. Purpose-built for public utility.", + peers: { + seeds: [ + { + id: "4acf579e2744268f834c713e894850995bbf0ffa", + address: "50.18.31.225:26656" + }, + { + id: "86afe23f116ba4754a19819a55d153008eb74b48", + address: "15.164.87.75:26656" + }, + { + id: "ade4d8bc8cbe014af6ebdf3cb7b1e9ad36f412c0", + address: "seeds.polkachu.com:12856", + provider: "Polkachu" + }, + { + id: "20e1000e88125698264454a884812746c2eb4807", + address: "seeds.lavenderfive.com:12856", + provider: "Lavender.Five Nodes 🐝" + }, + { + id: "ebc272824924ea1a27ea3183dd0b9ba713494f83", + address: "akash-mainnet-seed.autostake.com:26696", + provider: "AutoStake 🛡️ Slash Protected" + }, + { + id: "5e37aefd2a0b9d036b1609a45d6487606da0204b", + address: "rpc.ny.akash.farm:26656" + }, + { + id: "47f7b7a021497ad7a338ea041f19a1a11ae06795", + address: "rpc.la.akash.farm:26656" + }, + { + id: "e1b058e5cfa2b836ddaa496b10911da62dcf182e", + address: "akash-seed-de.allnodes.me:26656", + provider: "Allnodes.com ⚡️ Nodes & Staking" + }, + { + id: "e726816f42831689eab9378d5d577f1d06d25716", + address: "akash-seed-us.allnodes.me:26656", + provider: "Allnodes.com ⚡️ Nodes & Staking" + }, + { + id: "9aa4c9097c818871e45aaca4118a9fe5e86c60e2", + address: "seed-akash-01.stakeflow.io:1506", + provider: "Stakeflow" + } + ], + persistent_peers: [ + { + id: "4acf579e2744268f834c713e894850995bbf0ffa", + address: "50.18.31.225:26656" + }, + { + id: "86afe23f116ba4754a19819a55d153008eb74b48", + address: "15.164.87.75:26656" + }, + { + id: "20180c45451739668f6e272e007818139dba31e7", + address: "88.198.62.198:2020" + }, + { + id: "1bfbbf77beeb2c1ace50443478035a255a7e510f", + address: "136.24.44.100:26656" + }, + { + id: "ebc272824924ea1a27ea3183dd0b9ba713494f83", + address: "akash-mainnet-peer.autostake.com:26696", + provider: "AutoStake 🛡️ Slash Protected" + }, + { + id: "9aa4c9097c818871e45aaca4118a9fe5e86c60e2", + address: "peer-akash-01.stakeflow.io:1506", + provider: "Stakeflow" + } + ] + }, + apis: { + rpc: [ + { + address: "https://rpc.akash.forbole.com:443", + provider: "forbole" + }, + { + address: "https://rpc-akash.ecostake.com:443", + provider: "ecostake" + }, + { + address: "https://akash-rpc.lavenderfive.com:443", + provider: "Lavender.Five Nodes" + }, + { + address: "https://akash-rpc.polkachu.com", + provider: "Polkachu" + }, + { + address: "https://rpc-akash.cosmos-spaces.cloud", + provider: "Cosmos Spaces" + }, + { + address: "https://rpc-akash-ia.cosmosia.notional.ventures:443", + provider: "Notional" + }, + { + address: "http://akash.c29r3.xyz:80/rpc", + provider: "c29r3" + }, + { + address: "https://akash-mainnet-rpc.autostake.com:443", + provider: "AutoStake 🛡️ Slash Protected" + }, + { + address: "https://akash.rpc.interchain.ivaldilabs.xyz", + provider: "ivaldilabs" + }, + { + address: "https://akash-rpc.kleomedes.network", + provider: "Kleomedes" + }, + { + address: "https://rpc-akash-01.stakeflow.io", + provider: "Stakeflow" + }, + { + address: "https://akash-mainnet-rpc.cosmonautstakes.com:443", + provider: "Cosmonaut Stakes" + }, + { + address: "https://akash-rpc.w3coins.io", + provider: "w3coins" + }, + { + address: "https://akash-rpc.publicnode.com", + provider: "Allnodes.com ⚡️ Nodes & Staking" + }, + { + address: "https://akash-rpc.validatornode.com", + provider: "ValidatorNode" + } + ], + rest: [ + { + address: "https://api.akash.forbole.com:443", + provider: "forbole" + }, + { + address: "https://rest-akash.ecostake.com", + provider: "ecostake" + }, + { + address: "https://akash-api.lavenderfive.com:443", + provider: "Lavender.Five Nodes" + }, + { + address: "https://akash-api.polkachu.com", + provider: "Polkachu" + }, + { + address: "https://api-akash.cosmos-spaces.cloud", + provider: "Cosmos Spaces" + }, + { + address: "https://api-akash-ia.cosmosia.notional.ventures", + provider: "Notional" + }, + { + address: "https://akash.c29r3.xyz:443/api", + provider: "c29r3" + }, + { + address: "https://akash-mainnet-lcd.autostake.com:443", + provider: "AutoStake 🛡️ Slash Protected" + }, + { + address: "https://akash.rest.interchain.ivaldilabs.xyz", + provider: "ivaldilabs" + }, + { + address: "https://akash-api.kleomedes.network", + provider: "Kleomedes" + }, + { + address: "https://api-akash-01.stakeflow.io", + provider: "Stakeflow" + }, + { + address: "https://akash-mainnet-rest.cosmonautstakes.com:443", + provider: "Cosmonaut Stakes" + }, + { + address: "https://akash-api.w3coins.io", + provider: "w3coins" + }, + { + address: "https://akash-rest.publicnode.com", + provider: "Allnodes.com ⚡️ Nodes & Staking" + }, + { + address: "https://akash-api.validatornode.com", + provider: "ValidatorNode" + } + ], + grpc: [ + { + address: "grpc-akash-ia.cosmosia.notional.ventures:443", + provider: "Notional" + }, + { + address: "akash-grpc.lavenderfive.com:443", + provider: "Lavender.Five Nodes 🐝" + }, + { + address: "akash-grpc.polkachu.com:12890", + provider: "Polkachu" + }, + { + address: "akash-mainnet-grpc.autostake.com:443", + provider: "AutoStake 🛡️ Slash Protected" + }, + { + address: "grpc-akash.cosmos-spaces.cloud:1110", + provider: "Cosmos Spaces" + }, + { + address: "akash.grpc.interchain.ivaldilabs.xyz:443", + provider: "ivaldilabs" + }, + { + address: "grpc-akash-01.stakeflow.io:1502", + provider: "Stakeflow" + }, + { + address: "akash-grpc.w3coins.io:12890", + provider: "w3coins" + }, + { + address: "akash-grpc.publicnode.com:443", + provider: "Allnodes.com ⚡️ Nodes & Staking" + } + ] + }, + explorers: [ + { + kind: "EZ Staking", + url: "https://app.ezstaking.io/akash", + tx_page: "https://app.ezstaking.io/akash/txs/${txHash}", + account_page: "https://app.ezstaking.io/akash/account/${accountAddress}" + }, + { + kind: "mintscan", + url: "https://www.mintscan.io/akash", + tx_page: "https://www.mintscan.io/akash/transactions/${txHash}", + account_page: "https://www.mintscan.io/akash/accounts/${accountAddress}" + }, + { + kind: "ping.pub", + url: "https://ping.pub/akash-network", + tx_page: "https://ping.pub/akash-network/tx/${txHash}" + }, + { + kind: "bigdipper", + url: "https://akash.bigdipper.live/", + tx_page: "https://akash.bigdipper.live/transactions/${txHash}" + }, + { + kind: "atomscan", + url: "https://atomscan.com/akash", + tx_page: "https://atomscan.com/akash/transactions/${txHash}", + account_page: "https://atomscan.com/akash/accounts/${accountAddress}" + }, + { + kind: "Akash Stats", + url: "https://stats.akash.network/blocks", + tx_page: "https://stats.akash.network/transactions/${txHash}" + }, + { + kind: "Stakeflow", + url: "https://stakeflow.io/akash", + account_page: "https://stakeflow.io/akash/accounts/${accountAddress}" + }, + { + kind: "ValidatorNode", + url: "https://explorer.validatornode.com/akash-network", + tx_page: "https://explorer.validatornode.com/akash-network/tx/${txHash}" + } + ], + images: [ + { + png: "https://raw.githubusercontent.com/cosmos/chain-registry/master/akash/images/akt.png", + svg: "https://raw.githubusercontent.com/cosmos/chain-registry/master/akash/images/akt.svg" + } + ] +}; + +export const akashAssetList = assets.find(x => x.chain_name === "akash") as AssetList; diff --git a/apps/provider-console/src/chains/index.ts b/apps/provider-console/src/chains/index.ts new file mode 100644 index 000000000..7e0a192ff --- /dev/null +++ b/apps/provider-console/src/chains/index.ts @@ -0,0 +1,6 @@ +import { akash, akashAssetList } from "./akash"; +import { akashSandbox, akashSandboxAssetList } from "./akash-sandbox"; +import { akashTestnet, akashTestnetAssetList } from "./akash-testnet"; + +export { akash, akashSandbox, akashTestnet, akashAssetList, akashSandboxAssetList, akashTestnetAssetList }; +export const assetLists = [akashAssetList, akashSandboxAssetList, akashTestnetAssetList]; diff --git a/apps/provider-console/src/components/layout/Nav.tsx b/apps/provider-console/src/components/layout/Nav.tsx index 323e3f315..b968ef0b4 100644 --- a/apps/provider-console/src/components/layout/Nav.tsx +++ b/apps/provider-console/src/components/layout/Nav.tsx @@ -7,6 +7,7 @@ import useCookieTheme from "@src/hooks/useTheme"; import { accountBarHeight } from "@src/utils/constants"; import { UrlService } from "@src/utils/urlUtils"; import { AkashConsoleBetaLogoDark, AkashConsoleBetaLogoLight } from "../icons/AkashConsoleLogo"; +import { WalletStatus } from "./WalletStatus"; export const Nav = ({ isMobileOpen, @@ -38,9 +39,13 @@ export const Nav = ({ +
+
+ +
+
); }; - diff --git a/apps/provider-console/src/components/layout/TransactionModal.tsx b/apps/provider-console/src/components/layout/TransactionModal.tsx new file mode 100644 index 000000000..bb8d84cdc --- /dev/null +++ b/apps/provider-console/src/components/layout/TransactionModal.tsx @@ -0,0 +1,38 @@ +"use client"; +import { ReactNode } from "react"; +import { Popup, Spinner } from "@akashnetwork/ui/components"; + +type Props = { + state: "waitingForApproval" | "broadcasting"; + open: boolean; + onClose?: () => void; + children?: ReactNode; +}; + +export const TransactionModal: React.FunctionComponent = ({ state, open, onClose }) => { + return ( + Waiting for tx approval :
Transaction Pending
+ } + actions={[]} + onClose={onClose} + maxWidth="xs" + enableCloseOnBackdropClick={false} + hideCloseButton + > +
+
+ +
+ +
+ {state === "waitingForApproval" ? "APPROVE OR REJECT TX TO CONTINUE..." : "BROADCASTING TRANSACTION..."} +
+
+
+ ); +}; diff --git a/apps/provider-console/src/components/layout/WalletStatus.tsx b/apps/provider-console/src/components/layout/WalletStatus.tsx new file mode 100644 index 000000000..e825c91be --- /dev/null +++ b/apps/provider-console/src/components/layout/WalletStatus.tsx @@ -0,0 +1,182 @@ +"use client"; +import { FormattedNumber } from "react-intl"; +import { + Address, + Badge, + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Spinner, + Tooltip, + TooltipContent, + TooltipTrigger +} from "@akashnetwork/ui/components"; +import { Bank, LogOut, MoreHoriz, Wallet } from "iconoir-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; + +import { useWallet } from "@src/context/WalletProvider"; +import { useTotalWalletBalance } from "@src/hooks/useWalletBalance"; +import { udenomToDenom } from "@src/utils/mathHelpers"; +import { FormattedDecimal } from "../shared/FormattedDecimal"; +import { ConnectWalletButton } from "../wallet/ConnectWalletButton"; +import { useEffect } from "react"; +import { useChainWallet, useWalletClient } from "@cosmos-kit/react"; +import { useSelectedChain } from "@src/context/CustomChainProvider"; +import authClient from "@src/utils/authClient"; +// import { jwtDecode } from "jwt-decode"; + +export function WalletStatus() { + const { walletName, address, walletBalances, logout, isWalletLoaded, isWalletConnected } = useWallet(); + const { wallet } = useSelectedChain(); + + const walletBalance = useTotalWalletBalance(); + const router = useRouter(); + + const { signArbitrary: keplrSignArbitrary } = useChainWallet("akash", "keplr-extension"); + const { signArbitrary: leapSignArbitrary } = useChainWallet("akash", "leap-extension"); + + // Define your custom function to call on successful connection + const onWalletConnectSuccess = async () => { + if (!localStorage.getItem("accessToken")) { + //check if accesstoken is not expired + + // Get Nonce + const response = await authClient.get(`users/nonce/${address}`); + if (response.data.nonce) { + // Get Address + let url: string; + if (process.env.NODE_ENV === "development") { + url = "app-dev.praetor.dev"; + } else { + url = window.location.hostname; + } + console.log(wallet); + + const message = `${url} wants you to sign in with your Keplr account - ${address} using Nonce - ${response.data.nonce}`; + let result; + if (wallet?.name == "leap-extension") { + result = await leapSignArbitrary(address, message); + } else { + result = await keplrSignArbitrary(address, message); + } + + console.log(result); + if (result) { + const verifySign = await authClient.post("auth/verify", { signer: address, ...result }); + if (verifySign.data) { + localStorage.setItem("accessToken", verifySign.data.access_token); + localStorage.setItem("refreshToken", verifySign.data.refresh_token); + } else { + console.log("There is some error in signing"); + logout(); + } + } + } + } else { + // TODO Probably more work needs to be done for refresh token + + console.log("Access Token Found"); + // const decodedJwt: any = jwtDecode(localStorage.getItem("accessToken")); + + // if (decodedJwt.exp < Math.floor(Date.now() / 1000)) { + // // renew with access token + // // TODO renew access token logic here + // } + } + }; + + useEffect(() => { + if (isWalletConnected) { + onWalletConnectSuccess(); + } else { + console.log("Disconnected"); + } + }, [isWalletConnected]); // Ensure to include address as a dependency if needed + + function onDisconnectClick() { + logout(); + } + + return ( + <> + {isWalletLoaded ? ( + isWalletConnected ? ( + <> +
+
+ + + + + + onDisconnectClick()}> + +  Disconnect Wallet + + + +
+ +
+
+ + + + + {walletName} + + +
+ + + +
+ + {walletBalances && ( +
+ + + + + + + +
+
+ + AKT +
+
+ + USDC +
+
+
+
+
+ )} +
+
+ + ) : ( + + ) + ) : ( +
+ +
+ )} + + ); +} diff --git a/apps/provider-console/src/components/shared/FormattedDecimal.tsx b/apps/provider-console/src/components/shared/FormattedDecimal.tsx new file mode 100644 index 000000000..1fdcf257a --- /dev/null +++ b/apps/provider-console/src/components/shared/FormattedDecimal.tsx @@ -0,0 +1,39 @@ +"use client"; +import React from "react"; +import { FormattedNumberParts } from "react-intl"; + +type Props = { + value: number; + precision?: number; + style?: "currency" | "unit" | "decimal" | "percent"; + currency?: string; +}; + +export const FormattedDecimal: React.FunctionComponent = ({ value, precision = 6, style, currency }) => { + return ( + + {parts => ( + <> + {parts.map((part, i) => { + switch (part.type) { + case "integer": + case "group": + return {part.value}; + + case "decimal": + case "fraction": + return ( + + {part.value} + + ); + + default: + return {part.value}; + } + })} + + )} + + ); +}; diff --git a/apps/provider-console/src/components/wallet/ConnectWalletButton.tsx b/apps/provider-console/src/components/wallet/ConnectWalletButton.tsx new file mode 100644 index 000000000..e1acc9656 --- /dev/null +++ b/apps/provider-console/src/components/wallet/ConnectWalletButton.tsx @@ -0,0 +1,37 @@ +"use client"; +import React, { ReactNode, useEffect } from "react"; +import { Button, ButtonProps } from "@akashnetwork/ui/components"; +import { Wallet } from "iconoir-react"; + +import { useSelectedChain } from "@src/context/CustomChainProvider"; +import { cn } from "@src/utils/styleUtils"; + +interface Props extends ButtonProps { + children?: ReactNode; + className?: string; +} + +export const ConnectWalletButton: React.FunctionComponent = ({ className = "", ...rest }) => { + const { connect, status, isWalletConnected, address } = useSelectedChain(); + + // Define your custom function to call on successful connection + const onWalletConnectSuccess = () => { + console.log("Wallet connected successfully!", address); + // Add any other logic you want to execute upon successful connection + }; + + // Use useEffect to monitor the connection status + useEffect(() => { + console.log(isWalletConnected, address); + if (status === "Connected") { + onWalletConnectSuccess(); + } + }, [status, address]); // Ensure to include address as a dependency if needed + + return ( + + ); +}; diff --git a/apps/provider-console/src/context/ChainParamProvider/ChainParamProvider.tsx b/apps/provider-console/src/context/ChainParamProvider/ChainParamProvider.tsx new file mode 100644 index 000000000..a11682a83 --- /dev/null +++ b/apps/provider-console/src/context/ChainParamProvider/ChainParamProvider.tsx @@ -0,0 +1,37 @@ +"use client"; +import React from "react"; +import { useEffect } from "react"; + +import { useUsdcDenom } from "@src/hooks/useDenom"; +import { useDepositParams } from "@src/queries/useSettings"; +import { uAktDenom } from "@src/utils/constants"; +import { udenomToDenom } from "@src/utils/mathHelpers"; +import { uaktToAKT } from "@src/utils/priceUtils"; +import { useSettings } from "../SettingsProvider"; + +type MinDeposit = { + akt: number; + usdc: number; +}; + +type ContextType = { + minDeposit: MinDeposit; +}; + +const ChainParamContext = React.createContext({} as ContextType); + +export const ChainParamProvider = ({ children }) => { + const { isSettingsInit } = useSettings(); + const { data: depositParams, refetch: getDepositParams } = useDepositParams({ enabled: false }); + const usdcDenom = useUsdcDenom(); + const aktMinDeposit = depositParams ? uaktToAKT(parseFloat(depositParams.find(x => x.denom === uAktDenom)?.amount || "") || 0) : 0; + const usdcMinDeposit = depositParams ? udenomToDenom(parseFloat(depositParams.find(x => x.denom === usdcDenom)?.amount || "") || 0) : 0; + const minDeposit = { akt: aktMinDeposit, usdc: usdcMinDeposit }; + + return {children}; +}; + +// Hook +export function useChainParam() { + return { ...React.useContext(ChainParamContext) }; +} diff --git a/apps/provider-console/src/context/ChainParamProvider/index.ts b/apps/provider-console/src/context/ChainParamProvider/index.ts new file mode 100644 index 000000000..25dbec1da --- /dev/null +++ b/apps/provider-console/src/context/ChainParamProvider/index.ts @@ -0,0 +1 @@ +export { useChainParam, ChainParamProvider } from "./ChainParamProvider"; diff --git a/apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx b/apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx new file mode 100644 index 000000000..d469b1698 --- /dev/null +++ b/apps/provider-console/src/context/CustomChainProvider/CustomChainProvider.tsx @@ -0,0 +1,63 @@ +"use client"; +import { GasPrice } from "@cosmjs/stargate"; +import { wallets as cosmostation } from "@cosmos-kit/cosmostation-extension"; +import { wallets as keplr } from "@cosmos-kit/keplr"; +import { wallets as leap } from "@cosmos-kit/leap-extension"; +import { ChainProvider } from "@cosmos-kit/react"; +import { useChain } from "@cosmos-kit/react"; + +import { akash, akashSandbox, akashTestnet, assetLists } from "@src/chains"; +import { useSelectedNetwork } from "@src/hooks/useSelectedNetwork"; +import { customRegistry } from "@src/utils/customRegistry"; + +import "@interchain-ui/react/styles"; +import "@interchain-ui/react/globalStyles"; + +type Props = { + children: React.ReactNode; +}; + +export function CustomChainProvider({ children }: Props) { + return ( + { + console.log("session expired"); + window.localStorage.removeItem("cosmos-kit@2:core//current-wallet"); + window.location.reload(); + } + }} + walletConnectOptions={{ + signClient: { + projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID as string + } + }} + endpointOptions={{ + isLazy: true, + endpoints: { + akash: { rest: [], rpc: [] }, + "akash-sandbox": { rest: [], rpc: [] }, + "akash-testnet": { rest: [], rpc: [] } + } + }} + signerOptions={{ + preferredSignType: () => "direct", + signingStargate: () => ({ + registry: customRegistry, + gasPrice: GasPrice.fromString("0.025uakt") + }) + }} + > + {children} + + ); +} + +export function useSelectedChain() { + const { chainRegistryName } = useSelectedNetwork(); + return useChain(chainRegistryName); +} diff --git a/apps/provider-console/src/context/CustomChainProvider/index.ts b/apps/provider-console/src/context/CustomChainProvider/index.ts new file mode 100644 index 000000000..6ba54485b --- /dev/null +++ b/apps/provider-console/src/context/CustomChainProvider/index.ts @@ -0,0 +1 @@ +export { CustomChainProvider, useSelectedChain } from "./CustomChainProvider"; diff --git a/apps/provider-console/src/context/PricingProvider/PricingProvider.tsx b/apps/provider-console/src/context/PricingProvider/PricingProvider.tsx new file mode 100644 index 000000000..17deea308 --- /dev/null +++ b/apps/provider-console/src/context/PricingProvider/PricingProvider.tsx @@ -0,0 +1,56 @@ +"use client"; +import React from "react"; + +import { useUsdcDenom } from "@src/hooks/useDenom"; +import { useMarketData } from "@src/queries"; +import { uAktDenom } from "@src/utils/constants"; +import { roundDecimal } from "@src/utils/mathHelpers"; + +type ContextType = { + isLoaded: boolean; + isLoading: boolean; + price: number | undefined; + uaktToUSD: (amount: number) => number | null; + aktToUSD: (amount: number) => number | null; + getPriceForDenom: (denom: string) => number; +}; + +const PricingProviderContext = React.createContext({} as ContextType); + +export const PricingProvider = ({ children }) => { + const { data: marketData, isLoading } = useMarketData({ refetchInterval: 60_000 }); + const usdcIbcDenom = useUsdcDenom(); + + function uaktToUSD(amount: number) { + if (!marketData) return null; + return roundDecimal((amount * marketData.price) / 1_000_000, 2); + } + + function aktToUSD(amount: number) { + if (!marketData) return null; + return roundDecimal(amount * marketData.price, 2); + } + + const getPriceForDenom = (denom: string) => { + switch (denom) { + case uAktDenom: + return marketData?.price || 0; + case usdcIbcDenom: + return 1; // TODO Get price from API + + default: + return 0; + } + }; + + return ( + + {children} + + ); +}; + +// Hook +export function usePricing() { + return { ...React.useContext(PricingProviderContext) }; +} diff --git a/apps/provider-console/src/context/PricingProvider/index.ts b/apps/provider-console/src/context/PricingProvider/index.ts new file mode 100644 index 000000000..177c0a4a5 --- /dev/null +++ b/apps/provider-console/src/context/PricingProvider/index.ts @@ -0,0 +1 @@ +export { usePricing, PricingProvider } from "./PricingProvider"; diff --git a/apps/provider-console/src/context/SettingsProvider/SettingsProviderContext.tsx b/apps/provider-console/src/context/SettingsProvider/SettingsProviderContext.tsx new file mode 100644 index 000000000..eb1c1163b --- /dev/null +++ b/apps/provider-console/src/context/SettingsProvider/SettingsProviderContext.tsx @@ -0,0 +1,307 @@ +"use client"; +import React, { FC, ReactNode, useCallback, useEffect, useState } from "react"; +import axios from "axios"; + +import { useLocalStorage } from "@src/hooks/useLocalStorage"; +import { usePreviousRoute } from "@src/hooks/usePreviousRoute"; +import { queryClient } from "@src/queries"; +import { initiateNetworkData, networks } from "@src/store/networkStore"; +import { NodeStatus } from "@src/types/node"; +import { mainnetNodes } from "@src/utils/apiUtils"; +import { mainnetId } from "@src/utils/constants"; +import { initAppTypes } from "@src/utils/init"; + +export type BlockchainNode = { + api: string; + rpc: string; + status: string; + latency: number; + nodeInfo: NodeStatus | null; + id: string; +}; + +export type Settings = { + apiEndpoint: string; + rpcEndpoint: string; + isCustomNode: boolean; + nodes: Array; + selectedNode: BlockchainNode | null; + customNode: BlockchainNode | null; +}; + +type ContextType = { + settings: Settings; + setSettings: (newSettings: Settings) => void; + isLoadingSettings: boolean; + isSettingsInit: boolean; + refreshNodeStatuses: (settingsOverride?: Settings) => Promise; + isRefreshingNodeStatus: boolean; + selectedNetworkId: string; + setSelectedNetworkId: (value: React.SetStateAction) => void; +}; + +const SettingsProviderContext = React.createContext({} as ContextType); + +const defaultSettings: Settings = { + apiEndpoint: "", + rpcEndpoint: "", + isCustomNode: false, + nodes: [], + selectedNode: null, + customNode: null +}; + +export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => { + const [settings, setSettings] = useState(defaultSettings); + const [isLoadingSettings, setIsLoadingSettings] = useState(true); + const [isSettingsInit, setIsSettingsInit] = useState(false); + const [isRefreshingNodeStatus, setIsRefreshingNodeStatus] = useState(false); + const { getLocalStorageItem, setLocalStorageItem } = useLocalStorage(); + const [selectedNetworkId, setSelectedNetworkId] = useState(mainnetId); + const { isCustomNode, customNode, nodes, apiEndpoint, rpcEndpoint } = settings; + + usePreviousRoute(); + + // load settings from localStorage or set default values + useEffect(() => { + const initiateSettings = async () => { + setIsLoadingSettings(true); + + // Set the versions and metadata of available networks + await initiateNetworkData(); + + // Init app types based on the selected network id + initAppTypes(); + + const _selectedNetworkId = localStorage.getItem("selectedNetworkId") || mainnetId; + + setSelectedNetworkId(_selectedNetworkId); + + const settingsStr = getLocalStorageItem("settings"); + const settings = { ...defaultSettings, ...JSON.parse(settingsStr || "{}") } as Settings; + + // Set the available nodes list and default endpoints + const currentNetwork = networks.find(x => x.id === _selectedNetworkId); + const response = await axios.get(currentNetwork?.nodesUrl || mainnetNodes); + const nodes = response.data as Array<{ id: string; api: string; rpc: string }>; + const mappedNodes: Array = await Promise.all( + nodes.map(async node => { + const nodeStatus = await loadNodeStatus(node.rpc); + + return { + ...node, + status: nodeStatus.status, + latency: nodeStatus.latency, + nodeInfo: nodeStatus.nodeInfo + } as BlockchainNode; + }) + ); + + const hasSettings = + settingsStr && settings.apiEndpoint && settings.rpcEndpoint && settings.selectedNode && nodes?.find(x => x.id === settings.selectedNode?.id); + let defaultApiNode = hasSettings ?? settings.apiEndpoint; + let defaultRpcNode = hasSettings ?? settings.rpcEndpoint; + let selectedNode = hasSettings ?? settings.selectedNode; + + // If the user has a custom node set, use it no matter the status + if (hasSettings && settings.isCustomNode) { + const nodeStatus = await loadNodeStatus(settings.rpcEndpoint); + const customNodeUrl = new URL(settings.apiEndpoint); + + const customNode: Partial = { + status: nodeStatus.status, + latency: nodeStatus.latency, + nodeInfo: nodeStatus.nodeInfo, + id: customNodeUrl.hostname + }; + + updateSettings({ ...settings, apiEndpoint: defaultApiNode, rpcEndpoint: defaultRpcNode, selectedNode, customNode, nodes: mappedNodes }); + } + + // If the user has no settings or the selected node is inactive, use the fastest available active node + if (!hasSettings || (hasSettings && settings.selectedNode?.status === "inactive")) { + const randomNode = getFastestNode(mappedNodes); + // Use cosmos.directory as a backup if there's no active nodes in the list + defaultApiNode = randomNode?.api || "https://rest.cosmos.directory/akash"; + defaultRpcNode = randomNode?.rpc || "https://rpc.cosmos.directory/akash"; + selectedNode = randomNode || { + api: defaultApiNode, + rpc: defaultRpcNode, + status: "active", + latency: 0, + nodeInfo: null, + id: "https://rest.cosmos.directory/akash" + }; + updateSettings({ ...settings, apiEndpoint: defaultApiNode, rpcEndpoint: defaultRpcNode, selectedNode, nodes: mappedNodes }); + } else { + defaultApiNode = settings.apiEndpoint; + defaultRpcNode = settings.rpcEndpoint; + selectedNode = settings.selectedNode; + updateSettings({ ...settings, apiEndpoint: defaultApiNode, rpcEndpoint: defaultRpcNode, selectedNode, nodes: mappedNodes }); + } + + setIsLoadingSettings(false); + setIsSettingsInit(true); + }; + + initiateSettings(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + /** + * Load the node status from status rpc endpoint + * @param {*} nodeUrl + * @returns + */ + const loadNodeStatus = async (rpcUrl: string) => { + const start = performance.now(); + let latency: number, + status: "active" | "inactive" = "inactive", + nodeStatus: NodeStatus | null = null; + + try { + const response = await axios.get(`${rpcUrl}/status`, { timeout: 10000 }); + nodeStatus = response.data.result as NodeStatus; + status = "active"; + } catch (error) { + status = "inactive"; + } finally { + const end = performance.now(); + latency = end - start; + + // eslint-disable-next-line no-unsafe-finally + return { + latency, + status, + nodeInfo: nodeStatus + }; + } + }; + + /** + * Get the fastest node from the list based on latency + * @param {*} nodes + * @returns + */ + const getFastestNode = (nodes: Array) => { + const filteredNodes = nodes.filter(n => n.status === "active" && n.nodeInfo?.sync_info.catching_up === false); + let lowest = Number.POSITIVE_INFINITY, + fastestNode: BlockchainNode | null = null; + + // No active node, return the first one + if (filteredNodes.length === 0) { + return nodes[0]; + } + + filteredNodes.forEach(node => { + if (node.latency < lowest) { + lowest = node.latency; + fastestNode = node; + } + }); + + return fastestNode; + }; + + const updateSettings = newSettings => { + setSettings(prevSettings => { + clearQueries(prevSettings, newSettings); + setLocalStorageItem("settings", JSON.stringify(newSettings)); + + return newSettings; + }); + }; + + const clearQueries = (prevSettings, newSettings) => { + if (prevSettings.apiEndpoint !== newSettings.apiEndpoint || (prevSettings.isCustomNode && !newSettings.isCustomNode)) { + // Cancel and remove queries from cache if the api endpoint is changed + queryClient.resetQueries(); + queryClient.cancelQueries(); + queryClient.removeQueries(); + queryClient.clear(); + } + }; + + /** + * Refresh the nodes status and latency + * @returns + */ + const refreshNodeStatuses = useCallback( + async (settingsOverride?) => { + if (isRefreshingNodeStatus) return; + + setIsRefreshingNodeStatus(true); + let _nodes = settingsOverride ? settingsOverride.nodes : nodes; + let _customNode = settingsOverride ? settingsOverride.customNode : customNode; + const _isCustomNode = settingsOverride ? settingsOverride.isCustomNode : isCustomNode; + const _apiEndpoint = settingsOverride ? settingsOverride.apiEndpoint : apiEndpoint; + const _rpcEndpoint = settingsOverride ? settingsOverride.rpcEndpoint : rpcEndpoint; + + if (_isCustomNode) { + const nodeStatus = await loadNodeStatus(_rpcEndpoint); + const customNodeUrl = new URL(_apiEndpoint); + + _customNode = { + status: nodeStatus.status, + latency: nodeStatus.latency, + nodeInfo: nodeStatus.nodeInfo, + id: customNodeUrl.hostname + }; + } else { + _nodes = await Promise.all( + _nodes.map(async node => { + const nodeStatus = await loadNodeStatus(node.rpc); + + return { + ...node, + status: nodeStatus.status, + latency: nodeStatus.latency, + nodeInfo: nodeStatus.nodeInfo + }; + }) + ); + } + + setIsRefreshingNodeStatus(false); + + // Update the settings with callback to avoid stale state settings + setSettings(prevSettings => { + const selectedNode = _nodes.find(node => node.id === prevSettings.selectedNode?.id); + + const newSettings = { + ...prevSettings, + nodes: _nodes, + selectedNode, + customNode: _customNode + }; + + clearQueries(prevSettings, newSettings); + setLocalStorageItem("settings", JSON.stringify(newSettings)); + + return newSettings; + }); + }, + [isCustomNode, isRefreshingNodeStatus, customNode, setLocalStorageItem, apiEndpoint, nodes, setSettings] + ); + + return ( + + {children} + + ); +}; + +export const useSettings = () => { + return { ...React.useContext(SettingsProviderContext) }; +}; diff --git a/apps/provider-console/src/context/SettingsProvider/index.ts b/apps/provider-console/src/context/SettingsProvider/index.ts new file mode 100644 index 000000000..03b37599c --- /dev/null +++ b/apps/provider-console/src/context/SettingsProvider/index.ts @@ -0,0 +1 @@ +export { useSettings, SettingsProvider } from "./SettingsProviderContext"; diff --git a/apps/provider-console/src/context/WalletProvider/WalletProvider.tsx b/apps/provider-console/src/context/WalletProvider/WalletProvider.tsx new file mode 100644 index 000000000..085dd5acf --- /dev/null +++ b/apps/provider-console/src/context/WalletProvider/WalletProvider.tsx @@ -0,0 +1,325 @@ +"use client"; +import React, { useRef } from "react"; +import { useEffect, useState } from "react"; +import { Snackbar } from "@akashnetwork/ui/components"; +import { EncodeObject } from "@cosmjs/proto-signing"; +import { SigningStargateClient } from "@cosmjs/stargate"; +import { useManager } from "@cosmos-kit/react"; +import axios from "axios"; +import { OpenNewWindow } from "iconoir-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { SnackbarKey, useSnackbar } from "notistack"; + +import { TransactionModal } from "@src/components/layout/TransactionModal"; +// import { useAllowance } from "@src/hooks/useAllowance"; TODO +import { useUsdcDenom } from "@src/hooks/useDenom"; +import { getSelectedNetwork, useSelectedNetwork } from "@src/hooks/useSelectedNetwork"; +import { STATS_APP_URL, uAktDenom } from "@src/utils/constants"; +import { customRegistry } from "@src/utils/customRegistry"; +import { UrlService } from "@src/utils/urlUtils"; +import { LocalWalletDataType } from "@src/utils/walletUtils"; +import { useSelectedChain } from "../CustomChainProvider"; +import { useSettings } from "../SettingsProvider"; + +type Balances = { + uakt: number; + usdc: number; +}; + +type ContextType = { + address: string; + walletName: string; + walletBalances: Balances | null; + isWalletConnected: boolean; + isWalletLoaded: boolean; + connectWallet: () => Promise; + logout: () => void; + setIsWalletLoaded: React.Dispatch>; + signAndBroadcastTx: (msgs: EncodeObject[]) => Promise; + refreshBalances: (address?: string) => Promise; +}; + +const WalletProviderContext = React.createContext({} as ContextType); + +export const WalletProvider = ({ children }) => { + const [walletBalances, setWalletBalances] = useState(null); + const [isWalletLoaded, setIsWalletLoaded] = useState(true); + const [isBroadcastingTx, setIsBroadcastingTx] = useState(false); + const [isWaitingForApproval, setIsWaitingForApproval] = useState(false); + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + const sigingClient = useRef(null); + const router = useRouter(); + const { settings } = useSettings(); + const usdcIbcDenom = useUsdcDenom(); + const { disconnect, getOfflineSigner, isWalletConnected, address: walletAddress, connect, username, estimateFee, sign, broadcast } = useSelectedChain(); + const { addEndpoints } = useManager(); + // const { + // fee: { default: feeGranter } + // } = useAllowance(); + + useEffect(() => { + if (!settings.apiEndpoint || !settings.rpcEndpoint) return; + + addEndpoints({ + akash: { rest: [settings.apiEndpoint], rpc: [settings.rpcEndpoint] }, + "akash-sandbox": { rest: [settings.apiEndpoint], rpc: [settings.rpcEndpoint] }, + "akash-testnet": { rest: [settings.apiEndpoint], rpc: [settings.rpcEndpoint] } + }); + }, [settings.apiEndpoint, settings.rpcEndpoint]); + + useEffect(() => { + (async () => { + if (settings?.rpcEndpoint && isWalletConnected) { + sigingClient.current = await createStargateClient(); + } + })(); + }, [settings?.rpcEndpoint, isWalletConnected]); + + async function createStargateClient() { + const selectedNetwork = getSelectedNetwork(); + + const offlineSigner = getOfflineSigner(); + let rpc = settings?.rpcEndpoint ? settings?.rpcEndpoint : (selectedNetwork.rpcEndpoint as string); + + try { + await axios.get(`${rpc}/abci_info`); + } catch (error) { + // If the rpc node has cors enabled, switch to the backup rpc cosmos.directory + if (error.code === "ERR_NETWORK" || error?.response?.status === 0) { + rpc = selectedNetwork.rpcEndpoint as string; + } + } + + const client = await SigningStargateClient.connectWithSigner(rpc, offlineSigner, { + registry: customRegistry, + broadcastTimeoutMs: 300_000 // 5min + }); + + return client; + } + + async function getStargateClient() { + if (!sigingClient.current) { + sigingClient.current = await createStargateClient(); + } + + return sigingClient.current; + } + + function logout() { + localStorage.removeItem("accessToken"); + localStorage.removeItem("refreshToken"); + setWalletBalances(null); + disconnect(); + router.push(UrlService.home()); + } + + async function connectWallet() { + console.log("Connecting wallet with CosmosKit..."); + connect(); + + await loadWallet(); + } + + // Update balances on wallet address change + useEffect(() => { + if (walletAddress) { + loadWallet(); + } + }, [walletAddress]); + + async function loadWallet(): Promise { + const selectedNetwork = getSelectedNetwork(); + const storageWallets = JSON.parse(localStorage.getItem(`${selectedNetwork.id}/wallets`) || "[]") as LocalWalletDataType[]; + + let currentWallets = storageWallets ?? []; + + if (!currentWallets.some(x => x.address === walletAddress)) { + currentWallets.push({ name: username || "", address: walletAddress as string, selected: true }); + } + + currentWallets = currentWallets.map(x => ({ ...x, selected: x.address === walletAddress })); + + localStorage.setItem(`${selectedNetwork.id}/wallets`, JSON.stringify(currentWallets)); + + await refreshBalances(); + + setIsWalletLoaded(true); + } + + async function signAndBroadcastTx(msgs: EncodeObject[]): Promise { + setIsWaitingForApproval(true); + let pendingSnackbarKey: SnackbarKey | null = null; + try { + const estimatedFees = await estimateFee(msgs); + const txRaw = await sign(msgs, { + ...estimatedFees + // granter: feeGranter + }); + + setIsWaitingForApproval(false); + setIsBroadcastingTx(true); + + pendingSnackbarKey = enqueueSnackbar(, { + variant: "info", + autoHideDuration: null + }); + + const txResult = await broadcast(txRaw); + + setIsBroadcastingTx(false); + + if (txResult.code !== 0) { + throw new Error(txResult.rawLog); + } + + showTransactionSnackbar("Transaction success!", "", txResult.transactionHash, "success"); + + await refreshBalances(); + + return true; + } catch (err) { + console.error(err); + + const transactionHash = err.txHash; + let errorMsg = "An error has occured"; + + if (err.message?.includes("was submitted but was not yet found on the chain")) { + errorMsg = "Transaction timeout"; + } else if (err.message) { + try { + const reg = /Broadcasting transaction failed with code (.+?) \(codespace: (.+?)\)/i; + const match = err.message.match(reg); + const log = err.message.substring(err.message.indexOf("Log"), err.message.length); + + if (match) { + const code = parseInt(match[1]); + const codeSpace = match[2]; + + if (codeSpace === "sdk") { + const errorMessages = { + 5: "Insufficient funds", + 9: "Unknown address", + 11: "Out of gas", + 12: "Memo too large", + 13: "Insufficient fee", + 19: "Tx already in mempool", + 25: "Invalid gas adjustment" + }; + + if (code in errorMessages) { + errorMsg = errorMessages[code]; + } + } + } + + if (log) { + errorMsg += `. ${log}`; + } + } catch (err) { + console.error(err); + } + } + + showTransactionSnackbar("Transaction has failed...", errorMsg, transactionHash, "error"); + + return false; + } finally { + if (pendingSnackbarKey) { + closeSnackbar(pendingSnackbarKey); + } + + setIsWaitingForApproval(false); + setIsBroadcastingTx(false); + } + } + + const showTransactionSnackbar = ( + snackTitle: string, + snackMessage: string, + transactionHash: string, + snackVariant: React.ComponentProps["iconVariant"] + ) => { + enqueueSnackbar( + } + iconVariant={snackVariant} + />, + { + variant: snackVariant, + autoHideDuration: 10000 + } + ); + }; + + async function refreshBalances(address?: string): Promise<{ uakt: number; usdc: number }> { + const _address = address || walletAddress; + const client = await getStargateClient(); + + if (client) { + const balances = await client.getAllBalances(_address as string); + const uaktBalance = balances.find(b => b.denom === uAktDenom); + const usdcBalance = balances.find(b => b.denom === usdcIbcDenom); + + const walletBalances = { + uakt: uaktBalance ? parseInt(uaktBalance.amount) : 0, + usdc: usdcBalance ? parseInt(usdcBalance.amount) : 0 + }; + + setWalletBalances(walletBalances); + + return walletBalances; + } else { + return { + uakt: 0, + usdc: 0 + }; + } + } + + return ( + + {children} + + + + ); +}; + +// Hook +export function useWallet() { + return { ...React.useContext(WalletProviderContext) }; +} + +const TransactionSnackbarContent = ({ snackMessage, transactionHash }) => { + const selectedNetwork = useSelectedNetwork(); + const txUrl = transactionHash && `${STATS_APP_URL}/transactions/${transactionHash}?network=${selectedNetwork.id}`; + + return ( + <> + {snackMessage} + {snackMessage &&
} + {txUrl && ( + + View transaction + + + )} + + ); +}; diff --git a/apps/provider-console/src/context/WalletProvider/index.ts b/apps/provider-console/src/context/WalletProvider/index.ts new file mode 100644 index 000000000..268448160 --- /dev/null +++ b/apps/provider-console/src/context/WalletProvider/index.ts @@ -0,0 +1 @@ +export { useWallet, WalletProvider } from "./WalletProvider"; diff --git a/apps/provider-console/src/hooks/useAllowance.tsx b/apps/provider-console/src/hooks/useAllowance.tsx new file mode 100644 index 000000000..e553c3ec6 --- /dev/null +++ b/apps/provider-console/src/hooks/useAllowance.tsx @@ -0,0 +1,89 @@ +import React, { FC, useMemo } from "react"; +import { Snackbar } from "@akashnetwork/ui/components"; +import isAfter from "date-fns/isAfter"; +import parseISO from "date-fns/parseISO"; +import { OpenNewWindow } from "iconoir-react"; +import difference from "lodash/difference"; +import Link from "next/link"; +import { useSnackbar } from "notistack"; +import { useLocalStorage } from "usehooks-ts"; + +import { useWallet } from "@src/context/WalletProvider"; +import { useWhen } from "@src/hooks/useWhen"; +import { useAllowancesGranted } from "@src/queries/useGrantsQuery"; + +const persisted: Record = typeof window !== "undefined" ? JSON.parse(localStorage.getItem("fee-granters") || "{}") : {}; + +const AllowanceNotificationMessage: FC = () => ( + <> + You can update default fee granter in + + Authorizations Settings + + + +); + +export const useAllowance = () => { + const { address } = useWallet(); + const [defaultFeeGranter, setDefaultFeeGranter] = useLocalStorage("default-fee-granter", undefined); + const { data: allFeeGranters, isLoading, isFetched } = useAllowancesGranted(address); + const { enqueueSnackbar } = useSnackbar(); + + const actualAddresses = useMemo(() => { + if (!address || !allFeeGranters) { + return []; + } + + return allFeeGranters.reduce((acc, grant) => { + if (isAfter(parseISO(grant.allowance.expiration), new Date())) { + acc.push(grant.granter); + } + + return acc; + }, [] as string[]); + }, [allFeeGranters, address]); + + useWhen( + isFetched && address, + () => { + const persistedAddresses = persisted[address] || []; + const added = difference(actualAddresses, persistedAddresses); + const removed = difference(persistedAddresses, actualAddresses); + + if (added.length || removed.length) { + persisted[address] = actualAddresses; + localStorage.setItem(`fee-granters`, JSON.stringify(persisted)); + } + + if (added.length) { + enqueueSnackbar(} />, { + variant: "info" + }); + } + + if (removed.length) { + enqueueSnackbar(} />, { + variant: "warning" + }); + } + + if (defaultFeeGranter && removed.includes(defaultFeeGranter)) { + setDefaultFeeGranter(undefined); + } + }, + [actualAddresses, persisted] + ); + + return useMemo( + () => ({ + fee: { + all: allFeeGranters, + default: defaultFeeGranter, + setDefault: setDefaultFeeGranter, + isLoading + } + }), + [defaultFeeGranter, setDefaultFeeGranter, allFeeGranters, isLoading] + ); +}; diff --git a/apps/provider-console/src/hooks/useDenom.ts b/apps/provider-console/src/hooks/useDenom.ts new file mode 100644 index 000000000..9e63cbbd4 --- /dev/null +++ b/apps/provider-console/src/hooks/useDenom.ts @@ -0,0 +1,21 @@ +import { usdcIbcDenoms } from "@src/utils/constants"; +import { getSelectedNetwork, useSelectedNetwork } from "./useSelectedNetwork"; + +export const useUsdcDenom = () => { + const selectedNetwork = useSelectedNetwork(); + return usdcIbcDenoms[selectedNetwork.id]; +}; + +export const getUsdcDenom = () => { + const selectedNetwork = getSelectedNetwork(); + return usdcIbcDenoms[selectedNetwork.id]; +}; + +export const useSdlDenoms = () => { + const usdcDenom = useUsdcDenom(); + + return [ + { id: "uakt", label: "uAKT", tokenLabel: "AKT", value: "uakt" }, + { id: "uusdc", label: "uUSDC", tokenLabel: "USDC", value: usdcDenom } + ]; +}; diff --git a/apps/provider-console/src/hooks/useLocalStorage.ts b/apps/provider-console/src/hooks/useLocalStorage.ts new file mode 100644 index 000000000..32dc45ef2 --- /dev/null +++ b/apps/provider-console/src/hooks/useLocalStorage.ts @@ -0,0 +1,106 @@ +import { useEffect, useState } from "react"; +import { useEventListener } from "usehooks-ts"; + +import { useWallet } from "@src/context/WalletProvider"; + +export const useLocalStorage = () => { + const { address } = useWallet(); + + const getLocalStorageItem = (key: string) => { + const selectedNetworkId = localStorage.getItem("selectedNetworkId"); + + return localStorage.getItem(`${selectedNetworkId}${address ? "/" + address : ""}/${key}`); + }; + + const setLocalStorageItem = (key: string, value: string) => { + const selectedNetworkId = localStorage.getItem("selectedNetworkId"); + + localStorage.setItem(`${selectedNetworkId}${address ? "/" + address : ""}/${key}`, value); + }; + + const removeLocalStorageItem = (key: string) => { + const selectedNetworkId = localStorage.getItem("selectedNetworkId"); + localStorage.removeItem(`${selectedNetworkId}${address ? "/" + address : ""}/${key}`); + }; + + return { + removeLocalStorageItem, + setLocalStorageItem, + getLocalStorageItem + }; +}; + +export function useCustomLocalStorage(key: string, initialValue: T) { + // Get from local storage then + // parse stored json or return initialValue + const readValue = () => { + // Prevent build error "window is undefined" but keep keep working + if (typeof window === "undefined") { + return initialValue; + } + + try { + const item = window.localStorage.getItem(key); + return item ? (parseJSON(item) as T) : initialValue; + } catch (error) { + console.warn(`Error reading localStorage key “${key}”:`, error); + return initialValue; + } + }; + + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = useState(readValue); + + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue = (value: T | ((newValue: T) => T)) => { + // Prevent build error "window is undefined" but keeps working + if (typeof window == "undefined") { + console.warn(`Tried setting localStorage key “${key}” even though environment is not a client`); + } + + try { + // Allow value to be a function so we have the same API as useState + const newValue = value instanceof Function ? value(storedValue) : value; + + // Save to local storage + window.localStorage.setItem(key, JSON.stringify(newValue)); + + // Save state + setStoredValue(newValue); + + // We dispatch a custom event so every useLocalStorage hook are notified + window.dispatchEvent(new Event("local-storage")); + } catch (error) { + console.warn(`Error setting localStorage key “${key}”:`, error); + } + }; + + useEffect(() => { + setStoredValue(readValue()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleStorageChange = () => { + setStoredValue(readValue()); + }; + + // this only works for other documents, not the current one + useEventListener("storage", handleStorageChange); + + // this is a custom event, triggered in writeValueToLocalStorage + // See: useLocalStorage() + useEventListener("local-storage", handleStorageChange); + + return [storedValue, setValue]; +} + +// A wrapper for "JSON.parse()"" to support "undefined" value +function parseJSON(value: string) { + try { + return value === "undefined" ? undefined : JSON.parse(value ?? ""); + } catch (error) { + return value === "undefined" ? undefined : value; + } +} diff --git a/apps/provider-console/src/hooks/usePreviousRoute.ts b/apps/provider-console/src/hooks/usePreviousRoute.ts new file mode 100644 index 000000000..9205467c3 --- /dev/null +++ b/apps/provider-console/src/hooks/usePreviousRoute.ts @@ -0,0 +1,24 @@ +import { useAtom } from "jotai"; +import { useRouter } from "next/router"; +import { useEffectOnce } from "usehooks-ts"; + +import routeStore from "@src/store/routeStore"; + +export const usePreviousRoute = () => { + const router = useRouter(); + const [previousRoute, setPreviousRoute] = useAtom(routeStore.previousRoute); + + useEffectOnce(() => { + const handleRouteChange = (url: string) => { + setPreviousRoute(url); + }; + + router.events?.on("routeChangeStart", handleRouteChange); + + return () => { + router.events?.off("routeChangeStart", handleRouteChange); + }; + }); + + return previousRoute; +}; diff --git a/apps/provider-console/src/hooks/useSelectedNetwork.ts b/apps/provider-console/src/hooks/useSelectedNetwork.ts new file mode 100644 index 000000000..8cdd9969b --- /dev/null +++ b/apps/provider-console/src/hooks/useSelectedNetwork.ts @@ -0,0 +1,24 @@ +import { useAtom } from "jotai"; +import { useEffectOnce } from "usehooks-ts"; + +import networkStore, { networks } from "@src/store/networkStore"; +import { mainnetId } from "@src/utils/constants"; + +export const getSelectedNetwork = () => { + const selectedNetworkId = (typeof window !== "undefined" && localStorage.getItem("selectedNetworkId")) ?? mainnetId; + const selectedNetwork = networks.find(n => n.id === selectedNetworkId); + + // return mainnet if selected network is not found + return selectedNetwork ?? networks[0]; +}; + +export const useSelectedNetwork = () => { + const [selectedNetwork, setSelectedNetwork] = useAtom(networkStore.selectedNetwork); + + useEffectOnce(() => { + const selectedNetworkId = localStorage.getItem("selectedNetworkId") ?? mainnetId; + setSelectedNetwork(networks.find(n => n.id === selectedNetworkId) || networks[0]); + }); + + return selectedNetwork ?? networks[0]; +}; diff --git a/apps/provider-console/src/hooks/useWalletBalance.ts b/apps/provider-console/src/hooks/useWalletBalance.ts new file mode 100644 index 000000000..0b3885731 --- /dev/null +++ b/apps/provider-console/src/hooks/useWalletBalance.ts @@ -0,0 +1,71 @@ +import { useEffect, useState } from "react"; + +import { useChainParam } from "@src/context/ChainParamProvider"; +import { usePricing } from "@src/context/PricingProvider"; +import { useWallet } from "@src/context/WalletProvider"; +import { txFeeBuffer, uAktDenom } from "@src/utils/constants"; +import { udenomToDenom } from "@src/utils/mathHelpers"; +import { uaktToAKT } from "@src/utils/priceUtils"; +import { useUsdcDenom } from "./useDenom"; + +export const useTotalWalletBalance = () => { + const { isLoaded, price } = usePricing(); + const { walletBalances } = useWallet(); + const [walletBalance, setWalletBalance] = useState(0); + + useEffect(() => { + if (isLoaded && walletBalances && price) { + const aktUsdValue = uaktToAKT(walletBalances.uakt, 6) * price; + const totalUsdValue = udenomToDenom(walletBalances.usdc, 6); + + setWalletBalance(aktUsdValue + totalUsdValue); + } + }, [isLoaded, price, walletBalances]); + + return walletBalance; +}; + +type DenomData = { + min: number; + label: string; + balance: number; + inputMax: number; +}; + +export const useDenomData = (denom: string) => { + const { isLoaded, price } = usePricing(); + const { walletBalances } = useWallet(); + const [depositData, setDepositData] = useState(null); + const usdcIbcDenom = useUsdcDenom(); + const { minDeposit } = useChainParam(); + + useEffect(() => { + if (isLoaded && walletBalances && minDeposit?.akt && minDeposit?.usdc && price) { + let depositData: DenomData | null = null; + switch (denom) { + case uAktDenom: + depositData = { + min: minDeposit.akt, + label: "AKT", + balance: uaktToAKT(walletBalances.uakt, 6), + inputMax: uaktToAKT(Math.max(walletBalances.uakt - txFeeBuffer, 0), 6) + }; + break; + case usdcIbcDenom: + depositData = { + min: minDeposit.usdc, + label: "USDC", + balance: udenomToDenom(walletBalances.usdc, 6), + inputMax: udenomToDenom(Math.max(walletBalances.usdc - txFeeBuffer, 0), 6) + }; + break; + default: + break; + } + + setDepositData(depositData); + } + }, [denom, isLoaded, price, walletBalances, usdcIbcDenom, minDeposit]); + + return depositData; +}; diff --git a/apps/provider-console/src/hooks/useWhen.ts b/apps/provider-console/src/hooks/useWhen.ts new file mode 100644 index 000000000..66d9a2278 --- /dev/null +++ b/apps/provider-console/src/hooks/useWhen.ts @@ -0,0 +1,10 @@ +import { useEffect } from "react"; + +export function useWhen(condition: T, run: () => void, deps: unknown[] = []): void { + return useEffect(() => { + if (condition) { + run(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [condition, ...deps]); +} diff --git a/apps/provider-console/src/pages/_app.tsx b/apps/provider-console/src/pages/_app.tsx index 5f222b547..5974ff6d3 100644 --- a/apps/provider-console/src/pages/_app.tsx +++ b/apps/provider-console/src/pages/_app.tsx @@ -7,16 +7,37 @@ import { ThemeProvider } from "next-themes"; import { ColorModeProvider } from "@src/context/CustomThemeContext"; import { cn } from "@src/utils/styleUtils"; +import { Provider } from "jotai"; +import { CustomChainProvider } from "@src/context/CustomChainProvider"; +import { SettingsProvider } from "@src/context/SettingsProvider"; +import { WalletProvider } from "@src/context/WalletProvider"; +import { queryClient } from "@src/queries"; +import { PricingProvider } from "@src/context/PricingProvider"; +import { QueryClientProvider } from "react-query"; +import { TooltipProvider } from "@akashnetwork/ui/components"; export default function App({ Component, pageProps }: AppProps) { return (
- - - - - + + + + + + + + + + + + + + + + + + +
); } - diff --git a/apps/provider-console/src/queries/index.ts b/apps/provider-console/src/queries/index.ts new file mode 100644 index 000000000..cc0ccc415 --- /dev/null +++ b/apps/provider-console/src/queries/index.ts @@ -0,0 +1,3 @@ +export * from "./queryClient"; +export * from "./queryKeys"; +export * from "./useMarketData"; \ No newline at end of file diff --git a/apps/provider-console/src/queries/queryClient.ts b/apps/provider-console/src/queries/queryClient.ts new file mode 100644 index 000000000..3ae9b0e60 --- /dev/null +++ b/apps/provider-console/src/queries/queryClient.ts @@ -0,0 +1,5 @@ +import { QueryClient } from "react-query"; + +const queryClient = new QueryClient(); + +export { queryClient }; diff --git a/apps/provider-console/src/queries/queryKeys.ts b/apps/provider-console/src/queries/queryKeys.ts new file mode 100644 index 000000000..84fd26d1c --- /dev/null +++ b/apps/provider-console/src/queries/queryKeys.ts @@ -0,0 +1,49 @@ +export class QueryKeys { + static getFinancialDataKey = () => ["MARKET_DATA"]; + static getDashboardDataKey = () => ["DASHBOARD_DATA"]; + static getAddressNamesKey = (userId: string) => ["ADDRESS_NAMES", userId]; + static getBlocksKey = (limit: number) => ["BLOCKS", limit]; + static getTransactionsKey = (limit: number) => ["TRANSACTIONS", limit]; + static getAddressTransactionsKey = (address: string, skip: number, limit: number) => ["ADDRESS_TRANSACTIONS", address, skip, limit]; + static getAddressDeploymentsKey = (address: string, skip: number, limit: number, reverseSorting: boolean, filters: { [key: string]: string }) => [ + "ADDRESS_DEPLOYMENTS", + address, + skip, + limit, + reverseSorting, + JSON.stringify(filters) + ]; + static getValidatorsKey = () => ["VALIDATORS"]; + static getProposalsKey = () => ["PROPOSALS"]; + static getTemplateKey = (id: string) => ["SDL_TEMPLATES", id]; + static getUserTemplatesKey = (username: string) => ["USER_TEMPLATES", username]; + static getUserFavoriteTemplatesKey = (userId: string) => ["USER_FAVORITES_TEMPLATES", userId]; + static getGranterGrants = (address: string) => ["GRANTER_GRANTS", address]; + static getGranteeGrants = (address: string) => ["GRANTEE_GRANTS", address]; + static getAllowancesIssued = (address: string) => ["ALLOWANCES_ISSUED", address]; + static getAllowancesGranted = (address: string) => ["ALLOWANCES_GRANTED", address]; + + // Deploy + static getDeploymentListKey = (address: string) => ["DEPLOYMENT_LIST", address]; + static getDeploymentDetailKey = (address: string, dseq: string) => ["DEPLOYMENT_DETAIL", address, dseq]; + static getAllLeasesKey = (address: string) => ["ALL_LEASES", address]; + static getLeasesKey = (address: string, dseq: string) => ["LEASE_LIST", address, dseq]; + static getLeaseStatusKey = (dseq: string, gseq: number, oseq: number) => ["LEASE_STATUS", dseq, gseq, oseq]; + static getBidListKey = (address: string, dseq: string) => ["BID_LIST", address, dseq]; + static getBidInfoKey = (address: string, dseq: string, gseq: number, oseq: number, provider: string) => ["BID_INFO", address, dseq, gseq, oseq, provider]; + static getProvidersKey = () => ["PROVIDERS"]; + static getProviderListKey = () => ["PROVIDER_LIST"]; + static getProviderRegionsKey = () => ["PROVIDER_REGIONS"]; + static getProviderDetailKey = (owner: string) => ["PROVIDERS", owner]; + static getDataNodeProvidersKey = () => ["DATA_NODE_PROVIDERS"]; + static getProviderStatusKey = (providerUri: string) => ["PROVIDER_STATUS", providerUri]; + static getNetworkCapacity = () => ["NETWORK_CAPACITY"]; + static getProviderActiveLeasesGraph = (providerAddress: string) => ["PROVIDER_ACTIVE_LEASES_GRAPH", providerAddress]; + static getAuditorsKey = () => ["AUDITORS"]; + static getBlockKey = (id: string) => ["BLOCK", id]; + static getBalancesKey = (address: string) => ["BALANCES", address]; + static getTemplatesKey = () => ["TEMPLATES"]; + static getProviderAttributesSchema = () => ["PROVIDER_ATTRIBUTES_SCHEMA"]; + static getDepositParamsKey = () => ["DEPOSIT_PARAMS"]; + static getGpuModelsKey = () => ["GPU_MODELS"]; +} diff --git a/apps/provider-console/src/queries/useBlocksQuery.ts b/apps/provider-console/src/queries/useBlocksQuery.ts new file mode 100644 index 000000000..65c77c8c3 --- /dev/null +++ b/apps/provider-console/src/queries/useBlocksQuery.ts @@ -0,0 +1,34 @@ +import { QueryKey, useQuery, UseQueryOptions } from "react-query"; +import axios from "axios"; + +import { useSettings } from "@src/context/SettingsProvider"; +import { Block } from "@src/types"; +import { ApiUrlService } from "@src/utils/apiUtils"; +import { QueryKeys } from "./queryKeys"; + +// Block +async function getBlock(apiEndpoint, id) { + const response = await axios.get(ApiUrlService.block(apiEndpoint, id)); + + return response.data; +} + +export function useBlock(id, options = {}) { + const { settings } = useSettings(); + return useQuery(QueryKeys.getBlockKey(id), () => getBlock(settings.apiEndpoint, id), { + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + ...options + }); +} + +async function getBlocks(limit: number): Promise { + const response = await axios.get(ApiUrlService.blocks(limit)); + return response.data; +} + +export function useBlocks(limit: number, options?: Omit, "queryKey" | "queryFn">) { + return useQuery(QueryKeys.getBlocksKey(limit), () => getBlocks(limit), options); +} diff --git a/apps/provider-console/src/queries/useMarketData.ts b/apps/provider-console/src/queries/useMarketData.ts new file mode 100644 index 000000000..879a7dc41 --- /dev/null +++ b/apps/provider-console/src/queries/useMarketData.ts @@ -0,0 +1,15 @@ +import { QueryKey, useQuery, UseQueryOptions } from "react-query"; +import axios from "axios"; + +import { MarketData } from "@src/types"; +import { ApiUrlService } from "@src/utils/apiUtils"; +import { QueryKeys } from "./queryKeys"; + +async function getMarketData(): Promise { + const response = await axios.get(ApiUrlService.marketData()); + return response.data; +} + +export function useMarketData(options?: Omit, "queryKey" | "queryFn">) { + return useQuery(QueryKeys.getFinancialDataKey(), () => getMarketData(), options); +} diff --git a/apps/provider-console/src/queries/useSettings.ts b/apps/provider-console/src/queries/useSettings.ts new file mode 100644 index 000000000..858cad91a --- /dev/null +++ b/apps/provider-console/src/queries/useSettings.ts @@ -0,0 +1,38 @@ +import { useMutation, useQuery } from "react-query"; +import axios, { AxiosResponse } from "axios"; +import { useSnackbar } from "notistack"; + +import { useSettings } from "@src/context/SettingsProvider"; +import { DepositParams, RpcDepositParams } from "@src/types/deployment"; +// import { UserSettings } from "@src/types/user"; +import { ApiUrlService } from "@src/utils/apiUtils"; +import { QueryKeys } from "./queryKeys"; + +// export function useSaveSettings() { +// const { enqueueSnackbar } = useSnackbar(); +// const { checkSession } = useCustomUser(); + +// return useMutation, unknown, UserSettings>(newSettings => axios.put("/api/proxy/user/updateSettings", newSettings), { +// onSuccess: () => { +// enqueueSnackbar("Settings saved", { variant: "success" }); + +// checkSession(); +// }, +// onError: () => { +// enqueueSnackbar("Error saving settings", { variant: "error" }); +// } +// }); +// } + +async function getDepositParams(apiEndpoint: string) { + const depositParamsQuery = await axios.get(ApiUrlService.depositParams(apiEndpoint)); + const depositParams = depositParamsQuery.data as RpcDepositParams; + const params = JSON.parse(depositParams.param.value) as DepositParams[]; + + return params; +} + +export function useDepositParams(options = {}) { + const { settings } = useSettings(); + return useQuery(QueryKeys.getDepositParamsKey(), () => getDepositParams(settings.apiEndpoint), options); +} diff --git a/apps/provider-console/src/store/networkStore.ts b/apps/provider-console/src/store/networkStore.ts new file mode 100644 index 000000000..47c42b489 --- /dev/null +++ b/apps/provider-console/src/store/networkStore.ts @@ -0,0 +1,73 @@ +import axios from "axios"; +import { atom } from "jotai"; + +import { Network } from "@src/types/network"; +import { ApiUrlService, mainnetNodes, sandboxNodes, testnetNodes } from "@src/utils/apiUtils"; +import { mainnetId, sandboxId, testnetId } from "@src/utils/constants"; + +export let networks: Network[] = [ + { + id: mainnetId, + title: "Mainnet", + description: "Akash Network mainnet network.", + nodesUrl: mainnetNodes, + chainId: "akashnet-2", + chainRegistryName: "akash", + versionUrl: ApiUrlService.mainnetVersion(), + rpcEndpoint: "https://rpc.cosmos.directory/akash", + enabled: true, + version: null // Set asynchronously + }, + { + id: testnetId, + title: "GPU Testnet", + description: "Testnet of the new GPU features.", + nodesUrl: testnetNodes, + chainId: "testnet-02", + chainRegistryName: "akash-testnet", + versionUrl: ApiUrlService.testnetVersion(), + rpcEndpoint: "https://rpc.testnet-02.aksh.pw:443", + enabled: false, + version: null // Set asynchronously + }, + { + id: sandboxId, + title: "Sandbox", + description: "Sandbox of the mainnet version.", + nodesUrl: sandboxNodes, + chainId: "sandbox-01", + chainRegistryName: "akash-sandbox", + versionUrl: ApiUrlService.sandboxVersion(), + rpcEndpoint: "https://rpc.sandbox-01.aksh.pw:443", + version: null, // Set asynchronously + enabled: true + } +]; + +/** + * Get the actual versions and metadata of the available networks + */ +export const initiateNetworkData = async () => { + networks = await Promise.all( + networks.map(async network => { + let version = null; + try { + const response = await axios.get(network.versionUrl, { timeout: 10000 }); + version = response.data; + } catch (error) { + console.log(error); + } + + return { + ...network, + version + }; + }) + ); +}; + +const selectedNetwork = atom(networks[0]); + +export default { + selectedNetwork +}; diff --git a/apps/provider-console/src/store/routeStore.ts b/apps/provider-console/src/store/routeStore.ts new file mode 100644 index 000000000..d0988562d --- /dev/null +++ b/apps/provider-console/src/store/routeStore.ts @@ -0,0 +1,7 @@ +import { atom } from "jotai"; + +const previousRoute = atom(null); + +export default { + previousRoute +}; diff --git a/apps/provider-console/src/types/block.ts b/apps/provider-console/src/types/block.ts new file mode 100644 index 000000000..63ab2447a --- /dev/null +++ b/apps/provider-console/src/types/block.ts @@ -0,0 +1,28 @@ +import { TransactionMessage } from "./transaction"; +import { IValidatorAddess } from "./validator"; + +export interface Block { + datetime: string; + height: number; + proposer: IValidatorAddess; + transactionCount: number; +} + +export interface BlockDetail { + height: number; + proposer: IValidatorAddess; + datetime: string; + hash: string; + gasUsed: number; + gasWanted: number; + transactions: BlockTransaction[]; +} + +export interface BlockTransaction { + hash: string; + isSuccess: boolean; + error: string; + fee: number; + datetime: string; + messages: TransactionMessage[]; +} diff --git a/apps/provider-console/src/types/coin.ts b/apps/provider-console/src/types/coin.ts new file mode 100644 index 000000000..65fb504ed --- /dev/null +++ b/apps/provider-console/src/types/coin.ts @@ -0,0 +1,4 @@ +export interface Coin { + denom: string; + amount: number; +} diff --git a/apps/provider-console/src/types/dashboard.ts b/apps/provider-console/src/types/dashboard.ts new file mode 100644 index 000000000..8a2b008d4 --- /dev/null +++ b/apps/provider-console/src/types/dashboard.ts @@ -0,0 +1,113 @@ +import { Block } from "./block"; +import { TransactionDetail } from "./transaction"; + +export interface RevenueAmount { + akt: number; + uakt: number; + usd: number; +} + +export interface SpentStats { + amountAkt: number; + amountUAkt: number; + amountUSD: number; + revenueLast24: RevenueAmount; + revenuePrevious24: RevenueAmount; +} + +export interface DashboardBlockStats { + date: Date; + height: number; + activeLeaseCount: number; + totalLeaseCount: number; + dailyLeaseCount: number; + totalUAktSpent: number; + dailyUAktSpent: number; + totalUUsdcSpent: number; + dailyUUsdcSpent: number; + totalUUsdSpent: number; + dailyUUsdSpent: number; + activeCPU: number; + activeGPU: number; + activeMemory: number; + activeStorage: number; +} + +export interface DashboardNetworkCapacityState { + count: number; + cpu: number; + gpu: number; + memory: number; + storage: number; +} + +export interface NetworkCapacity { + activeProviderCount: number; + activeCPU: number; + activeGPU: number; + activeMemory: number; + activeStorage: number; + pendingCPU: number; + pendingGPU: number; + pendingMemory: number; + pendingStorage: number; + availableCPU: number; + availableGPU: number; + availableMemory: number; + availableStorage: number; + totalCPU: number; + totalGPU: number; + totalMemory: number; + totalStorage: number; +} + +export interface DashboardData { + chainStats: { + bondedTokens: number; + communityPool: number; + height: number; + inflation: number; + stakingAPR: number; + totalSupply: number; + transactionCount: number; + }; + now: DashboardBlockStats; + compare: DashboardBlockStats; + networkCapacity: NetworkCapacity; + networkCapacityStats: { + now: DashboardNetworkCapacityState; + compare: DashboardNetworkCapacityState; + }; + latestBlocks: Block[]; + latestTransactions: TransactionDetail[]; +} + +export interface SnapshotData { + minActiveDeploymentCount: number; + maxActiveDeploymentCount: number; + minCompute: number; + maxCompute: number; + minMemory: number; + maxMemory: number; + minStorage: number; + maxStorage: number; + allTimeDeploymentCount: number; + totalAktSpent: number; + dailyAktSpent: number; + dailyDeploymentCount: number; +} + +export interface MarketData { + price: number; + volume: number; + marketCap: number; + marketCapRank: number; + priceChange24h: number; + priceChangePercentage24: number; +} + +export interface ISnapshotMetadata { + value: number; + unit?: string; + modifiedValue?: number; +} diff --git a/apps/provider-console/src/types/deployment.ts b/apps/provider-console/src/types/deployment.ts new file mode 100644 index 000000000..e4a9f6edb --- /dev/null +++ b/apps/provider-console/src/types/deployment.ts @@ -0,0 +1,335 @@ +export interface DeploymentDetail { + owner: string; + dseq: string; + balance: number; + status: string; + denom: string; + totalMonthlyCostUDenom: number; + leases: { + oseq: number; + gseq: number; + status: string; + monthlyCostUDenom: number; + cpuUnits: number; + gpuUnits: number; + memoryQuantity: number; + storageQuantity: number; + provider: { + address: string; + hostUri: string; + isDeleted: boolean; + attributes: { + key: string; + value: string; + }[]; + }; + }[]; + events: { + txHash: string; + date: string; + type: string; + }[]; +} + +export interface DeploymentSummary { + owner: string; + dseq: string; + status: string; + createdHeight: number; + cpuUnits: number; + gpuUnits: number; + memoryQuantity: number; + storageQuantity: number; +} + +export interface RpcDeployment { + deployment: { + deployment_id: { + owner: string; + dseq: string; + }; + state: string; + version: string; + created_at: string; + }; + groups: Array; + escrow_account: EscrowAccount; +} + +type DeploymentGroup = DeploymentGroup_v2 | DeploymentGroup_v3; + +interface DeploymentResource_V2 { + cpu: { + units: { + val: string; + }; + attributes: { key: string; value: string }[]; + }; + gpu: { + units: { + val: string; + }; + attributes: { key: string; value: string }[]; + }; + memory: { + quantity: { + val: string; + }; + attributes: { key: string; value: string }[]; + }; + storage: Array<{ + name: string; + quantity: { + val: string; + }; + attributes: { key: string; value: string }[]; + }>; + endpoints: Array<{ + kind: string; + sequence_number: number; + }>; +} +interface DeploymentResource_V3 extends DeploymentResource_V2 {} + +interface DeploymentGroup_v2 { + group_id: { + owner: string; + dseq: string; + gseq: number; + }; + state: string; + group_spec: { + name: string; + requirements: { + signed_by: { + all_of: string[]; + any_of: string[]; + }; + attributes: Array<{ + key: string; + value: string; + }>; + }; + resources: Array<{ + resources: DeploymentResource_V2; + count: number; + price: { + denom: string; + amount: string; + }; + }>; + }; + created_at: string; +} + +interface DeploymentGroup_v3 { + group_id: { + owner: string; + dseq: string; + gseq: number; + }; + state: string; + group_spec: { + name: string; + requirements: { + signed_by: { + all_of: string[]; + any_of: string[]; + }; + attributes: Array<{ + key: string; + value: string; + }>; + }; + resources: Array<{ + resource: DeploymentResource_V3; + count: number; + price: { + denom: string; + amount: string; + }; + }>; + }; + created_at: string; +} + +interface EscrowAccount { + id: { + scope: string; + xid: string; + }; + owner: string; + state: string; + balance: { + denom: string; + amount: string; + }; + transferred: { + denom: string; + amount: string; + }; + settled_at: string; + depositor: string; + funds: { + denom: string; + amount: string; + }; +} + +export interface DeploymentDto { + dseq: string; + state: string; + version: string; + denom: string; + createdAt: number; + escrowBalance: number; + transferred: { + denom: string; + amount: string; + }; + cpuAmount: number; + gpuAmount?: number; + memoryAmount: number; + storageAmount: number; + escrowAccount: EscrowAccount; + groups: Array; +} + +export interface NamedDeploymentDto extends DeploymentDto { + name: string; +} + +export interface RpcLease { + lease: { + lease_id: { + owner: string; + dseq: string; + gseq: number; + oseq: number; + provider: string; + }; + state: string; + price: { + denom: string; + amount: string; + }; + created_at: string; + closed_on: string; + }; + escrow_payment: { + account_id: { + scope: string; + xid: string; + }; + payment_id: string; + owner: string; + state: string; + rate: { + denom: string; + amount: string; + }; + balance: { + denom: string; + amount: string; + }; + withdrawn: { + denom: string; + amount: string; + }; + }; +} + +export interface LeaseDto { + id: string; + owner: string; + provider: string; + dseq: string; + gseq: number; + oseq: number; + state: string; + price: { + denom: string; + amount: string; + }; + cpuAmount: number; + gpuAmount?: number; + memoryAmount: number; + storageAmount: number; + group: DeploymentGroup; +} + +export interface RpcBid { + bid: { + bid_id: { + owner: string; + dseq: string; + gseq: number; + oseq: number; + provider: string; + }; + state: string; + price: { + denom: string; + amount: string; + }; + created_at: string; + resources_offer: Array<{ + resources: DeploymentResource_V3; + count: number; + }>; + }; + escrow_account: { + id: { + scope: string; + xid: string; + }; + owner: string; + state: string; + balance: { + denom: string; + amount: string; + }; + transferred: { + denom: string; + amount: string; + }; + settled_at: string; + depositor: string; + funds: { + denom: string; + amount: string; + }; + }; +} + +export interface BidDto { + id: string; + owner: string; + provider: string; + dseq: string; + gseq: number; + oseq: number; + price: { + denom: string; + amount: string; + }; + state: string; + resourcesOffer: Array<{ + resources: DeploymentResource_V3; + count: number; + }>; +} + +export interface RpcDepositParams { + param: { + subspace: string; + key: string; + // Array of { denom: string, amount: string } + value: string; + }; +} + +export interface DepositParams { + denom: string; + amount: string; +} diff --git a/apps/provider-console/src/types/index.ts b/apps/provider-console/src/types/index.ts index 8b9c950b8..2242572e6 100644 --- a/apps/provider-console/src/types/index.ts +++ b/apps/provider-console/src/types/index.ts @@ -1,7 +1,7 @@ -// export * from "./dashboard"; -// export * from "./block"; -// export * from "./transaction"; -// export * from "./coin"; +export * from "./dashboard"; +export * from "./block"; +export * from "./transaction"; +export * from "./coin"; // export * from "./address"; // export * from "./snapshots"; // export * from "./sdlBuilder"; diff --git a/apps/provider-console/src/types/network.ts b/apps/provider-console/src/types/network.ts new file mode 100644 index 000000000..60f59ebe0 --- /dev/null +++ b/apps/provider-console/src/types/network.ts @@ -0,0 +1,14 @@ +import { NetworkId } from "@akashnetwork/akashjs/build/types/network"; + +export type Network = { + id: NetworkId; + title: string; + description: string; + nodesUrl: string; + chainId: string; + chainRegistryName: string; + versionUrl: string; + rpcEndpoint?: string; + version: string | null; + enabled: boolean; +}; diff --git a/apps/provider-console/src/types/node.ts b/apps/provider-console/src/types/node.ts new file mode 100644 index 000000000..f872dbf72 --- /dev/null +++ b/apps/provider-console/src/types/node.ts @@ -0,0 +1,38 @@ +export interface NodeStatus { + node_info: { + protocol_version: { + p2p: string; + block: string; + app: string; + }; + id: string; + listen_addr: string; + network: string; + version: string; + channels: string; + moniker: string; + other: { + tx_index: string; + rpc_address: string; + }; + }; + sync_info: { + latest_block_hash: string; + latest_app_hash: string; + latest_block_height: string; + latest_block_time: string; + earliest_block_hash: string; + earliest_app_hash: string; + earliest_block_height: string; + earliest_block_time: string; + catching_up: boolean; + }; + validator_info: { + address: string; + pub_key: { + type: string; + value: string; + }; + voting_power: string; + }; +} diff --git a/apps/provider-console/src/types/transaction.ts b/apps/provider-console/src/types/transaction.ts new file mode 100644 index 000000000..0f001a450 --- /dev/null +++ b/apps/provider-console/src/types/transaction.ts @@ -0,0 +1,22 @@ +export interface TransactionDetail { + height: number; + datetime: string; + hash: string; + isSuccess: boolean; + error: string; + gasUsed: number; + gasWanted: number; + fee: number; + memo: string; + multisigThreshold: number; + signers: string[]; + messages: TransactionMessage[]; +} + +export interface TransactionMessage { + id: string; + type: string; + data?: any; + isReceiver?: boolean; + amount?: number; +} diff --git a/apps/provider-console/src/types/validator.ts b/apps/provider-console/src/types/validator.ts new file mode 100644 index 000000000..0b15602c1 --- /dev/null +++ b/apps/provider-console/src/types/validator.ts @@ -0,0 +1,33 @@ +export interface ValidatorSummaryDetail { + rank: number; + operatorAddress: string; + keybaseAvatarUrl?: string; + moniker: string; + votingPower: number; + votingPowerRatio: number; + commission: number; + identity: string; +} + +export interface ValidatorDetail { + rank: number; + operatorAddress: string; + address: string; + keybaseAvatarUrl?: string; + keybaseUsername?: string; + moniker: string; + votingPower: number; + commission: number; + maxCommission: number; + maxCommissionChange: number; + identity: string; + description: string; + website: string; +} + +export interface IValidatorAddess { + address: string; + operatorAddress: string; + moniker: string; + avatarUrl: string; +} diff --git a/apps/provider-console/src/utils/apiUtils.ts b/apps/provider-console/src/utils/apiUtils.ts new file mode 100644 index 000000000..643df8277 --- /dev/null +++ b/apps/provider-console/src/utils/apiUtils.ts @@ -0,0 +1,33 @@ +import axios from "axios"; +import { BASE_API_URL } from "./constants"; +export class ApiUrlService { + static mainnetVersion() { + return `0.36.0`; + } + static testnetVersion() { + return `0.36.0`; + } + static sandboxVersion() { + return `0.36.0`; + } + + static mainnetNodes() { + return `${BASE_API_URL}/v1/nodes/mainnet`; + } + static testnetNodes() { + return `${BASE_API_URL}/v1/nodes/testnet`; + } + static sandboxNodes() { + return `${BASE_API_URL}/v1/nodes/sandbox`; + } + static depositParams(apiEndpoint: string) { + return `${apiEndpoint}/cosmos/params/v1beta1/params?subspace=deployment&key=MinDeposits`; + } + static marketData() { + return `${BASE_API_URL}/v1/market-data`; + } +} + +export const mainnetNodes = ApiUrlService.mainnetNodes(); +export const testnetNodes = ApiUrlService.testnetNodes(); +export const sandboxNodes = ApiUrlService.sandboxNodes(); \ No newline at end of file diff --git a/apps/provider-console/src/utils/authClient.ts b/apps/provider-console/src/utils/authClient.ts new file mode 100644 index 000000000..45f4ed5ff --- /dev/null +++ b/apps/provider-console/src/utils/authClient.ts @@ -0,0 +1,41 @@ +// import { notification } from 'antd' +import axios from "axios"; + +const errorNotification = (error = "Error Occurred") => { + // notification.error({ + // message: error, + // }) + console.log(error); +}; + +const authClient = axios.create({ + baseURL: `https://knight-dev.testcoders.com`, + timeout: 30000 +}); + +authClient.interceptors.response.use( + response => { + return response.data; + }, + error => { + // whatever you want to do with the error + if (typeof error.response === "undefined") { + errorNotification("Server is not reachable or CORS is not enable on the server!"); + } else if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + errorNotification("Server Error!"); + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + errorNotification("Server is not responding!"); + } else { + // Something happened in setting up the request that triggered an Error + errorNotification(error.message); + } + throw error; + } +); + +export default authClient; diff --git a/apps/provider-console/src/utils/constants.ts b/apps/provider-console/src/utils/constants.ts index c92adb807..e3ff5d8de 100644 --- a/apps/provider-console/src/utils/constants.ts +++ b/apps/provider-console/src/utils/constants.ts @@ -2,12 +2,71 @@ export const mainnetId = "mainnet"; export const testnetId = "testnet"; export const sandboxId = "sandbox"; +const productionHostnames = ["deploy.cloudmos.io", "console.akash.network", "staging-console.akash.network", "beta.cloudmos.io"]; + export const selectedRangeValues: { [key: string]: number } = { "7D": 7, "1M": 30, ALL: Number.MAX_SAFE_INTEGER }; + +const productionMainnetApiUrl = "https://api.cloudmos.io"; +const productionTestnetApiUrl = "https://api-testnet.cloudmos.io"; +const productionSandboxApiUrl = "https://api-sandbox.cloudmos.io"; +export const BASE_API_MAINNET_URL = getApiMainnetUrl(); +export const BASE_API_TESTNET_URL = getApiTestnetUrl(); +export const BASE_API_SANDBOX_URL = getApiSandboxUrl(); +export const BASE_API_URL = getApiUrl(); + + +function getApiMainnetUrl() { + if (process.env.API_MAINNET_BASE_URL) return process.env.API_MAINNET_BASE_URL; + if (typeof window === "undefined") return "http://localhost:3080"; + if (productionHostnames.includes(window.location?.hostname)) return productionMainnetApiUrl; + return "http://localhost:3080"; +} + +function getApiTestnetUrl() { + if (process.env.API_TESTNET_BASE_URL) return process.env.API_TESTNET_BASE_URL; + if (typeof window === "undefined") return "http://localhost:3080"; + if (productionHostnames.includes(window.location?.hostname)) return productionTestnetApiUrl; + return "http://localhost:3080"; +} + +function getApiSandboxUrl() { + if (process.env.API_SANDBOX_BASE_URL) return process.env.API_SANDBOX_BASE_URL; + if (typeof window === "undefined") return "http://localhost:3080"; + if (productionHostnames.includes(window.location?.hostname)) return productionSandboxApiUrl; + return "http://localhost:3080"; +} + +export function getNetworkBaseApiUrl(network: string) { + switch (network) { + case testnetId: + return BASE_API_TESTNET_URL; + case sandboxId: + return BASE_API_SANDBOX_URL; + default: + return BASE_API_MAINNET_URL; + } +} + +function getApiUrl() { + if (process.env.API_BASE_URL) return process.env.API_BASE_URL; + if (typeof window === "undefined") return "http://localhost:3080"; + if (productionHostnames.includes(window.location?.hostname)) { + try { + const _selectedNetworkId = localStorage.getItem("selectedNetworkId"); + return getNetworkBaseApiUrl(_selectedNetworkId || mainnetId); + } catch (e) { + console.error(e); + return productionMainnetApiUrl; + } + } + return "http://localhost:3080"; +} + // UI export const statusBarHeight = 30; export const drawerWidth = 240; diff --git a/apps/provider-console/src/utils/customRegistry.ts b/apps/provider-console/src/utils/customRegistry.ts new file mode 100644 index 000000000..590b07e69 --- /dev/null +++ b/apps/provider-console/src/utils/customRegistry.ts @@ -0,0 +1,2 @@ +import { Registry } from "@cosmjs/proto-signing"; +export const customRegistry = new Registry(); \ No newline at end of file diff --git a/apps/provider-console/src/utils/dateUtils.ts b/apps/provider-console/src/utils/dateUtils.ts new file mode 100644 index 000000000..e6bb0a6b4 --- /dev/null +++ b/apps/provider-console/src/utils/dateUtils.ts @@ -0,0 +1,56 @@ +import { roundDecimal } from "./mathHelpers"; + +export const averageDaysInMonth = 30.437; + +export const epochToDate = (epoch: number) => { + // The 0 sets the date to the epoch + const d = new Date(0); + d.setUTCSeconds(epoch); + + return d; +}; + +export const getDayStr = (date?: Date) => { + return date ? toUTC(date).toISOString().split("T")[0] : getTodayUTC().toISOString().split("T")[0]; +}; + +export function getTodayUTC() { + const currentDate = toUTC(new Date()); + currentDate.setUTCHours(0, 0, 0, 0); + + return currentDate; +} + +export function startOfDay(date: Date) { + const currentDate = toUTC(date); + currentDate.setUTCHours(0, 0, 0, 0); + + return currentDate; +} + +export function endOfDay(date: Date) { + const currentDate = toUTC(date); + currentDate.setUTCHours(23, 59, 59, 999); + + return currentDate; +} + +export function toUTC(date: Date) { + const now_utc = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); + + return new Date(now_utc); +} + +export function getPrettyTime(timeMs: number): string { + if (timeMs < 10) { + return `${roundDecimal(timeMs, 2)}ms`; + } else if (timeMs < 1_000) { + return `${roundDecimal(timeMs, 0)}ms`; + } else if (timeMs < 60 * 1_000) { + return `${roundDecimal(timeMs / 1_000, 2)}s`; + } else if (timeMs < 60 * 60 * 1_000) { + return `${Math.floor(timeMs / 1_000 / 60)}m ${roundDecimal((timeMs / 1000) % 60, 2)}s`; + } else { + return `${Math.floor(timeMs / 1_000 / 60 / 60)}h ${roundDecimal(timeMs / 1_000 / 60, 2) % 60}m`; + } +} diff --git a/apps/provider-console/src/utils/init.ts b/apps/provider-console/src/utils/init.ts new file mode 100644 index 000000000..2f18b4b3a --- /dev/null +++ b/apps/provider-console/src/utils/init.ts @@ -0,0 +1,7 @@ +import { setNetworkVersion } from "./constants"; +import { initProtoTypes } from "./proto"; + +export const initAppTypes = () => { + setNetworkVersion(); + initProtoTypes(); +}; diff --git a/apps/provider-console/src/utils/mathHelpers.ts b/apps/provider-console/src/utils/mathHelpers.ts new file mode 100644 index 000000000..93648bbdd --- /dev/null +++ b/apps/provider-console/src/utils/mathHelpers.ts @@ -0,0 +1,63 @@ +import { Coin } from "@src/types"; + +export function nFormatter(num: number, digits: number) { + const lookup = [ + { value: 1, symbol: "" }, + { value: 1e3, symbol: "k" }, + { value: 1e6, symbol: "M" }, + { value: 1e9, symbol: "G" }, + { value: 1e12, symbol: "T" }, + { value: 1e15, symbol: "P" }, + { value: 1e18, symbol: "E" } + ]; + const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; + const item = lookup + .slice() + .reverse() + .find(function (item) { + return num >= item.value; + }); + return item ? (num / item.value).toFixed(digits).replace(rx, "$1") + item.symbol : "0"; +} + +export function udenomToDenom(_amount: string | number, precision = 6, decimals: number = 1_000_000) { + const amount = typeof _amount === "string" ? parseFloat(_amount) : _amount; + return roundDecimal(amount / decimals, precision); +} + +export function denomToUdenom(amount: number, decimals: number = 1_000_000) { + return amount * decimals; +} + +export function randomInteger(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +export function roundDecimal(value: number, precision = 2) { + const multiplier = Math.pow(10, precision || 0); + return Math.round((value + Number.EPSILON) * multiplier) / multiplier; +} + +export function ceilDecimal(value: number) { + return Math.ceil((value + Number.EPSILON) * 1000) / 1000; +} + +export function coinsToAmount(coins: Coin[] | Coin, denom: string) { + const currentCoin = (coins as any).length !== undefined ? (coins as Coin[]).find(c => c.denom === denom) : (coins as Coin); + if (!currentCoin) return 0; + else return currentCoin.amount; +} + +export function percIncrease(a: number, b: number) { + let percent: number; + if (b !== 0) { + if (a !== 0) { + percent = (b - a) / a; + } else { + percent = b; + } + } else { + percent = -a; + } + return roundDecimal(percent, 4); +} \ No newline at end of file diff --git a/apps/provider-console/src/utils/priceUtils.ts b/apps/provider-console/src/utils/priceUtils.ts new file mode 100644 index 000000000..3df05cd34 --- /dev/null +++ b/apps/provider-console/src/utils/priceUtils.ts @@ -0,0 +1,83 @@ +import { Coin } from "@cosmjs/stargate"; +import add from "date-fns/add"; + +import { getUsdcDenom } from "@src/hooks/useDenom"; +import { useBlock } from "@src/queries/useBlocksQuery"; +import { readableDenoms, uAktDenom } from "./constants"; +import { averageDaysInMonth } from "./dateUtils"; +import { denomToUdenom } from "./mathHelpers"; + +export const averageBlockTime = 6.098; + +export function uaktToAKT(amount: number, precision: number = 3) { + return Math.round((amount / 1000000 + Number.EPSILON) * Math.pow(10, precision)) / Math.pow(10, precision); +} + +export function aktToUakt(amount: number | string) { + return Math.round((typeof amount === "string" ? parseFloat(amount) : amount) * 1_000_000); +} + +export function coinToUDenom(coin: Coin) { + let value: number | null = null; + const usdcDenom = getUsdcDenom(); + + if (coin.denom === "akt") { + value = denomToUdenom(parseFloat(coin.amount)); + } else if (coin.denom === uAktDenom || coin.denom === usdcDenom) { + value = parseFloat(coin.amount); + } else { + throw Error("Unrecognized denom: " + coin.denom); + } + + return value; +} + +export function coinToDenom(coin: Coin) { + let value: number | null = null; + const usdcDenom = getUsdcDenom(); + + if (coin.denom === "akt") { + value = parseFloat(coin.amount); + } else if (coin.denom === uAktDenom || coin.denom === usdcDenom) { + value = uaktToAKT(parseFloat(coin.amount), 6); + } else { + throw Error("Unrecognized denom: " + coin.denom); + } + + return value; +} + +export function getAvgCostPerMonth(pricePerBlock: number) { + const averagePrice = (pricePerBlock * averageDaysInMonth * 24 * 60 * 60) / averageBlockTime; + return averagePrice; +} + +export function getTimeLeft(pricePerBlock: number, balance: number) { + const blocksLeft = balance / pricePerBlock; + const timestamp = new Date().getTime(); + return add(new Date(timestamp), { seconds: blocksLeft * averageBlockTime }); +} + +export function useRealTimeLeft(pricePerBlock: number, balance: number, settledAt: number, createdAt: number) { + const { data: latestBlock } = useBlock("latest", { + refetchInterval: 30000 + }); + if (!latestBlock) return; + + const latestBlockHeight = latestBlock.block.header.height; + const blocksPassed = Math.abs(settledAt - latestBlockHeight); + const blocksSinceCreation = Math.abs(createdAt - latestBlockHeight); + + const blocksLeft = balance / pricePerBlock - blocksPassed; + const timestamp = new Date().getTime(); + + return { + timeLeft: add(new Date(timestamp), { seconds: blocksLeft * averageBlockTime }), + escrow: Math.max(blocksLeft * pricePerBlock, 0), + amountSpent: Math.min(blocksSinceCreation * pricePerBlock, balance) + }; +} + +export function toReadableDenom(denom: string) { + return readableDenoms[denom]; +} diff --git a/apps/provider-console/src/utils/proto/grant.ts b/apps/provider-console/src/utils/proto/grant.ts new file mode 100644 index 000000000..f5424a577 --- /dev/null +++ b/apps/provider-console/src/utils/proto/grant.ts @@ -0,0 +1,3 @@ +export { MsgGrantAllowance, MsgRevokeAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx"; +export { BasicAllowance, PeriodicAllowance, AllowedMsgAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant"; +export { MsgGrant, MsgRevoke } from "cosmjs-types/cosmos/authz/v1beta1/tx"; diff --git a/apps/provider-console/src/utils/proto/index.ts b/apps/provider-console/src/utils/proto/index.ts new file mode 100644 index 000000000..78e217a43 --- /dev/null +++ b/apps/provider-console/src/utils/proto/index.ts @@ -0,0 +1,29 @@ +import * as v1beta3 from "@akashnetwork/akash-api/v1beta3"; +import * as v1beta4 from "@akashnetwork/akash-api/v1beta4"; + +import { mainnetId, sandboxId, testnetId } from "../constants"; + +const commonTypes = { ...v1beta3, ...v1beta4 }; +const mainnetTypes = commonTypes; +const sandboxTypes = commonTypes; + +export let protoTypes; + +export function initProtoTypes() { + const selectedNetworkId = localStorage.getItem("selectedNetworkId"); + + switch (selectedNetworkId) { + case mainnetId: + case testnetId: + protoTypes = mainnetTypes; + break; + + case sandboxId: + protoTypes = sandboxTypes; + break; + + default: + protoTypes = mainnetTypes; + break; + } +} diff --git a/apps/provider-console/src/utils/restClient.ts b/apps/provider-console/src/utils/restClient.ts new file mode 100644 index 000000000..583dae141 --- /dev/null +++ b/apps/provider-console/src/utils/restClient.ts @@ -0,0 +1,96 @@ +// import { notification } from "antd"; +import axios from "axios"; +import authClient from "./authClient"; + +const errorNotification = (error = "Error Occurred") => { + console.log(error); +}; + +const restClient = axios.create({ + baseURL: `${process.env.REACT_APP_BACKEND_URL}`, + timeout: 60000 +}); + +restClient.interceptors.response.use( + response => { + return response.data; + }, + error => { + // whatever you want to do with the error + if (typeof error.response === "undefined") { + errorNotification("Server is not reachable or CORS is not enable on the server!"); + } else if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + + const originalRequest = error.config; + + if (error.response.status === 401 && error.response.data.detail === "Signature has expired" && !originalRequest.retry) { + originalRequest.retry = true; + + + // TODO Refresh Token Login Goes here + // if (window.refreshingToken) { + // setTimeout(() => { + // originalRequest.headers.Authorization = `Bearer ${localStorage.getItem("accessToken")}`; + // return restClient.request(originalRequest); + // }, 1500); + // } else { + // window.refreshingToken = true; + // return authClient + // .post("/auth/refresh", { + // refresh_token: localStorage.getItem("refreshToken"), + // address: localStorage.getItem("walletAddress") + // }) + // .then(res => { + // if (res.status === "success") { + // // 1) put token to LocalStorage + // localStorage.setItem("accessToken", res.data.access_token); + // localStorage.setItem("refreshToken", res.data.refresh_token); + // window.refreshingToken = false; + // // 2) Change Authorization header + // originalRequest.headers.Authorization = `Bearer ${getStorageItem("accessToken")}`; + // // 3) return originalRequest object with Axios. + // return restClient.request(originalRequest); + // } + // if (res.status === "error") { + // // purgeStorage(); + // localStorage.removeItem("accessToken"); + // localStorage.removeItem("refreshToken"); + // // history.push("/auth/login"); + // } + // return false; + // }); + // } + + + } + + if (error.response.status === 401 && error.response.data.detail !== "Signature has expired") { + // purgeStorage(); + + localStorage.removeItem("accessToken"); + localStorage.removeItem("refreshToken"); + // history.push("/auth/login"); + } + + errorNotification("Server Error!"); + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + errorNotification("Server is not responding!"); + } else { + // Something happened in setting up the request that triggered an Error + errorNotification(error.message); + } + throw error; + } +); + +restClient.interceptors.request.use(async request => { + request.headers.Authorization = `Bearer ${localStorage.getItem("accessToken")}`; + request.headers["ngrok-skip-browser-warning"] = "69420"; + return request; +}); +export default restClient; diff --git a/apps/provider-console/src/utils/walletUtils.ts b/apps/provider-console/src/utils/walletUtils.ts new file mode 100644 index 000000000..8daa3ba7f --- /dev/null +++ b/apps/provider-console/src/utils/walletUtils.ts @@ -0,0 +1,75 @@ +import { mainnetId } from "./constants"; + +export type LocalWalletDataType = { + address: string; + cert?: string; + certKey?: string; + name: string; + selected: boolean; +}; + +export const useStorageWallets = () => { + const wallets = getStorageWallets(); + + return { wallets }; +}; + +export function getSelectedStorageWallet() { + const wallets = getStorageWallets(); + + return wallets.find(w => w.selected) ?? wallets[0] ?? null; +} + +export function getStorageWallets() { + const selectedNetworkId = localStorage.getItem("selectedNetworkId") || mainnetId; + const wallets = JSON.parse(localStorage.getItem(`${selectedNetworkId}/wallets`) || "[]") as LocalWalletDataType[]; + + return wallets || []; +} + +export function updateWallet(address: string, func: (w: LocalWalletDataType) => LocalWalletDataType) { + const wallets = getStorageWallets(); + let wallet = wallets.find(w => w.address === address); + + if (wallet) { + wallet = func(wallet); + + const newWallets = wallets.map(w => (w.address === address ? (wallet as LocalWalletDataType) : w)); + updateStorageWallets(newWallets); + } +} + +export function updateStorageWallets(wallets: LocalWalletDataType[]) { + const selectedNetworkId = localStorage.getItem("selectedNetworkId") || mainnetId; + localStorage.setItem(`${selectedNetworkId}/wallets`, JSON.stringify(wallets)); +} + +export function deleteWalletFromStorage(address: string, deleteDeployments: boolean) { + const selectedNetworkId = localStorage.getItem("selectedNetworkId") || mainnetId; + const wallets = getStorageWallets(); + const newWallets = wallets.filter(w => w.address !== address).map((w, i) => ({ ...w, selected: i === 0 })); + + updateStorageWallets(newWallets); + + localStorage.removeItem(`${selectedNetworkId}/${address}/settings`); + localStorage.removeItem(`${selectedNetworkId}/${address}/provider.data`); + + if (deleteDeployments) { + const deploymentKeys = Object.keys(localStorage).filter(key => key.startsWith(`${selectedNetworkId}/${address}/deployments/`)); + for (const deploymentKey of deploymentKeys) { + localStorage.removeItem(deploymentKey); + } + } + + return newWallets; +} + +export function useSelectedWalletFromStorage() { + return getSelectedStorageWallet(); +} + +export function updateLocalStorageWalletName(address: string, name: string) { + updateWallet(address, wallet => { + return { ...wallet, name }; + }); +} diff --git a/package-lock.json b/package-lock.json index 73010567b..476421dd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -591,12 +591,19 @@ "version": "0.1.0", "dependencies": { "@akashnetwork/ui": "*", + "@cosmos-kit/cosmostation-extension": "^2.12.2", + "@cosmos-kit/keplr": "^2.12.2", + "@cosmos-kit/leap-extension": "^2.12.2", + "@cosmos-kit/react": "^2.18.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "jotai": "^2.9.0", + "jwt-decode": "^4.0.0", "lucide-react": "^0.395.0", "next": "14.2.4", "react": "^18", "react-dom": "^18", + "react-query": "^3.39.3", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "ts-loader": "^9.5.1" @@ -614,6 +621,103 @@ "typescript": "^5" } }, + "apps/provider-console/node_modules/@chain-registry/keplr": { + "version": "1.68.2", + "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.2.tgz", + "integrity": "sha512-H3rdf/cLx7bNyyKo+1nI9HpLTlLzyeqi0Rmt+ggwtFRC63ZmDaMg/3vPY4rHvu38OdcaOid4Nyfc+7h3EEPW8Q==", + "dependencies": { + "@chain-registry/types": "^0.45.1", + "@keplr-wallet/cosmos": "0.12.28", + "@keplr-wallet/crypto": "0.12.28", + "semver": "^7.5.0" + } + }, + "apps/provider-console/node_modules/@chain-registry/types": { + "version": "0.45.22", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.22.tgz", + "integrity": "sha512-6q8/n39/oqjaF2tviKrOTgaxzDlXekPi4T96iceMOBF4tcN8jhcVHq9J3FczpRNeRPL4jpa7w82jRaYiX/LYVg==" + }, + "apps/provider-console/node_modules/@cosmjs/crypto": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.32.4.tgz", + "integrity": "sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==", + "peer": true, + "dependencies": { + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.4", + "libsodium-wrappers-sumo": "^0.7.11" + } + }, + "apps/provider-console/node_modules/@cosmjs/encoding": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.32.4.tgz", + "integrity": "sha512-tjvaEy6ZGxJchiizzTn7HVRiyTg1i4CObRRaTRPknm5EalE13SV+TCHq38gIDfyUeden4fCuaBVEdBR5+ti7Hw==", + "peer": true, + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "apps/provider-console/node_modules/@cosmjs/math": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.32.4.tgz", + "integrity": "sha512-++dqq2TJkoB8zsPVYCvrt88oJWsy1vMOuSOKcdlnXuOA/ASheTJuYy4+oZlTQ3Fr8eALDLGGPhJI02W2HyAQaw==", + "peer": true, + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "apps/provider-console/node_modules/@cosmjs/proto-signing": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.32.4.tgz", + "integrity": "sha512-QdyQDbezvdRI4xxSlyM1rSVBO2st5sqtbEIl3IX03uJ7YiZIQHyv6vaHVf1V4mapusCqguiHJzm4N4gsFdLBbQ==", + "peer": true, + "dependencies": { + "@cosmjs/amino": "^0.32.4", + "@cosmjs/crypto": "^0.32.4", + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "cosmjs-types": "^0.9.0" + } + }, + "apps/provider-console/node_modules/@cosmos-kit/cosmostation-extension": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-extension/-/cosmostation-extension-2.12.2.tgz", + "integrity": "sha512-8+DTbm8t3PkHPoQ2c+vssrCR5rIqt6mPedyxGxsd1d4/H8RiZhkxtGIen+oDaGlLe62V6CD7AkLQ+I9HkSNzQA==", + "dependencies": { + "@chain-registry/cosmostation": "^1.66.2", + "@cosmos-kit/core": "^2.13.1", + "cosmjs-types": "^0.9.0" + }, + "peerDependencies": { + "@cosmjs/amino": ">=0.32.3", + "@cosmjs/proto-signing": ">=0.32.3" + } + }, + "apps/provider-console/node_modules/@cosmos-kit/leap-extension": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-extension/-/leap-extension-2.12.2.tgz", + "integrity": "sha512-IB6+kEUgSxp2FeQwtCN6JlZu8RVn3/EeOxn7TNfbarMi2nP9sAWkclI8Pv4RI7i4Mp5iRFoCokpx4mCBYrQGVQ==", + "dependencies": { + "@chain-registry/keplr": "1.68.2", + "@cosmos-kit/core": "^2.13.1" + }, + "peerDependencies": { + "@cosmjs/amino": ">=0.32.3", + "@cosmjs/proto-signing": ">=0.32.3" + } + }, + "apps/provider-console/node_modules/cosmjs-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.9.0.tgz", + "integrity": "sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==" + }, "apps/provider-console/node_modules/lucide-react": { "version": "0.395.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.395.0.tgz", @@ -4336,10 +4440,35 @@ "uuid": "^9.0.1" } }, + "node_modules/@cosmos-kit/core/node_modules/@chain-registry/keplr": { + "version": "1.68.26", + "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.26.tgz", + "integrity": "sha512-J08AYP6i9DGYdnkaHwNekwEu6s2S/Y0wbt7QuL7HTK8geHIB7XEm1VbuS+0/CzLLkVVtCxM+Qmu5zlq9W2x8Dw==", + "dependencies": { + "@chain-registry/types": "^0.45.22", + "@keplr-wallet/cosmos": "0.12.28", + "@keplr-wallet/crypto": "0.12.28", + "semver": "^7.5.0" + } + }, "node_modules/@cosmos-kit/core/node_modules/@chain-registry/types": { - "version": "0.45.41", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", - "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" + "version": "0.45.22", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.22.tgz", + "integrity": "sha512-6q8/n39/oqjaF2tviKrOTgaxzDlXekPi4T96iceMOBF4tcN8jhcVHq9J3FczpRNeRPL4jpa7w82jRaYiX/LYVg==" + }, + "node_modules/@cosmos-kit/core/node_modules/@cosmjs/crypto": { + "version": "0.32.3", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.32.3.tgz", + "integrity": "sha512-niQOWJHUtlJm2GG4F00yGT7sGPKxfUwz+2qQ30uO/E3p58gOusTcH2qjiJNVxb8vScYJhFYFqpm/OA/mVqoUGQ==", + "dependencies": { + "@cosmjs/encoding": "^0.32.3", + "@cosmjs/math": "^0.32.3", + "@cosmjs/utils": "^0.32.3", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.4", + "libsodium-wrappers-sumo": "^0.7.11" + } }, "node_modules/@cosmos-kit/core/node_modules/@cosmjs/encoding": { "version": "0.32.4", @@ -4399,7 +4528,72 @@ "@cosmos-kit/keplr-mobile": "^2.12.2" } }, - "node_modules/@cosmos-kit/keplr-extension": { + "node_modules/@cosmos-kit/keplr/node_modules/@chain-registry/keplr": { + "version": "1.68.26", + "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.26.tgz", + "integrity": "sha512-J08AYP6i9DGYdnkaHwNekwEu6s2S/Y0wbt7QuL7HTK8geHIB7XEm1VbuS+0/CzLLkVVtCxM+Qmu5zlq9W2x8Dw==", + "dependencies": { + "@chain-registry/types": "^0.45.22", + "@keplr-wallet/cosmos": "0.12.28", + "@keplr-wallet/crypto": "0.12.28", + "semver": "^7.5.0" + } + }, + "node_modules/@cosmos-kit/keplr/node_modules/@chain-registry/types": { + "version": "0.45.22", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.22.tgz", + "integrity": "sha512-6q8/n39/oqjaF2tviKrOTgaxzDlXekPi4T96iceMOBF4tcN8jhcVHq9J3FczpRNeRPL4jpa7w82jRaYiX/LYVg==" + }, + "node_modules/@cosmos-kit/keplr/node_modules/@cosmjs/crypto": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.32.4.tgz", + "integrity": "sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==", + "peer": true, + "dependencies": { + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.4", + "libsodium-wrappers-sumo": "^0.7.11" + } + }, + "node_modules/@cosmos-kit/keplr/node_modules/@cosmjs/encoding": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.32.4.tgz", + "integrity": "sha512-tjvaEy6ZGxJchiizzTn7HVRiyTg1i4CObRRaTRPknm5EalE13SV+TCHq38gIDfyUeden4fCuaBVEdBR5+ti7Hw==", + "peer": true, + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmos-kit/keplr/node_modules/@cosmjs/math": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.32.4.tgz", + "integrity": "sha512-++dqq2TJkoB8zsPVYCvrt88oJWsy1vMOuSOKcdlnXuOA/ASheTJuYy4+oZlTQ3Fr8eALDLGGPhJI02W2HyAQaw==", + "peer": true, + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmos-kit/keplr/node_modules/@cosmjs/proto-signing": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.32.4.tgz", + "integrity": "sha512-QdyQDbezvdRI4xxSlyM1rSVBO2st5sqtbEIl3IX03uJ7YiZIQHyv6vaHVf1V4mapusCqguiHJzm4N4gsFdLBbQ==", + "peer": true, + "dependencies": { + "@cosmjs/amino": "^0.32.4", + "@cosmjs/crypto": "^0.32.4", + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "cosmjs-types": "^0.9.0" + } + }, + "node_modules/@cosmos-kit/keplr/node_modules/@cosmos-kit/keplr-extension": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr-extension/-/keplr-extension-2.12.2.tgz", "integrity": "sha512-wYgJdkpM25e7TQLzLtUSb0Wc1Rglfqx/Yo7+7tlh9Ig5b8hTPReBl2RNSGVpQAmb4r/da3Wp+dw/RzF5WB0HTg==", @@ -4414,20 +4608,7 @@ "@cosmjs/proto-signing": ">=0.32.3" } }, - "node_modules/@cosmos-kit/keplr-extension/node_modules/@keplr-wallet/types": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", - "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", - "dependencies": { - "long": "^4.0.0" - } - }, - "node_modules/@cosmos-kit/keplr-extension/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/@cosmos-kit/keplr-mobile": { + "node_modules/@cosmos-kit/keplr/node_modules/@cosmos-kit/keplr-mobile": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/@cosmos-kit/keplr-mobile/-/keplr-mobile-2.12.2.tgz", "integrity": "sha512-EtSa2S7gkX/uO69/26orxVzNCeYA9dDKt3zxA17p7Weh6nAaiwHPJtxTkrGIuMShRDT+rOEuHzmR2CRC+7CHbA==", @@ -4444,7 +4625,7 @@ "@cosmjs/proto-signing": ">=0.32.3" } }, - "node_modules/@cosmos-kit/keplr-mobile/node_modules/@chain-registry/keplr": { + "node_modules/@cosmos-kit/keplr/node_modules/@cosmos-kit/keplr-mobile/node_modules/@chain-registry/keplr": { "version": "1.68.2", "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.2.tgz", "integrity": "sha512-H3rdf/cLx7bNyyKo+1nI9HpLTlLzyeqi0Rmt+ggwtFRC63ZmDaMg/3vPY4rHvu38OdcaOid4Nyfc+7h3EEPW8Q==", @@ -4455,6 +4636,17 @@ "semver": "^7.5.0" } }, + "node_modules/@cosmos-kit/keplr/node_modules/@keplr-wallet/types": { + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.110.tgz", + "integrity": "sha512-Ul67BZDdcw6NreuFoFwy58zdYhoX3sDFIchRtGaTnJmvm6fZIkyjpIuekINPOaJijLUyr4M1mK5MN71nycvN1A==", + "dependencies": { + "@chain-registry/types": "^0.45.1", + "@keplr-wallet/cosmos": "0.12.28", + "@keplr-wallet/crypto": "0.12.28", + "semver": "^7.5.0" + } + }, "node_modules/@cosmos-kit/keplr-mobile/node_modules/@chain-registry/types": { "version": "0.45.41", "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", @@ -4524,14 +4716,81 @@ } }, "node_modules/@cosmos-kit/react-lite/node_modules/@chain-registry/types": { - "version": "0.45.41", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", - "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" + "version": "0.45.22", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.22.tgz", + "integrity": "sha512-6q8/n39/oqjaF2tviKrOTgaxzDlXekPi4T96iceMOBF4tcN8jhcVHq9J3FczpRNeRPL4jpa7w82jRaYiX/LYVg==" + }, + "node_modules/@cosmos-kit/react-lite/node_modules/@cosmjs/crypto": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.32.4.tgz", + "integrity": "sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==", + "peer": true, + "dependencies": { + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.4", + "libsodium-wrappers-sumo": "^0.7.11" + } + }, + "node_modules/@cosmos-kit/react-lite/node_modules/@cosmjs/encoding": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.32.4.tgz", + "integrity": "sha512-tjvaEy6ZGxJchiizzTn7HVRiyTg1i4CObRRaTRPknm5EalE13SV+TCHq38gIDfyUeden4fCuaBVEdBR5+ti7Hw==", + "peer": true, + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmos-kit/react-lite/node_modules/@cosmjs/math": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.32.4.tgz", + "integrity": "sha512-++dqq2TJkoB8zsPVYCvrt88oJWsy1vMOuSOKcdlnXuOA/ASheTJuYy4+oZlTQ3Fr8eALDLGGPhJI02W2HyAQaw==", + "peer": true, + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmos-kit/react-lite/node_modules/@cosmjs/proto-signing": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.32.4.tgz", + "integrity": "sha512-QdyQDbezvdRI4xxSlyM1rSVBO2st5sqtbEIl3IX03uJ7YiZIQHyv6vaHVf1V4mapusCqguiHJzm4N4gsFdLBbQ==", + "peer": true, + "dependencies": { + "@cosmjs/amino": "^0.32.4", + "@cosmjs/crypto": "^0.32.4", + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "cosmjs-types": "^0.9.0" + } + }, + "node_modules/@cosmos-kit/react-lite/node_modules/@dao-dao/cosmiframe": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@dao-dao/cosmiframe/-/cosmiframe-0.1.0.tgz", + "integrity": "sha512-NW4pGt1ctqDfhn/A6RU2vwnFEu3O4aBNnBMrGnw31n+L35drYNEsA9ZB7KZsHmRRlkNx+jSuJSv2Fv0BFBDDJQ==", + "dependencies": { + "uuid": "^9.0.1" + }, + "peerDependencies": { + "@cosmjs/amino": ">= ^0.32", + "@cosmjs/proto-signing": ">= ^0.32" + } + }, + "node_modules/@cosmos-kit/react-lite/node_modules/cosmjs-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.9.0.tgz", + "integrity": "sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==", + "peer": true }, "node_modules/@cosmos-kit/react/node_modules/@chain-registry/types": { - "version": "0.45.41", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", - "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" + "version": "0.45.22", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.22.tgz", + "integrity": "sha512-6q8/n39/oqjaF2tviKrOTgaxzDlXekPi4T96iceMOBF4tcN8jhcVHq9J3FczpRNeRPL4jpa7w82jRaYiX/LYVg==" }, "node_modules/@cosmos-kit/walletconnect": { "version": "2.10.1", @@ -4549,6 +4808,56 @@ "@walletconnect/types": "2.11.0" } }, + "node_modules/@cosmos-kit/walletconnect/node_modules/@cosmjs/crypto": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.32.4.tgz", + "integrity": "sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==", + "dependencies": { + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.4", + "libsodium-wrappers-sumo": "^0.7.11" + } + }, + "node_modules/@cosmos-kit/walletconnect/node_modules/@cosmjs/encoding": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.32.4.tgz", + "integrity": "sha512-tjvaEy6ZGxJchiizzTn7HVRiyTg1i4CObRRaTRPknm5EalE13SV+TCHq38gIDfyUeden4fCuaBVEdBR5+ti7Hw==", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmos-kit/walletconnect/node_modules/@cosmjs/math": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.32.4.tgz", + "integrity": "sha512-++dqq2TJkoB8zsPVYCvrt88oJWsy1vMOuSOKcdlnXuOA/ASheTJuYy4+oZlTQ3Fr8eALDLGGPhJI02W2HyAQaw==", + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmos-kit/walletconnect/node_modules/@cosmjs/proto-signing": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.32.4.tgz", + "integrity": "sha512-QdyQDbezvdRI4xxSlyM1rSVBO2st5sqtbEIl3IX03uJ7YiZIQHyv6vaHVf1V4mapusCqguiHJzm4N4gsFdLBbQ==", + "dependencies": { + "@cosmjs/amino": "^0.32.4", + "@cosmjs/crypto": "^0.32.4", + "@cosmjs/encoding": "^0.32.4", + "@cosmjs/math": "^0.32.4", + "@cosmjs/utils": "^0.32.4", + "cosmjs-types": "^0.9.0" + } + }, + "node_modules/@cosmos-kit/walletconnect/node_modules/cosmjs-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.9.0.tgz", + "integrity": "sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==" + }, "node_modules/@cosmostation/extension-client": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/@cosmostation/extension-client/-/extension-client-0.1.15.tgz", @@ -8287,31 +8596,31 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/provider": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/provider/-/provider-0.12.119.tgz", - "integrity": "sha512-NfA6+HqzUdlCld5wbqMRu6fg3rcAWdXp8WbaujTp6RKyGQj3C5ex4pG81SzLmgq+Idjma/CrFCNqdg7SMwwbpA==", + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/provider/-/provider-0.12.110.tgz", + "integrity": "sha512-5jM86lwGQxEpvKjSBX6kU6tgfEw0M98LEFVoUk0t8GpGDdvJdqbk44Mgr/9i46Bas+aaLzMOn6UdsJNfA27bhg==", "dependencies": { - "@keplr-wallet/router": "0.12.119", - "@keplr-wallet/types": "0.12.119", + "@keplr-wallet/router": "0.12.110", + "@keplr-wallet/types": "0.12.110", "buffer": "^6.0.3", "deepmerge": "^4.2.2", "long": "^4.0.0" } }, "node_modules/@keplr-wallet/provider-extension": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/provider-extension/-/provider-extension-0.12.119.tgz", - "integrity": "sha512-KSErxeSHUTU34f1pfJ/0JA5cwQPPRAw7pw4fyePZwNMnAiw3FuRmYCTgMDTFb+6JIjArHvrbGdOHR45/kP9I0A==", + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/provider-extension/-/provider-extension-0.12.110.tgz", + "integrity": "sha512-5iDwCz4jkd4IwmeicoGRHq/OaFwDzCngYqKrE1NPA3qzjdgmk3s6F6uwv4qUntpe09VnofsUk5oclkw6YE9A2g==", "dependencies": { - "@keplr-wallet/types": "0.12.119", + "@keplr-wallet/types": "0.12.110", "deepmerge": "^4.2.2", "long": "^4.0.0" } }, "node_modules/@keplr-wallet/provider-extension/node_modules/@keplr-wallet/types": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", - "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.110.tgz", + "integrity": "sha512-Ul67BZDdcw6NreuFoFwy58zdYhoX3sDFIchRtGaTnJmvm6fZIkyjpIuekINPOaJijLUyr4M1mK5MN71nycvN1A==", "dependencies": { "long": "^4.0.0" } @@ -8322,9 +8631,9 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/provider/node_modules/@keplr-wallet/types": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", - "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.110.tgz", + "integrity": "sha512-Ul67BZDdcw6NreuFoFwy58zdYhoX3sDFIchRtGaTnJmvm6fZIkyjpIuekINPOaJijLUyr4M1mK5MN71nycvN1A==", "dependencies": { "long": "^4.0.0" } @@ -8335,9 +8644,9 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/router": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/router/-/router-0.12.119.tgz", - "integrity": "sha512-nMsGGBGKr9sX0cgLVKbBKBcaesmrNgcHHshgIL1Ic9oS/13VOnM+aJMsqzS8MBaCfnIuqMorvOgUJVZllFDaSQ==" + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/router/-/router-0.12.110.tgz", + "integrity": "sha512-KIBUlQCnvFxhxFdRVlGO9F/iwwhHWgv+NMTxyXoQlC8bsWc6ts5AhLAtJ2fyDJJthMw+2ulFJ2Rq7ldpuX1Mtg==" }, "node_modules/@keplr-wallet/simple-fetch": { "version": "0.12.28", @@ -8493,12 +8802,12 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/wc-client": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/wc-client/-/wc-client-0.12.119.tgz", - "integrity": "sha512-r6HHGJo5KqL8IPCGlrplbaEyKsCKRPnmYm10Kjua2eTgLpS2c6tACinaJ+jjqfMkYZFd/NEDXSLLrWa90L4LyA==", + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/wc-client/-/wc-client-0.12.110.tgz", + "integrity": "sha512-aGwpOndOCz3/W115VXnuIr+hHhqKAA+I33wgOLKiY+mCyYKfbIYqpvUbrGX+ACbJ1vFBrmYTthATGpoNi7xkYA==", "dependencies": { - "@keplr-wallet/provider": "0.12.119", - "@keplr-wallet/types": "0.12.119", + "@keplr-wallet/provider": "0.12.110", + "@keplr-wallet/types": "0.12.110", "buffer": "^6.0.3", "deepmerge": "^4.2.2", "long": "^3 || ^4 || ^5" @@ -8509,9 +8818,9 @@ } }, "node_modules/@keplr-wallet/wc-client/node_modules/@keplr-wallet/types": { - "version": "0.12.119", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", - "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", + "version": "0.12.110", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.110.tgz", + "integrity": "sha512-Ul67BZDdcw6NreuFoFwy58zdYhoX3sDFIchRtGaTnJmvm6fZIkyjpIuekINPOaJijLUyr4M1mK5MN71nycvN1A==", "dependencies": { "long": "^4.0.0" } @@ -32591,9 +32900,9 @@ } }, "node_modules/jotai": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.9.1.tgz", - "integrity": "sha512-t4Q7FIqQB3N/1art4OcqdlEtPmQ2h4DNIzTFhvt06WE0kCpQ1QoG+1A1IGTaQBi2KdDRsnywj+ojmHHKgw6PDA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.9.0.tgz", + "integrity": "sha512-MioTpMvR78IGfJ+W8EwQj3kwTkb+u0reGnTyg3oJZMWK9rK9v8NBSC9Rhrg9jrrFYA6bGZtzJa96zsuAYF6W3w==", "engines": { "node": ">=12.20.0" }, @@ -33174,6 +33483,41 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz",