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",